diff --git a/Documentation/DocBook/media/Makefile b/Documentation/DocBook/media/Makefile index 362520992ced..9b7e4c557928 100644 --- a/Documentation/DocBook/media/Makefile +++ b/Documentation/DocBook/media/Makefile @@ -300,7 +300,7 @@ $(MEDIA_OBJ_DIR)/media-entities.tmpl: $(MEDIA_OBJ_DIR)/v4l2.xml @( \ for ident in $(IOCTLS) ; do \ entity=`echo $$ident | tr _ -` ; \ - id=`grep "$$ident" $(MEDIA_OBJ_DIR)/vidioc-*.xml | sed -r s,"^.*/(.*).xml.*","\1",` ; \ + id=`grep "$$ident" $(MEDIA_OBJ_DIR)/vidioc-*.xml $(MEDIA_OBJ_DIR)/media-ioc-*.xml | sed -r s,"^.*/(.*).xml.*","\1",` ; \ echo "$$ident\">" \ >>$@ ; \ diff --git a/Documentation/DocBook/media/dvb/audio.xml b/Documentation/DocBook/media/dvb/audio.xml index d64386237207..a7ea56c71a27 100644 --- a/Documentation/DocBook/media/dvb/audio.xml +++ b/Documentation/DocBook/media/dvb/audio.xml @@ -1,12 +1,16 @@ DVB Audio Device The DVB audio device controls the MPEG2 audio decoder of the DVB hardware. It can be accessed through /dev/dvb/adapter0/audio0. Data types and and -ioctl definitions can be accessed by including linux/dvb/video.h in your +ioctl definitions can be accessed by including linux/dvb/audio.h in your application. Please note that some DVB cards don’t have their own MPEG decoder, which results in the omission of the audio and video device. + +These ioctls were also used by V4L2 to control MPEG decoders implemented in V4L2. The use +of these ioctls for that purpose has been made obsolete and proper V4L2 ioctls or controls +have been created to replace that functionality.
Audio Data Types @@ -558,6 +562,8 @@ role="subsection">AUDIO_SELECT_SOURCE role="subsection">AUDIO_SET_MUTE DESCRIPTION +This ioctl is for DVB devices only. To control a V4L2 decoder use the V4L2 +&VIDIOC-DECODER-CMD; with the V4L2_DEC_CMD_START_MUTE_AUDIO flag instead. This ioctl call asks the audio device to mute the stream that is currently being @@ -730,6 +736,8 @@ role="subsection">AUDIO_SET_BYPASS_MODE role="subsection">AUDIO_CHANNEL_SELECT DESCRIPTION +This ioctl is for DVB devices only. To control a V4L2 decoder use the V4L2 +V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK control instead. This ioctl call asks the Audio Device to select the requested channel if possible. @@ -772,6 +780,109 @@ role="subsection">AUDIO_CHANNEL_SELECT &return-value-dvb; +
AUDIO_BILINGUAL_CHANNEL_SELECT +DESCRIPTION + +This ioctl is obsolete. Do not use in new drivers. It has been replaced by +the V4L2 V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK control +for MPEG decoders controlled through V4L2. + +This ioctl call asks the Audio Device to select the requested channel for bilingual streams if possible. + + +SYNOPSIS + + +int ioctl(int fd, int request = + AUDIO_BILINGUAL_CHANNEL_SELECT, audio_channel_select_t); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals AUDIO_BILINGUAL_CHANNEL_SELECT for this + command. + + +audio_channel_select_t +ch + +Select the output format of the audio (mono left/right, + stereo). + + + +&return-value-dvb; + +
AUDIO_GET_PTS +DESCRIPTION + +This ioctl is obsolete. Do not use in new drivers. If you need this functionality, +then please contact the linux-media mailing list (&v4l-ml;). + +This ioctl call asks the Audio Device to return the current PTS timestamp. + + +SYNOPSIS + + +int ioctl(int fd, int request = + AUDIO_GET_PTS, __u64 *pts); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals AUDIO_GET_PTS for this + command. + + +__u64 *pts + + +Returns the 33-bit timestamp as defined in ITU T-REC-H.222.0 / ISO/IEC 13818-1. + + +The PTS should belong to the currently played +frame if possible, but may also be a value close to it +like the PTS of the last decoded frame or the last PTS +extracted by the PES parser. + + +&return-value-dvb; +
AUDIO_GET_STATUS DESCRIPTION diff --git a/Documentation/DocBook/media/dvb/ca.xml b/Documentation/DocBook/media/dvb/ca.xml index 5c4adb44b1c1..85eaf4fe2931 100644 --- a/Documentation/DocBook/media/dvb/ca.xml +++ b/Documentation/DocBook/media/dvb/ca.xml @@ -226,4 +226,357 @@ typedef struct ca_pid {
+ +
CA_RESET +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = CA_RESET); + + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals CA_RESET for this command. + + +&return-value-dvb; +
+ +
CA_GET_CAP +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = CA_GET_CAP, + ca_caps_t *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals CA_GET_CAP for this command. + + +ca_caps_t * + + +Undocumented. + + +&return-value-dvb; +
+ +
CA_GET_SLOT_INFO +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = CA_GET_SLOT_INFO, + ca_slot_info_t *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals CA_GET_SLOT_INFO for this command. + + +ca_slot_info_t * + + +Undocumented. + + +&return-value-dvb; +
+ +
CA_GET_DESCR_INFO +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = CA_GET_DESCR_INFO, + ca_descr_info_t *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals CA_GET_DESCR_INFO for this command. + + +ca_descr_info_t * + + +Undocumented. + + +&return-value-dvb; +
+ +
CA_GET_MSG +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = CA_GET_MSG, + ca_msg_t *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals CA_GET_MSG for this command. + + +ca_msg_t * + + +Undocumented. + + +&return-value-dvb; +
+ +
CA_SEND_MSG +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = CA_SEND_MSG, + ca_msg_t *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals CA_SEND_MSG for this command. + + +ca_msg_t * + + +Undocumented. + + +&return-value-dvb; +
+ +
CA_SET_DESCR +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = CA_SET_DESCR, + ca_descr_t *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals CA_SET_DESCR for this command. + + +ca_descr_t * + + +Undocumented. + + +&return-value-dvb; +
+ +
CA_SET_PID +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = CA_SET_PID, + ca_pid_t *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals CA_SET_PID for this command. + + +ca_pid_t * + + +Undocumented. + + +&return-value-dvb; +
+ diff --git a/Documentation/DocBook/media/dvb/demux.xml b/Documentation/DocBook/media/dvb/demux.xml index 37c17908aa40..86de89cfbd67 100644 --- a/Documentation/DocBook/media/dvb/demux.xml +++ b/Documentation/DocBook/media/dvb/demux.xml @@ -899,4 +899,232 @@ typedef enum { Invalid stc number. - + + +
DMX_GET_PES_PIDS +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = DMX_GET_PES_PIDS, + __u16[5]); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals DMX_GET_PES_PIDS for this command. + + +__u16[5] + + +Undocumented. + + +&return-value-dvb; +
+ +
DMX_GET_CAPS +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = DMX_GET_CAPS, + dmx_caps_t *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals DMX_GET_CAPS for this command. + + +dmx_caps_t * + + +Undocumented. + + +&return-value-dvb; +
+ +
DMX_SET_SOURCE +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = DMX_SET_SOURCE, + dmx_source_t *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals DMX_SET_SOURCE for this command. + + +dmx_source_t * + + +Undocumented. + + +&return-value-dvb; +
+ +
DMX_ADD_PID +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = DMX_ADD_PID, + __u16 *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals DMX_ADD_PID for this command. + + +__u16 * + + +Undocumented. + + +&return-value-dvb; +
+ +
DMX_REMOVE_PID +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = DMX_REMOVE_PID, + __u16 *); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals DMX_REMOVE_PID for this command. + + +__u16 * + + +Undocumented. + + +&return-value-dvb; +
+ + + diff --git a/Documentation/DocBook/media/dvb/dvbapi.xml b/Documentation/DocBook/media/dvb/dvbapi.xml index 2ab6ddcfc4e0..757488b24f4f 100644 --- a/Documentation/DocBook/media/dvb/dvbapi.xml +++ b/Documentation/DocBook/media/dvb/dvbapi.xml @@ -28,7 +28,7 @@ Convergence GmbH - 2009-2011 + 2009-2012 Mauro Carvalho Chehab @@ -84,7 +84,7 @@ Added ISDB-T test originally written by Patrick Boettcher LINUX DVB API -Version 5.2 +Version 5.8 &sub-intro; diff --git a/Documentation/DocBook/media/dvb/dvbproperty.xml b/Documentation/DocBook/media/dvb/dvbproperty.xml index e633c097a8d1..957e3acaae8e 100644 --- a/Documentation/DocBook/media/dvb/dvbproperty.xml +++ b/Documentation/DocBook/media/dvb/dvbproperty.xml @@ -194,6 +194,7 @@ get/set up to 64 properties. The actual meaning of each property is described on APSK_16, APSK_32, DQPSK, + QAM_4_NR, } fe_modulation_t; @@ -265,6 +266,7 @@ typedef enum fe_code_rate { FEC_AUTO, FEC_3_5, FEC_9_10, + FEC_2_5, } fe_code_rate_t; which correspond to error correction rates of 1/2, 2/3, etc., @@ -351,7 +353,7 @@ typedef enum fe_delivery_system { SYS_ISDBC, SYS_ATSC, SYS_ATSCMH, - SYS_DMBTH, + SYS_DTMB, SYS_CMMB, SYS_DAB, SYS_DVBT2, @@ -567,28 +569,33 @@ typedef enum fe_delivery_system { <constant>DTV_ATSCMH_RS_FRAME_MODE</constant> RS frame mode. Possible values are: + typedef enum atscmh_rs_frame_mode { ATSCMH_RSFRAME_PRI_ONLY = 0, ATSCMH_RSFRAME_PRI_SEC = 1, } atscmh_rs_frame_mode_t; +
<constant>DTV_ATSCMH_RS_FRAME_ENSEMBLE</constant> RS frame ensemble. Possible values are: + typedef enum atscmh_rs_frame_ensemble { ATSCMH_RSFRAME_ENS_PRI = 0, ATSCMH_RSFRAME_ENS_SEC = 1, } atscmh_rs_frame_ensemble_t; +
<constant>DTV_ATSCMH_RS_CODE_MODE_PRI</constant> RS code mode (primary). Possible values are: + typedef enum atscmh_rs_code_mode { ATSCMH_RSCODE_211_187 = 0, @@ -596,6 +603,7 @@ typedef enum atscmh_rs_code_mode { ATSCMH_RSCODE_235_187 = 2, } atscmh_rs_code_mode_t; +
<constant>DTV_ATSCMH_RS_CODE_MODE_SEC</constant> @@ -613,23 +621,27 @@ typedef enum atscmh_rs_code_mode { <constant>DTV_ATSCMH_SCCC_BLOCK_MODE</constant> Series Concatenated Convolutional Code Block Mode. Possible values are: + typedef enum atscmh_sccc_block_mode { ATSCMH_SCCC_BLK_SEP = 0, ATSCMH_SCCC_BLK_COMB = 1, } atscmh_sccc_block_mode_t; +
<constant>DTV_ATSCMH_SCCC_CODE_MODE_A</constant> Series Concatenated Convolutional Code Rate. Possible values are: + typedef enum atscmh_sccc_code_mode { ATSCMH_SCCC_CODE_HLF = 0, ATSCMH_SCCC_CODE_QTR = 1, } atscmh_sccc_code_mode_t; +
<constant>DTV_ATSCMH_SCCC_CODE_MODE_B</constant> @@ -725,6 +737,9 @@ typedef enum fe_guard_interval { GUARD_INTERVAL_1_128, GUARD_INTERVAL_19_128, GUARD_INTERVAL_19_256, + GUARD_INTERVAL_PN420, + GUARD_INTERVAL_PN595, + GUARD_INTERVAL_PN945, } fe_guard_interval_t; @@ -733,6 +748,7 @@ typedef enum fe_guard_interval { try to find the correct guard interval (if capable) and will use TMCC to fill in the missing parameters. 2) Intervals 1/128, 19/128 and 19/256 are used only for DVB-T2 at present + 3) DTMB specifies PN420, PN595 and PN945.
<constant>DTV_TRANSMISSION_MODE</constant> @@ -749,6 +765,8 @@ typedef enum fe_transmit_mode { TRANSMISSION_MODE_1K, TRANSMISSION_MODE_16K, TRANSMISSION_MODE_32K, + TRANSMISSION_MODE_C1, + TRANSMISSION_MODE_C3780, } fe_transmit_mode_t; Notes: @@ -760,6 +778,7 @@ typedef enum fe_transmit_mode { use TMCC to fill in the missing parameters. 3) DVB-T specifies 2K and 8K as valid sizes. 4) DVB-T2 specifies 1K, 2K, 4K, 8K, 16K and 32K. + 5) DTMB specifies C1 and C3780.
<constant>DTV_HIERARCHY</constant> @@ -774,17 +793,28 @@ typedef enum fe_hierarchy { } fe_hierarchy_t;
-
- <constant>DTV_ISDBS_TS_ID</constant> - Currently unused. +
+ <constant>DTV_STREAM_ID</constant> + DVB-S2, DVB-T2 and ISDB-S support the transmission of several + streams on a single transport stream. + This property enables the DVB driver to handle substream filtering, + when supported by the hardware. + By default, substream filtering is disabled. + + For DVB-S2 and DVB-T2, the valid substream id range is from 0 to 255. + + For ISDB, the valid substream id range is from 1 to 65535. + + To disable it, you should use the special macro NO_STREAM_ID_FILTER. + + Note: any value outside the id range also disables filtering. +
-
- <constant>DTV_DVBT2_PLP_ID</constant> - DVB-T2 supports Physical Layer Pipes (PLP) to allow transmission of - many data types via a single multiplex. The API will soon support this - at which point this section will be expanded. +
+ <constant>DTV_DVBT2_PLP_ID_LEGACY</constant> + Obsolete, replaced with DTV_STREAM_ID.
-
+
<constant>DTV_ENUM_DELSYS</constant> A Multi standard frontend needs to advertise the delivery systems provided. Applications need to enumerate the provided delivery systems, before using @@ -796,6 +826,29 @@ typedef enum fe_hierarchy { FE_GET_INFO. In the case of a legacy frontend, the result is just the same as with FE_GET_INFO, but in a more structured format
+
+ <constant>DTV_INTERLEAVING</constant> + Interleaving mode + +enum fe_interleaving { + INTERLEAVING_NONE, + INTERLEAVING_AUTO, + INTERLEAVING_240, + INTERLEAVING_720, +}; + +
+
+ <constant>DTV_LNA</constant> + Low-noise amplifier. + Hardware might offer controllable LNA which can be set manually + using that parameter. Usually LNA could be found only from + terrestrial devices if at all. + Possible values: 0, 1, LNA_AUTO + 0, LNA off + 1, LNA on + use the special macro LNA_AUTO to set LNA auto +
Properties used on terrestrial delivery systems @@ -816,6 +869,7 @@ typedef enum fe_hierarchy { DTV_GUARD_INTERVAL DTV_TRANSMISSION_MODE DTV_HIERARCHY + DTV_LNA
@@ -838,7 +892,8 @@ typedef enum fe_hierarchy { DTV_GUARD_INTERVAL DTV_TRANSMISSION_MODE DTV_HIERARCHY - DTV_DVBT2_PLP_ID + DTV_STREAM_ID + DTV_LNA
@@ -925,13 +980,32 @@ typedef enum fe_hierarchy { DTV_ATSCMH_PRC DTV_ATSCMH_RS_FRAME_MODE DTV_ATSCMH_RS_FRAME_ENSEMBLE - DTV_ATSCMH_CODE_MODE_PRI - DTV_ATSCMH_CODE_MODE_SEC + DTV_ATSCMH_RS_CODE_MODE_PRI + DTV_ATSCMH_RS_CODE_MODE_SEC DTV_ATSCMH_SCCC_BLOCK_MODE - DTV_ATSCMH_SCCC_CODE_MODE_A - DTV_ATSCMH_SCCC_CODE_MODE_B - DTV_ATSCMH_SCCC_CODE_MODE_C - DTV_ATSCMH_SCCC_CODE_MODE_D + DTV_ATSCMH_SCCC_CODE_MODE_A + DTV_ATSCMH_SCCC_CODE_MODE_B + DTV_ATSCMH_SCCC_CODE_MODE_C + DTV_ATSCMH_SCCC_CODE_MODE_D + +
+
+ DTMB delivery system + The following parameters are valid for DTMB: + + DTV_API_VERSION + DTV_DELIVERY_SYSTEM + DTV_TUNE + DTV_CLEAR + DTV_FREQUENCY + DTV_MODULATION + DTV_BANDWIDTH_HZ + DTV_INVERSION + DTV_INNER_FEC + DTV_GUARD_INTERVAL + DTV_TRANSMISSION_MODE + DTV_INTERLEAVING + DTV_LNA
@@ -952,6 +1026,7 @@ typedef enum fe_hierarchy { DTV_INVERSION DTV_SYMBOL_RATE DTV_INNER_FEC + DTV_LNA
@@ -966,6 +1041,7 @@ typedef enum fe_hierarchy { DTV_FREQUENCY DTV_MODULATION DTV_INVERSION + DTV_LNA
@@ -999,6 +1075,7 @@ typedef enum fe_hierarchy { DTV_MODULATION DTV_PILOT DTV_ROLLOFF + DTV_STREAM_ID
@@ -1021,7 +1098,7 @@ typedef enum fe_hierarchy { DTV_SYMBOL_RATE DTV_INNER_FEC DTV_VOLTAGE - DTV_ISDBS_TS_ID + DTV_STREAM_ID
diff --git a/Documentation/DocBook/media/dvb/frontend.xml b/Documentation/DocBook/media/dvb/frontend.xml index aeaed59d0f1f..426c2526a454 100644 --- a/Documentation/DocBook/media/dvb/frontend.xml +++ b/Documentation/DocBook/media/dvb/frontend.xml @@ -66,7 +66,7 @@ supported via the new FE_GET_PROPERTY/FE_GET The usage of this field is deprecated, as it doesn't report all supported standards, and will provide an incomplete information for frontends that support multiple delivery systems. -Please use DTV_ENUM_DELSYS instead. +Please use DTV_ENUM_DELSYS instead.
@@ -101,6 +101,7 @@ a specific frontend type. FE_CAN_8VSB = 0x200000, FE_CAN_16VSB = 0x400000, FE_HAS_EXTENDED_CAPS = 0x800000, + FE_CAN_MULTISTREAM = 0x4000000, FE_CAN_TURBO_FEC = 0x8000000, FE_CAN_2G_MODULATION = 0x10000000, FE_NEEDS_BENDING = 0x20000000, @@ -207,19 +208,45 @@ spec. Several functions of the frontend device use the fe_status data type defined by - typedef enum fe_status { - FE_HAS_SIGNAL = 0x01, /⋆ found something above the noise level ⋆/ - FE_HAS_CARRIER = 0x02, /⋆ found a DVB signal ⋆/ - FE_HAS_VITERBI = 0x04, /⋆ FEC is stable ⋆/ - FE_HAS_SYNC = 0x08, /⋆ found sync bytes ⋆/ - FE_HAS_LOCK = 0x10, /⋆ everything's working... ⋆/ - FE_TIMEDOUT = 0x20, /⋆ no lock within the last ~2 seconds ⋆/ - FE_REINIT = 0x40 /⋆ frontend was reinitialized, ⋆/ - } fe_status_t; /⋆ application is recommned to reset ⋆/ +typedef enum fe_status { + FE_HAS_SIGNAL = 0x01, + FE_HAS_CARRIER = 0x02, + FE_HAS_VITERBI = 0x04, + FE_HAS_SYNC = 0x08, + FE_HAS_LOCK = 0x10, + FE_TIMEDOUT = 0x20, + FE_REINIT = 0x40, +} fe_status_t; -to indicate the current state and/or state changes of the frontend hardware. +to indicate the current state and/or state changes of the frontend hardware: + + +FE_HAS_SIGNAL +The frontend has found something above the noise level + +FE_HAS_CARRIER +The frontend has found a DVB signal + +FE_HAS_VITERBI +The frontend FEC code is stable + +FE_HAS_SYNC +Syncronization bytes was found + +FE_HAS_LOCK +The DVB were locked and everything is working + +FE_TIMEDOUT +no lock within the last about 2 seconds + +FE_REINIT +The frontend was reinitialized, application is +recommended to reset DiSEqC, tone and parameters + + +
@@ -238,7 +265,7 @@ and to add newer delivery systems. FE_GET_PROPERTY/FE_SET_PROPERTY instead, in order to be able to support the newer System Delivery like DVB-S2, DVB-T2, DVB-C2, ISDB, etc. -All kinds of parameters are combined as an union in the FrontendParameters structure: +All kinds of parameters are combined as an union in the FrontendParameters structure: struct dvb_frontend_parameters { uint32_t frequency; /⋆ (absolute) frequency in Hz for QAM/OFDM ⋆/ @@ -251,12 +278,13 @@ struct dvb_frontend_parameters { struct dvb_vsb_parameters vsb; } u; }; - + In the case of QPSK frontends the frequency field specifies the intermediate frequency, i.e. the offset which is effectively added to the local oscillator frequency (LOF) of the LNB. The intermediate frequency has to be specified in units of kHz. For QAM and OFDM frontends the frequency specifies the absolute frequency and is given in Hz. +
QPSK parameters For satellite QPSK frontends you have to use the dvb_qpsk_parameters structure: @@ -321,8 +349,8 @@ itself.
frontend code rate The possible values for the fec_inner field used on -struct dvb_qpsk_parameters and -struct dvb_qam_parameters are: +struct dvb_qpsk_parameters and +struct dvb_qam_parameters are: typedef enum fe_code_rate { @@ -347,9 +375,9 @@ detection.
frontend modulation type for QAM, OFDM and VSB For cable and terrestrial frontends, e. g. for -struct dvb_qpsk_parameters, -struct dvb_qam_parameters and -struct dvb_qam_parameters, +struct dvb_qpsk_parameters, +struct dvb_qam_parameters and +struct dvb_qam_parameters, it needs to specify the quadrature modulation mode which can be one of the following: @@ -370,8 +398,8 @@ it needs to specify the quadrature modulation mode which can be one of the follo } fe_modulation_t;
-Finally, there are several more parameters for OFDM: - +
+More OFDM parameters
Number of carriers per channel @@ -427,6 +455,7 @@ typedef enum fe_hierarchy { } fe_hierarchy_t;
+
diff --git a/Documentation/DocBook/media/dvb/intro.xml b/Documentation/DocBook/media/dvb/intro.xml index 170064a3dc8f..2048b53d19b9 100644 --- a/Documentation/DocBook/media/dvb/intro.xml +++ b/Documentation/DocBook/media/dvb/intro.xml @@ -205,7 +205,7 @@ a partial path like: additional include file linux/dvb/version.h exists, which defines the constant DVB_API_VERSION. This document -describes DVB_API_VERSION 5.4. +describes DVB_API_VERSION 5.8.
diff --git a/Documentation/DocBook/media/dvb/kdapi.xml b/Documentation/DocBook/media/dvb/kdapi.xml index 6c67481eaa4b..6c11ec52cbee 100644 --- a/Documentation/DocBook/media/dvb/kdapi.xml +++ b/Documentation/DocBook/media/dvb/kdapi.xml @@ -2,7 +2,7 @@ The kernel demux API defines a driver-internal interface for registering low-level, hardware specific driver to a hardware independent demux layer. It is only of interest for DVB device driver writers. The header file for this API is named demux.h and located in -drivers/media/dvb/dvb-core. +drivers/media/dvb-core. Maintainer note: This section must be reviewed. It is probably out of date. diff --git a/Documentation/DocBook/media/dvb/net.xml b/Documentation/DocBook/media/dvb/net.xml index 67d37e5ce597..a193e86941b5 100644 --- a/Documentation/DocBook/media/dvb/net.xml +++ b/Documentation/DocBook/media/dvb/net.xml @@ -26,4 +26,131 @@ struct dvb_net_if { DVB net Function Calls To be written… + +
NET_ADD_IF +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = NET_ADD_IF, + struct dvb_net_if *if); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals NET_ADD_IF for this command. + + +struct dvb_net_if *if + + +Undocumented. + + +&return-value-dvb; +
+ +
NET_REMOVE_IF +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = NET_REMOVE_IF); + + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals NET_REMOVE_IF for this command. + + +&return-value-dvb; +
+ +
NET_GET_IF +DESCRIPTION + + +This ioctl is undocumented. Documentation is welcome. + + +SYNOPSIS + + +int ioctl(fd, int request = NET_GET_IF, + struct dvb_net_if *if); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals NET_GET_IF for this command. + + +struct dvb_net_if *if + + +Undocumented. + + +&return-value-dvb; +
diff --git a/Documentation/DocBook/media/dvb/video.xml b/Documentation/DocBook/media/dvb/video.xml index 25fb823226b4..3ea1ca7e785e 100644 --- a/Documentation/DocBook/media/dvb/video.xml +++ b/Documentation/DocBook/media/dvb/video.xml @@ -15,6 +15,10 @@ the audio and video device as well as the video4linux device. The ioctls that deal with SPUs (sub picture units) and navigation packets are only supported on some MPEG decoders made for DVD playback. + +These ioctls were also used by V4L2 to control MPEG decoders implemented in V4L2. The use +of these ioctls for that purpose has been made obsolete and proper V4L2 ioctls or controls +have been created to replace that functionality.
Video Data Types @@ -55,7 +59,7 @@ typedef enum {
-video stream source +video_stream_source_t The video stream source is set through the VIDEO_SELECT_SOURCE call and can take the following values, depending on whether we are replaying from an internal (demuxer) or external (user write) source. @@ -76,7 +80,7 @@ call.
-video play state +video_play_state_t The following values can be returned by the VIDEO_GET_STATUS call representing the state of video playback. @@ -90,9 +94,9 @@ typedef enum {
+struct video_command The structure must be zeroed before use by the application This ensures it can be extended safely in the future. -struct video-command struct video_command { __u32 cmd; @@ -121,7 +125,7 @@ struct video_command {
-struct video_size-t +video_size_t typedef struct { int w; @@ -217,7 +221,7 @@ bits set according to the hardwares capabilities.
-video system +video_system_t A call to VIDEO_SET_SYSTEM sets the desired video system for TV output. The following system types can be set: @@ -263,7 +267,7 @@ call expects the following format for that information:
-video SPU +struct video_spu Calling VIDEO_SET_SPU deactivates or activates SPU decoding, according to the following format: @@ -277,12 +281,12 @@ following format:
-video SPU palette +struct video_spu_palette The following structure is used to set the SPU palette by calling VIDEO_SPU_PALETTE: typedef - struct video_spu_palette{ + struct video_spu_palette { int length; uint8_t ⋆palette; } video_spu_palette_t; @@ -290,13 +294,13 @@ following format:
-video NAVI pack +struct video_navi_pack In order to get the navigational data the following structure has to be passed to the ioctl VIDEO_GET_NAVI: typedef - struct video_navi_pack{ + struct video_navi_pack { int length; /⋆ 0 ... 1024 ⋆/ uint8_t data[1024]; } video_navi_pack_t; @@ -305,7 +309,7 @@ VIDEO_GET_NAVI:
-video attributes +video_attributes_t The following attributes can be set by a call to VIDEO_SET_ATTRIBUTES: @@ -541,6 +545,8 @@ VIDEO_GET_NAVI: role="subsection">VIDEO_STOP DESCRIPTION +This ioctl is for DVB devices only. To control a V4L2 decoder use the V4L2 +&VIDIOC-DECODER-CMD; instead. This ioctl call asks the Video Device to stop playing the current stream. @@ -598,6 +604,8 @@ role="subsection">VIDEO_STOP role="subsection">VIDEO_PLAY DESCRIPTION +This ioctl is for DVB devices only. To control a V4L2 decoder use the V4L2 +&VIDIOC-DECODER-CMD; instead. This ioctl call asks the Video Device to start playing a video stream from the @@ -634,6 +642,8 @@ role="subsection">VIDEO_PLAY role="subsection">VIDEO_FREEZE DESCRIPTION +This ioctl is for DVB devices only. To control a V4L2 decoder use the V4L2 +&VIDIOC-DECODER-CMD; instead. This ioctl call suspends the live video stream being played. Decoding @@ -674,6 +684,8 @@ role="subsection">VIDEO_FREEZE role="subsection">VIDEO_CONTINUE DESCRIPTION +This ioctl is for DVB devices only. To control a V4L2 decoder use the V4L2 +&VIDIOC-DECODER-CMD; instead. This ioctl call restarts decoding and playing processes of the video stream @@ -710,6 +722,9 @@ role="subsection">VIDEO_CONTINUE role="subsection">VIDEO_SELECT_SOURCE DESCRIPTION +This ioctl is for DVB devices only. This ioctl was also supported by the +V4L2 ivtv driver, but that has been replaced by the ivtv-specific +IVTV_IOC_PASSTHROUGH_MODE ioctl. This ioctl call informs the video device which source shall be used for the input @@ -845,10 +860,160 @@ role="subsection">VIDEO_GET_STATUS &return-value-dvb; +
VIDEO_GET_FRAME_COUNT +DESCRIPTION + +This ioctl is obsolete. Do not use in new drivers. For V4L2 decoders this +ioctl has been replaced by the V4L2_CID_MPEG_VIDEO_DEC_FRAME control. + +This ioctl call asks the Video Device to return the number of displayed frames +since the decoder was started. + + +SYNOPSIS + + +int ioctl(int fd, int request = + VIDEO_GET_FRAME_COUNT, __u64 *pts); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals VIDEO_GET_FRAME_COUNT for this + command. + + +__u64 *pts + + +Returns the number of frames displayed since the decoder was started. + + + +&return-value-dvb; + +
VIDEO_GET_PTS +DESCRIPTION + +This ioctl is obsolete. Do not use in new drivers. For V4L2 decoders this +ioctl has been replaced by the V4L2_CID_MPEG_VIDEO_DEC_PTS control. + +This ioctl call asks the Video Device to return the current PTS timestamp. + + +SYNOPSIS + + +int ioctl(int fd, int request = + VIDEO_GET_PTS, __u64 *pts); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals VIDEO_GET_PTS for this + command. + + +__u64 *pts + + +Returns the 33-bit timestamp as defined in ITU T-REC-H.222.0 / ISO/IEC 13818-1. + + +The PTS should belong to the currently played +frame if possible, but may also be a value close to it +like the PTS of the last decoded frame or the last PTS +extracted by the PES parser. + + +&return-value-dvb; + +
VIDEO_GET_FRAME_RATE +DESCRIPTION + + +This ioctl call asks the Video Device to return the current framerate. + + +SYNOPSIS + + +int ioctl(int fd, int request = + VIDEO_GET_FRAME_RATE, unsigned int *rate); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals VIDEO_GET_FRAME_RATE for this + command. + + +unsigned int *rate + + +Returns the framerate in number of frames per 1000 seconds. + + + +&return-value-dvb; +
VIDEO_GET_EVENT DESCRIPTION +This ioctl is for DVB devices only. To get events from a V4L2 decoder use the V4L2 +&VIDIOC-DQEVENT; ioctl instead. This ioctl call returns an event of type video_event if available. If an event is @@ -914,6 +1079,152 @@ role="subsection">VIDEO_GET_EVENT +
VIDEO_COMMAND +DESCRIPTION + +This ioctl is obsolete. Do not use in new drivers. For V4L2 decoders this +ioctl has been replaced by the &VIDIOC-DECODER-CMD; ioctl. + +This ioctl commands the decoder. The video_command struct +is a subset of the v4l2_decoder_cmd struct, so refer to the +&VIDIOC-DECODER-CMD; documentation for more information. + + +SYNOPSIS + + +int ioctl(int fd, int request = + VIDEO_COMMAND, struct video_command *cmd); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals VIDEO_COMMAND for this + command. + + +struct video_command *cmd + + +Commands the decoder. + + + +&return-value-dvb; + +
VIDEO_TRY_COMMAND +DESCRIPTION + +This ioctl is obsolete. Do not use in new drivers. For V4L2 decoders this +ioctl has been replaced by the &VIDIOC-TRY-DECODER-CMD; ioctl. + +This ioctl tries a decoder command. The video_command struct +is a subset of the v4l2_decoder_cmd struct, so refer to the +&VIDIOC-TRY-DECODER-CMD; documentation for more information. + + +SYNOPSIS + + +int ioctl(int fd, int request = + VIDEO_TRY_COMMAND, struct video_command *cmd); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals VIDEO_TRY_COMMAND for this + command. + + +struct video_command *cmd + + +Try a decoder command. + + + +&return-value-dvb; + +
VIDEO_GET_SIZE +DESCRIPTION + + +This ioctl returns the size and aspect ratio. + + +SYNOPSIS + + +int ioctl(int fd, int request = + VIDEO_GET_SIZE, video_size_t *size); + + +PARAMETERS + + +int fd + +File descriptor returned by a previous call to open(). + + +int request + +Equals VIDEO_GET_SIZE for this + command. + + +video_size_t *size + + +Returns the size and aspect ratio. + + + +&return-value-dvb; +
VIDEO_SET_DISPLAY_FORMAT DESCRIPTION diff --git a/Documentation/DocBook/media/v4l/biblio.xml b/Documentation/DocBook/media/v4l/biblio.xml index 1078e45f189f..d2eb79e41a01 100644 --- a/Documentation/DocBook/media/v4l/biblio.xml +++ b/Documentation/DocBook/media/v4l/biblio.xml @@ -178,23 +178,23 @@ Signal - NTSC for Studio Applications" 1125-Line High-Definition Production" - - EN 50067 + + IEC 62106 - European Committee for Electrotechnical Standardization -(http://www.cenelec.eu) + International Electrotechnical Commission +(http://www.iec.ch) Specification of the radio data system (RDS) for VHF/FM sound broadcasting in the frequency range from 87,5 to 108,0 MHz - NRSC-4 + NRSC-4-B National Radio Systems Committee (http://www.nrscstandards.org) - NRSC-4: United States RBDS Standard + NRSC-4-B: United States RBDS Standard @@ -226,4 +226,44 @@ in the frequency range from 87,5 to 108,0 MHz VESA and Industry Standards and Guidelines for Computer Display Monitor Timing (DMT) + + EDID + + Video Electronics Standards Association +(http://www.vesa.org) + + VESA Enhanced Extended Display Identification Data Standard + Release A, Revision 2 + + + + HDCP + + Digital Content Protection LLC +(http://www.digital-cp.com) + + High-bandwidth Digital Content Protection System + Revision 1.3 + + + + HDMI + + HDMI Licensing LLC +(http://www.hdmi.org) + + High-Definition Multimedia Interface + Specification Version 1.4a + + + + DP + + Video Electronics Standards Association +(http://www.vesa.org) + + VESA DisplayPort Standard + Version 1, Revision 2 + + diff --git a/Documentation/DocBook/media/v4l/common.xml b/Documentation/DocBook/media/v4l/common.xml index b91d25313b63..73c6847436c9 100644 --- a/Documentation/DocBook/media/v4l/common.xml +++ b/Documentation/DocBook/media/v4l/common.xml @@ -564,7 +564,7 @@ automatically. To query and select the standard used by the current video input or output applications call the &VIDIOC-G-STD; and &VIDIOC-S-STD; ioctl, respectively. The received -standard can be sensed with the &VIDIOC-QUERYSTD; ioctl. Note parameter of all these ioctls is a pointer to a &v4l2-std-id; type (a standard set), not an index into the standard enumeration. +standard can be sensed with the &VIDIOC-QUERYSTD; ioctl. Note that the parameter of all these ioctls is a pointer to a &v4l2-std-id; type (a standard set), not an index into the standard enumeration. An alternative to the current scheme is to use pointers to indices as arguments of VIDIOC_G_STD and VIDIOC_S_STD, the &v4l2-input; and @@ -588,30 +588,28 @@ switch to a standard by &v4l2-std-id;. Drivers must implement all video standard ioctls when the device has one or more video inputs or outputs. - Special rules apply to USB cameras where the notion of video -standards makes little sense. More generally any capture device, -output devices accordingly, which is + Special rules apply to devices such as USB cameras where the notion of video +standards makes little sense. More generally for any capture or output device +which is: incapable of capturing fields or frames at the nominal rate of the video standard, or - where timestamps refer -to the instant the field or frame was received by the driver, not the -capture time, or - - - where sequence numbers -refer to the frames received by the driver, not the captured -frames. + that does not support the video standard formats at all. Here the driver shall set the std field of &v4l2-input; and &v4l2-output; -to zero, the VIDIOC_G_STD, +to zero and the VIDIOC_G_STD, VIDIOC_S_STD, VIDIOC_QUERYSTD and VIDIOC_ENUMSTD ioctls shall return the -&EINVAL;. +&ENOTTY;. + See for a rationale. + Applications can make use of the and + flags to determine whether the video standard ioctls +are available for the device. +&ENOTTY;. See for a rationale. Probably even USB cameras follow some well known video standard. It might have been better to explicitly indicate elsewhere if a device cannot live @@ -626,9 +624,9 @@ up to normal expectations, instead of this exception. &v4l2-standard; standard; if (-1 == ioctl (fd, &VIDIOC-G-STD;, &std_id)) { - /* Note when VIDIOC_ENUMSTD always returns EINVAL this + /* Note when VIDIOC_ENUMSTD always returns ENOTTY this is no video device or it falls under the USB exception, - and VIDIOC_G_STD returning EINVAL is no error. */ + and VIDIOC_G_STD returning ENOTTY is no error. */ perror ("VIDIOC_G_STD"); exit (EXIT_FAILURE); diff --git a/Documentation/DocBook/media/v4l/compat.xml b/Documentation/DocBook/media/v4l/compat.xml index faa0fd14666a..c6ae4c9d0e0c 100644 --- a/Documentation/DocBook/media/v4l/compat.xml +++ b/Documentation/DocBook/media/v4l/compat.xml @@ -1476,7 +1476,7 @@ follows. V4L2_BUF_TYPE_PRIVATE_BASE - V4L2_BUF_TYPE_PRIVATE + V4L2_BUF_TYPE_PRIVATE (but this is deprecated) @@ -2468,21 +2468,9 @@ that used it. It was originally scheduled for removal in 2.6.35. reserved2 and removed V4L2_BUF_FLAG_INPUT. - -
- -
- V4L2 in Linux 3.6 - Added V4L2_CAP_VIDEO_M2M and V4L2_CAP_VIDEO_M2M_MPLANE capabilities. - -
- -
- V4L2 in Linux 3.6 - Added support for frequency band enumerations: &VIDIOC-ENUM-FREQ-BANDS;. @@ -2567,29 +2555,6 @@ and may change in the future. Video Output Overlay (OSD) Interface, . - - V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY, - &v4l2-buf-type;, . - - - V4L2_CAP_VIDEO_OUTPUT_OVERLAY, -&VIDIOC-QUERYCAP; ioctl, . - - - &VIDIOC-ENUM-FRAMESIZES; and -&VIDIOC-ENUM-FRAMEINTERVALS; ioctls. - - - &VIDIOC-G-ENC-INDEX; ioctl. - - - &VIDIOC-ENCODER-CMD; and &VIDIOC-TRY-ENCODER-CMD; -ioctls. - - - &VIDIOC-DECODER-CMD; and &VIDIOC-TRY-DECODER-CMD; -ioctls. - &VIDIOC-DBG-G-REGISTER; and &VIDIOC-DBG-S-REGISTER; ioctls. @@ -2614,10 +2579,6 @@ ioctls. Sub-device selection API: &VIDIOC-SUBDEV-G-SELECTION; and &VIDIOC-SUBDEV-S-SELECTION; ioctls. - - - V4L2_CID_AUTO_FOCUS_AREA control. - Support for frequency band enumeration: &VIDIOC-ENUM-FREQ-BANDS; ioctl. diff --git a/Documentation/DocBook/media/v4l/controls.xml b/Documentation/DocBook/media/v4l/controls.xml index b0964fb4e834..272a5f718509 100644 --- a/Documentation/DocBook/media/v4l/controls.xml +++ b/Documentation/DocBook/media/v4l/controls.xml @@ -3505,7 +3505,7 @@ This encodes up to 31 pre-defined programme types. Sets the Programme Service name (PS_NAME) for transmission. It is intended for static display on a receiver. It is the primary aid to listeners in programme service -identification and selection. In Annex E of , the RDS specification, +identification and selection. In Annex E of , the RDS specification, there is a full description of the correct character encoding for Programme Service name strings. Also from RDS specification, PS is usually a single eight character text. However, it is also possible to find receivers which can scroll strings sized as 8 x N characters. So, this control must be configured @@ -3519,7 +3519,7 @@ with steps of 8 characters. The result is it must always contain a string with s what is being broadcasted. RDS Radio Text can be applied when broadcaster wishes to transmit longer PS names, programme-related information or any other text. In these cases, RadioText should be used in addition to V4L2_CID_RDS_TX_PS_NAME. The encoding for Radio Text strings is also fully described -in Annex E of . The length of Radio Text strings depends on which RDS Block is being +in Annex E of . The length of Radio Text strings depends on which RDS Block is being used to transmit it, either 32 (2A block) or 64 (2B block). However, it is also possible to find receivers which can scroll strings sized as 32 x N or 64 x N characters. So, this control must be configured with steps of 32 or 64 characters. The result is it must always contain a string with size multiple of 32 or 64. @@ -3650,7 +3650,7 @@ manually or automatically if set to zero. Unit, range and step are driver-specif For more details about RDS specification, refer to - document, from CENELEC. + document, from CENELEC.
@@ -3717,232 +3717,231 @@ interface and may change in the future. use case involving camera or individually. + + + Flash Control IDs + + + + + + + + + + + ID + Type + Description + + + + + + V4L2_CID_FLASH_CLASS + class + + + The FLASH class descriptor. + + + V4L2_CID_FLASH_LED_MODE + menu + + + Defines the mode of the flash LED, + the high-power white LED attached to the flash controller. + Setting this control may not be possible in presence of + some faults. See V4L2_CID_FLASH_FAULT. + + + + + + V4L2_FLASH_LED_MODE_NONE + Off. + + + V4L2_FLASH_LED_MODE_FLASH + Flash mode. + + + V4L2_FLASH_LED_MODE_TORCH + Torch mode. See V4L2_CID_FLASH_TORCH_INTENSITY. + + + + + + V4L2_CID_FLASH_STROBE_SOURCE + menu + + Defines the source of the flash LED + strobe. + + + + + + V4L2_FLASH_STROBE_SOURCE_SOFTWARE + The flash strobe is triggered by using + the V4L2_CID_FLASH_STROBE control. + + + V4L2_FLASH_STROBE_SOURCE_EXTERNAL + The flash strobe is triggered by an + external source. Typically this is a sensor, + which makes it possible to synchronises the + flash strobe start to exposure start. + + + + + + V4L2_CID_FLASH_STROBE + button + + + Strobe flash. Valid when + V4L2_CID_FLASH_LED_MODE is set to + V4L2_FLASH_LED_MODE_FLASH and V4L2_CID_FLASH_STROBE_SOURCE + is set to V4L2_FLASH_STROBE_SOURCE_SOFTWARE. Setting this + control may not be possible in presence of some faults. + See V4L2_CID_FLASH_FAULT. + + + V4L2_CID_FLASH_STROBE_STOP + button + + Stop flash strobe immediately. + + + V4L2_CID_FLASH_STROBE_STATUS + boolean + + + Strobe status: whether the flash + is strobing at the moment or not. This is a read-only + control. + + + V4L2_CID_FLASH_TIMEOUT + integer + + + Hardware timeout for flash. The + flash strobe is stopped after this period of time has + passed from the start of the strobe. + + + V4L2_CID_FLASH_INTENSITY + integer + + + Intensity of the flash strobe when + the flash LED is in flash mode + (V4L2_FLASH_LED_MODE_FLASH). The unit should be milliamps + (mA) if possible. + + + V4L2_CID_FLASH_TORCH_INTENSITY + integer + + + Intensity of the flash LED in + torch mode (V4L2_FLASH_LED_MODE_TORCH). The unit should be + milliamps (mA) if possible. Setting this control may not + be possible in presence of some faults. See + V4L2_CID_FLASH_FAULT. + + + V4L2_CID_FLASH_INDICATOR_INTENSITY + integer + + + Intensity of the indicator LED. + The indicator LED may be fully independent of the flash + LED. The unit should be microamps (uA) if possible. + + + V4L2_CID_FLASH_FAULT + bitmask + + + Faults related to the flash. The + faults tell about specific problems in the flash chip + itself or the LEDs attached to it. Faults may prevent + further use of some of the flash controls. In particular, + V4L2_CID_FLASH_LED_MODE is set to V4L2_FLASH_LED_MODE_NONE + if the fault affects the flash LED. Exactly which faults + have such an effect is chip dependent. Reading the faults + resets the control and returns the chip to a usable state + if possible. + + + + + + V4L2_FLASH_FAULT_OVER_VOLTAGE + Flash controller voltage to the flash LED + has exceeded the limit specific to the flash + controller. + + + V4L2_FLASH_FAULT_TIMEOUT + The flash strobe was still on when + the timeout set by the user --- + V4L2_CID_FLASH_TIMEOUT control --- has expired. + Not all flash controllers may set this in all + such conditions. + + + V4L2_FLASH_FAULT_OVER_TEMPERATURE + The flash controller has overheated. + + + V4L2_FLASH_FAULT_SHORT_CIRCUIT + The short circuit protection of the flash + controller has been triggered. + + + V4L2_FLASH_FAULT_OVER_CURRENT + Current in the LED power supply has exceeded the limit + specific to the flash controller. + + + V4L2_FLASH_FAULT_INDICATOR + The flash controller has detected a short or open + circuit condition on the indicator LED. + + + + + + V4L2_CID_FLASH_CHARGE + boolean + + Enable or disable charging of the xenon + flash capacitor. + + + V4L2_CID_FLASH_READY + boolean + + + Is the flash ready to strobe? + Xenon flashes require their capacitors charged before + strobing. LED flashes often require a cooldown period + after strobe during which another strobe will not be + possible. This is a read-only control. + + + + +
-
- - - Flash Control IDs - - - - - - - - - - - ID - Type - Description - - - - - - V4L2_CID_FLASH_CLASS - class - - - The FLASH class descriptor. - - - V4L2_CID_FLASH_LED_MODE - menu - - - Defines the mode of the flash LED, - the high-power white LED attached to the flash controller. - Setting this control may not be possible in presence of - some faults. See V4L2_CID_FLASH_FAULT. - - - - - - V4L2_FLASH_LED_MODE_NONE - Off. - - - V4L2_FLASH_LED_MODE_FLASH - Flash mode. - - - V4L2_FLASH_LED_MODE_TORCH - Torch mode. See V4L2_CID_FLASH_TORCH_INTENSITY. - - - - - - V4L2_CID_FLASH_STROBE_SOURCE - menu - - Defines the source of the flash LED - strobe. - - - - - - V4L2_FLASH_STROBE_SOURCE_SOFTWARE - The flash strobe is triggered by using - the V4L2_CID_FLASH_STROBE control. - - - V4L2_FLASH_STROBE_SOURCE_EXTERNAL - The flash strobe is triggered by an - external source. Typically this is a sensor, - which makes it possible to synchronises the - flash strobe start to exposure start. - - - - - - V4L2_CID_FLASH_STROBE - button - - - Strobe flash. Valid when - V4L2_CID_FLASH_LED_MODE is set to - V4L2_FLASH_LED_MODE_FLASH and V4L2_CID_FLASH_STROBE_SOURCE - is set to V4L2_FLASH_STROBE_SOURCE_SOFTWARE. Setting this - control may not be possible in presence of some faults. - See V4L2_CID_FLASH_FAULT. - - - V4L2_CID_FLASH_STROBE_STOP - button - - Stop flash strobe immediately. - - - V4L2_CID_FLASH_STROBE_STATUS - boolean - - - Strobe status: whether the flash - is strobing at the moment or not. This is a read-only - control. - - - V4L2_CID_FLASH_TIMEOUT - integer - - - Hardware timeout for flash. The - flash strobe is stopped after this period of time has - passed from the start of the strobe. - - - V4L2_CID_FLASH_INTENSITY - integer - - - Intensity of the flash strobe when - the flash LED is in flash mode - (V4L2_FLASH_LED_MODE_FLASH). The unit should be milliamps - (mA) if possible. - - - V4L2_CID_FLASH_TORCH_INTENSITY - integer - - - Intensity of the flash LED in - torch mode (V4L2_FLASH_LED_MODE_TORCH). The unit should be - milliamps (mA) if possible. Setting this control may not - be possible in presence of some faults. See - V4L2_CID_FLASH_FAULT. - - - V4L2_CID_FLASH_INDICATOR_INTENSITY - integer - - - Intensity of the indicator LED. - The indicator LED may be fully independent of the flash - LED. The unit should be microamps (uA) if possible. - - - V4L2_CID_FLASH_FAULT - bitmask - - - Faults related to the flash. The - faults tell about specific problems in the flash chip - itself or the LEDs attached to it. Faults may prevent - further use of some of the flash controls. In particular, - V4L2_CID_FLASH_LED_MODE is set to V4L2_FLASH_LED_MODE_NONE - if the fault affects the flash LED. Exactly which faults - have such an effect is chip dependent. Reading the faults - resets the control and returns the chip to a usable state - if possible. - - - - - - V4L2_FLASH_FAULT_OVER_VOLTAGE - Flash controller voltage to the flash LED - has exceeded the limit specific to the flash - controller. - - - V4L2_FLASH_FAULT_TIMEOUT - The flash strobe was still on when - the timeout set by the user --- - V4L2_CID_FLASH_TIMEOUT control --- has expired. - Not all flash controllers may set this in all - such conditions. - - - V4L2_FLASH_FAULT_OVER_TEMPERATURE - The flash controller has overheated. - - - V4L2_FLASH_FAULT_SHORT_CIRCUIT - The short circuit protection of the flash - controller has been triggered. - - - V4L2_FLASH_FAULT_OVER_CURRENT - Current in the LED power supply has exceeded the limit - specific to the flash controller. - - - V4L2_FLASH_FAULT_INDICATOR - The flash controller has detected a short or open - circuit condition on the indicator LED. - - - - - - V4L2_CID_FLASH_CHARGE - boolean - - Enable or disable charging of the xenon - flash capacitor. - - - V4L2_CID_FLASH_READY - boolean - - - Is the flash ready to strobe? - Xenon flashes require their capacitors charged before - strobing. LED flashes often require a cooldown period - after strobe during which another strobe will not be - possible. This is a read-only control. - - - - -
@@ -4274,4 +4273,165 @@ interface and may change in the future.
+ +
+ Digital Video Control Reference + + + Experimental + + This is an experimental interface and may + change in the future. + + + + The Digital Video control class is intended to control receivers + and transmitters for VGA, + DVI + (Digital Visual Interface), HDMI () and DisplayPort (). + These controls are generally expected to be private to the receiver or transmitter + subdevice that implements them, so they are only exposed on the + /dev/v4l-subdev* device node. + + + Note that these devices can have multiple input or output pads which are + hooked up to e.g. HDMI connectors. Even though the subdevice will receive or + transmit video from/to only one of those pads, the other pads can still be + active when it comes to EDID (Extended Display Identification Data, + ) and HDCP (High-bandwidth Digital Content + Protection System, ) processing, allowing the device + to do the fairly slow EDID/HDCP handling in advance. This allows for quick + switching between connectors. + + These pads appear in several of the controls in this section as + bitmasks, one bit for each pad. Bit 0 corresponds to pad 0, bit 1 to pad 1, + etc. The maximum value of the control is the set of valid pads. + + + Digital Video Control IDs + + + + + + + + + + + ID + Type + Description + + + + + + V4L2_CID_DV_CLASS + class + + + The Digital Video class descriptor. + + + V4L2_CID_DV_TX_HOTPLUG + bitmask + + + Many connectors have a hotplug pin which is high + if EDID information is available from the source. This control shows the + state of the hotplug pin as seen by the transmitter. + Each bit corresponds to an output pad on the transmitter. If an output pad + does not have an associated hotplug pin, then the bit for that pad will be 0. + This read-only control is applicable to DVI-D, HDMI and DisplayPort connectors. + + + + V4L2_CID_DV_TX_RXSENSE + bitmask + + + Rx Sense is the detection of pull-ups on the TMDS + clock lines. This normally means that the sink has left/entered standby (i.e. + the transmitter can sense that the receiver is ready to receive video). + Each bit corresponds to an output pad on the transmitter. If an output pad + does not have an associated Rx Sense, then the bit for that pad will be 0. + This read-only control is applicable to DVI-D and HDMI devices. + + + + V4L2_CID_DV_TX_EDID_PRESENT + bitmask + + + When the transmitter sees the hotplug signal from the + receiver it will attempt to read the EDID. If set, then the transmitter has read + at least the first block (= 128 bytes). + Each bit corresponds to an output pad on the transmitter. If an output pad + does not support EDIDs, then the bit for that pad will be 0. + This read-only control is applicable to VGA, DVI-A/D, HDMI and DisplayPort connectors. + + + + V4L2_CID_DV_TX_MODE + enum v4l2_dv_tx_mode + + + HDMI transmitters can transmit in DVI-D mode (just video) + or in HDMI mode (video + audio + auxiliary data). This control selects which mode + to use: V4L2_DV_TX_MODE_DVI_D or V4L2_DV_TX_MODE_HDMI. + This control is applicable to HDMI connectors. + + + + V4L2_CID_DV_TX_RGB_RANGE + enum v4l2_dv_rgb_range + + + Select the quantization range for RGB output. V4L2_DV_RANGE_AUTO + follows the RGB quantization range specified in the standard for the video interface + (ie. for HDMI). V4L2_DV_RANGE_LIMITED and V4L2_DV_RANGE_FULL override the standard + to be compatible with sinks that have not implemented the standard correctly + (unfortunately quite common for HDMI and DVI-D). Full range allows all possible values to be + used whereas limited range sets the range to (16 << (N-8)) - (235 << (N-8)) + where N is the number of bits per component. + This control is applicable to VGA, DVI-A/D, HDMI and DisplayPort connectors. + + + + V4L2_CID_DV_RX_POWER_PRESENT + bitmask + + + Detects whether the receiver receives power from the source + (e.g. HDMI carries 5V on one of the pins). This is often used to power an eeprom + which contains EDID information, such that the source can read the EDID even if + the sink is in standby/power off. + Each bit corresponds to an input pad on the transmitter. If an input pad + cannot detect whether power is present, then the bit for that pad will be 0. + This read-only control is applicable to DVI-D, HDMI and DisplayPort connectors. + + + + V4L2_CID_DV_RX_RGB_RANGE + enum v4l2_dv_rgb_range + + + Select the quantization range for RGB input. V4L2_DV_RANGE_AUTO + follows the RGB quantization range specified in the standard for the video interface + (ie. for HDMI). V4L2_DV_RANGE_LIMITED and V4L2_DV_RANGE_FULL override the standard + to be compatible with sources that have not implemented the standard correctly + (unfortunately quite common for HDMI and DVI-D). Full range allows all possible values to be + used whereas limited range sets the range to (16 << (N-8)) - (235 << (N-8)) + where N is the number of bits per component. + This control is applicable to VGA, DVI-A/D, HDMI and DisplayPort connectors. + + + + + +
+ +
diff --git a/Documentation/DocBook/media/v4l/dev-osd.xml b/Documentation/DocBook/media/v4l/dev-osd.xml index 479d9433869a..dd91d6134e8c 100644 --- a/Documentation/DocBook/media/v4l/dev-osd.xml +++ b/Documentation/DocBook/media/v4l/dev-osd.xml @@ -1,13 +1,6 @@ Video Output Overlay Interface Also known as On-Screen Display (OSD) - - Experimental - - This is an experimental -interface and may change in the future. - - Some video output devices can overlay a framebuffer image onto the outgoing video signal. Applications can set up such an overlay using this interface, which borrows structures and ioctls of the - For more information see the core RDS standard + For more information see the core RDS standard and the RBDS standard . Note that the RBDS standard as is used in the USA is almost identical diff --git a/Documentation/DocBook/media/v4l/dev-subdev.xml b/Documentation/DocBook/media/v4l/dev-subdev.xml index a3d9dd093268..d15aaf83f56f 100644 --- a/Documentation/DocBook/media/v4l/dev-subdev.xml +++ b/Documentation/DocBook/media/v4l/dev-subdev.xml @@ -374,29 +374,29 @@ rectangle --- if it is supported by the hardware. - Sink pad format. The user configures the sink pad + Sink pad format. The user configures the sink pad format. This format defines the parameters of the image the - entity receives through the pad for further processing. + entity receives through the pad for further processing. - Sink pad actual crop selection. The sink pad crop - defines the crop performed to the sink pad format. + Sink pad actual crop selection. The sink pad crop + defines the crop performed to the sink pad format. - Sink pad actual compose selection. The size of the + Sink pad actual compose selection. The size of the sink pad compose rectangle defines the scaling ratio compared to the size of the sink pad crop rectangle. The location of the compose rectangle specifies the location of the actual sink compose rectangle in the sink compose bounds - rectangle. + rectangle. - Source pad actual crop selection. Crop on the source + Source pad actual crop selection. Crop on the source pad defines crop performed to the image in the sink compose - bounds rectangle. + bounds rectangle. - Source pad format. The source pad format defines the + Source pad format. The source pad format defines the output pixel format of the subdev, as well as the other parameters with the exception of the image width and height. Width and height are defined by the size of the source pad - actual crop selection. + actual crop selection. Accessing any of the above rectangles not supported by the diff --git a/Documentation/DocBook/media/v4l/gen-errors.xml b/Documentation/DocBook/media/v4l/gen-errors.xml index 5bbf3ce1973a..7e29a4e1f696 100644 --- a/Documentation/DocBook/media/v4l/gen-errors.xml +++ b/Documentation/DocBook/media/v4l/gen-errors.xml @@ -6,6 +6,15 @@ &cs-str; + + EAGAIN (aka EWOULDBLOCK) + The ioctl can't be handled because the device is in state where + it can't perform it. This could happen for example in case where + device is sleeping and ioctl is performed to query statistics. + It is also returned when the ioctl would need to wait + for an event, but the device was opened in non-blocking mode. + + EBADF The file descriptor is not a valid. @@ -50,22 +59,12 @@ that this request would overcommit the usb bandwidth reserved for periodic transfers (up to 80% of the USB bandwidth). - - ENOSYS or EOPNOTSUPP - Function not available for this device (dvb API only. Will likely - be replaced anytime soon by ENOTTY). - EPERM Permission denied. Can be returned if the device needs write permission, or some special capabilities is needed (e. g. root) - - EWOULDBLOCK - Operation would block. Used when the ioctl would need to wait - for an event, but the device was opened in non-blocking mode. - diff --git a/Documentation/DocBook/media/v4l/io.xml b/Documentation/DocBook/media/v4l/io.xml index 1885cc0755cb..97f785add841 100644 --- a/Documentation/DocBook/media/v4l/io.xml +++ b/Documentation/DocBook/media/v4l/io.xml @@ -613,8 +613,8 @@ field is independent of the timestamp and __u32 sequence - Set by the driver, counting the frames in the -sequence. + Set by the driver, counting the frames (not fields!) in +sequence. This field is set for both input and output devices. In for details. __u32 reserved2 - A place holder for future extensions and custom -(driver defined) buffer types -V4L2_BUF_TYPE_PRIVATE and higher. Applications + A place holder for future extensions. Applications should set this to 0. __u32 reserved - A place holder for future extensions and custom -(driver defined) buffer types -V4L2_BUF_TYPE_PRIVATE and higher. Applications + A place holder for future extensions. Applications should set this to 0. @@ -827,14 +823,7 @@ should set this to 0. V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY 8 Buffer for video output overlay (OSD), see . Status: Experimental. - - - V4L2_BUF_TYPE_PRIVATE - 0x80 - This and higher values are reserved for custom -(driver defined) buffer types. + linkend="osd" />. diff --git a/Documentation/DocBook/media/v4l/pixfmt-srggb10dpcm8.xml b/Documentation/DocBook/media/v4l/pixfmt-srggb10dpcm8.xml index 8eace3e2e7d4..2d3f0b1aefe0 100644 --- a/Documentation/DocBook/media/v4l/pixfmt-srggb10dpcm8.xml +++ b/Documentation/DocBook/media/v4l/pixfmt-srggb10dpcm8.xml @@ -22,8 +22,7 @@ with 10 bits per colour compressed to 8 bits each, using DPCM compression. DPCM, differential pulse-code modulation, is lossy. Each colour component consumes 8 bits of memory. In other respects - this format is similar to . + this format is similar to . diff --git a/Documentation/DocBook/media/v4l/pixfmt-yvu420m.xml b/Documentation/DocBook/media/v4l/pixfmt-yvu420m.xml new file mode 100644 index 000000000000..2330667907c7 --- /dev/null +++ b/Documentation/DocBook/media/v4l/pixfmt-yvu420m.xml @@ -0,0 +1,154 @@ + + + V4L2_PIX_FMT_YVU420M ('YM21') + &manvol; + + + V4L2_PIX_FMT_YVU420M + Variation of V4L2_PIX_FMT_YVU420 + with planes non contiguous in memory. + + + + Description + + This is a multi-planar format, as opposed to a packed format. +The three components are separated into three sub-images or planes. + +The Y plane is first. The Y plane has one byte per pixel. The Cr data +constitutes the second plane which is half the width and half +the height of the Y plane (and of the image). Each Cr belongs to four +pixels, a two-by-two square of the image. For example, +Cr0 belongs to Y'00, +Y'01, Y'10, and +Y'11. The Cb data, just like the Cr plane, constitutes +the third plane. + + If the Y plane has pad bytes after each row, then the Cr +and Cb planes have half as many pad bytes after their rows. In other +words, two Cx rows (including padding) is exactly as long as one Y row +(including padding). + + V4L2_PIX_FMT_YVU420M is intended to be +used only in drivers and applications that support the multi-planar API, +described in . + + + <constant>V4L2_PIX_FMT_YVU420M</constant> 4 × 4 +pixel image + + + Byte Order. + Each cell is one byte. + + + + + + start0 + 0: + Y'00 + Y'01 + Y'02 + Y'03 + + + start0 + 4: + Y'10 + Y'11 + Y'12 + Y'13 + + + start0 + 8: + Y'20 + Y'21 + Y'22 + Y'23 + + + start0 + 12: + Y'30 + Y'31 + Y'32 + Y'33 + + + + start1 + 0: + Cr00 + Cr01 + + + start1 + 2: + Cr10 + Cr11 + + + + start2 + 0: + Cb00 + Cb01 + + + start2 + 2: + Cb10 + Cb11 + + + + + + + + + Color Sample Location. + + + + + + + 01 + 23 + + + 0 + YY + YY + + + + C + C + + + 1 + YY + YY + + + + + + 2 + YY + YY + + + + C + C + + + 3 + YY + YY + + + + + + + + + diff --git a/Documentation/DocBook/media/v4l/pixfmt.xml b/Documentation/DocBook/media/v4l/pixfmt.xml index e58934c92895..1ddbfabe3195 100644 --- a/Documentation/DocBook/media/v4l/pixfmt.xml +++ b/Documentation/DocBook/media/v4l/pixfmt.xml @@ -708,6 +708,7 @@ information. &sub-y41p; &sub-yuv420; &sub-yuv420m; + &sub-yvu420m; &sub-yuv410; &sub-yuv422p; &sub-yuv411p; diff --git a/Documentation/DocBook/media/v4l/selection-api.xml b/Documentation/DocBook/media/v4l/selection-api.xml index e7ed5077834d..4c238ce068b0 100644 --- a/Documentation/DocBook/media/v4l/selection-api.xml +++ b/Documentation/DocBook/media/v4l/selection-api.xml @@ -40,6 +40,7 @@ cropping and composing rectangles have the same size.
Selection targets +
Cropping and composing targets @@ -52,12 +53,12 @@ cropping and composing rectangles have the same size.
+
+ See for more + information.
- See for more - information. -
Configuration @@ -216,18 +217,17 @@ composing and cropping operations by setting the appropriate targets. The V4L2 API lacks any support for composing to and cropping from an image inside a memory buffer. The application could configure a capture device to fill only a part of an image by abusing V4L2 API. Cropping a smaller image from a larger -one is achieved by setting the field -&v4l2-pix-format;::bytesperline . Introducing an image offsets -could be done by modifying field &v4l2-buffer;::m:userptr - before calling VIDIOC_QBUF . Those +one is achieved by setting the field +&v4l2-pix-format;::bytesperline. Introducing an image offsets +could be done by modifying field &v4l2-buffer;::m_userptr +before calling VIDIOC_QBUF . Those operations should be avoided because they are not portable (endianness), and do not work for macroblock and Bayer formats and mmap buffers. The selection API deals with configuration of buffer cropping/composing in a clear, intuitive and portable way. Next, with the selection API the concepts of the padded target -and constraints flags are introduced. Finally, &v4l2-crop; - and &v4l2-cropcap; have no reserved -fields. Therefore there is no way to extend their functionality. The new - &v4l2-selection; provides a lot of place for future +and constraints flags are introduced. Finally, &v4l2-crop; and &v4l2-cropcap; +have no reserved fields. Therefore there is no way to extend their functionality. +The new &v4l2-selection; provides a lot of place for future extensions. Driver developers are encouraged to implement only selection API. The former cropping API would be simulated using the new one. diff --git a/Documentation/DocBook/media/v4l/v4l2.xml b/Documentation/DocBook/media/v4l/v4l2.xml index eee6908c749f..10ccde9d16d0 100644 --- a/Documentation/DocBook/media/v4l/v4l2.xml +++ b/Documentation/DocBook/media/v4l/v4l2.xml @@ -145,9 +145,12 @@ applications. --> hv Added VIDIOC_ENUM_FREQ_BANDS. + + + 3.5 2012-05-07 - sa, sn + sa, sn, hv Added V4L2_CTRL_TYPE_INTEGER_MENU and V4L2 subdev selections API. Improved the description of V4L2_CID_COLORFX control, added V4L2_CID_COLORFX_CBCR control. @@ -158,11 +161,8 @@ applications. --> V4L2_CID_3A_LOCK, V4L2_CID_AUTO_FOCUS_START, V4L2_CID_AUTO_FOCUS_STOP, V4L2_CID_AUTO_FOCUS_STATUS and V4L2_CID_AUTO_FOCUS_RANGE. - - 2012-05-01 - hv - Added VIDIOC_ENUM_DV_TIMINGS, VIDIOC_QUERY_DV_TIMINGS and - VIDIOC_DV_TIMINGS_CAP. + Added VIDIOC_ENUM_DV_TIMINGS, VIDIOC_QUERY_DV_TIMINGS and + VIDIOC_DV_TIMINGS_CAP. @@ -472,7 +472,7 @@ and discussions on the V4L mailing list. Video for Linux Two API Specification - Revision 3.5 + Revision 3.6 &sub-common; @@ -581,6 +581,7 @@ and discussions on the V4L mailing list. &sub-subdev-enum-frame-size; &sub-subdev-enum-mbus-code; &sub-subdev-g-crop; + &sub-subdev-g-edid; &sub-subdev-g-fmt; &sub-subdev-g-frame-interval; &sub-subdev-g-selection; diff --git a/Documentation/DocBook/media/v4l/vidioc-cropcap.xml b/Documentation/DocBook/media/v4l/vidioc-cropcap.xml index f1bac2c6e978..bf7cc979fdfa 100644 --- a/Documentation/DocBook/media/v4l/vidioc-cropcap.xml +++ b/Documentation/DocBook/media/v4l/vidioc-cropcap.xml @@ -59,6 +59,9 @@ constant except when switching the video standard. Remember this switch can occur implicit when switching the video input or output. + This ioctl must be implemented for video capture or output devices that +support cropping and/or scaling and/or have non-square pixels, and for overlay devices. + struct <structname>v4l2_cropcap</structname> @@ -70,10 +73,10 @@ output. Type of the data stream, set by the application. Only these types are valid here: V4L2_BUF_TYPE_VIDEO_CAPTURE, +V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, V4L2_BUF_TYPE_VIDEO_OUTPUT, -V4L2_BUF_TYPE_VIDEO_OVERLAY, and custom (driver -defined) types with code V4L2_BUF_TYPE_PRIVATE -and higher. See . +V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE and +V4L2_BUF_TYPE_VIDEO_OVERLAY. See . struct v4l2_rect @@ -156,8 +159,7 @@ on 22 Oct 2002 subject "Re:[V4L][patches!] Re:v4l2/kernel-2.5" --> EINVAL The &v4l2-cropcap; type is -invalid. This is not permitted for video capture, output and overlay devices, -which must support VIDIOC_CROPCAP. +invalid. diff --git a/Documentation/DocBook/media/v4l/vidioc-decoder-cmd.xml b/Documentation/DocBook/media/v4l/vidioc-decoder-cmd.xml index 74b87f6e480a..9215627b04c7 100644 --- a/Documentation/DocBook/media/v4l/vidioc-decoder-cmd.xml +++ b/Documentation/DocBook/media/v4l/vidioc-decoder-cmd.xml @@ -49,13 +49,6 @@ Description - - Experimental - - This is an experimental -interface and may change in the future. - - These ioctls control an audio/video (usually MPEG-) decoder. VIDIOC_DECODER_CMD sends a command to the decoder, VIDIOC_TRY_DECODER_CMD can be used to diff --git a/Documentation/DocBook/media/v4l/vidioc-encoder-cmd.xml b/Documentation/DocBook/media/v4l/vidioc-encoder-cmd.xml index f431b3ba79bd..0619ca5d2d36 100644 --- a/Documentation/DocBook/media/v4l/vidioc-encoder-cmd.xml +++ b/Documentation/DocBook/media/v4l/vidioc-encoder-cmd.xml @@ -49,13 +49,6 @@ Description - - Experimental - - This is an experimental -interface and may change in the future. - - These ioctls control an audio/video (usually MPEG-) encoder. VIDIOC_ENCODER_CMD sends a command to the encoder, VIDIOC_TRY_ENCODER_CMD can be used to diff --git a/Documentation/DocBook/media/v4l/vidioc-enum-dv-presets.xml b/Documentation/DocBook/media/v4l/vidioc-enum-dv-presets.xml index 509f0012d2a6..fced5fb0dbf0 100644 --- a/Documentation/DocBook/media/v4l/vidioc-enum-dv-presets.xml +++ b/Documentation/DocBook/media/v4l/vidioc-enum-dv-presets.xml @@ -229,6 +229,12 @@ intended for the user. is out of bounds. + + ENODATA + + Digital video presets are not supported for this input or output. + + diff --git a/Documentation/DocBook/media/v4l/vidioc-enum-dv-timings.xml b/Documentation/DocBook/media/v4l/vidioc-enum-dv-timings.xml index 24c3bf4fd29a..b3e17c1dfaf5 100644 --- a/Documentation/DocBook/media/v4l/vidioc-enum-dv-timings.xml +++ b/Documentation/DocBook/media/v4l/vidioc-enum-dv-timings.xml @@ -106,6 +106,12 @@ application. is out of bounds. + + ENODATA + + Digital video presets are not supported for this input or output. + + diff --git a/Documentation/DocBook/media/v4l/vidioc-enum-fmt.xml b/Documentation/DocBook/media/v4l/vidioc-enum-fmt.xml index 81ebe48317fe..f8dfeed34fca 100644 --- a/Documentation/DocBook/media/v4l/vidioc-enum-fmt.xml +++ b/Documentation/DocBook/media/v4l/vidioc-enum-fmt.xml @@ -58,6 +58,9 @@ structure. Drivers fill the rest of the structure or return an incrementing by one until EINVAL is returned. + Note that after switching input or output the list of enumerated image +formats may be different. +
struct <structname>v4l2_fmtdesc</structname> @@ -78,10 +81,8 @@ Only these types are valid here: V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, V4L2_BUF_TYPE_VIDEO_OUTPUT, -V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, -V4L2_BUF_TYPE_VIDEO_OVERLAY, and custom (driver -defined) types with code V4L2_BUF_TYPE_PRIVATE -and higher. See . +V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE and +V4L2_BUF_TYPE_VIDEO_OVERLAY. See . __u32 diff --git a/Documentation/DocBook/media/v4l/vidioc-enum-framesizes.xml b/Documentation/DocBook/media/v4l/vidioc-enum-framesizes.xml index f77a13f486d7..a78454b5abcd 100644 --- a/Documentation/DocBook/media/v4l/vidioc-enum-framesizes.xml +++ b/Documentation/DocBook/media/v4l/vidioc-enum-framesizes.xml @@ -50,13 +50,6 @@ and pixel format and receives a frame width and height. Description - - Experimental - - This is an experimental -interface and may change in the future. - - This ioctl allows applications to enumerate all frame sizes (&ie; width and height in pixels) that the device supports for the given pixel format. diff --git a/Documentation/DocBook/media/v4l/vidioc-enuminput.xml b/Documentation/DocBook/media/v4l/vidioc-enuminput.xml index 46d5a044a537..3c9a81305ad4 100644 --- a/Documentation/DocBook/media/v4l/vidioc-enuminput.xml +++ b/Documentation/DocBook/media/v4l/vidioc-enuminput.xml @@ -283,7 +283,7 @@ input/output interface to linux-media@vger.kernel.org on 19 Oct 2009. This input supports setting DV presets by using VIDIOC_S_DV_PRESET. - V4L2_IN_CAP_CUSTOM_TIMINGS + V4L2_IN_CAP_DV_TIMINGS 0x00000002 This input supports setting video timings by using VIDIOC_S_DV_TIMINGS. diff --git a/Documentation/DocBook/media/v4l/vidioc-enumoutput.xml b/Documentation/DocBook/media/v4l/vidioc-enumoutput.xml index 428020000ef0..f4ab0798545d 100644 --- a/Documentation/DocBook/media/v4l/vidioc-enumoutput.xml +++ b/Documentation/DocBook/media/v4l/vidioc-enumoutput.xml @@ -168,7 +168,7 @@ input/output interface to linux-media@vger.kernel.org on 19 Oct 2009. This output supports setting DV presets by using VIDIOC_S_DV_PRESET. - V4L2_OUT_CAP_CUSTOM_TIMINGS + V4L2_OUT_CAP_DV_TIMINGS 0x00000002 This output supports setting video timings by using VIDIOC_S_DV_TIMINGS. diff --git a/Documentation/DocBook/media/v4l/vidioc-enumstd.xml b/Documentation/DocBook/media/v4l/vidioc-enumstd.xml index 3a5fc5405f96..8065099401d1 100644 --- a/Documentation/DocBook/media/v4l/vidioc-enumstd.xml +++ b/Documentation/DocBook/media/v4l/vidioc-enumstd.xml @@ -378,6 +378,12 @@ system) is out of bounds. + + ENODATA + + Standard video timings are not supported for this input or output. + + diff --git a/Documentation/DocBook/media/v4l/vidioc-g-crop.xml b/Documentation/DocBook/media/v4l/vidioc-g-crop.xml index c4ff3b1887fb..75c6a93de3c1 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-crop.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-crop.xml @@ -104,10 +104,8 @@ changed and VIDIOC_S_CROP returns the type Type of the data stream, set by the application. Only these types are valid here: V4L2_BUF_TYPE_VIDEO_CAPTURE, -V4L2_BUF_TYPE_VIDEO_OUTPUT, -V4L2_BUF_TYPE_VIDEO_OVERLAY, and custom (driver -defined) types with code V4L2_BUF_TYPE_PRIVATE -and higher. See . +V4L2_BUF_TYPE_VIDEO_OUTPUT and +V4L2_BUF_TYPE_VIDEO_OVERLAY. See . &v4l2-rect; diff --git a/Documentation/DocBook/media/v4l/vidioc-g-dv-preset.xml b/Documentation/DocBook/media/v4l/vidioc-g-dv-preset.xml index 61be9fa3803a..b9ea37634f6c 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-dv-preset.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-dv-preset.xml @@ -77,6 +77,12 @@ If the preset is not supported, it returns an &EINVAL; VIDIOC_S_DV_PRESET,VIDIOC_S_DV_PRESET parameter was unsuitable. + + ENODATA + + Digital video presets are not supported for this input or output. + + EBUSY @@ -104,7 +110,4 @@ If the preset is not supported, it returns an &EINVAL;
- - &return-value; - diff --git a/Documentation/DocBook/media/v4l/vidioc-g-dv-timings.xml b/Documentation/DocBook/media/v4l/vidioc-g-dv-timings.xml index eda1a2991bbe..72369707bd77 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-dv-timings.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-dv-timings.xml @@ -56,7 +56,9 @@ a pointer to the &v4l2-dv-timings; structure as argument. If the ioctl is not su or the timing values are not correct, the driver returns &EINVAL;. The linux/v4l2-dv-timings.h header can be used to get the timings of the formats in the and -standards. +standards. If the current input or output does not support DV timings (e.g. if +&VIDIOC-ENUMINPUT; does not set the V4L2_IN_CAP_DV_TIMINGS flag), then +&ENODATA; is returned. @@ -70,6 +72,12 @@ standards. VIDIOC_S_DV_TIMINGS parameter was unsuitable. + + ENODATA + + Digital video timings are not supported for this input or output. + + EBUSY @@ -320,7 +328,4 @@ detected or used depends on the hardware. - - &return-value; - diff --git a/Documentation/DocBook/media/v4l/vidioc-g-enc-index.xml b/Documentation/DocBook/media/v4l/vidioc-g-enc-index.xml index 2aef02c9044e..be25029a16f1 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-enc-index.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-enc-index.xml @@ -48,13 +48,6 @@ Description - - Experimental - - This is an experimental -interface and may change in the future. - - The VIDIOC_G_ENC_INDEX ioctl provides meta data about a compressed video stream the same or another application currently reads from the driver, which is useful for diff --git a/Documentation/DocBook/media/v4l/vidioc-g-fmt.xml b/Documentation/DocBook/media/v4l/vidioc-g-fmt.xml index 52acff193a6f..ee8f56e1bac0 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-fmt.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-fmt.xml @@ -81,7 +81,7 @@ the application calls the VIDIOC_S_FMT ioctl with a pointer to a v4l2_format structure the driver checks and adjusts the parameters against hardware abilities. Drivers -should not return an error code unless the input is ambiguous, this is +should not return an error code unless the type field is invalid, this is a mechanism to fathom device capabilities and to approach parameters acceptable for both the application and driver. On success the driver may program the hardware, allocate resources and generally prepare for @@ -107,6 +107,10 @@ disabling I/O or possibly time consuming hardware preparations. Although strongly recommended drivers are not required to implement this ioctl. + The format as returned by VIDIOC_TRY_FMT +must be identical to what VIDIOC_S_FMT returns for +the same input or output. + struct <structname>v4l2_format</structname> @@ -170,9 +174,7 @@ capture and output devices. __u8 raw_data[200] - Place holder for future extensions and custom -(driver defined) formats with type -V4L2_BUF_TYPE_PRIVATE and higher. + Place holder for future extensions. @@ -187,8 +189,7 @@ capture and output devices.EINVAL The &v4l2-format; type -field is invalid, the requested buffer type not supported, or the -format is not supported with this buffer type. +field is invalid or the requested buffer type not supported. diff --git a/Documentation/DocBook/media/v4l/vidioc-g-parm.xml b/Documentation/DocBook/media/v4l/vidioc-g-parm.xml index f83d2cdd1185..9058224d1bbf 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-parm.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-parm.xml @@ -108,9 +108,7 @@ devices.__u8raw_data[200] - A place holder for future extensions and custom -(driver defined) buffer types V4L2_BUF_TYPE_PRIVATE and -higher. + A place holder for future extensions. diff --git a/Documentation/DocBook/media/v4l/vidioc-g-selection.xml b/Documentation/DocBook/media/v4l/vidioc-g-selection.xml index f76d8a6d9b92..b11ec75e21a1 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-selection.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-selection.xml @@ -152,12 +152,10 @@ satisfactory parameters have been negotiated. If constraints flags have to be violated at then ERANGE is returned. The error indicates that there exist no rectangle that satisfies the constraints. - - Selection targets and flags are documented in . -
+
Size adjustments with constraint flags. @@ -170,9 +168,9 @@ exist no rectangle that satisfies the constraints.
-
+ - +
struct <structname>v4l2_selection</structname> @@ -208,6 +206,7 @@ exist no rectangle that satisfies the constraints.
+
diff --git a/Documentation/DocBook/media/v4l/vidioc-g-std.xml b/Documentation/DocBook/media/v4l/vidioc-g-std.xml index 99ff1a016220..4a898417de28 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-std.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-std.xml @@ -72,7 +72,9 @@ flags, being a write-only ioctl it does not return the actual new standard as the current input does not support the requested standard the driver returns an &EINVAL;. When the standard set is ambiguous drivers may return EINVAL or choose any of the requested -standards. +standards. If the current input or output does not support standard video timings (e.g. if +&VIDIOC-ENUMINPUT; does not set the V4L2_IN_CAP_STD flag), then +&ENODATA; is returned. @@ -85,6 +87,12 @@ standards. The VIDIOC_S_STD parameter was unsuitable. + + ENODATA + + Standard video timings are not supported for this input or output. + + diff --git a/Documentation/DocBook/media/v4l/vidioc-g-tuner.xml b/Documentation/DocBook/media/v4l/vidioc-g-tuner.xml index 701138f1209d..6cc82010c736 100644 --- a/Documentation/DocBook/media/v4l/vidioc-g-tuner.xml +++ b/Documentation/DocBook/media/v4l/vidioc-g-tuner.xml @@ -354,6 +354,12 @@ radio tuners. The &VIDIOC-ENUM-FREQ-BANDS; ioctl can be used to enumerate the available frequency bands. + + V4L2_TUNER_CAP_HWSEEK_PROG_LIM + 0x0800 + The range to search when using the hardware seek functionality + is programmable, see &VIDIOC-S-HW-FREQ-SEEK; for details. + diff --git a/Documentation/DocBook/media/v4l/vidioc-qbuf.xml b/Documentation/DocBook/media/v4l/vidioc-qbuf.xml index 77ff5be0809d..6a821a65a5ae 100644 --- a/Documentation/DocBook/media/v4l/vidioc-qbuf.xml +++ b/Documentation/DocBook/media/v4l/vidioc-qbuf.xml @@ -155,6 +155,8 @@ or no buffers have been allocated yet, or the userptr or length are invalid. + + EIO VIDIOC_DQBUF failed due to an diff --git a/Documentation/DocBook/media/v4l/vidioc-query-dv-preset.xml b/Documentation/DocBook/media/v4l/vidioc-query-dv-preset.xml index 1bc8aeb3ff1f..68b49d09e245 100644 --- a/Documentation/DocBook/media/v4l/vidioc-query-dv-preset.xml +++ b/Documentation/DocBook/media/v4l/vidioc-query-dv-preset.xml @@ -65,5 +65,14 @@ returned. &return-value; + + + + ENODATA + + Digital video presets are not supported for this input or output. + + + diff --git a/Documentation/DocBook/media/v4l/vidioc-query-dv-timings.xml b/Documentation/DocBook/media/v4l/vidioc-query-dv-timings.xml index 44935a0ffcf0..e185f149e0a1 100644 --- a/Documentation/DocBook/media/v4l/vidioc-query-dv-timings.xml +++ b/Documentation/DocBook/media/v4l/vidioc-query-dv-timings.xml @@ -77,6 +77,12 @@ capabilities in order to give more precise feedback to the user. &return-value; + + ENODATA + + Digital video timings are not supported for this input or output. + + ENOLINK diff --git a/Documentation/DocBook/media/v4l/vidioc-querycap.xml b/Documentation/DocBook/media/v4l/vidioc-querycap.xml index f33dd746b66b..4c70215ae03f 100644 --- a/Documentation/DocBook/media/v4l/vidioc-querycap.xml +++ b/Documentation/DocBook/media/v4l/vidioc-querycap.xml @@ -90,11 +90,13 @@ ambiguities. __u8 bus_info[32] Location of the device in the system, a -NUL-terminated ASCII string. For example: "PCI Slot 4". This +NUL-terminated ASCII string. For example: "PCI:0000:05:06.0". This information is intended for users, to distinguish multiple -identical devices. If no such information is available the field may -simply count the devices controlled by the driver, or contain the -empty string (bus_info[0] = 0). +identical devices. If no such information is available the field must +simply count the devices controlled by the driver ("platform:vivi-000"). +The bus_info must start with "PCI:" for PCI boards, "PCIe:" for PCI Express boards, +"usb-" for USB devices, "I2C:" for i2c devices, "ISA:" for ISA devices, +"parport" for parallel port devices and "platform:" for platform devices. __u32 diff --git a/Documentation/DocBook/media/v4l/vidioc-querystd.xml b/Documentation/DocBook/media/v4l/vidioc-querystd.xml index 4b79c7c04ed6..fe80a183d957 100644 --- a/Documentation/DocBook/media/v4l/vidioc-querystd.xml +++ b/Documentation/DocBook/media/v4l/vidioc-querystd.xml @@ -62,5 +62,13 @@ current video input or output. &return-value; + + + ENODATA + + Standard video timings are not supported for this input or output. + + + diff --git a/Documentation/DocBook/media/v4l/vidioc-reqbufs.xml b/Documentation/DocBook/media/v4l/vidioc-reqbufs.xml index d7c95057bc51..2b50ef2007f3 100644 --- a/Documentation/DocBook/media/v4l/vidioc-reqbufs.xml +++ b/Documentation/DocBook/media/v4l/vidioc-reqbufs.xml @@ -109,9 +109,8 @@ as the &v4l2-format; type field. See __u32 reserved[2] - A place holder for future extensions and custom -(driver defined) buffer types V4L2_BUF_TYPE_PRIVATE and -higher. This array should be zeroed by applications. + A place holder for future extensions. This array should +be zeroed by applications. diff --git a/Documentation/DocBook/media/v4l/vidioc-s-hw-freq-seek.xml b/Documentation/DocBook/media/v4l/vidioc-s-hw-freq-seek.xml index 3dd1bec6d3c7..5b379e752194 100644 --- a/Documentation/DocBook/media/v4l/vidioc-s-hw-freq-seek.xml +++ b/Documentation/DocBook/media/v4l/vidioc-s-hw-freq-seek.xml @@ -75,6 +75,9 @@ seek is started. This ioctl is supported if the V4L2_CAP_HW_FREQ_SEEK capability is set. + If this ioctl is called from a non-blocking filehandle, then &EAGAIN; is + returned and no seek takes place. + struct <structname>v4l2_hw_freq_seek</structname> @@ -157,6 +160,13 @@ one of the values in the type, fields is wrong. + + EAGAIN + + Attempted to call VIDIOC_S_HW_FREQ_SEEK + with the filehandle in non-blocking mode. + + ENODATA diff --git a/Documentation/DocBook/media/v4l/vidioc-streamon.xml b/Documentation/DocBook/media/v4l/vidioc-streamon.xml index 81cca4569050..716ea15e54a1 100644 --- a/Documentation/DocBook/media/v4l/vidioc-streamon.xml +++ b/Documentation/DocBook/media/v4l/vidioc-streamon.xml @@ -74,7 +74,12 @@ not transmitted yet. I/O returns to the same state as after calling stream type. This is the same as &v4l2-requestbuffers; type. - Note applications can be preempted for unknown periods right + If VIDIOC_STREAMON is called when streaming +is already in progress, or if VIDIOC_STREAMOFF is called +when streaming is already stopped, then the ioctl does nothing and 0 is +returned. + + Note that applications can be preempted for unknown periods right before or after the VIDIOC_STREAMON or VIDIOC_STREAMOFF calls, there is no notion of starting or stopping "now". Buffer timestamps can be used to diff --git a/Documentation/DocBook/media/v4l/vidioc-subdev-g-edid.xml b/Documentation/DocBook/media/v4l/vidioc-subdev-g-edid.xml new file mode 100644 index 000000000000..bbd18f0e6ede --- /dev/null +++ b/Documentation/DocBook/media/v4l/vidioc-subdev-g-edid.xml @@ -0,0 +1,152 @@ + + + ioctl VIDIOC_SUBDEV_G_EDID, VIDIOC_SUBDEV_S_EDID + &manvol; + + + + VIDIOC_SUBDEV_G_EDID + VIDIOC_SUBDEV_S_EDID + Get or set the EDID of a video receiver/transmitter + + + + + + int ioctl + int fd + int request + struct v4l2_subdev_edid *argp + + + + + int ioctl + int fd + int request + const struct v4l2_subdev_edid *argp + + + + + + Arguments + + + + fd + + &fd; + + + + request + + VIDIOC_SUBDEV_G_EDID, VIDIOC_SUBDEV_S_EDID + + + + argp + + + + + + + + + Description + These ioctls can be used to get or set an EDID associated with an input pad + from a receiver or an output pad of a transmitter subdevice. + + To get the EDID data the application has to fill in the pad, + start_block, blocks and edid + fields and call VIDIOC_SUBDEV_G_EDID. The current EDID from block + start_block and of size blocks + will be placed in the memory edid points to. The edid + pointer must point to memory at least blocks * 128 bytes + large (the size of one block is 128 bytes). + + If there are fewer blocks than specified, then the driver will set blocks + to the actual number of blocks. If there are no EDID blocks available at all, then the error code + ENODATA is set. + + If blocks have to be retrieved from the sink, then this call will block until they + have been read. + + To set the EDID blocks of a receiver the application has to fill in the pad, + blocks and edid fields and set + start_block to 0. It is not possible to set part of an EDID, + it is always all or nothing. Setting the EDID data is only valid for receivers as it makes + no sense for a transmitter. + + The driver assumes that the full EDID is passed in. If there are more EDID blocks than + the hardware can handle then the EDID is not written, but instead the error code E2BIG is set + and blocks is set to the maximum that the hardware supports. + If start_block is any + value other than 0 then the error code EINVAL is set. + + To disable an EDID you set blocks to 0. Depending on the + hardware this will drive the hotplug pin low and/or block the source from reading the EDID + data in some way. In any case, the end result is the same: the EDID is no longer available. + + +
+ struct <structname>v4l2_subdev_edid</structname> + + &cs-str; + + + __u32 + pad + Pad for which to get/set the EDID blocks. + + + __u32 + start_block + Read the EDID from starting with this block. Must be 0 when setting + the EDID. + + + __u32 + blocks + The number of blocks to get or set. Must be less or equal to 256 (the + maximum number of blocks as defined by the standard). When you set the EDID and + blocks is 0, then the EDID is disabled or erased. + + + __u8 * + edid + Pointer to memory that contains the EDID. The minimum size is + blocks * 128. + + + __u32 + reserved[5] + Reserved for future extensions. Applications and drivers must + set the array to zero. + + + +
+ + + + &return-value; + + + + ENODATA + + The EDID data is not available. + + + + E2BIG + + The EDID data you provided is more than the hardware can handle. + + + + + diff --git a/Documentation/DocBook/media/v4l/vidioc-subdev-g-selection.xml b/Documentation/DocBook/media/v4l/vidioc-subdev-g-selection.xml index f33cc814a01d..1ba9e999af3f 100644 --- a/Documentation/DocBook/media/v4l/vidioc-subdev-g-selection.xml +++ b/Documentation/DocBook/media/v4l/vidioc-subdev-g-selection.xml @@ -69,23 +69,22 @@ more information on how each selection target affects the image processing pipeline inside the subdevice. -
+ Types of selection targets There are two types of selection targets: actual and bounds. The actual targets are the targets which configure the hardware. The BOUNDS target will return a rectangle that contain all possible actual rectangles. -
+ -
+ Discovering supported features To discover which targets are supported, the user can perform VIDIOC_SUBDEV_G_SELECTION on them. Any unsupported target will return EINVAL. -
Selection targets and flags are documented in . @@ -132,6 +131,7 @@ + diff --git a/Documentation/DocBook/media_api.tmpl b/Documentation/DocBook/media_api.tmpl index 4e8e8985cc17..f2413acfe241 100644 --- a/Documentation/DocBook/media_api.tmpl +++ b/Documentation/DocBook/media_api.tmpl @@ -29,7 +29,7 @@ LINUX MEDIA INFRASTRUCTURE API - 2009-2011 + 2009-2012 LinuxTV Developers @@ -53,7 +53,7 @@ Foundation. A copy of the license is included in the chapter entitled video and radio straming devices, including video cameras, analog and digital TV receiver cards, AM/FM receiver cards, streaming capture devices. - It is divided into three parts. + It is divided into four parts. The first part covers radio, capture, cameras and analog TV devices. The second part covers the @@ -62,7 +62,8 @@ Foundation. A copy of the license is included in the chapter entitled in fact it covers several different video standards including DVB-T, DVB-S, DVB-C and ATSC. The API is currently being updated to documment support also for DVB-S2, ISDB-T and ISDB-S. - The third part covers Remote Controller API + The third part covers the Remote Controller API. + The fourth part covers the Media Controller API. For additional information and for the latest development code, see: http://linuxtv.org. For discussing improvements, reporting troubles, sending new drivers, etc, please mail to: Linux Media Mailing List (LMML).. @@ -87,7 +88,7 @@ Foundation. A copy of the license is included in the chapter entitled - 2009-2011 + 2009-2012 Mauro Carvalho Chehab diff --git a/Documentation/dvb/README.dvb-usb b/Documentation/dvb/README.dvb-usb index c4d963a67d6f..8eb92264ee04 100644 --- a/Documentation/dvb/README.dvb-usb +++ b/Documentation/dvb/README.dvb-usb @@ -30,7 +30,7 @@ with the device via the bus. The connection between the DVB-API-functionality is done via callbacks, assigned in a static device-description (struct dvb_usb_device) each device-driver has to have. -For an example have a look in drivers/media/dvb/dvb-usb/vp7045*. +For an example have a look in drivers/media/usb/dvb-usb/vp7045*. Objective is to migrate all the usb-devices (dibusb, cinergyT2, maybe the ttusb; flexcop-usb already benefits from the generic flexcop-device) to use diff --git a/Documentation/dvb/get_dvb_firmware b/Documentation/dvb/get_dvb_firmware index 12d3952e83d5..32bc56b13b1c 100755 --- a/Documentation/dvb/get_dvb_firmware +++ b/Documentation/dvb/get_dvb_firmware @@ -116,7 +116,7 @@ sub tda10045 { sub tda10046 { my $sourcefile = "TT_PCI_2.19h_28_11_2006.zip"; - my $url = "http://www.tt-download.com/download/updates/219/$sourcefile"; + my $url = "http://technotrend.com.ua/download/software/219/$sourcefile"; my $hash = "6a7e1e2f2644b162ff0502367553c72d"; my $outfile = "dvb-fe-tda10046.fw"; my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 849b771c5e03..2152b0e7237d 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -178,7 +178,6 @@ Code Seq#(hex) Include File Comments 'V' C0 linux/ivtv.h conflict! 'V' C0 media/davinci/vpfe_capture.h conflict! 'V' C0 media/si4713.h conflict! -'V' C0-CF drivers/media/video/mxb.h conflict! 'W' 00-1F linux/watchdog.h conflict! 'W' 00-1F linux/wanrouter.h conflict! 'W' 00-3F sound/asound.h conflict! @@ -204,8 +203,6 @@ Code Seq#(hex) Include File Comments 'c' A0-AF arch/x86/include/asm/msr.h conflict! 'd' 00-FF linux/char/drm/drm/h conflict! 'd' 02-40 pcmcia/ds.h conflict! -'d' 10-3F drivers/media/video/dabusb.h conflict! -'d' C0-CF drivers/media/video/saa7191.h conflict! 'd' F0-FF linux/digi1.h 'e' all linux/digi1.h conflict! 'e' 00-1F drivers/net/irda/irtty-sir.h conflict! @@ -267,9 +264,7 @@ Code Seq#(hex) Include File Comments 'v' 00-1F linux/ext2_fs.h conflict! 'v' 00-1F linux/fs.h conflict! 'v' 00-0F linux/sonypi.h conflict! -'v' C0-DF media/pwc-ioctl.h conflict! 'v' C0-FF linux/meye.h conflict! -'v' D0-DF drivers/media/video/cpia2/cpia2dev.h conflict! 'w' all CERN SCI driver 'y' 00-1F packet based user level communications diff --git a/Documentation/video4linux/CARDLIST.cx23885 b/Documentation/video4linux/CARDLIST.cx23885 index 652aecd13199..1299b5e82d7f 100644 --- a/Documentation/video4linux/CARDLIST.cx23885 +++ b/Documentation/video4linux/CARDLIST.cx23885 @@ -35,3 +35,4 @@ 34 -> TerraTec Cinergy T PCIe Dual [153b:117e] 35 -> TeVii S471 [d471:9022] 36 -> Hauppauge WinTV-HVR1255 [0070:2259] + 37 -> Prof Revolution DVB-S2 8000 [8000:3034] diff --git a/Documentation/video4linux/CQcam.txt b/Documentation/video4linux/CQcam.txt index 6e680fec1e9c..0b69e4ee8e31 100644 --- a/Documentation/video4linux/CQcam.txt +++ b/Documentation/video4linux/CQcam.txt @@ -18,7 +18,7 @@ Table of Contents 1.0 Introduction - The file ../../drivers/media/video/c-qcam.c is a device driver for + The file ../../drivers/media/parport/c-qcam.c is a device driver for the Logitech (nee Connectix) parallel port interface color CCD camera. This is a fairly inexpensive device for capturing images. Logitech does not currently provide information for developers, but many people diff --git a/Documentation/video4linux/README.davinci-vpbe b/Documentation/video4linux/README.davinci-vpbe index 7a460b0685bb..dc9a297f49c3 100644 --- a/Documentation/video4linux/README.davinci-vpbe +++ b/Documentation/video4linux/README.davinci-vpbe @@ -5,22 +5,22 @@ File partitioning ----------------- V4L2 display device driver - drivers/media/video/davinci/vpbe_display.c - drivers/media/video/davinci/vpbe_display.h + drivers/media/platform/davinci/vpbe_display.c + drivers/media/platform/davinci/vpbe_display.h VPBE display controller - drivers/media/video/davinci/vpbe.c - drivers/media/video/davinci/vpbe.h + drivers/media/platform/davinci/vpbe.c + drivers/media/platform/davinci/vpbe.h VPBE venc sub device driver - drivers/media/video/davinci/vpbe_venc.c - drivers/media/video/davinci/vpbe_venc.h - drivers/media/video/davinci/vpbe_venc_regs.h + drivers/media/platform/davinci/vpbe_venc.c + drivers/media/platform/davinci/vpbe_venc.h + drivers/media/platform/davinci/vpbe_venc_regs.h VPBE osd driver - drivers/media/video/davinci/vpbe_osd.c - drivers/media/video/davinci/vpbe_osd.h - drivers/media/video/davinci/vpbe_osd_regs.h + drivers/media/platform/davinci/vpbe_osd.c + drivers/media/platform/davinci/vpbe_osd.h + drivers/media/platform/davinci/vpbe_osd_regs.h Functional partitioning ----------------------- diff --git a/Documentation/video4linux/fimc.txt b/Documentation/video4linux/fimc.txt index eb049708f3e4..fd02d9a4930a 100644 --- a/Documentation/video4linux/fimc.txt +++ b/Documentation/video4linux/fimc.txt @@ -10,7 +10,7 @@ data from LCD controller (FIMD) through the SoC internal writeback data path. There are multiple FIMC instances in the SoCs (up to 4), having slightly different capabilities, like pixel alignment constraints, rotator availability, LCD writeback support, etc. The driver is located at -drivers/media/video/s5p-fimc directory. +drivers/media/platform/s5p-fimc directory. 1. Supported SoCs ================= @@ -36,21 +36,21 @@ Not currently supported: ===================== - media device driver - drivers/media/video/s5p-fimc/fimc-mdevice.[ch] + drivers/media/platform/s5p-fimc/fimc-mdevice.[ch] - camera capture video device driver - drivers/media/video/s5p-fimc/fimc-capture.c + drivers/media/platform/s5p-fimc/fimc-capture.c - MIPI-CSI2 receiver subdev - drivers/media/video/s5p-fimc/mipi-csis.[ch] + drivers/media/platform/s5p-fimc/mipi-csis.[ch] - video post-processor (mem-to-mem) - drivers/media/video/s5p-fimc/fimc-core.c + drivers/media/platform/s5p-fimc/fimc-core.c - common files - drivers/media/video/s5p-fimc/fimc-core.h - drivers/media/video/s5p-fimc/fimc-reg.h - drivers/media/video/s5p-fimc/regs-fimc.h + drivers/media/platform/s5p-fimc/fimc-core.h + drivers/media/platform/s5p-fimc/fimc-reg.h + drivers/media/platform/s5p-fimc/regs-fimc.h 4. User space interfaces ======================== diff --git a/Documentation/video4linux/omap3isp.txt b/Documentation/video4linux/omap3isp.txt index 5dd1439b61fd..b9a9f83b1587 100644 --- a/Documentation/video4linux/omap3isp.txt +++ b/Documentation/video4linux/omap3isp.txt @@ -12,7 +12,7 @@ Introduction ============ This file documents the Texas Instruments OMAP 3 Image Signal Processor (ISP) -driver located under drivers/media/video/omap3isp. The original driver was +driver located under drivers/media/platform/omap3isp. The original driver was written by Texas Instruments but since that it has been rewritten (twice) at Nokia. diff --git a/Documentation/video4linux/v4l2-controls.txt b/Documentation/video4linux/v4l2-controls.txt index 43da22b89728..54270df99d5c 100644 --- a/Documentation/video4linux/v4l2-controls.txt +++ b/Documentation/video4linux/v4l2-controls.txt @@ -594,7 +594,11 @@ handler and finally add the first handler to the second. For example: v4l2_ctrl_new_std(&radio_ctrl_handler, &radio_ops, V4L2_CID_AUDIO_MUTE, ...); v4l2_ctrl_new_std(&video_ctrl_handler, &video_ops, V4L2_CID_BRIGHTNESS, ...); v4l2_ctrl_new_std(&video_ctrl_handler, &video_ops, V4L2_CID_CONTRAST, ...); - v4l2_ctrl_add_handler(&video_ctrl_handler, &radio_ctrl_handler); + v4l2_ctrl_add_handler(&video_ctrl_handler, &radio_ctrl_handler, NULL); + +The last argument to v4l2_ctrl_add_handler() is a filter function that allows +you to filter which controls will be added. Set it to NULL if you want to add +all controls. Or you can add specific controls to a handler: diff --git a/Documentation/video4linux/v4l2-framework.txt b/Documentation/video4linux/v4l2-framework.txt index 89318be6c1d2..32bfe926e8d7 100644 --- a/Documentation/video4linux/v4l2-framework.txt +++ b/Documentation/video4linux/v4l2-framework.txt @@ -583,11 +583,19 @@ You should also set these fields: - name: set to something descriptive and unique. +- vfl_dir: set this to VFL_DIR_RX for capture devices (VFL_DIR_RX has value 0, + so this is normally already the default), set to VFL_DIR_TX for output + devices and VFL_DIR_M2M for mem2mem (codec) devices. + - fops: set to the v4l2_file_operations struct. - ioctl_ops: if you use the v4l2_ioctl_ops to simplify ioctl maintenance (highly recommended to use this and it might become compulsory in the - future!), then set this to your v4l2_ioctl_ops struct. + future!), then set this to your v4l2_ioctl_ops struct. The vfl_type and + vfl_dir fields are used to disable ops that do not match the type/dir + combination. E.g. VBI ops are disabled for non-VBI nodes, and output ops + are disabled for a capture device. This makes it possible to provide + just one v4l2_ioctl_ops struct for both vbi and video nodes. - lock: leave to NULL if you want to do all the locking in the driver. Otherwise you give it a pointer to a struct mutex_lock and before the @@ -1054,4 +1062,4 @@ The first event type in the class is reserved for future use, so the first available event type is 'class base + 1'. An example on how the V4L2 events may be used can be found in the OMAP -3 ISP driver (drivers/media/video/omap3isp). +3 ISP driver (drivers/media/platform/omap3isp). diff --git a/Documentation/video4linux/videobuf b/Documentation/video4linux/videobuf index 1d00d7f15b8f..3ffe9e960b6f 100644 --- a/Documentation/video4linux/videobuf +++ b/Documentation/video4linux/videobuf @@ -349,7 +349,7 @@ again. Developers who are interested in more information can go into the relevant header files; there are a few low-level functions declared there which have not been talked about here. Also worthwhile is the vivi driver -(drivers/media/video/vivi.c), which is maintained as an example of how V4L2 +(drivers/media/platform/vivi.c), which is maintained as an example of how V4L2 drivers should be written. Vivi only uses the vmalloc() API, but it's good enough to get started with. Note also that all of these calls are exported GPL-only, so they will not be available to non-GPL kernel modules. diff --git a/MAINTAINERS b/MAINTAINERS index 4b58d5415929..84ee86719bd9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -184,6 +184,16 @@ S: Maintained F: Documentation/filesystems/9p.txt F: fs/9p/ +A8293 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/dvb-frontends/a8293* + AACRAID SCSI RAID DRIVER M: Adaptec OEM Raid Solutions L: linux-scsi@vger.kernel.org @@ -391,6 +401,26 @@ M: Riccardo Facchetti S: Maintained F: sound/oss/aedsp16.c +AF9013 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/dvb-frontends/af9013* + +AF9033 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/dvb-frontends/af9033* + AFFS FILE SYSTEM L: linux-fsdevel@vger.kernel.org S: Orphan @@ -1063,7 +1093,7 @@ L: linux-media@vger.kernel.org S: Maintained F: arch/arm/plat-s5p/dev-fimc* F: arch/arm/plat-samsung/include/plat/*fimc* -F: drivers/media/video/s5p-fimc/ +F: drivers/media/platform/s5p-fimc/ ARM/SAMSUNG S5P SERIES Multi Format Codec (MFC) SUPPORT M: Kyungmin Park @@ -1073,7 +1103,7 @@ L: linux-arm-kernel@lists.infradead.org L: linux-media@vger.kernel.org S: Maintained F: arch/arm/plat-s5p/dev-mfc.c -F: drivers/media/video/s5p-mfc/ +F: drivers/media/platform/s5p-mfc/ ARM/SAMSUNG S5P SERIES TV SUBSYSTEM SUPPORT M: Kyungmin Park @@ -1081,7 +1111,7 @@ M: Tomasz Stanislawski L: linux-arm-kernel@lists.infradead.org L: linux-media@vger.kernel.org S: Maintained -F: drivers/media/video/s5p-tv/ +F: drivers/media/platform/s5p-tv/ ARM/SHMOBILE ARM ARCHITECTURE M: Paul Mundt @@ -1353,7 +1383,7 @@ ATMEL ISI DRIVER M: Josh Wu L: linux-media@vger.kernel.org S: Supported -F: drivers/media/video/atmel-isi.c +F: drivers/media/platform/atmel-isi.c F: include/media/atmel-isi.h ATMEL LCDFB DRIVER @@ -1700,7 +1730,7 @@ W: http://linuxtv.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained F: Documentation/video4linux/bttv/ -F: drivers/media/video/bt8xx/bttv* +F: drivers/media/pci/bt8xx/bttv* C-MEDIA CMI8788 DRIVER M: Clemens Ladisch @@ -1730,7 +1760,7 @@ L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained F: Documentation/video4linux/cafe_ccic -F: drivers/media/video/marvell-ccic/ +F: drivers/media/platform/marvell-ccic/ CAIF NETWORK LAYER M: Sjur Braendeland @@ -2119,7 +2149,17 @@ W: http://linuxtv.org W: http://www.ivtvdriver.org/index.php/Cx18 S: Maintained F: Documentation/video4linux/cx18.txt -F: drivers/media/video/cx18/ +F: drivers/media/pci/cx18/ + +CXD2820R MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/dvb-frontends/cxd2820r* CXGB3 ETHERNET DRIVER (CXGB3) M: Divy Le Ray @@ -2474,6 +2514,117 @@ L: netdev@vger.kernel.org S: Maintained F: drivers/net/wan/dscc4.c +DVB_USB_AF9015 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/af9015* + +DVB_USB_AF9035 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/af9035* + +DVB_USB_ANYSEE MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/anysee* + +DVB_USB_AU6610 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/au6610* + +DVB_USB_CE6230 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/ce6230* + +DVB_USB_CXUSB MEDIA DRIVER +M: Michael Krufky +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://github.com/mkrufky +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/cxusb* + +DVB_USB_CYPRESS_FIRMWARE MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/cypress_firmware* + +DVB_USB_EC168 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/ec168* + +DVB_USB_MXL111SF MEDIA DRIVER +M: Michael Krufky +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://github.com/mkrufky +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/mkrufky/mxl111sf.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/mxl111sf* + +DVB_USB_RTL28XXU MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/rtl28xxu* + +DVB_USB_V2 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/usb/dvb-usb-v2/dvb_usb* +F: drivers/media/usb/dvb-usb-v2/usb_urb.c + DYNAMIC DEBUG M: Jason Baron S: Maintained @@ -2485,6 +2636,16 @@ M: "Maciej W. Rozycki" S: Maintained F: drivers/tty/serial/dz.* +E4000 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/tuners/e4000* + EATA-DMA SCSI DRIVER M: Michael Neuffer L: linux-eata@i-connect.net @@ -2513,6 +2674,16 @@ S: Maintained F: include/linux/netfilter_bridge/ebt_*.h F: net/bridge/netfilter/ebt*.c +EC100 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/dvb-frontends/ec100* + ECRYPT FILE SYSTEM M: Tyler Hicks M: Dustin Kirkland @@ -2795,8 +2966,18 @@ FC0011 TUNER DRIVER M: Michael Buesch L: linux-media@vger.kernel.org S: Maintained -F: drivers/media/common/tuners/fc0011.h -F: drivers/media/common/tuners/fc0011.c +F: drivers/media/tuners/fc0011.h +F: drivers/media/tuners/fc0011.c + +FC2580 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/tuners/fc2580* FANOTIFY M: Eric Paris @@ -3026,7 +3207,7 @@ M: Kyungmin Park M: Heungjun Kim L: linux-media@vger.kernel.org S: Maintained -F: drivers/media/video/m5mols/ +F: drivers/media/i2c/m5mols/ F: include/media/m5mols.h FUJITSU TABLET EXTRAS @@ -3144,49 +3325,56 @@ M: Frank Zago L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained -F: drivers/media/video/gspca/finepix.c +F: drivers/media/usb/gspca/finepix.c GSPCA GL860 SUBDRIVER M: Olivier Lorin L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained -F: drivers/media/video/gspca/gl860/ +F: drivers/media/usb/gspca/gl860/ GSPCA M5602 SUBDRIVER M: Erik Andren L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained -F: drivers/media/video/gspca/m5602/ +F: drivers/media/usb/gspca/m5602/ GSPCA PAC207 SONIXB SUBDRIVER M: Hans de Goede L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained -F: drivers/media/video/gspca/pac207.c +F: drivers/media/usb/gspca/pac207.c GSPCA SN9C20X SUBDRIVER M: Brian Johnson L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained -F: drivers/media/video/gspca/sn9c20x.c +F: drivers/media/usb/gspca/sn9c20x.c GSPCA T613 SUBDRIVER M: Leandro Costantino L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained -F: drivers/media/video/gspca/t613.c +F: drivers/media/usb/gspca/t613.c GSPCA USB WEBCAM DRIVER M: Hans de Goede L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained -F: drivers/media/video/gspca/ +F: drivers/media/usb/gspca/ + +STK1160 USB VIDEO CAPTURE DRIVER +M: Ezequiel Garcia +L: linux-media@vger.kernel.org +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git +S: Maintained +F: drivers/media/usb/stk1160/ HARD DRIVE ACTIVE PROTECTION SYSTEM (HDAPS) DRIVER M: Frank Seidel @@ -3240,6 +3428,16 @@ L: linux-parisc@vger.kernel.org S: Maintained F: sound/parisc/harmony.* +HD29L2 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/dvb-frontends/hd29l2* + HEWLETT-PACKARD SMART2 RAID DRIVER M: Chirag Kantharia L: iss_storagedev@hp.com @@ -3906,7 +4104,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git W: http://www.ivtvdriver.org S: Maintained F: Documentation/video4linux/*.ivtv -F: drivers/media/video/ivtv/ +F: drivers/media/pci/ivtv/ F: include/linux/ivtv* JC42.4 TEMPERATURE SENSOR DRIVER @@ -4200,6 +4398,26 @@ W: http://legousb.sourceforge.net/ S: Maintained F: drivers/usb/misc/legousbtower.c +LG2160 MEDIA DRIVER +M: Michael Krufky +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://github.com/mkrufky +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/mkrufky/tuners.git +S: Maintained +F: drivers/media/dvb-frontends/lg2160.* + +LGDT3305 MEDIA DRIVER +M: Michael Krufky +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://github.com/mkrufky +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/mkrufky/tuners.git +S: Maintained +F: drivers/media/dvb-frontends/lgdt3305.* + LGUEST M: Rusty Russell L: lguest@lists.ozlabs.org @@ -4602,7 +4820,7 @@ MOTION EYE VAIO PICTUREBOOK CAMERA DRIVER W: http://popies.net/meye/ S: Orphan F: Documentation/video4linux/meye.txt -F: drivers/media/video/meye.* +F: drivers/media/pci/meye/ F: include/linux/meye.h MOTOROLA IMX MMC/SD HOST CONTROLLER INTERFACE DRIVER @@ -4666,6 +4884,16 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb.git S: Maintained F: drivers/usb/musb/ +MXL5007T MEDIA DRIVER +M: Michael Krufky +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://github.com/mkrufky +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/mkrufky/tuners.git +S: Maintained +F: drivers/media/tuners/mxl5007t.* + MYRICOM MYRI-10G 10GbE DRIVER (MYRI10GE) M: Andrew Gallatin L: netdev@vger.kernel.org @@ -5006,7 +5234,7 @@ OMAP IMAGE SIGNAL PROCESSOR (ISP) M: Laurent Pinchart L: linux-media@vger.kernel.org S: Maintained -F: drivers/media/video/omap3isp/* +F: drivers/media/platform/omap3isp/ OMAP USB SUPPORT M: Felipe Balbi @@ -5047,7 +5275,7 @@ M: Jonathan Corbet L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained -F: drivers/media/video/ov7670.c +F: drivers/media/i2c/ov7670.c ONENAND FLASH DRIVER M: Kyungmin Park @@ -5591,7 +5819,7 @@ W: http://www.isely.net/pvrusb2/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained F: Documentation/video4linux/README.pvrusb2 -F: drivers/media/video/pvrusb2/ +F: drivers/media/usb/pvrusb2/ PWM SUBSYSTEM M: Thierry Reding @@ -5701,6 +5929,16 @@ F: fs/qnx4/ F: include/linux/qnx4_fs.h F: include/linux/qnxtypes.h +QT1010 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/tuners/qt1010* + QUALCOMM HEXAGON ARCHITECTURE M: Richard Kuo L: linux-hexagon@vger.kernel.org @@ -5867,6 +6105,16 @@ F: include/linux/rose.h F: include/net/rose.h F: net/rose/ +RTL2830 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/dvb-frontends/rtl2830* + RTL8180 WIRELESS DRIVER M: "John W. Linville" L: linux-wireless@vger.kernel.org @@ -5961,9 +6209,9 @@ L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git W: http://www.mihu.de/linux/saa7146 S: Maintained -F: drivers/media/common/saa7146* -F: drivers/media/video/*7146* -F: include/media/*7146* +F: drivers/media/common/saa7146/ +F: drivers/media/pci/saa7146/ +F: include/media/saa7146* SAMSUNG LAPTOP DRIVER M: Corentin Chary @@ -6024,7 +6272,7 @@ M: Huang Shijie M: Kang Yong M: Zhang Xiaobing S: Supported -F: drivers/media/video/tlg2300 +F: drivers/media/usb/tlg2300 SC1200 WDT DRIVER M: Zwane Mwaikambo @@ -6412,8 +6660,9 @@ M: Guennadi Liakhovetski L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git S: Maintained -F: include/media/v4l2* -F: drivers/media/video/v4l2* +F: include/media/soc* +F: drivers/media/i2c/soc_camera/ +F: drivers/media/platform/soc_camera/ SOEKRIS NET48XX LED SUPPORT M: Chris Boot @@ -6831,6 +7080,66 @@ W: http://tcp-lp-mod.sourceforge.net/ S: Maintained F: net/ipv4/tcp_lp.c +TDA10071 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/dvb-frontends/tda10071* + +TDA18212 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/tuners/tda18212* + +TDA18218 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/tuners/tda18218* + +TDA18271 MEDIA DRIVER +M: Michael Krufky +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://github.com/mkrufky +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/mkrufky/tuners.git +S: Maintained +F: drivers/media/tuners/tda18271* + +TDA827x MEDIA DRIVER +M: Michael Krufky +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://github.com/mkrufky +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/mkrufky/tuners.git +S: Maintained +F: drivers/media/tuners/tda8290.* + +TDA8290 MEDIA DRIVER +M: Michael Krufky +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://github.com/mkrufky +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/mkrufky/tuners.git +S: Maintained +F: drivers/media/tuners/tda8290.* + TEAM DRIVER M: Jiri Pirko L: netdev@vger.kernel.org @@ -7021,6 +7330,16 @@ F: include/linux/serial_core.h F: include/linux/serial.h F: include/linux/tty.h +TUA9001 MEDIA DRIVER +M: Antti Palosaari +L: linux-media@vger.kernel.org +W: http://linuxtv.org/ +W: http://palosaari.fi/linux/ +Q: http://patchwork.linuxtv.org/project/linux-media/list/ +T: git git://linuxtv.org/anttip/media_tree.git +S: Maintained +F: drivers/media/tuners/tua9001* + TULIP NETWORK DRIVERS M: Grant Grundler L: netdev@vger.kernel.org @@ -7193,15 +7512,6 @@ S: Maintained F: Documentation/usb/ehci.txt F: drivers/usb/host/ehci* -USB ET61X[12]51 DRIVER -M: Luca Risolia -L: linux-usb@vger.kernel.org -L: linux-media@vger.kernel.org -T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git -W: http://www.linux-projects.org -S: Maintained -F: drivers/media/video/et61x251/ - USB GADGET/PERIPHERAL SUBSYSTEM M: Felipe Balbi L: linux-usb@vger.kernel.org @@ -7371,7 +7681,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git W: http://www.linux-projects.org S: Maintained F: Documentation/video4linux/sn9c102.txt -F: drivers/media/video/sn9c102/ +F: drivers/media/usb/sn9c102/ USB SUBSYSTEM M: Greg Kroah-Hartman @@ -7406,17 +7716,7 @@ L: linux-media@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git W: http://www.ideasonboard.org/uvc/ S: Maintained -F: drivers/media/video/uvc/ - -USB W996[87]CF DRIVER -M: Luca Risolia -L: linux-usb@vger.kernel.org -L: linux-media@vger.kernel.org -T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git -W: http://www.linux-projects.org -S: Maintained -F: Documentation/video4linux/w9968cf.txt -F: drivers/media/video/w996* +F: drivers/media/usb/uvc/ USB WIRELESS RNDIS DRIVER (rndis_wlan) M: Jussi Kivilinna @@ -7445,7 +7745,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media.git W: http://royale.zerezo.com/zr364xx/ S: Maintained F: Documentation/video4linux/zr364xx.txt -F: drivers/media/video/zr364xx.c +F: drivers/media/usb/zr364xx/ USER-MODE LINUX (UML) M: Jeff Dike @@ -7503,7 +7803,7 @@ M: Marek Szyprowski M: Kyungmin Park L: linux-media@vger.kernel.org S: Maintained -F: drivers/media/video/videobuf2-* +F: drivers/media/v4l2-core/videobuf2-* F: include/media/videobuf2-* VIRTIO CONSOLE DRIVER @@ -7912,7 +8212,7 @@ L: linux-media@vger.kernel.org W: http://mjpeg.sourceforge.net/driver-zoran/ T: Mercurial http://linuxtv.org/hg/v4l-dvb S: Odd Fixes -F: drivers/media/video/zoran/ +F: drivers/media/pci/zoran/ ZS DECSTATION Z85C30 SERIAL DRIVER M: "Maciej W. Rozycki" diff --git a/arch/arm/mach-imx/clk-imx27.c b/arch/arm/mach-imx/clk-imx27.c index f69ca4680049..3b6b640eed24 100644 --- a/arch/arm/mach-imx/clk-imx27.c +++ b/arch/arm/mach-imx/clk-imx27.c @@ -239,8 +239,8 @@ int __init mx27_clocks_init(unsigned long fref) clk_register_clkdev(clk[ssi1_ipg_gate], NULL, "imx-ssi.0"); clk_register_clkdev(clk[ssi2_ipg_gate], NULL, "imx-ssi.1"); clk_register_clkdev(clk[nfc_baud_gate], NULL, "mxc_nand.0"); - clk_register_clkdev(clk[vpu_baud_gate], "per", "imx-vpu"); - clk_register_clkdev(clk[vpu_ahb_gate], "ahb", "imx-vpu"); + clk_register_clkdev(clk[vpu_baud_gate], "per", "coda-imx27.0"); + clk_register_clkdev(clk[vpu_ahb_gate], "ahb", "coda-imx27.0"); clk_register_clkdev(clk[dma_ahb_gate], "ahb", "imx-dma"); clk_register_clkdev(clk[dma_ipg_gate], "ipg", "imx-dma"); clk_register_clkdev(clk[fec_ipg_gate], "ipg", "imx27-fec.0"); diff --git a/arch/arm/mach-imx/devices-imx27.h b/arch/arm/mach-imx/devices-imx27.h index 436c5720fe6a..04822932cdd1 100644 --- a/arch/arm/mach-imx/devices-imx27.h +++ b/arch/arm/mach-imx/devices-imx27.h @@ -17,6 +17,10 @@ extern const struct imx_fsl_usb2_udc_data imx27_fsl_usb2_udc_data; #define imx27_add_fsl_usb2_udc(pdata) \ imx_add_fsl_usb2_udc(&imx27_fsl_usb2_udc_data, pdata) +extern const struct imx_imx27_coda_data imx27_coda_data; +#define imx27_add_coda() \ + imx_add_imx27_coda(&imx27_coda_data) + extern const struct imx_imx2_wdt_data imx27_imx2_wdt_data; #define imx27_add_imx2_wdt() \ imx_add_imx2_wdt(&imx27_imx2_wdt_data) diff --git a/arch/arm/mach-imx/mach-imx27_visstrim_m10.c b/arch/arm/mach-imx/mach-imx27_visstrim_m10.c index f264ddddd47c..821d6aac411c 100644 --- a/arch/arm/mach-imx/mach-imx27_visstrim_m10.c +++ b/arch/arm/mach-imx/mach-imx27_visstrim_m10.c @@ -32,13 +32,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include #include #include @@ -233,10 +233,8 @@ static void __init visstrim_camera_init(void) static void __init visstrim_reserve(void) { /* reserve 4 MiB for mx2-camera */ - mx2_camera_base = memblock_alloc(MX2_CAMERA_BUF_SIZE, + mx2_camera_base = arm_memblock_steal(3 * MX2_CAMERA_BUF_SIZE, MX2_CAMERA_BUF_SIZE); - memblock_free(mx2_camera_base, MX2_CAMERA_BUF_SIZE); - memblock_remove(mx2_camera_base, MX2_CAMERA_BUF_SIZE); } /* GPIOs used as events for applications */ @@ -405,6 +403,47 @@ static const struct imx_ssi_platform_data visstrim_m10_ssi_pdata __initconst = { .flags = IMX_SSI_DMA | IMX_SSI_SYN, }; +/* coda */ + +static void __init visstrim_coda_init(void) +{ + struct platform_device *pdev; + int dma; + + pdev = imx27_add_coda(); + dma = dma_declare_coherent_memory(&pdev->dev, + mx2_camera_base + MX2_CAMERA_BUF_SIZE, + mx2_camera_base + MX2_CAMERA_BUF_SIZE, + MX2_CAMERA_BUF_SIZE, + DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE); + if (!(dma & DMA_MEMORY_MAP)) + return; +} + +/* DMA deinterlace */ +static struct platform_device visstrim_deinterlace = { + .name = "m2m-deinterlace", + .id = 0, +}; + +static void __init visstrim_deinterlace_init(void) +{ + int ret = -ENOMEM; + struct platform_device *pdev = &visstrim_deinterlace; + int dma; + + ret = platform_device_register(pdev); + + dma = dma_declare_coherent_memory(&pdev->dev, + mx2_camera_base + 2 * MX2_CAMERA_BUF_SIZE, + mx2_camera_base + 2 * MX2_CAMERA_BUF_SIZE, + MX2_CAMERA_BUF_SIZE, + DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE); + if (!(dma & DMA_MEMORY_MAP)) + return; +} + + static void __init visstrim_m10_revision(void) { int exp_version = 0; @@ -467,7 +506,9 @@ static void __init visstrim_m10_board_init(void) platform_device_register_resndata(NULL, "soc-camera-pdrv", 0, NULL, 0, &iclink_tvp5150, sizeof(iclink_tvp5150)); gpio_led_register_device(0, &visstrim_m10_led_data); + visstrim_deinterlace_init(); visstrim_camera_init(); + visstrim_coda_init(); } static void __init visstrim_m10_timer_init(void) diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index 3945c5017085..ed85fb898c7f 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -33,6 +33,7 @@ #include "common.h" #include #include +#include #include "gpmc-smc91x.h" #include "board-rx51.h" @@ -46,6 +47,10 @@ #include <../drivers/staging/iio/light/tsl2563.h> #include +#if defined(CONFIG_IR_RX51) || defined(CONFIG_IR_RX51_MODULE) +#include +#endif + #include "mux.h" #include "hsmmc.h" #include "common-board-devices.h" @@ -1217,6 +1222,30 @@ static void __init rx51_init_tsc2005(void) gpio_to_irq(RX51_TSC2005_IRQ_GPIO); } +#if defined(CONFIG_IR_RX51) || defined(CONFIG_IR_RX51_MODULE) +static struct lirc_rx51_platform_data rx51_lirc_data = { + .set_max_mpu_wakeup_lat = omap_pm_set_max_mpu_wakeup_lat, + .pwm_timer = 9, /* Use GPT 9 for CIR */ +}; + +static struct platform_device rx51_lirc_device = { + .name = "lirc_rx51", + .id = -1, + .dev = { + .platform_data = &rx51_lirc_data, + }, +}; + +static void __init rx51_init_lirc(void) +{ + platform_device_register(&rx51_lirc_device); +} +#else +static void __init rx51_init_lirc(void) +{ +} +#endif + void __init rx51_peripherals_init(void) { rx51_i2c_init(); @@ -1227,6 +1256,7 @@ void __init rx51_peripherals_init(void) rx51_init_wl1251(); rx51_init_tsc2005(); rx51_init_si4713(); + rx51_init_lirc(); spi_register_board_info(rx51_peripherals_spi_board_info, ARRAY_SIZE(rx51_peripherals_spi_board_info)); diff --git a/arch/arm/plat-mxc/devices/Kconfig b/arch/arm/plat-mxc/devices/Kconfig index cb3e3eef55c0..6b46cee2f9cd 100644 --- a/arch/arm/plat-mxc/devices/Kconfig +++ b/arch/arm/plat-mxc/devices/Kconfig @@ -15,7 +15,11 @@ config IMX_HAVE_PLATFORM_GPIO_KEYS config IMX_HAVE_PLATFORM_IMX21_HCD bool - + +config IMX_HAVE_PLATFORM_IMX27_CODA + bool + default y if SOC_IMX27 + config IMX_HAVE_PLATFORM_IMX2_WDT bool diff --git a/arch/arm/plat-mxc/devices/Makefile b/arch/arm/plat-mxc/devices/Makefile index c11ac8472beb..76f3195475d0 100644 --- a/arch/arm/plat-mxc/devices/Makefile +++ b/arch/arm/plat-mxc/devices/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_IMX_HAVE_PLATFORM_FSL_USB2_UDC) += platform-fsl-usb2-udc.o obj-$(CONFIG_IMX_HAVE_PLATFORM_GPIO_KEYS) += platform-gpio_keys.o obj-y += platform-gpio-mxc.o obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX21_HCD) += platform-imx21-hcd.o +obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX27_CODA) += platform-imx27-coda.o obj-$(CONFIG_IMX_HAVE_PLATFORM_IMX2_WDT) += platform-imx2-wdt.o obj-$(CONFIG_IMX_HAVE_PLATFORM_IMXDI_RTC) += platform-imxdi_rtc.o obj-y += platform-imx-dma.o diff --git a/arch/arm/plat-mxc/devices/platform-imx27-coda.c b/arch/arm/plat-mxc/devices/platform-imx27-coda.c new file mode 100644 index 000000000000..8b12aacdf396 --- /dev/null +++ b/arch/arm/plat-mxc/devices/platform-imx27-coda.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 Vista Silicon + * Javier Martin + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by the + * Free Software Foundation. + */ + +#include +#include + +#ifdef CONFIG_SOC_IMX27 +const struct imx_imx27_coda_data imx27_coda_data __initconst = { + .iobase = MX27_VPU_BASE_ADDR, + .iosize = SZ_512, + .irq = MX27_INT_VPU, +}; +#endif + +struct platform_device *__init imx_add_imx27_coda( + const struct imx_imx27_coda_data *data) +{ + struct resource res[] = { + { + .start = data->iobase, + .end = data->iobase + data->iosize - 1, + .flags = IORESOURCE_MEM, + }, { + .start = data->irq, + .end = data->irq, + .flags = IORESOURCE_IRQ, + }, + }; + return imx_add_platform_device_dmamask("coda-imx27", 0, res, 2, NULL, + 0, DMA_BIT_MASK(32)); +} diff --git a/arch/arm/plat-mxc/include/mach/devices-common.h b/arch/arm/plat-mxc/include/mach/devices-common.h index 9e3e3d8ae8c2..eaf79d220c9a 100644 --- a/arch/arm/plat-mxc/include/mach/devices-common.h +++ b/arch/arm/plat-mxc/include/mach/devices-common.h @@ -83,6 +83,14 @@ struct platform_device *__init imx_add_imx21_hcd( const struct imx_imx21_hcd_data *data, const struct mx21_usbh_platform_data *pdata); +struct imx_imx27_coda_data { + resource_size_t iobase; + resource_size_t iosize; + resource_size_t irq; +}; +struct platform_device *__init imx_add_imx27_coda( + const struct imx_imx27_coda_data *data); + struct imx_imx2_wdt_data { int id; resource_size_t iobase; diff --git a/drivers/gpio/gpio-bt8xx.c b/drivers/gpio/gpio-bt8xx.c index aba97abda77c..7d9d7cb35f28 100644 --- a/drivers/gpio/gpio-bt8xx.c +++ b/drivers/gpio/gpio-bt8xx.c @@ -50,7 +50,7 @@ #include /* Steal the hardware definitions from the bttv driver. */ -#include "../media/video/bt8xx/bt848.h" +#include "../media/pci/bt8xx/bt848.h" #define BT8XXGPIO_NR_GPIOS 24 /* We have 24 GPIO pins */ diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index d941581ab921..dd13e3a4c272 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -99,11 +99,6 @@ config VIDEO_DEV depends on MEDIA_CAMERA_SUPPORT || MEDIA_ANALOG_TV_SUPPORT || MEDIA_RADIO_SUPPORT default y -config VIDEO_V4L2_COMMON - tristate - depends on (I2C || I2C=n) && VIDEO_DEV - default (I2C || I2C=n) && VIDEO_DEV - config VIDEO_V4L2_SUBDEV_API bool "V4L2 sub-device userspace API (EXPERIMENTAL)" depends on VIDEO_DEV && MEDIA_CONTROLLER && EXPERIMENTAL @@ -113,6 +108,8 @@ config VIDEO_V4L2_SUBDEV_API This API is mostly used by camera interfaces in embedded platforms. +source "drivers/media/v4l2-core/Kconfig" + # # DVB Core # Only enables if one of DTV is selected @@ -138,28 +135,54 @@ config DVB_NET You may want to disable the network support on embedded devices. If unsure say Y. +source "drivers/media/dvb-core/Kconfig" + comment "Media drivers" -source "drivers/media/common/Kconfig" source "drivers/media/rc/Kconfig" # -# Tuner drivers for DVB and V4L +# V4L platform/mem2mem drivers # -source "drivers/media/common/tuners/Kconfig" - -# -# Video/Radio/Hybrid adapters -# - -source "drivers/media/video/Kconfig" - +source "drivers/media/usb/Kconfig" +source "drivers/media/pci/Kconfig" +source "drivers/media/platform/Kconfig" +source "drivers/media/mmc/Kconfig" +source "drivers/media/parport/Kconfig" source "drivers/media/radio/Kconfig" +comment "Supported FireWire (IEEE 1394) Adapters" + depends on DVB_CORE && FIREWIRE +source "drivers/media/firewire/Kconfig" + +# Common driver options +source "drivers/media/common/Kconfig" + # -# DVB adapters +# Ancillary drivers (tuners, i2c, frontends) # -source "drivers/media/dvb/Kconfig" +config MEDIA_SUBDRV_AUTOSELECT + bool "Autoselect analog and hybrid tuner modules to build" + depends on MEDIA_TUNER + default y + help + By default, a TV driver auto-selects all possible tuners + thar could be used by the driver. + + This is generally the right thing to do, except when there + are strict constraints with regards to the kernel size. + + Use this option with care, as deselecting tuner drivers which + are in fact necessary will result in TV devices which cannot + be tuned due to lack of the tuning driver. + + If unsure say Y. + +comment "Media ancillary drivers (tuners, sensors, i2c, frontends)" + +source "drivers/media/i2c/Kconfig" +source "drivers/media/tuners/Kconfig" +source "drivers/media/dvb-frontends/Kconfig" endif # MEDIA_SUPPORT diff --git a/drivers/media/Makefile b/drivers/media/Makefile index 64755c99ded2..620f275a45c9 100644 --- a/drivers/media/Makefile +++ b/drivers/media/Makefile @@ -4,11 +4,30 @@ media-objs := media-device.o media-devnode.o media-entity.o +# +# I2C drivers should come before other drivers, otherwise they'll fail +# when compiled as builtin drivers +# +obj-y += i2c/ tuners/ +obj-$(CONFIG_DVB_CORE) += dvb-frontends/ + +# +# Now, let's link-in the media core +# ifeq ($(CONFIG_MEDIA_CONTROLLER),y) obj-$(CONFIG_MEDIA_SUPPORT) += media.o endif -obj-y += common/ rc/ video/ +obj-$(CONFIG_VIDEO_DEV) += v4l2-core/ +obj-$(CONFIG_DVB_CORE) += dvb-core/ +# There are both core and drivers at RC subtree - merge before drivers +obj-y += rc/ + +# +# Finally, merge the drivers that require the core +# + +obj-y += common/ platform/ pci/ usb/ mmc/ firewire/ parport/ obj-$(CONFIG_VIDEO_DEV) += radio/ -obj-$(CONFIG_DVB_CORE) += dvb/ + diff --git a/drivers/media/common/Kconfig b/drivers/media/common/Kconfig index 769c6f8142d2..121b0110af3c 100644 --- a/drivers/media/common/Kconfig +++ b/drivers/media/common/Kconfig @@ -1,9 +1,3 @@ -config VIDEO_SAA7146 - tristate - depends on I2C && PCI - -config VIDEO_SAA7146_VV - tristate - depends on VIDEO_V4L2 - select VIDEOBUF_DMA_SG - select VIDEO_SAA7146 +source "drivers/media/common/b2c2/Kconfig" +source "drivers/media/common/saa7146/Kconfig" +source "drivers/media/common/siano/Kconfig" diff --git a/drivers/media/common/Makefile b/drivers/media/common/Makefile index e3ec9639321b..b8e2e3a33a31 100644 --- a/drivers/media/common/Makefile +++ b/drivers/media/common/Makefile @@ -1,6 +1 @@ -saa7146-objs := saa7146_i2c.o saa7146_core.o -saa7146_vv-objs := saa7146_fops.o saa7146_video.o saa7146_hlp.o saa7146_vbi.o - -obj-y += tuners/ -obj-$(CONFIG_VIDEO_SAA7146) += saa7146.o -obj-$(CONFIG_VIDEO_SAA7146_VV) += saa7146_vv.o +obj-y += b2c2/ saa7146/ siano/ diff --git a/drivers/media/common/b2c2/Kconfig b/drivers/media/common/b2c2/Kconfig new file mode 100644 index 000000000000..1df9e578daa5 --- /dev/null +++ b/drivers/media/common/b2c2/Kconfig @@ -0,0 +1,28 @@ +config DVB_B2C2_FLEXCOP + tristate + depends on DVB_CORE && I2C + depends on DVB_B2C2_FLEXCOP_PCI || DVB_B2C2_FLEXCOP_USB + default y + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MT312 if MEDIA_SUBDRV_AUTOSELECT + select DVB_NXT200X if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0297 if MEDIA_SUBDRV_AUTOSELECT + select DVB_BCM3510 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1420 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TUNER_ITD1000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6421 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24123 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT + select DVB_TUNER_CX24113 if MEDIA_SUBDRV_AUTOSELECT + help + Support for the digital TV receiver chip made by B2C2 Inc. included in + Technisats PCI cards and USB boxes. + + Say Y if you own such a device and want to use it. + +# Selected via the PCI or USB flexcop drivers +config DVB_B2C2_FLEXCOP_DEBUG + bool diff --git a/drivers/media/common/b2c2/Makefile b/drivers/media/common/b2c2/Makefile new file mode 100644 index 000000000000..24993a5b38ba --- /dev/null +++ b/drivers/media/common/b2c2/Makefile @@ -0,0 +1,8 @@ +b2c2-flexcop-objs += flexcop.o flexcop-fe-tuner.o flexcop-i2c.o +b2c2-flexcop-objs += flexcop-sram.o flexcop-eeprom.o flexcop-misc.o +b2c2-flexcop-objs += flexcop-hw-filter.o +obj-$(CONFIG_DVB_B2C2_FLEXCOP) += b2c2-flexcop.o + +ccflags-y += -Idrivers/media/dvb-core/ +ccflags-y += -Idrivers/media/dvb-frontends/ +ccflags-y += -Idrivers/media/tuners/ diff --git a/drivers/media/dvb/b2c2/flexcop-common.h b/drivers/media/common/b2c2/flexcop-common.h similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-common.h rename to drivers/media/common/b2c2/flexcop-common.h diff --git a/drivers/media/dvb/b2c2/flexcop-eeprom.c b/drivers/media/common/b2c2/flexcop-eeprom.c similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-eeprom.c rename to drivers/media/common/b2c2/flexcop-eeprom.c diff --git a/drivers/media/dvb/b2c2/flexcop-fe-tuner.c b/drivers/media/common/b2c2/flexcop-fe-tuner.c similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-fe-tuner.c rename to drivers/media/common/b2c2/flexcop-fe-tuner.c diff --git a/drivers/media/dvb/b2c2/flexcop-hw-filter.c b/drivers/media/common/b2c2/flexcop-hw-filter.c similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-hw-filter.c rename to drivers/media/common/b2c2/flexcop-hw-filter.c diff --git a/drivers/media/dvb/b2c2/flexcop-i2c.c b/drivers/media/common/b2c2/flexcop-i2c.c similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-i2c.c rename to drivers/media/common/b2c2/flexcop-i2c.c diff --git a/drivers/media/dvb/b2c2/flexcop-misc.c b/drivers/media/common/b2c2/flexcop-misc.c similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-misc.c rename to drivers/media/common/b2c2/flexcop-misc.c diff --git a/drivers/media/dvb/b2c2/flexcop-reg.h b/drivers/media/common/b2c2/flexcop-reg.h similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-reg.h rename to drivers/media/common/b2c2/flexcop-reg.h diff --git a/drivers/media/dvb/b2c2/flexcop-sram.c b/drivers/media/common/b2c2/flexcop-sram.c similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-sram.c rename to drivers/media/common/b2c2/flexcop-sram.c diff --git a/drivers/media/common/b2c2/flexcop.c b/drivers/media/common/b2c2/flexcop.c new file mode 100644 index 000000000000..412c5daf2b48 --- /dev/null +++ b/drivers/media/common/b2c2/flexcop.c @@ -0,0 +1,325 @@ +/* + * Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III + * flexcop.c - main module part + * Copyright (C) 2004-9 Patrick Boettcher + * based on skystar2-driver Copyright (C) 2003 Vadim Catana, skystar@moldova.cc + * + * Acknowledgements: + * John Jurrius from BBTI, Inc. for extensive support + * with code examples and data books + * Bjarne Steinsbo, bjarne at steinsbo.com (some ideas for rewriting) + * + * Contributions to the skystar2-driver have been done by + * Vincenzo Di Massa, hawk.it at tiscalinet.it (several DiSEqC fixes) + * Roberto Ragusa, r.ragusa at libero.it (polishing, restyling the code) + * Uwe Bugla, uwe.bugla at gmx.de (doing tests, restyling code, writing docu) + * Niklas Peinecke, peinecke at gdv.uni-hannover.de (hardware pid/mac + * filtering) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser 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 "flexcop.h" + +#define DRIVER_NAME "B2C2 FlexcopII/II(b)/III digital TV receiver chip" +#define DRIVER_AUTHOR "Patrick Boettcher demux->priv; + return flexcop_pid_feed_control(fc, dvbdmxfeed, 1); +} + +static int flexcop_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct flexcop_device *fc = dvbdmxfeed->demux->priv; + return flexcop_pid_feed_control(fc, dvbdmxfeed, 0); +} + +static int flexcop_dvb_init(struct flexcop_device *fc) +{ + int ret = dvb_register_adapter(&fc->dvb_adapter, + "FlexCop Digital TV device", fc->owner, + fc->dev, adapter_nr); + if (ret < 0) { + err("error registering DVB adapter"); + return ret; + } + fc->dvb_adapter.priv = fc; + + fc->demux.dmx.capabilities = (DMX_TS_FILTERING | DMX_SECTION_FILTERING + | DMX_MEMORY_BASED_FILTERING); + fc->demux.priv = fc; + fc->demux.filternum = fc->demux.feednum = FC_MAX_FEED; + fc->demux.start_feed = flexcop_dvb_start_feed; + fc->demux.stop_feed = flexcop_dvb_stop_feed; + fc->demux.write_to_decoder = NULL; + + ret = dvb_dmx_init(&fc->demux); + if (ret < 0) { + err("dvb_dmx failed: error %d", ret); + goto err_dmx; + } + + fc->hw_frontend.source = DMX_FRONTEND_0; + + fc->dmxdev.filternum = fc->demux.feednum; + fc->dmxdev.demux = &fc->demux.dmx; + fc->dmxdev.capabilities = 0; + ret = dvb_dmxdev_init(&fc->dmxdev, &fc->dvb_adapter); + if (ret < 0) { + err("dvb_dmxdev_init failed: error %d", ret); + goto err_dmx_dev; + } + + ret = fc->demux.dmx.add_frontend(&fc->demux.dmx, &fc->hw_frontend); + if (ret < 0) { + err("adding hw_frontend to dmx failed: error %d", ret); + goto err_dmx_add_hw_frontend; + } + + fc->mem_frontend.source = DMX_MEMORY_FE; + ret = fc->demux.dmx.add_frontend(&fc->demux.dmx, &fc->mem_frontend); + if (ret < 0) { + err("adding mem_frontend to dmx failed: error %d", ret); + goto err_dmx_add_mem_frontend; + } + + ret = fc->demux.dmx.connect_frontend(&fc->demux.dmx, &fc->hw_frontend); + if (ret < 0) { + err("connect frontend failed: error %d", ret); + goto err_connect_frontend; + } + + ret = dvb_net_init(&fc->dvb_adapter, &fc->dvbnet, &fc->demux.dmx); + if (ret < 0) { + err("dvb_net_init failed: error %d", ret); + goto err_net; + } + + fc->init_state |= FC_STATE_DVB_INIT; + return 0; + +err_net: + fc->demux.dmx.disconnect_frontend(&fc->demux.dmx); +err_connect_frontend: + fc->demux.dmx.remove_frontend(&fc->demux.dmx, &fc->mem_frontend); +err_dmx_add_mem_frontend: + fc->demux.dmx.remove_frontend(&fc->demux.dmx, &fc->hw_frontend); +err_dmx_add_hw_frontend: + dvb_dmxdev_release(&fc->dmxdev); +err_dmx_dev: + dvb_dmx_release(&fc->demux); +err_dmx: + dvb_unregister_adapter(&fc->dvb_adapter); + return ret; +} + +static void flexcop_dvb_exit(struct flexcop_device *fc) +{ + if (fc->init_state & FC_STATE_DVB_INIT) { + dvb_net_release(&fc->dvbnet); + + fc->demux.dmx.close(&fc->demux.dmx); + fc->demux.dmx.remove_frontend(&fc->demux.dmx, + &fc->mem_frontend); + fc->demux.dmx.remove_frontend(&fc->demux.dmx, + &fc->hw_frontend); + dvb_dmxdev_release(&fc->dmxdev); + dvb_dmx_release(&fc->demux); + dvb_unregister_adapter(&fc->dvb_adapter); + deb_info("deinitialized dvb stuff\n"); + } + fc->init_state &= ~FC_STATE_DVB_INIT; +} + +/* these methods are necessary to achieve the long-term-goal of hiding the + * struct flexcop_device from the bus-parts */ +void flexcop_pass_dmx_data(struct flexcop_device *fc, u8 *buf, u32 len) +{ + dvb_dmx_swfilter(&fc->demux, buf, len); +} +EXPORT_SYMBOL(flexcop_pass_dmx_data); + +void flexcop_pass_dmx_packets(struct flexcop_device *fc, u8 *buf, u32 no) +{ + dvb_dmx_swfilter_packets(&fc->demux, buf, no); +} +EXPORT_SYMBOL(flexcop_pass_dmx_packets); + +static void flexcop_reset(struct flexcop_device *fc) +{ + flexcop_ibi_value v210, v204; + + /* reset the flexcop itself */ + fc->write_ibi_reg(fc,ctrl_208,ibi_zero); + + v210.raw = 0; + v210.sw_reset_210.reset_block_000 = 1; + v210.sw_reset_210.reset_block_100 = 1; + v210.sw_reset_210.reset_block_200 = 1; + v210.sw_reset_210.reset_block_300 = 1; + v210.sw_reset_210.reset_block_400 = 1; + v210.sw_reset_210.reset_block_500 = 1; + v210.sw_reset_210.reset_block_600 = 1; + v210.sw_reset_210.reset_block_700 = 1; + v210.sw_reset_210.Block_reset_enable = 0xb2; + v210.sw_reset_210.Special_controls = 0xc259; + fc->write_ibi_reg(fc,sw_reset_210,v210); + msleep(1); + + /* reset the periphical devices */ + + v204 = fc->read_ibi_reg(fc,misc_204); + v204.misc_204.Per_reset_sig = 0; + fc->write_ibi_reg(fc,misc_204,v204); + msleep(1); + v204.misc_204.Per_reset_sig = 1; + fc->write_ibi_reg(fc,misc_204,v204); +} + +void flexcop_reset_block_300(struct flexcop_device *fc) +{ + flexcop_ibi_value v208_save = fc->read_ibi_reg(fc, ctrl_208), + v210 = fc->read_ibi_reg(fc, sw_reset_210); + + deb_rdump("208: %08x, 210: %08x\n", v208_save.raw, v210.raw); + fc->write_ibi_reg(fc,ctrl_208,ibi_zero); + + v210.sw_reset_210.reset_block_300 = 1; + v210.sw_reset_210.Block_reset_enable = 0xb2; + + fc->write_ibi_reg(fc,sw_reset_210,v210); + fc->write_ibi_reg(fc,ctrl_208,v208_save); +} + +struct flexcop_device *flexcop_device_kmalloc(size_t bus_specific_len) +{ + void *bus; + struct flexcop_device *fc = kzalloc(sizeof(struct flexcop_device), + GFP_KERNEL); + if (!fc) { + err("no memory"); + return NULL; + } + + bus = kzalloc(bus_specific_len, GFP_KERNEL); + if (!bus) { + err("no memory"); + kfree(fc); + return NULL; + } + + fc->bus_specific = bus; + + return fc; +} +EXPORT_SYMBOL(flexcop_device_kmalloc); + +void flexcop_device_kfree(struct flexcop_device *fc) +{ + kfree(fc->bus_specific); + kfree(fc); +} +EXPORT_SYMBOL(flexcop_device_kfree); + +int flexcop_device_initialize(struct flexcop_device *fc) +{ + int ret; + ibi_zero.raw = 0; + + flexcop_reset(fc); + flexcop_determine_revision(fc); + flexcop_sram_init(fc); + flexcop_hw_filter_init(fc); + flexcop_smc_ctrl(fc, 0); + + ret = flexcop_dvb_init(fc); + if (ret) + goto error; + + /* i2c has to be done before doing EEProm stuff - + * because the EEProm is accessed via i2c */ + ret = flexcop_i2c_init(fc); + if (ret) + goto error; + + /* do the MAC address reading after initializing the dvb_adapter */ + if (fc->get_mac_addr(fc, 0) == 0) { + u8 *b = fc->dvb_adapter.proposed_mac; + info("MAC address = %pM", b); + flexcop_set_mac_filter(fc,b); + flexcop_mac_filter_ctrl(fc,1); + } else + warn("reading of MAC address failed.\n"); + + ret = flexcop_frontend_init(fc); + if (ret) + goto error; + + flexcop_device_name(fc,"initialization of","complete"); + return 0; + +error: + flexcop_device_exit(fc); + return ret; +} +EXPORT_SYMBOL(flexcop_device_initialize); + +void flexcop_device_exit(struct flexcop_device *fc) +{ + flexcop_frontend_exit(fc); + flexcop_i2c_exit(fc); + flexcop_dvb_exit(fc); +} +EXPORT_SYMBOL(flexcop_device_exit); + +static int flexcop_module_init(void) +{ + info(DRIVER_NAME " loaded successfully"); + return 0; +} + +static void flexcop_module_cleanup(void) +{ + info(DRIVER_NAME " unloaded successfully"); +} + +module_init(flexcop_module_init); +module_exit(flexcop_module_cleanup); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_NAME); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/b2c2/flexcop.h b/drivers/media/common/b2c2/flexcop.h similarity index 100% rename from drivers/media/dvb/b2c2/flexcop.h rename to drivers/media/common/b2c2/flexcop.h diff --git a/drivers/media/dvb/b2c2/flexcop_ibi_value_be.h b/drivers/media/common/b2c2/flexcop_ibi_value_be.h similarity index 100% rename from drivers/media/dvb/b2c2/flexcop_ibi_value_be.h rename to drivers/media/common/b2c2/flexcop_ibi_value_be.h diff --git a/drivers/media/dvb/b2c2/flexcop_ibi_value_le.h b/drivers/media/common/b2c2/flexcop_ibi_value_le.h similarity index 100% rename from drivers/media/dvb/b2c2/flexcop_ibi_value_le.h rename to drivers/media/common/b2c2/flexcop_ibi_value_le.h diff --git a/drivers/media/common/saa7146/Kconfig b/drivers/media/common/saa7146/Kconfig new file mode 100644 index 000000000000..769c6f8142d2 --- /dev/null +++ b/drivers/media/common/saa7146/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_SAA7146 + tristate + depends on I2C && PCI + +config VIDEO_SAA7146_VV + tristate + depends on VIDEO_V4L2 + select VIDEOBUF_DMA_SG + select VIDEO_SAA7146 diff --git a/drivers/media/common/saa7146/Makefile b/drivers/media/common/saa7146/Makefile new file mode 100644 index 000000000000..3219b00a8771 --- /dev/null +++ b/drivers/media/common/saa7146/Makefile @@ -0,0 +1,5 @@ +saa7146-objs := saa7146_i2c.o saa7146_core.o +saa7146_vv-objs := saa7146_fops.o saa7146_video.o saa7146_hlp.o saa7146_vbi.o + +obj-$(CONFIG_VIDEO_SAA7146) += saa7146.o +obj-$(CONFIG_VIDEO_SAA7146_VV) += saa7146_vv.o diff --git a/drivers/media/common/saa7146/saa7146_core.c b/drivers/media/common/saa7146/saa7146_core.c new file mode 100644 index 000000000000..bb6ee5191eb1 --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_core.c @@ -0,0 +1,592 @@ +/* + saa7146.o - driver for generic saa7146-based hardware + + Copyright (C) 1998-2003 Michael Hunold + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +static int saa7146_num; + +unsigned int saa7146_debug; + +module_param(saa7146_debug, uint, 0644); +MODULE_PARM_DESC(saa7146_debug, "debug level (default: 0)"); + +#if 0 +static void dump_registers(struct saa7146_dev* dev) +{ + int i = 0; + + pr_info(" @ %li jiffies:\n", jiffies); + for (i = 0; i <= 0x148; i += 4) + pr_info("0x%03x: 0x%08x\n", i, saa7146_read(dev, i)); +} +#endif + +/**************************************************************************** + * gpio and debi helper functions + ****************************************************************************/ + +void saa7146_setgpio(struct saa7146_dev *dev, int port, u32 data) +{ + u32 value = 0; + + BUG_ON(port > 3); + + value = saa7146_read(dev, GPIO_CTRL); + value &= ~(0xff << (8*port)); + value |= (data << (8*port)); + saa7146_write(dev, GPIO_CTRL, value); +} + +/* This DEBI code is based on the saa7146 Stradis driver by Nathan Laredo */ +static inline int saa7146_wait_for_debi_done_sleep(struct saa7146_dev *dev, + unsigned long us1, unsigned long us2) +{ + unsigned long timeout; + int err; + + /* wait for registers to be programmed */ + timeout = jiffies + usecs_to_jiffies(us1); + while (1) { + err = time_after(jiffies, timeout); + if (saa7146_read(dev, MC2) & 2) + break; + if (err) { + pr_err("%s: %s timed out while waiting for registers getting programmed\n", + dev->name, __func__); + return -ETIMEDOUT; + } + msleep(1); + } + + /* wait for transfer to complete */ + timeout = jiffies + usecs_to_jiffies(us2); + while (1) { + err = time_after(jiffies, timeout); + if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S)) + break; + saa7146_read(dev, MC2); + if (err) { + DEB_S("%s: %s timed out while waiting for transfer completion\n", + dev->name, __func__); + return -ETIMEDOUT; + } + msleep(1); + } + + return 0; +} + +static inline int saa7146_wait_for_debi_done_busyloop(struct saa7146_dev *dev, + unsigned long us1, unsigned long us2) +{ + unsigned long loops; + + /* wait for registers to be programmed */ + loops = us1; + while (1) { + if (saa7146_read(dev, MC2) & 2) + break; + if (!loops--) { + pr_err("%s: %s timed out while waiting for registers getting programmed\n", + dev->name, __func__); + return -ETIMEDOUT; + } + udelay(1); + } + + /* wait for transfer to complete */ + loops = us2 / 5; + while (1) { + if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S)) + break; + saa7146_read(dev, MC2); + if (!loops--) { + DEB_S("%s: %s timed out while waiting for transfer completion\n", + dev->name, __func__); + return -ETIMEDOUT; + } + udelay(5); + } + + return 0; +} + +int saa7146_wait_for_debi_done(struct saa7146_dev *dev, int nobusyloop) +{ + if (nobusyloop) + return saa7146_wait_for_debi_done_sleep(dev, 50000, 250000); + else + return saa7146_wait_for_debi_done_busyloop(dev, 50000, 250000); +} + +/**************************************************************************** + * general helper functions + ****************************************************************************/ + +/* this is videobuf_vmalloc_to_sg() from videobuf-dma-sg.c + make sure virt has been allocated with vmalloc_32(), otherwise the BUG() + may be triggered on highmem machines */ +static struct scatterlist* vmalloc_to_sg(unsigned char *virt, int nr_pages) +{ + struct scatterlist *sglist; + struct page *pg; + int i; + + sglist = kcalloc(nr_pages, sizeof(struct scatterlist), GFP_KERNEL); + if (NULL == sglist) + return NULL; + sg_init_table(sglist, nr_pages); + for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) { + pg = vmalloc_to_page(virt); + if (NULL == pg) + goto err; + BUG_ON(PageHighMem(pg)); + sg_set_page(&sglist[i], pg, PAGE_SIZE, 0); + } + return sglist; + + err: + kfree(sglist); + return NULL; +} + +/********************************************************************************/ +/* common page table functions */ + +void *saa7146_vmalloc_build_pgtable(struct pci_dev *pci, long length, struct saa7146_pgtable *pt) +{ + int pages = (length+PAGE_SIZE-1)/PAGE_SIZE; + void *mem = vmalloc_32(length); + int slen = 0; + + if (NULL == mem) + goto err_null; + + if (!(pt->slist = vmalloc_to_sg(mem, pages))) + goto err_free_mem; + + if (saa7146_pgtable_alloc(pci, pt)) + goto err_free_slist; + + pt->nents = pages; + slen = pci_map_sg(pci,pt->slist,pt->nents,PCI_DMA_FROMDEVICE); + if (0 == slen) + goto err_free_pgtable; + + if (0 != saa7146_pgtable_build_single(pci, pt, pt->slist, slen)) + goto err_unmap_sg; + + return mem; + +err_unmap_sg: + pci_unmap_sg(pci, pt->slist, pt->nents, PCI_DMA_FROMDEVICE); +err_free_pgtable: + saa7146_pgtable_free(pci, pt); +err_free_slist: + kfree(pt->slist); + pt->slist = NULL; +err_free_mem: + vfree(mem); +err_null: + return NULL; +} + +void saa7146_vfree_destroy_pgtable(struct pci_dev *pci, void *mem, struct saa7146_pgtable *pt) +{ + pci_unmap_sg(pci, pt->slist, pt->nents, PCI_DMA_FROMDEVICE); + saa7146_pgtable_free(pci, pt); + kfree(pt->slist); + pt->slist = NULL; + vfree(mem); +} + +void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt) +{ + if (NULL == pt->cpu) + return; + pci_free_consistent(pci, pt->size, pt->cpu, pt->dma); + pt->cpu = NULL; +} + +int saa7146_pgtable_alloc(struct pci_dev *pci, struct saa7146_pgtable *pt) +{ + __le32 *cpu; + dma_addr_t dma_addr = 0; + + cpu = pci_alloc_consistent(pci, PAGE_SIZE, &dma_addr); + if (NULL == cpu) { + return -ENOMEM; + } + pt->size = PAGE_SIZE; + pt->cpu = cpu; + pt->dma = dma_addr; + + return 0; +} + +int saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt, + struct scatterlist *list, int sglen ) +{ + __le32 *ptr, fill; + int nr_pages = 0; + int i,p; + + BUG_ON(0 == sglen); + BUG_ON(list->offset > PAGE_SIZE); + + /* if we have a user buffer, the first page may not be + aligned to a page boundary. */ + pt->offset = list->offset; + + ptr = pt->cpu; + for (i = 0; i < sglen; i++, list++) { +/* + pr_debug("i:%d, adr:0x%08x, len:%d, offset:%d\n", + i, sg_dma_address(list), sg_dma_len(list), + list->offset); +*/ + for (p = 0; p * 4096 < list->length; p++, ptr++) { + *ptr = cpu_to_le32(sg_dma_address(list) + p * 4096); + nr_pages++; + } + } + + + /* safety; fill the page table up with the last valid page */ + fill = *(ptr-1); + for(i=nr_pages;i<1024;i++) { + *ptr++ = fill; + } + +/* + ptr = pt->cpu; + pr_debug("offset: %d\n", pt->offset); + for(i=0;i<5;i++) { + pr_debug("ptr1 %d: 0x%08x\n", i, ptr[i]); + } +*/ + return 0; +} + +/********************************************************************************/ +/* interrupt handler */ +static irqreturn_t interrupt_hw(int irq, void *dev_id) +{ + struct saa7146_dev *dev = dev_id; + u32 isr; + u32 ack_isr; + + /* read out the interrupt status register */ + ack_isr = isr = saa7146_read(dev, ISR); + + /* is this our interrupt? */ + if ( 0 == isr ) { + /* nope, some other device */ + return IRQ_NONE; + } + + if (dev->ext) { + if (dev->ext->irq_mask & isr) { + if (dev->ext->irq_func) + dev->ext->irq_func(dev, &isr); + isr &= ~dev->ext->irq_mask; + } + } + if (0 != (isr & (MASK_27))) { + DEB_INT("irq: RPS0 (0x%08x)\n", isr); + if (dev->vv_data && dev->vv_callback) + dev->vv_callback(dev,isr); + isr &= ~MASK_27; + } + if (0 != (isr & (MASK_28))) { + if (dev->vv_data && dev->vv_callback) + dev->vv_callback(dev,isr); + isr &= ~MASK_28; + } + if (0 != (isr & (MASK_16|MASK_17))) { + SAA7146_IER_DISABLE(dev, MASK_16|MASK_17); + /* only wake up if we expect something */ + if (0 != dev->i2c_op) { + dev->i2c_op = 0; + wake_up(&dev->i2c_wq); + } else { + u32 psr = saa7146_read(dev, PSR); + u32 ssr = saa7146_read(dev, SSR); + pr_warn("%s: unexpected i2c irq: isr %08x psr %08x ssr %08x\n", + dev->name, isr, psr, ssr); + } + isr &= ~(MASK_16|MASK_17); + } + if( 0 != isr ) { + ERR("warning: interrupt enabled, but not handled properly.(0x%08x)\n", + isr); + ERR("disabling interrupt source(s)!\n"); + SAA7146_IER_DISABLE(dev,isr); + } + saa7146_write(dev, ISR, ack_isr); + return IRQ_HANDLED; +} + +/*********************************************************************************/ +/* configuration-functions */ + +static int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent) +{ + struct saa7146_pci_extension_data *pci_ext = (struct saa7146_pci_extension_data *)ent->driver_data; + struct saa7146_extension *ext = pci_ext->ext; + struct saa7146_dev *dev; + int err = -ENOMEM; + + /* clear out mem for sure */ + dev = kzalloc(sizeof(struct saa7146_dev), GFP_KERNEL); + if (!dev) { + ERR("out of memory\n"); + goto out; + } + + DEB_EE("pci:%p\n", pci); + + err = pci_enable_device(pci); + if (err < 0) { + ERR("pci_enable_device() failed\n"); + goto err_free; + } + + /* enable bus-mastering */ + pci_set_master(pci); + + dev->pci = pci; + + /* get chip-revision; this is needed to enable bug-fixes */ + dev->revision = pci->revision; + + /* remap the memory from virtual to physical address */ + + err = pci_request_region(pci, 0, "saa7146"); + if (err < 0) + goto err_disable; + + dev->mem = ioremap(pci_resource_start(pci, 0), + pci_resource_len(pci, 0)); + if (!dev->mem) { + ERR("ioremap() failed\n"); + err = -ENODEV; + goto err_release; + } + + /* we don't do a master reset here anymore, it screws up + some boards that don't have an i2c-eeprom for configuration + values */ +/* + saa7146_write(dev, MC1, MASK_31); +*/ + + /* disable all irqs */ + saa7146_write(dev, IER, 0); + + /* shut down all dma transfers and rps tasks */ + saa7146_write(dev, MC1, 0x30ff0000); + + /* clear out any rps-signals pending */ + saa7146_write(dev, MC2, 0xf8000000); + + /* request an interrupt for the saa7146 */ + err = request_irq(pci->irq, interrupt_hw, IRQF_SHARED | IRQF_DISABLED, + dev->name, dev); + if (err < 0) { + ERR("request_irq() failed\n"); + goto err_unmap; + } + + err = -ENOMEM; + + /* get memory for various stuff */ + dev->d_rps0.cpu_addr = pci_alloc_consistent(pci, SAA7146_RPS_MEM, + &dev->d_rps0.dma_handle); + if (!dev->d_rps0.cpu_addr) + goto err_free_irq; + memset(dev->d_rps0.cpu_addr, 0x0, SAA7146_RPS_MEM); + + dev->d_rps1.cpu_addr = pci_alloc_consistent(pci, SAA7146_RPS_MEM, + &dev->d_rps1.dma_handle); + if (!dev->d_rps1.cpu_addr) + goto err_free_rps0; + memset(dev->d_rps1.cpu_addr, 0x0, SAA7146_RPS_MEM); + + dev->d_i2c.cpu_addr = pci_alloc_consistent(pci, SAA7146_RPS_MEM, + &dev->d_i2c.dma_handle); + if (!dev->d_i2c.cpu_addr) + goto err_free_rps1; + memset(dev->d_i2c.cpu_addr, 0x0, SAA7146_RPS_MEM); + + /* the rest + print status message */ + + /* create a nice device name */ + sprintf(dev->name, "saa7146 (%d)", saa7146_num); + + pr_info("found saa7146 @ mem %p (revision %d, irq %d) (0x%04x,0x%04x)\n", + dev->mem, dev->revision, pci->irq, + pci->subsystem_vendor, pci->subsystem_device); + dev->ext = ext; + + mutex_init(&dev->v4l2_lock); + spin_lock_init(&dev->int_slock); + spin_lock_init(&dev->slock); + + mutex_init(&dev->i2c_lock); + + dev->module = THIS_MODULE; + init_waitqueue_head(&dev->i2c_wq); + + /* set some sane pci arbitrition values */ + saa7146_write(dev, PCI_BT_V1, 0x1c00101f); + + /* TODO: use the status code of the callback */ + + err = -ENODEV; + + if (ext->probe && ext->probe(dev)) { + DEB_D("ext->probe() failed for %p. skipping device.\n", dev); + goto err_free_i2c; + } + + if (ext->attach(dev, pci_ext)) { + DEB_D("ext->attach() failed for %p. skipping device.\n", dev); + goto err_free_i2c; + } + /* V4L extensions will set the pci drvdata to the v4l2_device in the + attach() above. So for those cards that do not use V4L we have to + set it explicitly. */ + pci_set_drvdata(pci, &dev->v4l2_dev); + + saa7146_num++; + + err = 0; +out: + return err; + +err_free_i2c: + pci_free_consistent(pci, SAA7146_RPS_MEM, dev->d_i2c.cpu_addr, + dev->d_i2c.dma_handle); +err_free_rps1: + pci_free_consistent(pci, SAA7146_RPS_MEM, dev->d_rps1.cpu_addr, + dev->d_rps1.dma_handle); +err_free_rps0: + pci_free_consistent(pci, SAA7146_RPS_MEM, dev->d_rps0.cpu_addr, + dev->d_rps0.dma_handle); +err_free_irq: + free_irq(pci->irq, (void *)dev); +err_unmap: + iounmap(dev->mem); +err_release: + pci_release_region(pci, 0); +err_disable: + pci_disable_device(pci); +err_free: + kfree(dev); + goto out; +} + +static void saa7146_remove_one(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); + struct saa7146_dev *dev = to_saa7146_dev(v4l2_dev); + struct { + void *addr; + dma_addr_t dma; + } dev_map[] = { + { dev->d_i2c.cpu_addr, dev->d_i2c.dma_handle }, + { dev->d_rps1.cpu_addr, dev->d_rps1.dma_handle }, + { dev->d_rps0.cpu_addr, dev->d_rps0.dma_handle }, + { NULL, 0 } + }, *p; + + DEB_EE("dev:%p\n", dev); + + dev->ext->detach(dev); + /* Zero the PCI drvdata after use. */ + pci_set_drvdata(pdev, NULL); + + /* shut down all video dma transfers */ + saa7146_write(dev, MC1, 0x00ff0000); + + /* disable all irqs, release irq-routine */ + saa7146_write(dev, IER, 0); + + free_irq(pdev->irq, dev); + + for (p = dev_map; p->addr; p++) + pci_free_consistent(pdev, SAA7146_RPS_MEM, p->addr, p->dma); + + iounmap(dev->mem); + pci_release_region(pdev, 0); + pci_disable_device(pdev); + kfree(dev); + + saa7146_num--; +} + +/*********************************************************************************/ +/* extension handling functions */ + +int saa7146_register_extension(struct saa7146_extension* ext) +{ + DEB_EE("ext:%p\n", ext); + + ext->driver.name = ext->name; + ext->driver.id_table = ext->pci_tbl; + ext->driver.probe = saa7146_init_one; + ext->driver.remove = saa7146_remove_one; + + pr_info("register extension '%s'\n", ext->name); + return pci_register_driver(&ext->driver); +} + +int saa7146_unregister_extension(struct saa7146_extension* ext) +{ + DEB_EE("ext:%p\n", ext); + pr_info("unregister extension '%s'\n", ext->name); + pci_unregister_driver(&ext->driver); + return 0; +} + +EXPORT_SYMBOL_GPL(saa7146_register_extension); +EXPORT_SYMBOL_GPL(saa7146_unregister_extension); + +/* misc functions used by extension modules */ +EXPORT_SYMBOL_GPL(saa7146_pgtable_alloc); +EXPORT_SYMBOL_GPL(saa7146_pgtable_free); +EXPORT_SYMBOL_GPL(saa7146_pgtable_build_single); +EXPORT_SYMBOL_GPL(saa7146_vmalloc_build_pgtable); +EXPORT_SYMBOL_GPL(saa7146_vfree_destroy_pgtable); +EXPORT_SYMBOL_GPL(saa7146_wait_for_debi_done); + +EXPORT_SYMBOL_GPL(saa7146_setgpio); + +EXPORT_SYMBOL_GPL(saa7146_i2c_adapter_prepare); + +EXPORT_SYMBOL_GPL(saa7146_debug); + +MODULE_AUTHOR("Michael Hunold "); +MODULE_DESCRIPTION("driver for generic saa7146-based hardware"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/saa7146/saa7146_fops.c b/drivers/media/common/saa7146/saa7146_fops.c new file mode 100644 index 000000000000..b3890bd49df6 --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_fops.c @@ -0,0 +1,664 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +/****************************************************************************/ +/* resource management functions, shamelessly stolen from saa7134 driver */ + +int saa7146_res_get(struct saa7146_fh *fh, unsigned int bit) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + + if (fh->resources & bit) { + DEB_D("already allocated! want: 0x%02x, cur:0x%02x\n", + bit, vv->resources); + /* have it already allocated */ + return 1; + } + + /* is it free? */ + if (vv->resources & bit) { + DEB_D("locked! vv->resources:0x%02x, we want:0x%02x\n", + vv->resources, bit); + /* no, someone else uses it */ + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + vv->resources |= bit; + DEB_D("res: get 0x%02x, cur:0x%02x\n", bit, vv->resources); + return 1; +} + +void saa7146_res_free(struct saa7146_fh *fh, unsigned int bits) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + + BUG_ON((fh->resources & bits) != bits); + + fh->resources &= ~bits; + vv->resources &= ~bits; + DEB_D("res: put 0x%02x, cur:0x%02x\n", bits, vv->resources); +} + + +/********************************************************************************/ +/* common dma functions */ + +void saa7146_dma_free(struct saa7146_dev *dev,struct videobuf_queue *q, + struct saa7146_buf *buf) +{ + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + DEB_EE("dev:%p, buf:%p\n", dev, buf); + + BUG_ON(in_interrupt()); + + videobuf_waiton(q, &buf->vb, 0, 0); + videobuf_dma_unmap(q->dev, dma); + videobuf_dma_free(dma); + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + + +/********************************************************************************/ +/* common buffer functions */ + +int saa7146_buffer_queue(struct saa7146_dev *dev, + struct saa7146_dmaqueue *q, + struct saa7146_buf *buf) +{ + assert_spin_locked(&dev->slock); + DEB_EE("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf); + + BUG_ON(!q); + + if (NULL == q->curr) { + q->curr = buf; + DEB_D("immediately activating buffer %p\n", buf); + buf->activate(dev,buf,NULL); + } else { + list_add_tail(&buf->vb.queue,&q->queue); + buf->vb.state = VIDEOBUF_QUEUED; + DEB_D("adding buffer %p to queue. (active buffer present)\n", + buf); + } + return 0; +} + +void saa7146_buffer_finish(struct saa7146_dev *dev, + struct saa7146_dmaqueue *q, + int state) +{ + assert_spin_locked(&dev->slock); + DEB_EE("dev:%p, dmaq:%p, state:%d\n", dev, q, state); + DEB_EE("q->curr:%p\n", q->curr); + + BUG_ON(!q->curr); + + /* finish current buffer */ + if (NULL == q->curr) { + DEB_D("aiii. no current buffer\n"); + return; + } + + q->curr->vb.state = state; + do_gettimeofday(&q->curr->vb.ts); + wake_up(&q->curr->vb.done); + + q->curr = NULL; +} + +void saa7146_buffer_next(struct saa7146_dev *dev, + struct saa7146_dmaqueue *q, int vbi) +{ + struct saa7146_buf *buf,*next = NULL; + + BUG_ON(!q); + + DEB_INT("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi); + + assert_spin_locked(&dev->slock); + if (!list_empty(&q->queue)) { + /* activate next one from queue */ + buf = list_entry(q->queue.next,struct saa7146_buf,vb.queue); + list_del(&buf->vb.queue); + if (!list_empty(&q->queue)) + next = list_entry(q->queue.next,struct saa7146_buf, vb.queue); + q->curr = buf; + DEB_INT("next buffer: buf:%p, prev:%p, next:%p\n", + buf, q->queue.prev, q->queue.next); + buf->activate(dev,buf,next); + } else { + DEB_INT("no next buffer. stopping.\n"); + if( 0 != vbi ) { + /* turn off video-dma3 */ + saa7146_write(dev,MC1, MASK_20); + } else { + /* nothing to do -- just prevent next video-dma1 transfer + by lowering the protection address */ + + // fixme: fix this for vflip != 0 + + saa7146_write(dev, PROT_ADDR1, 0); + saa7146_write(dev, MC2, (MASK_02|MASK_18)); + + /* write the address of the rps-program */ + saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle); + /* turn on rps */ + saa7146_write(dev, MC1, (MASK_12 | MASK_28)); + +/* + printk("vdma%d.base_even: 0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1)); + printk("vdma%d.base_odd: 0x%08x\n", 1,saa7146_read(dev,BASE_ODD1)); + printk("vdma%d.prot_addr: 0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1)); + printk("vdma%d.base_page: 0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1)); + printk("vdma%d.pitch: 0x%08x\n", 1,saa7146_read(dev,PITCH1)); + printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1)); +*/ + } + del_timer(&q->timeout); + } +} + +void saa7146_buffer_timeout(unsigned long data) +{ + struct saa7146_dmaqueue *q = (struct saa7146_dmaqueue*)data; + struct saa7146_dev *dev = q->dev; + unsigned long flags; + + DEB_EE("dev:%p, dmaq:%p\n", dev, q); + + spin_lock_irqsave(&dev->slock,flags); + if (q->curr) { + DEB_D("timeout on %p\n", q->curr); + saa7146_buffer_finish(dev,q,VIDEOBUF_ERROR); + } + + /* we don't restart the transfer here like other drivers do. when + a streaming capture is disabled, the timeout function will be + called for the current buffer. if we activate the next buffer now, + we mess up our capture logic. if a timeout occurs on another buffer, + then something is seriously broken before, so no need to buffer the + next capture IMHO... */ +/* + saa7146_buffer_next(dev,q); +*/ + spin_unlock_irqrestore(&dev->slock,flags); +} + +/********************************************************************************/ +/* file operations */ + +static int fops_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_dev *dev = video_drvdata(file); + struct saa7146_fh *fh = NULL; + int result = 0; + + DEB_EE("file:%p, dev:%s\n", file, video_device_node_name(vdev)); + + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + + DEB_D("using: %p\n", dev); + + /* check if an extension is registered */ + if( NULL == dev->ext ) { + DEB_S("no extension registered for this device\n"); + result = -ENODEV; + goto out; + } + + /* allocate per open data */ + fh = kzalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) { + DEB_S("cannot allocate memory for per open data\n"); + result = -ENOMEM; + goto out; + } + + v4l2_fh_init(&fh->fh, vdev); + + file->private_data = &fh->fh; + fh->dev = dev; + + if (vdev->vfl_type == VFL_TYPE_VBI) { + DEB_S("initializing vbi...\n"); + if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) + result = saa7146_vbi_uops.open(dev,file); + if (dev->ext_vv_data->vbi_fops.open) + dev->ext_vv_data->vbi_fops.open(file); + } else { + DEB_S("initializing video...\n"); + result = saa7146_video_uops.open(dev,file); + } + + if (0 != result) { + goto out; + } + + if( 0 == try_module_get(dev->ext->module)) { + result = -EINVAL; + goto out; + } + + result = 0; + v4l2_fh_add(&fh->fh); +out: + if (fh && result != 0) { + kfree(fh); + file->private_data = NULL; + } + mutex_unlock(vdev->lock); + return result; +} + +static int fops_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + + DEB_EE("file:%p\n", file); + + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + + if (vdev->vfl_type == VFL_TYPE_VBI) { + if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) + saa7146_vbi_uops.release(dev,file); + if (dev->ext_vv_data->vbi_fops.release) + dev->ext_vv_data->vbi_fops.release(file); + } else { + saa7146_video_uops.release(dev,file); + } + + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + module_put(dev->ext->module); + file->private_data = NULL; + kfree(fh); + + mutex_unlock(vdev->lock); + + return 0; +} + +static int fops_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + struct videobuf_queue *q; + int res; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: { + DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, vma:%p\n", + file, vma); + q = &fh->video_q; + break; + } + case VFL_TYPE_VBI: { + DEB_EE("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, vma:%p\n", + file, vma); + if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_SLICED_VBI_OUTPUT) + return -ENODEV; + q = &fh->vbi_q; + break; + } + default: + BUG(); + return 0; + } + + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + res = videobuf_mmap_mapper(q, vma); + mutex_unlock(vdev->lock); + return res; +} + +static unsigned int __fops_poll(struct file *file, struct poll_table_struct *wait) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + struct videobuf_buffer *buf = NULL; + struct videobuf_queue *q; + unsigned int res = v4l2_ctrl_poll(file, wait); + + DEB_EE("file:%p, poll:%p\n", file, wait); + + if (vdev->vfl_type == VFL_TYPE_VBI) { + if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_SLICED_VBI_OUTPUT) + return res | POLLOUT | POLLWRNORM; + if( 0 == fh->vbi_q.streaming ) + return res | videobuf_poll_stream(file, &fh->vbi_q, wait); + q = &fh->vbi_q; + } else { + DEB_D("using video queue\n"); + q = &fh->video_q; + } + + if (!list_empty(&q->stream)) + buf = list_entry(q->stream.next, struct videobuf_buffer, stream); + + if (!buf) { + DEB_D("buf == NULL!\n"); + return res | POLLERR; + } + + poll_wait(file, &buf->done, wait); + if (buf->state == VIDEOBUF_DONE || buf->state == VIDEOBUF_ERROR) { + DEB_D("poll succeeded!\n"); + return res | POLLIN | POLLRDNORM; + } + + DEB_D("nothing to poll for, buf->state:%d\n", buf->state); + return res; +} + +static unsigned int fops_poll(struct file *file, struct poll_table_struct *wait) +{ + struct video_device *vdev = video_devdata(file); + unsigned int res; + + mutex_lock(vdev->lock); + res = __fops_poll(file, wait); + mutex_unlock(vdev->lock); + return res; +} + +static ssize_t fops_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + int ret; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: +/* + DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, data:%p, count:%lun", + file, data, (unsigned long)count); +*/ + return saa7146_video_uops.read(file,data,count,ppos); + case VFL_TYPE_VBI: +/* + DEB_EE("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, data:%p, count:%lu\n", + file, data, (unsigned long)count); +*/ + if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) { + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + ret = saa7146_vbi_uops.read(file, data, count, ppos); + mutex_unlock(vdev->lock); + return ret; + } + return -EINVAL; + default: + BUG(); + return 0; + } +} + +static ssize_t fops_write(struct file *file, const char __user *data, size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_fh *fh = file->private_data; + int ret; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + return -EINVAL; + case VFL_TYPE_VBI: + if (fh->dev->ext_vv_data->vbi_fops.write) { + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + ret = fh->dev->ext_vv_data->vbi_fops.write(file, data, count, ppos); + mutex_unlock(vdev->lock); + return ret; + } + return -EINVAL; + default: + BUG(); + return -EINVAL; + } +} + +static const struct v4l2_file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = fops_open, + .release = fops_release, + .read = fops_read, + .write = fops_write, + .poll = fops_poll, + .mmap = fops_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static void vv_callback(struct saa7146_dev *dev, unsigned long status) +{ + u32 isr = status; + + DEB_INT("dev:%p, isr:0x%08x\n", dev, (u32)status); + + if (0 != (isr & (MASK_27))) { + DEB_INT("irq: RPS0 (0x%08x)\n", isr); + saa7146_video_uops.irq_done(dev,isr); + } + + if (0 != (isr & (MASK_28))) { + u32 mc2 = saa7146_read(dev, MC2); + if( 0 != (mc2 & MASK_15)) { + DEB_INT("irq: RPS1 vbi workaround (0x%08x)\n", isr); + wake_up(&dev->vv_data->vbi_wq); + saa7146_write(dev,MC2, MASK_31); + return; + } + DEB_INT("irq: RPS1 (0x%08x)\n", isr); + saa7146_vbi_uops.irq_done(dev,isr); + } +} + +static const struct v4l2_ctrl_ops saa7146_ctrl_ops = { + .s_ctrl = saa7146_s_ctrl, +}; + +int saa7146_vv_init(struct saa7146_dev* dev, struct saa7146_ext_vv *ext_vv) +{ + struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; + struct v4l2_pix_format *fmt; + struct v4l2_vbi_format *vbi; + struct saa7146_vv *vv; + int err; + + err = v4l2_device_register(&dev->pci->dev, &dev->v4l2_dev); + if (err) + return err; + + v4l2_ctrl_handler_init(hdl, 6); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (hdl->error) { + err = hdl->error; + v4l2_ctrl_handler_free(hdl); + return err; + } + dev->v4l2_dev.ctrl_handler = hdl; + + vv = kzalloc(sizeof(struct saa7146_vv), GFP_KERNEL); + if (vv == NULL) { + ERR("out of memory. aborting.\n"); + v4l2_ctrl_handler_free(hdl); + return -ENOMEM; + } + ext_vv->vid_ops = saa7146_video_ioctl_ops; + ext_vv->vbi_ops = saa7146_vbi_ioctl_ops; + ext_vv->core_ops = &saa7146_video_ioctl_ops; + + DEB_EE("dev:%p\n", dev); + + /* set default values for video parts of the saa7146 */ + saa7146_write(dev, BCS_CTRL, 0x80400040); + + /* enable video-port pins */ + saa7146_write(dev, MC1, (MASK_10 | MASK_26)); + + /* save per-device extension data (one extension can + handle different devices that might need different + configuration data) */ + dev->ext_vv_data = ext_vv; + + vv->d_clipping.cpu_addr = pci_alloc_consistent(dev->pci, SAA7146_CLIPPING_MEM, &vv->d_clipping.dma_handle); + if( NULL == vv->d_clipping.cpu_addr ) { + ERR("out of memory. aborting.\n"); + kfree(vv); + v4l2_ctrl_handler_free(hdl); + return -1; + } + memset(vv->d_clipping.cpu_addr, 0x0, SAA7146_CLIPPING_MEM); + + saa7146_video_uops.init(dev,vv); + if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) + saa7146_vbi_uops.init(dev,vv); + + fmt = &vv->ov_fb.fmt; + fmt->width = vv->standard->h_max_out; + fmt->height = vv->standard->v_max_out; + fmt->pixelformat = V4L2_PIX_FMT_RGB565; + fmt->bytesperline = 2 * fmt->width; + fmt->sizeimage = fmt->bytesperline * fmt->height; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + fmt = &vv->video_fmt; + fmt->width = 384; + fmt->height = 288; + fmt->pixelformat = V4L2_PIX_FMT_BGR24; + fmt->field = V4L2_FIELD_ANY; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt->bytesperline = 3 * fmt->width; + fmt->sizeimage = fmt->bytesperline * fmt->height; + + vbi = &vv->vbi_fmt; + vbi->sampling_rate = 27000000; + vbi->offset = 248; /* todo */ + vbi->samples_per_line = 720 * 2; + vbi->sample_format = V4L2_PIX_FMT_GREY; + + /* fixme: this only works for PAL */ + vbi->start[0] = 5; + vbi->count[0] = 16; + vbi->start[1] = 312; + vbi->count[1] = 16; + + init_timer(&vv->vbi_read_timeout); + + vv->ov_fb.capability = V4L2_FBUF_CAP_LIST_CLIPPING; + vv->ov_fb.flags = V4L2_FBUF_FLAG_PRIMARY; + dev->vv_data = vv; + dev->vv_callback = &vv_callback; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_vv_init); + +int saa7146_vv_release(struct saa7146_dev* dev) +{ + struct saa7146_vv *vv = dev->vv_data; + + DEB_EE("dev:%p\n", dev); + + v4l2_device_unregister(&dev->v4l2_dev); + pci_free_consistent(dev->pci, SAA7146_CLIPPING_MEM, vv->d_clipping.cpu_addr, vv->d_clipping.dma_handle); + v4l2_ctrl_handler_free(&dev->ctrl_handler); + kfree(vv); + dev->vv_data = NULL; + dev->vv_callback = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_vv_release); + +int saa7146_register_device(struct video_device **vid, struct saa7146_dev* dev, + char *name, int type) +{ + struct video_device *vfd; + int err; + int i; + + DEB_EE("dev:%p, name:'%s', type:%d\n", dev, name, type); + + // released by vfd->release + vfd = video_device_alloc(); + if (vfd == NULL) + return -ENOMEM; + + vfd->fops = &video_fops; + if (type == VFL_TYPE_GRABBER) + vfd->ioctl_ops = &dev->ext_vv_data->vid_ops; + else + vfd->ioctl_ops = &dev->ext_vv_data->vbi_ops; + vfd->release = video_device_release; + vfd->lock = &dev->v4l2_lock; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->tvnorms = 0; + set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); + for (i = 0; i < dev->ext_vv_data->num_stds; i++) + vfd->tvnorms |= dev->ext_vv_data->stds[i].id; + strlcpy(vfd->name, name, sizeof(vfd->name)); + video_set_drvdata(vfd, dev); + + err = video_register_device(vfd, type, -1); + if (err < 0) { + ERR("cannot register v4l2 device. skipping.\n"); + video_device_release(vfd); + return err; + } + + pr_info("%s: registered device %s [v4l2]\n", + dev->name, video_device_node_name(vfd)); + + *vid = vfd; + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_register_device); + +int saa7146_unregister_device(struct video_device **vid, struct saa7146_dev* dev) +{ + DEB_EE("dev:%p\n", dev); + + video_unregister_device(*vid); + *vid = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_unregister_device); + +static int __init saa7146_vv_init_module(void) +{ + return 0; +} + + +static void __exit saa7146_vv_cleanup_module(void) +{ +} + +module_init(saa7146_vv_init_module); +module_exit(saa7146_vv_cleanup_module); + +MODULE_AUTHOR("Michael Hunold "); +MODULE_DESCRIPTION("video4linux driver for saa7146-based hardware"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/saa7146_hlp.c b/drivers/media/common/saa7146/saa7146_hlp.c similarity index 100% rename from drivers/media/common/saa7146_hlp.c rename to drivers/media/common/saa7146/saa7146_hlp.c diff --git a/drivers/media/common/saa7146_i2c.c b/drivers/media/common/saa7146/saa7146_i2c.c similarity index 100% rename from drivers/media/common/saa7146_i2c.c rename to drivers/media/common/saa7146/saa7146_i2c.c diff --git a/drivers/media/common/saa7146_vbi.c b/drivers/media/common/saa7146/saa7146_vbi.c similarity index 100% rename from drivers/media/common/saa7146_vbi.c rename to drivers/media/common/saa7146/saa7146_vbi.c diff --git a/drivers/media/common/saa7146/saa7146_video.c b/drivers/media/common/saa7146/saa7146_video.c new file mode 100644 index 000000000000..4143d61f79b1 --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_video.c @@ -0,0 +1,1332 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +static int max_memory = 32; + +module_param(max_memory, int, 0644); +MODULE_PARM_DESC(max_memory, "maximum memory usage for capture buffers (default: 32Mb)"); + +#define IS_CAPTURE_ACTIVE(fh) \ + (((vv->video_status & STATUS_CAPTURE) != 0) && (vv->video_fh == fh)) + +#define IS_OVERLAY_ACTIVE(fh) \ + (((vv->video_status & STATUS_OVERLAY) != 0) && (vv->video_fh == fh)) + +/* format descriptions for capture and preview */ +static struct saa7146_format formats[] = { + { + .name = "RGB-8 (3-3-2)", + .pixelformat = V4L2_PIX_FMT_RGB332, + .trans = RGB08_COMPOSED, + .depth = 8, + .flags = 0, + }, { + .name = "RGB-16 (5/B-6/G-5/R)", + .pixelformat = V4L2_PIX_FMT_RGB565, + .trans = RGB16_COMPOSED, + .depth = 16, + .flags = 0, + }, { + .name = "RGB-24 (B-G-R)", + .pixelformat = V4L2_PIX_FMT_BGR24, + .trans = RGB24_COMPOSED, + .depth = 24, + .flags = 0, + }, { + .name = "RGB-32 (B-G-R)", + .pixelformat = V4L2_PIX_FMT_BGR32, + .trans = RGB32_COMPOSED, + .depth = 32, + .flags = 0, + }, { + .name = "RGB-32 (R-G-B)", + .pixelformat = V4L2_PIX_FMT_RGB32, + .trans = RGB32_COMPOSED, + .depth = 32, + .flags = 0, + .swap = 0x2, + }, { + .name = "Greyscale-8", + .pixelformat = V4L2_PIX_FMT_GREY, + .trans = Y8, + .depth = 8, + .flags = 0, + }, { + .name = "YUV 4:2:2 planar (Y-Cb-Cr)", + .pixelformat = V4L2_PIX_FMT_YUV422P, + .trans = YUV422_DECOMPOSED, + .depth = 16, + .flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, + }, { + .name = "YVU 4:2:0 planar (Y-Cb-Cr)", + .pixelformat = V4L2_PIX_FMT_YVU420, + .trans = YUV420_DECOMPOSED, + .depth = 12, + .flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, + }, { + .name = "YUV 4:2:0 planar (Y-Cb-Cr)", + .pixelformat = V4L2_PIX_FMT_YUV420, + .trans = YUV420_DECOMPOSED, + .depth = 12, + .flags = FORMAT_IS_PLANAR, + }, { + .name = "YUV 4:2:2 (U-Y-V-Y)", + .pixelformat = V4L2_PIX_FMT_UYVY, + .trans = YUV422_COMPOSED, + .depth = 16, + .flags = 0, + } +}; + +/* unfortunately, the saa7146 contains a bug which prevents it from doing on-the-fly byte swaps. + due to this, it's impossible to provide additional *packed* formats, which are simply byte swapped + (like V4L2_PIX_FMT_YUYV) ... 8-( */ + +static int NUM_FORMATS = sizeof(formats)/sizeof(struct saa7146_format); + +struct saa7146_format* saa7146_format_by_fourcc(struct saa7146_dev *dev, int fourcc) +{ + int i, j = NUM_FORMATS; + + for (i = 0; i < j; i++) { + if (formats[i].pixelformat == fourcc) { + return formats+i; + } + } + + DEB_D("unknown pixelformat:'%4.4s'\n", (char *)&fourcc); + return NULL; +} + +static int vidioc_try_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f); + +int saa7146_start_preview(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct v4l2_format fmt; + int ret = 0, err = 0; + + DEB_EE("dev:%p, fh:%p\n", dev, fh); + + /* check if we have overlay information */ + if (vv->ov.fh == NULL) { + DEB_D("no overlay data available. try S_FMT first.\n"); + return -EAGAIN; + } + + /* check if streaming capture is running */ + if (IS_CAPTURE_ACTIVE(fh) != 0) { + DEB_D("streaming capture is active\n"); + return -EBUSY; + } + + /* check if overlay is running */ + if (IS_OVERLAY_ACTIVE(fh) != 0) { + if (vv->video_fh == fh) { + DEB_D("overlay is already active\n"); + return 0; + } + DEB_D("overlay is already active in another open\n"); + return -EBUSY; + } + + if (0 == saa7146_res_get(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP)) { + DEB_D("cannot get necessary overlay resources\n"); + return -EBUSY; + } + + fmt.fmt.win = vv->ov.win; + err = vidioc_try_fmt_vid_overlay(NULL, fh, &fmt); + if (0 != err) { + saa7146_res_free(vv->video_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); + return -EBUSY; + } + vv->ov.win = fmt.fmt.win; + + DEB_D("%dx%d+%d+%d %s field=%s\n", + vv->ov.win.w.width, vv->ov.win.w.height, + vv->ov.win.w.left, vv->ov.win.w.top, + vv->ov_fmt->name, v4l2_field_names[vv->ov.win.field]); + + if (0 != (ret = saa7146_enable_overlay(fh))) { + DEB_D("enabling overlay failed: %d\n", ret); + saa7146_res_free(vv->video_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); + return ret; + } + + vv->video_status = STATUS_OVERLAY; + vv->video_fh = fh; + + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_start_preview); + +int saa7146_stop_preview(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + + DEB_EE("dev:%p, fh:%p\n", dev, fh); + + /* check if streaming capture is running */ + if (IS_CAPTURE_ACTIVE(fh) != 0) { + DEB_D("streaming capture is active\n"); + return -EBUSY; + } + + /* check if overlay is running at all */ + if ((vv->video_status & STATUS_OVERLAY) == 0) { + DEB_D("no active overlay\n"); + return 0; + } + + if (vv->video_fh != fh) { + DEB_D("overlay is active, but in another open\n"); + return -EBUSY; + } + + vv->video_status = 0; + vv->video_fh = NULL; + + saa7146_disable_overlay(fh); + + saa7146_res_free(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); + + return 0; +} +EXPORT_SYMBOL_GPL(saa7146_stop_preview); + +/********************************************************************************/ +/* common pagetable functions */ + +static int saa7146_pgtable_build(struct saa7146_dev *dev, struct saa7146_buf *buf) +{ + struct pci_dev *pci = dev->pci; + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + struct scatterlist *list = dma->sglist; + int length = dma->sglen; + struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + + DEB_EE("dev:%p, buf:%p, sg_len:%d\n", dev, buf, length); + + if( 0 != IS_PLANAR(sfmt->trans)) { + struct saa7146_pgtable *pt1 = &buf->pt[0]; + struct saa7146_pgtable *pt2 = &buf->pt[1]; + struct saa7146_pgtable *pt3 = &buf->pt[2]; + __le32 *ptr1, *ptr2, *ptr3; + __le32 fill; + + int size = buf->fmt->width*buf->fmt->height; + int i,p,m1,m2,m3,o1,o2; + + switch( sfmt->depth ) { + case 12: { + /* create some offsets inside the page table */ + m1 = ((size+PAGE_SIZE)/PAGE_SIZE)-1; + m2 = ((size+(size/4)+PAGE_SIZE)/PAGE_SIZE)-1; + m3 = ((size+(size/2)+PAGE_SIZE)/PAGE_SIZE)-1; + o1 = size%PAGE_SIZE; + o2 = (size+(size/4))%PAGE_SIZE; + DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", + size, m1, m2, m3, o1, o2); + break; + } + case 16: { + /* create some offsets inside the page table */ + m1 = ((size+PAGE_SIZE)/PAGE_SIZE)-1; + m2 = ((size+(size/2)+PAGE_SIZE)/PAGE_SIZE)-1; + m3 = ((2*size+PAGE_SIZE)/PAGE_SIZE)-1; + o1 = size%PAGE_SIZE; + o2 = (size+(size/2))%PAGE_SIZE; + DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", + size, m1, m2, m3, o1, o2); + break; + } + default: { + return -1; + } + } + + ptr1 = pt1->cpu; + ptr2 = pt2->cpu; + ptr3 = pt3->cpu; + + /* walk all pages, copy all page addresses to ptr1 */ + for (i = 0; i < length; i++, list++) { + for (p = 0; p * 4096 < list->length; p++, ptr1++) { + *ptr1 = cpu_to_le32(sg_dma_address(list) - list->offset); + } + } +/* + ptr1 = pt1->cpu; + for(j=0;j<40;j++) { + printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); + } +*/ + + /* if we have a user buffer, the first page may not be + aligned to a page boundary. */ + pt1->offset = dma->sglist->offset; + pt2->offset = pt1->offset+o1; + pt3->offset = pt1->offset+o2; + + /* create video-dma2 page table */ + ptr1 = pt1->cpu; + for(i = m1; i <= m2 ; i++, ptr2++) { + *ptr2 = ptr1[i]; + } + fill = *(ptr2-1); + for(;i<1024;i++,ptr2++) { + *ptr2 = fill; + } + /* create video-dma3 page table */ + ptr1 = pt1->cpu; + for(i = m2; i <= m3; i++,ptr3++) { + *ptr3 = ptr1[i]; + } + fill = *(ptr3-1); + for(;i<1024;i++,ptr3++) { + *ptr3 = fill; + } + /* finally: finish up video-dma1 page table */ + ptr1 = pt1->cpu+m1; + fill = pt1->cpu[m1]; + for(i=m1;i<1024;i++,ptr1++) { + *ptr1 = fill; + } +/* + ptr1 = pt1->cpu; + ptr2 = pt2->cpu; + ptr3 = pt3->cpu; + for(j=0;j<40;j++) { + printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); + } + for(j=0;j<40;j++) { + printk("ptr2 %d: 0x%08x\n",j,ptr2[j]); + } + for(j=0;j<40;j++) { + printk("ptr3 %d: 0x%08x\n",j,ptr3[j]); + } +*/ + } else { + struct saa7146_pgtable *pt = &buf->pt[0]; + return saa7146_pgtable_build_single(pci, pt, list, length); + } + + return 0; +} + + +/********************************************************************************/ +/* file operations */ + +static int video_begin(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_format *fmt = NULL; + unsigned int resource; + int ret = 0, err = 0; + + DEB_EE("dev:%p, fh:%p\n", dev, fh); + + if ((vv->video_status & STATUS_CAPTURE) != 0) { + if (vv->video_fh == fh) { + DEB_S("already capturing\n"); + return 0; + } + DEB_S("already capturing in another open\n"); + return -EBUSY; + } + + if ((vv->video_status & STATUS_OVERLAY) != 0) { + DEB_S("warning: suspending overlay video for streaming capture\n"); + vv->ov_suspend = vv->video_fh; + err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ + if (0 != err) { + DEB_D("suspending video failed. aborting\n"); + return err; + } + } + + fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); + /* we need to have a valid format set here */ + BUG_ON(NULL == fmt); + + if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { + resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; + } else { + resource = RESOURCE_DMA1_HPS; + } + + ret = saa7146_res_get(fh, resource); + if (0 == ret) { + DEB_S("cannot get capture resource %d\n", resource); + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + return -EBUSY; + } + + /* clear out beginning of streaming bit (rps register 0)*/ + saa7146_write(dev, MC2, MASK_27 ); + + /* enable rps0 irqs */ + SAA7146_IER_ENABLE(dev, MASK_27); + + vv->video_fh = fh; + vv->video_status = STATUS_CAPTURE; + + return 0; +} + +static int video_end(struct saa7146_fh *fh, struct file *file) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_format *fmt = NULL; + unsigned long flags; + unsigned int resource; + u32 dmas = 0; + DEB_EE("dev:%p, fh:%p\n", dev, fh); + + if ((vv->video_status & STATUS_CAPTURE) != STATUS_CAPTURE) { + DEB_S("not capturing\n"); + return 0; + } + + if (vv->video_fh != fh) { + DEB_S("capturing, but in another open\n"); + return -EBUSY; + } + + fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); + /* we need to have a valid format set here */ + BUG_ON(NULL == fmt); + + if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { + resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; + dmas = MASK_22 | MASK_21 | MASK_20; + } else { + resource = RESOURCE_DMA1_HPS; + dmas = MASK_22; + } + spin_lock_irqsave(&dev->slock,flags); + + /* disable rps0 */ + saa7146_write(dev, MC1, MASK_28); + + /* disable rps0 irqs */ + SAA7146_IER_DISABLE(dev, MASK_27); + + /* shut down all used video dma transfers */ + saa7146_write(dev, MC1, dmas); + + spin_unlock_irqrestore(&dev->slock, flags); + + vv->video_fh = NULL; + vv->video_status = 0; + + saa7146_res_free(fh, resource); + + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + + return 0; +} + +static int vidioc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + + strcpy((char *)cap->driver, "saa7146 v4l2"); + strlcpy((char *)cap->card, dev->ext->name, sizeof(cap->card)); + sprintf((char *)cap->bus_info, "PCI:%s", pci_name(dev->pci)); + cap->device_caps = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + cap->device_caps |= dev->ext_vv_data->capabilities; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + if (vdev->vfl_type == VFL_TYPE_GRABBER) + cap->device_caps &= + ~(V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_OUTPUT); + else + cap->device_caps &= + ~(V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_AUDIO); + return 0; +} + +static int vidioc_g_fbuf(struct file *file, void *fh, struct v4l2_framebuffer *fb) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + *fb = vv->ov_fb; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + fb->flags = V4L2_FBUF_FLAG_PRIMARY; + return 0; +} + +static int vidioc_s_fbuf(struct file *file, void *fh, const struct v4l2_framebuffer *fb) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_format *fmt; + + DEB_EE("VIDIOC_S_FBUF\n"); + + if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = saa7146_format_by_fourcc(dev, fb->fmt.pixelformat); + if (NULL == fmt) + return -EINVAL; + + /* planar formats are not allowed for overlay video, clipping and video dma would clash */ + if (fmt->flags & FORMAT_IS_PLANAR) + DEB_S("planar pixelformat '%4.4s' not allowed for overlay\n", + (char *)&fmt->pixelformat); + + /* check if overlay is running */ + if (IS_OVERLAY_ACTIVE(fh) != 0) { + if (vv->video_fh != fh) { + DEB_D("refusing to change framebuffer informations while overlay is active in another open\n"); + return -EBUSY; + } + } + + /* ok, accept it */ + vv->ov_fb = *fb; + vv->ov_fmt = fmt; + + if (vv->ov_fb.fmt.bytesperline < vv->ov_fb.fmt.width) { + vv->ov_fb.fmt.bytesperline = vv->ov_fb.fmt.width * fmt->depth / 8; + DEB_D("setting bytesperline to %d\n", vv->ov_fb.fmt.bytesperline); + } + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + if (f->index >= NUM_FORMATS) + return -EINVAL; + strlcpy((char *)f->description, formats[f->index].name, + sizeof(f->description)); + f->pixelformat = formats[f->index].pixelformat; + return 0; +} + +int saa7146_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct saa7146_dev *dev = container_of(ctrl->handler, + struct saa7146_dev, ctrl_handler); + struct saa7146_vv *vv = dev->vv_data; + u32 val; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + val = saa7146_read(dev, BCS_CTRL); + val &= 0x00ffffff; + val |= (ctrl->val << 24); + saa7146_write(dev, BCS_CTRL, val); + saa7146_write(dev, MC2, MASK_22 | MASK_06); + break; + + case V4L2_CID_CONTRAST: + val = saa7146_read(dev, BCS_CTRL); + val &= 0xff00ffff; + val |= (ctrl->val << 16); + saa7146_write(dev, BCS_CTRL, val); + saa7146_write(dev, MC2, MASK_22 | MASK_06); + break; + + case V4L2_CID_SATURATION: + val = saa7146_read(dev, BCS_CTRL); + val &= 0xffffff00; + val |= (ctrl->val << 0); + saa7146_write(dev, BCS_CTRL, val); + saa7146_write(dev, MC2, MASK_22 | MASK_06); + break; + + case V4L2_CID_HFLIP: + /* fixme: we can support changing VFLIP and HFLIP here... */ + if ((vv->video_status & STATUS_CAPTURE)) + return -EBUSY; + vv->hflip = ctrl->val; + break; + + case V4L2_CID_VFLIP: + if ((vv->video_status & STATUS_CAPTURE)) + return -EBUSY; + vv->vflip = ctrl->val; + break; + + default: + return -EINVAL; + } + + if ((vv->video_status & STATUS_OVERLAY) != 0) { /* CHECK: && (vv->video_fh == fh)) */ + struct saa7146_fh *fh = vv->video_fh; + + saa7146_stop_preview(fh); + saa7146_start_preview(fh); + } + return 0; +} + +static int vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *parm) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + parm->parm.capture.readbuffers = 1; + v4l2_video_std_frame_period(vv->standard->id, + &parm->parm.capture.timeperframe); + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + f->fmt.pix = vv->video_fmt; + return 0; +} + +static int vidioc_g_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + f->fmt.win = vv->ov.win; + return 0; +} + +static int vidioc_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + f->fmt.vbi = vv->vbi_fmt; + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_format *fmt; + enum v4l2_field field; + int maxw, maxh; + int calc_bpl; + + DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh); + + fmt = saa7146_format_by_fourcc(dev, f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = vv->standard->h_max_out; + maxh = vv->standard->v_max_out; + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh / 2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + switch (field) { + case V4L2_FIELD_ALTERNATE: + vv->last_field = V4L2_FIELD_TOP; + maxh = maxh / 2; + break; + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + vv->last_field = V4L2_FIELD_INTERLACED; + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + vv->last_field = V4L2_FIELD_INTERLACED; + break; + default: + DEB_D("no known field mode '%d'\n", field); + return -EINVAL; + } + + f->fmt.pix.field = field; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + + calc_bpl = (f->fmt.pix.width * fmt->depth) / 8; + + if (f->fmt.pix.bytesperline < calc_bpl) + f->fmt.pix.bytesperline = calc_bpl; + + if (f->fmt.pix.bytesperline > (2 * PAGE_SIZE * fmt->depth) / 8) /* arbitrary constraint */ + f->fmt.pix.bytesperline = calc_bpl; + + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + DEB_D("w:%d, h:%d, bytesperline:%d, sizeimage:%d\n", + f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); + + return 0; +} + + +static int vidioc_try_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + struct v4l2_window *win = &f->fmt.win; + enum v4l2_field field; + int maxw, maxh; + + DEB_EE("dev:%p\n", dev); + + if (NULL == vv->ov_fb.base) { + DEB_D("no fb base set\n"); + return -EINVAL; + } + if (NULL == vv->ov_fmt) { + DEB_D("no fb fmt set\n"); + return -EINVAL; + } + if (win->w.width < 48 || win->w.height < 32) { + DEB_D("min width/height. (%d,%d)\n", + win->w.width, win->w.height); + return -EINVAL; + } + if (win->clipcount > 16) { + DEB_D("clipcount too big\n"); + return -EINVAL; + } + + field = win->field; + maxw = vv->standard->h_max_out; + maxh = vv->standard->v_max_out; + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh / 2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_ALTERNATE: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + DEB_D("no known field mode '%d'\n", field); + return -EINVAL; + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *__fh, struct v4l2_format *f) +{ + struct saa7146_fh *fh = __fh; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + int err; + + DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh); + if (IS_CAPTURE_ACTIVE(fh) != 0) { + DEB_EE("streaming capture is active\n"); + return -EBUSY; + } + err = vidioc_try_fmt_vid_cap(file, fh, f); + if (0 != err) + return err; + vv->video_fmt = f->fmt.pix; + DEB_EE("set to pixelformat '%4.4s'\n", + (char *)&vv->video_fmt.pixelformat); + return 0; +} + +static int vidioc_s_fmt_vid_overlay(struct file *file, void *__fh, struct v4l2_format *f) +{ + struct saa7146_fh *fh = __fh; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + int err; + + DEB_EE("V4L2_BUF_TYPE_VIDEO_OVERLAY: dev:%p, fh:%p\n", dev, fh); + err = vidioc_try_fmt_vid_overlay(file, fh, f); + if (0 != err) + return err; + vv->ov.win = f->fmt.win; + vv->ov.nclips = f->fmt.win.clipcount; + if (vv->ov.nclips > 16) + vv->ov.nclips = 16; + if (copy_from_user(vv->ov.clips, f->fmt.win.clips, + sizeof(struct v4l2_clip) * vv->ov.nclips)) { + return -EFAULT; + } + + /* vv->ov.fh is used to indicate that we have valid overlay informations, too */ + vv->ov.fh = fh; + + /* check if our current overlay is active */ + if (IS_OVERLAY_ACTIVE(fh) != 0) { + saa7146_stop_preview(fh); + saa7146_start_preview(fh); + } + return 0; +} + +static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + + *norm = vv->standard->id; + return 0; +} + + /* the saa7146 supfhrts (used in conjunction with the saa7111a for example) + PAL / NTSC / SECAM. if your hardware does not (or does more) + -- override this function in your extension */ +/* + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + if (e->index < 0 ) + return -EINVAL; + if( e->index < dev->ext_vv_data->num_stds ) { + DEB_EE("VIDIOC_ENUMSTD: index:%d\n", e->index); + v4l2_video_std_construct(e, dev->ext_vv_data->stds[e->index].id, dev->ext_vv_data->stds[e->index].name); + return 0; + } + return -EINVAL; + } + */ + +static int vidioc_s_std(struct file *file, void *fh, v4l2_std_id *id) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct saa7146_vv *vv = dev->vv_data; + int found = 0; + int err, i; + + DEB_EE("VIDIOC_S_STD\n"); + + if ((vv->video_status & STATUS_CAPTURE) == STATUS_CAPTURE) { + DEB_D("cannot change video standard while streaming capture is active\n"); + return -EBUSY; + } + + if ((vv->video_status & STATUS_OVERLAY) != 0) { + vv->ov_suspend = vv->video_fh; + err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ + if (0 != err) { + DEB_D("suspending video failed. aborting\n"); + return err; + } + } + + for (i = 0; i < dev->ext_vv_data->num_stds; i++) + if (*id & dev->ext_vv_data->stds[i].id) + break; + if (i != dev->ext_vv_data->num_stds) { + vv->standard = &dev->ext_vv_data->stds[i]; + if (NULL != dev->ext_vv_data->std_callback) + dev->ext_vv_data->std_callback(dev, vv->standard); + found = 1; + } + + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + + if (!found) { + DEB_EE("VIDIOC_S_STD: standard not found\n"); + return -EINVAL; + } + + DEB_EE("VIDIOC_S_STD: set to standard to '%s'\n", vv->standard->name); + return 0; +} + +static int vidioc_overlay(struct file *file, void *fh, unsigned int on) +{ + int err; + + DEB_D("VIDIOC_OVERLAY on:%d\n", on); + if (on) + err = saa7146_start_preview(fh); + else + err = saa7146_stop_preview(fh); + return err; +} + +static int vidioc_reqbufs(struct file *file, void *__fh, struct v4l2_requestbuffers *b) +{ + struct saa7146_fh *fh = __fh; + + if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_reqbufs(&fh->video_q, b); + if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_reqbufs(&fh->vbi_q, b); + return -EINVAL; +} + +static int vidioc_querybuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ + struct saa7146_fh *fh = __fh; + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_querybuf(&fh->video_q, buf); + if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_querybuf(&fh->vbi_q, buf); + return -EINVAL; +} + +static int vidioc_qbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ + struct saa7146_fh *fh = __fh; + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_qbuf(&fh->video_q, buf); + if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_qbuf(&fh->vbi_q, buf); + return -EINVAL; +} + +static int vidioc_dqbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ + struct saa7146_fh *fh = __fh; + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_dqbuf(&fh->video_q, buf, file->f_flags & O_NONBLOCK); + if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_dqbuf(&fh->vbi_q, buf, file->f_flags & O_NONBLOCK); + return -EINVAL; +} + +static int vidioc_streamon(struct file *file, void *__fh, enum v4l2_buf_type type) +{ + struct saa7146_fh *fh = __fh; + int err; + + DEB_D("VIDIOC_STREAMON, type:%d\n", type); + + err = video_begin(fh); + if (err) + return err; + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return videobuf_streamon(&fh->video_q); + if (type == V4L2_BUF_TYPE_VBI_CAPTURE) + return videobuf_streamon(&fh->vbi_q); + return -EINVAL; +} + +static int vidioc_streamoff(struct file *file, void *__fh, enum v4l2_buf_type type) +{ + struct saa7146_fh *fh = __fh; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + int err; + + DEB_D("VIDIOC_STREAMOFF, type:%d\n", type); + + /* ugly: we need to copy some checks from video_end(), + because videobuf_streamoff() relies on the capture running. + check and fix this */ + if ((vv->video_status & STATUS_CAPTURE) != STATUS_CAPTURE) { + DEB_S("not capturing\n"); + return 0; + } + + if (vv->video_fh != fh) { + DEB_S("capturing, but in another open\n"); + return -EBUSY; + } + + err = -EINVAL; + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + err = videobuf_streamoff(&fh->video_q); + else if (type == V4L2_BUF_TYPE_VBI_CAPTURE) + err = videobuf_streamoff(&fh->vbi_q); + if (0 != err) { + DEB_D("warning: videobuf_streamoff() failed\n"); + video_end(fh, file); + } else { + err = video_end(fh, file); + } + return err; +} + +static int vidioc_g_chip_ident(struct file *file, void *__fh, + struct v4l2_dbg_chip_ident *chip) +{ + struct saa7146_fh *fh = __fh; + struct saa7146_dev *dev = fh->dev; + + chip->ident = V4L2_IDENT_NONE; + chip->revision = 0; + if (chip->match.type == V4L2_CHIP_MATCH_HOST) { + if (v4l2_chip_match_host(&chip->match)) + chip->ident = V4L2_IDENT_SAA7146; + return 0; + } + if (chip->match.type != V4L2_CHIP_MATCH_I2C_DRIVER && + chip->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + return v4l2_device_call_until_err(&dev->v4l2_dev, 0, + core, g_chip_ident, chip); +} + +const struct v4l2_ioctl_ops saa7146_video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_overlay = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_g_fmt_vid_overlay = vidioc_g_fmt_vid_overlay, + .vidioc_try_fmt_vid_overlay = vidioc_try_fmt_vid_overlay, + .vidioc_s_fmt_vid_overlay = vidioc_s_fmt_vid_overlay, + .vidioc_g_chip_ident = vidioc_g_chip_ident, + + .vidioc_overlay = vidioc_overlay, + .vidioc_g_fbuf = vidioc_g_fbuf, + .vidioc_s_fbuf = vidioc_s_fbuf, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +const struct v4l2_ioctl_ops saa7146_vbi_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi_cap, + .vidioc_g_chip_ident = vidioc_g_chip_ident, + + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/*********************************************************************************/ +/* buffer handling functions */ + +static int buffer_activate (struct saa7146_dev *dev, + struct saa7146_buf *buf, + struct saa7146_buf *next) +{ + struct saa7146_vv *vv = dev->vv_data; + + buf->vb.state = VIDEOBUF_ACTIVE; + saa7146_set_capture(dev,buf,next); + + mod_timer(&vv->video_dmaq.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static void release_all_pagetables(struct saa7146_dev *dev, struct saa7146_buf *buf) +{ + saa7146_pgtable_free(dev->pci, &buf->pt[0]); + saa7146_pgtable_free(dev->pci, &buf->pt[1]); + saa7146_pgtable_free(dev->pci, &buf->pt[2]); +} + +static int buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + int size,err = 0; + + DEB_CAP("vbuf:%p\n", vb); + + /* sanity checks */ + if (vv->video_fmt.width < 48 || + vv->video_fmt.height < 32 || + vv->video_fmt.width > vv->standard->h_max_out || + vv->video_fmt.height > vv->standard->v_max_out) { + DEB_D("w (%d) / h (%d) out of bounds\n", + vv->video_fmt.width, vv->video_fmt.height); + return -EINVAL; + } + + size = vv->video_fmt.sizeimage; + if (0 != buf->vb.baddr && buf->vb.bsize < size) { + DEB_D("size mismatch\n"); + return -EINVAL; + } + + DEB_CAP("buffer_prepare [size=%dx%d,bytes=%d,fields=%s]\n", + vv->video_fmt.width, vv->video_fmt.height, + size, v4l2_field_names[vv->video_fmt.field]); + if (buf->vb.width != vv->video_fmt.width || + buf->vb.bytesperline != vv->video_fmt.bytesperline || + buf->vb.height != vv->video_fmt.height || + buf->vb.size != size || + buf->vb.field != field || + buf->vb.field != vv->video_fmt.field || + buf->fmt != &vv->video_fmt) { + saa7146_dma_free(dev,q,buf); + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + struct saa7146_format *sfmt; + + buf->vb.bytesperline = vv->video_fmt.bytesperline; + buf->vb.width = vv->video_fmt.width; + buf->vb.height = vv->video_fmt.height; + buf->vb.size = size; + buf->vb.field = field; + buf->fmt = &vv->video_fmt; + buf->vb.field = vv->video_fmt.field; + + sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + + release_all_pagetables(dev, buf); + if( 0 != IS_PLANAR(sfmt->trans)) { + saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); + saa7146_pgtable_alloc(dev->pci, &buf->pt[1]); + saa7146_pgtable_alloc(dev->pci, &buf->pt[2]); + } else { + saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); + } + + err = videobuf_iolock(q,&buf->vb, &vv->ov_fb); + if (err) + goto oops; + err = saa7146_pgtable_build(dev,buf); + if (err) + goto oops; + } + buf->vb.state = VIDEOBUF_PREPARED; + buf->activate = buffer_activate; + + return 0; + + oops: + DEB_D("error out\n"); + saa7146_dma_free(dev,q,buf); + + return err; +} + +static int buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_vv *vv = fh->dev->vv_data; + + if (0 == *count || *count > MAX_SAA7146_CAPTURE_BUFFERS) + *count = MAX_SAA7146_CAPTURE_BUFFERS; + + *size = vv->video_fmt.sizeimage; + + /* check if we exceed the "max_memory" parameter */ + if( (*count * *size) > (max_memory*1048576) ) { + *count = (max_memory*1048576) / *size; + } + + DEB_CAP("%d buffers, %d bytes each\n", *count, *size); + + return 0; +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_CAP("vbuf:%p\n", vb); + saa7146_buffer_queue(fh->dev, &vv->video_dmaq, buf); +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct file *file = q->priv_data; + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_CAP("vbuf:%p\n", vb); + + saa7146_dma_free(dev,q,buf); + + release_all_pagetables(dev, buf); +} + +static struct videobuf_queue_ops video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/********************************************************************************/ +/* file operations */ + +static void video_init(struct saa7146_dev *dev, struct saa7146_vv *vv) +{ + INIT_LIST_HEAD(&vv->video_dmaq.queue); + + init_timer(&vv->video_dmaq.timeout); + vv->video_dmaq.timeout.function = saa7146_buffer_timeout; + vv->video_dmaq.timeout.data = (unsigned long)(&vv->video_dmaq); + vv->video_dmaq.dev = dev; + + /* set some default values */ + vv->standard = &dev->ext_vv_data->stds[0]; + + /* FIXME: what's this? */ + vv->current_hps_source = SAA7146_HPS_SOURCE_PORT_A; + vv->current_hps_sync = SAA7146_HPS_SYNC_PORT_A; +} + + +static int video_open(struct saa7146_dev *dev, struct file *file) +{ + struct saa7146_fh *fh = file->private_data; + + videobuf_queue_sg_init(&fh->video_q, &video_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct saa7146_buf), + file, &dev->v4l2_lock); + + return 0; +} + + +static void video_close(struct saa7146_dev *dev, struct file *file) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_vv *vv = dev->vv_data; + struct videobuf_queue *q = &fh->video_q; + + if (IS_CAPTURE_ACTIVE(fh) != 0) + video_end(fh, file); + else if (IS_OVERLAY_ACTIVE(fh) != 0) + saa7146_stop_preview(fh); + + videobuf_stop(q); + /* hmm, why is this function declared void? */ +} + + +static void video_irq_done(struct saa7146_dev *dev, unsigned long st) +{ + struct saa7146_vv *vv = dev->vv_data; + struct saa7146_dmaqueue *q = &vv->video_dmaq; + + spin_lock(&dev->slock); + DEB_CAP("called\n"); + + /* only finish the buffer if we have one... */ + if( NULL != q->curr ) { + saa7146_buffer_finish(dev,q,VIDEOBUF_DONE); + } + saa7146_buffer_next(dev,q,0); + + spin_unlock(&dev->slock); +} + +static ssize_t video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + ssize_t ret = 0; + + DEB_EE("called\n"); + + if ((vv->video_status & STATUS_CAPTURE) != 0) { + /* fixme: should we allow read() captures while streaming capture? */ + if (vv->video_fh == fh) { + DEB_S("already capturing\n"); + return -EBUSY; + } + DEB_S("already capturing in another open\n"); + return -EBUSY; + } + + ret = video_begin(fh); + if( 0 != ret) { + goto out; + } + + ret = videobuf_read_one(&fh->video_q , data, count, ppos, + file->f_flags & O_NONBLOCK); + if (ret != 0) { + video_end(fh, file); + } else { + ret = video_end(fh, file); + } +out: + /* restart overlay if it was active before */ + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + + return ret; +} + +struct saa7146_use_ops saa7146_video_uops = { + .init = video_init, + .open = video_open, + .release = video_close, + .irq_done = video_irq_done, + .read = video_read, +}; diff --git a/drivers/media/common/saa7146_core.c b/drivers/media/common/saa7146_core.c deleted file mode 100644 index d6b1cf66042d..000000000000 --- a/drivers/media/common/saa7146_core.c +++ /dev/null @@ -1,600 +0,0 @@ -/* - saa7146.o - driver for generic saa7146-based hardware - - Copyright (C) 1998-2003 Michael Hunold - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include - -LIST_HEAD(saa7146_devices); -DEFINE_MUTEX(saa7146_devices_lock); - -static int saa7146_num; - -unsigned int saa7146_debug; - -module_param(saa7146_debug, uint, 0644); -MODULE_PARM_DESC(saa7146_debug, "debug level (default: 0)"); - -#if 0 -static void dump_registers(struct saa7146_dev* dev) -{ - int i = 0; - - pr_info(" @ %li jiffies:\n", jiffies); - for (i = 0; i <= 0x148; i += 4) - pr_info("0x%03x: 0x%08x\n", i, saa7146_read(dev, i)); -} -#endif - -/**************************************************************************** - * gpio and debi helper functions - ****************************************************************************/ - -void saa7146_setgpio(struct saa7146_dev *dev, int port, u32 data) -{ - u32 value = 0; - - BUG_ON(port > 3); - - value = saa7146_read(dev, GPIO_CTRL); - value &= ~(0xff << (8*port)); - value |= (data << (8*port)); - saa7146_write(dev, GPIO_CTRL, value); -} - -/* This DEBI code is based on the saa7146 Stradis driver by Nathan Laredo */ -static inline int saa7146_wait_for_debi_done_sleep(struct saa7146_dev *dev, - unsigned long us1, unsigned long us2) -{ - unsigned long timeout; - int err; - - /* wait for registers to be programmed */ - timeout = jiffies + usecs_to_jiffies(us1); - while (1) { - err = time_after(jiffies, timeout); - if (saa7146_read(dev, MC2) & 2) - break; - if (err) { - pr_err("%s: %s timed out while waiting for registers getting programmed\n", - dev->name, __func__); - return -ETIMEDOUT; - } - msleep(1); - } - - /* wait for transfer to complete */ - timeout = jiffies + usecs_to_jiffies(us2); - while (1) { - err = time_after(jiffies, timeout); - if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S)) - break; - saa7146_read(dev, MC2); - if (err) { - DEB_S("%s: %s timed out while waiting for transfer completion\n", - dev->name, __func__); - return -ETIMEDOUT; - } - msleep(1); - } - - return 0; -} - -static inline int saa7146_wait_for_debi_done_busyloop(struct saa7146_dev *dev, - unsigned long us1, unsigned long us2) -{ - unsigned long loops; - - /* wait for registers to be programmed */ - loops = us1; - while (1) { - if (saa7146_read(dev, MC2) & 2) - break; - if (!loops--) { - pr_err("%s: %s timed out while waiting for registers getting programmed\n", - dev->name, __func__); - return -ETIMEDOUT; - } - udelay(1); - } - - /* wait for transfer to complete */ - loops = us2 / 5; - while (1) { - if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S)) - break; - saa7146_read(dev, MC2); - if (!loops--) { - DEB_S("%s: %s timed out while waiting for transfer completion\n", - dev->name, __func__); - return -ETIMEDOUT; - } - udelay(5); - } - - return 0; -} - -int saa7146_wait_for_debi_done(struct saa7146_dev *dev, int nobusyloop) -{ - if (nobusyloop) - return saa7146_wait_for_debi_done_sleep(dev, 50000, 250000); - else - return saa7146_wait_for_debi_done_busyloop(dev, 50000, 250000); -} - -/**************************************************************************** - * general helper functions - ****************************************************************************/ - -/* this is videobuf_vmalloc_to_sg() from videobuf-dma-sg.c - make sure virt has been allocated with vmalloc_32(), otherwise the BUG() - may be triggered on highmem machines */ -static struct scatterlist* vmalloc_to_sg(unsigned char *virt, int nr_pages) -{ - struct scatterlist *sglist; - struct page *pg; - int i; - - sglist = kcalloc(nr_pages, sizeof(struct scatterlist), GFP_KERNEL); - if (NULL == sglist) - return NULL; - sg_init_table(sglist, nr_pages); - for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) { - pg = vmalloc_to_page(virt); - if (NULL == pg) - goto err; - BUG_ON(PageHighMem(pg)); - sg_set_page(&sglist[i], pg, PAGE_SIZE, 0); - } - return sglist; - - err: - kfree(sglist); - return NULL; -} - -/********************************************************************************/ -/* common page table functions */ - -void *saa7146_vmalloc_build_pgtable(struct pci_dev *pci, long length, struct saa7146_pgtable *pt) -{ - int pages = (length+PAGE_SIZE-1)/PAGE_SIZE; - void *mem = vmalloc_32(length); - int slen = 0; - - if (NULL == mem) - goto err_null; - - if (!(pt->slist = vmalloc_to_sg(mem, pages))) - goto err_free_mem; - - if (saa7146_pgtable_alloc(pci, pt)) - goto err_free_slist; - - pt->nents = pages; - slen = pci_map_sg(pci,pt->slist,pt->nents,PCI_DMA_FROMDEVICE); - if (0 == slen) - goto err_free_pgtable; - - if (0 != saa7146_pgtable_build_single(pci, pt, pt->slist, slen)) - goto err_unmap_sg; - - return mem; - -err_unmap_sg: - pci_unmap_sg(pci, pt->slist, pt->nents, PCI_DMA_FROMDEVICE); -err_free_pgtable: - saa7146_pgtable_free(pci, pt); -err_free_slist: - kfree(pt->slist); - pt->slist = NULL; -err_free_mem: - vfree(mem); -err_null: - return NULL; -} - -void saa7146_vfree_destroy_pgtable(struct pci_dev *pci, void *mem, struct saa7146_pgtable *pt) -{ - pci_unmap_sg(pci, pt->slist, pt->nents, PCI_DMA_FROMDEVICE); - saa7146_pgtable_free(pci, pt); - kfree(pt->slist); - pt->slist = NULL; - vfree(mem); -} - -void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt) -{ - if (NULL == pt->cpu) - return; - pci_free_consistent(pci, pt->size, pt->cpu, pt->dma); - pt->cpu = NULL; -} - -int saa7146_pgtable_alloc(struct pci_dev *pci, struct saa7146_pgtable *pt) -{ - __le32 *cpu; - dma_addr_t dma_addr = 0; - - cpu = pci_alloc_consistent(pci, PAGE_SIZE, &dma_addr); - if (NULL == cpu) { - return -ENOMEM; - } - pt->size = PAGE_SIZE; - pt->cpu = cpu; - pt->dma = dma_addr; - - return 0; -} - -int saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt, - struct scatterlist *list, int sglen ) -{ - __le32 *ptr, fill; - int nr_pages = 0; - int i,p; - - BUG_ON(0 == sglen); - BUG_ON(list->offset > PAGE_SIZE); - - /* if we have a user buffer, the first page may not be - aligned to a page boundary. */ - pt->offset = list->offset; - - ptr = pt->cpu; - for (i = 0; i < sglen; i++, list++) { -/* - pr_debug("i:%d, adr:0x%08x, len:%d, offset:%d\n", - i, sg_dma_address(list), sg_dma_len(list), - list->offset); -*/ - for (p = 0; p * 4096 < list->length; p++, ptr++) { - *ptr = cpu_to_le32(sg_dma_address(list) + p * 4096); - nr_pages++; - } - } - - - /* safety; fill the page table up with the last valid page */ - fill = *(ptr-1); - for(i=nr_pages;i<1024;i++) { - *ptr++ = fill; - } - -/* - ptr = pt->cpu; - pr_debug("offset: %d\n", pt->offset); - for(i=0;i<5;i++) { - pr_debug("ptr1 %d: 0x%08x\n", i, ptr[i]); - } -*/ - return 0; -} - -/********************************************************************************/ -/* interrupt handler */ -static irqreturn_t interrupt_hw(int irq, void *dev_id) -{ - struct saa7146_dev *dev = dev_id; - u32 isr; - u32 ack_isr; - - /* read out the interrupt status register */ - ack_isr = isr = saa7146_read(dev, ISR); - - /* is this our interrupt? */ - if ( 0 == isr ) { - /* nope, some other device */ - return IRQ_NONE; - } - - if (dev->ext) { - if (dev->ext->irq_mask & isr) { - if (dev->ext->irq_func) - dev->ext->irq_func(dev, &isr); - isr &= ~dev->ext->irq_mask; - } - } - if (0 != (isr & (MASK_27))) { - DEB_INT("irq: RPS0 (0x%08x)\n", isr); - if (dev->vv_data && dev->vv_callback) - dev->vv_callback(dev,isr); - isr &= ~MASK_27; - } - if (0 != (isr & (MASK_28))) { - if (dev->vv_data && dev->vv_callback) - dev->vv_callback(dev,isr); - isr &= ~MASK_28; - } - if (0 != (isr & (MASK_16|MASK_17))) { - SAA7146_IER_DISABLE(dev, MASK_16|MASK_17); - /* only wake up if we expect something */ - if (0 != dev->i2c_op) { - dev->i2c_op = 0; - wake_up(&dev->i2c_wq); - } else { - u32 psr = saa7146_read(dev, PSR); - u32 ssr = saa7146_read(dev, SSR); - pr_warn("%s: unexpected i2c irq: isr %08x psr %08x ssr %08x\n", - dev->name, isr, psr, ssr); - } - isr &= ~(MASK_16|MASK_17); - } - if( 0 != isr ) { - ERR("warning: interrupt enabled, but not handled properly.(0x%08x)\n", - isr); - ERR("disabling interrupt source(s)!\n"); - SAA7146_IER_DISABLE(dev,isr); - } - saa7146_write(dev, ISR, ack_isr); - return IRQ_HANDLED; -} - -/*********************************************************************************/ -/* configuration-functions */ - -static int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent) -{ - struct saa7146_pci_extension_data *pci_ext = (struct saa7146_pci_extension_data *)ent->driver_data; - struct saa7146_extension *ext = pci_ext->ext; - struct saa7146_dev *dev; - int err = -ENOMEM; - - /* clear out mem for sure */ - dev = kzalloc(sizeof(struct saa7146_dev), GFP_KERNEL); - if (!dev) { - ERR("out of memory\n"); - goto out; - } - - DEB_EE("pci:%p\n", pci); - - err = pci_enable_device(pci); - if (err < 0) { - ERR("pci_enable_device() failed\n"); - goto err_free; - } - - /* enable bus-mastering */ - pci_set_master(pci); - - dev->pci = pci; - - /* get chip-revision; this is needed to enable bug-fixes */ - dev->revision = pci->revision; - - /* remap the memory from virtual to physical address */ - - err = pci_request_region(pci, 0, "saa7146"); - if (err < 0) - goto err_disable; - - dev->mem = ioremap(pci_resource_start(pci, 0), - pci_resource_len(pci, 0)); - if (!dev->mem) { - ERR("ioremap() failed\n"); - err = -ENODEV; - goto err_release; - } - - /* we don't do a master reset here anymore, it screws up - some boards that don't have an i2c-eeprom for configuration - values */ -/* - saa7146_write(dev, MC1, MASK_31); -*/ - - /* disable all irqs */ - saa7146_write(dev, IER, 0); - - /* shut down all dma transfers and rps tasks */ - saa7146_write(dev, MC1, 0x30ff0000); - - /* clear out any rps-signals pending */ - saa7146_write(dev, MC2, 0xf8000000); - - /* request an interrupt for the saa7146 */ - err = request_irq(pci->irq, interrupt_hw, IRQF_SHARED | IRQF_DISABLED, - dev->name, dev); - if (err < 0) { - ERR("request_irq() failed\n"); - goto err_unmap; - } - - err = -ENOMEM; - - /* get memory for various stuff */ - dev->d_rps0.cpu_addr = pci_alloc_consistent(pci, SAA7146_RPS_MEM, - &dev->d_rps0.dma_handle); - if (!dev->d_rps0.cpu_addr) - goto err_free_irq; - memset(dev->d_rps0.cpu_addr, 0x0, SAA7146_RPS_MEM); - - dev->d_rps1.cpu_addr = pci_alloc_consistent(pci, SAA7146_RPS_MEM, - &dev->d_rps1.dma_handle); - if (!dev->d_rps1.cpu_addr) - goto err_free_rps0; - memset(dev->d_rps1.cpu_addr, 0x0, SAA7146_RPS_MEM); - - dev->d_i2c.cpu_addr = pci_alloc_consistent(pci, SAA7146_RPS_MEM, - &dev->d_i2c.dma_handle); - if (!dev->d_i2c.cpu_addr) - goto err_free_rps1; - memset(dev->d_i2c.cpu_addr, 0x0, SAA7146_RPS_MEM); - - /* the rest + print status message */ - - /* create a nice device name */ - sprintf(dev->name, "saa7146 (%d)", saa7146_num); - - pr_info("found saa7146 @ mem %p (revision %d, irq %d) (0x%04x,0x%04x)\n", - dev->mem, dev->revision, pci->irq, - pci->subsystem_vendor, pci->subsystem_device); - dev->ext = ext; - - mutex_init(&dev->v4l2_lock); - spin_lock_init(&dev->int_slock); - spin_lock_init(&dev->slock); - - mutex_init(&dev->i2c_lock); - - dev->module = THIS_MODULE; - init_waitqueue_head(&dev->i2c_wq); - - /* set some sane pci arbitrition values */ - saa7146_write(dev, PCI_BT_V1, 0x1c00101f); - - /* TODO: use the status code of the callback */ - - err = -ENODEV; - - if (ext->probe && ext->probe(dev)) { - DEB_D("ext->probe() failed for %p. skipping device.\n", dev); - goto err_free_i2c; - } - - if (ext->attach(dev, pci_ext)) { - DEB_D("ext->attach() failed for %p. skipping device.\n", dev); - goto err_free_i2c; - } - /* V4L extensions will set the pci drvdata to the v4l2_device in the - attach() above. So for those cards that do not use V4L we have to - set it explicitly. */ - pci_set_drvdata(pci, &dev->v4l2_dev); - - INIT_LIST_HEAD(&dev->item); - list_add_tail(&dev->item,&saa7146_devices); - saa7146_num++; - - err = 0; -out: - return err; - -err_free_i2c: - pci_free_consistent(pci, SAA7146_RPS_MEM, dev->d_i2c.cpu_addr, - dev->d_i2c.dma_handle); -err_free_rps1: - pci_free_consistent(pci, SAA7146_RPS_MEM, dev->d_rps1.cpu_addr, - dev->d_rps1.dma_handle); -err_free_rps0: - pci_free_consistent(pci, SAA7146_RPS_MEM, dev->d_rps0.cpu_addr, - dev->d_rps0.dma_handle); -err_free_irq: - free_irq(pci->irq, (void *)dev); -err_unmap: - iounmap(dev->mem); -err_release: - pci_release_region(pci, 0); -err_disable: - pci_disable_device(pci); -err_free: - kfree(dev); - goto out; -} - -static void saa7146_remove_one(struct pci_dev *pdev) -{ - struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); - struct saa7146_dev *dev = to_saa7146_dev(v4l2_dev); - struct { - void *addr; - dma_addr_t dma; - } dev_map[] = { - { dev->d_i2c.cpu_addr, dev->d_i2c.dma_handle }, - { dev->d_rps1.cpu_addr, dev->d_rps1.dma_handle }, - { dev->d_rps0.cpu_addr, dev->d_rps0.dma_handle }, - { NULL, 0 } - }, *p; - - DEB_EE("dev:%p\n", dev); - - dev->ext->detach(dev); - /* Zero the PCI drvdata after use. */ - pci_set_drvdata(pdev, NULL); - - /* shut down all video dma transfers */ - saa7146_write(dev, MC1, 0x00ff0000); - - /* disable all irqs, release irq-routine */ - saa7146_write(dev, IER, 0); - - free_irq(pdev->irq, dev); - - for (p = dev_map; p->addr; p++) - pci_free_consistent(pdev, SAA7146_RPS_MEM, p->addr, p->dma); - - iounmap(dev->mem); - pci_release_region(pdev, 0); - list_del(&dev->item); - pci_disable_device(pdev); - kfree(dev); - - saa7146_num--; -} - -/*********************************************************************************/ -/* extension handling functions */ - -int saa7146_register_extension(struct saa7146_extension* ext) -{ - DEB_EE("ext:%p\n", ext); - - ext->driver.name = ext->name; - ext->driver.id_table = ext->pci_tbl; - ext->driver.probe = saa7146_init_one; - ext->driver.remove = saa7146_remove_one; - - pr_info("register extension '%s'\n", ext->name); - return pci_register_driver(&ext->driver); -} - -int saa7146_unregister_extension(struct saa7146_extension* ext) -{ - DEB_EE("ext:%p\n", ext); - pr_info("unregister extension '%s'\n", ext->name); - pci_unregister_driver(&ext->driver); - return 0; -} - -EXPORT_SYMBOL_GPL(saa7146_register_extension); -EXPORT_SYMBOL_GPL(saa7146_unregister_extension); - -/* misc functions used by extension modules */ -EXPORT_SYMBOL_GPL(saa7146_pgtable_alloc); -EXPORT_SYMBOL_GPL(saa7146_pgtable_free); -EXPORT_SYMBOL_GPL(saa7146_pgtable_build_single); -EXPORT_SYMBOL_GPL(saa7146_vmalloc_build_pgtable); -EXPORT_SYMBOL_GPL(saa7146_vfree_destroy_pgtable); -EXPORT_SYMBOL_GPL(saa7146_wait_for_debi_done); - -EXPORT_SYMBOL_GPL(saa7146_setgpio); - -EXPORT_SYMBOL_GPL(saa7146_i2c_adapter_prepare); - -EXPORT_SYMBOL_GPL(saa7146_debug); -EXPORT_SYMBOL_GPL(saa7146_devices); -EXPORT_SYMBOL_GPL(saa7146_devices_lock); - -MODULE_AUTHOR("Michael Hunold "); -MODULE_DESCRIPTION("driver for generic saa7146-based hardware"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/saa7146_fops.c b/drivers/media/common/saa7146_fops.c deleted file mode 100644 index 0cdbd742974a..000000000000 --- a/drivers/media/common/saa7146_fops.c +++ /dev/null @@ -1,641 +0,0 @@ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include - -/****************************************************************************/ -/* resource management functions, shamelessly stolen from saa7134 driver */ - -int saa7146_res_get(struct saa7146_fh *fh, unsigned int bit) -{ - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - - if (fh->resources & bit) { - DEB_D("already allocated! want: 0x%02x, cur:0x%02x\n", - bit, vv->resources); - /* have it already allocated */ - return 1; - } - - /* is it free? */ - if (vv->resources & bit) { - DEB_D("locked! vv->resources:0x%02x, we want:0x%02x\n", - vv->resources, bit); - /* no, someone else uses it */ - return 0; - } - /* it's free, grab it */ - fh->resources |= bit; - vv->resources |= bit; - DEB_D("res: get 0x%02x, cur:0x%02x\n", bit, vv->resources); - return 1; -} - -void saa7146_res_free(struct saa7146_fh *fh, unsigned int bits) -{ - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - - BUG_ON((fh->resources & bits) != bits); - - fh->resources &= ~bits; - vv->resources &= ~bits; - DEB_D("res: put 0x%02x, cur:0x%02x\n", bits, vv->resources); -} - - -/********************************************************************************/ -/* common dma functions */ - -void saa7146_dma_free(struct saa7146_dev *dev,struct videobuf_queue *q, - struct saa7146_buf *buf) -{ - struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); - DEB_EE("dev:%p, buf:%p\n", dev, buf); - - BUG_ON(in_interrupt()); - - videobuf_waiton(q, &buf->vb, 0, 0); - videobuf_dma_unmap(q->dev, dma); - videobuf_dma_free(dma); - buf->vb.state = VIDEOBUF_NEEDS_INIT; -} - - -/********************************************************************************/ -/* common buffer functions */ - -int saa7146_buffer_queue(struct saa7146_dev *dev, - struct saa7146_dmaqueue *q, - struct saa7146_buf *buf) -{ - assert_spin_locked(&dev->slock); - DEB_EE("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf); - - BUG_ON(!q); - - if (NULL == q->curr) { - q->curr = buf; - DEB_D("immediately activating buffer %p\n", buf); - buf->activate(dev,buf,NULL); - } else { - list_add_tail(&buf->vb.queue,&q->queue); - buf->vb.state = VIDEOBUF_QUEUED; - DEB_D("adding buffer %p to queue. (active buffer present)\n", - buf); - } - return 0; -} - -void saa7146_buffer_finish(struct saa7146_dev *dev, - struct saa7146_dmaqueue *q, - int state) -{ - assert_spin_locked(&dev->slock); - DEB_EE("dev:%p, dmaq:%p, state:%d\n", dev, q, state); - DEB_EE("q->curr:%p\n", q->curr); - - BUG_ON(!q->curr); - - /* finish current buffer */ - if (NULL == q->curr) { - DEB_D("aiii. no current buffer\n"); - return; - } - - q->curr->vb.state = state; - do_gettimeofday(&q->curr->vb.ts); - wake_up(&q->curr->vb.done); - - q->curr = NULL; -} - -void saa7146_buffer_next(struct saa7146_dev *dev, - struct saa7146_dmaqueue *q, int vbi) -{ - struct saa7146_buf *buf,*next = NULL; - - BUG_ON(!q); - - DEB_INT("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi); - - assert_spin_locked(&dev->slock); - if (!list_empty(&q->queue)) { - /* activate next one from queue */ - buf = list_entry(q->queue.next,struct saa7146_buf,vb.queue); - list_del(&buf->vb.queue); - if (!list_empty(&q->queue)) - next = list_entry(q->queue.next,struct saa7146_buf, vb.queue); - q->curr = buf; - DEB_INT("next buffer: buf:%p, prev:%p, next:%p\n", - buf, q->queue.prev, q->queue.next); - buf->activate(dev,buf,next); - } else { - DEB_INT("no next buffer. stopping.\n"); - if( 0 != vbi ) { - /* turn off video-dma3 */ - saa7146_write(dev,MC1, MASK_20); - } else { - /* nothing to do -- just prevent next video-dma1 transfer - by lowering the protection address */ - - // fixme: fix this for vflip != 0 - - saa7146_write(dev, PROT_ADDR1, 0); - saa7146_write(dev, MC2, (MASK_02|MASK_18)); - - /* write the address of the rps-program */ - saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle); - /* turn on rps */ - saa7146_write(dev, MC1, (MASK_12 | MASK_28)); - -/* - printk("vdma%d.base_even: 0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1)); - printk("vdma%d.base_odd: 0x%08x\n", 1,saa7146_read(dev,BASE_ODD1)); - printk("vdma%d.prot_addr: 0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1)); - printk("vdma%d.base_page: 0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1)); - printk("vdma%d.pitch: 0x%08x\n", 1,saa7146_read(dev,PITCH1)); - printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1)); -*/ - } - del_timer(&q->timeout); - } -} - -void saa7146_buffer_timeout(unsigned long data) -{ - struct saa7146_dmaqueue *q = (struct saa7146_dmaqueue*)data; - struct saa7146_dev *dev = q->dev; - unsigned long flags; - - DEB_EE("dev:%p, dmaq:%p\n", dev, q); - - spin_lock_irqsave(&dev->slock,flags); - if (q->curr) { - DEB_D("timeout on %p\n", q->curr); - saa7146_buffer_finish(dev,q,VIDEOBUF_ERROR); - } - - /* we don't restart the transfer here like other drivers do. when - a streaming capture is disabled, the timeout function will be - called for the current buffer. if we activate the next buffer now, - we mess up our capture logic. if a timeout occurs on another buffer, - then something is seriously broken before, so no need to buffer the - next capture IMHO... */ -/* - saa7146_buffer_next(dev,q); -*/ - spin_unlock_irqrestore(&dev->slock,flags); -} - -/********************************************************************************/ -/* file operations */ - -static int fops_open(struct file *file) -{ - struct video_device *vdev = video_devdata(file); - struct saa7146_dev *dev = video_drvdata(file); - struct saa7146_fh *fh = NULL; - int result = 0; - - DEB_EE("file:%p, dev:%s\n", file, video_device_node_name(vdev)); - - if (mutex_lock_interruptible(&saa7146_devices_lock)) - return -ERESTARTSYS; - - DEB_D("using: %p\n", dev); - - /* check if an extension is registered */ - if( NULL == dev->ext ) { - DEB_S("no extension registered for this device\n"); - result = -ENODEV; - goto out; - } - - /* allocate per open data */ - fh = kzalloc(sizeof(*fh),GFP_KERNEL); - if (NULL == fh) { - DEB_S("cannot allocate memory for per open data\n"); - result = -ENOMEM; - goto out; - } - - v4l2_fh_init(&fh->fh, vdev); - - file->private_data = &fh->fh; - fh->dev = dev; - - if (vdev->vfl_type == VFL_TYPE_VBI) { - DEB_S("initializing vbi...\n"); - if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) - result = saa7146_vbi_uops.open(dev,file); - if (dev->ext_vv_data->vbi_fops.open) - dev->ext_vv_data->vbi_fops.open(file); - } else { - DEB_S("initializing video...\n"); - result = saa7146_video_uops.open(dev,file); - } - - if (0 != result) { - goto out; - } - - if( 0 == try_module_get(dev->ext->module)) { - result = -EINVAL; - goto out; - } - - result = 0; - v4l2_fh_add(&fh->fh); -out: - if (fh && result != 0) { - kfree(fh); - file->private_data = NULL; - } - mutex_unlock(&saa7146_devices_lock); - return result; -} - -static int fops_release(struct file *file) -{ - struct video_device *vdev = video_devdata(file); - struct saa7146_fh *fh = file->private_data; - struct saa7146_dev *dev = fh->dev; - - DEB_EE("file:%p\n", file); - - if (mutex_lock_interruptible(&saa7146_devices_lock)) - return -ERESTARTSYS; - - if (vdev->vfl_type == VFL_TYPE_VBI) { - if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) - saa7146_vbi_uops.release(dev,file); - if (dev->ext_vv_data->vbi_fops.release) - dev->ext_vv_data->vbi_fops.release(file); - } else { - saa7146_video_uops.release(dev,file); - } - - v4l2_fh_del(&fh->fh); - v4l2_fh_exit(&fh->fh); - module_put(dev->ext->module); - file->private_data = NULL; - kfree(fh); - - mutex_unlock(&saa7146_devices_lock); - - return 0; -} - -static int fops_mmap(struct file *file, struct vm_area_struct * vma) -{ - struct video_device *vdev = video_devdata(file); - struct saa7146_fh *fh = file->private_data; - struct videobuf_queue *q; - - switch (vdev->vfl_type) { - case VFL_TYPE_GRABBER: { - DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, vma:%p\n", - file, vma); - q = &fh->video_q; - break; - } - case VFL_TYPE_VBI: { - DEB_EE("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, vma:%p\n", - file, vma); - if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_SLICED_VBI_OUTPUT) - return -ENODEV; - q = &fh->vbi_q; - break; - } - default: - BUG(); - return 0; - } - - return videobuf_mmap_mapper(q,vma); -} - -static unsigned int fops_poll(struct file *file, struct poll_table_struct *wait) -{ - struct video_device *vdev = video_devdata(file); - struct saa7146_fh *fh = file->private_data; - struct videobuf_buffer *buf = NULL; - struct videobuf_queue *q; - unsigned int res = v4l2_ctrl_poll(file, wait); - - DEB_EE("file:%p, poll:%p\n", file, wait); - - if (vdev->vfl_type == VFL_TYPE_VBI) { - if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_SLICED_VBI_OUTPUT) - return res | POLLOUT | POLLWRNORM; - if( 0 == fh->vbi_q.streaming ) - return res | videobuf_poll_stream(file, &fh->vbi_q, wait); - q = &fh->vbi_q; - } else { - DEB_D("using video queue\n"); - q = &fh->video_q; - } - - if (!list_empty(&q->stream)) - buf = list_entry(q->stream.next, struct videobuf_buffer, stream); - - if (!buf) { - DEB_D("buf == NULL!\n"); - return res | POLLERR; - } - - poll_wait(file, &buf->done, wait); - if (buf->state == VIDEOBUF_DONE || buf->state == VIDEOBUF_ERROR) { - DEB_D("poll succeeded!\n"); - return res | POLLIN | POLLRDNORM; - } - - DEB_D("nothing to poll for, buf->state:%d\n", buf->state); - return res; -} - -static ssize_t fops_read(struct file *file, char __user *data, size_t count, loff_t *ppos) -{ - struct video_device *vdev = video_devdata(file); - struct saa7146_fh *fh = file->private_data; - - switch (vdev->vfl_type) { - case VFL_TYPE_GRABBER: -/* - DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, data:%p, count:%lun", - file, data, (unsigned long)count); -*/ - return saa7146_video_uops.read(file,data,count,ppos); - case VFL_TYPE_VBI: -/* - DEB_EE("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, data:%p, count:%lu\n", - file, data, (unsigned long)count); -*/ - if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) - return saa7146_vbi_uops.read(file,data,count,ppos); - return -EINVAL; - default: - BUG(); - return 0; - } -} - -static ssize_t fops_write(struct file *file, const char __user *data, size_t count, loff_t *ppos) -{ - struct video_device *vdev = video_devdata(file); - struct saa7146_fh *fh = file->private_data; - - switch (vdev->vfl_type) { - case VFL_TYPE_GRABBER: - return -EINVAL; - case VFL_TYPE_VBI: - if (fh->dev->ext_vv_data->vbi_fops.write) - return fh->dev->ext_vv_data->vbi_fops.write(file, data, count, ppos); - else - return -EINVAL; - default: - BUG(); - return -EINVAL; - } -} - -static const struct v4l2_file_operations video_fops = -{ - .owner = THIS_MODULE, - .open = fops_open, - .release = fops_release, - .read = fops_read, - .write = fops_write, - .poll = fops_poll, - .mmap = fops_mmap, - .unlocked_ioctl = video_ioctl2, -}; - -static void vv_callback(struct saa7146_dev *dev, unsigned long status) -{ - u32 isr = status; - - DEB_INT("dev:%p, isr:0x%08x\n", dev, (u32)status); - - if (0 != (isr & (MASK_27))) { - DEB_INT("irq: RPS0 (0x%08x)\n", isr); - saa7146_video_uops.irq_done(dev,isr); - } - - if (0 != (isr & (MASK_28))) { - u32 mc2 = saa7146_read(dev, MC2); - if( 0 != (mc2 & MASK_15)) { - DEB_INT("irq: RPS1 vbi workaround (0x%08x)\n", isr); - wake_up(&dev->vv_data->vbi_wq); - saa7146_write(dev,MC2, MASK_31); - return; - } - DEB_INT("irq: RPS1 (0x%08x)\n", isr); - saa7146_vbi_uops.irq_done(dev,isr); - } -} - -static const struct v4l2_ctrl_ops saa7146_ctrl_ops = { - .s_ctrl = saa7146_s_ctrl, -}; - -int saa7146_vv_init(struct saa7146_dev* dev, struct saa7146_ext_vv *ext_vv) -{ - struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; - struct v4l2_pix_format *fmt; - struct v4l2_vbi_format *vbi; - struct saa7146_vv *vv; - int err; - - err = v4l2_device_register(&dev->pci->dev, &dev->v4l2_dev); - if (err) - return err; - - v4l2_ctrl_handler_init(hdl, 6); - v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, - V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); - v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, - V4L2_CID_CONTRAST, 0, 127, 1, 64); - v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, - V4L2_CID_SATURATION, 0, 127, 1, 64); - v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, - V4L2_CID_VFLIP, 0, 1, 1, 0); - v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, - V4L2_CID_HFLIP, 0, 1, 1, 0); - if (hdl->error) { - err = hdl->error; - v4l2_ctrl_handler_free(hdl); - return err; - } - dev->v4l2_dev.ctrl_handler = hdl; - - vv = kzalloc(sizeof(struct saa7146_vv), GFP_KERNEL); - if (vv == NULL) { - ERR("out of memory. aborting.\n"); - v4l2_ctrl_handler_free(hdl); - return -ENOMEM; - } - ext_vv->vid_ops = saa7146_video_ioctl_ops; - ext_vv->vbi_ops = saa7146_vbi_ioctl_ops; - ext_vv->core_ops = &saa7146_video_ioctl_ops; - - DEB_EE("dev:%p\n", dev); - - /* set default values for video parts of the saa7146 */ - saa7146_write(dev, BCS_CTRL, 0x80400040); - - /* enable video-port pins */ - saa7146_write(dev, MC1, (MASK_10 | MASK_26)); - - /* save per-device extension data (one extension can - handle different devices that might need different - configuration data) */ - dev->ext_vv_data = ext_vv; - - vv->d_clipping.cpu_addr = pci_alloc_consistent(dev->pci, SAA7146_CLIPPING_MEM, &vv->d_clipping.dma_handle); - if( NULL == vv->d_clipping.cpu_addr ) { - ERR("out of memory. aborting.\n"); - kfree(vv); - v4l2_ctrl_handler_free(hdl); - return -1; - } - memset(vv->d_clipping.cpu_addr, 0x0, SAA7146_CLIPPING_MEM); - - saa7146_video_uops.init(dev,vv); - if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) - saa7146_vbi_uops.init(dev,vv); - - fmt = &vv->ov_fb.fmt; - fmt->width = vv->standard->h_max_out; - fmt->height = vv->standard->v_max_out; - fmt->pixelformat = V4L2_PIX_FMT_RGB565; - fmt->bytesperline = 2 * fmt->width; - fmt->sizeimage = fmt->bytesperline * fmt->height; - fmt->colorspace = V4L2_COLORSPACE_SRGB; - - fmt = &vv->video_fmt; - fmt->width = 384; - fmt->height = 288; - fmt->pixelformat = V4L2_PIX_FMT_BGR24; - fmt->field = V4L2_FIELD_ANY; - fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; - fmt->bytesperline = 3 * fmt->width; - fmt->sizeimage = fmt->bytesperline * fmt->height; - - vbi = &vv->vbi_fmt; - vbi->sampling_rate = 27000000; - vbi->offset = 248; /* todo */ - vbi->samples_per_line = 720 * 2; - vbi->sample_format = V4L2_PIX_FMT_GREY; - - /* fixme: this only works for PAL */ - vbi->start[0] = 5; - vbi->count[0] = 16; - vbi->start[1] = 312; - vbi->count[1] = 16; - - init_timer(&vv->vbi_read_timeout); - - vv->ov_fb.capability = V4L2_FBUF_CAP_LIST_CLIPPING; - vv->ov_fb.flags = V4L2_FBUF_FLAG_PRIMARY; - dev->vv_data = vv; - dev->vv_callback = &vv_callback; - - return 0; -} -EXPORT_SYMBOL_GPL(saa7146_vv_init); - -int saa7146_vv_release(struct saa7146_dev* dev) -{ - struct saa7146_vv *vv = dev->vv_data; - - DEB_EE("dev:%p\n", dev); - - v4l2_device_unregister(&dev->v4l2_dev); - pci_free_consistent(dev->pci, SAA7146_CLIPPING_MEM, vv->d_clipping.cpu_addr, vv->d_clipping.dma_handle); - v4l2_ctrl_handler_free(&dev->ctrl_handler); - kfree(vv); - dev->vv_data = NULL; - dev->vv_callback = NULL; - - return 0; -} -EXPORT_SYMBOL_GPL(saa7146_vv_release); - -int saa7146_register_device(struct video_device **vid, struct saa7146_dev* dev, - char *name, int type) -{ - struct video_device *vfd; - int err; - int i; - - DEB_EE("dev:%p, name:'%s', type:%d\n", dev, name, type); - - // released by vfd->release - vfd = video_device_alloc(); - if (vfd == NULL) - return -ENOMEM; - - vfd->fops = &video_fops; - if (type == VFL_TYPE_GRABBER) - vfd->ioctl_ops = &dev->ext_vv_data->vid_ops; - else - vfd->ioctl_ops = &dev->ext_vv_data->vbi_ops; - vfd->release = video_device_release; - /* Locking in file operations other than ioctl should be done by - the driver, not the V4L2 core. - This driver needs auditing so that this flag can be removed. */ - set_bit(V4L2_FL_LOCK_ALL_FOPS, &vfd->flags); - vfd->lock = &dev->v4l2_lock; - vfd->v4l2_dev = &dev->v4l2_dev; - vfd->tvnorms = 0; - set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); - for (i = 0; i < dev->ext_vv_data->num_stds; i++) - vfd->tvnorms |= dev->ext_vv_data->stds[i].id; - strlcpy(vfd->name, name, sizeof(vfd->name)); - video_set_drvdata(vfd, dev); - - err = video_register_device(vfd, type, -1); - if (err < 0) { - ERR("cannot register v4l2 device. skipping.\n"); - video_device_release(vfd); - return err; - } - - pr_info("%s: registered device %s [v4l2]\n", - dev->name, video_device_node_name(vfd)); - - *vid = vfd; - return 0; -} -EXPORT_SYMBOL_GPL(saa7146_register_device); - -int saa7146_unregister_device(struct video_device **vid, struct saa7146_dev* dev) -{ - DEB_EE("dev:%p\n", dev); - - video_unregister_device(*vid); - *vid = NULL; - - return 0; -} -EXPORT_SYMBOL_GPL(saa7146_unregister_device); - -static int __init saa7146_vv_init_module(void) -{ - return 0; -} - - -static void __exit saa7146_vv_cleanup_module(void) -{ -} - -module_init(saa7146_vv_init_module); -module_exit(saa7146_vv_cleanup_module); - -MODULE_AUTHOR("Michael Hunold "); -MODULE_DESCRIPTION("video4linux driver for saa7146-based hardware"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/saa7146_video.c b/drivers/media/common/saa7146_video.c deleted file mode 100644 index 6d14785d4747..000000000000 --- a/drivers/media/common/saa7146_video.c +++ /dev/null @@ -1,1332 +0,0 @@ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include - -static int max_memory = 32; - -module_param(max_memory, int, 0644); -MODULE_PARM_DESC(max_memory, "maximum memory usage for capture buffers (default: 32Mb)"); - -#define IS_CAPTURE_ACTIVE(fh) \ - (((vv->video_status & STATUS_CAPTURE) != 0) && (vv->video_fh == fh)) - -#define IS_OVERLAY_ACTIVE(fh) \ - (((vv->video_status & STATUS_OVERLAY) != 0) && (vv->video_fh == fh)) - -/* format descriptions for capture and preview */ -static struct saa7146_format formats[] = { - { - .name = "RGB-8 (3-3-2)", - .pixelformat = V4L2_PIX_FMT_RGB332, - .trans = RGB08_COMPOSED, - .depth = 8, - .flags = 0, - }, { - .name = "RGB-16 (5/B-6/G-5/R)", - .pixelformat = V4L2_PIX_FMT_RGB565, - .trans = RGB16_COMPOSED, - .depth = 16, - .flags = 0, - }, { - .name = "RGB-24 (B-G-R)", - .pixelformat = V4L2_PIX_FMT_BGR24, - .trans = RGB24_COMPOSED, - .depth = 24, - .flags = 0, - }, { - .name = "RGB-32 (B-G-R)", - .pixelformat = V4L2_PIX_FMT_BGR32, - .trans = RGB32_COMPOSED, - .depth = 32, - .flags = 0, - }, { - .name = "RGB-32 (R-G-B)", - .pixelformat = V4L2_PIX_FMT_RGB32, - .trans = RGB32_COMPOSED, - .depth = 32, - .flags = 0, - .swap = 0x2, - }, { - .name = "Greyscale-8", - .pixelformat = V4L2_PIX_FMT_GREY, - .trans = Y8, - .depth = 8, - .flags = 0, - }, { - .name = "YUV 4:2:2 planar (Y-Cb-Cr)", - .pixelformat = V4L2_PIX_FMT_YUV422P, - .trans = YUV422_DECOMPOSED, - .depth = 16, - .flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, - }, { - .name = "YVU 4:2:0 planar (Y-Cb-Cr)", - .pixelformat = V4L2_PIX_FMT_YVU420, - .trans = YUV420_DECOMPOSED, - .depth = 12, - .flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, - }, { - .name = "YUV 4:2:0 planar (Y-Cb-Cr)", - .pixelformat = V4L2_PIX_FMT_YUV420, - .trans = YUV420_DECOMPOSED, - .depth = 12, - .flags = FORMAT_IS_PLANAR, - }, { - .name = "YUV 4:2:2 (U-Y-V-Y)", - .pixelformat = V4L2_PIX_FMT_UYVY, - .trans = YUV422_COMPOSED, - .depth = 16, - .flags = 0, - } -}; - -/* unfortunately, the saa7146 contains a bug which prevents it from doing on-the-fly byte swaps. - due to this, it's impossible to provide additional *packed* formats, which are simply byte swapped - (like V4L2_PIX_FMT_YUYV) ... 8-( */ - -static int NUM_FORMATS = sizeof(formats)/sizeof(struct saa7146_format); - -struct saa7146_format* saa7146_format_by_fourcc(struct saa7146_dev *dev, int fourcc) -{ - int i, j = NUM_FORMATS; - - for (i = 0; i < j; i++) { - if (formats[i].pixelformat == fourcc) { - return formats+i; - } - } - - DEB_D("unknown pixelformat:'%4.4s'\n", (char *)&fourcc); - return NULL; -} - -static int vidioc_try_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f); - -int saa7146_start_preview(struct saa7146_fh *fh) -{ - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - struct v4l2_format fmt; - int ret = 0, err = 0; - - DEB_EE("dev:%p, fh:%p\n", dev, fh); - - /* check if we have overlay information */ - if (vv->ov.fh == NULL) { - DEB_D("no overlay data available. try S_FMT first.\n"); - return -EAGAIN; - } - - /* check if streaming capture is running */ - if (IS_CAPTURE_ACTIVE(fh) != 0) { - DEB_D("streaming capture is active\n"); - return -EBUSY; - } - - /* check if overlay is running */ - if (IS_OVERLAY_ACTIVE(fh) != 0) { - if (vv->video_fh == fh) { - DEB_D("overlay is already active\n"); - return 0; - } - DEB_D("overlay is already active in another open\n"); - return -EBUSY; - } - - if (0 == saa7146_res_get(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP)) { - DEB_D("cannot get necessary overlay resources\n"); - return -EBUSY; - } - - fmt.fmt.win = vv->ov.win; - err = vidioc_try_fmt_vid_overlay(NULL, fh, &fmt); - if (0 != err) { - saa7146_res_free(vv->video_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); - return -EBUSY; - } - vv->ov.win = fmt.fmt.win; - - DEB_D("%dx%d+%d+%d %s field=%s\n", - vv->ov.win.w.width, vv->ov.win.w.height, - vv->ov.win.w.left, vv->ov.win.w.top, - vv->ov_fmt->name, v4l2_field_names[vv->ov.win.field]); - - if (0 != (ret = saa7146_enable_overlay(fh))) { - DEB_D("enabling overlay failed: %d\n", ret); - saa7146_res_free(vv->video_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); - return ret; - } - - vv->video_status = STATUS_OVERLAY; - vv->video_fh = fh; - - return 0; -} -EXPORT_SYMBOL_GPL(saa7146_start_preview); - -int saa7146_stop_preview(struct saa7146_fh *fh) -{ - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - - DEB_EE("dev:%p, fh:%p\n", dev, fh); - - /* check if streaming capture is running */ - if (IS_CAPTURE_ACTIVE(fh) != 0) { - DEB_D("streaming capture is active\n"); - return -EBUSY; - } - - /* check if overlay is running at all */ - if ((vv->video_status & STATUS_OVERLAY) == 0) { - DEB_D("no active overlay\n"); - return 0; - } - - if (vv->video_fh != fh) { - DEB_D("overlay is active, but in another open\n"); - return -EBUSY; - } - - vv->video_status = 0; - vv->video_fh = NULL; - - saa7146_disable_overlay(fh); - - saa7146_res_free(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); - - return 0; -} -EXPORT_SYMBOL_GPL(saa7146_stop_preview); - -/********************************************************************************/ -/* common pagetable functions */ - -static int saa7146_pgtable_build(struct saa7146_dev *dev, struct saa7146_buf *buf) -{ - struct pci_dev *pci = dev->pci; - struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); - struct scatterlist *list = dma->sglist; - int length = dma->sglen; - struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); - - DEB_EE("dev:%p, buf:%p, sg_len:%d\n", dev, buf, length); - - if( 0 != IS_PLANAR(sfmt->trans)) { - struct saa7146_pgtable *pt1 = &buf->pt[0]; - struct saa7146_pgtable *pt2 = &buf->pt[1]; - struct saa7146_pgtable *pt3 = &buf->pt[2]; - __le32 *ptr1, *ptr2, *ptr3; - __le32 fill; - - int size = buf->fmt->width*buf->fmt->height; - int i,p,m1,m2,m3,o1,o2; - - switch( sfmt->depth ) { - case 12: { - /* create some offsets inside the page table */ - m1 = ((size+PAGE_SIZE)/PAGE_SIZE)-1; - m2 = ((size+(size/4)+PAGE_SIZE)/PAGE_SIZE)-1; - m3 = ((size+(size/2)+PAGE_SIZE)/PAGE_SIZE)-1; - o1 = size%PAGE_SIZE; - o2 = (size+(size/4))%PAGE_SIZE; - DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", - size, m1, m2, m3, o1, o2); - break; - } - case 16: { - /* create some offsets inside the page table */ - m1 = ((size+PAGE_SIZE)/PAGE_SIZE)-1; - m2 = ((size+(size/2)+PAGE_SIZE)/PAGE_SIZE)-1; - m3 = ((2*size+PAGE_SIZE)/PAGE_SIZE)-1; - o1 = size%PAGE_SIZE; - o2 = (size+(size/2))%PAGE_SIZE; - DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", - size, m1, m2, m3, o1, o2); - break; - } - default: { - return -1; - } - } - - ptr1 = pt1->cpu; - ptr2 = pt2->cpu; - ptr3 = pt3->cpu; - - /* walk all pages, copy all page addresses to ptr1 */ - for (i = 0; i < length; i++, list++) { - for (p = 0; p * 4096 < list->length; p++, ptr1++) { - *ptr1 = cpu_to_le32(sg_dma_address(list) - list->offset); - } - } -/* - ptr1 = pt1->cpu; - for(j=0;j<40;j++) { - printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); - } -*/ - - /* if we have a user buffer, the first page may not be - aligned to a page boundary. */ - pt1->offset = dma->sglist->offset; - pt2->offset = pt1->offset+o1; - pt3->offset = pt1->offset+o2; - - /* create video-dma2 page table */ - ptr1 = pt1->cpu; - for(i = m1; i <= m2 ; i++, ptr2++) { - *ptr2 = ptr1[i]; - } - fill = *(ptr2-1); - for(;i<1024;i++,ptr2++) { - *ptr2 = fill; - } - /* create video-dma3 page table */ - ptr1 = pt1->cpu; - for(i = m2; i <= m3; i++,ptr3++) { - *ptr3 = ptr1[i]; - } - fill = *(ptr3-1); - for(;i<1024;i++,ptr3++) { - *ptr3 = fill; - } - /* finally: finish up video-dma1 page table */ - ptr1 = pt1->cpu+m1; - fill = pt1->cpu[m1]; - for(i=m1;i<1024;i++,ptr1++) { - *ptr1 = fill; - } -/* - ptr1 = pt1->cpu; - ptr2 = pt2->cpu; - ptr3 = pt3->cpu; - for(j=0;j<40;j++) { - printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); - } - for(j=0;j<40;j++) { - printk("ptr2 %d: 0x%08x\n",j,ptr2[j]); - } - for(j=0;j<40;j++) { - printk("ptr3 %d: 0x%08x\n",j,ptr3[j]); - } -*/ - } else { - struct saa7146_pgtable *pt = &buf->pt[0]; - return saa7146_pgtable_build_single(pci, pt, list, length); - } - - return 0; -} - - -/********************************************************************************/ -/* file operations */ - -static int video_begin(struct saa7146_fh *fh) -{ - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - struct saa7146_format *fmt = NULL; - unsigned int resource; - int ret = 0, err = 0; - - DEB_EE("dev:%p, fh:%p\n", dev, fh); - - if ((vv->video_status & STATUS_CAPTURE) != 0) { - if (vv->video_fh == fh) { - DEB_S("already capturing\n"); - return 0; - } - DEB_S("already capturing in another open\n"); - return -EBUSY; - } - - if ((vv->video_status & STATUS_OVERLAY) != 0) { - DEB_S("warning: suspending overlay video for streaming capture\n"); - vv->ov_suspend = vv->video_fh; - err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ - if (0 != err) { - DEB_D("suspending video failed. aborting\n"); - return err; - } - } - - fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); - /* we need to have a valid format set here */ - BUG_ON(NULL == fmt); - - if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { - resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; - } else { - resource = RESOURCE_DMA1_HPS; - } - - ret = saa7146_res_get(fh, resource); - if (0 == ret) { - DEB_S("cannot get capture resource %d\n", resource); - if (vv->ov_suspend != NULL) { - saa7146_start_preview(vv->ov_suspend); - vv->ov_suspend = NULL; - } - return -EBUSY; - } - - /* clear out beginning of streaming bit (rps register 0)*/ - saa7146_write(dev, MC2, MASK_27 ); - - /* enable rps0 irqs */ - SAA7146_IER_ENABLE(dev, MASK_27); - - vv->video_fh = fh; - vv->video_status = STATUS_CAPTURE; - - return 0; -} - -static int video_end(struct saa7146_fh *fh, struct file *file) -{ - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - struct saa7146_format *fmt = NULL; - unsigned long flags; - unsigned int resource; - u32 dmas = 0; - DEB_EE("dev:%p, fh:%p\n", dev, fh); - - if ((vv->video_status & STATUS_CAPTURE) != STATUS_CAPTURE) { - DEB_S("not capturing\n"); - return 0; - } - - if (vv->video_fh != fh) { - DEB_S("capturing, but in another open\n"); - return -EBUSY; - } - - fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); - /* we need to have a valid format set here */ - BUG_ON(NULL == fmt); - - if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { - resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; - dmas = MASK_22 | MASK_21 | MASK_20; - } else { - resource = RESOURCE_DMA1_HPS; - dmas = MASK_22; - } - spin_lock_irqsave(&dev->slock,flags); - - /* disable rps0 */ - saa7146_write(dev, MC1, MASK_28); - - /* disable rps0 irqs */ - SAA7146_IER_DISABLE(dev, MASK_27); - - /* shut down all used video dma transfers */ - saa7146_write(dev, MC1, dmas); - - spin_unlock_irqrestore(&dev->slock, flags); - - vv->video_fh = NULL; - vv->video_status = 0; - - saa7146_res_free(fh, resource); - - if (vv->ov_suspend != NULL) { - saa7146_start_preview(vv->ov_suspend); - vv->ov_suspend = NULL; - } - - return 0; -} - -static int vidioc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) -{ - struct video_device *vdev = video_devdata(file); - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - - strcpy((char *)cap->driver, "saa7146 v4l2"); - strlcpy((char *)cap->card, dev->ext->name, sizeof(cap->card)); - sprintf((char *)cap->bus_info, "PCI:%s", pci_name(dev->pci)); - cap->device_caps = - V4L2_CAP_VIDEO_CAPTURE | - V4L2_CAP_VIDEO_OVERLAY | - V4L2_CAP_READWRITE | - V4L2_CAP_STREAMING; - cap->device_caps |= dev->ext_vv_data->capabilities; - cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; - if (vdev->vfl_type == VFL_TYPE_GRABBER) - cap->device_caps &= - ~(V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_OUTPUT); - else - cap->device_caps &= - ~(V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_AUDIO); - return 0; -} - -static int vidioc_g_fbuf(struct file *file, void *fh, struct v4l2_framebuffer *fb) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - - *fb = vv->ov_fb; - fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; - fb->flags = V4L2_FBUF_FLAG_PRIMARY; - return 0; -} - -static int vidioc_s_fbuf(struct file *file, void *fh, struct v4l2_framebuffer *fb) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - struct saa7146_format *fmt; - - DEB_EE("VIDIOC_S_FBUF\n"); - - if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) - return -EPERM; - - /* check args */ - fmt = saa7146_format_by_fourcc(dev, fb->fmt.pixelformat); - if (NULL == fmt) - return -EINVAL; - - /* planar formats are not allowed for overlay video, clipping and video dma would clash */ - if (fmt->flags & FORMAT_IS_PLANAR) - DEB_S("planar pixelformat '%4.4s' not allowed for overlay\n", - (char *)&fmt->pixelformat); - - /* check if overlay is running */ - if (IS_OVERLAY_ACTIVE(fh) != 0) { - if (vv->video_fh != fh) { - DEB_D("refusing to change framebuffer informations while overlay is active in another open\n"); - return -EBUSY; - } - } - - /* ok, accept it */ - vv->ov_fb = *fb; - vv->ov_fmt = fmt; - - if (vv->ov_fb.fmt.bytesperline < vv->ov_fb.fmt.width) { - vv->ov_fb.fmt.bytesperline = vv->ov_fb.fmt.width * fmt->depth / 8; - DEB_D("setting bytesperline to %d\n", vv->ov_fb.fmt.bytesperline); - } - return 0; -} - -static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f) -{ - if (f->index >= NUM_FORMATS) - return -EINVAL; - strlcpy((char *)f->description, formats[f->index].name, - sizeof(f->description)); - f->pixelformat = formats[f->index].pixelformat; - return 0; -} - -int saa7146_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct saa7146_dev *dev = container_of(ctrl->handler, - struct saa7146_dev, ctrl_handler); - struct saa7146_vv *vv = dev->vv_data; - u32 val; - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - val = saa7146_read(dev, BCS_CTRL); - val &= 0x00ffffff; - val |= (ctrl->val << 24); - saa7146_write(dev, BCS_CTRL, val); - saa7146_write(dev, MC2, MASK_22 | MASK_06); - break; - - case V4L2_CID_CONTRAST: - val = saa7146_read(dev, BCS_CTRL); - val &= 0xff00ffff; - val |= (ctrl->val << 16); - saa7146_write(dev, BCS_CTRL, val); - saa7146_write(dev, MC2, MASK_22 | MASK_06); - break; - - case V4L2_CID_SATURATION: - val = saa7146_read(dev, BCS_CTRL); - val &= 0xffffff00; - val |= (ctrl->val << 0); - saa7146_write(dev, BCS_CTRL, val); - saa7146_write(dev, MC2, MASK_22 | MASK_06); - break; - - case V4L2_CID_HFLIP: - /* fixme: we can support changing VFLIP and HFLIP here... */ - if ((vv->video_status & STATUS_CAPTURE)) - return -EBUSY; - vv->hflip = ctrl->val; - break; - - case V4L2_CID_VFLIP: - if ((vv->video_status & STATUS_CAPTURE)) - return -EBUSY; - vv->vflip = ctrl->val; - break; - - default: - return -EINVAL; - } - - if ((vv->video_status & STATUS_OVERLAY) != 0) { /* CHECK: && (vv->video_fh == fh)) */ - struct saa7146_fh *fh = vv->video_fh; - - saa7146_stop_preview(fh); - saa7146_start_preview(fh); - } - return 0; -} - -static int vidioc_g_parm(struct file *file, void *fh, - struct v4l2_streamparm *parm) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - - if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) - return -EINVAL; - parm->parm.capture.readbuffers = 1; - v4l2_video_std_frame_period(vv->standard->id, - &parm->parm.capture.timeperframe); - return 0; -} - -static int vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - - f->fmt.pix = vv->video_fmt; - return 0; -} - -static int vidioc_g_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - - f->fmt.win = vv->ov.win; - return 0; -} - -static int vidioc_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - - f->fmt.vbi = vv->vbi_fmt; - return 0; -} - -static int vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - struct saa7146_format *fmt; - enum v4l2_field field; - int maxw, maxh; - int calc_bpl; - - DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh); - - fmt = saa7146_format_by_fourcc(dev, f->fmt.pix.pixelformat); - if (NULL == fmt) - return -EINVAL; - - field = f->fmt.pix.field; - maxw = vv->standard->h_max_out; - maxh = vv->standard->v_max_out; - - if (V4L2_FIELD_ANY == field) { - field = (f->fmt.pix.height > maxh / 2) - ? V4L2_FIELD_INTERLACED - : V4L2_FIELD_BOTTOM; - } - switch (field) { - case V4L2_FIELD_ALTERNATE: - vv->last_field = V4L2_FIELD_TOP; - maxh = maxh / 2; - break; - case V4L2_FIELD_TOP: - case V4L2_FIELD_BOTTOM: - vv->last_field = V4L2_FIELD_INTERLACED; - maxh = maxh / 2; - break; - case V4L2_FIELD_INTERLACED: - vv->last_field = V4L2_FIELD_INTERLACED; - break; - default: - DEB_D("no known field mode '%d'\n", field); - return -EINVAL; - } - - f->fmt.pix.field = field; - f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; - if (f->fmt.pix.width > maxw) - f->fmt.pix.width = maxw; - if (f->fmt.pix.height > maxh) - f->fmt.pix.height = maxh; - - calc_bpl = (f->fmt.pix.width * fmt->depth) / 8; - - if (f->fmt.pix.bytesperline < calc_bpl) - f->fmt.pix.bytesperline = calc_bpl; - - if (f->fmt.pix.bytesperline > (2 * PAGE_SIZE * fmt->depth) / 8) /* arbitrary constraint */ - f->fmt.pix.bytesperline = calc_bpl; - - f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; - DEB_D("w:%d, h:%d, bytesperline:%d, sizeimage:%d\n", - f->fmt.pix.width, f->fmt.pix.height, - f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); - - return 0; -} - - -static int vidioc_try_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - struct v4l2_window *win = &f->fmt.win; - enum v4l2_field field; - int maxw, maxh; - - DEB_EE("dev:%p\n", dev); - - if (NULL == vv->ov_fb.base) { - DEB_D("no fb base set\n"); - return -EINVAL; - } - if (NULL == vv->ov_fmt) { - DEB_D("no fb fmt set\n"); - return -EINVAL; - } - if (win->w.width < 48 || win->w.height < 32) { - DEB_D("min width/height. (%d,%d)\n", - win->w.width, win->w.height); - return -EINVAL; - } - if (win->clipcount > 16) { - DEB_D("clipcount too big\n"); - return -EINVAL; - } - - field = win->field; - maxw = vv->standard->h_max_out; - maxh = vv->standard->v_max_out; - - if (V4L2_FIELD_ANY == field) { - field = (win->w.height > maxh / 2) - ? V4L2_FIELD_INTERLACED - : V4L2_FIELD_TOP; - } - switch (field) { - case V4L2_FIELD_TOP: - case V4L2_FIELD_BOTTOM: - case V4L2_FIELD_ALTERNATE: - maxh = maxh / 2; - break; - case V4L2_FIELD_INTERLACED: - break; - default: - DEB_D("no known field mode '%d'\n", field); - return -EINVAL; - } - - win->field = field; - if (win->w.width > maxw) - win->w.width = maxw; - if (win->w.height > maxh) - win->w.height = maxh; - - return 0; -} - -static int vidioc_s_fmt_vid_cap(struct file *file, void *__fh, struct v4l2_format *f) -{ - struct saa7146_fh *fh = __fh; - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - int err; - - DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh); - if (IS_CAPTURE_ACTIVE(fh) != 0) { - DEB_EE("streaming capture is active\n"); - return -EBUSY; - } - err = vidioc_try_fmt_vid_cap(file, fh, f); - if (0 != err) - return err; - vv->video_fmt = f->fmt.pix; - DEB_EE("set to pixelformat '%4.4s'\n", - (char *)&vv->video_fmt.pixelformat); - return 0; -} - -static int vidioc_s_fmt_vid_overlay(struct file *file, void *__fh, struct v4l2_format *f) -{ - struct saa7146_fh *fh = __fh; - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - int err; - - DEB_EE("V4L2_BUF_TYPE_VIDEO_OVERLAY: dev:%p, fh:%p\n", dev, fh); - err = vidioc_try_fmt_vid_overlay(file, fh, f); - if (0 != err) - return err; - vv->ov.win = f->fmt.win; - vv->ov.nclips = f->fmt.win.clipcount; - if (vv->ov.nclips > 16) - vv->ov.nclips = 16; - if (copy_from_user(vv->ov.clips, f->fmt.win.clips, - sizeof(struct v4l2_clip) * vv->ov.nclips)) { - return -EFAULT; - } - - /* vv->ov.fh is used to indicate that we have valid overlay informations, too */ - vv->ov.fh = fh; - - /* check if our current overlay is active */ - if (IS_OVERLAY_ACTIVE(fh) != 0) { - saa7146_stop_preview(fh); - saa7146_start_preview(fh); - } - return 0; -} - -static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - - *norm = vv->standard->id; - return 0; -} - - /* the saa7146 supfhrts (used in conjunction with the saa7111a for example) - PAL / NTSC / SECAM. if your hardware does not (or does more) - -- override this function in your extension */ -/* - case VIDIOC_ENUMSTD: - { - struct v4l2_standard *e = arg; - if (e->index < 0 ) - return -EINVAL; - if( e->index < dev->ext_vv_data->num_stds ) { - DEB_EE("VIDIOC_ENUMSTD: index:%d\n", e->index); - v4l2_video_std_construct(e, dev->ext_vv_data->stds[e->index].id, dev->ext_vv_data->stds[e->index].name); - return 0; - } - return -EINVAL; - } - */ - -static int vidioc_s_std(struct file *file, void *fh, v4l2_std_id *id) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct saa7146_vv *vv = dev->vv_data; - int found = 0; - int err, i; - - DEB_EE("VIDIOC_S_STD\n"); - - if ((vv->video_status & STATUS_CAPTURE) == STATUS_CAPTURE) { - DEB_D("cannot change video standard while streaming capture is active\n"); - return -EBUSY; - } - - if ((vv->video_status & STATUS_OVERLAY) != 0) { - vv->ov_suspend = vv->video_fh; - err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ - if (0 != err) { - DEB_D("suspending video failed. aborting\n"); - return err; - } - } - - for (i = 0; i < dev->ext_vv_data->num_stds; i++) - if (*id & dev->ext_vv_data->stds[i].id) - break; - if (i != dev->ext_vv_data->num_stds) { - vv->standard = &dev->ext_vv_data->stds[i]; - if (NULL != dev->ext_vv_data->std_callback) - dev->ext_vv_data->std_callback(dev, vv->standard); - found = 1; - } - - if (vv->ov_suspend != NULL) { - saa7146_start_preview(vv->ov_suspend); - vv->ov_suspend = NULL; - } - - if (!found) { - DEB_EE("VIDIOC_S_STD: standard not found\n"); - return -EINVAL; - } - - DEB_EE("VIDIOC_S_STD: set to standard to '%s'\n", vv->standard->name); - return 0; -} - -static int vidioc_overlay(struct file *file, void *fh, unsigned int on) -{ - int err; - - DEB_D("VIDIOC_OVERLAY on:%d\n", on); - if (on) - err = saa7146_start_preview(fh); - else - err = saa7146_stop_preview(fh); - return err; -} - -static int vidioc_reqbufs(struct file *file, void *__fh, struct v4l2_requestbuffers *b) -{ - struct saa7146_fh *fh = __fh; - - if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) - return videobuf_reqbufs(&fh->video_q, b); - if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE) - return videobuf_reqbufs(&fh->vbi_q, b); - return -EINVAL; -} - -static int vidioc_querybuf(struct file *file, void *__fh, struct v4l2_buffer *buf) -{ - struct saa7146_fh *fh = __fh; - - if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) - return videobuf_querybuf(&fh->video_q, buf); - if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) - return videobuf_querybuf(&fh->vbi_q, buf); - return -EINVAL; -} - -static int vidioc_qbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) -{ - struct saa7146_fh *fh = __fh; - - if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) - return videobuf_qbuf(&fh->video_q, buf); - if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) - return videobuf_qbuf(&fh->vbi_q, buf); - return -EINVAL; -} - -static int vidioc_dqbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) -{ - struct saa7146_fh *fh = __fh; - - if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) - return videobuf_dqbuf(&fh->video_q, buf, file->f_flags & O_NONBLOCK); - if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) - return videobuf_dqbuf(&fh->vbi_q, buf, file->f_flags & O_NONBLOCK); - return -EINVAL; -} - -static int vidioc_streamon(struct file *file, void *__fh, enum v4l2_buf_type type) -{ - struct saa7146_fh *fh = __fh; - int err; - - DEB_D("VIDIOC_STREAMON, type:%d\n", type); - - err = video_begin(fh); - if (err) - return err; - if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) - return videobuf_streamon(&fh->video_q); - if (type == V4L2_BUF_TYPE_VBI_CAPTURE) - return videobuf_streamon(&fh->vbi_q); - return -EINVAL; -} - -static int vidioc_streamoff(struct file *file, void *__fh, enum v4l2_buf_type type) -{ - struct saa7146_fh *fh = __fh; - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - int err; - - DEB_D("VIDIOC_STREAMOFF, type:%d\n", type); - - /* ugly: we need to copy some checks from video_end(), - because videobuf_streamoff() relies on the capture running. - check and fix this */ - if ((vv->video_status & STATUS_CAPTURE) != STATUS_CAPTURE) { - DEB_S("not capturing\n"); - return 0; - } - - if (vv->video_fh != fh) { - DEB_S("capturing, but in another open\n"); - return -EBUSY; - } - - err = -EINVAL; - if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) - err = videobuf_streamoff(&fh->video_q); - else if (type == V4L2_BUF_TYPE_VBI_CAPTURE) - err = videobuf_streamoff(&fh->vbi_q); - if (0 != err) { - DEB_D("warning: videobuf_streamoff() failed\n"); - video_end(fh, file); - } else { - err = video_end(fh, file); - } - return err; -} - -static int vidioc_g_chip_ident(struct file *file, void *__fh, - struct v4l2_dbg_chip_ident *chip) -{ - struct saa7146_fh *fh = __fh; - struct saa7146_dev *dev = fh->dev; - - chip->ident = V4L2_IDENT_NONE; - chip->revision = 0; - if (chip->match.type == V4L2_CHIP_MATCH_HOST) { - if (v4l2_chip_match_host(&chip->match)) - chip->ident = V4L2_IDENT_SAA7146; - return 0; - } - if (chip->match.type != V4L2_CHIP_MATCH_I2C_DRIVER && - chip->match.type != V4L2_CHIP_MATCH_I2C_ADDR) - return -EINVAL; - return v4l2_device_call_until_err(&dev->v4l2_dev, 0, - core, g_chip_ident, chip); -} - -const struct v4l2_ioctl_ops saa7146_video_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, - .vidioc_enum_fmt_vid_overlay = vidioc_enum_fmt_vid_cap, - .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, - .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, - .vidioc_g_fmt_vid_overlay = vidioc_g_fmt_vid_overlay, - .vidioc_try_fmt_vid_overlay = vidioc_try_fmt_vid_overlay, - .vidioc_s_fmt_vid_overlay = vidioc_s_fmt_vid_overlay, - .vidioc_g_chip_ident = vidioc_g_chip_ident, - - .vidioc_overlay = vidioc_overlay, - .vidioc_g_fbuf = vidioc_g_fbuf, - .vidioc_s_fbuf = vidioc_s_fbuf, - .vidioc_reqbufs = vidioc_reqbufs, - .vidioc_querybuf = vidioc_querybuf, - .vidioc_qbuf = vidioc_qbuf, - .vidioc_dqbuf = vidioc_dqbuf, - .vidioc_g_std = vidioc_g_std, - .vidioc_s_std = vidioc_s_std, - .vidioc_streamon = vidioc_streamon, - .vidioc_streamoff = vidioc_streamoff, - .vidioc_g_parm = vidioc_g_parm, - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, -}; - -const struct v4l2_ioctl_ops saa7146_vbi_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi_cap, - .vidioc_g_chip_ident = vidioc_g_chip_ident, - - .vidioc_reqbufs = vidioc_reqbufs, - .vidioc_querybuf = vidioc_querybuf, - .vidioc_qbuf = vidioc_qbuf, - .vidioc_dqbuf = vidioc_dqbuf, - .vidioc_g_std = vidioc_g_std, - .vidioc_s_std = vidioc_s_std, - .vidioc_streamon = vidioc_streamon, - .vidioc_streamoff = vidioc_streamoff, - .vidioc_g_parm = vidioc_g_parm, - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, -}; - -/*********************************************************************************/ -/* buffer handling functions */ - -static int buffer_activate (struct saa7146_dev *dev, - struct saa7146_buf *buf, - struct saa7146_buf *next) -{ - struct saa7146_vv *vv = dev->vv_data; - - buf->vb.state = VIDEOBUF_ACTIVE; - saa7146_set_capture(dev,buf,next); - - mod_timer(&vv->video_dmaq.timeout, jiffies+BUFFER_TIMEOUT); - return 0; -} - -static void release_all_pagetables(struct saa7146_dev *dev, struct saa7146_buf *buf) -{ - saa7146_pgtable_free(dev->pci, &buf->pt[0]); - saa7146_pgtable_free(dev->pci, &buf->pt[1]); - saa7146_pgtable_free(dev->pci, &buf->pt[2]); -} - -static int buffer_prepare(struct videobuf_queue *q, - struct videobuf_buffer *vb, enum v4l2_field field) -{ - struct file *file = q->priv_data; - struct saa7146_fh *fh = file->private_data; - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - struct saa7146_buf *buf = (struct saa7146_buf *)vb; - int size,err = 0; - - DEB_CAP("vbuf:%p\n", vb); - - /* sanity checks */ - if (vv->video_fmt.width < 48 || - vv->video_fmt.height < 32 || - vv->video_fmt.width > vv->standard->h_max_out || - vv->video_fmt.height > vv->standard->v_max_out) { - DEB_D("w (%d) / h (%d) out of bounds\n", - vv->video_fmt.width, vv->video_fmt.height); - return -EINVAL; - } - - size = vv->video_fmt.sizeimage; - if (0 != buf->vb.baddr && buf->vb.bsize < size) { - DEB_D("size mismatch\n"); - return -EINVAL; - } - - DEB_CAP("buffer_prepare [size=%dx%d,bytes=%d,fields=%s]\n", - vv->video_fmt.width, vv->video_fmt.height, - size, v4l2_field_names[vv->video_fmt.field]); - if (buf->vb.width != vv->video_fmt.width || - buf->vb.bytesperline != vv->video_fmt.bytesperline || - buf->vb.height != vv->video_fmt.height || - buf->vb.size != size || - buf->vb.field != field || - buf->vb.field != vv->video_fmt.field || - buf->fmt != &vv->video_fmt) { - saa7146_dma_free(dev,q,buf); - } - - if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { - struct saa7146_format *sfmt; - - buf->vb.bytesperline = vv->video_fmt.bytesperline; - buf->vb.width = vv->video_fmt.width; - buf->vb.height = vv->video_fmt.height; - buf->vb.size = size; - buf->vb.field = field; - buf->fmt = &vv->video_fmt; - buf->vb.field = vv->video_fmt.field; - - sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); - - release_all_pagetables(dev, buf); - if( 0 != IS_PLANAR(sfmt->trans)) { - saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); - saa7146_pgtable_alloc(dev->pci, &buf->pt[1]); - saa7146_pgtable_alloc(dev->pci, &buf->pt[2]); - } else { - saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); - } - - err = videobuf_iolock(q,&buf->vb, &vv->ov_fb); - if (err) - goto oops; - err = saa7146_pgtable_build(dev,buf); - if (err) - goto oops; - } - buf->vb.state = VIDEOBUF_PREPARED; - buf->activate = buffer_activate; - - return 0; - - oops: - DEB_D("error out\n"); - saa7146_dma_free(dev,q,buf); - - return err; -} - -static int buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) -{ - struct file *file = q->priv_data; - struct saa7146_fh *fh = file->private_data; - struct saa7146_vv *vv = fh->dev->vv_data; - - if (0 == *count || *count > MAX_SAA7146_CAPTURE_BUFFERS) - *count = MAX_SAA7146_CAPTURE_BUFFERS; - - *size = vv->video_fmt.sizeimage; - - /* check if we exceed the "max_memory" parameter */ - if( (*count * *size) > (max_memory*1048576) ) { - *count = (max_memory*1048576) / *size; - } - - DEB_CAP("%d buffers, %d bytes each\n", *count, *size); - - return 0; -} - -static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) -{ - struct file *file = q->priv_data; - struct saa7146_fh *fh = file->private_data; - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - struct saa7146_buf *buf = (struct saa7146_buf *)vb; - - DEB_CAP("vbuf:%p\n", vb); - saa7146_buffer_queue(fh->dev, &vv->video_dmaq, buf); -} - -static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) -{ - struct file *file = q->priv_data; - struct saa7146_fh *fh = file->private_data; - struct saa7146_dev *dev = fh->dev; - struct saa7146_buf *buf = (struct saa7146_buf *)vb; - - DEB_CAP("vbuf:%p\n", vb); - - saa7146_dma_free(dev,q,buf); - - release_all_pagetables(dev, buf); -} - -static struct videobuf_queue_ops video_qops = { - .buf_setup = buffer_setup, - .buf_prepare = buffer_prepare, - .buf_queue = buffer_queue, - .buf_release = buffer_release, -}; - -/********************************************************************************/ -/* file operations */ - -static void video_init(struct saa7146_dev *dev, struct saa7146_vv *vv) -{ - INIT_LIST_HEAD(&vv->video_dmaq.queue); - - init_timer(&vv->video_dmaq.timeout); - vv->video_dmaq.timeout.function = saa7146_buffer_timeout; - vv->video_dmaq.timeout.data = (unsigned long)(&vv->video_dmaq); - vv->video_dmaq.dev = dev; - - /* set some default values */ - vv->standard = &dev->ext_vv_data->stds[0]; - - /* FIXME: what's this? */ - vv->current_hps_source = SAA7146_HPS_SOURCE_PORT_A; - vv->current_hps_sync = SAA7146_HPS_SYNC_PORT_A; -} - - -static int video_open(struct saa7146_dev *dev, struct file *file) -{ - struct saa7146_fh *fh = file->private_data; - - videobuf_queue_sg_init(&fh->video_q, &video_qops, - &dev->pci->dev, &dev->slock, - V4L2_BUF_TYPE_VIDEO_CAPTURE, - V4L2_FIELD_INTERLACED, - sizeof(struct saa7146_buf), - file, &dev->v4l2_lock); - - return 0; -} - - -static void video_close(struct saa7146_dev *dev, struct file *file) -{ - struct saa7146_fh *fh = file->private_data; - struct saa7146_vv *vv = dev->vv_data; - struct videobuf_queue *q = &fh->video_q; - - if (IS_CAPTURE_ACTIVE(fh) != 0) - video_end(fh, file); - else if (IS_OVERLAY_ACTIVE(fh) != 0) - saa7146_stop_preview(fh); - - videobuf_stop(q); - /* hmm, why is this function declared void? */ -} - - -static void video_irq_done(struct saa7146_dev *dev, unsigned long st) -{ - struct saa7146_vv *vv = dev->vv_data; - struct saa7146_dmaqueue *q = &vv->video_dmaq; - - spin_lock(&dev->slock); - DEB_CAP("called\n"); - - /* only finish the buffer if we have one... */ - if( NULL != q->curr ) { - saa7146_buffer_finish(dev,q,VIDEOBUF_DONE); - } - saa7146_buffer_next(dev,q,0); - - spin_unlock(&dev->slock); -} - -static ssize_t video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) -{ - struct saa7146_fh *fh = file->private_data; - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - ssize_t ret = 0; - - DEB_EE("called\n"); - - if ((vv->video_status & STATUS_CAPTURE) != 0) { - /* fixme: should we allow read() captures while streaming capture? */ - if (vv->video_fh == fh) { - DEB_S("already capturing\n"); - return -EBUSY; - } - DEB_S("already capturing in another open\n"); - return -EBUSY; - } - - ret = video_begin(fh); - if( 0 != ret) { - goto out; - } - - ret = videobuf_read_one(&fh->video_q , data, count, ppos, - file->f_flags & O_NONBLOCK); - if (ret != 0) { - video_end(fh, file); - } else { - ret = video_end(fh, file); - } -out: - /* restart overlay if it was active before */ - if (vv->ov_suspend != NULL) { - saa7146_start_preview(vv->ov_suspend); - vv->ov_suspend = NULL; - } - - return ret; -} - -struct saa7146_use_ops saa7146_video_uops = { - .init = video_init, - .open = video_open, - .release = video_close, - .irq_done = video_irq_done, - .read = video_read, -}; diff --git a/drivers/media/common/siano/Kconfig b/drivers/media/common/siano/Kconfig new file mode 100644 index 000000000000..425aeadfb49d --- /dev/null +++ b/drivers/media/common/siano/Kconfig @@ -0,0 +1,17 @@ +# +# Siano Mobile Silicon Digital TV device configuration +# + +config SMS_SIANO_MDTV + tristate + depends on DVB_CORE && RC_CORE && HAS_DMA + depends on SMS_USB_DRV || SMS_SDIO_DRV + default y + ---help--- + Choose Y or M here if you have MDTV receiver with a Siano chipset. + + To compile this driver as a module, choose M here + (The module will be called smsmdtv). + + Further documentation on this driver can be found on the WWW + at http://www.siano-ms.com/ diff --git a/drivers/media/common/siano/Makefile b/drivers/media/common/siano/Makefile new file mode 100644 index 000000000000..2a09279e0648 --- /dev/null +++ b/drivers/media/common/siano/Makefile @@ -0,0 +1,7 @@ +smsmdtv-objs := smscoreapi.o sms-cards.o smsendian.o smsir.o + +obj-$(CONFIG_SMS_SIANO_MDTV) += smsmdtv.o smsdvb.o + +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += $(extra-cflags-y) $(extra-cflags-m) + diff --git a/drivers/media/dvb/siano/sms-cards.c b/drivers/media/common/siano/sms-cards.c similarity index 100% rename from drivers/media/dvb/siano/sms-cards.c rename to drivers/media/common/siano/sms-cards.c diff --git a/drivers/media/dvb/siano/sms-cards.h b/drivers/media/common/siano/sms-cards.h similarity index 100% rename from drivers/media/dvb/siano/sms-cards.h rename to drivers/media/common/siano/sms-cards.h diff --git a/drivers/media/dvb/siano/smscoreapi.c b/drivers/media/common/siano/smscoreapi.c similarity index 100% rename from drivers/media/dvb/siano/smscoreapi.c rename to drivers/media/common/siano/smscoreapi.c diff --git a/drivers/media/dvb/siano/smscoreapi.h b/drivers/media/common/siano/smscoreapi.h similarity index 100% rename from drivers/media/dvb/siano/smscoreapi.h rename to drivers/media/common/siano/smscoreapi.h diff --git a/drivers/media/dvb/siano/smsdvb.c b/drivers/media/common/siano/smsdvb.c similarity index 100% rename from drivers/media/dvb/siano/smsdvb.c rename to drivers/media/common/siano/smsdvb.c diff --git a/drivers/media/dvb/siano/smsendian.c b/drivers/media/common/siano/smsendian.c similarity index 100% rename from drivers/media/dvb/siano/smsendian.c rename to drivers/media/common/siano/smsendian.c diff --git a/drivers/media/dvb/siano/smsendian.h b/drivers/media/common/siano/smsendian.h similarity index 100% rename from drivers/media/dvb/siano/smsendian.h rename to drivers/media/common/siano/smsendian.h diff --git a/drivers/media/dvb/siano/smsir.c b/drivers/media/common/siano/smsir.c similarity index 100% rename from drivers/media/dvb/siano/smsir.c rename to drivers/media/common/siano/smsir.c diff --git a/drivers/media/dvb/siano/smsir.h b/drivers/media/common/siano/smsir.h similarity index 100% rename from drivers/media/dvb/siano/smsir.h rename to drivers/media/common/siano/smsir.h diff --git a/drivers/media/common/tuners/Kconfig b/drivers/media/common/tuners/Kconfig deleted file mode 100644 index 94c6ff7a5da3..000000000000 --- a/drivers/media/common/tuners/Kconfig +++ /dev/null @@ -1,243 +0,0 @@ -config MEDIA_ATTACH - bool "Load and attach frontend and tuner driver modules as needed" - depends on MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT || MEDIA_RADIO_SUPPORT - depends on MODULES - default y if !EXPERT - help - Remove the static dependency of DVB card drivers on all - frontend modules for all possible card variants. Instead, - allow the card drivers to only load the frontend modules - they require. - - Also, tuner module will automatically load a tuner driver - when needed, for analog mode. - - This saves several KBytes of memory. - - Note: You will need module-init-tools v3.2 or later for this feature. - - If unsure say Y. - -config MEDIA_TUNER - tristate - depends on (MEDIA_ANALOG_TV_SUPPORT || MEDIA_RADIO_SUPPORT) && I2C - default y - select MEDIA_TUNER_XC2028 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_XC5000 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_XC4000 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MT20XX if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_TDA8290 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_TEA5761 if !MEDIA_TUNER_CUSTOMISE && MEDIA_RADIO_SUPPORT && EXPERIMENTAL - select MEDIA_TUNER_TEA5767 if !MEDIA_TUNER_CUSTOMISE && MEDIA_RADIO_SUPPORT - select MEDIA_TUNER_SIMPLE if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_TDA9887 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MC44S803 if !MEDIA_TUNER_CUSTOMISE - -config MEDIA_TUNER_CUSTOMISE - bool "Customize analog and hybrid tuner modules to build" - depends on MEDIA_TUNER - default y if EXPERT - help - This allows the user to deselect tuner drivers unnecessary - for their hardware from the build. Use this option with care - as deselecting tuner drivers which are in fact necessary will - result in V4L/DVB devices which cannot be tuned due to lack of - driver support - - If unsure say N. - -menu "Customize TV tuners" - visible if MEDIA_TUNER_CUSTOMISE - depends on MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT || MEDIA_RADIO_SUPPORT - -config MEDIA_TUNER_SIMPLE - tristate "Simple tuner support" - depends on MEDIA_SUPPORT && I2C - select MEDIA_TUNER_TDA9887 - default m if MEDIA_TUNER_CUSTOMISE - help - Say Y here to include support for various simple tuners. - -config MEDIA_TUNER_TDA8290 - tristate "TDA 8290/8295 + 8275(a)/18271 tuner combo" - depends on MEDIA_SUPPORT && I2C - select MEDIA_TUNER_TDA827X - select MEDIA_TUNER_TDA18271 - default m if MEDIA_TUNER_CUSTOMISE - help - Say Y here to include support for Philips TDA8290+8275(a) tuner. - -config MEDIA_TUNER_TDA827X - tristate "Philips TDA827X silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A DVB-T silicon tuner module. Say Y when you want to support this tuner. - -config MEDIA_TUNER_TDA18271 - tristate "NXP TDA18271 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A silicon tuner module. Say Y when you want to support this tuner. - -config MEDIA_TUNER_TDA9887 - tristate "TDA 9885/6/7 analog IF demodulator" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - Say Y here to include support for Philips TDA9885/6/7 - analog IF demodulator. - -config MEDIA_TUNER_TEA5761 - tristate "TEA 5761 radio tuner (EXPERIMENTAL)" - depends on MEDIA_SUPPORT && I2C - depends on EXPERIMENTAL - default m if MEDIA_TUNER_CUSTOMISE - help - Say Y here to include support for the Philips TEA5761 radio tuner. - -config MEDIA_TUNER_TEA5767 - tristate "TEA 5767 radio tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - Say Y here to include support for the Philips TEA5767 radio tuner. - -config MEDIA_TUNER_MT20XX - tristate "Microtune 2032 / 2050 tuners" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - Say Y here to include support for the MT2032 / MT2050 tuner. - -config MEDIA_TUNER_MT2060 - tristate "Microtune MT2060 silicon IF tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon IF tuner MT2060 from Microtune. - -config MEDIA_TUNER_MT2063 - tristate "Microtune MT2063 silicon IF tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon IF tuner MT2063 from Microtune. - -config MEDIA_TUNER_MT2266 - tristate "Microtune MT2266 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon baseband tuner MT2266 from Microtune. - -config MEDIA_TUNER_MT2131 - tristate "Microtune MT2131 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon baseband tuner MT2131 from Microtune. - -config MEDIA_TUNER_QT1010 - tristate "Quantek QT1010 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon tuner QT1010 from Quantek. - -config MEDIA_TUNER_XC2028 - tristate "XCeive xc2028/xc3028 tuners" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - Say Y here to include support for the xc2028/xc3028 tuners. - -config MEDIA_TUNER_XC5000 - tristate "Xceive XC5000 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon tuner XC5000 from Xceive. - This device is only used inside a SiP called together with a - demodulator for now. - -config MEDIA_TUNER_XC4000 - tristate "Xceive XC4000 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon tuner XC4000 from Xceive. - This device is only used inside a SiP called together with a - demodulator for now. - -config MEDIA_TUNER_MXL5005S - tristate "MaxLinear MSL5005S silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon tuner MXL5005S from MaxLinear. - -config MEDIA_TUNER_MXL5007T - tristate "MaxLinear MxL5007T silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon tuner MxL5007T from MaxLinear. - -config MEDIA_TUNER_MC44S803 - tristate "Freescale MC44S803 Low Power CMOS Broadband tuners" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the Freescale MC44S803 based tuners - -config MEDIA_TUNER_MAX2165 - tristate "Maxim MAX2165 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - A driver for the silicon tuner MAX2165 from Maxim. - -config MEDIA_TUNER_TDA18218 - tristate "NXP TDA18218 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - NXP TDA18218 silicon tuner driver. - -config MEDIA_TUNER_FC0011 - tristate "Fitipower FC0011 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - Fitipower FC0011 silicon tuner driver. - -config MEDIA_TUNER_FC0012 - tristate "Fitipower FC0012 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - Fitipower FC0012 silicon tuner driver. - -config MEDIA_TUNER_FC0013 - tristate "Fitipower FC0013 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - Fitipower FC0013 silicon tuner driver. - -config MEDIA_TUNER_TDA18212 - tristate "NXP TDA18212 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - NXP TDA18212 silicon tuner driver. - -config MEDIA_TUNER_TUA9001 - tristate "Infineon TUA 9001 silicon tuner" - depends on MEDIA_SUPPORT && I2C - default m if MEDIA_TUNER_CUSTOMISE - help - Infineon TUA 9001 silicon tuner driver. -endmenu diff --git a/drivers/media/common/tuners/Makefile b/drivers/media/common/tuners/Makefile deleted file mode 100644 index 891b80e60808..000000000000 --- a/drivers/media/common/tuners/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -# -# Makefile for common V4L/DVB tuners -# - -tda18271-objs := tda18271-maps.o tda18271-common.o tda18271-fe.o - -obj-$(CONFIG_MEDIA_TUNER_XC2028) += tuner-xc2028.o -obj-$(CONFIG_MEDIA_TUNER_SIMPLE) += tuner-simple.o -# tuner-types will be merged into tuner-simple, in the future -obj-$(CONFIG_MEDIA_TUNER_SIMPLE) += tuner-types.o -obj-$(CONFIG_MEDIA_TUNER_MT20XX) += mt20xx.o -obj-$(CONFIG_MEDIA_TUNER_TDA8290) += tda8290.o -obj-$(CONFIG_MEDIA_TUNER_TEA5767) += tea5767.o -obj-$(CONFIG_MEDIA_TUNER_TEA5761) += tea5761.o -obj-$(CONFIG_MEDIA_TUNER_TDA9887) += tda9887.o -obj-$(CONFIG_MEDIA_TUNER_TDA827X) += tda827x.o -obj-$(CONFIG_MEDIA_TUNER_TDA18271) += tda18271.o -obj-$(CONFIG_MEDIA_TUNER_XC5000) += xc5000.o -obj-$(CONFIG_MEDIA_TUNER_XC4000) += xc4000.o -obj-$(CONFIG_MEDIA_TUNER_MT2060) += mt2060.o -obj-$(CONFIG_MEDIA_TUNER_MT2063) += mt2063.o -obj-$(CONFIG_MEDIA_TUNER_MT2266) += mt2266.o -obj-$(CONFIG_MEDIA_TUNER_QT1010) += qt1010.o -obj-$(CONFIG_MEDIA_TUNER_MT2131) += mt2131.o -obj-$(CONFIG_MEDIA_TUNER_MXL5005S) += mxl5005s.o -obj-$(CONFIG_MEDIA_TUNER_MXL5007T) += mxl5007t.o -obj-$(CONFIG_MEDIA_TUNER_MC44S803) += mc44s803.o -obj-$(CONFIG_MEDIA_TUNER_MAX2165) += max2165.o -obj-$(CONFIG_MEDIA_TUNER_TDA18218) += tda18218.o -obj-$(CONFIG_MEDIA_TUNER_TDA18212) += tda18212.o -obj-$(CONFIG_MEDIA_TUNER_TUA9001) += tua9001.o -obj-$(CONFIG_MEDIA_TUNER_FC0011) += fc0011.o -obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o -obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o - -ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core -ccflags-y += -I$(srctree)/drivers/media/dvb/frontends diff --git a/drivers/media/common/tuners/mc44s803.c b/drivers/media/common/tuners/mc44s803.c deleted file mode 100644 index 5ddce7e326f7..000000000000 --- a/drivers/media/common/tuners/mc44s803.c +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Driver for Freescale MC44S803 Low Power CMOS Broadband Tuner - * - * Copyright (c) 2009 Jochen Friedrich - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA.= - */ - -#include -#include -#include -#include -#include - -#include "dvb_frontend.h" - -#include "mc44s803.h" -#include "mc44s803_priv.h" - -#define mc_printk(level, format, arg...) \ - printk(level "mc44s803: " format , ## arg) - -/* Writes a single register */ -static int mc44s803_writereg(struct mc44s803_priv *priv, u32 val) -{ - u8 buf[3]; - struct i2c_msg msg = { - .addr = priv->cfg->i2c_address, .flags = 0, .buf = buf, .len = 3 - }; - - buf[0] = (val & 0xff0000) >> 16; - buf[1] = (val & 0xff00) >> 8; - buf[2] = (val & 0xff); - - if (i2c_transfer(priv->i2c, &msg, 1) != 1) { - mc_printk(KERN_WARNING, "I2C write failed\n"); - return -EREMOTEIO; - } - return 0; -} - -/* Reads a single register */ -static int mc44s803_readreg(struct mc44s803_priv *priv, u8 reg, u32 *val) -{ - u32 wval; - u8 buf[3]; - int ret; - struct i2c_msg msg[] = { - { .addr = priv->cfg->i2c_address, .flags = I2C_M_RD, - .buf = buf, .len = 3 }, - }; - - wval = MC44S803_REG_SM(MC44S803_REG_DATAREG, MC44S803_ADDR) | - MC44S803_REG_SM(reg, MC44S803_D); - - ret = mc44s803_writereg(priv, wval); - if (ret) - return ret; - - if (i2c_transfer(priv->i2c, msg, 1) != 1) { - mc_printk(KERN_WARNING, "I2C read failed\n"); - return -EREMOTEIO; - } - - *val = (buf[0] << 16) | (buf[1] << 8) | buf[2]; - - return 0; -} - -static int mc44s803_release(struct dvb_frontend *fe) -{ - struct mc44s803_priv *priv = fe->tuner_priv; - - fe->tuner_priv = NULL; - kfree(priv); - - return 0; -} - -static int mc44s803_init(struct dvb_frontend *fe) -{ - struct mc44s803_priv *priv = fe->tuner_priv; - u32 val; - int err; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - -/* Reset chip */ - val = MC44S803_REG_SM(MC44S803_REG_RESET, MC44S803_ADDR) | - MC44S803_REG_SM(1, MC44S803_RS); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - val = MC44S803_REG_SM(MC44S803_REG_RESET, MC44S803_ADDR); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - -/* Power Up and Start Osc */ - - val = MC44S803_REG_SM(MC44S803_REG_REFOSC, MC44S803_ADDR) | - MC44S803_REG_SM(0xC0, MC44S803_REFOSC) | - MC44S803_REG_SM(1, MC44S803_OSCSEL); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - val = MC44S803_REG_SM(MC44S803_REG_POWER, MC44S803_ADDR) | - MC44S803_REG_SM(0x200, MC44S803_POWER); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - msleep(10); - - val = MC44S803_REG_SM(MC44S803_REG_REFOSC, MC44S803_ADDR) | - MC44S803_REG_SM(0x40, MC44S803_REFOSC) | - MC44S803_REG_SM(1, MC44S803_OSCSEL); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - msleep(20); - -/* Setup Mixer */ - - val = MC44S803_REG_SM(MC44S803_REG_MIXER, MC44S803_ADDR) | - MC44S803_REG_SM(1, MC44S803_TRI_STATE) | - MC44S803_REG_SM(0x7F, MC44S803_MIXER_RES); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - -/* Setup Cirquit Adjust */ - - val = MC44S803_REG_SM(MC44S803_REG_CIRCADJ, MC44S803_ADDR) | - MC44S803_REG_SM(1, MC44S803_G1) | - MC44S803_REG_SM(1, MC44S803_G3) | - MC44S803_REG_SM(0x3, MC44S803_CIRCADJ_RES) | - MC44S803_REG_SM(1, MC44S803_G6) | - MC44S803_REG_SM(priv->cfg->dig_out, MC44S803_S1) | - MC44S803_REG_SM(0x3, MC44S803_LP) | - MC44S803_REG_SM(1, MC44S803_CLRF) | - MC44S803_REG_SM(1, MC44S803_CLIF); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - val = MC44S803_REG_SM(MC44S803_REG_CIRCADJ, MC44S803_ADDR) | - MC44S803_REG_SM(1, MC44S803_G1) | - MC44S803_REG_SM(1, MC44S803_G3) | - MC44S803_REG_SM(0x3, MC44S803_CIRCADJ_RES) | - MC44S803_REG_SM(1, MC44S803_G6) | - MC44S803_REG_SM(priv->cfg->dig_out, MC44S803_S1) | - MC44S803_REG_SM(0x3, MC44S803_LP); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - -/* Setup Digtune */ - - val = MC44S803_REG_SM(MC44S803_REG_DIGTUNE, MC44S803_ADDR) | - MC44S803_REG_SM(3, MC44S803_XOD); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - -/* Setup AGC */ - - val = MC44S803_REG_SM(MC44S803_REG_LNAAGC, MC44S803_ADDR) | - MC44S803_REG_SM(1, MC44S803_AT1) | - MC44S803_REG_SM(1, MC44S803_AT2) | - MC44S803_REG_SM(1, MC44S803_AGC_AN_DIG) | - MC44S803_REG_SM(1, MC44S803_AGC_READ_EN) | - MC44S803_REG_SM(1, MC44S803_LNA0); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - return 0; - -exit: - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - - mc_printk(KERN_WARNING, "I/O Error\n"); - return err; -} - -static int mc44s803_set_params(struct dvb_frontend *fe) -{ - struct mc44s803_priv *priv = fe->tuner_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u32 r1, r2, n1, n2, lo1, lo2, freq, val; - int err; - - priv->frequency = c->frequency; - - r1 = MC44S803_OSC / 1000000; - r2 = MC44S803_OSC / 100000; - - n1 = (c->frequency + MC44S803_IF1 + 500000) / 1000000; - freq = MC44S803_OSC / r1 * n1; - lo1 = ((60 * n1) + (r1 / 2)) / r1; - freq = freq - c->frequency; - - n2 = (freq - MC44S803_IF2 + 50000) / 100000; - lo2 = ((60 * n2) + (r2 / 2)) / r2; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - - val = MC44S803_REG_SM(MC44S803_REG_REFDIV, MC44S803_ADDR) | - MC44S803_REG_SM(r1-1, MC44S803_R1) | - MC44S803_REG_SM(r2-1, MC44S803_R2) | - MC44S803_REG_SM(1, MC44S803_REFBUF_EN); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - val = MC44S803_REG_SM(MC44S803_REG_LO1, MC44S803_ADDR) | - MC44S803_REG_SM(n1-2, MC44S803_LO1); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - val = MC44S803_REG_SM(MC44S803_REG_LO2, MC44S803_ADDR) | - MC44S803_REG_SM(n2-2, MC44S803_LO2); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - val = MC44S803_REG_SM(MC44S803_REG_DIGTUNE, MC44S803_ADDR) | - MC44S803_REG_SM(1, MC44S803_DA) | - MC44S803_REG_SM(lo1, MC44S803_LO_REF) | - MC44S803_REG_SM(1, MC44S803_AT); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - val = MC44S803_REG_SM(MC44S803_REG_DIGTUNE, MC44S803_ADDR) | - MC44S803_REG_SM(2, MC44S803_DA) | - MC44S803_REG_SM(lo2, MC44S803_LO_REF) | - MC44S803_REG_SM(1, MC44S803_AT); - - err = mc44s803_writereg(priv, val); - if (err) - goto exit; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - - return 0; - -exit: - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - - mc_printk(KERN_WARNING, "I/O Error\n"); - return err; -} - -static int mc44s803_get_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct mc44s803_priv *priv = fe->tuner_priv; - *frequency = priv->frequency; - return 0; -} - -static const struct dvb_tuner_ops mc44s803_tuner_ops = { - .info = { - .name = "Freescale MC44S803", - .frequency_min = 48000000, - .frequency_max = 1000000000, - .frequency_step = 100000, - }, - - .release = mc44s803_release, - .init = mc44s803_init, - .set_params = mc44s803_set_params, - .get_frequency = mc44s803_get_frequency -}; - -/* This functions tries to identify a MC44S803 tuner by reading the ID - register. This is hasty. */ -struct dvb_frontend *mc44s803_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, struct mc44s803_config *cfg) -{ - struct mc44s803_priv *priv; - u32 reg; - u8 id; - int ret; - - reg = 0; - - priv = kzalloc(sizeof(struct mc44s803_priv), GFP_KERNEL); - if (priv == NULL) - return NULL; - - priv->cfg = cfg; - priv->i2c = i2c; - priv->fe = fe; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ - - ret = mc44s803_readreg(priv, MC44S803_REG_ID, ®); - if (ret) - goto error; - - id = MC44S803_REG_MS(reg, MC44S803_ID); - - if (id != 0x14) { - mc_printk(KERN_ERR, "unsupported ID " - "(%x should be 0x14)\n", id); - goto error; - } - - mc_printk(KERN_INFO, "successfully identified (ID = %x)\n", id); - memcpy(&fe->ops.tuner_ops, &mc44s803_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - fe->tuner_priv = priv; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ - - return fe; - -error: - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ - - kfree(priv); - return NULL; -} -EXPORT_SYMBOL(mc44s803_attach); - -MODULE_AUTHOR("Jochen Friedrich"); -MODULE_DESCRIPTION("Freescale MC44S803 silicon tuner driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/tuners/mxl5005s.c b/drivers/media/common/tuners/mxl5005s.c deleted file mode 100644 index 6133315fb0e3..000000000000 --- a/drivers/media/common/tuners/mxl5005s.c +++ /dev/null @@ -1,4109 +0,0 @@ -/* - MaxLinear MXL5005S VSB/QAM/DVBT tuner driver - - Copyright (C) 2008 MaxLinear - Copyright (C) 2006 Steven Toth - Functions: - mxl5005s_reset() - mxl5005s_writereg() - mxl5005s_writeregs() - mxl5005s_init() - mxl5005s_reconfigure() - mxl5005s_AssignTunerMode() - mxl5005s_set_params() - mxl5005s_get_frequency() - mxl5005s_get_bandwidth() - mxl5005s_release() - mxl5005s_attach() - - Copyright (C) 2008 Realtek - Copyright (C) 2008 Jan Hoogenraad - Functions: - mxl5005s_SetRfFreqHz() - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -/* - History of this driver (Steven Toth): - I was given a public release of a linux driver that included - support for the MaxLinear MXL5005S silicon tuner. Analysis of - the tuner driver showed clearly three things. - - 1. The tuner driver didn't support the LinuxTV tuner API - so the code Realtek added had to be removed. - - 2. A significant amount of the driver is reference driver code - from MaxLinear, I felt it was important to identify and - preserve this. - - 3. New code has to be added to interface correctly with the - LinuxTV API, as a regular kernel module. - - Other than the reference driver enum's, I've clearly marked - sections of the code and retained the copyright of the - respective owners. -*/ -#include -#include -#include -#include -#include -#include -#include "dvb_frontend.h" -#include "mxl5005s.h" - -static int debug; - -#define dprintk(level, arg...) do { \ - if (level <= debug) \ - printk(arg); \ - } while (0) - -#define TUNER_REGS_NUM 104 -#define INITCTRL_NUM 40 - -#ifdef _MXL_PRODUCTION -#define CHCTRL_NUM 39 -#else -#define CHCTRL_NUM 36 -#endif - -#define MXLCTRL_NUM 189 -#define MASTER_CONTROL_ADDR 9 - -/* Enumeration of Master Control Register State */ -enum master_control_state { - MC_LOAD_START = 1, - MC_POWER_DOWN, - MC_SYNTH_RESET, - MC_SEQ_OFF -}; - -/* Enumeration of MXL5005 Tuner Modulation Type */ -enum { - MXL_DEFAULT_MODULATION = 0, - MXL_DVBT, - MXL_ATSC, - MXL_QAM, - MXL_ANALOG_CABLE, - MXL_ANALOG_OTA -}; - -/* MXL5005 Tuner Register Struct */ -struct TunerReg { - u16 Reg_Num; /* Tuner Register Address */ - u16 Reg_Val; /* Current sw programmed value waiting to be written */ -}; - -enum { - /* Initialization Control Names */ - DN_IQTN_AMP_CUT = 1, /* 1 */ - BB_MODE, /* 2 */ - BB_BUF, /* 3 */ - BB_BUF_OA, /* 4 */ - BB_ALPF_BANDSELECT, /* 5 */ - BB_IQSWAP, /* 6 */ - BB_DLPF_BANDSEL, /* 7 */ - RFSYN_CHP_GAIN, /* 8 */ - RFSYN_EN_CHP_HIGAIN, /* 9 */ - AGC_IF, /* 10 */ - AGC_RF, /* 11 */ - IF_DIVVAL, /* 12 */ - IF_VCO_BIAS, /* 13 */ - CHCAL_INT_MOD_IF, /* 14 */ - CHCAL_FRAC_MOD_IF, /* 15 */ - DRV_RES_SEL, /* 16 */ - I_DRIVER, /* 17 */ - EN_AAF, /* 18 */ - EN_3P, /* 19 */ - EN_AUX_3P, /* 20 */ - SEL_AAF_BAND, /* 21 */ - SEQ_ENCLK16_CLK_OUT, /* 22 */ - SEQ_SEL4_16B, /* 23 */ - XTAL_CAPSELECT, /* 24 */ - IF_SEL_DBL, /* 25 */ - RFSYN_R_DIV, /* 26 */ - SEQ_EXTSYNTHCALIF, /* 27 */ - SEQ_EXTDCCAL, /* 28 */ - AGC_EN_RSSI, /* 29 */ - RFA_ENCLKRFAGC, /* 30 */ - RFA_RSSI_REFH, /* 31 */ - RFA_RSSI_REF, /* 32 */ - RFA_RSSI_REFL, /* 33 */ - RFA_FLR, /* 34 */ - RFA_CEIL, /* 35 */ - SEQ_EXTIQFSMPULSE, /* 36 */ - OVERRIDE_1, /* 37 */ - BB_INITSTATE_DLPF_TUNE, /* 38 */ - TG_R_DIV, /* 39 */ - EN_CHP_LIN_B, /* 40 */ - - /* Channel Change Control Names */ - DN_POLY = 51, /* 51 */ - DN_RFGAIN, /* 52 */ - DN_CAP_RFLPF, /* 53 */ - DN_EN_VHFUHFBAR, /* 54 */ - DN_GAIN_ADJUST, /* 55 */ - DN_IQTNBUF_AMP, /* 56 */ - DN_IQTNGNBFBIAS_BST, /* 57 */ - RFSYN_EN_OUTMUX, /* 58 */ - RFSYN_SEL_VCO_OUT, /* 59 */ - RFSYN_SEL_VCO_HI, /* 60 */ - RFSYN_SEL_DIVM, /* 61 */ - RFSYN_RF_DIV_BIAS, /* 62 */ - DN_SEL_FREQ, /* 63 */ - RFSYN_VCO_BIAS, /* 64 */ - CHCAL_INT_MOD_RF, /* 65 */ - CHCAL_FRAC_MOD_RF, /* 66 */ - RFSYN_LPF_R, /* 67 */ - CHCAL_EN_INT_RF, /* 68 */ - TG_LO_DIVVAL, /* 69 */ - TG_LO_SELVAL, /* 70 */ - TG_DIV_VAL, /* 71 */ - TG_VCO_BIAS, /* 72 */ - SEQ_EXTPOWERUP, /* 73 */ - OVERRIDE_2, /* 74 */ - OVERRIDE_3, /* 75 */ - OVERRIDE_4, /* 76 */ - SEQ_FSM_PULSE, /* 77 */ - GPIO_4B, /* 78 */ - GPIO_3B, /* 79 */ - GPIO_4, /* 80 */ - GPIO_3, /* 81 */ - GPIO_1B, /* 82 */ - DAC_A_ENABLE, /* 83 */ - DAC_B_ENABLE, /* 84 */ - DAC_DIN_A, /* 85 */ - DAC_DIN_B, /* 86 */ -#ifdef _MXL_PRODUCTION - RFSYN_EN_DIV, /* 87 */ - RFSYN_DIVM, /* 88 */ - DN_BYPASS_AGC_I2C /* 89 */ -#endif -}; - -/* - * The following context is source code provided by MaxLinear. - * MaxLinear source code - Common_MXL.h (?) - */ - -/* Constants */ -#define MXL5005S_REG_WRITING_TABLE_LEN_MAX 104 -#define MXL5005S_LATCH_BYTE 0xfe - -/* Register address, MSB, and LSB */ -#define MXL5005S_BB_IQSWAP_ADDR 59 -#define MXL5005S_BB_IQSWAP_MSB 0 -#define MXL5005S_BB_IQSWAP_LSB 0 - -#define MXL5005S_BB_DLPF_BANDSEL_ADDR 53 -#define MXL5005S_BB_DLPF_BANDSEL_MSB 4 -#define MXL5005S_BB_DLPF_BANDSEL_LSB 3 - -/* Standard modes */ -enum { - MXL5005S_STANDARD_DVBT, - MXL5005S_STANDARD_ATSC, -}; -#define MXL5005S_STANDARD_MODE_NUM 2 - -/* Bandwidth modes */ -enum { - MXL5005S_BANDWIDTH_6MHZ = 6000000, - MXL5005S_BANDWIDTH_7MHZ = 7000000, - MXL5005S_BANDWIDTH_8MHZ = 8000000, -}; -#define MXL5005S_BANDWIDTH_MODE_NUM 3 - -/* MXL5005 Tuner Control Struct */ -struct TunerControl { - u16 Ctrl_Num; /* Control Number */ - u16 size; /* Number of bits to represent Value */ - u16 addr[25]; /* Array of Tuner Register Address for each bit pos */ - u16 bit[25]; /* Array of bit pos in Reg Addr for each bit pos */ - u16 val[25]; /* Binary representation of Value */ -}; - -/* MXL5005 Tuner Struct */ -struct mxl5005s_state { - u8 Mode; /* 0: Analog Mode ; 1: Digital Mode */ - u8 IF_Mode; /* for Analog Mode, 0: zero IF; 1: low IF */ - u32 Chan_Bandwidth; /* filter channel bandwidth (6, 7, 8) */ - u32 IF_OUT; /* Desired IF Out Frequency */ - u16 IF_OUT_LOAD; /* IF Out Load Resistor (200/300 Ohms) */ - u32 RF_IN; /* RF Input Frequency */ - u32 Fxtal; /* XTAL Frequency */ - u8 AGC_Mode; /* AGC Mode 0: Dual AGC; 1: Single AGC */ - u16 TOP; /* Value: take over point */ - u8 CLOCK_OUT; /* 0: turn off clk out; 1: turn on clock out */ - u8 DIV_OUT; /* 4MHz or 16MHz */ - u8 CAPSELECT; /* 0: disable On-Chip pulling cap; 1: enable */ - u8 EN_RSSI; /* 0: disable RSSI; 1: enable RSSI */ - - /* Modulation Type; */ - /* 0 - Default; 1 - DVB-T; 2 - ATSC; 3 - QAM; 4 - Analog Cable */ - u8 Mod_Type; - - /* Tracking Filter Type */ - /* 0 - Default; 1 - Off; 2 - Type C; 3 - Type C-H */ - u8 TF_Type; - - /* Calculated Settings */ - u32 RF_LO; /* Synth RF LO Frequency */ - u32 IF_LO; /* Synth IF LO Frequency */ - u32 TG_LO; /* Synth TG_LO Frequency */ - - /* Pointers to ControlName Arrays */ - u16 Init_Ctrl_Num; /* Number of INIT Control Names */ - struct TunerControl - Init_Ctrl[INITCTRL_NUM]; /* INIT Control Names Array Pointer */ - - u16 CH_Ctrl_Num; /* Number of CH Control Names */ - struct TunerControl - CH_Ctrl[CHCTRL_NUM]; /* CH Control Name Array Pointer */ - - u16 MXL_Ctrl_Num; /* Number of MXL Control Names */ - struct TunerControl - MXL_Ctrl[MXLCTRL_NUM]; /* MXL Control Name Array Pointer */ - - /* Pointer to Tuner Register Array */ - u16 TunerRegs_Num; /* Number of Tuner Registers */ - struct TunerReg - TunerRegs[TUNER_REGS_NUM]; /* Tuner Register Array Pointer */ - - /* Linux driver framework specific */ - struct mxl5005s_config *config; - struct dvb_frontend *frontend; - struct i2c_adapter *i2c; - - /* Cache values */ - u32 current_mode; - -}; - -static u16 MXL_GetMasterControl(u8 *MasterReg, int state); -static u16 MXL_ControlWrite(struct dvb_frontend *fe, u16 ControlNum, u32 value); -static u16 MXL_ControlRead(struct dvb_frontend *fe, u16 controlNum, u32 *value); -static void MXL_RegWriteBit(struct dvb_frontend *fe, u8 address, u8 bit, - u8 bitVal); -static u16 MXL_GetCHRegister(struct dvb_frontend *fe, u8 *RegNum, - u8 *RegVal, int *count); -static u32 MXL_Ceiling(u32 value, u32 resolution); -static u16 MXL_RegRead(struct dvb_frontend *fe, u8 RegNum, u8 *RegVal); -static u16 MXL_ControlWrite_Group(struct dvb_frontend *fe, u16 controlNum, - u32 value, u16 controlGroup); -static u16 MXL_SetGPIO(struct dvb_frontend *fe, u8 GPIO_Num, u8 GPIO_Val); -static u16 MXL_GetInitRegister(struct dvb_frontend *fe, u8 *RegNum, - u8 *RegVal, int *count); -static u16 MXL_TuneRF(struct dvb_frontend *fe, u32 RF_Freq); -static void MXL_SynthIFLO_Calc(struct dvb_frontend *fe); -static void MXL_SynthRFTGLO_Calc(struct dvb_frontend *fe); -static u16 MXL_GetCHRegister_ZeroIF(struct dvb_frontend *fe, u8 *RegNum, - u8 *RegVal, int *count); -static int mxl5005s_writeregs(struct dvb_frontend *fe, u8 *addrtable, - u8 *datatable, u8 len); -static u16 MXL_IFSynthInit(struct dvb_frontend *fe); -static int mxl5005s_AssignTunerMode(struct dvb_frontend *fe, u32 mod_type, - u32 bandwidth); -static int mxl5005s_reconfigure(struct dvb_frontend *fe, u32 mod_type, - u32 bandwidth); - -/* ---------------------------------------------------------------- - * Begin: Custom code salvaged from the Realtek driver. - * Copyright (C) 2008 Realtek - * Copyright (C) 2008 Jan Hoogenraad - * This code is placed under the terms of the GNU General Public License - * - * Released by Realtek under GPLv2. - * Thanks to Realtek for a lot of support we received ! - * - * Revision: 080314 - original version - */ - -static int mxl5005s_SetRfFreqHz(struct dvb_frontend *fe, unsigned long RfFreqHz) -{ - struct mxl5005s_state *state = fe->tuner_priv; - unsigned char AddrTable[MXL5005S_REG_WRITING_TABLE_LEN_MAX]; - unsigned char ByteTable[MXL5005S_REG_WRITING_TABLE_LEN_MAX]; - int TableLen; - - u32 IfDivval = 0; - unsigned char MasterControlByte; - - dprintk(1, "%s() freq=%ld\n", __func__, RfFreqHz); - - /* Set MxL5005S tuner RF frequency according to example code. */ - - /* Tuner RF frequency setting stage 0 */ - MXL_GetMasterControl(ByteTable, MC_SYNTH_RESET); - AddrTable[0] = MASTER_CONTROL_ADDR; - ByteTable[0] |= state->config->AgcMasterByte; - - mxl5005s_writeregs(fe, AddrTable, ByteTable, 1); - - /* Tuner RF frequency setting stage 1 */ - MXL_TuneRF(fe, RfFreqHz); - - MXL_ControlRead(fe, IF_DIVVAL, &IfDivval); - - MXL_ControlWrite(fe, SEQ_FSM_PULSE, 0); - MXL_ControlWrite(fe, SEQ_EXTPOWERUP, 1); - MXL_ControlWrite(fe, IF_DIVVAL, 8); - MXL_GetCHRegister(fe, AddrTable, ByteTable, &TableLen); - - MXL_GetMasterControl(&MasterControlByte, MC_LOAD_START); - AddrTable[TableLen] = MASTER_CONTROL_ADDR ; - ByteTable[TableLen] = MasterControlByte | - state->config->AgcMasterByte; - TableLen += 1; - - mxl5005s_writeregs(fe, AddrTable, ByteTable, TableLen); - - /* Wait 30 ms. */ - msleep(150); - - /* Tuner RF frequency setting stage 2 */ - MXL_ControlWrite(fe, SEQ_FSM_PULSE, 1); - MXL_ControlWrite(fe, IF_DIVVAL, IfDivval); - MXL_GetCHRegister_ZeroIF(fe, AddrTable, ByteTable, &TableLen); - - MXL_GetMasterControl(&MasterControlByte, MC_LOAD_START); - AddrTable[TableLen] = MASTER_CONTROL_ADDR ; - ByteTable[TableLen] = MasterControlByte | - state->config->AgcMasterByte ; - TableLen += 1; - - mxl5005s_writeregs(fe, AddrTable, ByteTable, TableLen); - - msleep(100); - - return 0; -} -/* End: Custom code taken from the Realtek driver */ - -/* ---------------------------------------------------------------- - * Begin: Reference driver code found in the Realtek driver. - * Copyright (C) 2008 MaxLinear - */ -static u16 MXL5005_RegisterInit(struct dvb_frontend *fe) -{ - struct mxl5005s_state *state = fe->tuner_priv; - state->TunerRegs_Num = TUNER_REGS_NUM ; - - state->TunerRegs[0].Reg_Num = 9 ; - state->TunerRegs[0].Reg_Val = 0x40 ; - - state->TunerRegs[1].Reg_Num = 11 ; - state->TunerRegs[1].Reg_Val = 0x19 ; - - state->TunerRegs[2].Reg_Num = 12 ; - state->TunerRegs[2].Reg_Val = 0x60 ; - - state->TunerRegs[3].Reg_Num = 13 ; - state->TunerRegs[3].Reg_Val = 0x00 ; - - state->TunerRegs[4].Reg_Num = 14 ; - state->TunerRegs[4].Reg_Val = 0x00 ; - - state->TunerRegs[5].Reg_Num = 15 ; - state->TunerRegs[5].Reg_Val = 0xC0 ; - - state->TunerRegs[6].Reg_Num = 16 ; - state->TunerRegs[6].Reg_Val = 0x00 ; - - state->TunerRegs[7].Reg_Num = 17 ; - state->TunerRegs[7].Reg_Val = 0x00 ; - - state->TunerRegs[8].Reg_Num = 18 ; - state->TunerRegs[8].Reg_Val = 0x00 ; - - state->TunerRegs[9].Reg_Num = 19 ; - state->TunerRegs[9].Reg_Val = 0x34 ; - - state->TunerRegs[10].Reg_Num = 21 ; - state->TunerRegs[10].Reg_Val = 0x00 ; - - state->TunerRegs[11].Reg_Num = 22 ; - state->TunerRegs[11].Reg_Val = 0x6B ; - - state->TunerRegs[12].Reg_Num = 23 ; - state->TunerRegs[12].Reg_Val = 0x35 ; - - state->TunerRegs[13].Reg_Num = 24 ; - state->TunerRegs[13].Reg_Val = 0x70 ; - - state->TunerRegs[14].Reg_Num = 25 ; - state->TunerRegs[14].Reg_Val = 0x3E ; - - state->TunerRegs[15].Reg_Num = 26 ; - state->TunerRegs[15].Reg_Val = 0x82 ; - - state->TunerRegs[16].Reg_Num = 31 ; - state->TunerRegs[16].Reg_Val = 0x00 ; - - state->TunerRegs[17].Reg_Num = 32 ; - state->TunerRegs[17].Reg_Val = 0x40 ; - - state->TunerRegs[18].Reg_Num = 33 ; - state->TunerRegs[18].Reg_Val = 0x53 ; - - state->TunerRegs[19].Reg_Num = 34 ; - state->TunerRegs[19].Reg_Val = 0x81 ; - - state->TunerRegs[20].Reg_Num = 35 ; - state->TunerRegs[20].Reg_Val = 0xC9 ; - - state->TunerRegs[21].Reg_Num = 36 ; - state->TunerRegs[21].Reg_Val = 0x01 ; - - state->TunerRegs[22].Reg_Num = 37 ; - state->TunerRegs[22].Reg_Val = 0x00 ; - - state->TunerRegs[23].Reg_Num = 41 ; - state->TunerRegs[23].Reg_Val = 0x00 ; - - state->TunerRegs[24].Reg_Num = 42 ; - state->TunerRegs[24].Reg_Val = 0xF8 ; - - state->TunerRegs[25].Reg_Num = 43 ; - state->TunerRegs[25].Reg_Val = 0x43 ; - - state->TunerRegs[26].Reg_Num = 44 ; - state->TunerRegs[26].Reg_Val = 0x20 ; - - state->TunerRegs[27].Reg_Num = 45 ; - state->TunerRegs[27].Reg_Val = 0x80 ; - - state->TunerRegs[28].Reg_Num = 46 ; - state->TunerRegs[28].Reg_Val = 0x88 ; - - state->TunerRegs[29].Reg_Num = 47 ; - state->TunerRegs[29].Reg_Val = 0x86 ; - - state->TunerRegs[30].Reg_Num = 48 ; - state->TunerRegs[30].Reg_Val = 0x00 ; - - state->TunerRegs[31].Reg_Num = 49 ; - state->TunerRegs[31].Reg_Val = 0x00 ; - - state->TunerRegs[32].Reg_Num = 53 ; - state->TunerRegs[32].Reg_Val = 0x94 ; - - state->TunerRegs[33].Reg_Num = 54 ; - state->TunerRegs[33].Reg_Val = 0xFA ; - - state->TunerRegs[34].Reg_Num = 55 ; - state->TunerRegs[34].Reg_Val = 0x92 ; - - state->TunerRegs[35].Reg_Num = 56 ; - state->TunerRegs[35].Reg_Val = 0x80 ; - - state->TunerRegs[36].Reg_Num = 57 ; - state->TunerRegs[36].Reg_Val = 0x41 ; - - state->TunerRegs[37].Reg_Num = 58 ; - state->TunerRegs[37].Reg_Val = 0xDB ; - - state->TunerRegs[38].Reg_Num = 59 ; - state->TunerRegs[38].Reg_Val = 0x00 ; - - state->TunerRegs[39].Reg_Num = 60 ; - state->TunerRegs[39].Reg_Val = 0x00 ; - - state->TunerRegs[40].Reg_Num = 61 ; - state->TunerRegs[40].Reg_Val = 0x00 ; - - state->TunerRegs[41].Reg_Num = 62 ; - state->TunerRegs[41].Reg_Val = 0x00 ; - - state->TunerRegs[42].Reg_Num = 65 ; - state->TunerRegs[42].Reg_Val = 0xF8 ; - - state->TunerRegs[43].Reg_Num = 66 ; - state->TunerRegs[43].Reg_Val = 0xE4 ; - - state->TunerRegs[44].Reg_Num = 67 ; - state->TunerRegs[44].Reg_Val = 0x90 ; - - state->TunerRegs[45].Reg_Num = 68 ; - state->TunerRegs[45].Reg_Val = 0xC0 ; - - state->TunerRegs[46].Reg_Num = 69 ; - state->TunerRegs[46].Reg_Val = 0x01 ; - - state->TunerRegs[47].Reg_Num = 70 ; - state->TunerRegs[47].Reg_Val = 0x50 ; - - state->TunerRegs[48].Reg_Num = 71 ; - state->TunerRegs[48].Reg_Val = 0x06 ; - - state->TunerRegs[49].Reg_Num = 72 ; - state->TunerRegs[49].Reg_Val = 0x00 ; - - state->TunerRegs[50].Reg_Num = 73 ; - state->TunerRegs[50].Reg_Val = 0x20 ; - - state->TunerRegs[51].Reg_Num = 76 ; - state->TunerRegs[51].Reg_Val = 0xBB ; - - state->TunerRegs[52].Reg_Num = 77 ; - state->TunerRegs[52].Reg_Val = 0x13 ; - - state->TunerRegs[53].Reg_Num = 81 ; - state->TunerRegs[53].Reg_Val = 0x04 ; - - state->TunerRegs[54].Reg_Num = 82 ; - state->TunerRegs[54].Reg_Val = 0x75 ; - - state->TunerRegs[55].Reg_Num = 83 ; - state->TunerRegs[55].Reg_Val = 0x00 ; - - state->TunerRegs[56].Reg_Num = 84 ; - state->TunerRegs[56].Reg_Val = 0x00 ; - - state->TunerRegs[57].Reg_Num = 85 ; - state->TunerRegs[57].Reg_Val = 0x00 ; - - state->TunerRegs[58].Reg_Num = 91 ; - state->TunerRegs[58].Reg_Val = 0x70 ; - - state->TunerRegs[59].Reg_Num = 92 ; - state->TunerRegs[59].Reg_Val = 0x00 ; - - state->TunerRegs[60].Reg_Num = 93 ; - state->TunerRegs[60].Reg_Val = 0x00 ; - - state->TunerRegs[61].Reg_Num = 94 ; - state->TunerRegs[61].Reg_Val = 0x00 ; - - state->TunerRegs[62].Reg_Num = 95 ; - state->TunerRegs[62].Reg_Val = 0x0C ; - - state->TunerRegs[63].Reg_Num = 96 ; - state->TunerRegs[63].Reg_Val = 0x00 ; - - state->TunerRegs[64].Reg_Num = 97 ; - state->TunerRegs[64].Reg_Val = 0x00 ; - - state->TunerRegs[65].Reg_Num = 98 ; - state->TunerRegs[65].Reg_Val = 0xE2 ; - - state->TunerRegs[66].Reg_Num = 99 ; - state->TunerRegs[66].Reg_Val = 0x00 ; - - state->TunerRegs[67].Reg_Num = 100 ; - state->TunerRegs[67].Reg_Val = 0x00 ; - - state->TunerRegs[68].Reg_Num = 101 ; - state->TunerRegs[68].Reg_Val = 0x12 ; - - state->TunerRegs[69].Reg_Num = 102 ; - state->TunerRegs[69].Reg_Val = 0x80 ; - - state->TunerRegs[70].Reg_Num = 103 ; - state->TunerRegs[70].Reg_Val = 0x32 ; - - state->TunerRegs[71].Reg_Num = 104 ; - state->TunerRegs[71].Reg_Val = 0xB4 ; - - state->TunerRegs[72].Reg_Num = 105 ; - state->TunerRegs[72].Reg_Val = 0x60 ; - - state->TunerRegs[73].Reg_Num = 106 ; - state->TunerRegs[73].Reg_Val = 0x83 ; - - state->TunerRegs[74].Reg_Num = 107 ; - state->TunerRegs[74].Reg_Val = 0x84 ; - - state->TunerRegs[75].Reg_Num = 108 ; - state->TunerRegs[75].Reg_Val = 0x9C ; - - state->TunerRegs[76].Reg_Num = 109 ; - state->TunerRegs[76].Reg_Val = 0x02 ; - - state->TunerRegs[77].Reg_Num = 110 ; - state->TunerRegs[77].Reg_Val = 0x81 ; - - state->TunerRegs[78].Reg_Num = 111 ; - state->TunerRegs[78].Reg_Val = 0xC0 ; - - state->TunerRegs[79].Reg_Num = 112 ; - state->TunerRegs[79].Reg_Val = 0x10 ; - - state->TunerRegs[80].Reg_Num = 131 ; - state->TunerRegs[80].Reg_Val = 0x8A ; - - state->TunerRegs[81].Reg_Num = 132 ; - state->TunerRegs[81].Reg_Val = 0x10 ; - - state->TunerRegs[82].Reg_Num = 133 ; - state->TunerRegs[82].Reg_Val = 0x24 ; - - state->TunerRegs[83].Reg_Num = 134 ; - state->TunerRegs[83].Reg_Val = 0x00 ; - - state->TunerRegs[84].Reg_Num = 135 ; - state->TunerRegs[84].Reg_Val = 0x00 ; - - state->TunerRegs[85].Reg_Num = 136 ; - state->TunerRegs[85].Reg_Val = 0x7E ; - - state->TunerRegs[86].Reg_Num = 137 ; - state->TunerRegs[86].Reg_Val = 0x40 ; - - state->TunerRegs[87].Reg_Num = 138 ; - state->TunerRegs[87].Reg_Val = 0x38 ; - - state->TunerRegs[88].Reg_Num = 146 ; - state->TunerRegs[88].Reg_Val = 0xF6 ; - - state->TunerRegs[89].Reg_Num = 147 ; - state->TunerRegs[89].Reg_Val = 0x1A ; - - state->TunerRegs[90].Reg_Num = 148 ; - state->TunerRegs[90].Reg_Val = 0x62 ; - - state->TunerRegs[91].Reg_Num = 149 ; - state->TunerRegs[91].Reg_Val = 0x33 ; - - state->TunerRegs[92].Reg_Num = 150 ; - state->TunerRegs[92].Reg_Val = 0x80 ; - - state->TunerRegs[93].Reg_Num = 156 ; - state->TunerRegs[93].Reg_Val = 0x56 ; - - state->TunerRegs[94].Reg_Num = 157 ; - state->TunerRegs[94].Reg_Val = 0x17 ; - - state->TunerRegs[95].Reg_Num = 158 ; - state->TunerRegs[95].Reg_Val = 0xA9 ; - - state->TunerRegs[96].Reg_Num = 159 ; - state->TunerRegs[96].Reg_Val = 0x00 ; - - state->TunerRegs[97].Reg_Num = 160 ; - state->TunerRegs[97].Reg_Val = 0x00 ; - - state->TunerRegs[98].Reg_Num = 161 ; - state->TunerRegs[98].Reg_Val = 0x00 ; - - state->TunerRegs[99].Reg_Num = 162 ; - state->TunerRegs[99].Reg_Val = 0x40 ; - - state->TunerRegs[100].Reg_Num = 166 ; - state->TunerRegs[100].Reg_Val = 0xAE ; - - state->TunerRegs[101].Reg_Num = 167 ; - state->TunerRegs[101].Reg_Val = 0x1B ; - - state->TunerRegs[102].Reg_Num = 168 ; - state->TunerRegs[102].Reg_Val = 0xF2 ; - - state->TunerRegs[103].Reg_Num = 195 ; - state->TunerRegs[103].Reg_Val = 0x00 ; - - return 0 ; -} - -static u16 MXL5005_ControlInit(struct dvb_frontend *fe) -{ - struct mxl5005s_state *state = fe->tuner_priv; - state->Init_Ctrl_Num = INITCTRL_NUM; - - state->Init_Ctrl[0].Ctrl_Num = DN_IQTN_AMP_CUT ; - state->Init_Ctrl[0].size = 1 ; - state->Init_Ctrl[0].addr[0] = 73; - state->Init_Ctrl[0].bit[0] = 7; - state->Init_Ctrl[0].val[0] = 0; - - state->Init_Ctrl[1].Ctrl_Num = BB_MODE ; - state->Init_Ctrl[1].size = 1 ; - state->Init_Ctrl[1].addr[0] = 53; - state->Init_Ctrl[1].bit[0] = 2; - state->Init_Ctrl[1].val[0] = 1; - - state->Init_Ctrl[2].Ctrl_Num = BB_BUF ; - state->Init_Ctrl[2].size = 2 ; - state->Init_Ctrl[2].addr[0] = 53; - state->Init_Ctrl[2].bit[0] = 1; - state->Init_Ctrl[2].val[0] = 0; - state->Init_Ctrl[2].addr[1] = 57; - state->Init_Ctrl[2].bit[1] = 0; - state->Init_Ctrl[2].val[1] = 1; - - state->Init_Ctrl[3].Ctrl_Num = BB_BUF_OA ; - state->Init_Ctrl[3].size = 1 ; - state->Init_Ctrl[3].addr[0] = 53; - state->Init_Ctrl[3].bit[0] = 0; - state->Init_Ctrl[3].val[0] = 0; - - state->Init_Ctrl[4].Ctrl_Num = BB_ALPF_BANDSELECT ; - state->Init_Ctrl[4].size = 3 ; - state->Init_Ctrl[4].addr[0] = 53; - state->Init_Ctrl[4].bit[0] = 5; - state->Init_Ctrl[4].val[0] = 0; - state->Init_Ctrl[4].addr[1] = 53; - state->Init_Ctrl[4].bit[1] = 6; - state->Init_Ctrl[4].val[1] = 0; - state->Init_Ctrl[4].addr[2] = 53; - state->Init_Ctrl[4].bit[2] = 7; - state->Init_Ctrl[4].val[2] = 1; - - state->Init_Ctrl[5].Ctrl_Num = BB_IQSWAP ; - state->Init_Ctrl[5].size = 1 ; - state->Init_Ctrl[5].addr[0] = 59; - state->Init_Ctrl[5].bit[0] = 0; - state->Init_Ctrl[5].val[0] = 0; - - state->Init_Ctrl[6].Ctrl_Num = BB_DLPF_BANDSEL ; - state->Init_Ctrl[6].size = 2 ; - state->Init_Ctrl[6].addr[0] = 53; - state->Init_Ctrl[6].bit[0] = 3; - state->Init_Ctrl[6].val[0] = 0; - state->Init_Ctrl[6].addr[1] = 53; - state->Init_Ctrl[6].bit[1] = 4; - state->Init_Ctrl[6].val[1] = 1; - - state->Init_Ctrl[7].Ctrl_Num = RFSYN_CHP_GAIN ; - state->Init_Ctrl[7].size = 4 ; - state->Init_Ctrl[7].addr[0] = 22; - state->Init_Ctrl[7].bit[0] = 4; - state->Init_Ctrl[7].val[0] = 0; - state->Init_Ctrl[7].addr[1] = 22; - state->Init_Ctrl[7].bit[1] = 5; - state->Init_Ctrl[7].val[1] = 1; - state->Init_Ctrl[7].addr[2] = 22; - state->Init_Ctrl[7].bit[2] = 6; - state->Init_Ctrl[7].val[2] = 1; - state->Init_Ctrl[7].addr[3] = 22; - state->Init_Ctrl[7].bit[3] = 7; - state->Init_Ctrl[7].val[3] = 0; - - state->Init_Ctrl[8].Ctrl_Num = RFSYN_EN_CHP_HIGAIN ; - state->Init_Ctrl[8].size = 1 ; - state->Init_Ctrl[8].addr[0] = 22; - state->Init_Ctrl[8].bit[0] = 2; - state->Init_Ctrl[8].val[0] = 0; - - state->Init_Ctrl[9].Ctrl_Num = AGC_IF ; - state->Init_Ctrl[9].size = 4 ; - state->Init_Ctrl[9].addr[0] = 76; - state->Init_Ctrl[9].bit[0] = 0; - state->Init_Ctrl[9].val[0] = 1; - state->Init_Ctrl[9].addr[1] = 76; - state->Init_Ctrl[9].bit[1] = 1; - state->Init_Ctrl[9].val[1] = 1; - state->Init_Ctrl[9].addr[2] = 76; - state->Init_Ctrl[9].bit[2] = 2; - state->Init_Ctrl[9].val[2] = 0; - state->Init_Ctrl[9].addr[3] = 76; - state->Init_Ctrl[9].bit[3] = 3; - state->Init_Ctrl[9].val[3] = 1; - - state->Init_Ctrl[10].Ctrl_Num = AGC_RF ; - state->Init_Ctrl[10].size = 4 ; - state->Init_Ctrl[10].addr[0] = 76; - state->Init_Ctrl[10].bit[0] = 4; - state->Init_Ctrl[10].val[0] = 1; - state->Init_Ctrl[10].addr[1] = 76; - state->Init_Ctrl[10].bit[1] = 5; - state->Init_Ctrl[10].val[1] = 1; - state->Init_Ctrl[10].addr[2] = 76; - state->Init_Ctrl[10].bit[2] = 6; - state->Init_Ctrl[10].val[2] = 0; - state->Init_Ctrl[10].addr[3] = 76; - state->Init_Ctrl[10].bit[3] = 7; - state->Init_Ctrl[10].val[3] = 1; - - state->Init_Ctrl[11].Ctrl_Num = IF_DIVVAL ; - state->Init_Ctrl[11].size = 5 ; - state->Init_Ctrl[11].addr[0] = 43; - state->Init_Ctrl[11].bit[0] = 3; - state->Init_Ctrl[11].val[0] = 0; - state->Init_Ctrl[11].addr[1] = 43; - state->Init_Ctrl[11].bit[1] = 4; - state->Init_Ctrl[11].val[1] = 0; - state->Init_Ctrl[11].addr[2] = 43; - state->Init_Ctrl[11].bit[2] = 5; - state->Init_Ctrl[11].val[2] = 0; - state->Init_Ctrl[11].addr[3] = 43; - state->Init_Ctrl[11].bit[3] = 6; - state->Init_Ctrl[11].val[3] = 1; - state->Init_Ctrl[11].addr[4] = 43; - state->Init_Ctrl[11].bit[4] = 7; - state->Init_Ctrl[11].val[4] = 0; - - state->Init_Ctrl[12].Ctrl_Num = IF_VCO_BIAS ; - state->Init_Ctrl[12].size = 6 ; - state->Init_Ctrl[12].addr[0] = 44; - state->Init_Ctrl[12].bit[0] = 2; - state->Init_Ctrl[12].val[0] = 0; - state->Init_Ctrl[12].addr[1] = 44; - state->Init_Ctrl[12].bit[1] = 3; - state->Init_Ctrl[12].val[1] = 0; - state->Init_Ctrl[12].addr[2] = 44; - state->Init_Ctrl[12].bit[2] = 4; - state->Init_Ctrl[12].val[2] = 0; - state->Init_Ctrl[12].addr[3] = 44; - state->Init_Ctrl[12].bit[3] = 5; - state->Init_Ctrl[12].val[3] = 1; - state->Init_Ctrl[12].addr[4] = 44; - state->Init_Ctrl[12].bit[4] = 6; - state->Init_Ctrl[12].val[4] = 0; - state->Init_Ctrl[12].addr[5] = 44; - state->Init_Ctrl[12].bit[5] = 7; - state->Init_Ctrl[12].val[5] = 0; - - state->Init_Ctrl[13].Ctrl_Num = CHCAL_INT_MOD_IF ; - state->Init_Ctrl[13].size = 7 ; - state->Init_Ctrl[13].addr[0] = 11; - state->Init_Ctrl[13].bit[0] = 0; - state->Init_Ctrl[13].val[0] = 1; - state->Init_Ctrl[13].addr[1] = 11; - state->Init_Ctrl[13].bit[1] = 1; - state->Init_Ctrl[13].val[1] = 0; - state->Init_Ctrl[13].addr[2] = 11; - state->Init_Ctrl[13].bit[2] = 2; - state->Init_Ctrl[13].val[2] = 0; - state->Init_Ctrl[13].addr[3] = 11; - state->Init_Ctrl[13].bit[3] = 3; - state->Init_Ctrl[13].val[3] = 1; - state->Init_Ctrl[13].addr[4] = 11; - state->Init_Ctrl[13].bit[4] = 4; - state->Init_Ctrl[13].val[4] = 1; - state->Init_Ctrl[13].addr[5] = 11; - state->Init_Ctrl[13].bit[5] = 5; - state->Init_Ctrl[13].val[5] = 0; - state->Init_Ctrl[13].addr[6] = 11; - state->Init_Ctrl[13].bit[6] = 6; - state->Init_Ctrl[13].val[6] = 0; - - state->Init_Ctrl[14].Ctrl_Num = CHCAL_FRAC_MOD_IF ; - state->Init_Ctrl[14].size = 16 ; - state->Init_Ctrl[14].addr[0] = 13; - state->Init_Ctrl[14].bit[0] = 0; - state->Init_Ctrl[14].val[0] = 0; - state->Init_Ctrl[14].addr[1] = 13; - state->Init_Ctrl[14].bit[1] = 1; - state->Init_Ctrl[14].val[1] = 0; - state->Init_Ctrl[14].addr[2] = 13; - state->Init_Ctrl[14].bit[2] = 2; - state->Init_Ctrl[14].val[2] = 0; - state->Init_Ctrl[14].addr[3] = 13; - state->Init_Ctrl[14].bit[3] = 3; - state->Init_Ctrl[14].val[3] = 0; - state->Init_Ctrl[14].addr[4] = 13; - state->Init_Ctrl[14].bit[4] = 4; - state->Init_Ctrl[14].val[4] = 0; - state->Init_Ctrl[14].addr[5] = 13; - state->Init_Ctrl[14].bit[5] = 5; - state->Init_Ctrl[14].val[5] = 0; - state->Init_Ctrl[14].addr[6] = 13; - state->Init_Ctrl[14].bit[6] = 6; - state->Init_Ctrl[14].val[6] = 0; - state->Init_Ctrl[14].addr[7] = 13; - state->Init_Ctrl[14].bit[7] = 7; - state->Init_Ctrl[14].val[7] = 0; - state->Init_Ctrl[14].addr[8] = 12; - state->Init_Ctrl[14].bit[8] = 0; - state->Init_Ctrl[14].val[8] = 0; - state->Init_Ctrl[14].addr[9] = 12; - state->Init_Ctrl[14].bit[9] = 1; - state->Init_Ctrl[14].val[9] = 0; - state->Init_Ctrl[14].addr[10] = 12; - state->Init_Ctrl[14].bit[10] = 2; - state->Init_Ctrl[14].val[10] = 0; - state->Init_Ctrl[14].addr[11] = 12; - state->Init_Ctrl[14].bit[11] = 3; - state->Init_Ctrl[14].val[11] = 0; - state->Init_Ctrl[14].addr[12] = 12; - state->Init_Ctrl[14].bit[12] = 4; - state->Init_Ctrl[14].val[12] = 0; - state->Init_Ctrl[14].addr[13] = 12; - state->Init_Ctrl[14].bit[13] = 5; - state->Init_Ctrl[14].val[13] = 1; - state->Init_Ctrl[14].addr[14] = 12; - state->Init_Ctrl[14].bit[14] = 6; - state->Init_Ctrl[14].val[14] = 1; - state->Init_Ctrl[14].addr[15] = 12; - state->Init_Ctrl[14].bit[15] = 7; - state->Init_Ctrl[14].val[15] = 0; - - state->Init_Ctrl[15].Ctrl_Num = DRV_RES_SEL ; - state->Init_Ctrl[15].size = 3 ; - state->Init_Ctrl[15].addr[0] = 147; - state->Init_Ctrl[15].bit[0] = 2; - state->Init_Ctrl[15].val[0] = 0; - state->Init_Ctrl[15].addr[1] = 147; - state->Init_Ctrl[15].bit[1] = 3; - state->Init_Ctrl[15].val[1] = 1; - state->Init_Ctrl[15].addr[2] = 147; - state->Init_Ctrl[15].bit[2] = 4; - state->Init_Ctrl[15].val[2] = 1; - - state->Init_Ctrl[16].Ctrl_Num = I_DRIVER ; - state->Init_Ctrl[16].size = 2 ; - state->Init_Ctrl[16].addr[0] = 147; - state->Init_Ctrl[16].bit[0] = 0; - state->Init_Ctrl[16].val[0] = 0; - state->Init_Ctrl[16].addr[1] = 147; - state->Init_Ctrl[16].bit[1] = 1; - state->Init_Ctrl[16].val[1] = 1; - - state->Init_Ctrl[17].Ctrl_Num = EN_AAF ; - state->Init_Ctrl[17].size = 1 ; - state->Init_Ctrl[17].addr[0] = 147; - state->Init_Ctrl[17].bit[0] = 7; - state->Init_Ctrl[17].val[0] = 0; - - state->Init_Ctrl[18].Ctrl_Num = EN_3P ; - state->Init_Ctrl[18].size = 1 ; - state->Init_Ctrl[18].addr[0] = 147; - state->Init_Ctrl[18].bit[0] = 6; - state->Init_Ctrl[18].val[0] = 0; - - state->Init_Ctrl[19].Ctrl_Num = EN_AUX_3P ; - state->Init_Ctrl[19].size = 1 ; - state->Init_Ctrl[19].addr[0] = 156; - state->Init_Ctrl[19].bit[0] = 0; - state->Init_Ctrl[19].val[0] = 0; - - state->Init_Ctrl[20].Ctrl_Num = SEL_AAF_BAND ; - state->Init_Ctrl[20].size = 1 ; - state->Init_Ctrl[20].addr[0] = 147; - state->Init_Ctrl[20].bit[0] = 5; - state->Init_Ctrl[20].val[0] = 0; - - state->Init_Ctrl[21].Ctrl_Num = SEQ_ENCLK16_CLK_OUT ; - state->Init_Ctrl[21].size = 1 ; - state->Init_Ctrl[21].addr[0] = 137; - state->Init_Ctrl[21].bit[0] = 4; - state->Init_Ctrl[21].val[0] = 0; - - state->Init_Ctrl[22].Ctrl_Num = SEQ_SEL4_16B ; - state->Init_Ctrl[22].size = 1 ; - state->Init_Ctrl[22].addr[0] = 137; - state->Init_Ctrl[22].bit[0] = 7; - state->Init_Ctrl[22].val[0] = 0; - - state->Init_Ctrl[23].Ctrl_Num = XTAL_CAPSELECT ; - state->Init_Ctrl[23].size = 1 ; - state->Init_Ctrl[23].addr[0] = 91; - state->Init_Ctrl[23].bit[0] = 5; - state->Init_Ctrl[23].val[0] = 1; - - state->Init_Ctrl[24].Ctrl_Num = IF_SEL_DBL ; - state->Init_Ctrl[24].size = 1 ; - state->Init_Ctrl[24].addr[0] = 43; - state->Init_Ctrl[24].bit[0] = 0; - state->Init_Ctrl[24].val[0] = 1; - - state->Init_Ctrl[25].Ctrl_Num = RFSYN_R_DIV ; - state->Init_Ctrl[25].size = 2 ; - state->Init_Ctrl[25].addr[0] = 22; - state->Init_Ctrl[25].bit[0] = 0; - state->Init_Ctrl[25].val[0] = 1; - state->Init_Ctrl[25].addr[1] = 22; - state->Init_Ctrl[25].bit[1] = 1; - state->Init_Ctrl[25].val[1] = 1; - - state->Init_Ctrl[26].Ctrl_Num = SEQ_EXTSYNTHCALIF ; - state->Init_Ctrl[26].size = 1 ; - state->Init_Ctrl[26].addr[0] = 134; - state->Init_Ctrl[26].bit[0] = 2; - state->Init_Ctrl[26].val[0] = 0; - - state->Init_Ctrl[27].Ctrl_Num = SEQ_EXTDCCAL ; - state->Init_Ctrl[27].size = 1 ; - state->Init_Ctrl[27].addr[0] = 137; - state->Init_Ctrl[27].bit[0] = 3; - state->Init_Ctrl[27].val[0] = 0; - - state->Init_Ctrl[28].Ctrl_Num = AGC_EN_RSSI ; - state->Init_Ctrl[28].size = 1 ; - state->Init_Ctrl[28].addr[0] = 77; - state->Init_Ctrl[28].bit[0] = 7; - state->Init_Ctrl[28].val[0] = 0; - - state->Init_Ctrl[29].Ctrl_Num = RFA_ENCLKRFAGC ; - state->Init_Ctrl[29].size = 1 ; - state->Init_Ctrl[29].addr[0] = 166; - state->Init_Ctrl[29].bit[0] = 7; - state->Init_Ctrl[29].val[0] = 1; - - state->Init_Ctrl[30].Ctrl_Num = RFA_RSSI_REFH ; - state->Init_Ctrl[30].size = 3 ; - state->Init_Ctrl[30].addr[0] = 166; - state->Init_Ctrl[30].bit[0] = 0; - state->Init_Ctrl[30].val[0] = 0; - state->Init_Ctrl[30].addr[1] = 166; - state->Init_Ctrl[30].bit[1] = 1; - state->Init_Ctrl[30].val[1] = 1; - state->Init_Ctrl[30].addr[2] = 166; - state->Init_Ctrl[30].bit[2] = 2; - state->Init_Ctrl[30].val[2] = 1; - - state->Init_Ctrl[31].Ctrl_Num = RFA_RSSI_REF ; - state->Init_Ctrl[31].size = 3 ; - state->Init_Ctrl[31].addr[0] = 166; - state->Init_Ctrl[31].bit[0] = 3; - state->Init_Ctrl[31].val[0] = 1; - state->Init_Ctrl[31].addr[1] = 166; - state->Init_Ctrl[31].bit[1] = 4; - state->Init_Ctrl[31].val[1] = 0; - state->Init_Ctrl[31].addr[2] = 166; - state->Init_Ctrl[31].bit[2] = 5; - state->Init_Ctrl[31].val[2] = 1; - - state->Init_Ctrl[32].Ctrl_Num = RFA_RSSI_REFL ; - state->Init_Ctrl[32].size = 3 ; - state->Init_Ctrl[32].addr[0] = 167; - state->Init_Ctrl[32].bit[0] = 0; - state->Init_Ctrl[32].val[0] = 1; - state->Init_Ctrl[32].addr[1] = 167; - state->Init_Ctrl[32].bit[1] = 1; - state->Init_Ctrl[32].val[1] = 1; - state->Init_Ctrl[32].addr[2] = 167; - state->Init_Ctrl[32].bit[2] = 2; - state->Init_Ctrl[32].val[2] = 0; - - state->Init_Ctrl[33].Ctrl_Num = RFA_FLR ; - state->Init_Ctrl[33].size = 4 ; - state->Init_Ctrl[33].addr[0] = 168; - state->Init_Ctrl[33].bit[0] = 0; - state->Init_Ctrl[33].val[0] = 0; - state->Init_Ctrl[33].addr[1] = 168; - state->Init_Ctrl[33].bit[1] = 1; - state->Init_Ctrl[33].val[1] = 1; - state->Init_Ctrl[33].addr[2] = 168; - state->Init_Ctrl[33].bit[2] = 2; - state->Init_Ctrl[33].val[2] = 0; - state->Init_Ctrl[33].addr[3] = 168; - state->Init_Ctrl[33].bit[3] = 3; - state->Init_Ctrl[33].val[3] = 0; - - state->Init_Ctrl[34].Ctrl_Num = RFA_CEIL ; - state->Init_Ctrl[34].size = 4 ; - state->Init_Ctrl[34].addr[0] = 168; - state->Init_Ctrl[34].bit[0] = 4; - state->Init_Ctrl[34].val[0] = 1; - state->Init_Ctrl[34].addr[1] = 168; - state->Init_Ctrl[34].bit[1] = 5; - state->Init_Ctrl[34].val[1] = 1; - state->Init_Ctrl[34].addr[2] = 168; - state->Init_Ctrl[34].bit[2] = 6; - state->Init_Ctrl[34].val[2] = 1; - state->Init_Ctrl[34].addr[3] = 168; - state->Init_Ctrl[34].bit[3] = 7; - state->Init_Ctrl[34].val[3] = 1; - - state->Init_Ctrl[35].Ctrl_Num = SEQ_EXTIQFSMPULSE ; - state->Init_Ctrl[35].size = 1 ; - state->Init_Ctrl[35].addr[0] = 135; - state->Init_Ctrl[35].bit[0] = 0; - state->Init_Ctrl[35].val[0] = 0; - - state->Init_Ctrl[36].Ctrl_Num = OVERRIDE_1 ; - state->Init_Ctrl[36].size = 1 ; - state->Init_Ctrl[36].addr[0] = 56; - state->Init_Ctrl[36].bit[0] = 3; - state->Init_Ctrl[36].val[0] = 0; - - state->Init_Ctrl[37].Ctrl_Num = BB_INITSTATE_DLPF_TUNE ; - state->Init_Ctrl[37].size = 7 ; - state->Init_Ctrl[37].addr[0] = 59; - state->Init_Ctrl[37].bit[0] = 1; - state->Init_Ctrl[37].val[0] = 0; - state->Init_Ctrl[37].addr[1] = 59; - state->Init_Ctrl[37].bit[1] = 2; - state->Init_Ctrl[37].val[1] = 0; - state->Init_Ctrl[37].addr[2] = 59; - state->Init_Ctrl[37].bit[2] = 3; - state->Init_Ctrl[37].val[2] = 0; - state->Init_Ctrl[37].addr[3] = 59; - state->Init_Ctrl[37].bit[3] = 4; - state->Init_Ctrl[37].val[3] = 0; - state->Init_Ctrl[37].addr[4] = 59; - state->Init_Ctrl[37].bit[4] = 5; - state->Init_Ctrl[37].val[4] = 0; - state->Init_Ctrl[37].addr[5] = 59; - state->Init_Ctrl[37].bit[5] = 6; - state->Init_Ctrl[37].val[5] = 0; - state->Init_Ctrl[37].addr[6] = 59; - state->Init_Ctrl[37].bit[6] = 7; - state->Init_Ctrl[37].val[6] = 0; - - state->Init_Ctrl[38].Ctrl_Num = TG_R_DIV ; - state->Init_Ctrl[38].size = 6 ; - state->Init_Ctrl[38].addr[0] = 32; - state->Init_Ctrl[38].bit[0] = 2; - state->Init_Ctrl[38].val[0] = 0; - state->Init_Ctrl[38].addr[1] = 32; - state->Init_Ctrl[38].bit[1] = 3; - state->Init_Ctrl[38].val[1] = 0; - state->Init_Ctrl[38].addr[2] = 32; - state->Init_Ctrl[38].bit[2] = 4; - state->Init_Ctrl[38].val[2] = 0; - state->Init_Ctrl[38].addr[3] = 32; - state->Init_Ctrl[38].bit[3] = 5; - state->Init_Ctrl[38].val[3] = 0; - state->Init_Ctrl[38].addr[4] = 32; - state->Init_Ctrl[38].bit[4] = 6; - state->Init_Ctrl[38].val[4] = 1; - state->Init_Ctrl[38].addr[5] = 32; - state->Init_Ctrl[38].bit[5] = 7; - state->Init_Ctrl[38].val[5] = 0; - - state->Init_Ctrl[39].Ctrl_Num = EN_CHP_LIN_B ; - state->Init_Ctrl[39].size = 1 ; - state->Init_Ctrl[39].addr[0] = 25; - state->Init_Ctrl[39].bit[0] = 3; - state->Init_Ctrl[39].val[0] = 1; - - - state->CH_Ctrl_Num = CHCTRL_NUM ; - - state->CH_Ctrl[0].Ctrl_Num = DN_POLY ; - state->CH_Ctrl[0].size = 2 ; - state->CH_Ctrl[0].addr[0] = 68; - state->CH_Ctrl[0].bit[0] = 6; - state->CH_Ctrl[0].val[0] = 1; - state->CH_Ctrl[0].addr[1] = 68; - state->CH_Ctrl[0].bit[1] = 7; - state->CH_Ctrl[0].val[1] = 1; - - state->CH_Ctrl[1].Ctrl_Num = DN_RFGAIN ; - state->CH_Ctrl[1].size = 2 ; - state->CH_Ctrl[1].addr[0] = 70; - state->CH_Ctrl[1].bit[0] = 6; - state->CH_Ctrl[1].val[0] = 1; - state->CH_Ctrl[1].addr[1] = 70; - state->CH_Ctrl[1].bit[1] = 7; - state->CH_Ctrl[1].val[1] = 0; - - state->CH_Ctrl[2].Ctrl_Num = DN_CAP_RFLPF ; - state->CH_Ctrl[2].size = 9 ; - state->CH_Ctrl[2].addr[0] = 69; - state->CH_Ctrl[2].bit[0] = 5; - state->CH_Ctrl[2].val[0] = 0; - state->CH_Ctrl[2].addr[1] = 69; - state->CH_Ctrl[2].bit[1] = 6; - state->CH_Ctrl[2].val[1] = 0; - state->CH_Ctrl[2].addr[2] = 69; - state->CH_Ctrl[2].bit[2] = 7; - state->CH_Ctrl[2].val[2] = 0; - state->CH_Ctrl[2].addr[3] = 68; - state->CH_Ctrl[2].bit[3] = 0; - state->CH_Ctrl[2].val[3] = 0; - state->CH_Ctrl[2].addr[4] = 68; - state->CH_Ctrl[2].bit[4] = 1; - state->CH_Ctrl[2].val[4] = 0; - state->CH_Ctrl[2].addr[5] = 68; - state->CH_Ctrl[2].bit[5] = 2; - state->CH_Ctrl[2].val[5] = 0; - state->CH_Ctrl[2].addr[6] = 68; - state->CH_Ctrl[2].bit[6] = 3; - state->CH_Ctrl[2].val[6] = 0; - state->CH_Ctrl[2].addr[7] = 68; - state->CH_Ctrl[2].bit[7] = 4; - state->CH_Ctrl[2].val[7] = 0; - state->CH_Ctrl[2].addr[8] = 68; - state->CH_Ctrl[2].bit[8] = 5; - state->CH_Ctrl[2].val[8] = 0; - - state->CH_Ctrl[3].Ctrl_Num = DN_EN_VHFUHFBAR ; - state->CH_Ctrl[3].size = 1 ; - state->CH_Ctrl[3].addr[0] = 70; - state->CH_Ctrl[3].bit[0] = 5; - state->CH_Ctrl[3].val[0] = 0; - - state->CH_Ctrl[4].Ctrl_Num = DN_GAIN_ADJUST ; - state->CH_Ctrl[4].size = 3 ; - state->CH_Ctrl[4].addr[0] = 73; - state->CH_Ctrl[4].bit[0] = 4; - state->CH_Ctrl[4].val[0] = 0; - state->CH_Ctrl[4].addr[1] = 73; - state->CH_Ctrl[4].bit[1] = 5; - state->CH_Ctrl[4].val[1] = 1; - state->CH_Ctrl[4].addr[2] = 73; - state->CH_Ctrl[4].bit[2] = 6; - state->CH_Ctrl[4].val[2] = 0; - - state->CH_Ctrl[5].Ctrl_Num = DN_IQTNBUF_AMP ; - state->CH_Ctrl[5].size = 4 ; - state->CH_Ctrl[5].addr[0] = 70; - state->CH_Ctrl[5].bit[0] = 0; - state->CH_Ctrl[5].val[0] = 0; - state->CH_Ctrl[5].addr[1] = 70; - state->CH_Ctrl[5].bit[1] = 1; - state->CH_Ctrl[5].val[1] = 0; - state->CH_Ctrl[5].addr[2] = 70; - state->CH_Ctrl[5].bit[2] = 2; - state->CH_Ctrl[5].val[2] = 0; - state->CH_Ctrl[5].addr[3] = 70; - state->CH_Ctrl[5].bit[3] = 3; - state->CH_Ctrl[5].val[3] = 0; - - state->CH_Ctrl[6].Ctrl_Num = DN_IQTNGNBFBIAS_BST ; - state->CH_Ctrl[6].size = 1 ; - state->CH_Ctrl[6].addr[0] = 70; - state->CH_Ctrl[6].bit[0] = 4; - state->CH_Ctrl[6].val[0] = 1; - - state->CH_Ctrl[7].Ctrl_Num = RFSYN_EN_OUTMUX ; - state->CH_Ctrl[7].size = 1 ; - state->CH_Ctrl[7].addr[0] = 111; - state->CH_Ctrl[7].bit[0] = 4; - state->CH_Ctrl[7].val[0] = 0; - - state->CH_Ctrl[8].Ctrl_Num = RFSYN_SEL_VCO_OUT ; - state->CH_Ctrl[8].size = 1 ; - state->CH_Ctrl[8].addr[0] = 111; - state->CH_Ctrl[8].bit[0] = 7; - state->CH_Ctrl[8].val[0] = 1; - - state->CH_Ctrl[9].Ctrl_Num = RFSYN_SEL_VCO_HI ; - state->CH_Ctrl[9].size = 1 ; - state->CH_Ctrl[9].addr[0] = 111; - state->CH_Ctrl[9].bit[0] = 6; - state->CH_Ctrl[9].val[0] = 1; - - state->CH_Ctrl[10].Ctrl_Num = RFSYN_SEL_DIVM ; - state->CH_Ctrl[10].size = 1 ; - state->CH_Ctrl[10].addr[0] = 111; - state->CH_Ctrl[10].bit[0] = 5; - state->CH_Ctrl[10].val[0] = 0; - - state->CH_Ctrl[11].Ctrl_Num = RFSYN_RF_DIV_BIAS ; - state->CH_Ctrl[11].size = 2 ; - state->CH_Ctrl[11].addr[0] = 110; - state->CH_Ctrl[11].bit[0] = 0; - state->CH_Ctrl[11].val[0] = 1; - state->CH_Ctrl[11].addr[1] = 110; - state->CH_Ctrl[11].bit[1] = 1; - state->CH_Ctrl[11].val[1] = 0; - - state->CH_Ctrl[12].Ctrl_Num = DN_SEL_FREQ ; - state->CH_Ctrl[12].size = 3 ; - state->CH_Ctrl[12].addr[0] = 69; - state->CH_Ctrl[12].bit[0] = 2; - state->CH_Ctrl[12].val[0] = 0; - state->CH_Ctrl[12].addr[1] = 69; - state->CH_Ctrl[12].bit[1] = 3; - state->CH_Ctrl[12].val[1] = 0; - state->CH_Ctrl[12].addr[2] = 69; - state->CH_Ctrl[12].bit[2] = 4; - state->CH_Ctrl[12].val[2] = 0; - - state->CH_Ctrl[13].Ctrl_Num = RFSYN_VCO_BIAS ; - state->CH_Ctrl[13].size = 6 ; - state->CH_Ctrl[13].addr[0] = 110; - state->CH_Ctrl[13].bit[0] = 2; - state->CH_Ctrl[13].val[0] = 0; - state->CH_Ctrl[13].addr[1] = 110; - state->CH_Ctrl[13].bit[1] = 3; - state->CH_Ctrl[13].val[1] = 0; - state->CH_Ctrl[13].addr[2] = 110; - state->CH_Ctrl[13].bit[2] = 4; - state->CH_Ctrl[13].val[2] = 0; - state->CH_Ctrl[13].addr[3] = 110; - state->CH_Ctrl[13].bit[3] = 5; - state->CH_Ctrl[13].val[3] = 0; - state->CH_Ctrl[13].addr[4] = 110; - state->CH_Ctrl[13].bit[4] = 6; - state->CH_Ctrl[13].val[4] = 0; - state->CH_Ctrl[13].addr[5] = 110; - state->CH_Ctrl[13].bit[5] = 7; - state->CH_Ctrl[13].val[5] = 1; - - state->CH_Ctrl[14].Ctrl_Num = CHCAL_INT_MOD_RF ; - state->CH_Ctrl[14].size = 7 ; - state->CH_Ctrl[14].addr[0] = 14; - state->CH_Ctrl[14].bit[0] = 0; - state->CH_Ctrl[14].val[0] = 0; - state->CH_Ctrl[14].addr[1] = 14; - state->CH_Ctrl[14].bit[1] = 1; - state->CH_Ctrl[14].val[1] = 0; - state->CH_Ctrl[14].addr[2] = 14; - state->CH_Ctrl[14].bit[2] = 2; - state->CH_Ctrl[14].val[2] = 0; - state->CH_Ctrl[14].addr[3] = 14; - state->CH_Ctrl[14].bit[3] = 3; - state->CH_Ctrl[14].val[3] = 0; - state->CH_Ctrl[14].addr[4] = 14; - state->CH_Ctrl[14].bit[4] = 4; - state->CH_Ctrl[14].val[4] = 0; - state->CH_Ctrl[14].addr[5] = 14; - state->CH_Ctrl[14].bit[5] = 5; - state->CH_Ctrl[14].val[5] = 0; - state->CH_Ctrl[14].addr[6] = 14; - state->CH_Ctrl[14].bit[6] = 6; - state->CH_Ctrl[14].val[6] = 0; - - state->CH_Ctrl[15].Ctrl_Num = CHCAL_FRAC_MOD_RF ; - state->CH_Ctrl[15].size = 18 ; - state->CH_Ctrl[15].addr[0] = 17; - state->CH_Ctrl[15].bit[0] = 6; - state->CH_Ctrl[15].val[0] = 0; - state->CH_Ctrl[15].addr[1] = 17; - state->CH_Ctrl[15].bit[1] = 7; - state->CH_Ctrl[15].val[1] = 0; - state->CH_Ctrl[15].addr[2] = 16; - state->CH_Ctrl[15].bit[2] = 0; - state->CH_Ctrl[15].val[2] = 0; - state->CH_Ctrl[15].addr[3] = 16; - state->CH_Ctrl[15].bit[3] = 1; - state->CH_Ctrl[15].val[3] = 0; - state->CH_Ctrl[15].addr[4] = 16; - state->CH_Ctrl[15].bit[4] = 2; - state->CH_Ctrl[15].val[4] = 0; - state->CH_Ctrl[15].addr[5] = 16; - state->CH_Ctrl[15].bit[5] = 3; - state->CH_Ctrl[15].val[5] = 0; - state->CH_Ctrl[15].addr[6] = 16; - state->CH_Ctrl[15].bit[6] = 4; - state->CH_Ctrl[15].val[6] = 0; - state->CH_Ctrl[15].addr[7] = 16; - state->CH_Ctrl[15].bit[7] = 5; - state->CH_Ctrl[15].val[7] = 0; - state->CH_Ctrl[15].addr[8] = 16; - state->CH_Ctrl[15].bit[8] = 6; - state->CH_Ctrl[15].val[8] = 0; - state->CH_Ctrl[15].addr[9] = 16; - state->CH_Ctrl[15].bit[9] = 7; - state->CH_Ctrl[15].val[9] = 0; - state->CH_Ctrl[15].addr[10] = 15; - state->CH_Ctrl[15].bit[10] = 0; - state->CH_Ctrl[15].val[10] = 0; - state->CH_Ctrl[15].addr[11] = 15; - state->CH_Ctrl[15].bit[11] = 1; - state->CH_Ctrl[15].val[11] = 0; - state->CH_Ctrl[15].addr[12] = 15; - state->CH_Ctrl[15].bit[12] = 2; - state->CH_Ctrl[15].val[12] = 0; - state->CH_Ctrl[15].addr[13] = 15; - state->CH_Ctrl[15].bit[13] = 3; - state->CH_Ctrl[15].val[13] = 0; - state->CH_Ctrl[15].addr[14] = 15; - state->CH_Ctrl[15].bit[14] = 4; - state->CH_Ctrl[15].val[14] = 0; - state->CH_Ctrl[15].addr[15] = 15; - state->CH_Ctrl[15].bit[15] = 5; - state->CH_Ctrl[15].val[15] = 0; - state->CH_Ctrl[15].addr[16] = 15; - state->CH_Ctrl[15].bit[16] = 6; - state->CH_Ctrl[15].val[16] = 1; - state->CH_Ctrl[15].addr[17] = 15; - state->CH_Ctrl[15].bit[17] = 7; - state->CH_Ctrl[15].val[17] = 1; - - state->CH_Ctrl[16].Ctrl_Num = RFSYN_LPF_R ; - state->CH_Ctrl[16].size = 5 ; - state->CH_Ctrl[16].addr[0] = 112; - state->CH_Ctrl[16].bit[0] = 0; - state->CH_Ctrl[16].val[0] = 0; - state->CH_Ctrl[16].addr[1] = 112; - state->CH_Ctrl[16].bit[1] = 1; - state->CH_Ctrl[16].val[1] = 0; - state->CH_Ctrl[16].addr[2] = 112; - state->CH_Ctrl[16].bit[2] = 2; - state->CH_Ctrl[16].val[2] = 0; - state->CH_Ctrl[16].addr[3] = 112; - state->CH_Ctrl[16].bit[3] = 3; - state->CH_Ctrl[16].val[3] = 0; - state->CH_Ctrl[16].addr[4] = 112; - state->CH_Ctrl[16].bit[4] = 4; - state->CH_Ctrl[16].val[4] = 1; - - state->CH_Ctrl[17].Ctrl_Num = CHCAL_EN_INT_RF ; - state->CH_Ctrl[17].size = 1 ; - state->CH_Ctrl[17].addr[0] = 14; - state->CH_Ctrl[17].bit[0] = 7; - state->CH_Ctrl[17].val[0] = 0; - - state->CH_Ctrl[18].Ctrl_Num = TG_LO_DIVVAL ; - state->CH_Ctrl[18].size = 4 ; - state->CH_Ctrl[18].addr[0] = 107; - state->CH_Ctrl[18].bit[0] = 3; - state->CH_Ctrl[18].val[0] = 0; - state->CH_Ctrl[18].addr[1] = 107; - state->CH_Ctrl[18].bit[1] = 4; - state->CH_Ctrl[18].val[1] = 0; - state->CH_Ctrl[18].addr[2] = 107; - state->CH_Ctrl[18].bit[2] = 5; - state->CH_Ctrl[18].val[2] = 0; - state->CH_Ctrl[18].addr[3] = 107; - state->CH_Ctrl[18].bit[3] = 6; - state->CH_Ctrl[18].val[3] = 0; - - state->CH_Ctrl[19].Ctrl_Num = TG_LO_SELVAL ; - state->CH_Ctrl[19].size = 3 ; - state->CH_Ctrl[19].addr[0] = 107; - state->CH_Ctrl[19].bit[0] = 7; - state->CH_Ctrl[19].val[0] = 1; - state->CH_Ctrl[19].addr[1] = 106; - state->CH_Ctrl[19].bit[1] = 0; - state->CH_Ctrl[19].val[1] = 1; - state->CH_Ctrl[19].addr[2] = 106; - state->CH_Ctrl[19].bit[2] = 1; - state->CH_Ctrl[19].val[2] = 1; - - state->CH_Ctrl[20].Ctrl_Num = TG_DIV_VAL ; - state->CH_Ctrl[20].size = 11 ; - state->CH_Ctrl[20].addr[0] = 109; - state->CH_Ctrl[20].bit[0] = 2; - state->CH_Ctrl[20].val[0] = 0; - state->CH_Ctrl[20].addr[1] = 109; - state->CH_Ctrl[20].bit[1] = 3; - state->CH_Ctrl[20].val[1] = 0; - state->CH_Ctrl[20].addr[2] = 109; - state->CH_Ctrl[20].bit[2] = 4; - state->CH_Ctrl[20].val[2] = 0; - state->CH_Ctrl[20].addr[3] = 109; - state->CH_Ctrl[20].bit[3] = 5; - state->CH_Ctrl[20].val[3] = 0; - state->CH_Ctrl[20].addr[4] = 109; - state->CH_Ctrl[20].bit[4] = 6; - state->CH_Ctrl[20].val[4] = 0; - state->CH_Ctrl[20].addr[5] = 109; - state->CH_Ctrl[20].bit[5] = 7; - state->CH_Ctrl[20].val[5] = 0; - state->CH_Ctrl[20].addr[6] = 108; - state->CH_Ctrl[20].bit[6] = 0; - state->CH_Ctrl[20].val[6] = 0; - state->CH_Ctrl[20].addr[7] = 108; - state->CH_Ctrl[20].bit[7] = 1; - state->CH_Ctrl[20].val[7] = 0; - state->CH_Ctrl[20].addr[8] = 108; - state->CH_Ctrl[20].bit[8] = 2; - state->CH_Ctrl[20].val[8] = 1; - state->CH_Ctrl[20].addr[9] = 108; - state->CH_Ctrl[20].bit[9] = 3; - state->CH_Ctrl[20].val[9] = 1; - state->CH_Ctrl[20].addr[10] = 108; - state->CH_Ctrl[20].bit[10] = 4; - state->CH_Ctrl[20].val[10] = 1; - - state->CH_Ctrl[21].Ctrl_Num = TG_VCO_BIAS ; - state->CH_Ctrl[21].size = 6 ; - state->CH_Ctrl[21].addr[0] = 106; - state->CH_Ctrl[21].bit[0] = 2; - state->CH_Ctrl[21].val[0] = 0; - state->CH_Ctrl[21].addr[1] = 106; - state->CH_Ctrl[21].bit[1] = 3; - state->CH_Ctrl[21].val[1] = 0; - state->CH_Ctrl[21].addr[2] = 106; - state->CH_Ctrl[21].bit[2] = 4; - state->CH_Ctrl[21].val[2] = 0; - state->CH_Ctrl[21].addr[3] = 106; - state->CH_Ctrl[21].bit[3] = 5; - state->CH_Ctrl[21].val[3] = 0; - state->CH_Ctrl[21].addr[4] = 106; - state->CH_Ctrl[21].bit[4] = 6; - state->CH_Ctrl[21].val[4] = 0; - state->CH_Ctrl[21].addr[5] = 106; - state->CH_Ctrl[21].bit[5] = 7; - state->CH_Ctrl[21].val[5] = 1; - - state->CH_Ctrl[22].Ctrl_Num = SEQ_EXTPOWERUP ; - state->CH_Ctrl[22].size = 1 ; - state->CH_Ctrl[22].addr[0] = 138; - state->CH_Ctrl[22].bit[0] = 4; - state->CH_Ctrl[22].val[0] = 1; - - state->CH_Ctrl[23].Ctrl_Num = OVERRIDE_2 ; - state->CH_Ctrl[23].size = 1 ; - state->CH_Ctrl[23].addr[0] = 17; - state->CH_Ctrl[23].bit[0] = 5; - state->CH_Ctrl[23].val[0] = 0; - - state->CH_Ctrl[24].Ctrl_Num = OVERRIDE_3 ; - state->CH_Ctrl[24].size = 1 ; - state->CH_Ctrl[24].addr[0] = 111; - state->CH_Ctrl[24].bit[0] = 3; - state->CH_Ctrl[24].val[0] = 0; - - state->CH_Ctrl[25].Ctrl_Num = OVERRIDE_4 ; - state->CH_Ctrl[25].size = 1 ; - state->CH_Ctrl[25].addr[0] = 112; - state->CH_Ctrl[25].bit[0] = 7; - state->CH_Ctrl[25].val[0] = 0; - - state->CH_Ctrl[26].Ctrl_Num = SEQ_FSM_PULSE ; - state->CH_Ctrl[26].size = 1 ; - state->CH_Ctrl[26].addr[0] = 136; - state->CH_Ctrl[26].bit[0] = 7; - state->CH_Ctrl[26].val[0] = 0; - - state->CH_Ctrl[27].Ctrl_Num = GPIO_4B ; - state->CH_Ctrl[27].size = 1 ; - state->CH_Ctrl[27].addr[0] = 149; - state->CH_Ctrl[27].bit[0] = 7; - state->CH_Ctrl[27].val[0] = 0; - - state->CH_Ctrl[28].Ctrl_Num = GPIO_3B ; - state->CH_Ctrl[28].size = 1 ; - state->CH_Ctrl[28].addr[0] = 149; - state->CH_Ctrl[28].bit[0] = 6; - state->CH_Ctrl[28].val[0] = 0; - - state->CH_Ctrl[29].Ctrl_Num = GPIO_4 ; - state->CH_Ctrl[29].size = 1 ; - state->CH_Ctrl[29].addr[0] = 149; - state->CH_Ctrl[29].bit[0] = 5; - state->CH_Ctrl[29].val[0] = 1; - - state->CH_Ctrl[30].Ctrl_Num = GPIO_3 ; - state->CH_Ctrl[30].size = 1 ; - state->CH_Ctrl[30].addr[0] = 149; - state->CH_Ctrl[30].bit[0] = 4; - state->CH_Ctrl[30].val[0] = 1; - - state->CH_Ctrl[31].Ctrl_Num = GPIO_1B ; - state->CH_Ctrl[31].size = 1 ; - state->CH_Ctrl[31].addr[0] = 149; - state->CH_Ctrl[31].bit[0] = 3; - state->CH_Ctrl[31].val[0] = 0; - - state->CH_Ctrl[32].Ctrl_Num = DAC_A_ENABLE ; - state->CH_Ctrl[32].size = 1 ; - state->CH_Ctrl[32].addr[0] = 93; - state->CH_Ctrl[32].bit[0] = 1; - state->CH_Ctrl[32].val[0] = 0; - - state->CH_Ctrl[33].Ctrl_Num = DAC_B_ENABLE ; - state->CH_Ctrl[33].size = 1 ; - state->CH_Ctrl[33].addr[0] = 93; - state->CH_Ctrl[33].bit[0] = 0; - state->CH_Ctrl[33].val[0] = 0; - - state->CH_Ctrl[34].Ctrl_Num = DAC_DIN_A ; - state->CH_Ctrl[34].size = 6 ; - state->CH_Ctrl[34].addr[0] = 92; - state->CH_Ctrl[34].bit[0] = 2; - state->CH_Ctrl[34].val[0] = 0; - state->CH_Ctrl[34].addr[1] = 92; - state->CH_Ctrl[34].bit[1] = 3; - state->CH_Ctrl[34].val[1] = 0; - state->CH_Ctrl[34].addr[2] = 92; - state->CH_Ctrl[34].bit[2] = 4; - state->CH_Ctrl[34].val[2] = 0; - state->CH_Ctrl[34].addr[3] = 92; - state->CH_Ctrl[34].bit[3] = 5; - state->CH_Ctrl[34].val[3] = 0; - state->CH_Ctrl[34].addr[4] = 92; - state->CH_Ctrl[34].bit[4] = 6; - state->CH_Ctrl[34].val[4] = 0; - state->CH_Ctrl[34].addr[5] = 92; - state->CH_Ctrl[34].bit[5] = 7; - state->CH_Ctrl[34].val[5] = 0; - - state->CH_Ctrl[35].Ctrl_Num = DAC_DIN_B ; - state->CH_Ctrl[35].size = 6 ; - state->CH_Ctrl[35].addr[0] = 93; - state->CH_Ctrl[35].bit[0] = 2; - state->CH_Ctrl[35].val[0] = 0; - state->CH_Ctrl[35].addr[1] = 93; - state->CH_Ctrl[35].bit[1] = 3; - state->CH_Ctrl[35].val[1] = 0; - state->CH_Ctrl[35].addr[2] = 93; - state->CH_Ctrl[35].bit[2] = 4; - state->CH_Ctrl[35].val[2] = 0; - state->CH_Ctrl[35].addr[3] = 93; - state->CH_Ctrl[35].bit[3] = 5; - state->CH_Ctrl[35].val[3] = 0; - state->CH_Ctrl[35].addr[4] = 93; - state->CH_Ctrl[35].bit[4] = 6; - state->CH_Ctrl[35].val[4] = 0; - state->CH_Ctrl[35].addr[5] = 93; - state->CH_Ctrl[35].bit[5] = 7; - state->CH_Ctrl[35].val[5] = 0; - -#ifdef _MXL_PRODUCTION - state->CH_Ctrl[36].Ctrl_Num = RFSYN_EN_DIV ; - state->CH_Ctrl[36].size = 1 ; - state->CH_Ctrl[36].addr[0] = 109; - state->CH_Ctrl[36].bit[0] = 1; - state->CH_Ctrl[36].val[0] = 1; - - state->CH_Ctrl[37].Ctrl_Num = RFSYN_DIVM ; - state->CH_Ctrl[37].size = 2 ; - state->CH_Ctrl[37].addr[0] = 112; - state->CH_Ctrl[37].bit[0] = 5; - state->CH_Ctrl[37].val[0] = 0; - state->CH_Ctrl[37].addr[1] = 112; - state->CH_Ctrl[37].bit[1] = 6; - state->CH_Ctrl[37].val[1] = 0; - - state->CH_Ctrl[38].Ctrl_Num = DN_BYPASS_AGC_I2C ; - state->CH_Ctrl[38].size = 1 ; - state->CH_Ctrl[38].addr[0] = 65; - state->CH_Ctrl[38].bit[0] = 1; - state->CH_Ctrl[38].val[0] = 0; -#endif - - return 0 ; -} - -static void InitTunerControls(struct dvb_frontend *fe) -{ - MXL5005_RegisterInit(fe); - MXL5005_ControlInit(fe); -#ifdef _MXL_INTERNAL - MXL5005_MXLControlInit(fe); -#endif -} - -static u16 MXL5005_TunerConfig(struct dvb_frontend *fe, - u8 Mode, /* 0: Analog Mode ; 1: Digital Mode */ - u8 IF_mode, /* for Analog Mode, 0: zero IF; 1: low IF */ - u32 Bandwidth, /* filter channel bandwidth (6, 7, 8) */ - u32 IF_out, /* Desired IF Out Frequency */ - u32 Fxtal, /* XTAL Frequency */ - u8 AGC_Mode, /* AGC Mode - Dual AGC: 0, Single AGC: 1 */ - u16 TOP, /* 0: Dual AGC; Value: take over point */ - u16 IF_OUT_LOAD, /* IF Out Load Resistor (200 / 300 Ohms) */ - u8 CLOCK_OUT, /* 0: turn off clk out; 1: turn on clock out */ - u8 DIV_OUT, /* 0: Div-1; 1: Div-4 */ - u8 CAPSELECT, /* 0: disable On-Chip pulling cap; 1: enable */ - u8 EN_RSSI, /* 0: disable RSSI; 1: enable RSSI */ - - /* Modulation Type; */ - /* 0 - Default; 1 - DVB-T; 2 - ATSC; 3 - QAM; 4 - Analog Cable */ - u8 Mod_Type, - - /* Tracking Filter */ - /* 0 - Default; 1 - Off; 2 - Type C; 3 - Type C-H */ - u8 TF_Type - ) -{ - struct mxl5005s_state *state = fe->tuner_priv; - u16 status = 0; - - state->Mode = Mode; - state->IF_Mode = IF_mode; - state->Chan_Bandwidth = Bandwidth; - state->IF_OUT = IF_out; - state->Fxtal = Fxtal; - state->AGC_Mode = AGC_Mode; - state->TOP = TOP; - state->IF_OUT_LOAD = IF_OUT_LOAD; - state->CLOCK_OUT = CLOCK_OUT; - state->DIV_OUT = DIV_OUT; - state->CAPSELECT = CAPSELECT; - state->EN_RSSI = EN_RSSI; - state->Mod_Type = Mod_Type; - state->TF_Type = TF_Type; - - /* Initialize all the controls and registers */ - InitTunerControls(fe); - - /* Synthesizer LO frequency calculation */ - MXL_SynthIFLO_Calc(fe); - - return status; -} - -static void MXL_SynthIFLO_Calc(struct dvb_frontend *fe) -{ - struct mxl5005s_state *state = fe->tuner_priv; - if (state->Mode == 1) /* Digital Mode */ - state->IF_LO = state->IF_OUT; - else /* Analog Mode */ { - if (state->IF_Mode == 0) /* Analog Zero IF mode */ - state->IF_LO = state->IF_OUT + 400000; - else /* Analog Low IF mode */ - state->IF_LO = state->IF_OUT + state->Chan_Bandwidth/2; - } -} - -static void MXL_SynthRFTGLO_Calc(struct dvb_frontend *fe) -{ - struct mxl5005s_state *state = fe->tuner_priv; - - if (state->Mode == 1) /* Digital Mode */ { - /* remove 20.48MHz setting for 2.6.10 */ - state->RF_LO = state->RF_IN; - /* change for 2.6.6 */ - state->TG_LO = state->RF_IN - 750000; - } else /* Analog Mode */ { - if (state->IF_Mode == 0) /* Analog Zero IF mode */ { - state->RF_LO = state->RF_IN - 400000; - state->TG_LO = state->RF_IN - 1750000; - } else /* Analog Low IF mode */ { - state->RF_LO = state->RF_IN - state->Chan_Bandwidth/2; - state->TG_LO = state->RF_IN - - state->Chan_Bandwidth + 500000; - } - } -} - -static u16 MXL_OverwriteICDefault(struct dvb_frontend *fe) -{ - u16 status = 0; - - status += MXL_ControlWrite(fe, OVERRIDE_1, 1); - status += MXL_ControlWrite(fe, OVERRIDE_2, 1); - status += MXL_ControlWrite(fe, OVERRIDE_3, 1); - status += MXL_ControlWrite(fe, OVERRIDE_4, 1); - - return status; -} - -static u16 MXL_BlockInit(struct dvb_frontend *fe) -{ - struct mxl5005s_state *state = fe->tuner_priv; - u16 status = 0; - - status += MXL_OverwriteICDefault(fe); - - /* Downconverter Control Dig Ana */ - status += MXL_ControlWrite(fe, DN_IQTN_AMP_CUT, state->Mode ? 1 : 0); - - /* Filter Control Dig Ana */ - status += MXL_ControlWrite(fe, BB_MODE, state->Mode ? 0 : 1); - status += MXL_ControlWrite(fe, BB_BUF, state->Mode ? 3 : 2); - status += MXL_ControlWrite(fe, BB_BUF_OA, state->Mode ? 1 : 0); - status += MXL_ControlWrite(fe, BB_IQSWAP, state->Mode ? 0 : 1); - status += MXL_ControlWrite(fe, BB_INITSTATE_DLPF_TUNE, 0); - - /* Initialize Low-Pass Filter */ - if (state->Mode) { /* Digital Mode */ - switch (state->Chan_Bandwidth) { - case 8000000: - status += MXL_ControlWrite(fe, BB_DLPF_BANDSEL, 0); - break; - case 7000000: - status += MXL_ControlWrite(fe, BB_DLPF_BANDSEL, 2); - break; - case 6000000: - status += MXL_ControlWrite(fe, - BB_DLPF_BANDSEL, 3); - break; - } - } else { /* Analog Mode */ - switch (state->Chan_Bandwidth) { - case 8000000: /* Low Zero */ - status += MXL_ControlWrite(fe, BB_ALPF_BANDSELECT, - (state->IF_Mode ? 0 : 3)); - break; - case 7000000: - status += MXL_ControlWrite(fe, BB_ALPF_BANDSELECT, - (state->IF_Mode ? 1 : 4)); - break; - case 6000000: - status += MXL_ControlWrite(fe, BB_ALPF_BANDSELECT, - (state->IF_Mode ? 2 : 5)); - break; - } - } - - /* Charge Pump Control Dig Ana */ - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, state->Mode ? 5 : 8); - status += MXL_ControlWrite(fe, - RFSYN_EN_CHP_HIGAIN, state->Mode ? 1 : 1); - status += MXL_ControlWrite(fe, EN_CHP_LIN_B, state->Mode ? 0 : 0); - - /* AGC TOP Control */ - if (state->AGC_Mode == 0) /* Dual AGC */ { - status += MXL_ControlWrite(fe, AGC_IF, 15); - status += MXL_ControlWrite(fe, AGC_RF, 15); - } else /* Single AGC Mode Dig Ana */ - status += MXL_ControlWrite(fe, AGC_RF, state->Mode ? 15 : 12); - - if (state->TOP == 55) /* TOP == 5.5 */ - status += MXL_ControlWrite(fe, AGC_IF, 0x0); - - if (state->TOP == 72) /* TOP == 7.2 */ - status += MXL_ControlWrite(fe, AGC_IF, 0x1); - - if (state->TOP == 92) /* TOP == 9.2 */ - status += MXL_ControlWrite(fe, AGC_IF, 0x2); - - if (state->TOP == 110) /* TOP == 11.0 */ - status += MXL_ControlWrite(fe, AGC_IF, 0x3); - - if (state->TOP == 129) /* TOP == 12.9 */ - status += MXL_ControlWrite(fe, AGC_IF, 0x4); - - if (state->TOP == 147) /* TOP == 14.7 */ - status += MXL_ControlWrite(fe, AGC_IF, 0x5); - - if (state->TOP == 168) /* TOP == 16.8 */ - status += MXL_ControlWrite(fe, AGC_IF, 0x6); - - if (state->TOP == 194) /* TOP == 19.4 */ - status += MXL_ControlWrite(fe, AGC_IF, 0x7); - - if (state->TOP == 212) /* TOP == 21.2 */ - status += MXL_ControlWrite(fe, AGC_IF, 0x9); - - if (state->TOP == 232) /* TOP == 23.2 */ - status += MXL_ControlWrite(fe, AGC_IF, 0xA); - - if (state->TOP == 252) /* TOP == 25.2 */ - status += MXL_ControlWrite(fe, AGC_IF, 0xB); - - if (state->TOP == 271) /* TOP == 27.1 */ - status += MXL_ControlWrite(fe, AGC_IF, 0xC); - - if (state->TOP == 292) /* TOP == 29.2 */ - status += MXL_ControlWrite(fe, AGC_IF, 0xD); - - if (state->TOP == 317) /* TOP == 31.7 */ - status += MXL_ControlWrite(fe, AGC_IF, 0xE); - - if (state->TOP == 349) /* TOP == 34.9 */ - status += MXL_ControlWrite(fe, AGC_IF, 0xF); - - /* IF Synthesizer Control */ - status += MXL_IFSynthInit(fe); - - /* IF UpConverter Control */ - if (state->IF_OUT_LOAD == 200) { - status += MXL_ControlWrite(fe, DRV_RES_SEL, 6); - status += MXL_ControlWrite(fe, I_DRIVER, 2); - } - if (state->IF_OUT_LOAD == 300) { - status += MXL_ControlWrite(fe, DRV_RES_SEL, 4); - status += MXL_ControlWrite(fe, I_DRIVER, 1); - } - - /* Anti-Alias Filtering Control - * initialise Anti-Aliasing Filter - */ - if (state->Mode) { /* Digital Mode */ - if (state->IF_OUT >= 4000000UL && state->IF_OUT <= 6280000UL) { - status += MXL_ControlWrite(fe, EN_AAF, 1); - status += MXL_ControlWrite(fe, EN_3P, 1); - status += MXL_ControlWrite(fe, EN_AUX_3P, 1); - status += MXL_ControlWrite(fe, SEL_AAF_BAND, 0); - } - if ((state->IF_OUT == 36125000UL) || - (state->IF_OUT == 36150000UL)) { - status += MXL_ControlWrite(fe, EN_AAF, 1); - status += MXL_ControlWrite(fe, EN_3P, 1); - status += MXL_ControlWrite(fe, EN_AUX_3P, 1); - status += MXL_ControlWrite(fe, SEL_AAF_BAND, 1); - } - if (state->IF_OUT > 36150000UL) { - status += MXL_ControlWrite(fe, EN_AAF, 0); - status += MXL_ControlWrite(fe, EN_3P, 1); - status += MXL_ControlWrite(fe, EN_AUX_3P, 1); - status += MXL_ControlWrite(fe, SEL_AAF_BAND, 1); - } - } else { /* Analog Mode */ - if (state->IF_OUT >= 4000000UL && state->IF_OUT <= 5000000UL) { - status += MXL_ControlWrite(fe, EN_AAF, 1); - status += MXL_ControlWrite(fe, EN_3P, 1); - status += MXL_ControlWrite(fe, EN_AUX_3P, 1); - status += MXL_ControlWrite(fe, SEL_AAF_BAND, 0); - } - if (state->IF_OUT > 5000000UL) { - status += MXL_ControlWrite(fe, EN_AAF, 0); - status += MXL_ControlWrite(fe, EN_3P, 0); - status += MXL_ControlWrite(fe, EN_AUX_3P, 0); - status += MXL_ControlWrite(fe, SEL_AAF_BAND, 0); - } - } - - /* Demod Clock Out */ - if (state->CLOCK_OUT) - status += MXL_ControlWrite(fe, SEQ_ENCLK16_CLK_OUT, 1); - else - status += MXL_ControlWrite(fe, SEQ_ENCLK16_CLK_OUT, 0); - - if (state->DIV_OUT == 1) - status += MXL_ControlWrite(fe, SEQ_SEL4_16B, 1); - if (state->DIV_OUT == 0) - status += MXL_ControlWrite(fe, SEQ_SEL4_16B, 0); - - /* Crystal Control */ - if (state->CAPSELECT) - status += MXL_ControlWrite(fe, XTAL_CAPSELECT, 1); - else - status += MXL_ControlWrite(fe, XTAL_CAPSELECT, 0); - - if (state->Fxtal >= 12000000UL && state->Fxtal <= 16000000UL) - status += MXL_ControlWrite(fe, IF_SEL_DBL, 1); - if (state->Fxtal > 16000000UL && state->Fxtal <= 32000000UL) - status += MXL_ControlWrite(fe, IF_SEL_DBL, 0); - - if (state->Fxtal >= 12000000UL && state->Fxtal <= 22000000UL) - status += MXL_ControlWrite(fe, RFSYN_R_DIV, 3); - if (state->Fxtal > 22000000UL && state->Fxtal <= 32000000UL) - status += MXL_ControlWrite(fe, RFSYN_R_DIV, 0); - - /* Misc Controls */ - if (state->Mode == 0 && state->IF_Mode == 1) /* Analog LowIF mode */ - status += MXL_ControlWrite(fe, SEQ_EXTIQFSMPULSE, 0); - else - status += MXL_ControlWrite(fe, SEQ_EXTIQFSMPULSE, 1); - - /* status += MXL_ControlRead(fe, IF_DIVVAL, &IF_DIVVAL_Val); */ - - /* Set TG_R_DIV */ - status += MXL_ControlWrite(fe, TG_R_DIV, - MXL_Ceiling(state->Fxtal, 1000000)); - - /* Apply Default value to BB_INITSTATE_DLPF_TUNE */ - - /* RSSI Control */ - if (state->EN_RSSI) { - status += MXL_ControlWrite(fe, SEQ_EXTSYNTHCALIF, 1); - status += MXL_ControlWrite(fe, SEQ_EXTDCCAL, 1); - status += MXL_ControlWrite(fe, AGC_EN_RSSI, 1); - status += MXL_ControlWrite(fe, RFA_ENCLKRFAGC, 1); - - /* RSSI reference point */ - status += MXL_ControlWrite(fe, RFA_RSSI_REF, 2); - status += MXL_ControlWrite(fe, RFA_RSSI_REFH, 3); - status += MXL_ControlWrite(fe, RFA_RSSI_REFL, 1); - - /* TOP point */ - status += MXL_ControlWrite(fe, RFA_FLR, 0); - status += MXL_ControlWrite(fe, RFA_CEIL, 12); - } - - /* Modulation type bit settings - * Override the control values preset - */ - if (state->Mod_Type == MXL_DVBT) /* DVB-T Mode */ { - state->AGC_Mode = 1; /* Single AGC Mode */ - - /* Enable RSSI */ - status += MXL_ControlWrite(fe, SEQ_EXTSYNTHCALIF, 1); - status += MXL_ControlWrite(fe, SEQ_EXTDCCAL, 1); - status += MXL_ControlWrite(fe, AGC_EN_RSSI, 1); - status += MXL_ControlWrite(fe, RFA_ENCLKRFAGC, 1); - - /* RSSI reference point */ - status += MXL_ControlWrite(fe, RFA_RSSI_REF, 3); - status += MXL_ControlWrite(fe, RFA_RSSI_REFH, 5); - status += MXL_ControlWrite(fe, RFA_RSSI_REFL, 1); - - /* TOP point */ - status += MXL_ControlWrite(fe, RFA_FLR, 2); - status += MXL_ControlWrite(fe, RFA_CEIL, 13); - if (state->IF_OUT <= 6280000UL) /* Low IF */ - status += MXL_ControlWrite(fe, BB_IQSWAP, 0); - else /* High IF */ - status += MXL_ControlWrite(fe, BB_IQSWAP, 1); - - } - if (state->Mod_Type == MXL_ATSC) /* ATSC Mode */ { - state->AGC_Mode = 1; /* Single AGC Mode */ - - /* Enable RSSI */ - status += MXL_ControlWrite(fe, SEQ_EXTSYNTHCALIF, 1); - status += MXL_ControlWrite(fe, SEQ_EXTDCCAL, 1); - status += MXL_ControlWrite(fe, AGC_EN_RSSI, 1); - status += MXL_ControlWrite(fe, RFA_ENCLKRFAGC, 1); - - /* RSSI reference point */ - status += MXL_ControlWrite(fe, RFA_RSSI_REF, 2); - status += MXL_ControlWrite(fe, RFA_RSSI_REFH, 4); - status += MXL_ControlWrite(fe, RFA_RSSI_REFL, 1); - - /* TOP point */ - status += MXL_ControlWrite(fe, RFA_FLR, 2); - status += MXL_ControlWrite(fe, RFA_CEIL, 13); - status += MXL_ControlWrite(fe, BB_INITSTATE_DLPF_TUNE, 1); - /* Low Zero */ - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, 5); - - if (state->IF_OUT <= 6280000UL) /* Low IF */ - status += MXL_ControlWrite(fe, BB_IQSWAP, 0); - else /* High IF */ - status += MXL_ControlWrite(fe, BB_IQSWAP, 1); - } - if (state->Mod_Type == MXL_QAM) /* QAM Mode */ { - state->Mode = MXL_DIGITAL_MODE; - - /* state->AGC_Mode = 1; */ /* Single AGC Mode */ - - /* Disable RSSI */ /* change here for v2.6.5 */ - status += MXL_ControlWrite(fe, SEQ_EXTSYNTHCALIF, 1); - status += MXL_ControlWrite(fe, SEQ_EXTDCCAL, 1); - status += MXL_ControlWrite(fe, AGC_EN_RSSI, 0); - status += MXL_ControlWrite(fe, RFA_ENCLKRFAGC, 1); - - /* RSSI reference point */ - status += MXL_ControlWrite(fe, RFA_RSSI_REFH, 5); - status += MXL_ControlWrite(fe, RFA_RSSI_REF, 3); - status += MXL_ControlWrite(fe, RFA_RSSI_REFL, 2); - /* change here for v2.6.5 */ - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, 3); - - if (state->IF_OUT <= 6280000UL) /* Low IF */ - status += MXL_ControlWrite(fe, BB_IQSWAP, 0); - else /* High IF */ - status += MXL_ControlWrite(fe, BB_IQSWAP, 1); - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, 2); - - } - if (state->Mod_Type == MXL_ANALOG_CABLE) { - /* Analog Cable Mode */ - /* state->Mode = MXL_DIGITAL_MODE; */ - - state->AGC_Mode = 1; /* Single AGC Mode */ - - /* Disable RSSI */ - status += MXL_ControlWrite(fe, SEQ_EXTSYNTHCALIF, 1); - status += MXL_ControlWrite(fe, SEQ_EXTDCCAL, 1); - status += MXL_ControlWrite(fe, AGC_EN_RSSI, 0); - status += MXL_ControlWrite(fe, RFA_ENCLKRFAGC, 1); - /* change for 2.6.3 */ - status += MXL_ControlWrite(fe, AGC_IF, 1); - status += MXL_ControlWrite(fe, AGC_RF, 15); - status += MXL_ControlWrite(fe, BB_IQSWAP, 1); - } - - if (state->Mod_Type == MXL_ANALOG_OTA) { - /* Analog OTA Terrestrial mode add for 2.6.7 */ - /* state->Mode = MXL_ANALOG_MODE; */ - - /* Enable RSSI */ - status += MXL_ControlWrite(fe, SEQ_EXTSYNTHCALIF, 1); - status += MXL_ControlWrite(fe, SEQ_EXTDCCAL, 1); - status += MXL_ControlWrite(fe, AGC_EN_RSSI, 1); - status += MXL_ControlWrite(fe, RFA_ENCLKRFAGC, 1); - - /* RSSI reference point */ - status += MXL_ControlWrite(fe, RFA_RSSI_REFH, 5); - status += MXL_ControlWrite(fe, RFA_RSSI_REF, 3); - status += MXL_ControlWrite(fe, RFA_RSSI_REFL, 2); - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, 3); - status += MXL_ControlWrite(fe, BB_IQSWAP, 1); - } - - /* RSSI disable */ - if (state->EN_RSSI == 0) { - status += MXL_ControlWrite(fe, SEQ_EXTSYNTHCALIF, 1); - status += MXL_ControlWrite(fe, SEQ_EXTDCCAL, 1); - status += MXL_ControlWrite(fe, AGC_EN_RSSI, 0); - status += MXL_ControlWrite(fe, RFA_ENCLKRFAGC, 1); - } - - return status; -} - -static u16 MXL_IFSynthInit(struct dvb_frontend *fe) -{ - struct mxl5005s_state *state = fe->tuner_priv; - u16 status = 0 ; - u32 Fref = 0 ; - u32 Kdbl, intModVal ; - u32 fracModVal ; - Kdbl = 2 ; - - if (state->Fxtal >= 12000000UL && state->Fxtal <= 16000000UL) - Kdbl = 2 ; - if (state->Fxtal > 16000000UL && state->Fxtal <= 32000000UL) - Kdbl = 1 ; - - /* IF Synthesizer Control */ - if (state->Mode == 0 && state->IF_Mode == 1) /* Analog Low IF mode */ { - if (state->IF_LO == 41000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x0C); - Fref = 328000000UL ; - } - if (state->IF_LO == 47000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 376000000UL ; - } - if (state->IF_LO == 54000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x10); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x0C); - Fref = 324000000UL ; - } - if (state->IF_LO == 60000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x10); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 360000000UL ; - } - if (state->IF_LO == 39250000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x0C); - Fref = 314000000UL ; - } - if (state->IF_LO == 39650000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x0C); - Fref = 317200000UL ; - } - if (state->IF_LO == 40150000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x0C); - Fref = 321200000UL ; - } - if (state->IF_LO == 40650000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x0C); - Fref = 325200000UL ; - } - } - - if (state->Mode || (state->Mode == 0 && state->IF_Mode == 0)) { - if (state->IF_LO == 57000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x10); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 342000000UL ; - } - if (state->IF_LO == 44000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 352000000UL ; - } - if (state->IF_LO == 43750000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 350000000UL ; - } - if (state->IF_LO == 36650000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x04); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 366500000UL ; - } - if (state->IF_LO == 36150000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x04); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 361500000UL ; - } - if (state->IF_LO == 36000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x04); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 360000000UL ; - } - if (state->IF_LO == 35250000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x04); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 352500000UL ; - } - if (state->IF_LO == 34750000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x04); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 347500000UL ; - } - if (state->IF_LO == 6280000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x07); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 376800000UL ; - } - if (state->IF_LO == 5000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x09); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 360000000UL ; - } - if (state->IF_LO == 4500000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x06); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 360000000UL ; - } - if (state->IF_LO == 4570000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x06); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 365600000UL ; - } - if (state->IF_LO == 4000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x05); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 360000000UL ; - } - if (state->IF_LO == 57400000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x10); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 344400000UL ; - } - if (state->IF_LO == 44400000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 355200000UL ; - } - if (state->IF_LO == 44150000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x08); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 353200000UL ; - } - if (state->IF_LO == 37050000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x04); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 370500000UL ; - } - if (state->IF_LO == 36550000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x04); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 365500000UL ; - } - if (state->IF_LO == 36125000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x04); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 361250000UL ; - } - if (state->IF_LO == 6000000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x07); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 360000000UL ; - } - if (state->IF_LO == 5400000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x07); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x0C); - Fref = 324000000UL ; - } - if (state->IF_LO == 5380000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x07); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x0C); - Fref = 322800000UL ; - } - if (state->IF_LO == 5200000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x09); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 374400000UL ; - } - if (state->IF_LO == 4900000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x09); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 352800000UL ; - } - if (state->IF_LO == 4400000UL) { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x06); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 352000000UL ; - } - if (state->IF_LO == 4063000UL) /* add for 2.6.8 */ { - status += MXL_ControlWrite(fe, IF_DIVVAL, 0x05); - status += MXL_ControlWrite(fe, IF_VCO_BIAS, 0x08); - Fref = 365670000UL ; - } - } - /* CHCAL_INT_MOD_IF */ - /* CHCAL_FRAC_MOD_IF */ - intModVal = Fref / (state->Fxtal * Kdbl/2); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_IF, intModVal); - - fracModVal = (2<<15)*(Fref/1000 - (state->Fxtal/1000 * Kdbl/2) * - intModVal); - - fracModVal = fracModVal / ((state->Fxtal * Kdbl/2)/1000); - status += MXL_ControlWrite(fe, CHCAL_FRAC_MOD_IF, fracModVal); - - return status ; -} - -static u16 MXL_TuneRF(struct dvb_frontend *fe, u32 RF_Freq) -{ - struct mxl5005s_state *state = fe->tuner_priv; - u16 status = 0; - u32 divider_val, E3, E4, E5, E5A; - u32 Fmax, Fmin, FmaxBin, FminBin; - u32 Kdbl_RF = 2; - u32 tg_divval; - u32 tg_lo; - - u32 Fref_TG; - u32 Fvco; - - state->RF_IN = RF_Freq; - - MXL_SynthRFTGLO_Calc(fe); - - if (state->Fxtal >= 12000000UL && state->Fxtal <= 22000000UL) - Kdbl_RF = 2; - if (state->Fxtal > 22000000 && state->Fxtal <= 32000000) - Kdbl_RF = 1; - - /* Downconverter Controls - * Look-Up Table Implementation for: - * DN_POLY - * DN_RFGAIN - * DN_CAP_RFLPF - * DN_EN_VHFUHFBAR - * DN_GAIN_ADJUST - * Change the boundary reference from RF_IN to RF_LO - */ - if (state->RF_LO < 40000000UL) - return -1; - - if (state->RF_LO >= 40000000UL && state->RF_LO <= 75000000UL) { - status += MXL_ControlWrite(fe, DN_POLY, 2); - status += MXL_ControlWrite(fe, DN_RFGAIN, 3); - status += MXL_ControlWrite(fe, DN_CAP_RFLPF, 423); - status += MXL_ControlWrite(fe, DN_EN_VHFUHFBAR, 1); - status += MXL_ControlWrite(fe, DN_GAIN_ADJUST, 1); - } - if (state->RF_LO > 75000000UL && state->RF_LO <= 100000000UL) { - status += MXL_ControlWrite(fe, DN_POLY, 3); - status += MXL_ControlWrite(fe, DN_RFGAIN, 3); - status += MXL_ControlWrite(fe, DN_CAP_RFLPF, 222); - status += MXL_ControlWrite(fe, DN_EN_VHFUHFBAR, 1); - status += MXL_ControlWrite(fe, DN_GAIN_ADJUST, 1); - } - if (state->RF_LO > 100000000UL && state->RF_LO <= 150000000UL) { - status += MXL_ControlWrite(fe, DN_POLY, 3); - status += MXL_ControlWrite(fe, DN_RFGAIN, 3); - status += MXL_ControlWrite(fe, DN_CAP_RFLPF, 147); - status += MXL_ControlWrite(fe, DN_EN_VHFUHFBAR, 1); - status += MXL_ControlWrite(fe, DN_GAIN_ADJUST, 2); - } - if (state->RF_LO > 150000000UL && state->RF_LO <= 200000000UL) { - status += MXL_ControlWrite(fe, DN_POLY, 3); - status += MXL_ControlWrite(fe, DN_RFGAIN, 3); - status += MXL_ControlWrite(fe, DN_CAP_RFLPF, 9); - status += MXL_ControlWrite(fe, DN_EN_VHFUHFBAR, 1); - status += MXL_ControlWrite(fe, DN_GAIN_ADJUST, 2); - } - if (state->RF_LO > 200000000UL && state->RF_LO <= 300000000UL) { - status += MXL_ControlWrite(fe, DN_POLY, 3); - status += MXL_ControlWrite(fe, DN_RFGAIN, 3); - status += MXL_ControlWrite(fe, DN_CAP_RFLPF, 0); - status += MXL_ControlWrite(fe, DN_EN_VHFUHFBAR, 1); - status += MXL_ControlWrite(fe, DN_GAIN_ADJUST, 3); - } - if (state->RF_LO > 300000000UL && state->RF_LO <= 650000000UL) { - status += MXL_ControlWrite(fe, DN_POLY, 3); - status += MXL_ControlWrite(fe, DN_RFGAIN, 1); - status += MXL_ControlWrite(fe, DN_CAP_RFLPF, 0); - status += MXL_ControlWrite(fe, DN_EN_VHFUHFBAR, 0); - status += MXL_ControlWrite(fe, DN_GAIN_ADJUST, 3); - } - if (state->RF_LO > 650000000UL && state->RF_LO <= 900000000UL) { - status += MXL_ControlWrite(fe, DN_POLY, 3); - status += MXL_ControlWrite(fe, DN_RFGAIN, 2); - status += MXL_ControlWrite(fe, DN_CAP_RFLPF, 0); - status += MXL_ControlWrite(fe, DN_EN_VHFUHFBAR, 0); - status += MXL_ControlWrite(fe, DN_GAIN_ADJUST, 3); - } - if (state->RF_LO > 900000000UL) - return -1; - - /* DN_IQTNBUF_AMP */ - /* DN_IQTNGNBFBIAS_BST */ - if (state->RF_LO >= 40000000UL && state->RF_LO <= 75000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 75000000UL && state->RF_LO <= 100000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 100000000UL && state->RF_LO <= 150000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 150000000UL && state->RF_LO <= 200000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 200000000UL && state->RF_LO <= 300000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 300000000UL && state->RF_LO <= 400000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 400000000UL && state->RF_LO <= 450000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 450000000UL && state->RF_LO <= 500000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 500000000UL && state->RF_LO <= 550000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 550000000UL && state->RF_LO <= 600000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 600000000UL && state->RF_LO <= 650000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 650000000UL && state->RF_LO <= 700000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 700000000UL && state->RF_LO <= 750000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 750000000UL && state->RF_LO <= 800000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 1); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 0); - } - if (state->RF_LO > 800000000UL && state->RF_LO <= 850000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 10); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 1); - } - if (state->RF_LO > 850000000UL && state->RF_LO <= 900000000UL) { - status += MXL_ControlWrite(fe, DN_IQTNBUF_AMP, 10); - status += MXL_ControlWrite(fe, DN_IQTNGNBFBIAS_BST, 1); - } - - /* - * Set RF Synth and LO Path Control - * - * Look-Up table implementation for: - * RFSYN_EN_OUTMUX - * RFSYN_SEL_VCO_OUT - * RFSYN_SEL_VCO_HI - * RFSYN_SEL_DIVM - * RFSYN_RF_DIV_BIAS - * DN_SEL_FREQ - * - * Set divider_val, Fmax, Fmix to use in Equations - */ - FminBin = 28000000UL ; - FmaxBin = 42500000UL ; - if (state->RF_LO >= 40000000UL && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 1); - divider_val = 64 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 42500000UL ; - FmaxBin = 56000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 1); - divider_val = 64 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 56000000UL ; - FmaxBin = 85000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 1); - divider_val = 32 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 85000000UL ; - FmaxBin = 112000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 1); - divider_val = 32 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 112000000UL ; - FmaxBin = 170000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 2); - divider_val = 16 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 170000000UL ; - FmaxBin = 225000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 2); - divider_val = 16 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 225000000UL ; - FmaxBin = 300000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 4); - divider_val = 8 ; - Fmax = 340000000UL ; - Fmin = FminBin ; - } - FminBin = 300000000UL ; - FmaxBin = 340000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 0); - divider_val = 8 ; - Fmax = FmaxBin ; - Fmin = 225000000UL ; - } - FminBin = 340000000UL ; - FmaxBin = 450000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 2); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 0); - divider_val = 8 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 450000000UL ; - FmaxBin = 680000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 1); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 0); - divider_val = 4 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 680000000UL ; - FmaxBin = 900000000UL ; - if (state->RF_LO > FminBin && state->RF_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 1); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 0); - divider_val = 4 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - - /* CHCAL_INT_MOD_RF - * CHCAL_FRAC_MOD_RF - * RFSYN_LPF_R - * CHCAL_EN_INT_RF - */ - /* Equation E3 RFSYN_VCO_BIAS */ - E3 = (((Fmax-state->RF_LO)/1000)*32)/((Fmax-Fmin)/1000) + 8 ; - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, E3); - - /* Equation E4 CHCAL_INT_MOD_RF */ - E4 = (state->RF_LO*divider_val/1000)/(2*state->Fxtal*Kdbl_RF/1000); - MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, E4); - - /* Equation E5 CHCAL_FRAC_MOD_RF CHCAL_EN_INT_RF */ - E5 = ((2<<17)*(state->RF_LO/10000*divider_val - - (E4*(2*state->Fxtal*Kdbl_RF)/10000))) / - (2*state->Fxtal*Kdbl_RF/10000); - - status += MXL_ControlWrite(fe, CHCAL_FRAC_MOD_RF, E5); - - /* Equation E5A RFSYN_LPF_R */ - E5A = (((Fmax - state->RF_LO)/1000)*4/((Fmax-Fmin)/1000)) + 1 ; - status += MXL_ControlWrite(fe, RFSYN_LPF_R, E5A); - - /* Euqation E5B CHCAL_EN_INIT_RF */ - status += MXL_ControlWrite(fe, CHCAL_EN_INT_RF, ((E5 == 0) ? 1 : 0)); - /*if (E5 == 0) - * status += MXL_ControlWrite(fe, CHCAL_EN_INT_RF, 1); - *else - * status += MXL_ControlWrite(fe, CHCAL_FRAC_MOD_RF, E5); - */ - - /* - * Set TG Synth - * - * Look-Up table implementation for: - * TG_LO_DIVVAL - * TG_LO_SELVAL - * - * Set divider_val, Fmax, Fmix to use in Equations - */ - if (state->TG_LO < 33000000UL) - return -1; - - FminBin = 33000000UL ; - FmaxBin = 50000000UL ; - if (state->TG_LO >= FminBin && state->TG_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, TG_LO_DIVVAL, 0x6); - status += MXL_ControlWrite(fe, TG_LO_SELVAL, 0x0); - divider_val = 36 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 50000000UL ; - FmaxBin = 67000000UL ; - if (state->TG_LO > FminBin && state->TG_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, TG_LO_DIVVAL, 0x1); - status += MXL_ControlWrite(fe, TG_LO_SELVAL, 0x0); - divider_val = 24 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 67000000UL ; - FmaxBin = 100000000UL ; - if (state->TG_LO > FminBin && state->TG_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, TG_LO_DIVVAL, 0xC); - status += MXL_ControlWrite(fe, TG_LO_SELVAL, 0x2); - divider_val = 18 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 100000000UL ; - FmaxBin = 150000000UL ; - if (state->TG_LO > FminBin && state->TG_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, TG_LO_DIVVAL, 0x8); - status += MXL_ControlWrite(fe, TG_LO_SELVAL, 0x2); - divider_val = 12 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 150000000UL ; - FmaxBin = 200000000UL ; - if (state->TG_LO > FminBin && state->TG_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, TG_LO_DIVVAL, 0x0); - status += MXL_ControlWrite(fe, TG_LO_SELVAL, 0x2); - divider_val = 8 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 200000000UL ; - FmaxBin = 300000000UL ; - if (state->TG_LO > FminBin && state->TG_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, TG_LO_DIVVAL, 0x8); - status += MXL_ControlWrite(fe, TG_LO_SELVAL, 0x3); - divider_val = 6 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 300000000UL ; - FmaxBin = 400000000UL ; - if (state->TG_LO > FminBin && state->TG_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, TG_LO_DIVVAL, 0x0); - status += MXL_ControlWrite(fe, TG_LO_SELVAL, 0x3); - divider_val = 4 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 400000000UL ; - FmaxBin = 600000000UL ; - if (state->TG_LO > FminBin && state->TG_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, TG_LO_DIVVAL, 0x8); - status += MXL_ControlWrite(fe, TG_LO_SELVAL, 0x7); - divider_val = 3 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - FminBin = 600000000UL ; - FmaxBin = 900000000UL ; - if (state->TG_LO > FminBin && state->TG_LO <= FmaxBin) { - status += MXL_ControlWrite(fe, TG_LO_DIVVAL, 0x0); - status += MXL_ControlWrite(fe, TG_LO_SELVAL, 0x7); - divider_val = 2 ; - Fmax = FmaxBin ; - Fmin = FminBin ; - } - - /* TG_DIV_VAL */ - tg_divval = (state->TG_LO*divider_val/100000) * - (MXL_Ceiling(state->Fxtal, 1000000) * 100) / - (state->Fxtal/1000); - - status += MXL_ControlWrite(fe, TG_DIV_VAL, tg_divval); - - if (state->TG_LO > 600000000UL) - status += MXL_ControlWrite(fe, TG_DIV_VAL, tg_divval + 1); - - Fmax = 1800000000UL ; - Fmin = 1200000000UL ; - - /* prevent overflow of 32 bit unsigned integer, use - * following equation. Edit for v2.6.4 - */ - /* Fref_TF = Fref_TG * 1000 */ - Fref_TG = (state->Fxtal/1000) / MXL_Ceiling(state->Fxtal, 1000000); - - /* Fvco = Fvco/10 */ - Fvco = (state->TG_LO/10000) * divider_val * Fref_TG; - - tg_lo = (((Fmax/10 - Fvco)/100)*32) / ((Fmax-Fmin)/1000)+8; - - /* below equation is same as above but much harder to debug. - * - * static u32 MXL_GetXtalInt(u32 Xtal_Freq) - * { - * if ((Xtal_Freq % 1000000) == 0) - * return (Xtal_Freq / 10000); - * else - * return (((Xtal_Freq / 1000000) + 1)*100); - * } - * - * u32 Xtal_Int = MXL_GetXtalInt(state->Fxtal); - * tg_lo = ( ((Fmax/10000 * Xtal_Int)/100) - - * ((state->TG_LO/10000)*divider_val * - * (state->Fxtal/10000)/100) )*32/((Fmax-Fmin)/10000 * - * Xtal_Int/100) + 8; - */ - - status += MXL_ControlWrite(fe, TG_VCO_BIAS , tg_lo); - - /* add for 2.6.5 Special setting for QAM */ - if (state->Mod_Type == MXL_QAM) { - if (state->config->qam_gain != 0) - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, - state->config->qam_gain); - else if (state->RF_IN < 680000000) - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, 3); - else - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, 2); - } - - /* Off Chip Tracking Filter Control */ - if (state->TF_Type == MXL_TF_OFF) { - /* Tracking Filter Off State; turn off all the banks */ - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 3, 1); /* Bank1 Off */ - status += MXL_SetGPIO(fe, 1, 1); /* Bank2 Off */ - status += MXL_SetGPIO(fe, 4, 1); /* Bank3 Off */ - } - - if (state->TF_Type == MXL_TF_C) /* Tracking Filter type C */ { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_ControlWrite(fe, DAC_DIN_A, 0); - - if (state->RF_IN >= 43000000 && state->RF_IN < 150000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - status += MXL_SetGPIO(fe, 3, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 4, 1); - } - if (state->RF_IN >= 150000000 && state->RF_IN < 280000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 4, 1); - } - if (state->RF_IN >= 280000000 && state->RF_IN < 360000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 4, 0); - } - if (state->RF_IN >= 360000000 && state->RF_IN < 560000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 4, 0); - } - if (state->RF_IN >= 560000000 && state->RF_IN < 580000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_ControlWrite(fe, DAC_DIN_B, 29); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 4, 0); - } - if (state->RF_IN >= 580000000 && state->RF_IN < 630000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 4, 0); - } - if (state->RF_IN >= 630000000 && state->RF_IN < 700000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_ControlWrite(fe, DAC_DIN_B, 16); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 4, 1); - } - if (state->RF_IN >= 700000000 && state->RF_IN < 760000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_ControlWrite(fe, DAC_DIN_B, 7); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 4, 1); - } - if (state->RF_IN >= 760000000 && state->RF_IN <= 900000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 4, 1); - } - } - - if (state->TF_Type == MXL_TF_C_H) { - - /* Tracking Filter type C-H for Hauppauge only */ - status += MXL_ControlWrite(fe, DAC_DIN_A, 0); - - if (state->RF_IN >= 43000000 && state->RF_IN < 150000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - } - if (state->RF_IN >= 150000000 && state->RF_IN < 280000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 3, 0); - status += MXL_SetGPIO(fe, 1, 1); - } - if (state->RF_IN >= 280000000 && state->RF_IN < 360000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 3, 0); - status += MXL_SetGPIO(fe, 1, 0); - } - if (state->RF_IN >= 360000000 && state->RF_IN < 560000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 0); - } - if (state->RF_IN >= 560000000 && state->RF_IN < 580000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 0); - } - if (state->RF_IN >= 580000000 && state->RF_IN < 630000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 0); - } - if (state->RF_IN >= 630000000 && state->RF_IN < 700000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - } - if (state->RF_IN >= 700000000 && state->RF_IN < 760000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - } - if (state->RF_IN >= 760000000 && state->RF_IN <= 900000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - } - } - - if (state->TF_Type == MXL_TF_D) { /* Tracking Filter type D */ - - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - - if (state->RF_IN >= 43000000 && state->RF_IN < 174000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 174000000 && state->RF_IN < 250000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 250000000 && state->RF_IN < 310000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 310000000 && state->RF_IN < 360000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 360000000 && state->RF_IN < 470000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 470000000 && state->RF_IN < 640000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 640000000 && state->RF_IN <= 900000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - } - - if (state->TF_Type == MXL_TF_D_L) { - - /* Tracking Filter type D-L for Lumanate ONLY change 2.6.3 */ - status += MXL_ControlWrite(fe, DAC_DIN_A, 0); - - /* if UHF and terrestrial => Turn off Tracking Filter */ - if (state->RF_IN >= 471000000 && - (state->RF_IN - 471000000)%6000000 != 0) { - /* Turn off all the banks */ - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_ControlWrite(fe, AGC_IF, 10); - } else { - /* if VHF or cable => Turn on Tracking Filter */ - if (state->RF_IN >= 43000000 && - state->RF_IN < 140000000) { - - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 140000000 && - state->RF_IN < 240000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 240000000 && - state->RF_IN < 340000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 340000000 && - state->RF_IN < 430000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 430000000 && - state->RF_IN < 470000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 470000000 && - state->RF_IN < 570000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 570000000 && - state->RF_IN < 620000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 620000000 && - state->RF_IN < 760000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 760000000 && - state->RF_IN <= 900000000) { - status += MXL_ControlWrite(fe, DAC_A_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - } - } - - if (state->TF_Type == MXL_TF_E) /* Tracking Filter type E */ { - - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - - if (state->RF_IN >= 43000000 && state->RF_IN < 174000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 174000000 && state->RF_IN < 250000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 250000000 && state->RF_IN < 310000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 310000000 && state->RF_IN < 360000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 360000000 && state->RF_IN < 470000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 470000000 && state->RF_IN < 640000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 640000000 && state->RF_IN <= 900000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - } - - if (state->TF_Type == MXL_TF_F) { - - /* Tracking Filter type F */ - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - - if (state->RF_IN >= 43000000 && state->RF_IN < 160000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 160000000 && state->RF_IN < 210000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 210000000 && state->RF_IN < 300000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 300000000 && state->RF_IN < 390000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 390000000 && state->RF_IN < 515000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 515000000 && state->RF_IN < 650000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 650000000 && state->RF_IN <= 900000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - } - - if (state->TF_Type == MXL_TF_E_2) { - - /* Tracking Filter type E_2 */ - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - - if (state->RF_IN >= 43000000 && state->RF_IN < 174000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 174000000 && state->RF_IN < 250000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 250000000 && state->RF_IN < 350000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 350000000 && state->RF_IN < 400000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 400000000 && state->RF_IN < 570000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 570000000 && state->RF_IN < 770000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 770000000 && state->RF_IN <= 900000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - } - - if (state->TF_Type == MXL_TF_G) { - - /* Tracking Filter type G add for v2.6.8 */ - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - - if (state->RF_IN >= 50000000 && state->RF_IN < 190000000) { - - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 190000000 && state->RF_IN < 280000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 280000000 && state->RF_IN < 350000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 350000000 && state->RF_IN < 400000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 400000000 && state->RF_IN < 470000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 470000000 && state->RF_IN < 640000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 640000000 && state->RF_IN < 820000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 820000000 && state->RF_IN <= 900000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - } - - if (state->TF_Type == MXL_TF_E_NA) { - - /* Tracking Filter type E-NA for Empia ONLY change for 2.6.8 */ - status += MXL_ControlWrite(fe, DAC_DIN_B, 0); - - /* if UHF and terrestrial=> Turn off Tracking Filter */ - if (state->RF_IN >= 471000000 && - (state->RF_IN - 471000000)%6000000 != 0) { - - /* Turn off all the banks */ - status += MXL_SetGPIO(fe, 3, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - - /* 2.6.12 Turn on RSSI */ - status += MXL_ControlWrite(fe, SEQ_EXTSYNTHCALIF, 1); - status += MXL_ControlWrite(fe, SEQ_EXTDCCAL, 1); - status += MXL_ControlWrite(fe, AGC_EN_RSSI, 1); - status += MXL_ControlWrite(fe, RFA_ENCLKRFAGC, 1); - - /* RSSI reference point */ - status += MXL_ControlWrite(fe, RFA_RSSI_REFH, 5); - status += MXL_ControlWrite(fe, RFA_RSSI_REF, 3); - status += MXL_ControlWrite(fe, RFA_RSSI_REFL, 2); - - /* following parameter is from analog OTA mode, - * can be change to seek better performance */ - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, 3); - } else { - /* if VHF or Cable => Turn on Tracking Filter */ - - /* 2.6.12 Turn off RSSI */ - status += MXL_ControlWrite(fe, AGC_EN_RSSI, 0); - - /* change back from above condition */ - status += MXL_ControlWrite(fe, RFSYN_CHP_GAIN, 5); - - - if (state->RF_IN >= 43000000 && state->RF_IN < 174000000) { - - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 174000000 && state->RF_IN < 250000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 0); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 250000000 && state->RF_IN < 350000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 1); - } - if (state->RF_IN >= 350000000 && state->RF_IN < 400000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 0); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 400000000 && state->RF_IN < 570000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 0); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 570000000 && state->RF_IN < 770000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 0); - } - if (state->RF_IN >= 770000000 && state->RF_IN <= 900000000) { - status += MXL_ControlWrite(fe, DAC_B_ENABLE, 1); - status += MXL_SetGPIO(fe, 4, 1); - status += MXL_SetGPIO(fe, 1, 1); - status += MXL_SetGPIO(fe, 3, 1); - } - } - } - return status ; -} - -static u16 MXL_SetGPIO(struct dvb_frontend *fe, u8 GPIO_Num, u8 GPIO_Val) -{ - u16 status = 0; - - if (GPIO_Num == 1) - status += MXL_ControlWrite(fe, GPIO_1B, GPIO_Val ? 0 : 1); - - /* GPIO2 is not available */ - - if (GPIO_Num == 3) { - if (GPIO_Val == 1) { - status += MXL_ControlWrite(fe, GPIO_3, 0); - status += MXL_ControlWrite(fe, GPIO_3B, 0); - } - if (GPIO_Val == 0) { - status += MXL_ControlWrite(fe, GPIO_3, 1); - status += MXL_ControlWrite(fe, GPIO_3B, 1); - } - if (GPIO_Val == 3) { /* tri-state */ - status += MXL_ControlWrite(fe, GPIO_3, 0); - status += MXL_ControlWrite(fe, GPIO_3B, 1); - } - } - if (GPIO_Num == 4) { - if (GPIO_Val == 1) { - status += MXL_ControlWrite(fe, GPIO_4, 0); - status += MXL_ControlWrite(fe, GPIO_4B, 0); - } - if (GPIO_Val == 0) { - status += MXL_ControlWrite(fe, GPIO_4, 1); - status += MXL_ControlWrite(fe, GPIO_4B, 1); - } - if (GPIO_Val == 3) { /* tri-state */ - status += MXL_ControlWrite(fe, GPIO_4, 0); - status += MXL_ControlWrite(fe, GPIO_4B, 1); - } - } - - return status; -} - -static u16 MXL_ControlWrite(struct dvb_frontend *fe, u16 ControlNum, u32 value) -{ - u16 status = 0; - - /* Will write ALL Matching Control Name */ - /* Write Matching INIT Control */ - status += MXL_ControlWrite_Group(fe, ControlNum, value, 1); - /* Write Matching CH Control */ - status += MXL_ControlWrite_Group(fe, ControlNum, value, 2); -#ifdef _MXL_INTERNAL - /* Write Matching MXL Control */ - status += MXL_ControlWrite_Group(fe, ControlNum, value, 3); -#endif - return status; -} - -static u16 MXL_ControlWrite_Group(struct dvb_frontend *fe, u16 controlNum, - u32 value, u16 controlGroup) -{ - struct mxl5005s_state *state = fe->tuner_priv; - u16 i, j, k; - u32 highLimit; - u32 ctrlVal; - - if (controlGroup == 1) /* Initial Control */ { - - for (i = 0; i < state->Init_Ctrl_Num; i++) { - - if (controlNum == state->Init_Ctrl[i].Ctrl_Num) { - - highLimit = 1 << state->Init_Ctrl[i].size; - if (value < highLimit) { - for (j = 0; j < state->Init_Ctrl[i].size; j++) { - state->Init_Ctrl[i].val[j] = (u8)((value >> j) & 0x01); - MXL_RegWriteBit(fe, (u8)(state->Init_Ctrl[i].addr[j]), - (u8)(state->Init_Ctrl[i].bit[j]), - (u8)((value>>j) & 0x01)); - } - ctrlVal = 0; - for (k = 0; k < state->Init_Ctrl[i].size; k++) - ctrlVal += state->Init_Ctrl[i].val[k] * (1 << k); - } else - return -1; - } - } - } - if (controlGroup == 2) /* Chan change Control */ { - - for (i = 0; i < state->CH_Ctrl_Num; i++) { - - if (controlNum == state->CH_Ctrl[i].Ctrl_Num) { - - highLimit = 1 << state->CH_Ctrl[i].size; - if (value < highLimit) { - for (j = 0; j < state->CH_Ctrl[i].size; j++) { - state->CH_Ctrl[i].val[j] = (u8)((value >> j) & 0x01); - MXL_RegWriteBit(fe, (u8)(state->CH_Ctrl[i].addr[j]), - (u8)(state->CH_Ctrl[i].bit[j]), - (u8)((value>>j) & 0x01)); - } - ctrlVal = 0; - for (k = 0; k < state->CH_Ctrl[i].size; k++) - ctrlVal += state->CH_Ctrl[i].val[k] * (1 << k); - } else - return -1; - } - } - } -#ifdef _MXL_INTERNAL - if (controlGroup == 3) /* Maxlinear Control */ { - - for (i = 0; i < state->MXL_Ctrl_Num; i++) { - - if (controlNum == state->MXL_Ctrl[i].Ctrl_Num) { - - highLimit = (1 << state->MXL_Ctrl[i].size); - if (value < highLimit) { - for (j = 0; j < state->MXL_Ctrl[i].size; j++) { - state->MXL_Ctrl[i].val[j] = (u8)((value >> j) & 0x01); - MXL_RegWriteBit(fe, (u8)(state->MXL_Ctrl[i].addr[j]), - (u8)(state->MXL_Ctrl[i].bit[j]), - (u8)((value>>j) & 0x01)); - } - ctrlVal = 0; - for (k = 0; k < state->MXL_Ctrl[i].size; k++) - ctrlVal += state-> - MXL_Ctrl[i].val[k] * - (1 << k); - } else - return -1; - } - } - } -#endif - return 0 ; /* successful return */ -} - -static u16 MXL_RegRead(struct dvb_frontend *fe, u8 RegNum, u8 *RegVal) -{ - struct mxl5005s_state *state = fe->tuner_priv; - int i ; - - for (i = 0; i < 104; i++) { - if (RegNum == state->TunerRegs[i].Reg_Num) { - *RegVal = (u8)(state->TunerRegs[i].Reg_Val); - return 0; - } - } - - return 1; -} - -static u16 MXL_ControlRead(struct dvb_frontend *fe, u16 controlNum, u32 *value) -{ - struct mxl5005s_state *state = fe->tuner_priv; - u32 ctrlVal ; - u16 i, k ; - - for (i = 0; i < state->Init_Ctrl_Num ; i++) { - - if (controlNum == state->Init_Ctrl[i].Ctrl_Num) { - - ctrlVal = 0; - for (k = 0; k < state->Init_Ctrl[i].size; k++) - ctrlVal += state->Init_Ctrl[i].val[k] * (1<CH_Ctrl_Num ; i++) { - - if (controlNum == state->CH_Ctrl[i].Ctrl_Num) { - - ctrlVal = 0; - for (k = 0; k < state->CH_Ctrl[i].size; k++) - ctrlVal += state->CH_Ctrl[i].val[k] * (1 << k); - *value = ctrlVal; - return 0; - - } - } - -#ifdef _MXL_INTERNAL - for (i = 0; i < state->MXL_Ctrl_Num ; i++) { - - if (controlNum == state->MXL_Ctrl[i].Ctrl_Num) { - - ctrlVal = 0; - for (k = 0; k < state->MXL_Ctrl[i].size; k++) - ctrlVal += state->MXL_Ctrl[i].val[k] * (1<tuner_priv; - int i ; - - const u8 AND_MAP[8] = { - 0xFE, 0xFD, 0xFB, 0xF7, - 0xEF, 0xDF, 0xBF, 0x7F } ; - - const u8 OR_MAP[8] = { - 0x01, 0x02, 0x04, 0x08, - 0x10, 0x20, 0x40, 0x80 } ; - - for (i = 0; i < state->TunerRegs_Num; i++) { - if (state->TunerRegs[i].Reg_Num == address) { - if (bitVal) - state->TunerRegs[i].Reg_Val |= OR_MAP[bit]; - else - state->TunerRegs[i].Reg_Val &= AND_MAP[bit]; - break ; - } - } -} - -static u32 MXL_Ceiling(u32 value, u32 resolution) -{ - return value / resolution + (value % resolution > 0 ? 1 : 0); -} - -/* Retrieve the Initialzation Registers */ -static u16 MXL_GetInitRegister(struct dvb_frontend *fe, u8 *RegNum, - u8 *RegVal, int *count) -{ - u16 status = 0; - int i ; - - u8 RegAddr[] = { - 11, 12, 13, 22, 32, 43, 44, 53, 56, 59, 73, - 76, 77, 91, 134, 135, 137, 147, - 156, 166, 167, 168, 25 }; - - *count = ARRAY_SIZE(RegAddr); - - status += MXL_BlockInit(fe); - - for (i = 0 ; i < *count; i++) { - RegNum[i] = RegAddr[i]; - status += MXL_RegRead(fe, RegNum[i], &RegVal[i]); - } - - return status; -} - -static u16 MXL_GetCHRegister(struct dvb_frontend *fe, u8 *RegNum, u8 *RegVal, - int *count) -{ - u16 status = 0; - int i ; - -/* add 77, 166, 167, 168 register for 2.6.12 */ -#ifdef _MXL_PRODUCTION - u8 RegAddr[] = {14, 15, 16, 17, 22, 43, 65, 68, 69, 70, 73, 92, 93, 106, - 107, 108, 109, 110, 111, 112, 136, 138, 149, 77, 166, 167, 168 } ; -#else - u8 RegAddr[] = {14, 15, 16, 17, 22, 43, 68, 69, 70, 73, 92, 93, 106, - 107, 108, 109, 110, 111, 112, 136, 138, 149, 77, 166, 167, 168 } ; - /* - u8 RegAddr[171]; - for (i = 0; i <= 170; i++) - RegAddr[i] = i; - */ -#endif - - *count = ARRAY_SIZE(RegAddr); - - for (i = 0 ; i < *count; i++) { - RegNum[i] = RegAddr[i]; - status += MXL_RegRead(fe, RegNum[i], &RegVal[i]); - } - - return status; -} - -static u16 MXL_GetCHRegister_ZeroIF(struct dvb_frontend *fe, u8 *RegNum, - u8 *RegVal, int *count) -{ - u16 status = 0; - int i; - - u8 RegAddr[] = {43, 136}; - - *count = ARRAY_SIZE(RegAddr); - - for (i = 0; i < *count; i++) { - RegNum[i] = RegAddr[i]; - status += MXL_RegRead(fe, RegNum[i], &RegVal[i]); - } - - return status; -} - -static u16 MXL_GetMasterControl(u8 *MasterReg, int state) -{ - if (state == 1) /* Load_Start */ - *MasterReg = 0xF3; - if (state == 2) /* Power_Down */ - *MasterReg = 0x41; - if (state == 3) /* Synth_Reset */ - *MasterReg = 0xB1; - if (state == 4) /* Seq_Off */ - *MasterReg = 0xF1; - - return 0; -} - -#ifdef _MXL_PRODUCTION -static u16 MXL_VCORange_Test(struct dvb_frontend *fe, int VCO_Range) -{ - struct mxl5005s_state *state = fe->tuner_priv; - u16 status = 0 ; - - if (VCO_Range == 1) { - status += MXL_ControlWrite(fe, RFSYN_EN_DIV, 1); - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_DIVM, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 0); - if (state->Mode == 0 && state->IF_Mode == 1) { - /* Analog Low IF Mode */ - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 8); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 56); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 180224); - } - if (state->Mode == 0 && state->IF_Mode == 0) { - /* Analog Zero IF Mode */ - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 8); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 56); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 222822); - } - if (state->Mode == 1) /* Digital Mode */ { - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 8); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 56); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 229376); - } - } - - if (VCO_Range == 2) { - status += MXL_ControlWrite(fe, RFSYN_EN_DIV, 1); - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_DIVM, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 40); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 41); - if (state->Mode == 0 && state->IF_Mode == 1) { - /* Analog Low IF Mode */ - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 40); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 42); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 206438); - } - if (state->Mode == 0 && state->IF_Mode == 0) { - /* Analog Zero IF Mode */ - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 40); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 42); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 206438); - } - if (state->Mode == 1) /* Digital Mode */ { - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 1); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 40); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 41); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 16384); - } - } - - if (VCO_Range == 3) { - status += MXL_ControlWrite(fe, RFSYN_EN_DIV, 1); - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_DIVM, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 8); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 42); - if (state->Mode == 0 && state->IF_Mode == 1) { - /* Analog Low IF Mode */ - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 8); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 44); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 173670); - } - if (state->Mode == 0 && state->IF_Mode == 0) { - /* Analog Zero IF Mode */ - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 8); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 44); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 173670); - } - if (state->Mode == 1) /* Digital Mode */ { - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 8); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 42); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 245760); - } - } - - if (VCO_Range == 4) { - status += MXL_ControlWrite(fe, RFSYN_EN_DIV, 1); - status += MXL_ControlWrite(fe, RFSYN_EN_OUTMUX, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_DIVM, 0); - status += MXL_ControlWrite(fe, RFSYN_DIVM, 1); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_OUT, 1); - status += MXL_ControlWrite(fe, RFSYN_RF_DIV_BIAS, 1); - status += MXL_ControlWrite(fe, DN_SEL_FREQ, 0); - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 40); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 27); - if (state->Mode == 0 && state->IF_Mode == 1) { - /* Analog Low IF Mode */ - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 40); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 27); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 206438); - } - if (state->Mode == 0 && state->IF_Mode == 0) { - /* Analog Zero IF Mode */ - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 40); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 27); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 206438); - } - if (state->Mode == 1) /* Digital Mode */ { - status += MXL_ControlWrite(fe, RFSYN_SEL_VCO_HI, 0); - status += MXL_ControlWrite(fe, RFSYN_VCO_BIAS, 40); - status += MXL_ControlWrite(fe, CHCAL_INT_MOD_RF, 27); - status += MXL_ControlWrite(fe, - CHCAL_FRAC_MOD_RF, 212992); - } - } - - return status; -} - -static u16 MXL_Hystersis_Test(struct dvb_frontend *fe, int Hystersis) -{ - struct mxl5005s_state *state = fe->tuner_priv; - u16 status = 0; - - if (Hystersis == 1) - status += MXL_ControlWrite(fe, DN_BYPASS_AGC_I2C, 1); - - return status; -} -#endif -/* End: Reference driver code found in the Realtek driver that - * is copyright MaxLinear */ - -/* ---------------------------------------------------------------- - * Begin: Everything after here is new code to adapt the - * proprietary Realtek driver into a Linux API tuner. - * Copyright (C) 2008 Steven Toth - */ -static int mxl5005s_reset(struct dvb_frontend *fe) -{ - struct mxl5005s_state *state = fe->tuner_priv; - int ret = 0; - - u8 buf[2] = { 0xff, 0x00 }; - struct i2c_msg msg = { .addr = state->config->i2c_address, .flags = 0, - .buf = buf, .len = 2 }; - - dprintk(2, "%s()\n", __func__); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - - if (i2c_transfer(state->i2c, &msg, 1) != 1) { - printk(KERN_WARNING "mxl5005s I2C reset failed\n"); - ret = -EREMOTEIO; - } - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - - return ret; -} - -/* Write a single byte to a single reg, latch the value if required by - * following the transaction with the latch byte. - */ -static int mxl5005s_writereg(struct dvb_frontend *fe, u8 reg, u8 val, int latch) -{ - struct mxl5005s_state *state = fe->tuner_priv; - u8 buf[3] = { reg, val, MXL5005S_LATCH_BYTE }; - struct i2c_msg msg = { .addr = state->config->i2c_address, .flags = 0, - .buf = buf, .len = 3 }; - - if (latch == 0) - msg.len = 2; - - dprintk(2, "%s(0x%x, 0x%x, 0x%x)\n", __func__, reg, val, msg.addr); - - if (i2c_transfer(state->i2c, &msg, 1) != 1) { - printk(KERN_WARNING "mxl5005s I2C write failed\n"); - return -EREMOTEIO; - } - return 0; -} - -static int mxl5005s_writeregs(struct dvb_frontend *fe, u8 *addrtable, - u8 *datatable, u8 len) -{ - int ret = 0, i; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - - for (i = 0 ; i < len-1; i++) { - ret = mxl5005s_writereg(fe, addrtable[i], datatable[i], 0); - if (ret < 0) - break; - } - - ret = mxl5005s_writereg(fe, addrtable[i], datatable[i], 1); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - - return ret; -} - -static int mxl5005s_init(struct dvb_frontend *fe) -{ - struct mxl5005s_state *state = fe->tuner_priv; - - dprintk(1, "%s()\n", __func__); - state->current_mode = MXL_QAM; - return mxl5005s_reconfigure(fe, MXL_QAM, MXL5005S_BANDWIDTH_6MHZ); -} - -static int mxl5005s_reconfigure(struct dvb_frontend *fe, u32 mod_type, - u32 bandwidth) -{ - struct mxl5005s_state *state = fe->tuner_priv; - - u8 AddrTable[MXL5005S_REG_WRITING_TABLE_LEN_MAX]; - u8 ByteTable[MXL5005S_REG_WRITING_TABLE_LEN_MAX]; - int TableLen; - - dprintk(1, "%s(type=%d, bw=%d)\n", __func__, mod_type, bandwidth); - - mxl5005s_reset(fe); - - /* Tuner initialization stage 0 */ - MXL_GetMasterControl(ByteTable, MC_SYNTH_RESET); - AddrTable[0] = MASTER_CONTROL_ADDR; - ByteTable[0] |= state->config->AgcMasterByte; - - mxl5005s_writeregs(fe, AddrTable, ByteTable, 1); - - mxl5005s_AssignTunerMode(fe, mod_type, bandwidth); - - /* Tuner initialization stage 1 */ - MXL_GetInitRegister(fe, AddrTable, ByteTable, &TableLen); - - mxl5005s_writeregs(fe, AddrTable, ByteTable, TableLen); - - return 0; -} - -static int mxl5005s_AssignTunerMode(struct dvb_frontend *fe, u32 mod_type, - u32 bandwidth) -{ - struct mxl5005s_state *state = fe->tuner_priv; - struct mxl5005s_config *c = state->config; - - InitTunerControls(fe); - - /* Set MxL5005S parameters. */ - MXL5005_TunerConfig( - fe, - c->mod_mode, - c->if_mode, - bandwidth, - c->if_freq, - c->xtal_freq, - c->agc_mode, - c->top, - c->output_load, - c->clock_out, - c->div_out, - c->cap_select, - c->rssi_enable, - mod_type, - c->tracking_filter); - - return 0; -} - -static int mxl5005s_set_params(struct dvb_frontend *fe) -{ - struct mxl5005s_state *state = fe->tuner_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u32 delsys = c->delivery_system; - u32 bw = c->bandwidth_hz; - u32 req_mode, req_bw = 0; - int ret; - - dprintk(1, "%s()\n", __func__); - - switch (delsys) { - case SYS_ATSC: - req_mode = MXL_ATSC; - req_bw = MXL5005S_BANDWIDTH_6MHZ; - break; - case SYS_DVBC_ANNEX_B: - req_mode = MXL_QAM; - req_bw = MXL5005S_BANDWIDTH_6MHZ; - break; - default: /* Assume DVB-T */ - req_mode = MXL_DVBT; - switch (bw) { - case 6000000: - req_bw = MXL5005S_BANDWIDTH_6MHZ; - break; - case 7000000: - req_bw = MXL5005S_BANDWIDTH_7MHZ; - break; - case 8000000: - case 0: - req_bw = MXL5005S_BANDWIDTH_8MHZ; - break; - default: - return -EINVAL; - } - } - - /* Change tuner for new modulation type if reqd */ - if (req_mode != state->current_mode || - req_bw != state->Chan_Bandwidth) { - state->current_mode = req_mode; - ret = mxl5005s_reconfigure(fe, req_mode, req_bw); - - } else - ret = 0; - - if (ret == 0) { - dprintk(1, "%s() freq=%d\n", __func__, c->frequency); - ret = mxl5005s_SetRfFreqHz(fe, c->frequency); - } - - return ret; -} - -static int mxl5005s_get_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct mxl5005s_state *state = fe->tuner_priv; - dprintk(1, "%s()\n", __func__); - - *frequency = state->RF_IN; - - return 0; -} - -static int mxl5005s_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) -{ - struct mxl5005s_state *state = fe->tuner_priv; - dprintk(1, "%s()\n", __func__); - - *bandwidth = state->Chan_Bandwidth; - - return 0; -} - -static int mxl5005s_release(struct dvb_frontend *fe) -{ - dprintk(1, "%s()\n", __func__); - kfree(fe->tuner_priv); - fe->tuner_priv = NULL; - return 0; -} - -static const struct dvb_tuner_ops mxl5005s_tuner_ops = { - .info = { - .name = "MaxLinear MXL5005S", - .frequency_min = 48000000, - .frequency_max = 860000000, - .frequency_step = 50000, - }, - - .release = mxl5005s_release, - .init = mxl5005s_init, - - .set_params = mxl5005s_set_params, - .get_frequency = mxl5005s_get_frequency, - .get_bandwidth = mxl5005s_get_bandwidth, -}; - -struct dvb_frontend *mxl5005s_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, - struct mxl5005s_config *config) -{ - struct mxl5005s_state *state = NULL; - dprintk(1, "%s()\n", __func__); - - state = kzalloc(sizeof(struct mxl5005s_state), GFP_KERNEL); - if (state == NULL) - return NULL; - - state->frontend = fe; - state->config = config; - state->i2c = i2c; - - printk(KERN_INFO "MXL5005S: Attached at address 0x%02x\n", - config->i2c_address); - - memcpy(&fe->ops.tuner_ops, &mxl5005s_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - fe->tuner_priv = state; - return fe; -} -EXPORT_SYMBOL(mxl5005s_attach); - -MODULE_DESCRIPTION("MaxLinear MXL5005S silicon tuner driver"); -MODULE_AUTHOR("Steven Toth"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/tuners/qt1010.c b/drivers/media/common/tuners/qt1010.c deleted file mode 100644 index 2d79b1f5d5eb..000000000000 --- a/drivers/media/common/tuners/qt1010.c +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Driver for Quantek QT1010 silicon tuner - * - * Copyright (C) 2006 Antti Palosaari - * Aapo Tahkola - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -#include "qt1010.h" -#include "qt1010_priv.h" - -static int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); - -#define dprintk(args...) \ - do { \ - if (debug) printk(KERN_DEBUG "QT1010: " args); \ - } while (0) - -/* read single register */ -static int qt1010_readreg(struct qt1010_priv *priv, u8 reg, u8 *val) -{ - struct i2c_msg msg[2] = { - { .addr = priv->cfg->i2c_address, - .flags = 0, .buf = ®, .len = 1 }, - { .addr = priv->cfg->i2c_address, - .flags = I2C_M_RD, .buf = val, .len = 1 }, - }; - - if (i2c_transfer(priv->i2c, msg, 2) != 2) { - printk(KERN_WARNING "qt1010 I2C read failed\n"); - return -EREMOTEIO; - } - return 0; -} - -/* write single register */ -static int qt1010_writereg(struct qt1010_priv *priv, u8 reg, u8 val) -{ - u8 buf[2] = { reg, val }; - struct i2c_msg msg = { .addr = priv->cfg->i2c_address, - .flags = 0, .buf = buf, .len = 2 }; - - if (i2c_transfer(priv->i2c, &msg, 1) != 1) { - printk(KERN_WARNING "qt1010 I2C write failed\n"); - return -EREMOTEIO; - } - return 0; -} - -/* dump all registers */ -static void qt1010_dump_regs(struct qt1010_priv *priv) -{ - u8 reg, val; - - for (reg = 0; ; reg++) { - if (reg % 16 == 0) { - if (reg) - printk(KERN_CONT "\n"); - printk(KERN_DEBUG "%02x:", reg); - } - if (qt1010_readreg(priv, reg, &val) == 0) - printk(KERN_CONT " %02x", val); - else - printk(KERN_CONT " --"); - if (reg == 0x2f) - break; - } - printk(KERN_CONT "\n"); -} - -static int qt1010_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct qt1010_priv *priv; - int err; - u32 freq, div, mod1, mod2; - u8 i, tmpval, reg05; - qt1010_i2c_oper_t rd[48] = { - { QT1010_WR, 0x01, 0x80 }, - { QT1010_WR, 0x02, 0x3f }, - { QT1010_WR, 0x05, 0xff }, /* 02 c write */ - { QT1010_WR, 0x06, 0x44 }, - { QT1010_WR, 0x07, 0xff }, /* 04 c write */ - { QT1010_WR, 0x08, 0x08 }, - { QT1010_WR, 0x09, 0xff }, /* 06 c write */ - { QT1010_WR, 0x0a, 0xff }, /* 07 c write */ - { QT1010_WR, 0x0b, 0xff }, /* 08 c write */ - { QT1010_WR, 0x0c, 0xe1 }, - { QT1010_WR, 0x1a, 0xff }, /* 10 c write */ - { QT1010_WR, 0x1b, 0x00 }, - { QT1010_WR, 0x1c, 0x89 }, - { QT1010_WR, 0x11, 0xff }, /* 13 c write */ - { QT1010_WR, 0x12, 0xff }, /* 14 c write */ - { QT1010_WR, 0x22, 0xff }, /* 15 c write */ - { QT1010_WR, 0x1e, 0x00 }, - { QT1010_WR, 0x1e, 0xd0 }, - { QT1010_RD, 0x22, 0xff }, /* 16 c read */ - { QT1010_WR, 0x1e, 0x00 }, - { QT1010_RD, 0x05, 0xff }, /* 20 c read */ - { QT1010_RD, 0x22, 0xff }, /* 21 c read */ - { QT1010_WR, 0x23, 0xd0 }, - { QT1010_WR, 0x1e, 0x00 }, - { QT1010_WR, 0x1e, 0xe0 }, - { QT1010_RD, 0x23, 0xff }, /* 25 c read */ - { QT1010_RD, 0x23, 0xff }, /* 26 c read */ - { QT1010_WR, 0x1e, 0x00 }, - { QT1010_WR, 0x24, 0xd0 }, - { QT1010_WR, 0x1e, 0x00 }, - { QT1010_WR, 0x1e, 0xf0 }, - { QT1010_RD, 0x24, 0xff }, /* 31 c read */ - { QT1010_WR, 0x1e, 0x00 }, - { QT1010_WR, 0x14, 0x7f }, - { QT1010_WR, 0x15, 0x7f }, - { QT1010_WR, 0x05, 0xff }, /* 35 c write */ - { QT1010_WR, 0x06, 0x00 }, - { QT1010_WR, 0x15, 0x1f }, - { QT1010_WR, 0x16, 0xff }, - { QT1010_WR, 0x18, 0xff }, - { QT1010_WR, 0x1f, 0xff }, /* 40 c write */ - { QT1010_WR, 0x20, 0xff }, /* 41 c write */ - { QT1010_WR, 0x21, 0x53 }, - { QT1010_WR, 0x25, 0xff }, /* 43 c write */ - { QT1010_WR, 0x26, 0x15 }, - { QT1010_WR, 0x00, 0xff }, /* 45 c write */ - { QT1010_WR, 0x02, 0x00 }, - { QT1010_WR, 0x01, 0x00 } - }; - -#define FREQ1 32000000 /* 32 MHz */ -#define FREQ2 4000000 /* 4 MHz Quartz oscillator in the stick? */ - - priv = fe->tuner_priv; - freq = c->frequency; - div = (freq + QT1010_OFFSET) / QT1010_STEP; - freq = (div * QT1010_STEP) - QT1010_OFFSET; - mod1 = (freq + QT1010_OFFSET) % FREQ1; - mod2 = (freq + QT1010_OFFSET) % FREQ2; - priv->frequency = freq; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ - - /* reg 05 base value */ - if (freq < 290000000) reg05 = 0x14; /* 290 MHz */ - else if (freq < 610000000) reg05 = 0x34; /* 610 MHz */ - else if (freq < 802000000) reg05 = 0x54; /* 802 MHz */ - else reg05 = 0x74; - - /* 0x5 */ - rd[2].val = reg05; - - /* 07 - set frequency: 32 MHz scale */ - rd[4].val = (freq + QT1010_OFFSET) / FREQ1; - - /* 09 - changes every 8/24 MHz */ - if (mod1 < 8000000) rd[6].val = 0x1d; - else rd[6].val = 0x1c; - - /* 0a - set frequency: 4 MHz scale (max 28 MHz) */ - if (mod1 < 1*FREQ2) rd[7].val = 0x09; /* +0 MHz */ - else if (mod1 < 2*FREQ2) rd[7].val = 0x08; /* +4 MHz */ - else if (mod1 < 3*FREQ2) rd[7].val = 0x0f; /* +8 MHz */ - else if (mod1 < 4*FREQ2) rd[7].val = 0x0e; /* +12 MHz */ - else if (mod1 < 5*FREQ2) rd[7].val = 0x0d; /* +16 MHz */ - else if (mod1 < 6*FREQ2) rd[7].val = 0x0c; /* +20 MHz */ - else if (mod1 < 7*FREQ2) rd[7].val = 0x0b; /* +24 MHz */ - else rd[7].val = 0x0a; /* +28 MHz */ - - /* 0b - changes every 2/2 MHz */ - if (mod2 < 2000000) rd[8].val = 0x45; - else rd[8].val = 0x44; - - /* 1a - set frequency: 125 kHz scale (max 3875 kHz)*/ - tmpval = 0x78; /* byte, overflows intentionally */ - rd[10].val = tmpval-((mod2/QT1010_STEP)*0x08); - - /* 11 */ - rd[13].val = 0xfd; /* TODO: correct value calculation */ - - /* 12 */ - rd[14].val = 0x91; /* TODO: correct value calculation */ - - /* 22 */ - if (freq < 450000000) rd[15].val = 0xd0; /* 450 MHz */ - else if (freq < 482000000) rd[15].val = 0xd1; /* 482 MHz */ - else if (freq < 514000000) rd[15].val = 0xd4; /* 514 MHz */ - else if (freq < 546000000) rd[15].val = 0xd7; /* 546 MHz */ - else if (freq < 610000000) rd[15].val = 0xda; /* 610 MHz */ - else rd[15].val = 0xd0; - - /* 05 */ - rd[35].val = (reg05 & 0xf0); - - /* 1f */ - if (mod1 < 8000000) tmpval = 0x00; - else if (mod1 < 12000000) tmpval = 0x01; - else if (mod1 < 16000000) tmpval = 0x02; - else if (mod1 < 24000000) tmpval = 0x03; - else if (mod1 < 28000000) tmpval = 0x04; - else tmpval = 0x05; - rd[40].val = (priv->reg1f_init_val + 0x0e + tmpval); - - /* 20 */ - if (mod1 < 8000000) tmpval = 0x00; - else if (mod1 < 12000000) tmpval = 0x01; - else if (mod1 < 20000000) tmpval = 0x02; - else if (mod1 < 24000000) tmpval = 0x03; - else if (mod1 < 28000000) tmpval = 0x04; - else tmpval = 0x05; - rd[41].val = (priv->reg20_init_val + 0x0d + tmpval); - - /* 25 */ - rd[43].val = priv->reg25_init_val; - - /* 00 */ - rd[45].val = 0x92; /* TODO: correct value calculation */ - - dprintk("freq:%u 05:%02x 07:%02x 09:%02x 0a:%02x 0b:%02x " \ - "1a:%02x 11:%02x 12:%02x 22:%02x 05:%02x 1f:%02x " \ - "20:%02x 25:%02x 00:%02x", \ - freq, rd[2].val, rd[4].val, rd[6].val, rd[7].val, rd[8].val, \ - rd[10].val, rd[13].val, rd[14].val, rd[15].val, rd[35].val, \ - rd[40].val, rd[41].val, rd[43].val, rd[45].val); - - for (i = 0; i < ARRAY_SIZE(rd); i++) { - if (rd[i].oper == QT1010_WR) { - err = qt1010_writereg(priv, rd[i].reg, rd[i].val); - } else { /* read is required to proper locking */ - err = qt1010_readreg(priv, rd[i].reg, &tmpval); - } - if (err) return err; - } - - if (debug) - qt1010_dump_regs(priv); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ - - return 0; -} - -static int qt1010_init_meas1(struct qt1010_priv *priv, - u8 oper, u8 reg, u8 reg_init_val, u8 *retval) -{ - u8 i, val1, val2; - int err; - - qt1010_i2c_oper_t i2c_data[] = { - { QT1010_WR, reg, reg_init_val }, - { QT1010_WR, 0x1e, 0x00 }, - { QT1010_WR, 0x1e, oper }, - { QT1010_RD, reg, 0xff } - }; - - for (i = 0; i < ARRAY_SIZE(i2c_data); i++) { - if (i2c_data[i].oper == QT1010_WR) { - err = qt1010_writereg(priv, i2c_data[i].reg, - i2c_data[i].val); - } else { - err = qt1010_readreg(priv, i2c_data[i].reg, &val2); - } - if (err) return err; - } - - do { - val1 = val2; - err = qt1010_readreg(priv, reg, &val2); - if (err) return err; - dprintk("compare reg:%02x %02x %02x", reg, val1, val2); - } while (val1 != val2); - *retval = val1; - - return qt1010_writereg(priv, 0x1e, 0x00); -} - -static u8 qt1010_init_meas2(struct qt1010_priv *priv, - u8 reg_init_val, u8 *retval) -{ - u8 i, val; - int err; - qt1010_i2c_oper_t i2c_data[] = { - { QT1010_WR, 0x07, reg_init_val }, - { QT1010_WR, 0x22, 0xd0 }, - { QT1010_WR, 0x1e, 0x00 }, - { QT1010_WR, 0x1e, 0xd0 }, - { QT1010_RD, 0x22, 0xff }, - { QT1010_WR, 0x1e, 0x00 }, - { QT1010_WR, 0x22, 0xff } - }; - for (i = 0; i < ARRAY_SIZE(i2c_data); i++) { - if (i2c_data[i].oper == QT1010_WR) { - err = qt1010_writereg(priv, i2c_data[i].reg, - i2c_data[i].val); - } else { - err = qt1010_readreg(priv, i2c_data[i].reg, &val); - } - if (err) return err; - } - *retval = val; - return 0; -} - -static int qt1010_init(struct dvb_frontend *fe) -{ - struct qt1010_priv *priv = fe->tuner_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int err = 0; - u8 i, tmpval, *valptr = NULL; - - qt1010_i2c_oper_t i2c_data[] = { - { QT1010_WR, 0x01, 0x80 }, - { QT1010_WR, 0x0d, 0x84 }, - { QT1010_WR, 0x0e, 0xb7 }, - { QT1010_WR, 0x2a, 0x23 }, - { QT1010_WR, 0x2c, 0xdc }, - { QT1010_M1, 0x25, 0x40 }, /* get reg 25 init value */ - { QT1010_M1, 0x81, 0xff }, /* get reg 25 init value */ - { QT1010_WR, 0x2b, 0x70 }, - { QT1010_WR, 0x2a, 0x23 }, - { QT1010_M1, 0x26, 0x08 }, - { QT1010_M1, 0x82, 0xff }, - { QT1010_WR, 0x05, 0x14 }, - { QT1010_WR, 0x06, 0x44 }, - { QT1010_WR, 0x07, 0x28 }, - { QT1010_WR, 0x08, 0x0b }, - { QT1010_WR, 0x11, 0xfd }, - { QT1010_M1, 0x22, 0x0d }, - { QT1010_M1, 0xd0, 0xff }, - { QT1010_WR, 0x06, 0x40 }, - { QT1010_WR, 0x16, 0xf0 }, - { QT1010_WR, 0x02, 0x38 }, - { QT1010_WR, 0x03, 0x18 }, - { QT1010_WR, 0x20, 0xe0 }, - { QT1010_M1, 0x1f, 0x20 }, /* get reg 1f init value */ - { QT1010_M1, 0x84, 0xff }, /* get reg 1f init value */ - { QT1010_RD, 0x20, 0x20 }, /* get reg 20 init value */ - { QT1010_WR, 0x03, 0x19 }, - { QT1010_WR, 0x02, 0x3f }, - { QT1010_WR, 0x21, 0x53 }, - { QT1010_RD, 0x21, 0xff }, - { QT1010_WR, 0x11, 0xfd }, - { QT1010_WR, 0x05, 0x34 }, - { QT1010_WR, 0x06, 0x44 }, - { QT1010_WR, 0x08, 0x08 } - }; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ - - for (i = 0; i < ARRAY_SIZE(i2c_data); i++) { - switch (i2c_data[i].oper) { - case QT1010_WR: - err = qt1010_writereg(priv, i2c_data[i].reg, - i2c_data[i].val); - break; - case QT1010_RD: - if (i2c_data[i].val == 0x20) - valptr = &priv->reg20_init_val; - else - valptr = &tmpval; - err = qt1010_readreg(priv, i2c_data[i].reg, valptr); - break; - case QT1010_M1: - if (i2c_data[i].val == 0x25) - valptr = &priv->reg25_init_val; - else if (i2c_data[i].val == 0x1f) - valptr = &priv->reg1f_init_val; - else - valptr = &tmpval; - err = qt1010_init_meas1(priv, i2c_data[i+1].reg, - i2c_data[i].reg, - i2c_data[i].val, valptr); - i++; - break; - } - if (err) return err; - } - - for (i = 0x31; i < 0x3a; i++) /* 0x31 - 0x39 */ - if ((err = qt1010_init_meas2(priv, i, &tmpval))) - return err; - - c->frequency = 545000000; /* Sigmatek DVB-110 545000000 */ - /* MSI Megasky 580 GL861 533000000 */ - return qt1010_set_params(fe); -} - -static int qt1010_release(struct dvb_frontend *fe) -{ - kfree(fe->tuner_priv); - fe->tuner_priv = NULL; - return 0; -} - -static int qt1010_get_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct qt1010_priv *priv = fe->tuner_priv; - *frequency = priv->frequency; - return 0; -} - -static int qt1010_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - *frequency = 36125000; - return 0; -} - -static const struct dvb_tuner_ops qt1010_tuner_ops = { - .info = { - .name = "Quantek QT1010", - .frequency_min = QT1010_MIN_FREQ, - .frequency_max = QT1010_MAX_FREQ, - .frequency_step = QT1010_STEP, - }, - - .release = qt1010_release, - .init = qt1010_init, - /* TODO: implement sleep */ - - .set_params = qt1010_set_params, - .get_frequency = qt1010_get_frequency, - .get_if_frequency = qt1010_get_if_frequency, -}; - -struct dvb_frontend * qt1010_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, - struct qt1010_config *cfg) -{ - struct qt1010_priv *priv = NULL; - u8 id; - - priv = kzalloc(sizeof(struct qt1010_priv), GFP_KERNEL); - if (priv == NULL) - return NULL; - - priv->cfg = cfg; - priv->i2c = i2c; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ - - - /* Try to detect tuner chip. Probably this is not correct register. */ - if (qt1010_readreg(priv, 0x29, &id) != 0 || (id != 0x39)) { - kfree(priv); - return NULL; - } - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ - - printk(KERN_INFO "Quantek QT1010 successfully identified.\n"); - memcpy(&fe->ops.tuner_ops, &qt1010_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - fe->tuner_priv = priv; - return fe; -} -EXPORT_SYMBOL(qt1010_attach); - -MODULE_DESCRIPTION("Quantek QT1010 silicon tuner driver"); -MODULE_AUTHOR("Antti Palosaari "); -MODULE_AUTHOR("Aapo Tahkola "); -MODULE_VERSION("0.1"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/tuners/tda18212.c b/drivers/media/common/tuners/tda18212.c deleted file mode 100644 index 602c2e392b17..000000000000 --- a/drivers/media/common/tuners/tda18212.c +++ /dev/null @@ -1,326 +0,0 @@ -/* - * NXP TDA18212HN silicon tuner driver - * - * Copyright (C) 2011 Antti Palosaari - * - * 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. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "tda18212.h" - -struct tda18212_priv { - struct tda18212_config *cfg; - struct i2c_adapter *i2c; - - u32 if_frequency; -}; - -#define dbg(fmt, arg...) \ -do { \ - if (debug) \ - pr_info("%s: " fmt, __func__, ##arg); \ -} while (0) - -static int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); - -/* write multiple registers */ -static int tda18212_wr_regs(struct tda18212_priv *priv, u8 reg, u8 *val, - int len) -{ - int ret; - u8 buf[len+1]; - struct i2c_msg msg[1] = { - { - .addr = priv->cfg->i2c_address, - .flags = 0, - .len = sizeof(buf), - .buf = buf, - } - }; - - buf[0] = reg; - memcpy(&buf[1], val, len); - - ret = i2c_transfer(priv->i2c, msg, 1); - if (ret == 1) { - ret = 0; - } else { - pr_warn("i2c wr failed ret:%d reg:%02x len:%d\n", - ret, reg, len); - ret = -EREMOTEIO; - } - return ret; -} - -/* read multiple registers */ -static int tda18212_rd_regs(struct tda18212_priv *priv, u8 reg, u8 *val, - int len) -{ - int ret; - u8 buf[len]; - struct i2c_msg msg[2] = { - { - .addr = priv->cfg->i2c_address, - .flags = 0, - .len = 1, - .buf = ®, - }, { - .addr = priv->cfg->i2c_address, - .flags = I2C_M_RD, - .len = sizeof(buf), - .buf = buf, - } - }; - - ret = i2c_transfer(priv->i2c, msg, 2); - if (ret == 2) { - memcpy(val, buf, len); - ret = 0; - } else { - pr_warn("i2c rd failed ret:%d reg:%02x len:%d\n", - ret, reg, len); - ret = -EREMOTEIO; - } - - return ret; -} - -/* write single register */ -static int tda18212_wr_reg(struct tda18212_priv *priv, u8 reg, u8 val) -{ - return tda18212_wr_regs(priv, reg, &val, 1); -} - -/* read single register */ -static int tda18212_rd_reg(struct tda18212_priv *priv, u8 reg, u8 *val) -{ - return tda18212_rd_regs(priv, reg, val, 1); -} - -#if 0 /* keep, useful when developing driver */ -static void tda18212_dump_regs(struct tda18212_priv *priv) -{ - int i; - u8 buf[256]; - - #define TDA18212_RD_LEN 32 - for (i = 0; i < sizeof(buf); i += TDA18212_RD_LEN) - tda18212_rd_regs(priv, i, &buf[i], TDA18212_RD_LEN); - - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 32, 1, buf, - sizeof(buf), true); - - return; -} -#endif - -static int tda18212_set_params(struct dvb_frontend *fe) -{ - struct tda18212_priv *priv = fe->tuner_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i; - u32 if_khz; - u8 buf[9]; - #define DVBT_6 0 - #define DVBT_7 1 - #define DVBT_8 2 - #define DVBT2_6 3 - #define DVBT2_7 4 - #define DVBT2_8 5 - #define DVBC_6 6 - #define DVBC_8 7 - static const u8 bw_params[][3] = { - /* reg: 0f 13 23 */ - [DVBT_6] = { 0xb3, 0x20, 0x03 }, - [DVBT_7] = { 0xb3, 0x31, 0x01 }, - [DVBT_8] = { 0xb3, 0x22, 0x01 }, - [DVBT2_6] = { 0xbc, 0x20, 0x03 }, - [DVBT2_7] = { 0xbc, 0x72, 0x03 }, - [DVBT2_8] = { 0xbc, 0x22, 0x01 }, - [DVBC_6] = { 0x92, 0x50, 0x03 }, - [DVBC_8] = { 0x92, 0x53, 0x03 }, - }; - - dbg("delsys=%d RF=%d BW=%d\n", - c->delivery_system, c->frequency, c->bandwidth_hz); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */ - - switch (c->delivery_system) { - case SYS_DVBT: - switch (c->bandwidth_hz) { - case 6000000: - if_khz = priv->cfg->if_dvbt_6; - i = DVBT_6; - break; - case 7000000: - if_khz = priv->cfg->if_dvbt_7; - i = DVBT_7; - break; - case 8000000: - if_khz = priv->cfg->if_dvbt_8; - i = DVBT_8; - break; - default: - ret = -EINVAL; - goto error; - } - break; - case SYS_DVBT2: - switch (c->bandwidth_hz) { - case 6000000: - if_khz = priv->cfg->if_dvbt2_6; - i = DVBT2_6; - break; - case 7000000: - if_khz = priv->cfg->if_dvbt2_7; - i = DVBT2_7; - break; - case 8000000: - if_khz = priv->cfg->if_dvbt2_8; - i = DVBT2_8; - break; - default: - ret = -EINVAL; - goto error; - } - break; - case SYS_DVBC_ANNEX_A: - case SYS_DVBC_ANNEX_C: - if_khz = priv->cfg->if_dvbc; - i = DVBC_8; - break; - default: - ret = -EINVAL; - goto error; - } - - ret = tda18212_wr_reg(priv, 0x23, bw_params[i][2]); - if (ret) - goto error; - - ret = tda18212_wr_reg(priv, 0x06, 0x00); - if (ret) - goto error; - - ret = tda18212_wr_reg(priv, 0x0f, bw_params[i][0]); - if (ret) - goto error; - - buf[0] = 0x02; - buf[1] = bw_params[i][1]; - buf[2] = 0x03; /* default value */ - buf[3] = DIV_ROUND_CLOSEST(if_khz, 50); - buf[4] = ((c->frequency / 1000) >> 16) & 0xff; - buf[5] = ((c->frequency / 1000) >> 8) & 0xff; - buf[6] = ((c->frequency / 1000) >> 0) & 0xff; - buf[7] = 0xc1; - buf[8] = 0x01; - ret = tda18212_wr_regs(priv, 0x12, buf, sizeof(buf)); - if (ret) - goto error; - - /* actual IF rounded as it is on register */ - priv->if_frequency = buf[3] * 50 * 1000; - -exit: - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */ - - return ret; - -error: - dbg("failed:%d\n", ret); - goto exit; -} - -static int tda18212_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct tda18212_priv *priv = fe->tuner_priv; - - *frequency = priv->if_frequency; - - return 0; -} - -static int tda18212_release(struct dvb_frontend *fe) -{ - kfree(fe->tuner_priv); - fe->tuner_priv = NULL; - return 0; -} - -static const struct dvb_tuner_ops tda18212_tuner_ops = { - .info = { - .name = "NXP TDA18212", - - .frequency_min = 48000000, - .frequency_max = 864000000, - .frequency_step = 1000, - }, - - .release = tda18212_release, - - .set_params = tda18212_set_params, - .get_if_frequency = tda18212_get_if_frequency, -}; - -struct dvb_frontend *tda18212_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, struct tda18212_config *cfg) -{ - struct tda18212_priv *priv = NULL; - int ret; - u8 val; - - priv = kzalloc(sizeof(struct tda18212_priv), GFP_KERNEL); - if (priv == NULL) - return NULL; - - priv->cfg = cfg; - priv->i2c = i2c; - fe->tuner_priv = priv; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */ - - /* check if the tuner is there */ - ret = tda18212_rd_reg(priv, 0x00, &val); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */ - - dbg("ret:%d chip ID:%02x\n", ret, val); - if (ret || val != 0xc7) { - kfree(priv); - return NULL; - } - - pr_info("NXP TDA18212HN successfully identified\n"); - - memcpy(&fe->ops.tuner_ops, &tda18212_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - return fe; -} -EXPORT_SYMBOL(tda18212_attach); - -MODULE_DESCRIPTION("NXP TDA18212HN silicon tuner driver"); -MODULE_AUTHOR("Antti Palosaari "); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/tuners/tda18218.c b/drivers/media/common/tuners/tda18218.c deleted file mode 100644 index dfb3a831df45..000000000000 --- a/drivers/media/common/tuners/tda18218.c +++ /dev/null @@ -1,342 +0,0 @@ -/* - * NXP TDA18218HN silicon tuner driver - * - * Copyright (C) 2010 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include "tda18218.h" -#include "tda18218_priv.h" - -static int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); - -/* write multiple registers */ -static int tda18218_wr_regs(struct tda18218_priv *priv, u8 reg, u8 *val, u8 len) -{ - int ret = 0; - u8 buf[1+len], quotient, remainder, i, msg_len, msg_len_max; - struct i2c_msg msg[1] = { - { - .addr = priv->cfg->i2c_address, - .flags = 0, - .buf = buf, - } - }; - - msg_len_max = priv->cfg->i2c_wr_max - 1; - quotient = len / msg_len_max; - remainder = len % msg_len_max; - msg_len = msg_len_max; - for (i = 0; (i <= quotient && remainder); i++) { - if (i == quotient) /* set len of the last msg */ - msg_len = remainder; - - msg[0].len = msg_len + 1; - buf[0] = reg + i * msg_len_max; - memcpy(&buf[1], &val[i * msg_len_max], msg_len); - - ret = i2c_transfer(priv->i2c, msg, 1); - if (ret != 1) - break; - } - - if (ret == 1) { - ret = 0; - } else { - warn("i2c wr failed ret:%d reg:%02x len:%d", ret, reg, len); - ret = -EREMOTEIO; - } - - return ret; -} - -/* read multiple registers */ -static int tda18218_rd_regs(struct tda18218_priv *priv, u8 reg, u8 *val, u8 len) -{ - int ret; - u8 buf[reg+len]; /* we must start read always from reg 0x00 */ - struct i2c_msg msg[2] = { - { - .addr = priv->cfg->i2c_address, - .flags = 0, - .len = 1, - .buf = "\x00", - }, { - .addr = priv->cfg->i2c_address, - .flags = I2C_M_RD, - .len = sizeof(buf), - .buf = buf, - } - }; - - ret = i2c_transfer(priv->i2c, msg, 2); - if (ret == 2) { - memcpy(val, &buf[reg], len); - ret = 0; - } else { - warn("i2c rd failed ret:%d reg:%02x len:%d", ret, reg, len); - ret = -EREMOTEIO; - } - - return ret; -} - -/* write single register */ -static int tda18218_wr_reg(struct tda18218_priv *priv, u8 reg, u8 val) -{ - return tda18218_wr_regs(priv, reg, &val, 1); -} - -/* read single register */ - -static int tda18218_rd_reg(struct tda18218_priv *priv, u8 reg, u8 *val) -{ - return tda18218_rd_regs(priv, reg, val, 1); -} - -static int tda18218_set_params(struct dvb_frontend *fe) -{ - struct tda18218_priv *priv = fe->tuner_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u32 bw = c->bandwidth_hz; - int ret; - u8 buf[3], i, BP_Filter, LP_Fc; - u32 LO_Frac; - /* TODO: find out correct AGC algorithm */ - u8 agc[][2] = { - { R20_AGC11, 0x60 }, - { R23_AGC21, 0x02 }, - { R20_AGC11, 0xa0 }, - { R23_AGC21, 0x09 }, - { R20_AGC11, 0xe0 }, - { R23_AGC21, 0x0c }, - { R20_AGC11, 0x40 }, - { R23_AGC21, 0x01 }, - { R20_AGC11, 0x80 }, - { R23_AGC21, 0x08 }, - { R20_AGC11, 0xc0 }, - { R23_AGC21, 0x0b }, - { R24_AGC22, 0x1c }, - { R24_AGC22, 0x0c }, - }; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */ - - /* low-pass filter cut-off frequency */ - if (bw <= 6000000) { - LP_Fc = 0; - priv->if_frequency = 3000000; - } else if (bw <= 7000000) { - LP_Fc = 1; - priv->if_frequency = 3500000; - } else { - LP_Fc = 2; - priv->if_frequency = 4000000; - } - - LO_Frac = c->frequency + priv->if_frequency; - - /* band-pass filter */ - if (LO_Frac < 188000000) - BP_Filter = 3; - else if (LO_Frac < 253000000) - BP_Filter = 4; - else if (LO_Frac < 343000000) - BP_Filter = 5; - else - BP_Filter = 6; - - buf[0] = (priv->regs[R1A_IF1] & ~7) | BP_Filter; /* BP_Filter */ - buf[1] = (priv->regs[R1B_IF2] & ~3) | LP_Fc; /* LP_Fc */ - buf[2] = priv->regs[R1C_AGC2B]; - ret = tda18218_wr_regs(priv, R1A_IF1, buf, 3); - if (ret) - goto error; - - buf[0] = (LO_Frac / 1000) >> 12; /* LO_Frac_0 */ - buf[1] = (LO_Frac / 1000) >> 4; /* LO_Frac_1 */ - buf[2] = (LO_Frac / 1000) << 4 | - (priv->regs[R0C_MD5] & 0x0f); /* LO_Frac_2 */ - ret = tda18218_wr_regs(priv, R0A_MD3, buf, 3); - if (ret) - goto error; - - buf[0] = priv->regs[R0F_MD8] | (1 << 6); /* Freq_prog_Start */ - ret = tda18218_wr_regs(priv, R0F_MD8, buf, 1); - if (ret) - goto error; - - buf[0] = priv->regs[R0F_MD8] & ~(1 << 6); /* Freq_prog_Start */ - ret = tda18218_wr_regs(priv, R0F_MD8, buf, 1); - if (ret) - goto error; - - /* trigger AGC */ - for (i = 0; i < ARRAY_SIZE(agc); i++) { - ret = tda18218_wr_reg(priv, agc[i][0], agc[i][1]); - if (ret) - goto error; - } - -error: - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */ - - if (ret) - dbg("%s: failed ret:%d", __func__, ret); - - return ret; -} - -static int tda18218_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct tda18218_priv *priv = fe->tuner_priv; - *frequency = priv->if_frequency; - dbg("%s: if=%d", __func__, *frequency); - return 0; -} - -static int tda18218_sleep(struct dvb_frontend *fe) -{ - struct tda18218_priv *priv = fe->tuner_priv; - int ret; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */ - - /* standby */ - ret = tda18218_wr_reg(priv, R17_PD1, priv->regs[R17_PD1] | (1 << 0)); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */ - - if (ret) - dbg("%s: failed ret:%d", __func__, ret); - - return ret; -} - -static int tda18218_init(struct dvb_frontend *fe) -{ - struct tda18218_priv *priv = fe->tuner_priv; - int ret; - - /* TODO: calibrations */ - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */ - - ret = tda18218_wr_regs(priv, R00_ID, priv->regs, TDA18218_NUM_REGS); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */ - - if (ret) - dbg("%s: failed ret:%d", __func__, ret); - - return ret; -} - -static int tda18218_release(struct dvb_frontend *fe) -{ - kfree(fe->tuner_priv); - fe->tuner_priv = NULL; - return 0; -} - -static const struct dvb_tuner_ops tda18218_tuner_ops = { - .info = { - .name = "NXP TDA18218", - - .frequency_min = 174000000, - .frequency_max = 864000000, - .frequency_step = 1000, - }, - - .release = tda18218_release, - .init = tda18218_init, - .sleep = tda18218_sleep, - - .set_params = tda18218_set_params, - - .get_if_frequency = tda18218_get_if_frequency, -}; - -struct dvb_frontend *tda18218_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, struct tda18218_config *cfg) -{ - struct tda18218_priv *priv = NULL; - u8 val; - int ret; - /* chip default registers values */ - static u8 def_regs[] = { - 0xc0, 0x88, 0x00, 0x8e, 0x03, 0x00, 0x00, 0xd0, 0x00, 0x40, - 0x00, 0x00, 0x07, 0xff, 0x84, 0x09, 0x00, 0x13, 0x00, 0x00, - 0x01, 0x84, 0x09, 0xf0, 0x19, 0x0a, 0x8e, 0x69, 0x98, 0x01, - 0x00, 0x58, 0x10, 0x40, 0x8c, 0x00, 0x0c, 0x48, 0x85, 0xc9, - 0xa7, 0x00, 0x00, 0x00, 0x30, 0x81, 0x80, 0x00, 0x39, 0x00, - 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xf6 - }; - - priv = kzalloc(sizeof(struct tda18218_priv), GFP_KERNEL); - if (priv == NULL) - return NULL; - - priv->cfg = cfg; - priv->i2c = i2c; - fe->tuner_priv = priv; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open I2C-gate */ - - /* check if the tuner is there */ - ret = tda18218_rd_reg(priv, R00_ID, &val); - dbg("%s: ret:%d chip ID:%02x", __func__, ret, val); - if (ret || val != def_regs[R00_ID]) { - kfree(priv); - return NULL; - } - - info("NXP TDA18218HN successfully identified."); - - memcpy(&fe->ops.tuner_ops, &tda18218_tuner_ops, - sizeof(struct dvb_tuner_ops)); - memcpy(priv->regs, def_regs, sizeof(def_regs)); - - /* loop-through enabled chip default register values */ - if (priv->cfg->loop_through) { - priv->regs[R17_PD1] = 0xb0; - priv->regs[R18_PD2] = 0x59; - } - - /* standby */ - ret = tda18218_wr_reg(priv, R17_PD1, priv->regs[R17_PD1] | (1 << 0)); - if (ret) - dbg("%s: failed ret:%d", __func__, ret); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close I2C-gate */ - - return fe; -} -EXPORT_SYMBOL(tda18218_attach); - -MODULE_DESCRIPTION("NXP TDA18218HN silicon tuner driver"); -MODULE_AUTHOR("Antti Palosaari "); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/tuners/tda18218_priv.h b/drivers/media/common/tuners/tda18218_priv.h deleted file mode 100644 index dc52b72e1407..000000000000 --- a/drivers/media/common/tuners/tda18218_priv.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * NXP TDA18218HN silicon tuner driver - * - * Copyright (C) 2010 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#ifndef TDA18218_PRIV_H -#define TDA18218_PRIV_H - -#define LOG_PREFIX "tda18218" - -#undef dbg -#define dbg(f, arg...) \ - if (debug) \ - printk(KERN_DEBUG LOG_PREFIX": " f "\n" , ## arg) -#undef err -#define err(f, arg...) printk(KERN_ERR LOG_PREFIX": " f "\n" , ## arg) -#undef info -#define info(f, arg...) printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef warn -#define warn(f, arg...) printk(KERN_WARNING LOG_PREFIX": " f "\n" , ## arg) - -#define R00_ID 0x00 /* ID byte */ -#define R01_R1 0x01 /* Read byte 1 */ -#define R02_R2 0x02 /* Read byte 2 */ -#define R03_R3 0x03 /* Read byte 3 */ -#define R04_R4 0x04 /* Read byte 4 */ -#define R05_R5 0x05 /* Read byte 5 */ -#define R06_R6 0x06 /* Read byte 6 */ -#define R07_MD1 0x07 /* Main divider byte 1 */ -#define R08_PSM1 0x08 /* PSM byte 1 */ -#define R09_MD2 0x09 /* Main divider byte 2 */ -#define R0A_MD3 0x0a /* Main divider byte 1 */ -#define R0B_MD4 0x0b /* Main divider byte 4 */ -#define R0C_MD5 0x0c /* Main divider byte 5 */ -#define R0D_MD6 0x0d /* Main divider byte 6 */ -#define R0E_MD7 0x0e /* Main divider byte 7 */ -#define R0F_MD8 0x0f /* Main divider byte 8 */ -#define R10_CD1 0x10 /* Call divider byte 1 */ -#define R11_CD2 0x11 /* Call divider byte 2 */ -#define R12_CD3 0x12 /* Call divider byte 3 */ -#define R13_CD4 0x13 /* Call divider byte 4 */ -#define R14_CD5 0x14 /* Call divider byte 5 */ -#define R15_CD6 0x15 /* Call divider byte 6 */ -#define R16_CD7 0x16 /* Call divider byte 7 */ -#define R17_PD1 0x17 /* Power-down byte 1 */ -#define R18_PD2 0x18 /* Power-down byte 2 */ -#define R19_XTOUT 0x19 /* XTOUT byte */ -#define R1A_IF1 0x1a /* IF byte 1 */ -#define R1B_IF2 0x1b /* IF byte 2 */ -#define R1C_AGC2B 0x1c /* AGC2b byte */ -#define R1D_PSM2 0x1d /* PSM byte 2 */ -#define R1E_PSM3 0x1e /* PSM byte 3 */ -#define R1F_PSM4 0x1f /* PSM byte 4 */ -#define R20_AGC11 0x20 /* AGC1 byte 1 */ -#define R21_AGC12 0x21 /* AGC1 byte 2 */ -#define R22_AGC13 0x22 /* AGC1 byte 3 */ -#define R23_AGC21 0x23 /* AGC2 byte 1 */ -#define R24_AGC22 0x24 /* AGC2 byte 2 */ -#define R25_AAGC 0x25 /* Analog AGC byte */ -#define R26_RC 0x26 /* RC byte */ -#define R27_RSSI 0x27 /* RSSI byte */ -#define R28_IRCAL1 0x28 /* IR CAL byte 1 */ -#define R29_IRCAL2 0x29 /* IR CAL byte 2 */ -#define R2A_IRCAL3 0x2a /* IR CAL byte 3 */ -#define R2B_IRCAL4 0x2b /* IR CAL byte 4 */ -#define R2C_RFCAL1 0x2c /* RF CAL byte 1 */ -#define R2D_RFCAL2 0x2d /* RF CAL byte 2 */ -#define R2E_RFCAL3 0x2e /* RF CAL byte 3 */ -#define R2F_RFCAL4 0x2f /* RF CAL byte 4 */ -#define R30_RFCAL5 0x30 /* RF CAL byte 5 */ -#define R31_RFCAL6 0x31 /* RF CAL byte 6 */ -#define R32_RFCAL7 0x32 /* RF CAL byte 7 */ -#define R33_RFCAL8 0x33 /* RF CAL byte 8 */ -#define R34_RFCAL9 0x34 /* RF CAL byte 9 */ -#define R35_RFCAL10 0x35 /* RF CAL byte 10 */ -#define R36_RFCALRAM1 0x36 /* RF CAL RAM byte 1 */ -#define R37_RFCALRAM2 0x37 /* RF CAL RAM byte 2 */ -#define R38_MARGIN 0x38 /* Margin byte */ -#define R39_FMAX1 0x39 /* Fmax byte 1 */ -#define R3A_FMAX2 0x3a /* Fmax byte 2 */ - -#define TDA18218_NUM_REGS 59 - -struct tda18218_priv { - struct tda18218_config *cfg; - struct i2c_adapter *i2c; - - u32 if_frequency; - - u8 regs[TDA18218_NUM_REGS]; -}; - -#endif diff --git a/drivers/media/common/tuners/tda18271-common.c b/drivers/media/common/tuners/tda18271-common.c deleted file mode 100644 index 39c645787b62..000000000000 --- a/drivers/media/common/tuners/tda18271-common.c +++ /dev/null @@ -1,703 +0,0 @@ -/* - tda18271-common.c - driver for the Philips / NXP TDA18271 silicon tuner - - Copyright (C) 2007, 2008 Michael Krufky - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include "tda18271-priv.h" - -static int tda18271_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct tda18271_priv *priv = fe->tuner_priv; - enum tda18271_i2c_gate gate; - int ret = 0; - - switch (priv->gate) { - case TDA18271_GATE_DIGITAL: - case TDA18271_GATE_ANALOG: - gate = priv->gate; - break; - case TDA18271_GATE_AUTO: - default: - switch (priv->mode) { - case TDA18271_DIGITAL: - gate = TDA18271_GATE_DIGITAL; - break; - case TDA18271_ANALOG: - default: - gate = TDA18271_GATE_ANALOG; - break; - } - } - - switch (gate) { - case TDA18271_GATE_ANALOG: - if (fe->ops.analog_ops.i2c_gate_ctrl) - ret = fe->ops.analog_ops.i2c_gate_ctrl(fe, enable); - break; - case TDA18271_GATE_DIGITAL: - if (fe->ops.i2c_gate_ctrl) - ret = fe->ops.i2c_gate_ctrl(fe, enable); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -}; - -/*---------------------------------------------------------------------*/ - -static void tda18271_dump_regs(struct dvb_frontend *fe, int extended) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - - tda_reg("=== TDA18271 REG DUMP ===\n"); - tda_reg("ID_BYTE = 0x%02x\n", 0xff & regs[R_ID]); - tda_reg("THERMO_BYTE = 0x%02x\n", 0xff & regs[R_TM]); - tda_reg("POWER_LEVEL_BYTE = 0x%02x\n", 0xff & regs[R_PL]); - tda_reg("EASY_PROG_BYTE_1 = 0x%02x\n", 0xff & regs[R_EP1]); - tda_reg("EASY_PROG_BYTE_2 = 0x%02x\n", 0xff & regs[R_EP2]); - tda_reg("EASY_PROG_BYTE_3 = 0x%02x\n", 0xff & regs[R_EP3]); - tda_reg("EASY_PROG_BYTE_4 = 0x%02x\n", 0xff & regs[R_EP4]); - tda_reg("EASY_PROG_BYTE_5 = 0x%02x\n", 0xff & regs[R_EP5]); - tda_reg("CAL_POST_DIV_BYTE = 0x%02x\n", 0xff & regs[R_CPD]); - tda_reg("CAL_DIV_BYTE_1 = 0x%02x\n", 0xff & regs[R_CD1]); - tda_reg("CAL_DIV_BYTE_2 = 0x%02x\n", 0xff & regs[R_CD2]); - tda_reg("CAL_DIV_BYTE_3 = 0x%02x\n", 0xff & regs[R_CD3]); - tda_reg("MAIN_POST_DIV_BYTE = 0x%02x\n", 0xff & regs[R_MPD]); - tda_reg("MAIN_DIV_BYTE_1 = 0x%02x\n", 0xff & regs[R_MD1]); - tda_reg("MAIN_DIV_BYTE_2 = 0x%02x\n", 0xff & regs[R_MD2]); - tda_reg("MAIN_DIV_BYTE_3 = 0x%02x\n", 0xff & regs[R_MD3]); - - /* only dump extended regs if DBG_ADV is set */ - if (!(tda18271_debug & DBG_ADV)) - return; - - /* W indicates write-only registers. - * Register dump for write-only registers shows last value written. */ - - tda_reg("EXTENDED_BYTE_1 = 0x%02x\n", 0xff & regs[R_EB1]); - tda_reg("EXTENDED_BYTE_2 = 0x%02x\n", 0xff & regs[R_EB2]); - tda_reg("EXTENDED_BYTE_3 = 0x%02x\n", 0xff & regs[R_EB3]); - tda_reg("EXTENDED_BYTE_4 = 0x%02x\n", 0xff & regs[R_EB4]); - tda_reg("EXTENDED_BYTE_5 = 0x%02x\n", 0xff & regs[R_EB5]); - tda_reg("EXTENDED_BYTE_6 = 0x%02x\n", 0xff & regs[R_EB6]); - tda_reg("EXTENDED_BYTE_7 = 0x%02x\n", 0xff & regs[R_EB7]); - tda_reg("EXTENDED_BYTE_8 = 0x%02x\n", 0xff & regs[R_EB8]); - tda_reg("EXTENDED_BYTE_9 W = 0x%02x\n", 0xff & regs[R_EB9]); - tda_reg("EXTENDED_BYTE_10 = 0x%02x\n", 0xff & regs[R_EB10]); - tda_reg("EXTENDED_BYTE_11 = 0x%02x\n", 0xff & regs[R_EB11]); - tda_reg("EXTENDED_BYTE_12 = 0x%02x\n", 0xff & regs[R_EB12]); - tda_reg("EXTENDED_BYTE_13 = 0x%02x\n", 0xff & regs[R_EB13]); - tda_reg("EXTENDED_BYTE_14 = 0x%02x\n", 0xff & regs[R_EB14]); - tda_reg("EXTENDED_BYTE_15 = 0x%02x\n", 0xff & regs[R_EB15]); - tda_reg("EXTENDED_BYTE_16 W = 0x%02x\n", 0xff & regs[R_EB16]); - tda_reg("EXTENDED_BYTE_17 W = 0x%02x\n", 0xff & regs[R_EB17]); - tda_reg("EXTENDED_BYTE_18 = 0x%02x\n", 0xff & regs[R_EB18]); - tda_reg("EXTENDED_BYTE_19 W = 0x%02x\n", 0xff & regs[R_EB19]); - tda_reg("EXTENDED_BYTE_20 W = 0x%02x\n", 0xff & regs[R_EB20]); - tda_reg("EXTENDED_BYTE_21 = 0x%02x\n", 0xff & regs[R_EB21]); - tda_reg("EXTENDED_BYTE_22 = 0x%02x\n", 0xff & regs[R_EB22]); - tda_reg("EXTENDED_BYTE_23 = 0x%02x\n", 0xff & regs[R_EB23]); -} - -int tda18271_read_regs(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - unsigned char buf = 0x00; - int ret; - struct i2c_msg msg[] = { - { .addr = priv->i2c_props.addr, .flags = 0, - .buf = &buf, .len = 1 }, - { .addr = priv->i2c_props.addr, .flags = I2C_M_RD, - .buf = regs, .len = 16 } - }; - - tda18271_i2c_gate_ctrl(fe, 1); - - /* read all registers */ - ret = i2c_transfer(priv->i2c_props.adap, msg, 2); - - tda18271_i2c_gate_ctrl(fe, 0); - - if (ret != 2) - tda_err("ERROR: i2c_transfer returned: %d\n", ret); - - if (tda18271_debug & DBG_REG) - tda18271_dump_regs(fe, 0); - - return (ret == 2 ? 0 : ret); -} - -int tda18271_read_extended(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - unsigned char regdump[TDA18271_NUM_REGS]; - unsigned char buf = 0x00; - int ret, i; - struct i2c_msg msg[] = { - { .addr = priv->i2c_props.addr, .flags = 0, - .buf = &buf, .len = 1 }, - { .addr = priv->i2c_props.addr, .flags = I2C_M_RD, - .buf = regdump, .len = TDA18271_NUM_REGS } - }; - - tda18271_i2c_gate_ctrl(fe, 1); - - /* read all registers */ - ret = i2c_transfer(priv->i2c_props.adap, msg, 2); - - tda18271_i2c_gate_ctrl(fe, 0); - - if (ret != 2) - tda_err("ERROR: i2c_transfer returned: %d\n", ret); - - for (i = 0; i < TDA18271_NUM_REGS; i++) { - /* don't update write-only registers */ - if ((i != R_EB9) && - (i != R_EB16) && - (i != R_EB17) && - (i != R_EB19) && - (i != R_EB20)) - regs[i] = regdump[i]; - } - - if (tda18271_debug & DBG_REG) - tda18271_dump_regs(fe, 1); - - return (ret == 2 ? 0 : ret); -} - -int tda18271_write_regs(struct dvb_frontend *fe, int idx, int len) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - unsigned char buf[TDA18271_NUM_REGS + 1]; - struct i2c_msg msg = { .addr = priv->i2c_props.addr, .flags = 0, - .buf = buf }; - int i, ret = 1, max; - - BUG_ON((len == 0) || (idx + len > sizeof(buf))); - - - switch (priv->small_i2c) { - case TDA18271_03_BYTE_CHUNK_INIT: - max = 3; - break; - case TDA18271_08_BYTE_CHUNK_INIT: - max = 8; - break; - case TDA18271_16_BYTE_CHUNK_INIT: - max = 16; - break; - case TDA18271_39_BYTE_CHUNK_INIT: - default: - max = 39; - } - - tda18271_i2c_gate_ctrl(fe, 1); - while (len) { - if (max > len) - max = len; - - buf[0] = idx; - for (i = 1; i <= max; i++) - buf[i] = regs[idx - 1 + i]; - - msg.len = max + 1; - - /* write registers */ - ret = i2c_transfer(priv->i2c_props.adap, &msg, 1); - if (ret != 1) - break; - - idx += max; - len -= max; - } - tda18271_i2c_gate_ctrl(fe, 0); - - if (ret != 1) - tda_err("ERROR: idx = 0x%x, len = %d, " - "i2c_transfer returned: %d\n", idx, max, ret); - - return (ret == 1 ? 0 : ret); -} - -/*---------------------------------------------------------------------*/ - -int tda18271_charge_pump_source(struct dvb_frontend *fe, - enum tda18271_pll pll, int force) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - - int r_cp = (pll == TDA18271_CAL_PLL) ? R_EB7 : R_EB4; - - regs[r_cp] &= ~0x20; - regs[r_cp] |= ((force & 1) << 5); - - return tda18271_write_regs(fe, r_cp, 1); -} - -int tda18271_init_regs(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - - tda_dbg("initializing registers for device @ %d-%04x\n", - i2c_adapter_id(priv->i2c_props.adap), - priv->i2c_props.addr); - - /* initialize registers */ - switch (priv->id) { - case TDA18271HDC1: - regs[R_ID] = 0x83; - break; - case TDA18271HDC2: - regs[R_ID] = 0x84; - break; - }; - - regs[R_TM] = 0x08; - regs[R_PL] = 0x80; - regs[R_EP1] = 0xc6; - regs[R_EP2] = 0xdf; - regs[R_EP3] = 0x16; - regs[R_EP4] = 0x60; - regs[R_EP5] = 0x80; - regs[R_CPD] = 0x80; - regs[R_CD1] = 0x00; - regs[R_CD2] = 0x00; - regs[R_CD3] = 0x00; - regs[R_MPD] = 0x00; - regs[R_MD1] = 0x00; - regs[R_MD2] = 0x00; - regs[R_MD3] = 0x00; - - switch (priv->id) { - case TDA18271HDC1: - regs[R_EB1] = 0xff; - break; - case TDA18271HDC2: - regs[R_EB1] = 0xfc; - break; - }; - - regs[R_EB2] = 0x01; - regs[R_EB3] = 0x84; - regs[R_EB4] = 0x41; - regs[R_EB5] = 0x01; - regs[R_EB6] = 0x84; - regs[R_EB7] = 0x40; - regs[R_EB8] = 0x07; - regs[R_EB9] = 0x00; - regs[R_EB10] = 0x00; - regs[R_EB11] = 0x96; - - switch (priv->id) { - case TDA18271HDC1: - regs[R_EB12] = 0x0f; - break; - case TDA18271HDC2: - regs[R_EB12] = 0x33; - break; - }; - - regs[R_EB13] = 0xc1; - regs[R_EB14] = 0x00; - regs[R_EB15] = 0x8f; - regs[R_EB16] = 0x00; - regs[R_EB17] = 0x00; - - switch (priv->id) { - case TDA18271HDC1: - regs[R_EB18] = 0x00; - break; - case TDA18271HDC2: - regs[R_EB18] = 0x8c; - break; - }; - - regs[R_EB19] = 0x00; - regs[R_EB20] = 0x20; - - switch (priv->id) { - case TDA18271HDC1: - regs[R_EB21] = 0x33; - break; - case TDA18271HDC2: - regs[R_EB21] = 0xb3; - break; - }; - - regs[R_EB22] = 0x48; - regs[R_EB23] = 0xb0; - - tda18271_write_regs(fe, 0x00, TDA18271_NUM_REGS); - - /* setup agc1 gain */ - regs[R_EB17] = 0x00; - tda18271_write_regs(fe, R_EB17, 1); - regs[R_EB17] = 0x03; - tda18271_write_regs(fe, R_EB17, 1); - regs[R_EB17] = 0x43; - tda18271_write_regs(fe, R_EB17, 1); - regs[R_EB17] = 0x4c; - tda18271_write_regs(fe, R_EB17, 1); - - /* setup agc2 gain */ - if ((priv->id) == TDA18271HDC1) { - regs[R_EB20] = 0xa0; - tda18271_write_regs(fe, R_EB20, 1); - regs[R_EB20] = 0xa7; - tda18271_write_regs(fe, R_EB20, 1); - regs[R_EB20] = 0xe7; - tda18271_write_regs(fe, R_EB20, 1); - regs[R_EB20] = 0xec; - tda18271_write_regs(fe, R_EB20, 1); - } - - /* image rejection calibration */ - - /* low-band */ - regs[R_EP3] = 0x1f; - regs[R_EP4] = 0x66; - regs[R_EP5] = 0x81; - regs[R_CPD] = 0xcc; - regs[R_CD1] = 0x6c; - regs[R_CD2] = 0x00; - regs[R_CD3] = 0x00; - regs[R_MPD] = 0xcd; - regs[R_MD1] = 0x77; - regs[R_MD2] = 0x08; - regs[R_MD3] = 0x00; - - tda18271_write_regs(fe, R_EP3, 11); - - if ((priv->id) == TDA18271HDC2) { - /* main pll cp source on */ - tda18271_charge_pump_source(fe, TDA18271_MAIN_PLL, 1); - msleep(1); - - /* main pll cp source off */ - tda18271_charge_pump_source(fe, TDA18271_MAIN_PLL, 0); - } - - msleep(5); /* pll locking */ - - /* launch detector */ - tda18271_write_regs(fe, R_EP1, 1); - msleep(5); /* wanted low measurement */ - - regs[R_EP5] = 0x85; - regs[R_CPD] = 0xcb; - regs[R_CD1] = 0x66; - regs[R_CD2] = 0x70; - - tda18271_write_regs(fe, R_EP3, 7); - msleep(5); /* pll locking */ - - /* launch optimization algorithm */ - tda18271_write_regs(fe, R_EP2, 1); - msleep(30); /* image low optimization completion */ - - /* mid-band */ - regs[R_EP5] = 0x82; - regs[R_CPD] = 0xa8; - regs[R_CD2] = 0x00; - regs[R_MPD] = 0xa9; - regs[R_MD1] = 0x73; - regs[R_MD2] = 0x1a; - - tda18271_write_regs(fe, R_EP3, 11); - msleep(5); /* pll locking */ - - /* launch detector */ - tda18271_write_regs(fe, R_EP1, 1); - msleep(5); /* wanted mid measurement */ - - regs[R_EP5] = 0x86; - regs[R_CPD] = 0xa8; - regs[R_CD1] = 0x66; - regs[R_CD2] = 0xa0; - - tda18271_write_regs(fe, R_EP3, 7); - msleep(5); /* pll locking */ - - /* launch optimization algorithm */ - tda18271_write_regs(fe, R_EP2, 1); - msleep(30); /* image mid optimization completion */ - - /* high-band */ - regs[R_EP5] = 0x83; - regs[R_CPD] = 0x98; - regs[R_CD1] = 0x65; - regs[R_CD2] = 0x00; - regs[R_MPD] = 0x99; - regs[R_MD1] = 0x71; - regs[R_MD2] = 0xcd; - - tda18271_write_regs(fe, R_EP3, 11); - msleep(5); /* pll locking */ - - /* launch detector */ - tda18271_write_regs(fe, R_EP1, 1); - msleep(5); /* wanted high measurement */ - - regs[R_EP5] = 0x87; - regs[R_CD1] = 0x65; - regs[R_CD2] = 0x50; - - tda18271_write_regs(fe, R_EP3, 7); - msleep(5); /* pll locking */ - - /* launch optimization algorithm */ - tda18271_write_regs(fe, R_EP2, 1); - msleep(30); /* image high optimization completion */ - - /* return to normal mode */ - regs[R_EP4] = 0x64; - tda18271_write_regs(fe, R_EP4, 1); - - /* synchronize */ - tda18271_write_regs(fe, R_EP1, 1); - - return 0; -} - -/*---------------------------------------------------------------------*/ - -/* - * Standby modes, EP3 [7:5] - * - * | SM || SM_LT || SM_XT || mode description - * |=====\\=======\\=======\\=================================== - * | 0 || 0 || 0 || normal mode - * |-----||-------||-------||----------------------------------- - * | || || || standby mode w/ slave tuner output - * | 1 || 0 || 0 || & loop thru & xtal oscillator on - * |-----||-------||-------||----------------------------------- - * | 1 || 1 || 0 || standby mode w/ xtal oscillator on - * |-----||-------||-------||----------------------------------- - * | 1 || 1 || 1 || power off - * - */ - -int tda18271_set_standby_mode(struct dvb_frontend *fe, - int sm, int sm_lt, int sm_xt) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - - if (tda18271_debug & DBG_ADV) - tda_dbg("sm = %d, sm_lt = %d, sm_xt = %d\n", sm, sm_lt, sm_xt); - - regs[R_EP3] &= ~0xe0; /* clear sm, sm_lt, sm_xt */ - regs[R_EP3] |= (sm ? (1 << 7) : 0) | - (sm_lt ? (1 << 6) : 0) | - (sm_xt ? (1 << 5) : 0); - - return tda18271_write_regs(fe, R_EP3, 1); -} - -/*---------------------------------------------------------------------*/ - -int tda18271_calc_main_pll(struct dvb_frontend *fe, u32 freq) -{ - /* sets main post divider & divider bytes, but does not write them */ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - u8 d, pd; - u32 div; - - int ret = tda18271_lookup_pll_map(fe, MAIN_PLL, &freq, &pd, &d); - if (tda_fail(ret)) - goto fail; - - regs[R_MPD] = (0x7f & pd); - - div = ((d * (freq / 1000)) << 7) / 125; - - regs[R_MD1] = 0x7f & (div >> 16); - regs[R_MD2] = 0xff & (div >> 8); - regs[R_MD3] = 0xff & div; -fail: - return ret; -} - -int tda18271_calc_cal_pll(struct dvb_frontend *fe, u32 freq) -{ - /* sets cal post divider & divider bytes, but does not write them */ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - u8 d, pd; - u32 div; - - int ret = tda18271_lookup_pll_map(fe, CAL_PLL, &freq, &pd, &d); - if (tda_fail(ret)) - goto fail; - - regs[R_CPD] = pd; - - div = ((d * (freq / 1000)) << 7) / 125; - - regs[R_CD1] = 0x7f & (div >> 16); - regs[R_CD2] = 0xff & (div >> 8); - regs[R_CD3] = 0xff & div; -fail: - return ret; -} - -/*---------------------------------------------------------------------*/ - -int tda18271_calc_bp_filter(struct dvb_frontend *fe, u32 *freq) -{ - /* sets bp filter bits, but does not write them */ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - u8 val; - - int ret = tda18271_lookup_map(fe, BP_FILTER, freq, &val); - if (tda_fail(ret)) - goto fail; - - regs[R_EP1] &= ~0x07; /* clear bp filter bits */ - regs[R_EP1] |= (0x07 & val); -fail: - return ret; -} - -int tda18271_calc_km(struct dvb_frontend *fe, u32 *freq) -{ - /* sets K & M bits, but does not write them */ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - u8 val; - - int ret = tda18271_lookup_map(fe, RF_CAL_KMCO, freq, &val); - if (tda_fail(ret)) - goto fail; - - regs[R_EB13] &= ~0x7c; /* clear k & m bits */ - regs[R_EB13] |= (0x7c & val); -fail: - return ret; -} - -int tda18271_calc_rf_band(struct dvb_frontend *fe, u32 *freq) -{ - /* sets rf band bits, but does not write them */ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - u8 val; - - int ret = tda18271_lookup_map(fe, RF_BAND, freq, &val); - if (tda_fail(ret)) - goto fail; - - regs[R_EP2] &= ~0xe0; /* clear rf band bits */ - regs[R_EP2] |= (0xe0 & (val << 5)); -fail: - return ret; -} - -int tda18271_calc_gain_taper(struct dvb_frontend *fe, u32 *freq) -{ - /* sets gain taper bits, but does not write them */ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - u8 val; - - int ret = tda18271_lookup_map(fe, GAIN_TAPER, freq, &val); - if (tda_fail(ret)) - goto fail; - - regs[R_EP2] &= ~0x1f; /* clear gain taper bits */ - regs[R_EP2] |= (0x1f & val); -fail: - return ret; -} - -int tda18271_calc_ir_measure(struct dvb_frontend *fe, u32 *freq) -{ - /* sets IR Meas bits, but does not write them */ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - u8 val; - - int ret = tda18271_lookup_map(fe, IR_MEASURE, freq, &val); - if (tda_fail(ret)) - goto fail; - - regs[R_EP5] &= ~0x07; - regs[R_EP5] |= (0x07 & val); -fail: - return ret; -} - -int tda18271_calc_rf_cal(struct dvb_frontend *fe, u32 *freq) -{ - /* sets rf cal byte (RFC_Cprog), but does not write it */ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - u8 val; - - int ret = tda18271_lookup_map(fe, RF_CAL, freq, &val); - /* The TDA18271HD/C1 rf_cal map lookup is expected to go out of range - * for frequencies above 61.1 MHz. In these cases, the internal RF - * tracking filters calibration mechanism is used. - * - * There is no need to warn the user about this. - */ - if (ret < 0) - goto fail; - - regs[R_EB14] = val; -fail: - return ret; -} - -int _tda_printk(struct tda18271_priv *state, const char *level, - const char *func, const char *fmt, ...) -{ - struct va_format vaf; - va_list args; - int rtn; - - va_start(args, fmt); - - vaf.fmt = fmt; - vaf.va = &args; - - if (state) - rtn = printk("%s%s: [%d-%04x|%c] %pV", - level, func, i2c_adapter_id(state->i2c_props.adap), - state->i2c_props.addr, - (state->role == TDA18271_MASTER) ? 'M' : 'S', - &vaf); - else - rtn = printk("%s%s: %pV", level, func, &vaf); - - va_end(args); - - return rtn; -} diff --git a/drivers/media/common/tuners/tda18271-fe.c b/drivers/media/common/tuners/tda18271-fe.c deleted file mode 100644 index 2e67f4459904..000000000000 --- a/drivers/media/common/tuners/tda18271-fe.c +++ /dev/null @@ -1,1345 +0,0 @@ -/* - tda18271-fe.c - driver for the Philips / NXP TDA18271 silicon tuner - - Copyright (C) 2007, 2008 Michael Krufky - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include -#include -#include "tda18271-priv.h" - -int tda18271_debug; -module_param_named(debug, tda18271_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debug level " - "(info=1, map=2, reg=4, adv=8, cal=16 (or-able))"); - -static int tda18271_cal_on_startup = -1; -module_param_named(cal, tda18271_cal_on_startup, int, 0644); -MODULE_PARM_DESC(cal, "perform RF tracking filter calibration on startup"); - -static DEFINE_MUTEX(tda18271_list_mutex); -static LIST_HEAD(hybrid_tuner_instance_list); - -/*---------------------------------------------------------------------*/ - -static int tda18271_toggle_output(struct dvb_frontend *fe, int standby) -{ - struct tda18271_priv *priv = fe->tuner_priv; - - int ret = tda18271_set_standby_mode(fe, standby ? 1 : 0, - priv->output_opt & TDA18271_OUTPUT_LT_OFF ? 1 : 0, - priv->output_opt & TDA18271_OUTPUT_XT_OFF ? 1 : 0); - - if (tda_fail(ret)) - goto fail; - - tda_dbg("%s mode: xtal oscillator %s, slave tuner loop thru %s\n", - standby ? "standby" : "active", - priv->output_opt & TDA18271_OUTPUT_XT_OFF ? "off" : "on", - priv->output_opt & TDA18271_OUTPUT_LT_OFF ? "off" : "on"); -fail: - return ret; -} - -/*---------------------------------------------------------------------*/ - -static inline int charge_pump_source(struct dvb_frontend *fe, int force) -{ - struct tda18271_priv *priv = fe->tuner_priv; - return tda18271_charge_pump_source(fe, - (priv->role == TDA18271_SLAVE) ? - TDA18271_CAL_PLL : - TDA18271_MAIN_PLL, force); -} - -static inline void tda18271_set_if_notch(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - - switch (priv->mode) { - case TDA18271_ANALOG: - regs[R_MPD] &= ~0x80; /* IF notch = 0 */ - break; - case TDA18271_DIGITAL: - regs[R_MPD] |= 0x80; /* IF notch = 1 */ - break; - } -} - -static int tda18271_channel_configuration(struct dvb_frontend *fe, - struct tda18271_std_map_item *map, - u32 freq, u32 bw) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - int ret; - u32 N; - - /* update TV broadcast parameters */ - - /* set standard */ - regs[R_EP3] &= ~0x1f; /* clear std bits */ - regs[R_EP3] |= (map->agc_mode << 3) | map->std; - - if (priv->id == TDA18271HDC2) { - /* set rfagc to high speed mode */ - regs[R_EP3] &= ~0x04; - } - - /* set cal mode to normal */ - regs[R_EP4] &= ~0x03; - - /* update IF output level */ - regs[R_EP4] &= ~0x1c; /* clear if level bits */ - regs[R_EP4] |= (map->if_lvl << 2); - - /* update FM_RFn */ - regs[R_EP4] &= ~0x80; - regs[R_EP4] |= map->fm_rfn << 7; - - /* update rf top / if top */ - regs[R_EB22] = 0x00; - regs[R_EB22] |= map->rfagc_top; - ret = tda18271_write_regs(fe, R_EB22, 1); - if (tda_fail(ret)) - goto fail; - - /* --------------------------------------------------------------- */ - - /* disable Power Level Indicator */ - regs[R_EP1] |= 0x40; - - /* make sure thermometer is off */ - regs[R_TM] &= ~0x10; - - /* frequency dependent parameters */ - - tda18271_calc_ir_measure(fe, &freq); - - tda18271_calc_bp_filter(fe, &freq); - - tda18271_calc_rf_band(fe, &freq); - - tda18271_calc_gain_taper(fe, &freq); - - /* --------------------------------------------------------------- */ - - /* dual tuner and agc1 extra configuration */ - - switch (priv->role) { - case TDA18271_MASTER: - regs[R_EB1] |= 0x04; /* main vco */ - break; - case TDA18271_SLAVE: - regs[R_EB1] &= ~0x04; /* cal vco */ - break; - } - - /* agc1 always active */ - regs[R_EB1] &= ~0x02; - - /* agc1 has priority on agc2 */ - regs[R_EB1] &= ~0x01; - - ret = tda18271_write_regs(fe, R_EB1, 1); - if (tda_fail(ret)) - goto fail; - - /* --------------------------------------------------------------- */ - - N = map->if_freq * 1000 + freq; - - switch (priv->role) { - case TDA18271_MASTER: - tda18271_calc_main_pll(fe, N); - tda18271_set_if_notch(fe); - tda18271_write_regs(fe, R_MPD, 4); - break; - case TDA18271_SLAVE: - tda18271_calc_cal_pll(fe, N); - tda18271_write_regs(fe, R_CPD, 4); - - regs[R_MPD] = regs[R_CPD] & 0x7f; - tda18271_set_if_notch(fe); - tda18271_write_regs(fe, R_MPD, 1); - break; - } - - ret = tda18271_write_regs(fe, R_TM, 7); - if (tda_fail(ret)) - goto fail; - - /* force charge pump source */ - charge_pump_source(fe, 1); - - msleep(1); - - /* return pll to normal operation */ - charge_pump_source(fe, 0); - - msleep(20); - - if (priv->id == TDA18271HDC2) { - /* set rfagc to normal speed mode */ - if (map->fm_rfn) - regs[R_EP3] &= ~0x04; - else - regs[R_EP3] |= 0x04; - ret = tda18271_write_regs(fe, R_EP3, 1); - } -fail: - return ret; -} - -static int tda18271_read_thermometer(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - int tm; - - /* switch thermometer on */ - regs[R_TM] |= 0x10; - tda18271_write_regs(fe, R_TM, 1); - - /* read thermometer info */ - tda18271_read_regs(fe); - - if ((((regs[R_TM] & 0x0f) == 0x00) && ((regs[R_TM] & 0x20) == 0x20)) || - (((regs[R_TM] & 0x0f) == 0x08) && ((regs[R_TM] & 0x20) == 0x00))) { - - if ((regs[R_TM] & 0x20) == 0x20) - regs[R_TM] &= ~0x20; - else - regs[R_TM] |= 0x20; - - tda18271_write_regs(fe, R_TM, 1); - - msleep(10); /* temperature sensing */ - - /* read thermometer info */ - tda18271_read_regs(fe); - } - - tm = tda18271_lookup_thermometer(fe); - - /* switch thermometer off */ - regs[R_TM] &= ~0x10; - tda18271_write_regs(fe, R_TM, 1); - - /* set CAL mode to normal */ - regs[R_EP4] &= ~0x03; - tda18271_write_regs(fe, R_EP4, 1); - - return tm; -} - -/* ------------------------------------------------------------------ */ - -static int tda18271c2_rf_tracking_filters_correction(struct dvb_frontend *fe, - u32 freq) -{ - struct tda18271_priv *priv = fe->tuner_priv; - struct tda18271_rf_tracking_filter_cal *map = priv->rf_cal_state; - unsigned char *regs = priv->tda18271_regs; - int i, ret; - u8 tm_current, dc_over_dt, rf_tab; - s32 rfcal_comp, approx; - - /* power up */ - ret = tda18271_set_standby_mode(fe, 0, 0, 0); - if (tda_fail(ret)) - goto fail; - - /* read die current temperature */ - tm_current = tda18271_read_thermometer(fe); - - /* frequency dependent parameters */ - - tda18271_calc_rf_cal(fe, &freq); - rf_tab = regs[R_EB14]; - - i = tda18271_lookup_rf_band(fe, &freq, NULL); - if (tda_fail(i)) - return i; - - if ((0 == map[i].rf3) || (freq / 1000 < map[i].rf2)) { - approx = map[i].rf_a1 * (s32)(freq / 1000 - map[i].rf1) + - map[i].rf_b1 + rf_tab; - } else { - approx = map[i].rf_a2 * (s32)(freq / 1000 - map[i].rf2) + - map[i].rf_b2 + rf_tab; - } - - if (approx < 0) - approx = 0; - if (approx > 255) - approx = 255; - - tda18271_lookup_map(fe, RF_CAL_DC_OVER_DT, &freq, &dc_over_dt); - - /* calculate temperature compensation */ - rfcal_comp = dc_over_dt * (s32)(tm_current - priv->tm_rfcal) / 1000; - - regs[R_EB14] = (unsigned char)(approx + rfcal_comp); - ret = tda18271_write_regs(fe, R_EB14, 1); -fail: - return ret; -} - -static int tda18271_por(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - int ret; - - /* power up detector 1 */ - regs[R_EB12] &= ~0x20; - ret = tda18271_write_regs(fe, R_EB12, 1); - if (tda_fail(ret)) - goto fail; - - regs[R_EB18] &= ~0x80; /* turn agc1 loop on */ - regs[R_EB18] &= ~0x03; /* set agc1_gain to 6 dB */ - ret = tda18271_write_regs(fe, R_EB18, 1); - if (tda_fail(ret)) - goto fail; - - regs[R_EB21] |= 0x03; /* set agc2_gain to -6 dB */ - - /* POR mode */ - ret = tda18271_set_standby_mode(fe, 1, 0, 0); - if (tda_fail(ret)) - goto fail; - - /* disable 1.5 MHz low pass filter */ - regs[R_EB23] &= ~0x04; /* forcelp_fc2_en = 0 */ - regs[R_EB23] &= ~0x02; /* XXX: lp_fc[2] = 0 */ - ret = tda18271_write_regs(fe, R_EB21, 3); -fail: - return ret; -} - -static int tda18271_calibrate_rf(struct dvb_frontend *fe, u32 freq) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - u32 N; - - /* set CAL mode to normal */ - regs[R_EP4] &= ~0x03; - tda18271_write_regs(fe, R_EP4, 1); - - /* switch off agc1 */ - regs[R_EP3] |= 0x40; /* sm_lt = 1 */ - - regs[R_EB18] |= 0x03; /* set agc1_gain to 15 dB */ - tda18271_write_regs(fe, R_EB18, 1); - - /* frequency dependent parameters */ - - tda18271_calc_bp_filter(fe, &freq); - tda18271_calc_gain_taper(fe, &freq); - tda18271_calc_rf_band(fe, &freq); - tda18271_calc_km(fe, &freq); - - tda18271_write_regs(fe, R_EP1, 3); - tda18271_write_regs(fe, R_EB13, 1); - - /* main pll charge pump source */ - tda18271_charge_pump_source(fe, TDA18271_MAIN_PLL, 1); - - /* cal pll charge pump source */ - tda18271_charge_pump_source(fe, TDA18271_CAL_PLL, 1); - - /* force dcdc converter to 0 V */ - regs[R_EB14] = 0x00; - tda18271_write_regs(fe, R_EB14, 1); - - /* disable plls lock */ - regs[R_EB20] &= ~0x20; - tda18271_write_regs(fe, R_EB20, 1); - - /* set CAL mode to RF tracking filter calibration */ - regs[R_EP4] |= 0x03; - tda18271_write_regs(fe, R_EP4, 2); - - /* --------------------------------------------------------------- */ - - /* set the internal calibration signal */ - N = freq; - - tda18271_calc_cal_pll(fe, N); - tda18271_write_regs(fe, R_CPD, 4); - - /* downconvert internal calibration */ - N += 1000000; - - tda18271_calc_main_pll(fe, N); - tda18271_write_regs(fe, R_MPD, 4); - - msleep(5); - - tda18271_write_regs(fe, R_EP2, 1); - tda18271_write_regs(fe, R_EP1, 1); - tda18271_write_regs(fe, R_EP2, 1); - tda18271_write_regs(fe, R_EP1, 1); - - /* --------------------------------------------------------------- */ - - /* normal operation for the main pll */ - tda18271_charge_pump_source(fe, TDA18271_MAIN_PLL, 0); - - /* normal operation for the cal pll */ - tda18271_charge_pump_source(fe, TDA18271_CAL_PLL, 0); - - msleep(10); /* plls locking */ - - /* launch the rf tracking filters calibration */ - regs[R_EB20] |= 0x20; - tda18271_write_regs(fe, R_EB20, 1); - - msleep(60); /* calibration */ - - /* --------------------------------------------------------------- */ - - /* set CAL mode to normal */ - regs[R_EP4] &= ~0x03; - - /* switch on agc1 */ - regs[R_EP3] &= ~0x40; /* sm_lt = 0 */ - - regs[R_EB18] &= ~0x03; /* set agc1_gain to 6 dB */ - tda18271_write_regs(fe, R_EB18, 1); - - tda18271_write_regs(fe, R_EP3, 2); - - /* synchronization */ - tda18271_write_regs(fe, R_EP1, 1); - - /* get calibration result */ - tda18271_read_extended(fe); - - return regs[R_EB14]; -} - -static int tda18271_powerscan(struct dvb_frontend *fe, - u32 *freq_in, u32 *freq_out) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - int sgn, bcal, count, wait, ret; - u8 cid_target; - u16 count_limit; - u32 freq; - - freq = *freq_in; - - tda18271_calc_rf_band(fe, &freq); - tda18271_calc_rf_cal(fe, &freq); - tda18271_calc_gain_taper(fe, &freq); - tda18271_lookup_cid_target(fe, &freq, &cid_target, &count_limit); - - tda18271_write_regs(fe, R_EP2, 1); - tda18271_write_regs(fe, R_EB14, 1); - - /* downconvert frequency */ - freq += 1000000; - - tda18271_calc_main_pll(fe, freq); - tda18271_write_regs(fe, R_MPD, 4); - - msleep(5); /* pll locking */ - - /* detection mode */ - regs[R_EP4] &= ~0x03; - regs[R_EP4] |= 0x01; - tda18271_write_regs(fe, R_EP4, 1); - - /* launch power detection measurement */ - tda18271_write_regs(fe, R_EP2, 1); - - /* read power detection info, stored in EB10 */ - ret = tda18271_read_extended(fe); - if (tda_fail(ret)) - return ret; - - /* algorithm initialization */ - sgn = 1; - *freq_out = *freq_in; - bcal = 0; - count = 0; - wait = false; - - while ((regs[R_EB10] & 0x3f) < cid_target) { - /* downconvert updated freq to 1 MHz */ - freq = *freq_in + (sgn * count) + 1000000; - - tda18271_calc_main_pll(fe, freq); - tda18271_write_regs(fe, R_MPD, 4); - - if (wait) { - msleep(5); /* pll locking */ - wait = false; - } else - udelay(100); /* pll locking */ - - /* launch power detection measurement */ - tda18271_write_regs(fe, R_EP2, 1); - - /* read power detection info, stored in EB10 */ - ret = tda18271_read_extended(fe); - if (tda_fail(ret)) - return ret; - - count += 200; - - if (count <= count_limit) - continue; - - if (sgn <= 0) - break; - - sgn = -1 * sgn; - count = 200; - wait = true; - } - - if ((regs[R_EB10] & 0x3f) >= cid_target) { - bcal = 1; - *freq_out = freq - 1000000; - } else - bcal = 0; - - tda_cal("bcal = %d, freq_in = %d, freq_out = %d (freq = %d)\n", - bcal, *freq_in, *freq_out, freq); - - return bcal; -} - -static int tda18271_powerscan_init(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - int ret; - - /* set standard to digital */ - regs[R_EP3] &= ~0x1f; /* clear std bits */ - regs[R_EP3] |= 0x12; - - /* set cal mode to normal */ - regs[R_EP4] &= ~0x03; - - /* update IF output level */ - regs[R_EP4] &= ~0x1c; /* clear if level bits */ - - ret = tda18271_write_regs(fe, R_EP3, 2); - if (tda_fail(ret)) - goto fail; - - regs[R_EB18] &= ~0x03; /* set agc1_gain to 6 dB */ - ret = tda18271_write_regs(fe, R_EB18, 1); - if (tda_fail(ret)) - goto fail; - - regs[R_EB21] &= ~0x03; /* set agc2_gain to -15 dB */ - - /* 1.5 MHz low pass filter */ - regs[R_EB23] |= 0x04; /* forcelp_fc2_en = 1 */ - regs[R_EB23] |= 0x02; /* lp_fc[2] = 1 */ - - ret = tda18271_write_regs(fe, R_EB21, 3); -fail: - return ret; -} - -static int tda18271_rf_tracking_filters_init(struct dvb_frontend *fe, u32 freq) -{ - struct tda18271_priv *priv = fe->tuner_priv; - struct tda18271_rf_tracking_filter_cal *map = priv->rf_cal_state; - unsigned char *regs = priv->tda18271_regs; - int bcal, rf, i; - s32 divisor, dividend; -#define RF1 0 -#define RF2 1 -#define RF3 2 - u32 rf_default[3]; - u32 rf_freq[3]; - s32 prog_cal[3]; - s32 prog_tab[3]; - - i = tda18271_lookup_rf_band(fe, &freq, NULL); - - if (tda_fail(i)) - return i; - - rf_default[RF1] = 1000 * map[i].rf1_def; - rf_default[RF2] = 1000 * map[i].rf2_def; - rf_default[RF3] = 1000 * map[i].rf3_def; - - for (rf = RF1; rf <= RF3; rf++) { - if (0 == rf_default[rf]) - return 0; - tda_cal("freq = %d, rf = %d\n", freq, rf); - - /* look for optimized calibration frequency */ - bcal = tda18271_powerscan(fe, &rf_default[rf], &rf_freq[rf]); - if (tda_fail(bcal)) - return bcal; - - tda18271_calc_rf_cal(fe, &rf_freq[rf]); - prog_tab[rf] = (s32)regs[R_EB14]; - - if (1 == bcal) - prog_cal[rf] = - (s32)tda18271_calibrate_rf(fe, rf_freq[rf]); - else - prog_cal[rf] = prog_tab[rf]; - - switch (rf) { - case RF1: - map[i].rf_a1 = 0; - map[i].rf_b1 = (prog_cal[RF1] - prog_tab[RF1]); - map[i].rf1 = rf_freq[RF1] / 1000; - break; - case RF2: - dividend = (prog_cal[RF2] - prog_tab[RF2] - - prog_cal[RF1] + prog_tab[RF1]); - divisor = (s32)(rf_freq[RF2] - rf_freq[RF1]) / 1000; - map[i].rf_a1 = (dividend / divisor); - map[i].rf2 = rf_freq[RF2] / 1000; - break; - case RF3: - dividend = (prog_cal[RF3] - prog_tab[RF3] - - prog_cal[RF2] + prog_tab[RF2]); - divisor = (s32)(rf_freq[RF3] - rf_freq[RF2]) / 1000; - map[i].rf_a2 = (dividend / divisor); - map[i].rf_b2 = (prog_cal[RF2] - prog_tab[RF2]); - map[i].rf3 = rf_freq[RF3] / 1000; - break; - default: - BUG(); - } - } - - return 0; -} - -static int tda18271_calc_rf_filter_curve(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned int i; - int ret; - - tda_info("tda18271: performing RF tracking filter calibration\n"); - - /* wait for die temperature stabilization */ - msleep(200); - - ret = tda18271_powerscan_init(fe); - if (tda_fail(ret)) - goto fail; - - /* rf band calibration */ - for (i = 0; priv->rf_cal_state[i].rfmax != 0; i++) { - ret = - tda18271_rf_tracking_filters_init(fe, 1000 * - priv->rf_cal_state[i].rfmax); - if (tda_fail(ret)) - goto fail; - } - - priv->tm_rfcal = tda18271_read_thermometer(fe); -fail: - return ret; -} - -/* ------------------------------------------------------------------ */ - -static int tda18271c2_rf_cal_init(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - int ret; - - /* test RF_CAL_OK to see if we need init */ - if ((regs[R_EP1] & 0x10) == 0) - priv->cal_initialized = false; - - if (priv->cal_initialized) - return 0; - - ret = tda18271_calc_rf_filter_curve(fe); - if (tda_fail(ret)) - goto fail; - - ret = tda18271_por(fe); - if (tda_fail(ret)) - goto fail; - - tda_info("tda18271: RF tracking filter calibration complete\n"); - - priv->cal_initialized = true; - goto end; -fail: - tda_info("tda18271: RF tracking filter calibration failed!\n"); -end: - return ret; -} - -static int tda18271c1_rf_tracking_filter_calibration(struct dvb_frontend *fe, - u32 freq, u32 bw) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - int ret; - u32 N = 0; - - /* calculate bp filter */ - tda18271_calc_bp_filter(fe, &freq); - tda18271_write_regs(fe, R_EP1, 1); - - regs[R_EB4] &= 0x07; - regs[R_EB4] |= 0x60; - tda18271_write_regs(fe, R_EB4, 1); - - regs[R_EB7] = 0x60; - tda18271_write_regs(fe, R_EB7, 1); - - regs[R_EB14] = 0x00; - tda18271_write_regs(fe, R_EB14, 1); - - regs[R_EB20] = 0xcc; - tda18271_write_regs(fe, R_EB20, 1); - - /* set cal mode to RF tracking filter calibration */ - regs[R_EP4] |= 0x03; - - /* calculate cal pll */ - - switch (priv->mode) { - case TDA18271_ANALOG: - N = freq - 1250000; - break; - case TDA18271_DIGITAL: - N = freq + bw / 2; - break; - } - - tda18271_calc_cal_pll(fe, N); - - /* calculate main pll */ - - switch (priv->mode) { - case TDA18271_ANALOG: - N = freq - 250000; - break; - case TDA18271_DIGITAL: - N = freq + bw / 2 + 1000000; - break; - } - - tda18271_calc_main_pll(fe, N); - - ret = tda18271_write_regs(fe, R_EP3, 11); - if (tda_fail(ret)) - return ret; - - msleep(5); /* RF tracking filter calibration initialization */ - - /* search for K,M,CO for RF calibration */ - tda18271_calc_km(fe, &freq); - tda18271_write_regs(fe, R_EB13, 1); - - /* search for rf band */ - tda18271_calc_rf_band(fe, &freq); - - /* search for gain taper */ - tda18271_calc_gain_taper(fe, &freq); - - tda18271_write_regs(fe, R_EP2, 1); - tda18271_write_regs(fe, R_EP1, 1); - tda18271_write_regs(fe, R_EP2, 1); - tda18271_write_regs(fe, R_EP1, 1); - - regs[R_EB4] &= 0x07; - regs[R_EB4] |= 0x40; - tda18271_write_regs(fe, R_EB4, 1); - - regs[R_EB7] = 0x40; - tda18271_write_regs(fe, R_EB7, 1); - msleep(10); /* pll locking */ - - regs[R_EB20] = 0xec; - tda18271_write_regs(fe, R_EB20, 1); - msleep(60); /* RF tracking filter calibration completion */ - - regs[R_EP4] &= ~0x03; /* set cal mode to normal */ - tda18271_write_regs(fe, R_EP4, 1); - - tda18271_write_regs(fe, R_EP1, 1); - - /* RF tracking filter correction for VHF_Low band */ - if (0 == tda18271_calc_rf_cal(fe, &freq)) - tda18271_write_regs(fe, R_EB14, 1); - - return 0; -} - -/* ------------------------------------------------------------------ */ - -static int tda18271_ir_cal_init(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - int ret; - - ret = tda18271_read_regs(fe); - if (tda_fail(ret)) - goto fail; - - /* test IR_CAL_OK to see if we need init */ - if ((regs[R_EP1] & 0x08) == 0) - ret = tda18271_init_regs(fe); -fail: - return ret; -} - -static int tda18271_init(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - int ret; - - mutex_lock(&priv->lock); - - /* full power up */ - ret = tda18271_set_standby_mode(fe, 0, 0, 0); - if (tda_fail(ret)) - goto fail; - - /* initialization */ - ret = tda18271_ir_cal_init(fe); - if (tda_fail(ret)) - goto fail; - - if (priv->id == TDA18271HDC2) - tda18271c2_rf_cal_init(fe); -fail: - mutex_unlock(&priv->lock); - - return ret; -} - -static int tda18271_sleep(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - int ret; - - mutex_lock(&priv->lock); - - /* enter standby mode, with required output features enabled */ - ret = tda18271_toggle_output(fe, 1); - - mutex_unlock(&priv->lock); - - return ret; -} - -/* ------------------------------------------------------------------ */ - -static int tda18271_agc(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - int ret = 0; - - switch (priv->config) { - case 0: - /* no external agc configuration required */ - if (tda18271_debug & DBG_ADV) - tda_dbg("no agc configuration provided\n"); - break; - case 3: - /* switch with GPIO of saa713x */ - tda_dbg("invoking callback\n"); - if (fe->callback) - ret = fe->callback(priv->i2c_props.adap->algo_data, - DVB_FRONTEND_COMPONENT_TUNER, - TDA18271_CALLBACK_CMD_AGC_ENABLE, - priv->mode); - break; - case 1: - case 2: - default: - /* n/a - currently not supported */ - tda_err("unsupported configuration: %d\n", priv->config); - ret = -EINVAL; - break; - } - return ret; -} - -static int tda18271_tune(struct dvb_frontend *fe, - struct tda18271_std_map_item *map, u32 freq, u32 bw) -{ - struct tda18271_priv *priv = fe->tuner_priv; - int ret; - - tda_dbg("freq = %d, ifc = %d, bw = %d, agc_mode = %d, std = %d\n", - freq, map->if_freq, bw, map->agc_mode, map->std); - - ret = tda18271_agc(fe); - if (tda_fail(ret)) - tda_warn("failed to configure agc\n"); - - ret = tda18271_init(fe); - if (tda_fail(ret)) - goto fail; - - mutex_lock(&priv->lock); - - switch (priv->id) { - case TDA18271HDC1: - tda18271c1_rf_tracking_filter_calibration(fe, freq, bw); - break; - case TDA18271HDC2: - tda18271c2_rf_tracking_filters_correction(fe, freq); - break; - } - ret = tda18271_channel_configuration(fe, map, freq, bw); - - mutex_unlock(&priv->lock); -fail: - return ret; -} - -/* ------------------------------------------------------------------ */ - -static int tda18271_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u32 delsys = c->delivery_system; - u32 bw = c->bandwidth_hz; - u32 freq = c->frequency; - struct tda18271_priv *priv = fe->tuner_priv; - struct tda18271_std_map *std_map = &priv->std; - struct tda18271_std_map_item *map; - int ret; - - priv->mode = TDA18271_DIGITAL; - - switch (delsys) { - case SYS_ATSC: - map = &std_map->atsc_6; - bw = 6000000; - break; - case SYS_ISDBT: - case SYS_DVBT: - case SYS_DVBT2: - if (bw <= 6000000) { - map = &std_map->dvbt_6; - } else if (bw <= 7000000) { - map = &std_map->dvbt_7; - } else { - map = &std_map->dvbt_8; - } - break; - case SYS_DVBC_ANNEX_B: - bw = 6000000; - /* falltrough */ - case SYS_DVBC_ANNEX_A: - case SYS_DVBC_ANNEX_C: - if (bw <= 6000000) { - map = &std_map->qam_6; - } else if (bw <= 7000000) { - map = &std_map->qam_7; - } else { - map = &std_map->qam_8; - } - break; - default: - tda_warn("modulation type not supported!\n"); - return -EINVAL; - } - - /* When tuning digital, the analog demod must be tri-stated */ - if (fe->ops.analog_ops.standby) - fe->ops.analog_ops.standby(fe); - - ret = tda18271_tune(fe, map, freq, bw); - - if (tda_fail(ret)) - goto fail; - - priv->if_freq = map->if_freq; - priv->frequency = freq; - priv->bandwidth = bw; -fail: - return ret; -} - -static int tda18271_set_analog_params(struct dvb_frontend *fe, - struct analog_parameters *params) -{ - struct tda18271_priv *priv = fe->tuner_priv; - struct tda18271_std_map *std_map = &priv->std; - struct tda18271_std_map_item *map; - char *mode; - int ret; - u32 freq = params->frequency * 125 * - ((params->mode == V4L2_TUNER_RADIO) ? 1 : 1000) / 2; - - priv->mode = TDA18271_ANALOG; - - if (params->mode == V4L2_TUNER_RADIO) { - map = &std_map->fm_radio; - mode = "fm"; - } else if (params->std & V4L2_STD_MN) { - map = &std_map->atv_mn; - mode = "MN"; - } else if (params->std & V4L2_STD_B) { - map = &std_map->atv_b; - mode = "B"; - } else if (params->std & V4L2_STD_GH) { - map = &std_map->atv_gh; - mode = "GH"; - } else if (params->std & V4L2_STD_PAL_I) { - map = &std_map->atv_i; - mode = "I"; - } else if (params->std & V4L2_STD_DK) { - map = &std_map->atv_dk; - mode = "DK"; - } else if (params->std & V4L2_STD_SECAM_L) { - map = &std_map->atv_l; - mode = "L"; - } else if (params->std & V4L2_STD_SECAM_LC) { - map = &std_map->atv_lc; - mode = "L'"; - } else { - map = &std_map->atv_i; - mode = "xx"; - } - - tda_dbg("setting tda18271 to system %s\n", mode); - - ret = tda18271_tune(fe, map, freq, 0); - - if (tda_fail(ret)) - goto fail; - - priv->if_freq = map->if_freq; - priv->frequency = freq; - priv->bandwidth = 0; -fail: - return ret; -} - -static int tda18271_release(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - - mutex_lock(&tda18271_list_mutex); - - if (priv) - hybrid_tuner_release_state(priv); - - mutex_unlock(&tda18271_list_mutex); - - fe->tuner_priv = NULL; - - return 0; -} - -static int tda18271_get_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct tda18271_priv *priv = fe->tuner_priv; - *frequency = priv->frequency; - return 0; -} - -static int tda18271_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) -{ - struct tda18271_priv *priv = fe->tuner_priv; - *bandwidth = priv->bandwidth; - return 0; -} - -static int tda18271_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct tda18271_priv *priv = fe->tuner_priv; - *frequency = (u32)priv->if_freq * 1000; - return 0; -} - -/* ------------------------------------------------------------------ */ - -#define tda18271_update_std(std_cfg, name) do { \ - if (map->std_cfg.if_freq + \ - map->std_cfg.agc_mode + map->std_cfg.std + \ - map->std_cfg.if_lvl + map->std_cfg.rfagc_top > 0) { \ - tda_dbg("Using custom std config for %s\n", name); \ - memcpy(&std->std_cfg, &map->std_cfg, \ - sizeof(struct tda18271_std_map_item)); \ - } } while (0) - -#define tda18271_dump_std_item(std_cfg, name) do { \ - tda_dbg("(%s) if_freq = %d, agc_mode = %d, std = %d, " \ - "if_lvl = %d, rfagc_top = 0x%02x\n", \ - name, std->std_cfg.if_freq, \ - std->std_cfg.agc_mode, std->std_cfg.std, \ - std->std_cfg.if_lvl, std->std_cfg.rfagc_top); \ - } while (0) - -static int tda18271_dump_std_map(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - struct tda18271_std_map *std = &priv->std; - - tda_dbg("========== STANDARD MAP SETTINGS ==========\n"); - tda18271_dump_std_item(fm_radio, " fm "); - tda18271_dump_std_item(atv_b, "atv b "); - tda18271_dump_std_item(atv_dk, "atv dk"); - tda18271_dump_std_item(atv_gh, "atv gh"); - tda18271_dump_std_item(atv_i, "atv i "); - tda18271_dump_std_item(atv_l, "atv l "); - tda18271_dump_std_item(atv_lc, "atv l'"); - tda18271_dump_std_item(atv_mn, "atv mn"); - tda18271_dump_std_item(atsc_6, "atsc 6"); - tda18271_dump_std_item(dvbt_6, "dvbt 6"); - tda18271_dump_std_item(dvbt_7, "dvbt 7"); - tda18271_dump_std_item(dvbt_8, "dvbt 8"); - tda18271_dump_std_item(qam_6, "qam 6 "); - tda18271_dump_std_item(qam_8, "qam 8 "); - - return 0; -} - -static int tda18271_update_std_map(struct dvb_frontend *fe, - struct tda18271_std_map *map) -{ - struct tda18271_priv *priv = fe->tuner_priv; - struct tda18271_std_map *std = &priv->std; - - if (!map) - return -EINVAL; - - tda18271_update_std(fm_radio, "fm"); - tda18271_update_std(atv_b, "atv b"); - tda18271_update_std(atv_dk, "atv dk"); - tda18271_update_std(atv_gh, "atv gh"); - tda18271_update_std(atv_i, "atv i"); - tda18271_update_std(atv_l, "atv l"); - tda18271_update_std(atv_lc, "atv l'"); - tda18271_update_std(atv_mn, "atv mn"); - tda18271_update_std(atsc_6, "atsc 6"); - tda18271_update_std(dvbt_6, "dvbt 6"); - tda18271_update_std(dvbt_7, "dvbt 7"); - tda18271_update_std(dvbt_8, "dvbt 8"); - tda18271_update_std(qam_6, "qam 6"); - tda18271_update_std(qam_8, "qam 8"); - - return 0; -} - -static int tda18271_get_id(struct dvb_frontend *fe) -{ - struct tda18271_priv *priv = fe->tuner_priv; - unsigned char *regs = priv->tda18271_regs; - char *name; - - mutex_lock(&priv->lock); - tda18271_read_regs(fe); - mutex_unlock(&priv->lock); - - switch (regs[R_ID] & 0x7f) { - case 3: - name = "TDA18271HD/C1"; - priv->id = TDA18271HDC1; - break; - case 4: - name = "TDA18271HD/C2"; - priv->id = TDA18271HDC2; - break; - default: - tda_info("Unknown device (%i) detected @ %d-%04x, device not supported.\n", - regs[R_ID], i2c_adapter_id(priv->i2c_props.adap), - priv->i2c_props.addr); - return -EINVAL; - } - - tda_info("%s detected @ %d-%04x\n", name, - i2c_adapter_id(priv->i2c_props.adap), priv->i2c_props.addr); - - return 0; -} - -static int tda18271_setup_configuration(struct dvb_frontend *fe, - struct tda18271_config *cfg) -{ - struct tda18271_priv *priv = fe->tuner_priv; - - priv->gate = (cfg) ? cfg->gate : TDA18271_GATE_AUTO; - priv->role = (cfg) ? cfg->role : TDA18271_MASTER; - priv->config = (cfg) ? cfg->config : 0; - priv->small_i2c = (cfg) ? - cfg->small_i2c : TDA18271_39_BYTE_CHUNK_INIT; - priv->output_opt = (cfg) ? - cfg->output_opt : TDA18271_OUTPUT_LT_XT_ON; - - return 0; -} - -static inline int tda18271_need_cal_on_startup(struct tda18271_config *cfg) -{ - /* tda18271_cal_on_startup == -1 when cal module option is unset */ - return ((tda18271_cal_on_startup == -1) ? - /* honor configuration setting */ - ((cfg) && (cfg->rf_cal_on_startup)) : - /* module option overrides configuration setting */ - (tda18271_cal_on_startup)) ? 1 : 0; -} - -static int tda18271_set_config(struct dvb_frontend *fe, void *priv_cfg) -{ - struct tda18271_config *cfg = (struct tda18271_config *) priv_cfg; - - tda18271_setup_configuration(fe, cfg); - - if (tda18271_need_cal_on_startup(cfg)) - tda18271_init(fe); - - /* override default std map with values in config struct */ - if ((cfg) && (cfg->std_map)) - tda18271_update_std_map(fe, cfg->std_map); - - return 0; -} - -static const struct dvb_tuner_ops tda18271_tuner_ops = { - .info = { - .name = "NXP TDA18271HD", - .frequency_min = 45000000, - .frequency_max = 864000000, - .frequency_step = 62500 - }, - .init = tda18271_init, - .sleep = tda18271_sleep, - .set_params = tda18271_set_params, - .set_analog_params = tda18271_set_analog_params, - .release = tda18271_release, - .set_config = tda18271_set_config, - .get_frequency = tda18271_get_frequency, - .get_bandwidth = tda18271_get_bandwidth, - .get_if_frequency = tda18271_get_if_frequency, -}; - -struct dvb_frontend *tda18271_attach(struct dvb_frontend *fe, u8 addr, - struct i2c_adapter *i2c, - struct tda18271_config *cfg) -{ - struct tda18271_priv *priv = NULL; - int instance, ret; - - mutex_lock(&tda18271_list_mutex); - - instance = hybrid_tuner_request_state(struct tda18271_priv, priv, - hybrid_tuner_instance_list, - i2c, addr, "tda18271"); - switch (instance) { - case 0: - goto fail; - case 1: - /* new tuner instance */ - fe->tuner_priv = priv; - - tda18271_setup_configuration(fe, cfg); - - priv->cal_initialized = false; - mutex_init(&priv->lock); - - ret = tda18271_get_id(fe); - if (tda_fail(ret)) - goto fail; - - ret = tda18271_assign_map_layout(fe); - if (tda_fail(ret)) - goto fail; - - mutex_lock(&priv->lock); - tda18271_init_regs(fe); - - if ((tda18271_need_cal_on_startup(cfg)) && - (priv->id == TDA18271HDC2)) - tda18271c2_rf_cal_init(fe); - - mutex_unlock(&priv->lock); - break; - default: - /* existing tuner instance */ - fe->tuner_priv = priv; - - /* allow dvb driver to override configuration settings */ - if (cfg) { - if (cfg->gate != TDA18271_GATE_ANALOG) - priv->gate = cfg->gate; - if (cfg->role) - priv->role = cfg->role; - if (cfg->config) - priv->config = cfg->config; - if (cfg->small_i2c) - priv->small_i2c = cfg->small_i2c; - if (cfg->output_opt) - priv->output_opt = cfg->output_opt; - if (cfg->std_map) - tda18271_update_std_map(fe, cfg->std_map); - } - if (tda18271_need_cal_on_startup(cfg)) - tda18271_init(fe); - break; - } - - /* override default std map with values in config struct */ - if ((cfg) && (cfg->std_map)) - tda18271_update_std_map(fe, cfg->std_map); - - mutex_unlock(&tda18271_list_mutex); - - memcpy(&fe->ops.tuner_ops, &tda18271_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - if (tda18271_debug & (DBG_MAP | DBG_ADV)) - tda18271_dump_std_map(fe); - - return fe; -fail: - mutex_unlock(&tda18271_list_mutex); - - tda18271_release(fe); - return NULL; -} -EXPORT_SYMBOL_GPL(tda18271_attach); -MODULE_DESCRIPTION("NXP TDA18271HD analog / digital tuner driver"); -MODULE_AUTHOR("Michael Krufky "); -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.4"); - -/* - * Overrides for Emacs so that we follow Linus's tabbing style. - * --------------------------------------------------------------------------- - * Local variables: - * c-basic-offset: 8 - * End: - */ diff --git a/drivers/media/common/tuners/tda18271.h b/drivers/media/common/tuners/tda18271.h deleted file mode 100644 index 640bae4e6a5a..000000000000 --- a/drivers/media/common/tuners/tda18271.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - tda18271.h - header for the Philips / NXP TDA18271 silicon tuner - - Copyright (C) 2007, 2008 Michael Krufky - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#ifndef __TDA18271_H__ -#define __TDA18271_H__ - -#include -#include "dvb_frontend.h" - -struct tda18271_std_map_item { - u16 if_freq; - - /* EP3[4:3] */ - unsigned int agc_mode:2; - /* EP3[2:0] */ - unsigned int std:3; - /* EP4[7] */ - unsigned int fm_rfn:1; - /* EP4[4:2] */ - unsigned int if_lvl:3; - /* EB22[6:0] */ - unsigned int rfagc_top:7; -}; - -struct tda18271_std_map { - struct tda18271_std_map_item fm_radio; - struct tda18271_std_map_item atv_b; - struct tda18271_std_map_item atv_dk; - struct tda18271_std_map_item atv_gh; - struct tda18271_std_map_item atv_i; - struct tda18271_std_map_item atv_l; - struct tda18271_std_map_item atv_lc; - struct tda18271_std_map_item atv_mn; - struct tda18271_std_map_item atsc_6; - struct tda18271_std_map_item dvbt_6; - struct tda18271_std_map_item dvbt_7; - struct tda18271_std_map_item dvbt_8; - struct tda18271_std_map_item qam_6; - struct tda18271_std_map_item qam_7; - struct tda18271_std_map_item qam_8; -}; - -enum tda18271_role { - TDA18271_MASTER = 0, - TDA18271_SLAVE, -}; - -enum tda18271_i2c_gate { - TDA18271_GATE_AUTO = 0, - TDA18271_GATE_ANALOG, - TDA18271_GATE_DIGITAL, -}; - -enum tda18271_output_options { - /* slave tuner output & loop thru & xtal oscillator always on */ - TDA18271_OUTPUT_LT_XT_ON = 0, - - /* slave tuner output loop thru off */ - TDA18271_OUTPUT_LT_OFF = 1, - - /* xtal oscillator off */ - TDA18271_OUTPUT_XT_OFF = 2, -}; - -enum tda18271_small_i2c { - TDA18271_39_BYTE_CHUNK_INIT = 0, - TDA18271_16_BYTE_CHUNK_INIT = 16, - TDA18271_08_BYTE_CHUNK_INIT = 8, - TDA18271_03_BYTE_CHUNK_INIT = 3, -}; - -struct tda18271_config { - /* override default if freq / std settings (optional) */ - struct tda18271_std_map *std_map; - - /* master / slave tuner: master uses main pll, slave uses cal pll */ - enum tda18271_role role; - - /* use i2c gate provided by analog or digital demod */ - enum tda18271_i2c_gate gate; - - /* output options that can be disabled */ - enum tda18271_output_options output_opt; - - /* some i2c providers can't write all 39 registers at once */ - enum tda18271_small_i2c small_i2c; - - /* force rf tracking filter calibration on startup */ - unsigned int rf_cal_on_startup:1; - - /* interface to saa713x / tda829x */ - unsigned int config; -}; - -#define TDA18271_CALLBACK_CMD_AGC_ENABLE 0 - -enum tda18271_mode { - TDA18271_ANALOG = 0, - TDA18271_DIGITAL, -}; - -#if defined(CONFIG_MEDIA_TUNER_TDA18271) || (defined(CONFIG_MEDIA_TUNER_TDA18271_MODULE) && defined(MODULE)) -extern struct dvb_frontend *tda18271_attach(struct dvb_frontend *fe, u8 addr, - struct i2c_adapter *i2c, - struct tda18271_config *cfg); -#else -static inline struct dvb_frontend *tda18271_attach(struct dvb_frontend *fe, - u8 addr, - struct i2c_adapter *i2c, - struct tda18271_config *cfg) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif - -#endif /* __TDA18271_H__ */ diff --git a/drivers/media/common/tuners/tua9001.c b/drivers/media/common/tuners/tua9001.c deleted file mode 100644 index de2607084672..000000000000 --- a/drivers/media/common/tuners/tua9001.c +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Infineon TUA 9001 silicon tuner driver - * - * Copyright (C) 2009 Antti Palosaari - * - * 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. - */ - -#include "tua9001.h" -#include "tua9001_priv.h" - -/* write register */ -static int tua9001_wr_reg(struct tua9001_priv *priv, u8 reg, u16 val) -{ - int ret; - u8 buf[3] = { reg, (val >> 8) & 0xff, (val >> 0) & 0xff }; - struct i2c_msg msg[1] = { - { - .addr = priv->cfg->i2c_addr, - .flags = 0, - .len = sizeof(buf), - .buf = buf, - } - }; - - ret = i2c_transfer(priv->i2c, msg, 1); - if (ret == 1) { - ret = 0; - } else { - printk(KERN_WARNING "%s: I2C wr failed=%d reg=%02x\n", - __func__, ret, reg); - ret = -EREMOTEIO; - } - - return ret; -} - -static int tua9001_release(struct dvb_frontend *fe) -{ - kfree(fe->tuner_priv); - fe->tuner_priv = NULL; - - return 0; -} - -static int tua9001_init(struct dvb_frontend *fe) -{ - struct tua9001_priv *priv = fe->tuner_priv; - int ret = 0; - u8 i; - struct reg_val data[] = { - { 0x1e, 0x6512 }, - { 0x25, 0xb888 }, - { 0x39, 0x5460 }, - { 0x3b, 0x00c0 }, - { 0x3a, 0xf000 }, - { 0x08, 0x0000 }, - { 0x32, 0x0030 }, - { 0x41, 0x703a }, - { 0x40, 0x1c78 }, - { 0x2c, 0x1c00 }, - { 0x36, 0xc013 }, - { 0x37, 0x6f18 }, - { 0x27, 0x0008 }, - { 0x2a, 0x0001 }, - { 0x34, 0x0a40 }, - }; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c-gate */ - - for (i = 0; i < ARRAY_SIZE(data); i++) { - ret = tua9001_wr_reg(priv, data[i].reg, data[i].val); - if (ret) - break; - } - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c-gate */ - - if (ret < 0) - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int tua9001_set_params(struct dvb_frontend *fe) -{ - struct tua9001_priv *priv = fe->tuner_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i; - u16 val; - u32 frequency; - struct reg_val data[2]; - - pr_debug("%s: delivery_system=%d frequency=%d bandwidth_hz=%d\n", - __func__, c->delivery_system, c->frequency, - c->bandwidth_hz); - - switch (c->delivery_system) { - case SYS_DVBT: - switch (c->bandwidth_hz) { - case 8000000: - val = 0x0000; - break; - case 7000000: - val = 0x1000; - break; - case 6000000: - val = 0x2000; - break; - case 5000000: - val = 0x3000; - break; - default: - ret = -EINVAL; - goto err; - } - break; - default: - ret = -EINVAL; - goto err; - } - - data[0].reg = 0x04; - data[0].val = val; - - frequency = (c->frequency - 150000000); - frequency /= 100; - frequency *= 48; - frequency /= 10000; - - data[1].reg = 0x1f; - data[1].val = frequency; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c-gate */ - - for (i = 0; i < ARRAY_SIZE(data); i++) { - ret = tua9001_wr_reg(priv, data[i].reg, data[i].val); - if (ret < 0) - break; - } - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c-gate */ - -err: - if (ret < 0) - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int tua9001_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - *frequency = 0; /* Zero-IF */ - - return 0; -} - -static const struct dvb_tuner_ops tua9001_tuner_ops = { - .info = { - .name = "Infineon TUA 9001", - - .frequency_min = 170000000, - .frequency_max = 862000000, - .frequency_step = 0, - }, - - .release = tua9001_release, - - .init = tua9001_init, - .set_params = tua9001_set_params, - - .get_if_frequency = tua9001_get_if_frequency, -}; - -struct dvb_frontend *tua9001_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, struct tua9001_config *cfg) -{ - struct tua9001_priv *priv = NULL; - - priv = kzalloc(sizeof(struct tua9001_priv), GFP_KERNEL); - if (priv == NULL) - return NULL; - - priv->cfg = cfg; - priv->i2c = i2c; - - printk(KERN_INFO "Infineon TUA 9001 successfully attached."); - - memcpy(&fe->ops.tuner_ops, &tua9001_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - fe->tuner_priv = priv; - return fe; -} -EXPORT_SYMBOL(tua9001_attach); - -MODULE_DESCRIPTION("Infineon TUA 9001 silicon tuner driver"); -MODULE_AUTHOR("Antti Palosaari "); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/tuners/tua9001.h b/drivers/media/common/tuners/tua9001.h deleted file mode 100644 index 38d6ae76b1d6..000000000000 --- a/drivers/media/common/tuners/tua9001.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Infineon TUA 9001 silicon tuner driver - * - * Copyright (C) 2009 Antti Palosaari - * - * 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. - */ - -#ifndef TUA9001_H -#define TUA9001_H - -#include "dvb_frontend.h" - -struct tua9001_config { - /* - * I2C address - */ - u8 i2c_addr; -}; - -#if defined(CONFIG_MEDIA_TUNER_TUA9001) || \ - (defined(CONFIG_MEDIA_TUNER_TUA9001_MODULE) && defined(MODULE)) -extern struct dvb_frontend *tua9001_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, struct tua9001_config *cfg); -#else -static inline struct dvb_frontend *tua9001_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, struct tua9001_config *cfg) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif - -#endif diff --git a/drivers/media/common/tuners/tuner-xc2028.c b/drivers/media/common/tuners/tuner-xc2028.c deleted file mode 100644 index ea0550eafe7d..000000000000 --- a/drivers/media/common/tuners/tuner-xc2028.c +++ /dev/null @@ -1,1510 +0,0 @@ -/* tuner-xc2028 - * - * Copyright (c) 2007-2008 Mauro Carvalho Chehab (mchehab@infradead.org) - * - * Copyright (c) 2007 Michel Ludwig (michel.ludwig@gmail.com) - * - frontend interface - * - * This code is placed under the terms of the GNU General Public License v2 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "tuner-i2c.h" -#include "tuner-xc2028.h" -#include "tuner-xc2028-types.h" - -#include -#include "dvb_frontend.h" - -/* Registers (Write-only) */ -#define XREG_INIT 0x00 -#define XREG_RF_FREQ 0x02 -#define XREG_POWER_DOWN 0x08 - -/* Registers (Read-only) */ -#define XREG_FREQ_ERROR 0x01 -#define XREG_LOCK 0x02 -#define XREG_VERSION 0x04 -#define XREG_PRODUCT_ID 0x08 -#define XREG_HSYNC_FREQ 0x10 -#define XREG_FRAME_LINES 0x20 -#define XREG_SNR 0x40 - -#define XREG_ADC_ENV 0x0100 - -static int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "enable verbose debug messages"); - -static int no_poweroff; -module_param(no_poweroff, int, 0644); -MODULE_PARM_DESC(no_poweroff, "0 (default) powers device off when not used.\n" - "1 keep device energized and with tuner ready all the times.\n" - " Faster, but consumes more power and keeps the device hotter\n"); - -static char audio_std[8]; -module_param_string(audio_std, audio_std, sizeof(audio_std), 0); -MODULE_PARM_DESC(audio_std, - "Audio standard. XC3028 audio decoder explicitly " - "needs to know what audio\n" - "standard is needed for some video standards with audio A2 or NICAM.\n" - "The valid values are:\n" - "A2\n" - "A2/A\n" - "A2/B\n" - "NICAM\n" - "NICAM/A\n" - "NICAM/B\n"); - -static char firmware_name[30]; -module_param_string(firmware_name, firmware_name, sizeof(firmware_name), 0); -MODULE_PARM_DESC(firmware_name, "Firmware file name. Allows overriding the " - "default firmware name\n"); - -static LIST_HEAD(hybrid_tuner_instance_list); -static DEFINE_MUTEX(xc2028_list_mutex); - -/* struct for storing firmware table */ -struct firmware_description { - unsigned int type; - v4l2_std_id id; - __u16 int_freq; - unsigned char *ptr; - unsigned int size; -}; - -struct firmware_properties { - unsigned int type; - v4l2_std_id id; - v4l2_std_id std_req; - __u16 int_freq; - unsigned int scode_table; - int scode_nr; -}; - -enum xc2028_state { - XC2028_NO_FIRMWARE = 0, - XC2028_WAITING_FIRMWARE, - XC2028_ACTIVE, - XC2028_SLEEP, - XC2028_NODEV, -}; - -struct xc2028_data { - struct list_head hybrid_tuner_instance_list; - struct tuner_i2c_props i2c_props; - __u32 frequency; - - enum xc2028_state state; - const char *fname; - - struct firmware_description *firm; - int firm_size; - __u16 firm_version; - - __u16 hwmodel; - __u16 hwvers; - - struct xc2028_ctrl ctrl; - - struct firmware_properties cur_fw; - - struct mutex lock; -}; - -#define i2c_send(priv, buf, size) ({ \ - int _rc; \ - _rc = tuner_i2c_xfer_send(&priv->i2c_props, buf, size); \ - if (size != _rc) \ - tuner_info("i2c output error: rc = %d (should be %d)\n",\ - _rc, (int)size); \ - if (priv->ctrl.msleep) \ - msleep(priv->ctrl.msleep); \ - _rc; \ -}) - -#define i2c_rcv(priv, buf, size) ({ \ - int _rc; \ - _rc = tuner_i2c_xfer_recv(&priv->i2c_props, buf, size); \ - if (size != _rc) \ - tuner_err("i2c input error: rc = %d (should be %d)\n", \ - _rc, (int)size); \ - _rc; \ -}) - -#define i2c_send_recv(priv, obuf, osize, ibuf, isize) ({ \ - int _rc; \ - _rc = tuner_i2c_xfer_send_recv(&priv->i2c_props, obuf, osize, \ - ibuf, isize); \ - if (isize != _rc) \ - tuner_err("i2c input error: rc = %d (should be %d)\n", \ - _rc, (int)isize); \ - if (priv->ctrl.msleep) \ - msleep(priv->ctrl.msleep); \ - _rc; \ -}) - -#define send_seq(priv, data...) ({ \ - static u8 _val[] = data; \ - int _rc; \ - if (sizeof(_val) != \ - (_rc = tuner_i2c_xfer_send(&priv->i2c_props, \ - _val, sizeof(_val)))) { \ - tuner_err("Error on line %d: %d\n", __LINE__, _rc); \ - } else if (priv->ctrl.msleep) \ - msleep(priv->ctrl.msleep); \ - _rc; \ -}) - -static int xc2028_get_reg(struct xc2028_data *priv, u16 reg, u16 *val) -{ - unsigned char buf[2]; - unsigned char ibuf[2]; - - tuner_dbg("%s %04x called\n", __func__, reg); - - buf[0] = reg >> 8; - buf[1] = (unsigned char) reg; - - if (i2c_send_recv(priv, buf, 2, ibuf, 2) != 2) - return -EIO; - - *val = (ibuf[1]) | (ibuf[0] << 8); - return 0; -} - -#define dump_firm_type(t) dump_firm_type_and_int_freq(t, 0) -static void dump_firm_type_and_int_freq(unsigned int type, u16 int_freq) -{ - if (type & BASE) - printk("BASE "); - if (type & INIT1) - printk("INIT1 "); - if (type & F8MHZ) - printk("F8MHZ "); - if (type & MTS) - printk("MTS "); - if (type & D2620) - printk("D2620 "); - if (type & D2633) - printk("D2633 "); - if (type & DTV6) - printk("DTV6 "); - if (type & QAM) - printk("QAM "); - if (type & DTV7) - printk("DTV7 "); - if (type & DTV78) - printk("DTV78 "); - if (type & DTV8) - printk("DTV8 "); - if (type & FM) - printk("FM "); - if (type & INPUT1) - printk("INPUT1 "); - if (type & LCD) - printk("LCD "); - if (type & NOGD) - printk("NOGD "); - if (type & MONO) - printk("MONO "); - if (type & ATSC) - printk("ATSC "); - if (type & IF) - printk("IF "); - if (type & LG60) - printk("LG60 "); - if (type & ATI638) - printk("ATI638 "); - if (type & OREN538) - printk("OREN538 "); - if (type & OREN36) - printk("OREN36 "); - if (type & TOYOTA388) - printk("TOYOTA388 "); - if (type & TOYOTA794) - printk("TOYOTA794 "); - if (type & DIBCOM52) - printk("DIBCOM52 "); - if (type & ZARLINK456) - printk("ZARLINK456 "); - if (type & CHINA) - printk("CHINA "); - if (type & F6MHZ) - printk("F6MHZ "); - if (type & INPUT2) - printk("INPUT2 "); - if (type & SCODE) - printk("SCODE "); - if (type & HAS_IF) - printk("HAS_IF_%d ", int_freq); -} - -static v4l2_std_id parse_audio_std_option(void) -{ - if (strcasecmp(audio_std, "A2") == 0) - return V4L2_STD_A2; - if (strcasecmp(audio_std, "A2/A") == 0) - return V4L2_STD_A2_A; - if (strcasecmp(audio_std, "A2/B") == 0) - return V4L2_STD_A2_B; - if (strcasecmp(audio_std, "NICAM") == 0) - return V4L2_STD_NICAM; - if (strcasecmp(audio_std, "NICAM/A") == 0) - return V4L2_STD_NICAM_A; - if (strcasecmp(audio_std, "NICAM/B") == 0) - return V4L2_STD_NICAM_B; - - return 0; -} - -static int check_device_status(struct xc2028_data *priv) -{ - switch (priv->state) { - case XC2028_NO_FIRMWARE: - case XC2028_WAITING_FIRMWARE: - return -EAGAIN; - case XC2028_ACTIVE: - case XC2028_SLEEP: - return 0; - case XC2028_NODEV: - return -ENODEV; - } - return 0; -} - -static void free_firmware(struct xc2028_data *priv) -{ - int i; - tuner_dbg("%s called\n", __func__); - - if (!priv->firm) - return; - - for (i = 0; i < priv->firm_size; i++) - kfree(priv->firm[i].ptr); - - kfree(priv->firm); - - priv->firm = NULL; - priv->firm_size = 0; - priv->state = XC2028_NO_FIRMWARE; - - memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); -} - -static int load_all_firmwares(struct dvb_frontend *fe, - const struct firmware *fw) -{ - struct xc2028_data *priv = fe->tuner_priv; - const unsigned char *p, *endp; - int rc = 0; - int n, n_array; - char name[33]; - - tuner_dbg("%s called\n", __func__); - - p = fw->data; - endp = p + fw->size; - - if (fw->size < sizeof(name) - 1 + 2 + 2) { - tuner_err("Error: firmware file %s has invalid size!\n", - priv->fname); - goto corrupt; - } - - memcpy(name, p, sizeof(name) - 1); - name[sizeof(name) - 1] = 0; - p += sizeof(name) - 1; - - priv->firm_version = get_unaligned_le16(p); - p += 2; - - n_array = get_unaligned_le16(p); - p += 2; - - tuner_info("Loading %d firmware images from %s, type: %s, ver %d.%d\n", - n_array, priv->fname, name, - priv->firm_version >> 8, priv->firm_version & 0xff); - - priv->firm = kcalloc(n_array, sizeof(*priv->firm), GFP_KERNEL); - if (priv->firm == NULL) { - tuner_err("Not enough memory to load firmware file.\n"); - rc = -ENOMEM; - goto err; - } - priv->firm_size = n_array; - - n = -1; - while (p < endp) { - __u32 type, size; - v4l2_std_id id; - __u16 int_freq = 0; - - n++; - if (n >= n_array) { - tuner_err("More firmware images in file than " - "were expected!\n"); - goto corrupt; - } - - /* Checks if there's enough bytes to read */ - if (endp - p < sizeof(type) + sizeof(id) + sizeof(size)) - goto header; - - type = get_unaligned_le32(p); - p += sizeof(type); - - id = get_unaligned_le64(p); - p += sizeof(id); - - if (type & HAS_IF) { - int_freq = get_unaligned_le16(p); - p += sizeof(int_freq); - if (endp - p < sizeof(size)) - goto header; - } - - size = get_unaligned_le32(p); - p += sizeof(size); - - if (!size || size > endp - p) { - tuner_err("Firmware type "); - dump_firm_type(type); - printk("(%x), id %llx is corrupted " - "(size=%d, expected %d)\n", - type, (unsigned long long)id, - (unsigned)(endp - p), size); - goto corrupt; - } - - priv->firm[n].ptr = kzalloc(size, GFP_KERNEL); - if (priv->firm[n].ptr == NULL) { - tuner_err("Not enough memory to load firmware file.\n"); - rc = -ENOMEM; - goto err; - } - tuner_dbg("Reading firmware type "); - if (debug) { - dump_firm_type_and_int_freq(type, int_freq); - printk("(%x), id %llx, size=%d.\n", - type, (unsigned long long)id, size); - } - - memcpy(priv->firm[n].ptr, p, size); - priv->firm[n].type = type; - priv->firm[n].id = id; - priv->firm[n].size = size; - priv->firm[n].int_freq = int_freq; - - p += size; - } - - if (n + 1 != priv->firm_size) { - tuner_err("Firmware file is incomplete!\n"); - goto corrupt; - } - - goto done; - -header: - tuner_err("Firmware header is incomplete!\n"); -corrupt: - rc = -EINVAL; - tuner_err("Error: firmware file is corrupted!\n"); - -err: - tuner_info("Releasing partially loaded firmware file.\n"); - free_firmware(priv); - -done: - if (rc == 0) - tuner_dbg("Firmware files loaded.\n"); - else - priv->state = XC2028_NODEV; - - return rc; -} - -static int seek_firmware(struct dvb_frontend *fe, unsigned int type, - v4l2_std_id *id) -{ - struct xc2028_data *priv = fe->tuner_priv; - int i, best_i = -1, best_nr_matches = 0; - unsigned int type_mask = 0; - - tuner_dbg("%s called, want type=", __func__); - if (debug) { - dump_firm_type(type); - printk("(%x), id %016llx.\n", type, (unsigned long long)*id); - } - - if (!priv->firm) { - tuner_err("Error! firmware not loaded\n"); - return -EINVAL; - } - - if (((type & ~SCODE) == 0) && (*id == 0)) - *id = V4L2_STD_PAL; - - if (type & BASE) - type_mask = BASE_TYPES; - else if (type & SCODE) { - type &= SCODE_TYPES; - type_mask = SCODE_TYPES & ~HAS_IF; - } else if (type & DTV_TYPES) - type_mask = DTV_TYPES; - else if (type & STD_SPECIFIC_TYPES) - type_mask = STD_SPECIFIC_TYPES; - - type &= type_mask; - - if (!(type & SCODE)) - type_mask = ~0; - - /* Seek for exact match */ - for (i = 0; i < priv->firm_size; i++) { - if ((type == (priv->firm[i].type & type_mask)) && - (*id == priv->firm[i].id)) - goto found; - } - - /* Seek for generic video standard match */ - for (i = 0; i < priv->firm_size; i++) { - v4l2_std_id match_mask; - int nr_matches; - - if (type != (priv->firm[i].type & type_mask)) - continue; - - match_mask = *id & priv->firm[i].id; - if (!match_mask) - continue; - - if ((*id & match_mask) == *id) - goto found; /* Supports all the requested standards */ - - nr_matches = hweight64(match_mask); - if (nr_matches > best_nr_matches) { - best_nr_matches = nr_matches; - best_i = i; - } - } - - if (best_nr_matches > 0) { - tuner_dbg("Selecting best matching firmware (%d bits) for " - "type=", best_nr_matches); - dump_firm_type(type); - printk("(%x), id %016llx:\n", type, (unsigned long long)*id); - i = best_i; - goto found; - } - - /*FIXME: Would make sense to seek for type "hint" match ? */ - - i = -ENOENT; - goto ret; - -found: - *id = priv->firm[i].id; - -ret: - tuner_dbg("%s firmware for type=", (i < 0) ? "Can't find" : "Found"); - if (debug) { - dump_firm_type(type); - printk("(%x), id %016llx.\n", type, (unsigned long long)*id); - } - return i; -} - -static inline int do_tuner_callback(struct dvb_frontend *fe, int cmd, int arg) -{ - struct xc2028_data *priv = fe->tuner_priv; - - /* analog side (tuner-core) uses i2c_adap->algo_data. - * digital side is not guaranteed to have algo_data defined. - * - * digital side will always have fe->dvb defined. - * analog side (tuner-core) doesn't (yet) define fe->dvb. - */ - - return (!fe->callback) ? -EINVAL : - fe->callback(((fe->dvb) && (fe->dvb->priv)) ? - fe->dvb->priv : priv->i2c_props.adap->algo_data, - DVB_FRONTEND_COMPONENT_TUNER, cmd, arg); -} - -static int load_firmware(struct dvb_frontend *fe, unsigned int type, - v4l2_std_id *id) -{ - struct xc2028_data *priv = fe->tuner_priv; - int pos, rc; - unsigned char *p, *endp, buf[priv->ctrl.max_len]; - - tuner_dbg("%s called\n", __func__); - - pos = seek_firmware(fe, type, id); - if (pos < 0) - return pos; - - tuner_info("Loading firmware for type="); - dump_firm_type(priv->firm[pos].type); - printk("(%x), id %016llx.\n", priv->firm[pos].type, - (unsigned long long)*id); - - p = priv->firm[pos].ptr; - endp = p + priv->firm[pos].size; - - while (p < endp) { - __u16 size; - - /* Checks if there's enough bytes to read */ - if (p + sizeof(size) > endp) { - tuner_err("Firmware chunk size is wrong\n"); - return -EINVAL; - } - - size = le16_to_cpu(*(__u16 *) p); - p += sizeof(size); - - if (size == 0xffff) - return 0; - - if (!size) { - /* Special callback command received */ - rc = do_tuner_callback(fe, XC2028_TUNER_RESET, 0); - if (rc < 0) { - tuner_err("Error at RESET code %d\n", - (*p) & 0x7f); - return -EINVAL; - } - continue; - } - if (size >= 0xff00) { - switch (size) { - case 0xff00: - rc = do_tuner_callback(fe, XC2028_RESET_CLK, 0); - if (rc < 0) { - tuner_err("Error at RESET code %d\n", - (*p) & 0x7f); - return -EINVAL; - } - break; - default: - tuner_info("Invalid RESET code %d\n", - size & 0x7f); - return -EINVAL; - - } - continue; - } - - /* Checks for a sleep command */ - if (size & 0x8000) { - msleep(size & 0x7fff); - continue; - } - - if ((size + p > endp)) { - tuner_err("missing bytes: need %d, have %d\n", - size, (int)(endp - p)); - return -EINVAL; - } - - buf[0] = *p; - p++; - size--; - - /* Sends message chunks */ - while (size > 0) { - int len = (size < priv->ctrl.max_len - 1) ? - size : priv->ctrl.max_len - 1; - - memcpy(buf + 1, p, len); - - rc = i2c_send(priv, buf, len + 1); - if (rc < 0) { - tuner_err("%d returned from send\n", rc); - return -EINVAL; - } - - p += len; - size -= len; - } - - /* silently fail if the frontend doesn't support I2C flush */ - rc = do_tuner_callback(fe, XC2028_I2C_FLUSH, 0); - if ((rc < 0) && (rc != -EINVAL)) { - tuner_err("error executing flush: %d\n", rc); - return rc; - } - } - return 0; -} - -static int load_scode(struct dvb_frontend *fe, unsigned int type, - v4l2_std_id *id, __u16 int_freq, int scode) -{ - struct xc2028_data *priv = fe->tuner_priv; - int pos, rc; - unsigned char *p; - - tuner_dbg("%s called\n", __func__); - - if (!int_freq) { - pos = seek_firmware(fe, type, id); - if (pos < 0) - return pos; - } else { - for (pos = 0; pos < priv->firm_size; pos++) { - if ((priv->firm[pos].int_freq == int_freq) && - (priv->firm[pos].type & HAS_IF)) - break; - } - if (pos == priv->firm_size) - return -ENOENT; - } - - p = priv->firm[pos].ptr; - - if (priv->firm[pos].type & HAS_IF) { - if (priv->firm[pos].size != 12 * 16 || scode >= 16) - return -EINVAL; - p += 12 * scode; - } else { - /* 16 SCODE entries per file; each SCODE entry is 12 bytes and - * has a 2-byte size header in the firmware format. */ - if (priv->firm[pos].size != 14 * 16 || scode >= 16 || - le16_to_cpu(*(__u16 *)(p + 14 * scode)) != 12) - return -EINVAL; - p += 14 * scode + 2; - } - - tuner_info("Loading SCODE for type="); - dump_firm_type_and_int_freq(priv->firm[pos].type, - priv->firm[pos].int_freq); - printk("(%x), id %016llx.\n", priv->firm[pos].type, - (unsigned long long)*id); - - if (priv->firm_version < 0x0202) - rc = send_seq(priv, {0x20, 0x00, 0x00, 0x00}); - else - rc = send_seq(priv, {0xa0, 0x00, 0x00, 0x00}); - if (rc < 0) - return -EIO; - - rc = i2c_send(priv, p, 12); - if (rc < 0) - return -EIO; - - rc = send_seq(priv, {0x00, 0x8c}); - if (rc < 0) - return -EIO; - - return 0; -} - -static int check_firmware(struct dvb_frontend *fe, unsigned int type, - v4l2_std_id std, __u16 int_freq) -{ - struct xc2028_data *priv = fe->tuner_priv; - struct firmware_properties new_fw; - int rc, retry_count = 0; - u16 version, hwmodel; - v4l2_std_id std0; - - tuner_dbg("%s called\n", __func__); - - rc = check_device_status(priv); - if (rc < 0) - return rc; - - if (priv->ctrl.mts && !(type & FM)) - type |= MTS; - -retry: - new_fw.type = type; - new_fw.id = std; - new_fw.std_req = std; - new_fw.scode_table = SCODE | priv->ctrl.scode_table; - new_fw.scode_nr = 0; - new_fw.int_freq = int_freq; - - tuner_dbg("checking firmware, user requested type="); - if (debug) { - dump_firm_type(new_fw.type); - printk("(%x), id %016llx, ", new_fw.type, - (unsigned long long)new_fw.std_req); - if (!int_freq) { - printk("scode_tbl "); - dump_firm_type(priv->ctrl.scode_table); - printk("(%x), ", priv->ctrl.scode_table); - } else - printk("int_freq %d, ", new_fw.int_freq); - printk("scode_nr %d\n", new_fw.scode_nr); - } - - /* - * No need to reload base firmware if it matches and if the tuner - * is not at sleep mode - */ - if ((priv->state == XC2028_ACTIVE) && - (((BASE | new_fw.type) & BASE_TYPES) == - (priv->cur_fw.type & BASE_TYPES))) { - tuner_dbg("BASE firmware not changed.\n"); - goto skip_base; - } - - /* Updating BASE - forget about all currently loaded firmware */ - memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); - - /* Reset is needed before loading firmware */ - rc = do_tuner_callback(fe, XC2028_TUNER_RESET, 0); - if (rc < 0) - goto fail; - - /* BASE firmwares are all std0 */ - std0 = 0; - rc = load_firmware(fe, BASE | new_fw.type, &std0); - if (rc < 0) { - tuner_err("Error %d while loading base firmware\n", - rc); - goto fail; - } - - /* Load INIT1, if needed */ - tuner_dbg("Load init1 firmware, if exists\n"); - - rc = load_firmware(fe, BASE | INIT1 | new_fw.type, &std0); - if (rc == -ENOENT) - rc = load_firmware(fe, (BASE | INIT1 | new_fw.type) & ~F8MHZ, - &std0); - if (rc < 0 && rc != -ENOENT) { - tuner_err("Error %d while loading init1 firmware\n", - rc); - goto fail; - } - -skip_base: - /* - * No need to reload standard specific firmware if base firmware - * was not reloaded and requested video standards have not changed. - */ - if (priv->cur_fw.type == (BASE | new_fw.type) && - priv->cur_fw.std_req == std) { - tuner_dbg("Std-specific firmware already loaded.\n"); - goto skip_std_specific; - } - - /* Reloading std-specific firmware forces a SCODE update */ - priv->cur_fw.scode_table = 0; - - rc = load_firmware(fe, new_fw.type, &new_fw.id); - if (rc == -ENOENT) - rc = load_firmware(fe, new_fw.type & ~F8MHZ, &new_fw.id); - - if (rc < 0) - goto fail; - -skip_std_specific: - if (priv->cur_fw.scode_table == new_fw.scode_table && - priv->cur_fw.scode_nr == new_fw.scode_nr) { - tuner_dbg("SCODE firmware already loaded.\n"); - goto check_device; - } - - if (new_fw.type & FM) - goto check_device; - - /* Load SCODE firmware, if exists */ - tuner_dbg("Trying to load scode %d\n", new_fw.scode_nr); - - rc = load_scode(fe, new_fw.type | new_fw.scode_table, &new_fw.id, - new_fw.int_freq, new_fw.scode_nr); - -check_device: - if (xc2028_get_reg(priv, 0x0004, &version) < 0 || - xc2028_get_reg(priv, 0x0008, &hwmodel) < 0) { - tuner_err("Unable to read tuner registers.\n"); - goto fail; - } - - tuner_dbg("Device is Xceive %d version %d.%d, " - "firmware version %d.%d\n", - hwmodel, (version & 0xf000) >> 12, (version & 0xf00) >> 8, - (version & 0xf0) >> 4, version & 0xf); - - - if (priv->ctrl.read_not_reliable) - goto read_not_reliable; - - /* Check firmware version against what we downloaded. */ - if (priv->firm_version != ((version & 0xf0) << 4 | (version & 0x0f))) { - if (!priv->ctrl.read_not_reliable) { - tuner_err("Incorrect readback of firmware version.\n"); - goto fail; - } else { - tuner_err("Returned an incorrect version. However, " - "read is not reliable enough. Ignoring it.\n"); - hwmodel = 3028; - } - } - - /* Check that the tuner hardware model remains consistent over time. */ - if (priv->hwmodel == 0 && (hwmodel == 2028 || hwmodel == 3028)) { - priv->hwmodel = hwmodel; - priv->hwvers = version & 0xff00; - } else if (priv->hwmodel == 0 || priv->hwmodel != hwmodel || - priv->hwvers != (version & 0xff00)) { - tuner_err("Read invalid device hardware information - tuner " - "hung?\n"); - goto fail; - } - -read_not_reliable: - memcpy(&priv->cur_fw, &new_fw, sizeof(priv->cur_fw)); - - /* - * By setting BASE in cur_fw.type only after successfully loading all - * firmwares, we can: - * 1. Identify that BASE firmware with type=0 has been loaded; - * 2. Tell whether BASE firmware was just changed the next time through. - */ - priv->cur_fw.type |= BASE; - priv->state = XC2028_ACTIVE; - - return 0; - -fail: - priv->state = XC2028_SLEEP; - - memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); - if (retry_count < 8) { - msleep(50); - retry_count++; - tuner_dbg("Retrying firmware load\n"); - goto retry; - } - - if (rc == -ENOENT) - rc = -EINVAL; - return rc; -} - -static int xc2028_signal(struct dvb_frontend *fe, u16 *strength) -{ - struct xc2028_data *priv = fe->tuner_priv; - u16 frq_lock, signal = 0; - int rc, i; - - tuner_dbg("%s called\n", __func__); - - rc = check_device_status(priv); - if (rc < 0) - return rc; - - mutex_lock(&priv->lock); - - /* Sync Lock Indicator */ - for (i = 0; i < 3; i++) { - rc = xc2028_get_reg(priv, XREG_LOCK, &frq_lock); - if (rc < 0) - goto ret; - - if (frq_lock) - break; - msleep(6); - } - - /* Frequency didn't lock */ - if (frq_lock == 2) - goto ret; - - /* Get SNR of the video signal */ - rc = xc2028_get_reg(priv, XREG_SNR, &signal); - if (rc < 0) - goto ret; - - /* Signal level is 3 bits only */ - - signal = ((1 << 12) - 1) | ((signal & 0x07) << 12); - -ret: - mutex_unlock(&priv->lock); - - *strength = signal; - - tuner_dbg("signal strength is %d\n", signal); - - return rc; -} - -static int xc2028_get_afc(struct dvb_frontend *fe, s32 *afc) -{ - struct xc2028_data *priv = fe->tuner_priv; - int i, rc; - u16 frq_lock = 0; - s16 afc_reg = 0; - - rc = check_device_status(priv); - if (rc < 0) - return rc; - - mutex_lock(&priv->lock); - - /* Sync Lock Indicator */ - for (i = 0; i < 3; i++) { - rc = xc2028_get_reg(priv, XREG_LOCK, &frq_lock); - if (rc < 0) - goto ret; - - if (frq_lock) - break; - msleep(6); - } - - /* Frequency didn't lock */ - if (frq_lock == 2) - goto ret; - - /* Get AFC */ - rc = xc2028_get_reg(priv, XREG_FREQ_ERROR, &afc_reg); - if (rc < 0) - goto ret; - - *afc = afc_reg * 15625; /* Hz */ - - tuner_dbg("AFC is %d Hz\n", *afc); - -ret: - mutex_unlock(&priv->lock); - - return rc; -} - -#define DIV 15625 - -static int generic_set_freq(struct dvb_frontend *fe, u32 freq /* in HZ */, - enum v4l2_tuner_type new_type, - unsigned int type, - v4l2_std_id std, - u16 int_freq) -{ - struct xc2028_data *priv = fe->tuner_priv; - int rc = -EINVAL; - unsigned char buf[4]; - u32 div, offset = 0; - - tuner_dbg("%s called\n", __func__); - - mutex_lock(&priv->lock); - - tuner_dbg("should set frequency %d kHz\n", freq / 1000); - - if (check_firmware(fe, type, std, int_freq) < 0) - goto ret; - - /* On some cases xc2028 can disable video output, if - * very weak signals are received. By sending a soft - * reset, this is re-enabled. So, it is better to always - * send a soft reset before changing channels, to be sure - * that xc2028 will be in a safe state. - * Maybe this might also be needed for DTV. - */ - switch (new_type) { - case V4L2_TUNER_ANALOG_TV: - rc = send_seq(priv, {0x00, 0x00}); - - /* Analog mode requires offset = 0 */ - break; - case V4L2_TUNER_RADIO: - /* Radio mode requires offset = 0 */ - break; - case V4L2_TUNER_DIGITAL_TV: - /* - * Digital modes require an offset to adjust to the - * proper frequency. The offset depends on what - * firmware version is used. - */ - - /* - * Adjust to the center frequency. This is calculated by the - * formula: offset = 1.25MHz - BW/2 - * For DTV 7/8, the firmware uses BW = 8000, so it needs a - * further adjustment to get the frequency center on VHF - */ - - /* - * The firmware DTV78 used to work fine in UHF band (8 MHz - * bandwidth) but not at all in VHF band (7 MHz bandwidth). - * The real problem was connected to the formula used to - * calculate the center frequency offset in VHF band. - * In fact, removing the 500KHz adjustment fixed the problem. - * This is coherent to what was implemented for the DTV7 - * firmware. - * In the end, now the center frequency is the same for all 3 - * firmwares (DTV7, DTV8, DTV78) and doesn't depend on channel - * bandwidth. - */ - - if (priv->cur_fw.type & DTV6) - offset = 1750000; - else /* DTV7 or DTV8 or DTV78 */ - offset = 2750000; - - /* - * xc3028 additional "magic" - * Depending on the firmware version, it needs some adjustments - * to properly centralize the frequency. This seems to be - * needed to compensate the SCODE table adjustments made by - * newer firmwares - */ - - /* - * The proper adjustment would be to do it at s-code table. - * However, this didn't work, as reported by - * Robert Lowery - */ - -#if 0 - /* - * Still need tests for XC3028L (firmware 3.2 or upper) - * So, for now, let's just comment the per-firmware - * version of this change. Reports with xc3028l working - * with and without the lines bellow are welcome - */ - - if (priv->firm_version < 0x0302) { - if (priv->cur_fw.type & DTV7) - offset += 500000; - } else { - if (priv->cur_fw.type & DTV7) - offset -= 300000; - else if (type != ATSC) /* DVB @6MHz, DTV 8 and DTV 7/8 */ - offset += 200000; - } -#endif - } - - div = (freq - offset + DIV / 2) / DIV; - - /* CMD= Set frequency */ - if (priv->firm_version < 0x0202) - rc = send_seq(priv, {0x00, XREG_RF_FREQ, 0x00, 0x00}); - else - rc = send_seq(priv, {0x80, XREG_RF_FREQ, 0x00, 0x00}); - if (rc < 0) - goto ret; - - /* Return code shouldn't be checked. - The reset CLK is needed only with tm6000. - Driver should work fine even if this fails. - */ - if (priv->ctrl.msleep) - msleep(priv->ctrl.msleep); - do_tuner_callback(fe, XC2028_RESET_CLK, 1); - - msleep(10); - - buf[0] = 0xff & (div >> 24); - buf[1] = 0xff & (div >> 16); - buf[2] = 0xff & (div >> 8); - buf[3] = 0xff & (div); - - rc = i2c_send(priv, buf, sizeof(buf)); - if (rc < 0) - goto ret; - msleep(100); - - priv->frequency = freq; - - tuner_dbg("divisor= %02x %02x %02x %02x (freq=%d.%03d)\n", - buf[0], buf[1], buf[2], buf[3], - freq / 1000000, (freq % 1000000) / 1000); - - rc = 0; - -ret: - mutex_unlock(&priv->lock); - - return rc; -} - -static int xc2028_set_analog_freq(struct dvb_frontend *fe, - struct analog_parameters *p) -{ - struct xc2028_data *priv = fe->tuner_priv; - unsigned int type=0; - - tuner_dbg("%s called\n", __func__); - - if (p->mode == V4L2_TUNER_RADIO) { - type |= FM; - if (priv->ctrl.input1) - type |= INPUT1; - return generic_set_freq(fe, (625l * p->frequency) / 10, - V4L2_TUNER_RADIO, type, 0, 0); - } - - /* if std is not defined, choose one */ - if (!p->std) - p->std = V4L2_STD_MN; - - /* PAL/M, PAL/N, PAL/Nc and NTSC variants should use 6MHz firmware */ - if (!(p->std & V4L2_STD_MN)) - type |= F8MHZ; - - /* Add audio hack to std mask */ - p->std |= parse_audio_std_option(); - - return generic_set_freq(fe, 62500l * p->frequency, - V4L2_TUNER_ANALOG_TV, type, p->std, 0); -} - -static int xc2028_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u32 delsys = c->delivery_system; - u32 bw = c->bandwidth_hz; - struct xc2028_data *priv = fe->tuner_priv; - int rc; - unsigned int type = 0; - u16 demod = 0; - - tuner_dbg("%s called\n", __func__); - - rc = check_device_status(priv); - if (rc < 0) - return rc; - - switch (delsys) { - case SYS_DVBT: - case SYS_DVBT2: - /* - * The only countries with 6MHz seem to be Taiwan/Uruguay. - * Both seem to require QAM firmware for OFDM decoding - * Tested in Taiwan by Terry Wu - */ - if (bw <= 6000000) - type |= QAM; - - switch (priv->ctrl.type) { - case XC2028_D2633: - type |= D2633; - break; - case XC2028_D2620: - type |= D2620; - break; - case XC2028_AUTO: - default: - /* Zarlink seems to need D2633 */ - if (priv->ctrl.demod == XC3028_FE_ZARLINK456) - type |= D2633; - else - type |= D2620; - } - break; - case SYS_ATSC: - /* The only ATSC firmware (at least on v2.7) is D2633 */ - type |= ATSC | D2633; - break; - /* DVB-S and pure QAM (FE_QAM) are not supported */ - default: - return -EINVAL; - } - - if (bw <= 6000000) { - type |= DTV6; - priv->ctrl.vhfbw7 = 0; - priv->ctrl.uhfbw8 = 0; - } else if (bw <= 7000000) { - if (c->frequency < 470000000) - priv->ctrl.vhfbw7 = 1; - else - priv->ctrl.uhfbw8 = 0; - type |= (priv->ctrl.vhfbw7 && priv->ctrl.uhfbw8) ? DTV78 : DTV7; - type |= F8MHZ; - } else { - if (c->frequency < 470000000) - priv->ctrl.vhfbw7 = 0; - else - priv->ctrl.uhfbw8 = 1; - type |= (priv->ctrl.vhfbw7 && priv->ctrl.uhfbw8) ? DTV78 : DTV8; - type |= F8MHZ; - } - - /* All S-code tables need a 200kHz shift */ - if (priv->ctrl.demod) { - demod = priv->ctrl.demod; - - /* - * Newer firmwares require a 200 kHz offset only for ATSC - */ - if (type == ATSC || priv->firm_version < 0x0302) - demod += 200; - /* - * The DTV7 S-code table needs a 700 kHz shift. - * - * DTV7 is only used in Australia. Germany or Italy may also - * use this firmware after initialization, but a tune to a UHF - * channel should then cause DTV78 to be used. - * - * Unfortunately, on real-field tests, the s-code offset - * didn't work as expected, as reported by - * Robert Lowery - */ - } - - return generic_set_freq(fe, c->frequency, - V4L2_TUNER_DIGITAL_TV, type, 0, demod); -} - -static int xc2028_sleep(struct dvb_frontend *fe) -{ - struct xc2028_data *priv = fe->tuner_priv; - int rc; - - rc = check_device_status(priv); - if (rc < 0) - return rc; - - /* Avoid firmware reload on slow devices or if PM disabled */ - if (no_poweroff || priv->ctrl.disable_power_mgmt) - return 0; - - tuner_dbg("Putting xc2028/3028 into poweroff mode.\n"); - if (debug > 1) { - tuner_dbg("Printing sleep stack trace:\n"); - dump_stack(); - } - - mutex_lock(&priv->lock); - - if (priv->firm_version < 0x0202) - rc = send_seq(priv, {0x00, XREG_POWER_DOWN, 0x00, 0x00}); - else - rc = send_seq(priv, {0x80, XREG_POWER_DOWN, 0x00, 0x00}); - - priv->state = XC2028_SLEEP; - - mutex_unlock(&priv->lock); - - return rc; -} - -static int xc2028_dvb_release(struct dvb_frontend *fe) -{ - struct xc2028_data *priv = fe->tuner_priv; - - tuner_dbg("%s called\n", __func__); - - mutex_lock(&xc2028_list_mutex); - - /* only perform final cleanup if this is the last instance */ - if (hybrid_tuner_report_instance_count(priv) == 1) { - free_firmware(priv); - kfree(priv->ctrl.fname); - priv->ctrl.fname = NULL; - } - - if (priv) - hybrid_tuner_release_state(priv); - - mutex_unlock(&xc2028_list_mutex); - - fe->tuner_priv = NULL; - - return 0; -} - -static int xc2028_get_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct xc2028_data *priv = fe->tuner_priv; - int rc; - - tuner_dbg("%s called\n", __func__); - - rc = check_device_status(priv); - if (rc < 0) - return rc; - - *frequency = priv->frequency; - - return 0; -} - -static void load_firmware_cb(const struct firmware *fw, - void *context) -{ - struct dvb_frontend *fe = context; - struct xc2028_data *priv = fe->tuner_priv; - int rc; - - tuner_dbg("request_firmware_nowait(): %s\n", fw ? "OK" : "error"); - if (!fw) { - tuner_err("Could not load firmware %s.\n", priv->fname); - priv->state = XC2028_NODEV; - return; - } - - rc = load_all_firmwares(fe, fw); - - release_firmware(fw); - - if (rc < 0) - return; - priv->state = XC2028_SLEEP; -} - -static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg) -{ - struct xc2028_data *priv = fe->tuner_priv; - struct xc2028_ctrl *p = priv_cfg; - int rc = 0; - - tuner_dbg("%s called\n", __func__); - - mutex_lock(&priv->lock); - - /* - * Copy the config data. - * For the firmware name, keep a local copy of the string, - * in order to avoid troubles during device release. - */ - if (priv->ctrl.fname) - kfree(priv->ctrl.fname); - memcpy(&priv->ctrl, p, sizeof(priv->ctrl)); - if (p->fname) { - priv->ctrl.fname = kstrdup(p->fname, GFP_KERNEL); - if (priv->ctrl.fname == NULL) - rc = -ENOMEM; - } - - /* - * If firmware name changed, frees firmware. As free_firmware will - * reset the status to NO_FIRMWARE, this forces a new request_firmware - */ - if (!firmware_name[0] && p->fname && - priv->fname && strcmp(p->fname, priv->fname)) - free_firmware(priv); - - if (priv->ctrl.max_len < 9) - priv->ctrl.max_len = 13; - - if (priv->state == XC2028_NO_FIRMWARE) { - if (!firmware_name[0]) - priv->fname = priv->ctrl.fname; - else - priv->fname = firmware_name; - - rc = request_firmware_nowait(THIS_MODULE, 1, - priv->fname, - priv->i2c_props.adap->dev.parent, - GFP_KERNEL, - fe, load_firmware_cb); - if (rc < 0) { - tuner_err("Failed to request firmware %s\n", - priv->fname); - priv->state = XC2028_NODEV; - } - priv->state = XC2028_WAITING_FIRMWARE; - } - mutex_unlock(&priv->lock); - - return rc; -} - -static const struct dvb_tuner_ops xc2028_dvb_tuner_ops = { - .info = { - .name = "Xceive XC3028", - .frequency_min = 42000000, - .frequency_max = 864000000, - .frequency_step = 50000, - }, - - .set_config = xc2028_set_config, - .set_analog_params = xc2028_set_analog_freq, - .release = xc2028_dvb_release, - .get_frequency = xc2028_get_frequency, - .get_rf_strength = xc2028_signal, - .get_afc = xc2028_get_afc, - .set_params = xc2028_set_params, - .sleep = xc2028_sleep, -}; - -struct dvb_frontend *xc2028_attach(struct dvb_frontend *fe, - struct xc2028_config *cfg) -{ - struct xc2028_data *priv; - int instance; - - if (debug) - printk(KERN_DEBUG "xc2028: Xcv2028/3028 init called!\n"); - - if (NULL == cfg) - return NULL; - - if (!fe) { - printk(KERN_ERR "xc2028: No frontend!\n"); - return NULL; - } - - mutex_lock(&xc2028_list_mutex); - - instance = hybrid_tuner_request_state(struct xc2028_data, priv, - hybrid_tuner_instance_list, - cfg->i2c_adap, cfg->i2c_addr, - "xc2028"); - switch (instance) { - case 0: - /* memory allocation failure */ - goto fail; - break; - case 1: - /* new tuner instance */ - priv->ctrl.max_len = 13; - - mutex_init(&priv->lock); - - fe->tuner_priv = priv; - break; - case 2: - /* existing tuner instance */ - fe->tuner_priv = priv; - break; - } - - memcpy(&fe->ops.tuner_ops, &xc2028_dvb_tuner_ops, - sizeof(xc2028_dvb_tuner_ops)); - - tuner_info("type set to %s\n", "XCeive xc2028/xc3028 tuner"); - - if (cfg->ctrl) - xc2028_set_config(fe, cfg->ctrl); - - mutex_unlock(&xc2028_list_mutex); - - return fe; -fail: - mutex_unlock(&xc2028_list_mutex); - - xc2028_dvb_release(fe); - return NULL; -} - -EXPORT_SYMBOL(xc2028_attach); - -MODULE_DESCRIPTION("Xceive xc2028/xc3028 tuner driver"); -MODULE_AUTHOR("Michel Ludwig "); -MODULE_AUTHOR("Mauro Carvalho Chehab "); -MODULE_LICENSE("GPL"); -MODULE_FIRMWARE(XC2028_DEFAULT_FIRMWARE); -MODULE_FIRMWARE(XC3028L_DEFAULT_FIRMWARE); diff --git a/drivers/media/common/tuners/xc4000.c b/drivers/media/common/tuners/xc4000.c deleted file mode 100644 index 68397110b7d9..000000000000 --- a/drivers/media/common/tuners/xc4000.c +++ /dev/null @@ -1,1758 +0,0 @@ -/* - * Driver for Xceive XC4000 "QAM/8VSB single chip tuner" - * - * Copyright (c) 2007 Xceive Corporation - * Copyright (c) 2007 Steven Toth - * Copyright (c) 2009 Devin Heitmueller - * Copyright (c) 2009 Davide Ferri - * Copyright (c) 2010 Istvan Varga - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dvb_frontend.h" - -#include "xc4000.h" -#include "tuner-i2c.h" -#include "tuner-xc2028-types.h" - -static int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Debugging level (0 to 2, default: 0 (off))."); - -static int no_poweroff; -module_param(no_poweroff, int, 0644); -MODULE_PARM_DESC(no_poweroff, "Power management (1: disabled, 2: enabled, " - "0 (default): use device-specific default mode)."); - -static int audio_std; -module_param(audio_std, int, 0644); -MODULE_PARM_DESC(audio_std, "Audio standard. XC4000 audio decoder explicitly " - "needs to know what audio standard is needed for some video standards " - "with audio A2 or NICAM. The valid settings are a sum of:\n" - " 1: use NICAM/B or A2/B instead of NICAM/A or A2/A\n" - " 2: use A2 instead of NICAM or BTSC\n" - " 4: use SECAM/K3 instead of K1\n" - " 8: use PAL-D/K audio for SECAM-D/K\n" - "16: use FM radio input 1 instead of input 2\n" - "32: use mono audio (the lower three bits are ignored)"); - -static char firmware_name[30]; -module_param_string(firmware_name, firmware_name, sizeof(firmware_name), 0); -MODULE_PARM_DESC(firmware_name, "Firmware file name. Allows overriding the " - "default firmware name."); - -static DEFINE_MUTEX(xc4000_list_mutex); -static LIST_HEAD(hybrid_tuner_instance_list); - -#define dprintk(level, fmt, arg...) if (debug >= level) \ - printk(KERN_INFO "%s: " fmt, "xc4000", ## arg) - -/* struct for storing firmware table */ -struct firmware_description { - unsigned int type; - v4l2_std_id id; - __u16 int_freq; - unsigned char *ptr; - unsigned int size; -}; - -struct firmware_properties { - unsigned int type; - v4l2_std_id id; - v4l2_std_id std_req; - __u16 int_freq; - unsigned int scode_table; - int scode_nr; -}; - -struct xc4000_priv { - struct tuner_i2c_props i2c_props; - struct list_head hybrid_tuner_instance_list; - struct firmware_description *firm; - int firm_size; - u32 if_khz; - u32 freq_hz; - u32 bandwidth; - u8 video_standard; - u8 rf_mode; - u8 default_pm; - u8 dvb_amplitude; - u8 set_smoothedcvbs; - u8 ignore_i2c_write_errors; - __u16 firm_version; - struct firmware_properties cur_fw; - __u16 hwmodel; - __u16 hwvers; - struct mutex lock; -}; - -#define XC4000_AUDIO_STD_B 1 -#define XC4000_AUDIO_STD_A2 2 -#define XC4000_AUDIO_STD_K3 4 -#define XC4000_AUDIO_STD_L 8 -#define XC4000_AUDIO_STD_INPUT1 16 -#define XC4000_AUDIO_STD_MONO 32 - -#define XC4000_DEFAULT_FIRMWARE "dvb-fe-xc4000-1.4.fw" - -/* Misc Defines */ -#define MAX_TV_STANDARD 24 -#define XC_MAX_I2C_WRITE_LENGTH 64 -#define XC_POWERED_DOWN 0x80000000U - -/* Signal Types */ -#define XC_RF_MODE_AIR 0 -#define XC_RF_MODE_CABLE 1 - -/* Product id */ -#define XC_PRODUCT_ID_FW_NOT_LOADED 0x2000 -#define XC_PRODUCT_ID_XC4000 0x0FA0 -#define XC_PRODUCT_ID_XC4100 0x1004 - -/* Registers (Write-only) */ -#define XREG_INIT 0x00 -#define XREG_VIDEO_MODE 0x01 -#define XREG_AUDIO_MODE 0x02 -#define XREG_RF_FREQ 0x03 -#define XREG_D_CODE 0x04 -#define XREG_DIRECTSITTING_MODE 0x05 -#define XREG_SEEK_MODE 0x06 -#define XREG_POWER_DOWN 0x08 -#define XREG_SIGNALSOURCE 0x0A -#define XREG_SMOOTHEDCVBS 0x0E -#define XREG_AMPLITUDE 0x10 - -/* Registers (Read-only) */ -#define XREG_ADC_ENV 0x00 -#define XREG_QUALITY 0x01 -#define XREG_FRAME_LINES 0x02 -#define XREG_HSYNC_FREQ 0x03 -#define XREG_LOCK 0x04 -#define XREG_FREQ_ERROR 0x05 -#define XREG_SNR 0x06 -#define XREG_VERSION 0x07 -#define XREG_PRODUCT_ID 0x08 -#define XREG_SIGNAL_LEVEL 0x0A -#define XREG_NOISE_LEVEL 0x0B - -/* - Basic firmware description. This will remain with - the driver for documentation purposes. - - This represents an I2C firmware file encoded as a - string of unsigned char. Format is as follows: - - char[0 ]=len0_MSB -> len = len_MSB * 256 + len_LSB - char[1 ]=len0_LSB -> length of first write transaction - char[2 ]=data0 -> first byte to be sent - char[3 ]=data1 - char[4 ]=data2 - char[ ]=... - char[M ]=dataN -> last byte to be sent - char[M+1]=len1_MSB -> len = len_MSB * 256 + len_LSB - char[M+2]=len1_LSB -> length of second write transaction - char[M+3]=data0 - char[M+4]=data1 - ... - etc. - - The [len] value should be interpreted as follows: - - len= len_MSB _ len_LSB - len=1111_1111_1111_1111 : End of I2C_SEQUENCE - len=0000_0000_0000_0000 : Reset command: Do hardware reset - len=0NNN_NNNN_NNNN_NNNN : Normal transaction: number of bytes = {1:32767) - len=1WWW_WWWW_WWWW_WWWW : Wait command: wait for {1:32767} ms - - For the RESET and WAIT commands, the two following bytes will contain - immediately the length of the following transaction. -*/ - -struct XC_TV_STANDARD { - const char *Name; - u16 audio_mode; - u16 video_mode; - u16 int_freq; -}; - -/* Tuner standards */ -#define XC4000_MN_NTSC_PAL_BTSC 0 -#define XC4000_MN_NTSC_PAL_A2 1 -#define XC4000_MN_NTSC_PAL_EIAJ 2 -#define XC4000_MN_NTSC_PAL_Mono 3 -#define XC4000_BG_PAL_A2 4 -#define XC4000_BG_PAL_NICAM 5 -#define XC4000_BG_PAL_MONO 6 -#define XC4000_I_PAL_NICAM 7 -#define XC4000_I_PAL_NICAM_MONO 8 -#define XC4000_DK_PAL_A2 9 -#define XC4000_DK_PAL_NICAM 10 -#define XC4000_DK_PAL_MONO 11 -#define XC4000_DK_SECAM_A2DK1 12 -#define XC4000_DK_SECAM_A2LDK3 13 -#define XC4000_DK_SECAM_A2MONO 14 -#define XC4000_DK_SECAM_NICAM 15 -#define XC4000_L_SECAM_NICAM 16 -#define XC4000_LC_SECAM_NICAM 17 -#define XC4000_DTV6 18 -#define XC4000_DTV8 19 -#define XC4000_DTV7_8 20 -#define XC4000_DTV7 21 -#define XC4000_FM_Radio_INPUT2 22 -#define XC4000_FM_Radio_INPUT1 23 - -static struct XC_TV_STANDARD xc4000_standard[MAX_TV_STANDARD] = { - {"M/N-NTSC/PAL-BTSC", 0x0000, 0x80A0, 4500}, - {"M/N-NTSC/PAL-A2", 0x0000, 0x80A0, 4600}, - {"M/N-NTSC/PAL-EIAJ", 0x0040, 0x80A0, 4500}, - {"M/N-NTSC/PAL-Mono", 0x0078, 0x80A0, 4500}, - {"B/G-PAL-A2", 0x0000, 0x8159, 5640}, - {"B/G-PAL-NICAM", 0x0004, 0x8159, 5740}, - {"B/G-PAL-MONO", 0x0078, 0x8159, 5500}, - {"I-PAL-NICAM", 0x0080, 0x8049, 6240}, - {"I-PAL-NICAM-MONO", 0x0078, 0x8049, 6000}, - {"D/K-PAL-A2", 0x0000, 0x8049, 6380}, - {"D/K-PAL-NICAM", 0x0080, 0x8049, 6200}, - {"D/K-PAL-MONO", 0x0078, 0x8049, 6500}, - {"D/K-SECAM-A2 DK1", 0x0000, 0x8049, 6340}, - {"D/K-SECAM-A2 L/DK3", 0x0000, 0x8049, 6000}, - {"D/K-SECAM-A2 MONO", 0x0078, 0x8049, 6500}, - {"D/K-SECAM-NICAM", 0x0080, 0x8049, 6200}, - {"L-SECAM-NICAM", 0x8080, 0x0009, 6200}, - {"L'-SECAM-NICAM", 0x8080, 0x4009, 6200}, - {"DTV6", 0x00C0, 0x8002, 0}, - {"DTV8", 0x00C0, 0x800B, 0}, - {"DTV7/8", 0x00C0, 0x801B, 0}, - {"DTV7", 0x00C0, 0x8007, 0}, - {"FM Radio-INPUT2", 0x0008, 0x9800, 10700}, - {"FM Radio-INPUT1", 0x0008, 0x9000, 10700} -}; - -static int xc4000_readreg(struct xc4000_priv *priv, u16 reg, u16 *val); -static int xc4000_tuner_reset(struct dvb_frontend *fe); -static void xc_debug_dump(struct xc4000_priv *priv); - -static int xc_send_i2c_data(struct xc4000_priv *priv, u8 *buf, int len) -{ - struct i2c_msg msg = { .addr = priv->i2c_props.addr, - .flags = 0, .buf = buf, .len = len }; - if (i2c_transfer(priv->i2c_props.adap, &msg, 1) != 1) { - if (priv->ignore_i2c_write_errors == 0) { - printk(KERN_ERR "xc4000: I2C write failed (len=%i)\n", - len); - if (len == 4) { - printk(KERN_ERR "bytes %02x %02x %02x %02x\n", buf[0], - buf[1], buf[2], buf[3]); - } - return -EREMOTEIO; - } - } - return 0; -} - -static int xc4000_tuner_reset(struct dvb_frontend *fe) -{ - struct xc4000_priv *priv = fe->tuner_priv; - int ret; - - dprintk(1, "%s()\n", __func__); - - if (fe->callback) { - ret = fe->callback(((fe->dvb) && (fe->dvb->priv)) ? - fe->dvb->priv : - priv->i2c_props.adap->algo_data, - DVB_FRONTEND_COMPONENT_TUNER, - XC4000_TUNER_RESET, 0); - if (ret) { - printk(KERN_ERR "xc4000: reset failed\n"); - return -EREMOTEIO; - } - } else { - printk(KERN_ERR "xc4000: no tuner reset callback function, " - "fatal\n"); - return -EINVAL; - } - return 0; -} - -static int xc_write_reg(struct xc4000_priv *priv, u16 regAddr, u16 i2cData) -{ - u8 buf[4]; - int result; - - buf[0] = (regAddr >> 8) & 0xFF; - buf[1] = regAddr & 0xFF; - buf[2] = (i2cData >> 8) & 0xFF; - buf[3] = i2cData & 0xFF; - result = xc_send_i2c_data(priv, buf, 4); - - return result; -} - -static int xc_load_i2c_sequence(struct dvb_frontend *fe, const u8 *i2c_sequence) -{ - struct xc4000_priv *priv = fe->tuner_priv; - - int i, nbytes_to_send, result; - unsigned int len, pos, index; - u8 buf[XC_MAX_I2C_WRITE_LENGTH]; - - index = 0; - while ((i2c_sequence[index] != 0xFF) || - (i2c_sequence[index + 1] != 0xFF)) { - len = i2c_sequence[index] * 256 + i2c_sequence[index+1]; - if (len == 0x0000) { - /* RESET command */ - /* NOTE: this is ignored, as the reset callback was */ - /* already called by check_firmware() */ - index += 2; - } else if (len & 0x8000) { - /* WAIT command */ - msleep(len & 0x7FFF); - index += 2; - } else { - /* Send i2c data whilst ensuring individual transactions - * do not exceed XC_MAX_I2C_WRITE_LENGTH bytes. - */ - index += 2; - buf[0] = i2c_sequence[index]; - buf[1] = i2c_sequence[index + 1]; - pos = 2; - while (pos < len) { - if ((len - pos) > XC_MAX_I2C_WRITE_LENGTH - 2) - nbytes_to_send = - XC_MAX_I2C_WRITE_LENGTH; - else - nbytes_to_send = (len - pos + 2); - for (i = 2; i < nbytes_to_send; i++) { - buf[i] = i2c_sequence[index + pos + - i - 2]; - } - result = xc_send_i2c_data(priv, buf, - nbytes_to_send); - - if (result != 0) - return result; - - pos += nbytes_to_send - 2; - } - index += len; - } - } - return 0; -} - -static int xc_set_tv_standard(struct xc4000_priv *priv, - u16 video_mode, u16 audio_mode) -{ - int ret; - dprintk(1, "%s(0x%04x,0x%04x)\n", __func__, video_mode, audio_mode); - dprintk(1, "%s() Standard = %s\n", - __func__, - xc4000_standard[priv->video_standard].Name); - - /* Don't complain when the request fails because of i2c stretching */ - priv->ignore_i2c_write_errors = 1; - - ret = xc_write_reg(priv, XREG_VIDEO_MODE, video_mode); - if (ret == 0) - ret = xc_write_reg(priv, XREG_AUDIO_MODE, audio_mode); - - priv->ignore_i2c_write_errors = 0; - - return ret; -} - -static int xc_set_signal_source(struct xc4000_priv *priv, u16 rf_mode) -{ - dprintk(1, "%s(%d) Source = %s\n", __func__, rf_mode, - rf_mode == XC_RF_MODE_AIR ? "ANTENNA" : "CABLE"); - - if ((rf_mode != XC_RF_MODE_AIR) && (rf_mode != XC_RF_MODE_CABLE)) { - rf_mode = XC_RF_MODE_CABLE; - printk(KERN_ERR - "%s(), Invalid mode, defaulting to CABLE", - __func__); - } - return xc_write_reg(priv, XREG_SIGNALSOURCE, rf_mode); -} - -static const struct dvb_tuner_ops xc4000_tuner_ops; - -static int xc_set_rf_frequency(struct xc4000_priv *priv, u32 freq_hz) -{ - u16 freq_code; - - dprintk(1, "%s(%u)\n", __func__, freq_hz); - - if ((freq_hz > xc4000_tuner_ops.info.frequency_max) || - (freq_hz < xc4000_tuner_ops.info.frequency_min)) - return -EINVAL; - - freq_code = (u16)(freq_hz / 15625); - - /* WAS: Starting in firmware version 1.1.44, Xceive recommends using the - FINERFREQ for all normal tuning (the doc indicates reg 0x03 should - only be used for fast scanning for channel lock) */ - /* WAS: XREG_FINERFREQ */ - return xc_write_reg(priv, XREG_RF_FREQ, freq_code); -} - -static int xc_get_adc_envelope(struct xc4000_priv *priv, u16 *adc_envelope) -{ - return xc4000_readreg(priv, XREG_ADC_ENV, adc_envelope); -} - -static int xc_get_frequency_error(struct xc4000_priv *priv, u32 *freq_error_hz) -{ - int result; - u16 regData; - u32 tmp; - - result = xc4000_readreg(priv, XREG_FREQ_ERROR, ®Data); - if (result != 0) - return result; - - tmp = (u32)regData & 0xFFFFU; - tmp = (tmp < 0x8000U ? tmp : 0x10000U - tmp); - (*freq_error_hz) = tmp * 15625; - return result; -} - -static int xc_get_lock_status(struct xc4000_priv *priv, u16 *lock_status) -{ - return xc4000_readreg(priv, XREG_LOCK, lock_status); -} - -static int xc_get_version(struct xc4000_priv *priv, - u8 *hw_majorversion, u8 *hw_minorversion, - u8 *fw_majorversion, u8 *fw_minorversion) -{ - u16 data; - int result; - - result = xc4000_readreg(priv, XREG_VERSION, &data); - if (result != 0) - return result; - - (*hw_majorversion) = (data >> 12) & 0x0F; - (*hw_minorversion) = (data >> 8) & 0x0F; - (*fw_majorversion) = (data >> 4) & 0x0F; - (*fw_minorversion) = data & 0x0F; - - return 0; -} - -static int xc_get_hsync_freq(struct xc4000_priv *priv, u32 *hsync_freq_hz) -{ - u16 regData; - int result; - - result = xc4000_readreg(priv, XREG_HSYNC_FREQ, ®Data); - if (result != 0) - return result; - - (*hsync_freq_hz) = ((regData & 0x0fff) * 763)/100; - return result; -} - -static int xc_get_frame_lines(struct xc4000_priv *priv, u16 *frame_lines) -{ - return xc4000_readreg(priv, XREG_FRAME_LINES, frame_lines); -} - -static int xc_get_quality(struct xc4000_priv *priv, u16 *quality) -{ - return xc4000_readreg(priv, XREG_QUALITY, quality); -} - -static int xc_get_signal_level(struct xc4000_priv *priv, u16 *signal) -{ - return xc4000_readreg(priv, XREG_SIGNAL_LEVEL, signal); -} - -static int xc_get_noise_level(struct xc4000_priv *priv, u16 *noise) -{ - return xc4000_readreg(priv, XREG_NOISE_LEVEL, noise); -} - -static u16 xc_wait_for_lock(struct xc4000_priv *priv) -{ - u16 lock_state = 0; - int watchdog_count = 40; - - while ((lock_state == 0) && (watchdog_count > 0)) { - xc_get_lock_status(priv, &lock_state); - if (lock_state != 1) { - msleep(5); - watchdog_count--; - } - } - return lock_state; -} - -static int xc_tune_channel(struct xc4000_priv *priv, u32 freq_hz) -{ - int found = 1; - int result; - - dprintk(1, "%s(%u)\n", __func__, freq_hz); - - /* Don't complain when the request fails because of i2c stretching */ - priv->ignore_i2c_write_errors = 1; - result = xc_set_rf_frequency(priv, freq_hz); - priv->ignore_i2c_write_errors = 0; - - if (result != 0) - return 0; - - /* wait for lock only in analog TV mode */ - if ((priv->cur_fw.type & (FM | DTV6 | DTV7 | DTV78 | DTV8)) == 0) { - if (xc_wait_for_lock(priv) != 1) - found = 0; - } - - /* Wait for stats to stabilize. - * Frame Lines needs two frame times after initial lock - * before it is valid. - */ - msleep(debug ? 100 : 10); - - if (debug) - xc_debug_dump(priv); - - return found; -} - -static int xc4000_readreg(struct xc4000_priv *priv, u16 reg, u16 *val) -{ - u8 buf[2] = { reg >> 8, reg & 0xff }; - u8 bval[2] = { 0, 0 }; - struct i2c_msg msg[2] = { - { .addr = priv->i2c_props.addr, - .flags = 0, .buf = &buf[0], .len = 2 }, - { .addr = priv->i2c_props.addr, - .flags = I2C_M_RD, .buf = &bval[0], .len = 2 }, - }; - - if (i2c_transfer(priv->i2c_props.adap, msg, 2) != 2) { - printk(KERN_ERR "xc4000: I2C read failed\n"); - return -EREMOTEIO; - } - - *val = (bval[0] << 8) | bval[1]; - return 0; -} - -#define dump_firm_type(t) dump_firm_type_and_int_freq(t, 0) -static void dump_firm_type_and_int_freq(unsigned int type, u16 int_freq) -{ - if (type & BASE) - printk(KERN_CONT "BASE "); - if (type & INIT1) - printk(KERN_CONT "INIT1 "); - if (type & F8MHZ) - printk(KERN_CONT "F8MHZ "); - if (type & MTS) - printk(KERN_CONT "MTS "); - if (type & D2620) - printk(KERN_CONT "D2620 "); - if (type & D2633) - printk(KERN_CONT "D2633 "); - if (type & DTV6) - printk(KERN_CONT "DTV6 "); - if (type & QAM) - printk(KERN_CONT "QAM "); - if (type & DTV7) - printk(KERN_CONT "DTV7 "); - if (type & DTV78) - printk(KERN_CONT "DTV78 "); - if (type & DTV8) - printk(KERN_CONT "DTV8 "); - if (type & FM) - printk(KERN_CONT "FM "); - if (type & INPUT1) - printk(KERN_CONT "INPUT1 "); - if (type & LCD) - printk(KERN_CONT "LCD "); - if (type & NOGD) - printk(KERN_CONT "NOGD "); - if (type & MONO) - printk(KERN_CONT "MONO "); - if (type & ATSC) - printk(KERN_CONT "ATSC "); - if (type & IF) - printk(KERN_CONT "IF "); - if (type & LG60) - printk(KERN_CONT "LG60 "); - if (type & ATI638) - printk(KERN_CONT "ATI638 "); - if (type & OREN538) - printk(KERN_CONT "OREN538 "); - if (type & OREN36) - printk(KERN_CONT "OREN36 "); - if (type & TOYOTA388) - printk(KERN_CONT "TOYOTA388 "); - if (type & TOYOTA794) - printk(KERN_CONT "TOYOTA794 "); - if (type & DIBCOM52) - printk(KERN_CONT "DIBCOM52 "); - if (type & ZARLINK456) - printk(KERN_CONT "ZARLINK456 "); - if (type & CHINA) - printk(KERN_CONT "CHINA "); - if (type & F6MHZ) - printk(KERN_CONT "F6MHZ "); - if (type & INPUT2) - printk(KERN_CONT "INPUT2 "); - if (type & SCODE) - printk(KERN_CONT "SCODE "); - if (type & HAS_IF) - printk(KERN_CONT "HAS_IF_%d ", int_freq); -} - -static int seek_firmware(struct dvb_frontend *fe, unsigned int type, - v4l2_std_id *id) -{ - struct xc4000_priv *priv = fe->tuner_priv; - int i, best_i = -1; - unsigned int best_nr_diffs = 255U; - - if (!priv->firm) { - printk(KERN_ERR "Error! firmware not loaded\n"); - return -EINVAL; - } - - if (((type & ~SCODE) == 0) && (*id == 0)) - *id = V4L2_STD_PAL; - - /* Seek for generic video standard match */ - for (i = 0; i < priv->firm_size; i++) { - v4l2_std_id id_diff_mask = - (priv->firm[i].id ^ (*id)) & (*id); - unsigned int type_diff_mask = - (priv->firm[i].type ^ type) - & (BASE_TYPES | DTV_TYPES | LCD | NOGD | MONO | SCODE); - unsigned int nr_diffs; - - if (type_diff_mask - & (BASE | INIT1 | FM | DTV6 | DTV7 | DTV78 | DTV8 | SCODE)) - continue; - - nr_diffs = hweight64(id_diff_mask) + hweight32(type_diff_mask); - if (!nr_diffs) /* Supports all the requested standards */ - goto found; - - if (nr_diffs < best_nr_diffs) { - best_nr_diffs = nr_diffs; - best_i = i; - } - } - - /* FIXME: Would make sense to seek for type "hint" match ? */ - if (best_i < 0) { - i = -ENOENT; - goto ret; - } - - if (best_nr_diffs > 0U) { - printk(KERN_WARNING - "Selecting best matching firmware (%u bits differ) for " - "type=(%x), id %016llx:\n", - best_nr_diffs, type, (unsigned long long)*id); - i = best_i; - } - -found: - *id = priv->firm[i].id; - -ret: - if (debug) { - printk(KERN_DEBUG "%s firmware for type=", - (i < 0) ? "Can't find" : "Found"); - dump_firm_type(type); - printk(KERN_DEBUG "(%x), id %016llx.\n", type, (unsigned long long)*id); - } - return i; -} - -static int load_firmware(struct dvb_frontend *fe, unsigned int type, - v4l2_std_id *id) -{ - struct xc4000_priv *priv = fe->tuner_priv; - int pos, rc; - unsigned char *p; - - pos = seek_firmware(fe, type, id); - if (pos < 0) - return pos; - - p = priv->firm[pos].ptr; - - /* Don't complain when the request fails because of i2c stretching */ - priv->ignore_i2c_write_errors = 1; - - rc = xc_load_i2c_sequence(fe, p); - - priv->ignore_i2c_write_errors = 0; - - return rc; -} - -static int xc4000_fwupload(struct dvb_frontend *fe) -{ - struct xc4000_priv *priv = fe->tuner_priv; - const struct firmware *fw = NULL; - const unsigned char *p, *endp; - int rc = 0; - int n, n_array; - char name[33]; - const char *fname; - - if (firmware_name[0] != '\0') - fname = firmware_name; - else - fname = XC4000_DEFAULT_FIRMWARE; - - dprintk(1, "Reading firmware %s\n", fname); - rc = request_firmware(&fw, fname, priv->i2c_props.adap->dev.parent); - if (rc < 0) { - if (rc == -ENOENT) - printk(KERN_ERR "Error: firmware %s not found.\n", fname); - else - printk(KERN_ERR "Error %d while requesting firmware %s\n", - rc, fname); - - return rc; - } - p = fw->data; - endp = p + fw->size; - - if (fw->size < sizeof(name) - 1 + 2 + 2) { - printk(KERN_ERR "Error: firmware file %s has invalid size!\n", - fname); - goto corrupt; - } - - memcpy(name, p, sizeof(name) - 1); - name[sizeof(name) - 1] = '\0'; - p += sizeof(name) - 1; - - priv->firm_version = get_unaligned_le16(p); - p += 2; - - n_array = get_unaligned_le16(p); - p += 2; - - dprintk(1, "Loading %d firmware images from %s, type: %s, ver %d.%d\n", - n_array, fname, name, - priv->firm_version >> 8, priv->firm_version & 0xff); - - priv->firm = kcalloc(n_array, sizeof(*priv->firm), GFP_KERNEL); - if (priv->firm == NULL) { - printk(KERN_ERR "Not enough memory to load firmware file.\n"); - rc = -ENOMEM; - goto done; - } - priv->firm_size = n_array; - - n = -1; - while (p < endp) { - __u32 type, size; - v4l2_std_id id; - __u16 int_freq = 0; - - n++; - if (n >= n_array) { - printk(KERN_ERR "More firmware images in file than " - "were expected!\n"); - goto corrupt; - } - - /* Checks if there's enough bytes to read */ - if (endp - p < sizeof(type) + sizeof(id) + sizeof(size)) - goto header; - - type = get_unaligned_le32(p); - p += sizeof(type); - - id = get_unaligned_le64(p); - p += sizeof(id); - - if (type & HAS_IF) { - int_freq = get_unaligned_le16(p); - p += sizeof(int_freq); - if (endp - p < sizeof(size)) - goto header; - } - - size = get_unaligned_le32(p); - p += sizeof(size); - - if (!size || size > endp - p) { - printk(KERN_ERR "Firmware type (%x), id %llx is corrupted (size=%d, expected %d)\n", - type, (unsigned long long)id, - (unsigned)(endp - p), size); - goto corrupt; - } - - priv->firm[n].ptr = kzalloc(size, GFP_KERNEL); - if (priv->firm[n].ptr == NULL) { - printk(KERN_ERR "Not enough memory to load firmware file.\n"); - rc = -ENOMEM; - goto done; - } - - if (debug) { - printk(KERN_DEBUG "Reading firmware type "); - dump_firm_type_and_int_freq(type, int_freq); - printk(KERN_DEBUG "(%x), id %llx, size=%d.\n", - type, (unsigned long long)id, size); - } - - memcpy(priv->firm[n].ptr, p, size); - priv->firm[n].type = type; - priv->firm[n].id = id; - priv->firm[n].size = size; - priv->firm[n].int_freq = int_freq; - - p += size; - } - - if (n + 1 != priv->firm_size) { - printk(KERN_ERR "Firmware file is incomplete!\n"); - goto corrupt; - } - - goto done; - -header: - printk(KERN_ERR "Firmware header is incomplete!\n"); -corrupt: - rc = -EINVAL; - printk(KERN_ERR "Error: firmware file is corrupted!\n"); - -done: - release_firmware(fw); - if (rc == 0) - dprintk(1, "Firmware files loaded.\n"); - - return rc; -} - -static int load_scode(struct dvb_frontend *fe, unsigned int type, - v4l2_std_id *id, __u16 int_freq, int scode) -{ - struct xc4000_priv *priv = fe->tuner_priv; - int pos, rc; - unsigned char *p; - u8 scode_buf[13]; - u8 indirect_mode[5]; - - dprintk(1, "%s called int_freq=%d\n", __func__, int_freq); - - if (!int_freq) { - pos = seek_firmware(fe, type, id); - if (pos < 0) - return pos; - } else { - for (pos = 0; pos < priv->firm_size; pos++) { - if ((priv->firm[pos].int_freq == int_freq) && - (priv->firm[pos].type & HAS_IF)) - break; - } - if (pos == priv->firm_size) - return -ENOENT; - } - - p = priv->firm[pos].ptr; - - if (priv->firm[pos].size != 12 * 16 || scode >= 16) - return -EINVAL; - p += 12 * scode; - - if (debug) { - tuner_info("Loading SCODE for type="); - dump_firm_type_and_int_freq(priv->firm[pos].type, - priv->firm[pos].int_freq); - printk(KERN_CONT "(%x), id %016llx.\n", priv->firm[pos].type, - (unsigned long long)*id); - } - - scode_buf[0] = 0x00; - memcpy(&scode_buf[1], p, 12); - - /* Enter direct-mode */ - rc = xc_write_reg(priv, XREG_DIRECTSITTING_MODE, 0); - if (rc < 0) { - printk(KERN_ERR "failed to put device into direct mode!\n"); - return -EIO; - } - - rc = xc_send_i2c_data(priv, scode_buf, 13); - if (rc != 0) { - /* Even if the send failed, make sure we set back to indirect - mode */ - printk(KERN_ERR "Failed to set scode %d\n", rc); - } - - /* Switch back to indirect-mode */ - memset(indirect_mode, 0, sizeof(indirect_mode)); - indirect_mode[4] = 0x88; - xc_send_i2c_data(priv, indirect_mode, sizeof(indirect_mode)); - msleep(10); - - return 0; -} - -static int check_firmware(struct dvb_frontend *fe, unsigned int type, - v4l2_std_id std, __u16 int_freq) -{ - struct xc4000_priv *priv = fe->tuner_priv; - struct firmware_properties new_fw; - int rc = 0, is_retry = 0; - u16 hwmodel; - v4l2_std_id std0; - u8 hw_major, hw_minor, fw_major, fw_minor; - - dprintk(1, "%s called\n", __func__); - - if (!priv->firm) { - rc = xc4000_fwupload(fe); - if (rc < 0) - return rc; - } - -retry: - new_fw.type = type; - new_fw.id = std; - new_fw.std_req = std; - new_fw.scode_table = SCODE; - new_fw.scode_nr = 0; - new_fw.int_freq = int_freq; - - dprintk(1, "checking firmware, user requested type="); - if (debug) { - dump_firm_type(new_fw.type); - printk(KERN_CONT "(%x), id %016llx, ", new_fw.type, - (unsigned long long)new_fw.std_req); - if (!int_freq) - printk(KERN_CONT "scode_tbl "); - else - printk(KERN_CONT "int_freq %d, ", new_fw.int_freq); - printk(KERN_CONT "scode_nr %d\n", new_fw.scode_nr); - } - - /* No need to reload base firmware if it matches */ - if (priv->cur_fw.type & BASE) { - dprintk(1, "BASE firmware not changed.\n"); - goto skip_base; - } - - /* Updating BASE - forget about all currently loaded firmware */ - memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); - - /* Reset is needed before loading firmware */ - rc = xc4000_tuner_reset(fe); - if (rc < 0) - goto fail; - - /* BASE firmwares are all std0 */ - std0 = 0; - rc = load_firmware(fe, BASE, &std0); - if (rc < 0) { - printk(KERN_ERR "Error %d while loading base firmware\n", rc); - goto fail; - } - - /* Load INIT1, if needed */ - dprintk(1, "Load init1 firmware, if exists\n"); - - rc = load_firmware(fe, BASE | INIT1, &std0); - if (rc == -ENOENT) - rc = load_firmware(fe, BASE | INIT1, &std0); - if (rc < 0 && rc != -ENOENT) { - tuner_err("Error %d while loading init1 firmware\n", - rc); - goto fail; - } - -skip_base: - /* - * No need to reload standard specific firmware if base firmware - * was not reloaded and requested video standards have not changed. - */ - if (priv->cur_fw.type == (BASE | new_fw.type) && - priv->cur_fw.std_req == std) { - dprintk(1, "Std-specific firmware already loaded.\n"); - goto skip_std_specific; - } - - /* Reloading std-specific firmware forces a SCODE update */ - priv->cur_fw.scode_table = 0; - - /* Load the standard firmware */ - rc = load_firmware(fe, new_fw.type, &new_fw.id); - - if (rc < 0) - goto fail; - -skip_std_specific: - if (priv->cur_fw.scode_table == new_fw.scode_table && - priv->cur_fw.scode_nr == new_fw.scode_nr) { - dprintk(1, "SCODE firmware already loaded.\n"); - goto check_device; - } - - /* Load SCODE firmware, if exists */ - rc = load_scode(fe, new_fw.type | new_fw.scode_table, &new_fw.id, - new_fw.int_freq, new_fw.scode_nr); - if (rc != 0) - dprintk(1, "load scode failed %d\n", rc); - -check_device: - rc = xc4000_readreg(priv, XREG_PRODUCT_ID, &hwmodel); - - if (xc_get_version(priv, &hw_major, &hw_minor, &fw_major, - &fw_minor) != 0) { - printk(KERN_ERR "Unable to read tuner registers.\n"); - goto fail; - } - - dprintk(1, "Device is Xceive %d version %d.%d, " - "firmware version %d.%d\n", - hwmodel, hw_major, hw_minor, fw_major, fw_minor); - - /* Check firmware version against what we downloaded. */ - if (priv->firm_version != ((fw_major << 8) | fw_minor)) { - printk(KERN_WARNING - "Incorrect readback of firmware version %d.%d.\n", - fw_major, fw_minor); - goto fail; - } - - /* Check that the tuner hardware model remains consistent over time. */ - if (priv->hwmodel == 0 && - (hwmodel == XC_PRODUCT_ID_XC4000 || - hwmodel == XC_PRODUCT_ID_XC4100)) { - priv->hwmodel = hwmodel; - priv->hwvers = (hw_major << 8) | hw_minor; - } else if (priv->hwmodel == 0 || priv->hwmodel != hwmodel || - priv->hwvers != ((hw_major << 8) | hw_minor)) { - printk(KERN_WARNING - "Read invalid device hardware information - tuner " - "hung?\n"); - goto fail; - } - - memcpy(&priv->cur_fw, &new_fw, sizeof(priv->cur_fw)); - - /* - * By setting BASE in cur_fw.type only after successfully loading all - * firmwares, we can: - * 1. Identify that BASE firmware with type=0 has been loaded; - * 2. Tell whether BASE firmware was just changed the next time through. - */ - priv->cur_fw.type |= BASE; - - return 0; - -fail: - memset(&priv->cur_fw, 0, sizeof(priv->cur_fw)); - if (!is_retry) { - msleep(50); - is_retry = 1; - dprintk(1, "Retrying firmware load\n"); - goto retry; - } - - if (rc == -ENOENT) - rc = -EINVAL; - return rc; -} - -static void xc_debug_dump(struct xc4000_priv *priv) -{ - u16 adc_envelope; - u32 freq_error_hz = 0; - u16 lock_status; - u32 hsync_freq_hz = 0; - u16 frame_lines; - u16 quality; - u16 signal = 0; - u16 noise = 0; - u8 hw_majorversion = 0, hw_minorversion = 0; - u8 fw_majorversion = 0, fw_minorversion = 0; - - xc_get_adc_envelope(priv, &adc_envelope); - dprintk(1, "*** ADC envelope (0-1023) = %d\n", adc_envelope); - - xc_get_frequency_error(priv, &freq_error_hz); - dprintk(1, "*** Frequency error = %d Hz\n", freq_error_hz); - - xc_get_lock_status(priv, &lock_status); - dprintk(1, "*** Lock status (0-Wait, 1-Locked, 2-No-signal) = %d\n", - lock_status); - - xc_get_version(priv, &hw_majorversion, &hw_minorversion, - &fw_majorversion, &fw_minorversion); - dprintk(1, "*** HW: V%02x.%02x, FW: V%02x.%02x\n", - hw_majorversion, hw_minorversion, - fw_majorversion, fw_minorversion); - - if (priv->video_standard < XC4000_DTV6) { - xc_get_hsync_freq(priv, &hsync_freq_hz); - dprintk(1, "*** Horizontal sync frequency = %d Hz\n", - hsync_freq_hz); - - xc_get_frame_lines(priv, &frame_lines); - dprintk(1, "*** Frame lines = %d\n", frame_lines); - } - - xc_get_quality(priv, &quality); - dprintk(1, "*** Quality (0:<8dB, 7:>56dB) = %d\n", quality); - - xc_get_signal_level(priv, &signal); - dprintk(1, "*** Signal level = -%ddB (%d)\n", signal >> 8, signal); - - xc_get_noise_level(priv, &noise); - dprintk(1, "*** Noise level = %ddB (%d)\n", noise >> 8, noise); -} - -static int xc4000_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u32 delsys = c->delivery_system; - u32 bw = c->bandwidth_hz; - struct xc4000_priv *priv = fe->tuner_priv; - unsigned int type; - int ret = -EREMOTEIO; - - dprintk(1, "%s() frequency=%d (Hz)\n", __func__, c->frequency); - - mutex_lock(&priv->lock); - - switch (delsys) { - case SYS_ATSC: - dprintk(1, "%s() VSB modulation\n", __func__); - priv->rf_mode = XC_RF_MODE_AIR; - priv->freq_hz = c->frequency - 1750000; - priv->video_standard = XC4000_DTV6; - type = DTV6; - break; - case SYS_DVBC_ANNEX_B: - dprintk(1, "%s() QAM modulation\n", __func__); - priv->rf_mode = XC_RF_MODE_CABLE; - priv->freq_hz = c->frequency - 1750000; - priv->video_standard = XC4000_DTV6; - type = DTV6; - break; - case SYS_DVBT: - case SYS_DVBT2: - dprintk(1, "%s() OFDM\n", __func__); - if (bw == 0) { - if (c->frequency < 400000000) { - priv->freq_hz = c->frequency - 2250000; - } else { - priv->freq_hz = c->frequency - 2750000; - } - priv->video_standard = XC4000_DTV7_8; - type = DTV78; - } else if (bw <= 6000000) { - priv->video_standard = XC4000_DTV6; - priv->freq_hz = c->frequency - 1750000; - type = DTV6; - } else if (bw <= 7000000) { - priv->video_standard = XC4000_DTV7; - priv->freq_hz = c->frequency - 2250000; - type = DTV7; - } else { - priv->video_standard = XC4000_DTV8; - priv->freq_hz = c->frequency - 2750000; - type = DTV8; - } - priv->rf_mode = XC_RF_MODE_AIR; - break; - default: - printk(KERN_ERR "xc4000 delivery system not supported!\n"); - ret = -EINVAL; - goto fail; - } - - dprintk(1, "%s() frequency=%d (compensated)\n", - __func__, priv->freq_hz); - - /* Make sure the correct firmware type is loaded */ - if (check_firmware(fe, type, 0, priv->if_khz) != 0) - goto fail; - - priv->bandwidth = c->bandwidth_hz; - - ret = xc_set_signal_source(priv, priv->rf_mode); - if (ret != 0) { - printk(KERN_ERR "xc4000: xc_set_signal_source(%d) failed\n", - priv->rf_mode); - goto fail; - } else { - u16 video_mode, audio_mode; - video_mode = xc4000_standard[priv->video_standard].video_mode; - audio_mode = xc4000_standard[priv->video_standard].audio_mode; - if (type == DTV6 && priv->firm_version != 0x0102) - video_mode |= 0x0001; - ret = xc_set_tv_standard(priv, video_mode, audio_mode); - if (ret != 0) { - printk(KERN_ERR "xc4000: xc_set_tv_standard failed\n"); - /* DJH - do not return when it fails... */ - /* goto fail; */ - } - } - - if (xc_write_reg(priv, XREG_D_CODE, 0) == 0) - ret = 0; - if (priv->dvb_amplitude != 0) { - if (xc_write_reg(priv, XREG_AMPLITUDE, - (priv->firm_version != 0x0102 || - priv->dvb_amplitude != 134 ? - priv->dvb_amplitude : 132)) != 0) - ret = -EREMOTEIO; - } - if (priv->set_smoothedcvbs != 0) { - if (xc_write_reg(priv, XREG_SMOOTHEDCVBS, 1) != 0) - ret = -EREMOTEIO; - } - if (ret != 0) { - printk(KERN_ERR "xc4000: setting registers failed\n"); - /* goto fail; */ - } - - xc_tune_channel(priv, priv->freq_hz); - - ret = 0; - -fail: - mutex_unlock(&priv->lock); - - return ret; -} - -static int xc4000_set_analog_params(struct dvb_frontend *fe, - struct analog_parameters *params) -{ - struct xc4000_priv *priv = fe->tuner_priv; - unsigned int type = 0; - int ret = -EREMOTEIO; - - if (params->mode == V4L2_TUNER_RADIO) { - dprintk(1, "%s() frequency=%d (in units of 62.5Hz)\n", - __func__, params->frequency); - - mutex_lock(&priv->lock); - - params->std = 0; - priv->freq_hz = params->frequency * 125L / 2; - - if (audio_std & XC4000_AUDIO_STD_INPUT1) { - priv->video_standard = XC4000_FM_Radio_INPUT1; - type = FM | INPUT1; - } else { - priv->video_standard = XC4000_FM_Radio_INPUT2; - type = FM | INPUT2; - } - - goto tune_channel; - } - - dprintk(1, "%s() frequency=%d (in units of 62.5khz)\n", - __func__, params->frequency); - - mutex_lock(&priv->lock); - - /* params->frequency is in units of 62.5khz */ - priv->freq_hz = params->frequency * 62500; - - params->std &= V4L2_STD_ALL; - /* if std is not defined, choose one */ - if (!params->std) - params->std = V4L2_STD_PAL_BG; - - if (audio_std & XC4000_AUDIO_STD_MONO) - type = MONO; - - if (params->std & V4L2_STD_MN) { - params->std = V4L2_STD_MN; - if (audio_std & XC4000_AUDIO_STD_MONO) { - priv->video_standard = XC4000_MN_NTSC_PAL_Mono; - } else if (audio_std & XC4000_AUDIO_STD_A2) { - params->std |= V4L2_STD_A2; - priv->video_standard = XC4000_MN_NTSC_PAL_A2; - } else { - params->std |= V4L2_STD_BTSC; - priv->video_standard = XC4000_MN_NTSC_PAL_BTSC; - } - goto tune_channel; - } - - if (params->std & V4L2_STD_PAL_BG) { - params->std = V4L2_STD_PAL_BG; - if (audio_std & XC4000_AUDIO_STD_MONO) { - priv->video_standard = XC4000_BG_PAL_MONO; - } else if (!(audio_std & XC4000_AUDIO_STD_A2)) { - if (!(audio_std & XC4000_AUDIO_STD_B)) { - params->std |= V4L2_STD_NICAM_A; - priv->video_standard = XC4000_BG_PAL_NICAM; - } else { - params->std |= V4L2_STD_NICAM_B; - priv->video_standard = XC4000_BG_PAL_NICAM; - } - } else { - if (!(audio_std & XC4000_AUDIO_STD_B)) { - params->std |= V4L2_STD_A2_A; - priv->video_standard = XC4000_BG_PAL_A2; - } else { - params->std |= V4L2_STD_A2_B; - priv->video_standard = XC4000_BG_PAL_A2; - } - } - goto tune_channel; - } - - if (params->std & V4L2_STD_PAL_I) { - /* default to NICAM audio standard */ - params->std = V4L2_STD_PAL_I | V4L2_STD_NICAM; - if (audio_std & XC4000_AUDIO_STD_MONO) - priv->video_standard = XC4000_I_PAL_NICAM_MONO; - else - priv->video_standard = XC4000_I_PAL_NICAM; - goto tune_channel; - } - - if (params->std & V4L2_STD_PAL_DK) { - params->std = V4L2_STD_PAL_DK; - if (audio_std & XC4000_AUDIO_STD_MONO) { - priv->video_standard = XC4000_DK_PAL_MONO; - } else if (audio_std & XC4000_AUDIO_STD_A2) { - params->std |= V4L2_STD_A2; - priv->video_standard = XC4000_DK_PAL_A2; - } else { - params->std |= V4L2_STD_NICAM; - priv->video_standard = XC4000_DK_PAL_NICAM; - } - goto tune_channel; - } - - if (params->std & V4L2_STD_SECAM_DK) { - /* default to A2 audio standard */ - params->std = V4L2_STD_SECAM_DK | V4L2_STD_A2; - if (audio_std & XC4000_AUDIO_STD_L) { - type = 0; - priv->video_standard = XC4000_DK_SECAM_NICAM; - } else if (audio_std & XC4000_AUDIO_STD_MONO) { - priv->video_standard = XC4000_DK_SECAM_A2MONO; - } else if (audio_std & XC4000_AUDIO_STD_K3) { - params->std |= V4L2_STD_SECAM_K3; - priv->video_standard = XC4000_DK_SECAM_A2LDK3; - } else { - priv->video_standard = XC4000_DK_SECAM_A2DK1; - } - goto tune_channel; - } - - if (params->std & V4L2_STD_SECAM_L) { - /* default to NICAM audio standard */ - type = 0; - params->std = V4L2_STD_SECAM_L | V4L2_STD_NICAM; - priv->video_standard = XC4000_L_SECAM_NICAM; - goto tune_channel; - } - - if (params->std & V4L2_STD_SECAM_LC) { - /* default to NICAM audio standard */ - type = 0; - params->std = V4L2_STD_SECAM_LC | V4L2_STD_NICAM; - priv->video_standard = XC4000_LC_SECAM_NICAM; - goto tune_channel; - } - -tune_channel: - /* FIXME: it could be air. */ - priv->rf_mode = XC_RF_MODE_CABLE; - - if (check_firmware(fe, type, params->std, - xc4000_standard[priv->video_standard].int_freq) != 0) - goto fail; - - ret = xc_set_signal_source(priv, priv->rf_mode); - if (ret != 0) { - printk(KERN_ERR - "xc4000: xc_set_signal_source(%d) failed\n", - priv->rf_mode); - goto fail; - } else { - u16 video_mode, audio_mode; - video_mode = xc4000_standard[priv->video_standard].video_mode; - audio_mode = xc4000_standard[priv->video_standard].audio_mode; - if (priv->video_standard < XC4000_BG_PAL_A2) { - if (type & NOGD) - video_mode &= 0xFF7F; - } else if (priv->video_standard < XC4000_I_PAL_NICAM) { - if (priv->firm_version == 0x0102) - video_mode &= 0xFEFF; - if (audio_std & XC4000_AUDIO_STD_B) - video_mode |= 0x0080; - } - ret = xc_set_tv_standard(priv, video_mode, audio_mode); - if (ret != 0) { - printk(KERN_ERR "xc4000: xc_set_tv_standard failed\n"); - goto fail; - } - } - - if (xc_write_reg(priv, XREG_D_CODE, 0) == 0) - ret = 0; - if (xc_write_reg(priv, XREG_AMPLITUDE, 1) != 0) - ret = -EREMOTEIO; - if (priv->set_smoothedcvbs != 0) { - if (xc_write_reg(priv, XREG_SMOOTHEDCVBS, 1) != 0) - ret = -EREMOTEIO; - } - if (ret != 0) { - printk(KERN_ERR "xc4000: setting registers failed\n"); - goto fail; - } - - xc_tune_channel(priv, priv->freq_hz); - - ret = 0; - -fail: - mutex_unlock(&priv->lock); - - return ret; -} - -static int xc4000_get_signal(struct dvb_frontend *fe, u16 *strength) -{ - struct xc4000_priv *priv = fe->tuner_priv; - u16 value = 0; - int rc; - - mutex_lock(&priv->lock); - rc = xc4000_readreg(priv, XREG_SIGNAL_LEVEL, &value); - mutex_unlock(&priv->lock); - - if (rc < 0) - goto ret; - - /* Informations from real testing of DVB-T and radio part, - coeficient for one dB is 0xff. - */ - tuner_dbg("Signal strength: -%ddB (%05d)\n", value >> 8, value); - - /* all known digital modes */ - if ((priv->video_standard == XC4000_DTV6) || - (priv->video_standard == XC4000_DTV7) || - (priv->video_standard == XC4000_DTV7_8) || - (priv->video_standard == XC4000_DTV8)) - goto digital; - - /* Analog mode has NOISE LEVEL important, signal - depends only on gain of antenna and amplifiers, - but it doesn't tell anything about real quality - of reception. - */ - mutex_lock(&priv->lock); - rc = xc4000_readreg(priv, XREG_NOISE_LEVEL, &value); - mutex_unlock(&priv->lock); - - tuner_dbg("Noise level: %ddB (%05d)\n", value >> 8, value); - - /* highest noise level: 32dB */ - if (value >= 0x2000) { - value = 0; - } else { - value = ~value << 3; - } - - goto ret; - - /* Digital mode has SIGNAL LEVEL important and real - noise level is stored in demodulator registers. - */ -digital: - /* best signal: -50dB */ - if (value <= 0x3200) { - value = 0xffff; - /* minimum: -114dB - should be 0x7200 but real zero is 0x713A */ - } else if (value >= 0x713A) { - value = 0; - } else { - value = ~(value - 0x3200) << 2; - } - -ret: - *strength = value; - - return rc; -} - -static int xc4000_get_frequency(struct dvb_frontend *fe, u32 *freq) -{ - struct xc4000_priv *priv = fe->tuner_priv; - - *freq = priv->freq_hz; - - if (debug) { - mutex_lock(&priv->lock); - if ((priv->cur_fw.type - & (BASE | FM | DTV6 | DTV7 | DTV78 | DTV8)) == BASE) { - u16 snr = 0; - if (xc4000_readreg(priv, XREG_SNR, &snr) == 0) { - mutex_unlock(&priv->lock); - dprintk(1, "%s() freq = %u, SNR = %d\n", - __func__, *freq, snr); - return 0; - } - } - mutex_unlock(&priv->lock); - } - - dprintk(1, "%s()\n", __func__); - - return 0; -} - -static int xc4000_get_bandwidth(struct dvb_frontend *fe, u32 *bw) -{ - struct xc4000_priv *priv = fe->tuner_priv; - dprintk(1, "%s()\n", __func__); - - *bw = priv->bandwidth; - return 0; -} - -static int xc4000_get_status(struct dvb_frontend *fe, u32 *status) -{ - struct xc4000_priv *priv = fe->tuner_priv; - u16 lock_status = 0; - - mutex_lock(&priv->lock); - - if (priv->cur_fw.type & BASE) - xc_get_lock_status(priv, &lock_status); - - *status = (lock_status == 1 ? - TUNER_STATUS_LOCKED | TUNER_STATUS_STEREO : 0); - if (priv->cur_fw.type & (DTV6 | DTV7 | DTV78 | DTV8)) - *status &= (~TUNER_STATUS_STEREO); - - mutex_unlock(&priv->lock); - - dprintk(2, "%s() lock_status = %d\n", __func__, lock_status); - - return 0; -} - -static int xc4000_sleep(struct dvb_frontend *fe) -{ - struct xc4000_priv *priv = fe->tuner_priv; - int ret = 0; - - dprintk(1, "%s()\n", __func__); - - mutex_lock(&priv->lock); - - /* Avoid firmware reload on slow devices */ - if ((no_poweroff == 2 || - (no_poweroff == 0 && priv->default_pm != 0)) && - (priv->cur_fw.type & BASE) != 0) { - /* force reset and firmware reload */ - priv->cur_fw.type = XC_POWERED_DOWN; - - if (xc_write_reg(priv, XREG_POWER_DOWN, 0) != 0) { - printk(KERN_ERR - "xc4000: %s() unable to shutdown tuner\n", - __func__); - ret = -EREMOTEIO; - } - msleep(20); - } - - mutex_unlock(&priv->lock); - - return ret; -} - -static int xc4000_init(struct dvb_frontend *fe) -{ - dprintk(1, "%s()\n", __func__); - - return 0; -} - -static int xc4000_release(struct dvb_frontend *fe) -{ - struct xc4000_priv *priv = fe->tuner_priv; - - dprintk(1, "%s()\n", __func__); - - mutex_lock(&xc4000_list_mutex); - - if (priv) - hybrid_tuner_release_state(priv); - - mutex_unlock(&xc4000_list_mutex); - - fe->tuner_priv = NULL; - - return 0; -} - -static const struct dvb_tuner_ops xc4000_tuner_ops = { - .info = { - .name = "Xceive XC4000", - .frequency_min = 1000000, - .frequency_max = 1023000000, - .frequency_step = 50000, - }, - - .release = xc4000_release, - .init = xc4000_init, - .sleep = xc4000_sleep, - - .set_params = xc4000_set_params, - .set_analog_params = xc4000_set_analog_params, - .get_frequency = xc4000_get_frequency, - .get_rf_strength = xc4000_get_signal, - .get_bandwidth = xc4000_get_bandwidth, - .get_status = xc4000_get_status -}; - -struct dvb_frontend *xc4000_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, - struct xc4000_config *cfg) -{ - struct xc4000_priv *priv = NULL; - int instance; - u16 id = 0; - - dprintk(1, "%s(%d-%04x)\n", __func__, - i2c ? i2c_adapter_id(i2c) : -1, - cfg ? cfg->i2c_address : -1); - - mutex_lock(&xc4000_list_mutex); - - instance = hybrid_tuner_request_state(struct xc4000_priv, priv, - hybrid_tuner_instance_list, - i2c, cfg->i2c_address, "xc4000"); - switch (instance) { - case 0: - goto fail; - break; - case 1: - /* new tuner instance */ - priv->bandwidth = 6000000; - /* set default configuration */ - priv->if_khz = 4560; - priv->default_pm = 0; - priv->dvb_amplitude = 134; - priv->set_smoothedcvbs = 1; - mutex_init(&priv->lock); - fe->tuner_priv = priv; - break; - default: - /* existing tuner instance */ - fe->tuner_priv = priv; - break; - } - - if (cfg->if_khz != 0) { - /* copy configuration if provided by the caller */ - priv->if_khz = cfg->if_khz; - priv->default_pm = cfg->default_pm; - priv->dvb_amplitude = cfg->dvb_amplitude; - priv->set_smoothedcvbs = cfg->set_smoothedcvbs; - } - - /* Check if firmware has been loaded. It is possible that another - instance of the driver has loaded the firmware. - */ - - if (instance == 1) { - if (xc4000_readreg(priv, XREG_PRODUCT_ID, &id) != 0) - goto fail; - } else { - id = ((priv->cur_fw.type & BASE) != 0 ? - priv->hwmodel : XC_PRODUCT_ID_FW_NOT_LOADED); - } - - switch (id) { - case XC_PRODUCT_ID_XC4000: - case XC_PRODUCT_ID_XC4100: - printk(KERN_INFO - "xc4000: Successfully identified at address 0x%02x\n", - cfg->i2c_address); - printk(KERN_INFO - "xc4000: Firmware has been loaded previously\n"); - break; - case XC_PRODUCT_ID_FW_NOT_LOADED: - printk(KERN_INFO - "xc4000: Successfully identified at address 0x%02x\n", - cfg->i2c_address); - printk(KERN_INFO - "xc4000: Firmware has not been loaded previously\n"); - break; - default: - printk(KERN_ERR - "xc4000: Device not found at addr 0x%02x (0x%x)\n", - cfg->i2c_address, id); - goto fail; - } - - mutex_unlock(&xc4000_list_mutex); - - memcpy(&fe->ops.tuner_ops, &xc4000_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - if (instance == 1) { - int ret; - mutex_lock(&priv->lock); - ret = xc4000_fwupload(fe); - mutex_unlock(&priv->lock); - if (ret != 0) - goto fail2; - } - - return fe; -fail: - mutex_unlock(&xc4000_list_mutex); -fail2: - xc4000_release(fe); - return NULL; -} -EXPORT_SYMBOL(xc4000_attach); - -MODULE_AUTHOR("Steven Toth, Davide Ferri"); -MODULE_DESCRIPTION("Xceive xc4000 silicon tuner driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/tuners/xc5000.c b/drivers/media/common/tuners/xc5000.c deleted file mode 100644 index 362a8d7c9738..000000000000 --- a/drivers/media/common/tuners/xc5000.c +++ /dev/null @@ -1,1265 +0,0 @@ -/* - * Driver for Xceive XC5000 "QAM/8VSB single chip tuner" - * - * Copyright (c) 2007 Xceive Corporation - * Copyright (c) 2007 Steven Toth - * Copyright (c) 2009 Devin Heitmueller - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include - -#include "dvb_frontend.h" - -#include "xc5000.h" -#include "tuner-i2c.h" - -static int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); - -static int no_poweroff; -module_param(no_poweroff, int, 0644); -MODULE_PARM_DESC(no_poweroff, "0 (default) powers device off when not used.\n" - "\t\t1 keep device energized and with tuner ready all the times.\n" - "\t\tFaster, but consumes more power and keeps the device hotter"); - -static DEFINE_MUTEX(xc5000_list_mutex); -static LIST_HEAD(hybrid_tuner_instance_list); - -#define dprintk(level, fmt, arg...) if (debug >= level) \ - printk(KERN_INFO "%s: " fmt, "xc5000", ## arg) - -struct xc5000_priv { - struct tuner_i2c_props i2c_props; - struct list_head hybrid_tuner_instance_list; - - u32 if_khz; - u16 xtal_khz; - u32 freq_hz; - u32 bandwidth; - u8 video_standard; - u8 rf_mode; - u8 radio_input; - - int chip_id; -}; - -/* Misc Defines */ -#define MAX_TV_STANDARD 24 -#define XC_MAX_I2C_WRITE_LENGTH 64 - -/* Signal Types */ -#define XC_RF_MODE_AIR 0 -#define XC_RF_MODE_CABLE 1 - -/* Result codes */ -#define XC_RESULT_SUCCESS 0 -#define XC_RESULT_RESET_FAILURE 1 -#define XC_RESULT_I2C_WRITE_FAILURE 2 -#define XC_RESULT_I2C_READ_FAILURE 3 -#define XC_RESULT_OUT_OF_RANGE 5 - -/* Product id */ -#define XC_PRODUCT_ID_FW_NOT_LOADED 0x2000 -#define XC_PRODUCT_ID_FW_LOADED 0x1388 - -/* Registers */ -#define XREG_INIT 0x00 -#define XREG_VIDEO_MODE 0x01 -#define XREG_AUDIO_MODE 0x02 -#define XREG_RF_FREQ 0x03 -#define XREG_D_CODE 0x04 -#define XREG_IF_OUT 0x05 -#define XREG_SEEK_MODE 0x07 -#define XREG_POWER_DOWN 0x0A /* Obsolete */ -/* Set the output amplitude - SIF for analog, DTVP/DTVN for digital */ -#define XREG_OUTPUT_AMP 0x0B -#define XREG_SIGNALSOURCE 0x0D /* 0=Air, 1=Cable */ -#define XREG_SMOOTHEDCVBS 0x0E -#define XREG_XTALFREQ 0x0F -#define XREG_FINERFREQ 0x10 -#define XREG_DDIMODE 0x11 - -#define XREG_ADC_ENV 0x00 -#define XREG_QUALITY 0x01 -#define XREG_FRAME_LINES 0x02 -#define XREG_HSYNC_FREQ 0x03 -#define XREG_LOCK 0x04 -#define XREG_FREQ_ERROR 0x05 -#define XREG_SNR 0x06 -#define XREG_VERSION 0x07 -#define XREG_PRODUCT_ID 0x08 -#define XREG_BUSY 0x09 -#define XREG_BUILD 0x0D - -/* - Basic firmware description. This will remain with - the driver for documentation purposes. - - This represents an I2C firmware file encoded as a - string of unsigned char. Format is as follows: - - char[0 ]=len0_MSB -> len = len_MSB * 256 + len_LSB - char[1 ]=len0_LSB -> length of first write transaction - char[2 ]=data0 -> first byte to be sent - char[3 ]=data1 - char[4 ]=data2 - char[ ]=... - char[M ]=dataN -> last byte to be sent - char[M+1]=len1_MSB -> len = len_MSB * 256 + len_LSB - char[M+2]=len1_LSB -> length of second write transaction - char[M+3]=data0 - char[M+4]=data1 - ... - etc. - - The [len] value should be interpreted as follows: - - len= len_MSB _ len_LSB - len=1111_1111_1111_1111 : End of I2C_SEQUENCE - len=0000_0000_0000_0000 : Reset command: Do hardware reset - len=0NNN_NNNN_NNNN_NNNN : Normal transaction: number of bytes = {1:32767) - len=1WWW_WWWW_WWWW_WWWW : Wait command: wait for {1:32767} ms - - For the RESET and WAIT commands, the two following bytes will contain - immediately the length of the following transaction. - -*/ -struct XC_TV_STANDARD { - char *Name; - u16 AudioMode; - u16 VideoMode; -}; - -/* Tuner standards */ -#define MN_NTSC_PAL_BTSC 0 -#define MN_NTSC_PAL_A2 1 -#define MN_NTSC_PAL_EIAJ 2 -#define MN_NTSC_PAL_Mono 3 -#define BG_PAL_A2 4 -#define BG_PAL_NICAM 5 -#define BG_PAL_MONO 6 -#define I_PAL_NICAM 7 -#define I_PAL_NICAM_MONO 8 -#define DK_PAL_A2 9 -#define DK_PAL_NICAM 10 -#define DK_PAL_MONO 11 -#define DK_SECAM_A2DK1 12 -#define DK_SECAM_A2LDK3 13 -#define DK_SECAM_A2MONO 14 -#define L_SECAM_NICAM 15 -#define LC_SECAM_NICAM 16 -#define DTV6 17 -#define DTV8 18 -#define DTV7_8 19 -#define DTV7 20 -#define FM_Radio_INPUT2 21 -#define FM_Radio_INPUT1 22 -#define FM_Radio_INPUT1_MONO 23 - -static struct XC_TV_STANDARD XC5000_Standard[MAX_TV_STANDARD] = { - {"M/N-NTSC/PAL-BTSC", 0x0400, 0x8020}, - {"M/N-NTSC/PAL-A2", 0x0600, 0x8020}, - {"M/N-NTSC/PAL-EIAJ", 0x0440, 0x8020}, - {"M/N-NTSC/PAL-Mono", 0x0478, 0x8020}, - {"B/G-PAL-A2", 0x0A00, 0x8049}, - {"B/G-PAL-NICAM", 0x0C04, 0x8049}, - {"B/G-PAL-MONO", 0x0878, 0x8059}, - {"I-PAL-NICAM", 0x1080, 0x8009}, - {"I-PAL-NICAM-MONO", 0x0E78, 0x8009}, - {"D/K-PAL-A2", 0x1600, 0x8009}, - {"D/K-PAL-NICAM", 0x0E80, 0x8009}, - {"D/K-PAL-MONO", 0x1478, 0x8009}, - {"D/K-SECAM-A2 DK1", 0x1200, 0x8009}, - {"D/K-SECAM-A2 L/DK3", 0x0E00, 0x8009}, - {"D/K-SECAM-A2 MONO", 0x1478, 0x8009}, - {"L-SECAM-NICAM", 0x8E82, 0x0009}, - {"L'-SECAM-NICAM", 0x8E82, 0x4009}, - {"DTV6", 0x00C0, 0x8002}, - {"DTV8", 0x00C0, 0x800B}, - {"DTV7/8", 0x00C0, 0x801B}, - {"DTV7", 0x00C0, 0x8007}, - {"FM Radio-INPUT2", 0x9802, 0x9002}, - {"FM Radio-INPUT1", 0x0208, 0x9002}, - {"FM Radio-INPUT1_MONO", 0x0278, 0x9002} -}; - - -struct xc5000_fw_cfg { - char *name; - u16 size; -}; - -#define XC5000A_FIRMWARE "dvb-fe-xc5000-1.6.114.fw" -static const struct xc5000_fw_cfg xc5000a_1_6_114 = { - .name = XC5000A_FIRMWARE, - .size = 12401, -}; - -#define XC5000C_FIRMWARE "dvb-fe-xc5000c-41.024.5.fw" -static const struct xc5000_fw_cfg xc5000c_41_024_5 = { - .name = XC5000C_FIRMWARE, - .size = 16497, -}; - -static inline const struct xc5000_fw_cfg *xc5000_assign_firmware(int chip_id) -{ - switch (chip_id) { - default: - case XC5000A: - return &xc5000a_1_6_114; - case XC5000C: - return &xc5000c_41_024_5; - } -} - -static int xc_load_fw_and_init_tuner(struct dvb_frontend *fe); -static int xc5000_is_firmware_loaded(struct dvb_frontend *fe); -static int xc5000_readreg(struct xc5000_priv *priv, u16 reg, u16 *val); -static int xc5000_TunerReset(struct dvb_frontend *fe); - -static int xc_send_i2c_data(struct xc5000_priv *priv, u8 *buf, int len) -{ - struct i2c_msg msg = { .addr = priv->i2c_props.addr, - .flags = 0, .buf = buf, .len = len }; - - if (i2c_transfer(priv->i2c_props.adap, &msg, 1) != 1) { - printk(KERN_ERR "xc5000: I2C write failed (len=%i)\n", len); - return XC_RESULT_I2C_WRITE_FAILURE; - } - return XC_RESULT_SUCCESS; -} - -#if 0 -/* This routine is never used because the only time we read data from the - i2c bus is when we read registers, and we want that to be an atomic i2c - transaction in case we are on a multi-master bus */ -static int xc_read_i2c_data(struct xc5000_priv *priv, u8 *buf, int len) -{ - struct i2c_msg msg = { .addr = priv->i2c_props.addr, - .flags = I2C_M_RD, .buf = buf, .len = len }; - - if (i2c_transfer(priv->i2c_props.adap, &msg, 1) != 1) { - printk(KERN_ERR "xc5000 I2C read failed (len=%i)\n", len); - return -EREMOTEIO; - } - return 0; -} -#endif - -static int xc5000_readreg(struct xc5000_priv *priv, u16 reg, u16 *val) -{ - u8 buf[2] = { reg >> 8, reg & 0xff }; - u8 bval[2] = { 0, 0 }; - struct i2c_msg msg[2] = { - { .addr = priv->i2c_props.addr, - .flags = 0, .buf = &buf[0], .len = 2 }, - { .addr = priv->i2c_props.addr, - .flags = I2C_M_RD, .buf = &bval[0], .len = 2 }, - }; - - if (i2c_transfer(priv->i2c_props.adap, msg, 2) != 2) { - printk(KERN_WARNING "xc5000: I2C read failed\n"); - return -EREMOTEIO; - } - - *val = (bval[0] << 8) | bval[1]; - return XC_RESULT_SUCCESS; -} - -static void xc_wait(int wait_ms) -{ - msleep(wait_ms); -} - -static int xc5000_TunerReset(struct dvb_frontend *fe) -{ - struct xc5000_priv *priv = fe->tuner_priv; - int ret; - - dprintk(1, "%s()\n", __func__); - - if (fe->callback) { - ret = fe->callback(((fe->dvb) && (fe->dvb->priv)) ? - fe->dvb->priv : - priv->i2c_props.adap->algo_data, - DVB_FRONTEND_COMPONENT_TUNER, - XC5000_TUNER_RESET, 0); - if (ret) { - printk(KERN_ERR "xc5000: reset failed\n"); - return XC_RESULT_RESET_FAILURE; - } - } else { - printk(KERN_ERR "xc5000: no tuner reset callback function, fatal\n"); - return XC_RESULT_RESET_FAILURE; - } - return XC_RESULT_SUCCESS; -} - -static int xc_write_reg(struct xc5000_priv *priv, u16 regAddr, u16 i2cData) -{ - u8 buf[4]; - int WatchDogTimer = 100; - int result; - - buf[0] = (regAddr >> 8) & 0xFF; - buf[1] = regAddr & 0xFF; - buf[2] = (i2cData >> 8) & 0xFF; - buf[3] = i2cData & 0xFF; - result = xc_send_i2c_data(priv, buf, 4); - if (result == XC_RESULT_SUCCESS) { - /* wait for busy flag to clear */ - while ((WatchDogTimer > 0) && (result == XC_RESULT_SUCCESS)) { - result = xc5000_readreg(priv, XREG_BUSY, (u16 *)buf); - if (result == XC_RESULT_SUCCESS) { - if ((buf[0] == 0) && (buf[1] == 0)) { - /* busy flag cleared */ - break; - } else { - xc_wait(5); /* wait 5 ms */ - WatchDogTimer--; - } - } - } - } - if (WatchDogTimer < 0) - result = XC_RESULT_I2C_WRITE_FAILURE; - - return result; -} - -static int xc_load_i2c_sequence(struct dvb_frontend *fe, const u8 *i2c_sequence) -{ - struct xc5000_priv *priv = fe->tuner_priv; - - int i, nbytes_to_send, result; - unsigned int len, pos, index; - u8 buf[XC_MAX_I2C_WRITE_LENGTH]; - - index = 0; - while ((i2c_sequence[index] != 0xFF) || - (i2c_sequence[index + 1] != 0xFF)) { - len = i2c_sequence[index] * 256 + i2c_sequence[index+1]; - if (len == 0x0000) { - /* RESET command */ - result = xc5000_TunerReset(fe); - index += 2; - if (result != XC_RESULT_SUCCESS) - return result; - } else if (len & 0x8000) { - /* WAIT command */ - xc_wait(len & 0x7FFF); - index += 2; - } else { - /* Send i2c data whilst ensuring individual transactions - * do not exceed XC_MAX_I2C_WRITE_LENGTH bytes. - */ - index += 2; - buf[0] = i2c_sequence[index]; - buf[1] = i2c_sequence[index + 1]; - pos = 2; - while (pos < len) { - if ((len - pos) > XC_MAX_I2C_WRITE_LENGTH - 2) - nbytes_to_send = - XC_MAX_I2C_WRITE_LENGTH; - else - nbytes_to_send = (len - pos + 2); - for (i = 2; i < nbytes_to_send; i++) { - buf[i] = i2c_sequence[index + pos + - i - 2]; - } - result = xc_send_i2c_data(priv, buf, - nbytes_to_send); - - if (result != XC_RESULT_SUCCESS) - return result; - - pos += nbytes_to_send - 2; - } - index += len; - } - } - return XC_RESULT_SUCCESS; -} - -static int xc_initialize(struct xc5000_priv *priv) -{ - dprintk(1, "%s()\n", __func__); - return xc_write_reg(priv, XREG_INIT, 0); -} - -static int xc_SetTVStandard(struct xc5000_priv *priv, - u16 VideoMode, u16 AudioMode) -{ - int ret; - dprintk(1, "%s(0x%04x,0x%04x)\n", __func__, VideoMode, AudioMode); - dprintk(1, "%s() Standard = %s\n", - __func__, - XC5000_Standard[priv->video_standard].Name); - - ret = xc_write_reg(priv, XREG_VIDEO_MODE, VideoMode); - if (ret == XC_RESULT_SUCCESS) - ret = xc_write_reg(priv, XREG_AUDIO_MODE, AudioMode); - - return ret; -} - -static int xc_SetSignalSource(struct xc5000_priv *priv, u16 rf_mode) -{ - dprintk(1, "%s(%d) Source = %s\n", __func__, rf_mode, - rf_mode == XC_RF_MODE_AIR ? "ANTENNA" : "CABLE"); - - if ((rf_mode != XC_RF_MODE_AIR) && (rf_mode != XC_RF_MODE_CABLE)) { - rf_mode = XC_RF_MODE_CABLE; - printk(KERN_ERR - "%s(), Invalid mode, defaulting to CABLE", - __func__); - } - return xc_write_reg(priv, XREG_SIGNALSOURCE, rf_mode); -} - -static const struct dvb_tuner_ops xc5000_tuner_ops; - -static int xc_set_RF_frequency(struct xc5000_priv *priv, u32 freq_hz) -{ - u16 freq_code; - - dprintk(1, "%s(%u)\n", __func__, freq_hz); - - if ((freq_hz > xc5000_tuner_ops.info.frequency_max) || - (freq_hz < xc5000_tuner_ops.info.frequency_min)) - return XC_RESULT_OUT_OF_RANGE; - - freq_code = (u16)(freq_hz / 15625); - - /* Starting in firmware version 1.1.44, Xceive recommends using the - FINERFREQ for all normal tuning (the doc indicates reg 0x03 should - only be used for fast scanning for channel lock) */ - return xc_write_reg(priv, XREG_FINERFREQ, freq_code); -} - - -static int xc_set_IF_frequency(struct xc5000_priv *priv, u32 freq_khz) -{ - u32 freq_code = (freq_khz * 1024)/1000; - dprintk(1, "%s(freq_khz = %d) freq_code = 0x%x\n", - __func__, freq_khz, freq_code); - - return xc_write_reg(priv, XREG_IF_OUT, freq_code); -} - - -static int xc_get_ADC_Envelope(struct xc5000_priv *priv, u16 *adc_envelope) -{ - return xc5000_readreg(priv, XREG_ADC_ENV, adc_envelope); -} - -static int xc_get_frequency_error(struct xc5000_priv *priv, u32 *freq_error_hz) -{ - int result; - u16 regData; - u32 tmp; - - result = xc5000_readreg(priv, XREG_FREQ_ERROR, ®Data); - if (result != XC_RESULT_SUCCESS) - return result; - - tmp = (u32)regData; - (*freq_error_hz) = (tmp * 15625) / 1000; - return result; -} - -static int xc_get_lock_status(struct xc5000_priv *priv, u16 *lock_status) -{ - return xc5000_readreg(priv, XREG_LOCK, lock_status); -} - -static int xc_get_version(struct xc5000_priv *priv, - u8 *hw_majorversion, u8 *hw_minorversion, - u8 *fw_majorversion, u8 *fw_minorversion) -{ - u16 data; - int result; - - result = xc5000_readreg(priv, XREG_VERSION, &data); - if (result != XC_RESULT_SUCCESS) - return result; - - (*hw_majorversion) = (data >> 12) & 0x0F; - (*hw_minorversion) = (data >> 8) & 0x0F; - (*fw_majorversion) = (data >> 4) & 0x0F; - (*fw_minorversion) = data & 0x0F; - - return 0; -} - -static int xc_get_buildversion(struct xc5000_priv *priv, u16 *buildrev) -{ - return xc5000_readreg(priv, XREG_BUILD, buildrev); -} - -static int xc_get_hsync_freq(struct xc5000_priv *priv, u32 *hsync_freq_hz) -{ - u16 regData; - int result; - - result = xc5000_readreg(priv, XREG_HSYNC_FREQ, ®Data); - if (result != XC_RESULT_SUCCESS) - return result; - - (*hsync_freq_hz) = ((regData & 0x0fff) * 763)/100; - return result; -} - -static int xc_get_frame_lines(struct xc5000_priv *priv, u16 *frame_lines) -{ - return xc5000_readreg(priv, XREG_FRAME_LINES, frame_lines); -} - -static int xc_get_quality(struct xc5000_priv *priv, u16 *quality) -{ - return xc5000_readreg(priv, XREG_QUALITY, quality); -} - -static u16 WaitForLock(struct xc5000_priv *priv) -{ - u16 lockState = 0; - int watchDogCount = 40; - - while ((lockState == 0) && (watchDogCount > 0)) { - xc_get_lock_status(priv, &lockState); - if (lockState != 1) { - xc_wait(5); - watchDogCount--; - } - } - return lockState; -} - -#define XC_TUNE_ANALOG 0 -#define XC_TUNE_DIGITAL 1 -static int xc_tune_channel(struct xc5000_priv *priv, u32 freq_hz, int mode) -{ - int found = 0; - - dprintk(1, "%s(%u)\n", __func__, freq_hz); - - if (xc_set_RF_frequency(priv, freq_hz) != XC_RESULT_SUCCESS) - return 0; - - if (mode == XC_TUNE_ANALOG) { - if (WaitForLock(priv) == 1) - found = 1; - } - - return found; -} - -static int xc_set_xtal(struct dvb_frontend *fe) -{ - struct xc5000_priv *priv = fe->tuner_priv; - int ret = XC_RESULT_SUCCESS; - - switch (priv->chip_id) { - default: - case XC5000A: - /* 32.000 MHz xtal is default */ - break; - case XC5000C: - switch (priv->xtal_khz) { - default: - case 32000: - /* 32.000 MHz xtal is default */ - break; - case 31875: - /* 31.875 MHz xtal configuration */ - ret = xc_write_reg(priv, 0x000f, 0x8081); - break; - } - break; - } - return ret; -} - -static int xc5000_fwupload(struct dvb_frontend *fe) -{ - struct xc5000_priv *priv = fe->tuner_priv; - const struct firmware *fw; - int ret; - const struct xc5000_fw_cfg *desired_fw = - xc5000_assign_firmware(priv->chip_id); - - /* request the firmware, this will block and timeout */ - printk(KERN_INFO "xc5000: waiting for firmware upload (%s)...\n", - desired_fw->name); - - ret = request_firmware(&fw, desired_fw->name, - priv->i2c_props.adap->dev.parent); - if (ret) { - printk(KERN_ERR "xc5000: Upload failed. (file not found?)\n"); - ret = XC_RESULT_RESET_FAILURE; - goto out; - } else { - printk(KERN_DEBUG "xc5000: firmware read %Zu bytes.\n", - fw->size); - ret = XC_RESULT_SUCCESS; - } - - if (fw->size != desired_fw->size) { - printk(KERN_ERR "xc5000: firmware incorrect size\n"); - ret = XC_RESULT_RESET_FAILURE; - } else { - printk(KERN_INFO "xc5000: firmware uploading...\n"); - ret = xc_load_i2c_sequence(fe, fw->data); - if (XC_RESULT_SUCCESS == ret) - ret = xc_set_xtal(fe); - if (XC_RESULT_SUCCESS == ret) - printk(KERN_INFO "xc5000: firmware upload complete...\n"); - else - printk(KERN_ERR "xc5000: firmware upload failed...\n"); - } - -out: - release_firmware(fw); - return ret; -} - -static void xc_debug_dump(struct xc5000_priv *priv) -{ - u16 adc_envelope; - u32 freq_error_hz = 0; - u16 lock_status; - u32 hsync_freq_hz = 0; - u16 frame_lines; - u16 quality; - u8 hw_majorversion = 0, hw_minorversion = 0; - u8 fw_majorversion = 0, fw_minorversion = 0; - u16 fw_buildversion = 0; - - /* Wait for stats to stabilize. - * Frame Lines needs two frame times after initial lock - * before it is valid. - */ - xc_wait(100); - - xc_get_ADC_Envelope(priv, &adc_envelope); - dprintk(1, "*** ADC envelope (0-1023) = %d\n", adc_envelope); - - xc_get_frequency_error(priv, &freq_error_hz); - dprintk(1, "*** Frequency error = %d Hz\n", freq_error_hz); - - xc_get_lock_status(priv, &lock_status); - dprintk(1, "*** Lock status (0-Wait, 1-Locked, 2-No-signal) = %d\n", - lock_status); - - xc_get_version(priv, &hw_majorversion, &hw_minorversion, - &fw_majorversion, &fw_minorversion); - xc_get_buildversion(priv, &fw_buildversion); - dprintk(1, "*** HW: V%02x.%02x, FW: V%02x.%02x.%04x\n", - hw_majorversion, hw_minorversion, - fw_majorversion, fw_minorversion, fw_buildversion); - - xc_get_hsync_freq(priv, &hsync_freq_hz); - dprintk(1, "*** Horizontal sync frequency = %d Hz\n", hsync_freq_hz); - - xc_get_frame_lines(priv, &frame_lines); - dprintk(1, "*** Frame lines = %d\n", frame_lines); - - xc_get_quality(priv, &quality); - dprintk(1, "*** Quality (0:<8dB, 7:>56dB) = %d\n", quality); -} - -static int xc5000_set_params(struct dvb_frontend *fe) -{ - int ret, b; - struct xc5000_priv *priv = fe->tuner_priv; - u32 bw = fe->dtv_property_cache.bandwidth_hz; - u32 freq = fe->dtv_property_cache.frequency; - u32 delsys = fe->dtv_property_cache.delivery_system; - - if (xc5000_is_firmware_loaded(fe) != XC_RESULT_SUCCESS) { - if (xc_load_fw_and_init_tuner(fe) != XC_RESULT_SUCCESS) { - dprintk(1, "Unable to load firmware and init tuner\n"); - return -EINVAL; - } - } - - dprintk(1, "%s() frequency=%d (Hz)\n", __func__, freq); - - switch (delsys) { - case SYS_ATSC: - dprintk(1, "%s() VSB modulation\n", __func__); - priv->rf_mode = XC_RF_MODE_AIR; - priv->freq_hz = freq - 1750000; - priv->video_standard = DTV6; - break; - case SYS_DVBC_ANNEX_B: - dprintk(1, "%s() QAM modulation\n", __func__); - priv->rf_mode = XC_RF_MODE_CABLE; - priv->freq_hz = freq - 1750000; - priv->video_standard = DTV6; - break; - case SYS_ISDBT: - /* All ISDB-T are currently for 6 MHz bw */ - if (!bw) - bw = 6000000; - /* fall to OFDM handling */ - case SYS_DMBTH: - case SYS_DVBT: - case SYS_DVBT2: - dprintk(1, "%s() OFDM\n", __func__); - switch (bw) { - case 6000000: - priv->video_standard = DTV6; - priv->freq_hz = freq - 1750000; - break; - case 7000000: - priv->video_standard = DTV7; - priv->freq_hz = freq - 2250000; - break; - case 8000000: - priv->video_standard = DTV8; - priv->freq_hz = freq - 2750000; - break; - default: - printk(KERN_ERR "xc5000 bandwidth not set!\n"); - return -EINVAL; - } - priv->rf_mode = XC_RF_MODE_AIR; - case SYS_DVBC_ANNEX_A: - case SYS_DVBC_ANNEX_C: - dprintk(1, "%s() QAM modulation\n", __func__); - priv->rf_mode = XC_RF_MODE_CABLE; - if (bw <= 6000000) { - priv->video_standard = DTV6; - priv->freq_hz = freq - 1750000; - b = 6; - } else if (bw <= 7000000) { - priv->video_standard = DTV7; - priv->freq_hz = freq - 2250000; - b = 7; - } else { - priv->video_standard = DTV7_8; - priv->freq_hz = freq - 2750000; - b = 8; - } - dprintk(1, "%s() Bandwidth %dMHz (%d)\n", __func__, - b, bw); - break; - default: - printk(KERN_ERR "xc5000: delivery system is not supported!\n"); - return -EINVAL; - } - - dprintk(1, "%s() frequency=%d (compensated to %d)\n", - __func__, freq, priv->freq_hz); - - ret = xc_SetSignalSource(priv, priv->rf_mode); - if (ret != XC_RESULT_SUCCESS) { - printk(KERN_ERR - "xc5000: xc_SetSignalSource(%d) failed\n", - priv->rf_mode); - return -EREMOTEIO; - } - - ret = xc_SetTVStandard(priv, - XC5000_Standard[priv->video_standard].VideoMode, - XC5000_Standard[priv->video_standard].AudioMode); - if (ret != XC_RESULT_SUCCESS) { - printk(KERN_ERR "xc5000: xc_SetTVStandard failed\n"); - return -EREMOTEIO; - } - - ret = xc_set_IF_frequency(priv, priv->if_khz); - if (ret != XC_RESULT_SUCCESS) { - printk(KERN_ERR "xc5000: xc_Set_IF_frequency(%d) failed\n", - priv->if_khz); - return -EIO; - } - - xc_write_reg(priv, XREG_OUTPUT_AMP, 0x8a); - - xc_tune_channel(priv, priv->freq_hz, XC_TUNE_DIGITAL); - - if (debug) - xc_debug_dump(priv); - - priv->bandwidth = bw; - - return 0; -} - -static int xc5000_is_firmware_loaded(struct dvb_frontend *fe) -{ - struct xc5000_priv *priv = fe->tuner_priv; - int ret; - u16 id; - - ret = xc5000_readreg(priv, XREG_PRODUCT_ID, &id); - if (ret == XC_RESULT_SUCCESS) { - if (id == XC_PRODUCT_ID_FW_NOT_LOADED) - ret = XC_RESULT_RESET_FAILURE; - else - ret = XC_RESULT_SUCCESS; - } - - dprintk(1, "%s() returns %s id = 0x%x\n", __func__, - ret == XC_RESULT_SUCCESS ? "True" : "False", id); - return ret; -} - -static int xc5000_set_tv_freq(struct dvb_frontend *fe, - struct analog_parameters *params) -{ - struct xc5000_priv *priv = fe->tuner_priv; - int ret; - - dprintk(1, "%s() frequency=%d (in units of 62.5khz)\n", - __func__, params->frequency); - - /* Fix me: it could be air. */ - priv->rf_mode = params->mode; - if (params->mode > XC_RF_MODE_CABLE) - priv->rf_mode = XC_RF_MODE_CABLE; - - /* params->frequency is in units of 62.5khz */ - priv->freq_hz = params->frequency * 62500; - - /* FIX ME: Some video standards may have several possible audio - standards. We simply default to one of them here. - */ - if (params->std & V4L2_STD_MN) { - /* default to BTSC audio standard */ - priv->video_standard = MN_NTSC_PAL_BTSC; - goto tune_channel; - } - - if (params->std & V4L2_STD_PAL_BG) { - /* default to NICAM audio standard */ - priv->video_standard = BG_PAL_NICAM; - goto tune_channel; - } - - if (params->std & V4L2_STD_PAL_I) { - /* default to NICAM audio standard */ - priv->video_standard = I_PAL_NICAM; - goto tune_channel; - } - - if (params->std & V4L2_STD_PAL_DK) { - /* default to NICAM audio standard */ - priv->video_standard = DK_PAL_NICAM; - goto tune_channel; - } - - if (params->std & V4L2_STD_SECAM_DK) { - /* default to A2 DK1 audio standard */ - priv->video_standard = DK_SECAM_A2DK1; - goto tune_channel; - } - - if (params->std & V4L2_STD_SECAM_L) { - priv->video_standard = L_SECAM_NICAM; - goto tune_channel; - } - - if (params->std & V4L2_STD_SECAM_LC) { - priv->video_standard = LC_SECAM_NICAM; - goto tune_channel; - } - -tune_channel: - ret = xc_SetSignalSource(priv, priv->rf_mode); - if (ret != XC_RESULT_SUCCESS) { - printk(KERN_ERR - "xc5000: xc_SetSignalSource(%d) failed\n", - priv->rf_mode); - return -EREMOTEIO; - } - - ret = xc_SetTVStandard(priv, - XC5000_Standard[priv->video_standard].VideoMode, - XC5000_Standard[priv->video_standard].AudioMode); - if (ret != XC_RESULT_SUCCESS) { - printk(KERN_ERR "xc5000: xc_SetTVStandard failed\n"); - return -EREMOTEIO; - } - - xc_write_reg(priv, XREG_OUTPUT_AMP, 0x09); - - xc_tune_channel(priv, priv->freq_hz, XC_TUNE_ANALOG); - - if (debug) - xc_debug_dump(priv); - - return 0; -} - -static int xc5000_set_radio_freq(struct dvb_frontend *fe, - struct analog_parameters *params) -{ - struct xc5000_priv *priv = fe->tuner_priv; - int ret = -EINVAL; - u8 radio_input; - - dprintk(1, "%s() frequency=%d (in units of khz)\n", - __func__, params->frequency); - - if (priv->radio_input == XC5000_RADIO_NOT_CONFIGURED) { - dprintk(1, "%s() radio input not configured\n", __func__); - return -EINVAL; - } - - if (priv->radio_input == XC5000_RADIO_FM1) - radio_input = FM_Radio_INPUT1; - else if (priv->radio_input == XC5000_RADIO_FM2) - radio_input = FM_Radio_INPUT2; - else if (priv->radio_input == XC5000_RADIO_FM1_MONO) - radio_input = FM_Radio_INPUT1_MONO; - else { - dprintk(1, "%s() unknown radio input %d\n", __func__, - priv->radio_input); - return -EINVAL; - } - - priv->freq_hz = params->frequency * 125 / 2; - - priv->rf_mode = XC_RF_MODE_AIR; - - ret = xc_SetTVStandard(priv, XC5000_Standard[radio_input].VideoMode, - XC5000_Standard[radio_input].AudioMode); - - if (ret != XC_RESULT_SUCCESS) { - printk(KERN_ERR "xc5000: xc_SetTVStandard failed\n"); - return -EREMOTEIO; - } - - ret = xc_SetSignalSource(priv, priv->rf_mode); - if (ret != XC_RESULT_SUCCESS) { - printk(KERN_ERR - "xc5000: xc_SetSignalSource(%d) failed\n", - priv->rf_mode); - return -EREMOTEIO; - } - - if ((priv->radio_input == XC5000_RADIO_FM1) || - (priv->radio_input == XC5000_RADIO_FM2)) - xc_write_reg(priv, XREG_OUTPUT_AMP, 0x09); - else if (priv->radio_input == XC5000_RADIO_FM1_MONO) - xc_write_reg(priv, XREG_OUTPUT_AMP, 0x06); - - xc_tune_channel(priv, priv->freq_hz, XC_TUNE_ANALOG); - - return 0; -} - -static int xc5000_set_analog_params(struct dvb_frontend *fe, - struct analog_parameters *params) -{ - struct xc5000_priv *priv = fe->tuner_priv; - int ret = -EINVAL; - - if (priv->i2c_props.adap == NULL) - return -EINVAL; - - if (xc5000_is_firmware_loaded(fe) != XC_RESULT_SUCCESS) { - if (xc_load_fw_and_init_tuner(fe) != XC_RESULT_SUCCESS) { - dprintk(1, "Unable to load firmware and init tuner\n"); - return -EINVAL; - } - } - - switch (params->mode) { - case V4L2_TUNER_RADIO: - ret = xc5000_set_radio_freq(fe, params); - break; - case V4L2_TUNER_ANALOG_TV: - case V4L2_TUNER_DIGITAL_TV: - ret = xc5000_set_tv_freq(fe, params); - break; - } - - return ret; -} - - -static int xc5000_get_frequency(struct dvb_frontend *fe, u32 *freq) -{ - struct xc5000_priv *priv = fe->tuner_priv; - dprintk(1, "%s()\n", __func__); - *freq = priv->freq_hz; - return 0; -} - -static int xc5000_get_if_frequency(struct dvb_frontend *fe, u32 *freq) -{ - struct xc5000_priv *priv = fe->tuner_priv; - dprintk(1, "%s()\n", __func__); - *freq = priv->if_khz * 1000; - return 0; -} - -static int xc5000_get_bandwidth(struct dvb_frontend *fe, u32 *bw) -{ - struct xc5000_priv *priv = fe->tuner_priv; - dprintk(1, "%s()\n", __func__); - - *bw = priv->bandwidth; - return 0; -} - -static int xc5000_get_status(struct dvb_frontend *fe, u32 *status) -{ - struct xc5000_priv *priv = fe->tuner_priv; - u16 lock_status = 0; - - xc_get_lock_status(priv, &lock_status); - - dprintk(1, "%s() lock_status = 0x%08x\n", __func__, lock_status); - - *status = lock_status; - - return 0; -} - -static int xc_load_fw_and_init_tuner(struct dvb_frontend *fe) -{ - struct xc5000_priv *priv = fe->tuner_priv; - int ret = 0; - - if (xc5000_is_firmware_loaded(fe) != XC_RESULT_SUCCESS) { - ret = xc5000_fwupload(fe); - if (ret != XC_RESULT_SUCCESS) - return ret; - } - - /* Start the tuner self-calibration process */ - ret |= xc_initialize(priv); - - /* Wait for calibration to complete. - * We could continue but XC5000 will clock stretch subsequent - * I2C transactions until calibration is complete. This way we - * don't have to rely on clock stretching working. - */ - xc_wait(100); - - /* Default to "CABLE" mode */ - ret |= xc_write_reg(priv, XREG_SIGNALSOURCE, XC_RF_MODE_CABLE); - - return ret; -} - -static int xc5000_sleep(struct dvb_frontend *fe) -{ - int ret; - - dprintk(1, "%s()\n", __func__); - - /* Avoid firmware reload on slow devices */ - if (no_poweroff) - return 0; - - /* According to Xceive technical support, the "powerdown" register - was removed in newer versions of the firmware. The "supported" - way to sleep the tuner is to pull the reset pin low for 10ms */ - ret = xc5000_TunerReset(fe); - if (ret != XC_RESULT_SUCCESS) { - printk(KERN_ERR - "xc5000: %s() unable to shutdown tuner\n", - __func__); - return -EREMOTEIO; - } else - return XC_RESULT_SUCCESS; -} - -static int xc5000_init(struct dvb_frontend *fe) -{ - struct xc5000_priv *priv = fe->tuner_priv; - dprintk(1, "%s()\n", __func__); - - if (xc_load_fw_and_init_tuner(fe) != XC_RESULT_SUCCESS) { - printk(KERN_ERR "xc5000: Unable to initialise tuner\n"); - return -EREMOTEIO; - } - - if (debug) - xc_debug_dump(priv); - - return 0; -} - -static int xc5000_release(struct dvb_frontend *fe) -{ - struct xc5000_priv *priv = fe->tuner_priv; - - dprintk(1, "%s()\n", __func__); - - mutex_lock(&xc5000_list_mutex); - - if (priv) - hybrid_tuner_release_state(priv); - - mutex_unlock(&xc5000_list_mutex); - - fe->tuner_priv = NULL; - - return 0; -} - -static int xc5000_set_config(struct dvb_frontend *fe, void *priv_cfg) -{ - struct xc5000_priv *priv = fe->tuner_priv; - struct xc5000_config *p = priv_cfg; - - dprintk(1, "%s()\n", __func__); - - if (p->if_khz) - priv->if_khz = p->if_khz; - - if (p->radio_input) - priv->radio_input = p->radio_input; - - return 0; -} - - -static const struct dvb_tuner_ops xc5000_tuner_ops = { - .info = { - .name = "Xceive XC5000", - .frequency_min = 1000000, - .frequency_max = 1023000000, - .frequency_step = 50000, - }, - - .release = xc5000_release, - .init = xc5000_init, - .sleep = xc5000_sleep, - - .set_config = xc5000_set_config, - .set_params = xc5000_set_params, - .set_analog_params = xc5000_set_analog_params, - .get_frequency = xc5000_get_frequency, - .get_if_frequency = xc5000_get_if_frequency, - .get_bandwidth = xc5000_get_bandwidth, - .get_status = xc5000_get_status -}; - -struct dvb_frontend *xc5000_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, - const struct xc5000_config *cfg) -{ - struct xc5000_priv *priv = NULL; - int instance; - u16 id = 0; - - dprintk(1, "%s(%d-%04x)\n", __func__, - i2c ? i2c_adapter_id(i2c) : -1, - cfg ? cfg->i2c_address : -1); - - mutex_lock(&xc5000_list_mutex); - - instance = hybrid_tuner_request_state(struct xc5000_priv, priv, - hybrid_tuner_instance_list, - i2c, cfg->i2c_address, "xc5000"); - switch (instance) { - case 0: - goto fail; - break; - case 1: - /* new tuner instance */ - priv->bandwidth = 6000000; - fe->tuner_priv = priv; - break; - default: - /* existing tuner instance */ - fe->tuner_priv = priv; - break; - } - - if (priv->if_khz == 0) { - /* If the IF hasn't been set yet, use the value provided by - the caller (occurs in hybrid devices where the analog - call to xc5000_attach occurs before the digital side) */ - priv->if_khz = cfg->if_khz; - } - - if (priv->xtal_khz == 0) - priv->xtal_khz = cfg->xtal_khz; - - if (priv->radio_input == 0) - priv->radio_input = cfg->radio_input; - - /* don't override chip id if it's already been set - unless explicitly specified */ - if ((priv->chip_id == 0) || (cfg->chip_id)) - /* use default chip id if none specified, set to 0 so - it can be overridden if this is a hybrid driver */ - priv->chip_id = (cfg->chip_id) ? cfg->chip_id : 0; - - /* Check if firmware has been loaded. It is possible that another - instance of the driver has loaded the firmware. - */ - if (xc5000_readreg(priv, XREG_PRODUCT_ID, &id) != XC_RESULT_SUCCESS) - goto fail; - - switch (id) { - case XC_PRODUCT_ID_FW_LOADED: - printk(KERN_INFO - "xc5000: Successfully identified at address 0x%02x\n", - cfg->i2c_address); - printk(KERN_INFO - "xc5000: Firmware has been loaded previously\n"); - break; - case XC_PRODUCT_ID_FW_NOT_LOADED: - printk(KERN_INFO - "xc5000: Successfully identified at address 0x%02x\n", - cfg->i2c_address); - printk(KERN_INFO - "xc5000: Firmware has not been loaded previously\n"); - break; - default: - printk(KERN_ERR - "xc5000: Device not found at addr 0x%02x (0x%x)\n", - cfg->i2c_address, id); - goto fail; - } - - mutex_unlock(&xc5000_list_mutex); - - memcpy(&fe->ops.tuner_ops, &xc5000_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - return fe; -fail: - mutex_unlock(&xc5000_list_mutex); - - xc5000_release(fe); - return NULL; -} -EXPORT_SYMBOL(xc5000_attach); - -MODULE_AUTHOR("Steven Toth"); -MODULE_DESCRIPTION("Xceive xc5000 silicon tuner driver"); -MODULE_LICENSE("GPL"); -MODULE_FIRMWARE(XC5000A_FIRMWARE); -MODULE_FIRMWARE(XC5000C_FIRMWARE); diff --git a/drivers/media/dvb-core/Kconfig b/drivers/media/dvb-core/Kconfig new file mode 100644 index 000000000000..fa7a2490ed5f --- /dev/null +++ b/drivers/media/dvb-core/Kconfig @@ -0,0 +1,29 @@ +# +# DVB device configuration +# + +config DVB_MAX_ADAPTERS + int "maximum number of DVB/ATSC adapters" + depends on DVB_CORE + default 8 + range 1 255 + help + Maximum number of DVB/ATSC adapters. Increasing this number + increases the memory consumption of the DVB subsystem even + if a much lower number of DVB/ATSC adapters is present. + Only values in the range 4-32 are tested. + + If you are unsure about this, use the default value 8 + +config DVB_DYNAMIC_MINORS + bool "Dynamic DVB minor allocation" + depends on DVB_CORE + default n + help + If you say Y here, the DVB subsystem will use dynamic minor + allocation for any device that uses the DVB major number. + This means that you can have more than 4 of a single type + of device (like demuxes and frontends) per adapter, but udev + will be required to manage the device nodes. + + If you are unsure about this, say N here. diff --git a/drivers/media/dvb/dvb-core/Makefile b/drivers/media/dvb-core/Makefile similarity index 100% rename from drivers/media/dvb/dvb-core/Makefile rename to drivers/media/dvb-core/Makefile diff --git a/drivers/media/dvb/dvb-core/demux.h b/drivers/media/dvb-core/demux.h similarity index 100% rename from drivers/media/dvb/dvb-core/demux.h rename to drivers/media/dvb-core/demux.h diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c new file mode 100644 index 000000000000..889c9c16c6df --- /dev/null +++ b/drivers/media/dvb-core/dmxdev.c @@ -0,0 +1,1275 @@ +/* + * dmxdev.c - DVB demultiplexer device + * + * Copyright (C) 2000 Ralph Metzler & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "dmxdev.h" + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); + +#define dprintk if (debug) printk + +static int dvb_dmxdev_buffer_write(struct dvb_ringbuffer *buf, + const u8 *src, size_t len) +{ + ssize_t free; + + if (!len) + return 0; + if (!buf->data) + return 0; + + free = dvb_ringbuffer_free(buf); + if (len > free) { + dprintk("dmxdev: buffer overflow\n"); + return -EOVERFLOW; + } + + return dvb_ringbuffer_write(buf, src, len); +} + +static ssize_t dvb_dmxdev_buffer_read(struct dvb_ringbuffer *src, + int non_blocking, char __user *buf, + size_t count, loff_t *ppos) +{ + size_t todo; + ssize_t avail; + ssize_t ret = 0; + + if (!src->data) + return 0; + + if (src->error) { + ret = src->error; + dvb_ringbuffer_flush(src); + return ret; + } + + for (todo = count; todo > 0; todo -= ret) { + if (non_blocking && dvb_ringbuffer_empty(src)) { + ret = -EWOULDBLOCK; + break; + } + + ret = wait_event_interruptible(src->queue, + !dvb_ringbuffer_empty(src) || + (src->error != 0)); + if (ret < 0) + break; + + if (src->error) { + ret = src->error; + dvb_ringbuffer_flush(src); + break; + } + + avail = dvb_ringbuffer_avail(src); + if (avail > todo) + avail = todo; + + ret = dvb_ringbuffer_read_user(src, buf, avail); + if (ret < 0) + break; + + buf += ret; + } + + return (count - todo) ? (count - todo) : ret; +} + +static struct dmx_frontend *get_fe(struct dmx_demux *demux, int type) +{ + struct list_head *head, *pos; + + head = demux->get_frontends(demux); + if (!head) + return NULL; + list_for_each(pos, head) + if (DMX_FE_ENTRY(pos)->source == type) + return DMX_FE_ENTRY(pos); + + return NULL; +} + +static int dvb_dvr_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + struct dmx_frontend *front; + + dprintk("function : %s\n", __func__); + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (dmxdev->exit) { + mutex_unlock(&dmxdev->mutex); + return -ENODEV; + } + + if ((file->f_flags & O_ACCMODE) == O_RDWR) { + if (!(dmxdev->capabilities & DMXDEV_CAP_DUPLEX)) { + mutex_unlock(&dmxdev->mutex); + return -EOPNOTSUPP; + } + } + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + void *mem; + if (!dvbdev->readers) { + mutex_unlock(&dmxdev->mutex); + return -EBUSY; + } + mem = vmalloc(DVR_BUFFER_SIZE); + if (!mem) { + mutex_unlock(&dmxdev->mutex); + return -ENOMEM; + } + dvb_ringbuffer_init(&dmxdev->dvr_buffer, mem, DVR_BUFFER_SIZE); + dvbdev->readers--; + } + + if ((file->f_flags & O_ACCMODE) == O_WRONLY) { + dmxdev->dvr_orig_fe = dmxdev->demux->frontend; + + if (!dmxdev->demux->write) { + mutex_unlock(&dmxdev->mutex); + return -EOPNOTSUPP; + } + + front = get_fe(dmxdev->demux, DMX_MEMORY_FE); + + if (!front) { + mutex_unlock(&dmxdev->mutex); + return -EINVAL; + } + dmxdev->demux->disconnect_frontend(dmxdev->demux); + dmxdev->demux->connect_frontend(dmxdev->demux, front); + } + dvbdev->users++; + mutex_unlock(&dmxdev->mutex); + return 0; +} + +static int dvb_dvr_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + + mutex_lock(&dmxdev->mutex); + + if ((file->f_flags & O_ACCMODE) == O_WRONLY) { + dmxdev->demux->disconnect_frontend(dmxdev->demux); + dmxdev->demux->connect_frontend(dmxdev->demux, + dmxdev->dvr_orig_fe); + } + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + dvbdev->readers++; + if (dmxdev->dvr_buffer.data) { + void *mem = dmxdev->dvr_buffer.data; + mb(); + spin_lock_irq(&dmxdev->lock); + dmxdev->dvr_buffer.data = NULL; + spin_unlock_irq(&dmxdev->lock); + vfree(mem); + } + } + /* TODO */ + dvbdev->users--; + if (dvbdev->users == 1 && dmxdev->exit == 1) { + fops_put(file->f_op); + file->f_op = NULL; + mutex_unlock(&dmxdev->mutex); + wake_up(&dvbdev->wait_queue); + } else + mutex_unlock(&dmxdev->mutex); + + return 0; +} + +static ssize_t dvb_dvr_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + int ret; + + if (!dmxdev->demux->write) + return -EOPNOTSUPP; + if ((file->f_flags & O_ACCMODE) != O_WRONLY) + return -EINVAL; + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (dmxdev->exit) { + mutex_unlock(&dmxdev->mutex); + return -ENODEV; + } + ret = dmxdev->demux->write(dmxdev->demux, buf, count); + mutex_unlock(&dmxdev->mutex); + return ret; +} + +static ssize_t dvb_dvr_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + + if (dmxdev->exit) + return -ENODEV; + + return dvb_dmxdev_buffer_read(&dmxdev->dvr_buffer, + file->f_flags & O_NONBLOCK, + buf, count, ppos); +} + +static int dvb_dvr_set_buffer_size(struct dmxdev *dmxdev, + unsigned long size) +{ + struct dvb_ringbuffer *buf = &dmxdev->dvr_buffer; + void *newmem; + void *oldmem; + + dprintk("function : %s\n", __func__); + + if (buf->size == size) + return 0; + if (!size) + return -EINVAL; + + newmem = vmalloc(size); + if (!newmem) + return -ENOMEM; + + oldmem = buf->data; + + spin_lock_irq(&dmxdev->lock); + buf->data = newmem; + buf->size = size; + + /* reset and not flush in case the buffer shrinks */ + dvb_ringbuffer_reset(buf); + spin_unlock_irq(&dmxdev->lock); + + vfree(oldmem); + + return 0; +} + +static inline void dvb_dmxdev_filter_state_set(struct dmxdev_filter + *dmxdevfilter, int state) +{ + spin_lock_irq(&dmxdevfilter->dev->lock); + dmxdevfilter->state = state; + spin_unlock_irq(&dmxdevfilter->dev->lock); +} + +static int dvb_dmxdev_set_buffer_size(struct dmxdev_filter *dmxdevfilter, + unsigned long size) +{ + struct dvb_ringbuffer *buf = &dmxdevfilter->buffer; + void *newmem; + void *oldmem; + + if (buf->size == size) + return 0; + if (!size) + return -EINVAL; + if (dmxdevfilter->state >= DMXDEV_STATE_GO) + return -EBUSY; + + newmem = vmalloc(size); + if (!newmem) + return -ENOMEM; + + oldmem = buf->data; + + spin_lock_irq(&dmxdevfilter->dev->lock); + buf->data = newmem; + buf->size = size; + + /* reset and not flush in case the buffer shrinks */ + dvb_ringbuffer_reset(buf); + spin_unlock_irq(&dmxdevfilter->dev->lock); + + vfree(oldmem); + + return 0; +} + +static void dvb_dmxdev_filter_timeout(unsigned long data) +{ + struct dmxdev_filter *dmxdevfilter = (struct dmxdev_filter *)data; + + dmxdevfilter->buffer.error = -ETIMEDOUT; + spin_lock_irq(&dmxdevfilter->dev->lock); + dmxdevfilter->state = DMXDEV_STATE_TIMEDOUT; + spin_unlock_irq(&dmxdevfilter->dev->lock); + wake_up(&dmxdevfilter->buffer.queue); +} + +static void dvb_dmxdev_filter_timer(struct dmxdev_filter *dmxdevfilter) +{ + struct dmx_sct_filter_params *para = &dmxdevfilter->params.sec; + + del_timer(&dmxdevfilter->timer); + if (para->timeout) { + dmxdevfilter->timer.function = dvb_dmxdev_filter_timeout; + dmxdevfilter->timer.data = (unsigned long)dmxdevfilter; + dmxdevfilter->timer.expires = + jiffies + 1 + (HZ / 2 + HZ * para->timeout) / 1000; + add_timer(&dmxdevfilter->timer); + } +} + +static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_section_filter *filter, + enum dmx_success success) +{ + struct dmxdev_filter *dmxdevfilter = filter->priv; + int ret; + + if (dmxdevfilter->buffer.error) { + wake_up(&dmxdevfilter->buffer.queue); + return 0; + } + spin_lock(&dmxdevfilter->dev->lock); + if (dmxdevfilter->state != DMXDEV_STATE_GO) { + spin_unlock(&dmxdevfilter->dev->lock); + return 0; + } + del_timer(&dmxdevfilter->timer); + dprintk("dmxdev: section callback %*ph\n", 6, buffer1); + ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, buffer1, + buffer1_len); + if (ret == buffer1_len) { + ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, buffer2, + buffer2_len); + } + if (ret < 0) { + dvb_ringbuffer_flush(&dmxdevfilter->buffer); + dmxdevfilter->buffer.error = ret; + } + if (dmxdevfilter->params.sec.flags & DMX_ONESHOT) + dmxdevfilter->state = DMXDEV_STATE_DONE; + spin_unlock(&dmxdevfilter->dev->lock); + wake_up(&dmxdevfilter->buffer.queue); + return 0; +} + +static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_ts_feed *feed, + enum dmx_success success) +{ + struct dmxdev_filter *dmxdevfilter = feed->priv; + struct dvb_ringbuffer *buffer; + int ret; + + spin_lock(&dmxdevfilter->dev->lock); + if (dmxdevfilter->params.pes.output == DMX_OUT_DECODER) { + spin_unlock(&dmxdevfilter->dev->lock); + return 0; + } + + if (dmxdevfilter->params.pes.output == DMX_OUT_TAP + || dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP) + buffer = &dmxdevfilter->buffer; + else + buffer = &dmxdevfilter->dev->dvr_buffer; + if (buffer->error) { + spin_unlock(&dmxdevfilter->dev->lock); + wake_up(&buffer->queue); + return 0; + } + ret = dvb_dmxdev_buffer_write(buffer, buffer1, buffer1_len); + if (ret == buffer1_len) + ret = dvb_dmxdev_buffer_write(buffer, buffer2, buffer2_len); + if (ret < 0) { + dvb_ringbuffer_flush(buffer); + buffer->error = ret; + } + spin_unlock(&dmxdevfilter->dev->lock); + wake_up(&buffer->queue); + return 0; +} + +/* stop feed but only mark the specified filter as stopped (state set) */ +static int dvb_dmxdev_feed_stop(struct dmxdev_filter *dmxdevfilter) +{ + struct dmxdev_feed *feed; + + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); + + switch (dmxdevfilter->type) { + case DMXDEV_TYPE_SEC: + del_timer(&dmxdevfilter->timer); + dmxdevfilter->feed.sec->stop_filtering(dmxdevfilter->feed.sec); + break; + case DMXDEV_TYPE_PES: + list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) + feed->ts->stop_filtering(feed->ts); + break; + default: + return -EINVAL; + } + return 0; +} + +/* start feed associated with the specified filter */ +static int dvb_dmxdev_feed_start(struct dmxdev_filter *filter) +{ + struct dmxdev_feed *feed; + int ret; + + dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); + + switch (filter->type) { + case DMXDEV_TYPE_SEC: + return filter->feed.sec->start_filtering(filter->feed.sec); + case DMXDEV_TYPE_PES: + list_for_each_entry(feed, &filter->feed.ts, next) { + ret = feed->ts->start_filtering(feed->ts); + if (ret < 0) { + dvb_dmxdev_feed_stop(filter); + return ret; + } + } + break; + default: + return -EINVAL; + } + + return 0; +} + +/* restart section feed if it has filters left associated with it, + otherwise release the feed */ +static int dvb_dmxdev_feed_restart(struct dmxdev_filter *filter) +{ + int i; + struct dmxdev *dmxdev = filter->dev; + u16 pid = filter->params.sec.pid; + + for (i = 0; i < dmxdev->filternum; i++) + if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && + dmxdev->filter[i].type == DMXDEV_TYPE_SEC && + dmxdev->filter[i].params.sec.pid == pid) { + dvb_dmxdev_feed_start(&dmxdev->filter[i]); + return 0; + } + + filter->dev->demux->release_section_feed(dmxdev->demux, + filter->feed.sec); + + return 0; +} + +static int dvb_dmxdev_filter_stop(struct dmxdev_filter *dmxdevfilter) +{ + struct dmxdev_feed *feed; + struct dmx_demux *demux; + + if (dmxdevfilter->state < DMXDEV_STATE_GO) + return 0; + + switch (dmxdevfilter->type) { + case DMXDEV_TYPE_SEC: + if (!dmxdevfilter->feed.sec) + break; + dvb_dmxdev_feed_stop(dmxdevfilter); + if (dmxdevfilter->filter.sec) + dmxdevfilter->feed.sec-> + release_filter(dmxdevfilter->feed.sec, + dmxdevfilter->filter.sec); + dvb_dmxdev_feed_restart(dmxdevfilter); + dmxdevfilter->feed.sec = NULL; + break; + case DMXDEV_TYPE_PES: + dvb_dmxdev_feed_stop(dmxdevfilter); + demux = dmxdevfilter->dev->demux; + list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) { + demux->release_ts_feed(demux, feed->ts); + feed->ts = NULL; + } + break; + default: + if (dmxdevfilter->state == DMXDEV_STATE_ALLOCATED) + return 0; + return -EINVAL; + } + + dvb_ringbuffer_flush(&dmxdevfilter->buffer); + return 0; +} + +static void dvb_dmxdev_delete_pids(struct dmxdev_filter *dmxdevfilter) +{ + struct dmxdev_feed *feed, *tmp; + + /* delete all PIDs */ + list_for_each_entry_safe(feed, tmp, &dmxdevfilter->feed.ts, next) { + list_del(&feed->next); + kfree(feed); + } + + BUG_ON(!list_empty(&dmxdevfilter->feed.ts)); +} + +static inline int dvb_dmxdev_filter_reset(struct dmxdev_filter *dmxdevfilter) +{ + if (dmxdevfilter->state < DMXDEV_STATE_SET) + return 0; + + if (dmxdevfilter->type == DMXDEV_TYPE_PES) + dvb_dmxdev_delete_pids(dmxdevfilter); + + dmxdevfilter->type = DMXDEV_TYPE_NONE; + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); + return 0; +} + +static int dvb_dmxdev_start_feed(struct dmxdev *dmxdev, + struct dmxdev_filter *filter, + struct dmxdev_feed *feed) +{ + struct timespec timeout = { 0 }; + struct dmx_pes_filter_params *para = &filter->params.pes; + dmx_output_t otype; + int ret; + int ts_type; + dmx_pes_type_t ts_pes; + struct dmx_ts_feed *tsfeed; + + feed->ts = NULL; + otype = para->output; + + ts_pes = para->pes_type; + + if (ts_pes < DMX_PES_OTHER) + ts_type = TS_DECODER; + else + ts_type = 0; + + if (otype == DMX_OUT_TS_TAP) + ts_type |= TS_PACKET; + else if (otype == DMX_OUT_TSDEMUX_TAP) + ts_type |= TS_PACKET | TS_DEMUX; + else if (otype == DMX_OUT_TAP) + ts_type |= TS_PACKET | TS_DEMUX | TS_PAYLOAD_ONLY; + + ret = dmxdev->demux->allocate_ts_feed(dmxdev->demux, &feed->ts, + dvb_dmxdev_ts_callback); + if (ret < 0) + return ret; + + tsfeed = feed->ts; + tsfeed->priv = filter; + + ret = tsfeed->set(tsfeed, feed->pid, ts_type, ts_pes, 32768, timeout); + if (ret < 0) { + dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); + return ret; + } + + ret = tsfeed->start_filtering(tsfeed); + if (ret < 0) { + dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); + return ret; + } + + return 0; +} + +static int dvb_dmxdev_filter_start(struct dmxdev_filter *filter) +{ + struct dmxdev *dmxdev = filter->dev; + struct dmxdev_feed *feed; + void *mem; + int ret, i; + + if (filter->state < DMXDEV_STATE_SET) + return -EINVAL; + + if (filter->state >= DMXDEV_STATE_GO) + dvb_dmxdev_filter_stop(filter); + + if (!filter->buffer.data) { + mem = vmalloc(filter->buffer.size); + if (!mem) + return -ENOMEM; + spin_lock_irq(&filter->dev->lock); + filter->buffer.data = mem; + spin_unlock_irq(&filter->dev->lock); + } + + dvb_ringbuffer_flush(&filter->buffer); + + switch (filter->type) { + case DMXDEV_TYPE_SEC: + { + struct dmx_sct_filter_params *para = &filter->params.sec; + struct dmx_section_filter **secfilter = &filter->filter.sec; + struct dmx_section_feed **secfeed = &filter->feed.sec; + + *secfilter = NULL; + *secfeed = NULL; + + + /* find active filter/feed with same PID */ + for (i = 0; i < dmxdev->filternum; i++) { + if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && + dmxdev->filter[i].type == DMXDEV_TYPE_SEC && + dmxdev->filter[i].params.sec.pid == para->pid) { + *secfeed = dmxdev->filter[i].feed.sec; + break; + } + } + + /* if no feed found, try to allocate new one */ + if (!*secfeed) { + ret = dmxdev->demux->allocate_section_feed(dmxdev->demux, + secfeed, + dvb_dmxdev_section_callback); + if (ret < 0) { + printk("DVB (%s): could not alloc feed\n", + __func__); + return ret; + } + + ret = (*secfeed)->set(*secfeed, para->pid, 32768, + (para->flags & DMX_CHECK_CRC) ? 1 : 0); + if (ret < 0) { + printk("DVB (%s): could not set feed\n", + __func__); + dvb_dmxdev_feed_restart(filter); + return ret; + } + } else { + dvb_dmxdev_feed_stop(filter); + } + + ret = (*secfeed)->allocate_filter(*secfeed, secfilter); + if (ret < 0) { + dvb_dmxdev_feed_restart(filter); + filter->feed.sec->start_filtering(*secfeed); + dprintk("could not get filter\n"); + return ret; + } + + (*secfilter)->priv = filter; + + memcpy(&((*secfilter)->filter_value[3]), + &(para->filter.filter[1]), DMX_FILTER_SIZE - 1); + memcpy(&(*secfilter)->filter_mask[3], + ¶->filter.mask[1], DMX_FILTER_SIZE - 1); + memcpy(&(*secfilter)->filter_mode[3], + ¶->filter.mode[1], DMX_FILTER_SIZE - 1); + + (*secfilter)->filter_value[0] = para->filter.filter[0]; + (*secfilter)->filter_mask[0] = para->filter.mask[0]; + (*secfilter)->filter_mode[0] = para->filter.mode[0]; + (*secfilter)->filter_mask[1] = 0; + (*secfilter)->filter_mask[2] = 0; + + filter->todo = 0; + + ret = filter->feed.sec->start_filtering(filter->feed.sec); + if (ret < 0) + return ret; + + dvb_dmxdev_filter_timer(filter); + break; + } + case DMXDEV_TYPE_PES: + list_for_each_entry(feed, &filter->feed.ts, next) { + ret = dvb_dmxdev_start_feed(dmxdev, filter, feed); + if (ret < 0) { + dvb_dmxdev_filter_stop(filter); + return ret; + } + } + break; + default: + return -EINVAL; + } + + dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); + return 0; +} + +static int dvb_demux_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + int i; + struct dmxdev_filter *dmxdevfilter; + + if (!dmxdev->filter) + return -EINVAL; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + for (i = 0; i < dmxdev->filternum; i++) + if (dmxdev->filter[i].state == DMXDEV_STATE_FREE) + break; + + if (i == dmxdev->filternum) { + mutex_unlock(&dmxdev->mutex); + return -EMFILE; + } + + dmxdevfilter = &dmxdev->filter[i]; + mutex_init(&dmxdevfilter->mutex); + file->private_data = dmxdevfilter; + + dvb_ringbuffer_init(&dmxdevfilter->buffer, NULL, 8192); + dmxdevfilter->type = DMXDEV_TYPE_NONE; + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); + init_timer(&dmxdevfilter->timer); + + dvbdev->users++; + + mutex_unlock(&dmxdev->mutex); + return 0; +} + +static int dvb_dmxdev_filter_free(struct dmxdev *dmxdev, + struct dmxdev_filter *dmxdevfilter) +{ + mutex_lock(&dmxdev->mutex); + mutex_lock(&dmxdevfilter->mutex); + + dvb_dmxdev_filter_stop(dmxdevfilter); + dvb_dmxdev_filter_reset(dmxdevfilter); + + if (dmxdevfilter->buffer.data) { + void *mem = dmxdevfilter->buffer.data; + + spin_lock_irq(&dmxdev->lock); + dmxdevfilter->buffer.data = NULL; + spin_unlock_irq(&dmxdev->lock); + vfree(mem); + } + + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_FREE); + wake_up(&dmxdevfilter->buffer.queue); + mutex_unlock(&dmxdevfilter->mutex); + mutex_unlock(&dmxdev->mutex); + return 0; +} + +static inline void invert_mode(dmx_filter_t *filter) +{ + int i; + + for (i = 0; i < DMX_FILTER_SIZE; i++) + filter->mode[i] ^= 0xff; +} + +static int dvb_dmxdev_add_pid(struct dmxdev *dmxdev, + struct dmxdev_filter *filter, u16 pid) +{ + struct dmxdev_feed *feed; + + if ((filter->type != DMXDEV_TYPE_PES) || + (filter->state < DMXDEV_STATE_SET)) + return -EINVAL; + + /* only TS packet filters may have multiple PIDs */ + if ((filter->params.pes.output != DMX_OUT_TSDEMUX_TAP) && + (!list_empty(&filter->feed.ts))) + return -EINVAL; + + feed = kzalloc(sizeof(struct dmxdev_feed), GFP_KERNEL); + if (feed == NULL) + return -ENOMEM; + + feed->pid = pid; + list_add(&feed->next, &filter->feed.ts); + + if (filter->state >= DMXDEV_STATE_GO) + return dvb_dmxdev_start_feed(dmxdev, filter, feed); + + return 0; +} + +static int dvb_dmxdev_remove_pid(struct dmxdev *dmxdev, + struct dmxdev_filter *filter, u16 pid) +{ + struct dmxdev_feed *feed, *tmp; + + if ((filter->type != DMXDEV_TYPE_PES) || + (filter->state < DMXDEV_STATE_SET)) + return -EINVAL; + + list_for_each_entry_safe(feed, tmp, &filter->feed.ts, next) { + if ((feed->pid == pid) && (feed->ts != NULL)) { + feed->ts->stop_filtering(feed->ts); + filter->dev->demux->release_ts_feed(filter->dev->demux, + feed->ts); + list_del(&feed->next); + kfree(feed); + } + } + + return 0; +} + +static int dvb_dmxdev_filter_set(struct dmxdev *dmxdev, + struct dmxdev_filter *dmxdevfilter, + struct dmx_sct_filter_params *params) +{ + dprintk("function : %s\n", __func__); + + dvb_dmxdev_filter_stop(dmxdevfilter); + + dmxdevfilter->type = DMXDEV_TYPE_SEC; + memcpy(&dmxdevfilter->params.sec, + params, sizeof(struct dmx_sct_filter_params)); + invert_mode(&dmxdevfilter->params.sec.filter); + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); + + if (params->flags & DMX_IMMEDIATE_START) + return dvb_dmxdev_filter_start(dmxdevfilter); + + return 0; +} + +static int dvb_dmxdev_pes_filter_set(struct dmxdev *dmxdev, + struct dmxdev_filter *dmxdevfilter, + struct dmx_pes_filter_params *params) +{ + int ret; + + dvb_dmxdev_filter_stop(dmxdevfilter); + dvb_dmxdev_filter_reset(dmxdevfilter); + + if (params->pes_type > DMX_PES_OTHER || params->pes_type < 0) + return -EINVAL; + + dmxdevfilter->type = DMXDEV_TYPE_PES; + memcpy(&dmxdevfilter->params, params, + sizeof(struct dmx_pes_filter_params)); + INIT_LIST_HEAD(&dmxdevfilter->feed.ts); + + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); + + ret = dvb_dmxdev_add_pid(dmxdev, dmxdevfilter, + dmxdevfilter->params.pes.pid); + if (ret < 0) + return ret; + + if (params->flags & DMX_IMMEDIATE_START) + return dvb_dmxdev_filter_start(dmxdevfilter); + + return 0; +} + +static ssize_t dvb_dmxdev_read_sec(struct dmxdev_filter *dfil, + struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int result, hcount; + int done = 0; + + if (dfil->todo <= 0) { + hcount = 3 + dfil->todo; + if (hcount > count) + hcount = count; + result = dvb_dmxdev_buffer_read(&dfil->buffer, + file->f_flags & O_NONBLOCK, + buf, hcount, ppos); + if (result < 0) { + dfil->todo = 0; + return result; + } + if (copy_from_user(dfil->secheader - dfil->todo, buf, result)) + return -EFAULT; + buf += result; + done = result; + count -= result; + dfil->todo -= result; + if (dfil->todo > -3) + return done; + dfil->todo = ((dfil->secheader[1] << 8) | dfil->secheader[2]) & 0xfff; + if (!count) + return done; + } + if (count > dfil->todo) + count = dfil->todo; + result = dvb_dmxdev_buffer_read(&dfil->buffer, + file->f_flags & O_NONBLOCK, + buf, count, ppos); + if (result < 0) + return result; + dfil->todo -= result; + return (result + done); +} + +static ssize_t +dvb_demux_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + int ret; + + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) + return -ERESTARTSYS; + + if (dmxdevfilter->type == DMXDEV_TYPE_SEC) + ret = dvb_dmxdev_read_sec(dmxdevfilter, file, buf, count, ppos); + else + ret = dvb_dmxdev_buffer_read(&dmxdevfilter->buffer, + file->f_flags & O_NONBLOCK, + buf, count, ppos); + + mutex_unlock(&dmxdevfilter->mutex); + return ret; +} + +static int dvb_demux_do_ioctl(struct file *file, + unsigned int cmd, void *parg) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + struct dmxdev *dmxdev = dmxdevfilter->dev; + unsigned long arg = (unsigned long)parg; + int ret = 0; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + switch (cmd) { + case DMX_START: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + if (dmxdevfilter->state < DMXDEV_STATE_SET) + ret = -EINVAL; + else + ret = dvb_dmxdev_filter_start(dmxdevfilter); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_STOP: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_filter_stop(dmxdevfilter); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_SET_FILTER: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_filter_set(dmxdev, dmxdevfilter, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_SET_PES_FILTER: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_pes_filter_set(dmxdev, dmxdevfilter, parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_SET_BUFFER_SIZE: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + mutex_unlock(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_set_buffer_size(dmxdevfilter, arg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_GET_PES_PIDS: + if (!dmxdev->demux->get_pes_pids) { + ret = -EINVAL; + break; + } + dmxdev->demux->get_pes_pids(dmxdev->demux, parg); + break; + + case DMX_GET_CAPS: + if (!dmxdev->demux->get_caps) { + ret = -EINVAL; + break; + } + ret = dmxdev->demux->get_caps(dmxdev->demux, parg); + break; + + case DMX_SET_SOURCE: + if (!dmxdev->demux->set_source) { + ret = -EINVAL; + break; + } + ret = dmxdev->demux->set_source(dmxdev->demux, parg); + break; + + case DMX_GET_STC: + if (!dmxdev->demux->get_stc) { + ret = -EINVAL; + break; + } + ret = dmxdev->demux->get_stc(dmxdev->demux, + ((struct dmx_stc *)parg)->num, + &((struct dmx_stc *)parg)->stc, + &((struct dmx_stc *)parg)->base); + break; + + case DMX_ADD_PID: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + ret = -ERESTARTSYS; + break; + } + ret = dvb_dmxdev_add_pid(dmxdev, dmxdevfilter, *(u16 *)parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + case DMX_REMOVE_PID: + if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { + ret = -ERESTARTSYS; + break; + } + ret = dvb_dmxdev_remove_pid(dmxdev, dmxdevfilter, *(u16 *)parg); + mutex_unlock(&dmxdevfilter->mutex); + break; + + default: + ret = -EINVAL; + break; + } + mutex_unlock(&dmxdev->mutex); + return ret; +} + +static long dvb_demux_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return dvb_usercopy(file, cmd, arg, dvb_demux_do_ioctl); +} + +static unsigned int dvb_demux_poll(struct file *file, poll_table *wait) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + unsigned int mask = 0; + + if (!dmxdevfilter) + return -EINVAL; + + poll_wait(file, &dmxdevfilter->buffer.queue, wait); + + if (dmxdevfilter->state != DMXDEV_STATE_GO && + dmxdevfilter->state != DMXDEV_STATE_DONE && + dmxdevfilter->state != DMXDEV_STATE_TIMEDOUT) + return 0; + + if (dmxdevfilter->buffer.error) + mask |= (POLLIN | POLLRDNORM | POLLPRI | POLLERR); + + if (!dvb_ringbuffer_empty(&dmxdevfilter->buffer)) + mask |= (POLLIN | POLLRDNORM | POLLPRI); + + return mask; +} + +static int dvb_demux_release(struct inode *inode, struct file *file) +{ + struct dmxdev_filter *dmxdevfilter = file->private_data; + struct dmxdev *dmxdev = dmxdevfilter->dev; + + int ret; + + ret = dvb_dmxdev_filter_free(dmxdev, dmxdevfilter); + + mutex_lock(&dmxdev->mutex); + dmxdev->dvbdev->users--; + if(dmxdev->dvbdev->users==1 && dmxdev->exit==1) { + fops_put(file->f_op); + file->f_op = NULL; + mutex_unlock(&dmxdev->mutex); + wake_up(&dmxdev->dvbdev->wait_queue); + } else + mutex_unlock(&dmxdev->mutex); + + return ret; +} + +static const struct file_operations dvb_demux_fops = { + .owner = THIS_MODULE, + .read = dvb_demux_read, + .unlocked_ioctl = dvb_demux_ioctl, + .open = dvb_demux_open, + .release = dvb_demux_release, + .poll = dvb_demux_poll, + .llseek = default_llseek, +}; + +static struct dvb_device dvbdev_demux = { + .priv = NULL, + .users = 1, + .writers = 1, + .fops = &dvb_demux_fops +}; + +static int dvb_dvr_do_ioctl(struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + unsigned long arg = (unsigned long)parg; + int ret; + + if (mutex_lock_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + switch (cmd) { + case DMX_SET_BUFFER_SIZE: + ret = dvb_dvr_set_buffer_size(dmxdev, arg); + break; + + default: + ret = -EINVAL; + break; + } + mutex_unlock(&dmxdev->mutex); + return ret; +} + +static long dvb_dvr_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return dvb_usercopy(file, cmd, arg, dvb_dvr_do_ioctl); +} + +static unsigned int dvb_dvr_poll(struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = file->private_data; + struct dmxdev *dmxdev = dvbdev->priv; + unsigned int mask = 0; + + dprintk("function : %s\n", __func__); + + poll_wait(file, &dmxdev->dvr_buffer.queue, wait); + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + if (dmxdev->dvr_buffer.error) + mask |= (POLLIN | POLLRDNORM | POLLPRI | POLLERR); + + if (!dvb_ringbuffer_empty(&dmxdev->dvr_buffer)) + mask |= (POLLIN | POLLRDNORM | POLLPRI); + } else + mask |= (POLLOUT | POLLWRNORM | POLLPRI); + + return mask; +} + +static const struct file_operations dvb_dvr_fops = { + .owner = THIS_MODULE, + .read = dvb_dvr_read, + .write = dvb_dvr_write, + .unlocked_ioctl = dvb_dvr_ioctl, + .open = dvb_dvr_open, + .release = dvb_dvr_release, + .poll = dvb_dvr_poll, + .llseek = default_llseek, +}; + +static struct dvb_device dvbdev_dvr = { + .priv = NULL, + .readers = 1, + .users = 1, + .fops = &dvb_dvr_fops +}; + +int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) +{ + int i; + + if (dmxdev->demux->open(dmxdev->demux) < 0) + return -EUSERS; + + dmxdev->filter = vmalloc(dmxdev->filternum * sizeof(struct dmxdev_filter)); + if (!dmxdev->filter) + return -ENOMEM; + + mutex_init(&dmxdev->mutex); + spin_lock_init(&dmxdev->lock); + for (i = 0; i < dmxdev->filternum; i++) { + dmxdev->filter[i].dev = dmxdev; + dmxdev->filter[i].buffer.data = NULL; + dvb_dmxdev_filter_state_set(&dmxdev->filter[i], + DMXDEV_STATE_FREE); + } + + dvb_register_device(dvb_adapter, &dmxdev->dvbdev, &dvbdev_demux, dmxdev, + DVB_DEVICE_DEMUX); + dvb_register_device(dvb_adapter, &dmxdev->dvr_dvbdev, &dvbdev_dvr, + dmxdev, DVB_DEVICE_DVR); + + dvb_ringbuffer_init(&dmxdev->dvr_buffer, NULL, 8192); + + return 0; +} + +EXPORT_SYMBOL(dvb_dmxdev_init); + +void dvb_dmxdev_release(struct dmxdev *dmxdev) +{ + dmxdev->exit=1; + if (dmxdev->dvbdev->users > 1) { + wait_event(dmxdev->dvbdev->wait_queue, + dmxdev->dvbdev->users==1); + } + if (dmxdev->dvr_dvbdev->users > 1) { + wait_event(dmxdev->dvr_dvbdev->wait_queue, + dmxdev->dvr_dvbdev->users==1); + } + + dvb_unregister_device(dmxdev->dvbdev); + dvb_unregister_device(dmxdev->dvr_dvbdev); + + vfree(dmxdev->filter); + dmxdev->filter = NULL; + dmxdev->demux->close(dmxdev->demux); +} + +EXPORT_SYMBOL(dvb_dmxdev_release); diff --git a/drivers/media/dvb/dvb-core/dmxdev.h b/drivers/media/dvb-core/dmxdev.h similarity index 100% rename from drivers/media/dvb/dvb-core/dmxdev.h rename to drivers/media/dvb-core/dmxdev.h diff --git a/drivers/media/dvb-core/dvb-usb-ids.h b/drivers/media/dvb-core/dvb-usb-ids.h new file mode 100644 index 000000000000..58e0220447c0 --- /dev/null +++ b/drivers/media/dvb-core/dvb-usb-ids.h @@ -0,0 +1,364 @@ +/* dvb-usb-ids.h is part of the DVB USB library. + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) see + * dvb-usb-init.c for copyright information. + * + * a header file containing define's for the USB device supported by the + * various drivers. + */ +#ifndef _DVB_USB_IDS_H_ +#define _DVB_USB_IDS_H_ + +/* Vendor IDs */ +#define USB_VID_ADSTECH 0x06e1 +#define USB_VID_AFATECH 0x15a4 +#define USB_VID_ALCOR_MICRO 0x058f +#define USB_VID_ALINK 0x05e3 +#define USB_VID_AMT 0x1c73 +#define USB_VID_ANCHOR 0x0547 +#define USB_VID_ANSONIC 0x10b9 +#define USB_VID_ANUBIS_ELECTRONIC 0x10fd +#define USB_VID_ASUS 0x0b05 +#define USB_VID_AVERMEDIA 0x07ca +#define USB_VID_COMPRO 0x185b +#define USB_VID_COMPRO_UNK 0x145f +#define USB_VID_CONEXANT 0x0572 +#define USB_VID_CYPRESS 0x04b4 +#define USB_VID_DEXATEK 0x1d19 +#define USB_VID_DIBCOM 0x10b8 +#define USB_VID_DPOSH 0x1498 +#define USB_VID_DVICO 0x0fe9 +#define USB_VID_E3C 0x18b4 +#define USB_VID_ELGATO 0x0fd9 +#define USB_VID_EMPIA 0xeb1a +#define USB_VID_GENPIX 0x09c0 +#define USB_VID_GRANDTEC 0x5032 +#define USB_VID_GTEK 0x1f4d +#define USB_VID_HANFTEK 0x15f4 +#define USB_VID_HAUPPAUGE 0x2040 +#define USB_VID_HYPER_PALTEK 0x1025 +#define USB_VID_INTEL 0x8086 +#define USB_VID_ITETECH 0x048d +#define USB_VID_KWORLD 0xeb2a +#define USB_VID_KWORLD_2 0x1b80 +#define USB_VID_KYE 0x0458 +#define USB_VID_LEADTEK 0x0413 +#define USB_VID_LITEON 0x04ca +#define USB_VID_MEDION 0x1660 +#define USB_VID_MIGLIA 0x18f3 +#define USB_VID_MSI 0x0db0 +#define USB_VID_MSI_2 0x1462 +#define USB_VID_OPERA1 0x695c +#define USB_VID_PINNACLE 0x2304 +#define USB_VID_PCTV 0x2013 +#define USB_VID_PIXELVIEW 0x1554 +#define USB_VID_REALTEK 0x0bda +#define USB_VID_TECHNOTREND 0x0b48 +#define USB_VID_TERRATEC 0x0ccd +#define USB_VID_TELESTAR 0x10b9 +#define USB_VID_VISIONPLUS 0x13d3 +#define USB_VID_SONY 0x1415 +#define USB_VID_TWINHAN 0x1822 +#define USB_VID_ULTIMA_ELECTRONIC 0x05d8 +#define USB_VID_UNIWILL 0x1584 +#define USB_VID_WIDEVIEW 0x14aa +#define USB_VID_GIGABYTE 0x1044 +#define USB_VID_YUAN 0x1164 +#define USB_VID_XTENSIONS 0x1ae7 +#define USB_VID_HUMAX_COEX 0x10b9 +#define USB_VID_774 0x7a69 +#define USB_VID_EVOLUTEPC 0x1e59 +#define USB_VID_AZUREWAVE 0x13d3 +#define USB_VID_TECHNISAT 0x14f7 + +/* Product IDs */ +#define USB_PID_ADSTECH_USB2_COLD 0xa333 +#define USB_PID_ADSTECH_USB2_WARM 0xa334 +#define USB_PID_AFATECH_AF9005 0x9020 +#define USB_PID_AFATECH_AF9015_9015 0x9015 +#define USB_PID_AFATECH_AF9015_9016 0x9016 +#define USB_PID_AFATECH_AF9035_1000 0x1000 +#define USB_PID_AFATECH_AF9035_1001 0x1001 +#define USB_PID_AFATECH_AF9035_1002 0x1002 +#define USB_PID_AFATECH_AF9035_1003 0x1003 +#define USB_PID_AFATECH_AF9035_9035 0x9035 +#define USB_PID_TREKSTOR_DVBT 0x901b +#define USB_PID_TREKSTOR_TERRES_2_0 0xC803 +#define USB_VID_ALINK_DTU 0xf170 +#define USB_PID_ANSONIC_DVBT_USB 0x6000 +#define USB_PID_ANYSEE 0x861f +#define USB_PID_AZUREWAVE_AD_TU700 0x3237 +#define USB_PID_AZUREWAVE_6007 0x0ccd +#define USB_PID_AVERMEDIA_DVBT_USB_COLD 0x0001 +#define USB_PID_AVERMEDIA_DVBT_USB_WARM 0x0002 +#define USB_PID_AVERMEDIA_DVBT_USB2_COLD 0xa800 +#define USB_PID_AVERMEDIA_DVBT_USB2_WARM 0xa801 +#define USB_PID_COMPRO_DVBU2000_COLD 0xd000 +#define USB_PID_COMPRO_DVBU2000_WARM 0xd001 +#define USB_PID_COMPRO_DVBU2000_UNK_COLD 0x010c +#define USB_PID_COMPRO_DVBU2000_UNK_WARM 0x010d +#define USB_PID_COMPRO_VIDEOMATE_U500 0x1e78 +#define USB_PID_COMPRO_VIDEOMATE_U500_PC 0x1e80 +#define USB_PID_CONCEPTRONIC_CTVDIGRCU 0xe397 +#define USB_PID_CONEXANT_D680_DMB 0x86d6 +#define USB_PID_CREATIX_CTX1921 0x1921 +#define USB_PID_DELOCK_USB2_DVBT 0xb803 +#define USB_PID_DIBCOM_HOOK_DEFAULT 0x0064 +#define USB_PID_DIBCOM_HOOK_DEFAULT_REENUM 0x0065 +#define USB_PID_DIBCOM_MOD3000_COLD 0x0bb8 +#define USB_PID_DIBCOM_MOD3000_WARM 0x0bb9 +#define USB_PID_DIBCOM_MOD3001_COLD 0x0bc6 +#define USB_PID_DIBCOM_MOD3001_WARM 0x0bc7 +#define USB_PID_DIBCOM_STK7700P 0x1e14 +#define USB_PID_DIBCOM_STK7700P_PC 0x1e78 +#define USB_PID_DIBCOM_STK7700D 0x1ef0 +#define USB_PID_DIBCOM_STK7700_U7000 0x7001 +#define USB_PID_DIBCOM_STK7070P 0x1ebc +#define USB_PID_DIBCOM_STK7070PD 0x1ebe +#define USB_PID_DIBCOM_STK807XP 0x1f90 +#define USB_PID_DIBCOM_STK807XPVR 0x1f98 +#define USB_PID_DIBCOM_STK8096GP 0x1fa0 +#define USB_PID_DIBCOM_NIM8096MD 0x1fa8 +#define USB_PID_DIBCOM_TFE8096P 0x1f9C +#define USB_PID_DIBCOM_ANCHOR_2135_COLD 0x2131 +#define USB_PID_DIBCOM_STK7770P 0x1e80 +#define USB_PID_DIBCOM_NIM7090 0x1bb2 +#define USB_PID_DIBCOM_TFE7090PVR 0x1bb4 +#define USB_PID_DIBCOM_TFE7090E 0x1bb7 +#define USB_PID_DIBCOM_TFE7790E 0x1e6e +#define USB_PID_DIBCOM_NIM9090M 0x2383 +#define USB_PID_DIBCOM_NIM9090MD 0x2384 +#define USB_PID_DPOSH_M9206_COLD 0x9206 +#define USB_PID_DPOSH_M9206_WARM 0xa090 +#define USB_PID_E3C_EC168 0x1689 +#define USB_PID_E3C_EC168_2 0xfffa +#define USB_PID_E3C_EC168_3 0xfffb +#define USB_PID_E3C_EC168_4 0x1001 +#define USB_PID_E3C_EC168_5 0x1002 +#define USB_PID_FREECOM_DVBT 0x0160 +#define USB_PID_FREECOM_DVBT_2 0x0161 +#define USB_PID_UNIWILL_STK7700P 0x6003 +#define USB_PID_GENIUS_TVGO_DVB_T03 0x4012 +#define USB_PID_GRANDTEC_DVBT_USB_COLD 0x0fa0 +#define USB_PID_GRANDTEC_DVBT_USB_WARM 0x0fa1 +#define USB_PID_INTEL_CE9500 0x9500 +#define USB_PID_ITETECH_IT9135 0x9135 +#define USB_PID_ITETECH_IT9135_9005 0x9005 +#define USB_PID_ITETECH_IT9135_9006 0x9006 +#define USB_PID_KWORLD_399U 0xe399 +#define USB_PID_KWORLD_399U_2 0xe400 +#define USB_PID_KWORLD_395U 0xe396 +#define USB_PID_KWORLD_395U_2 0xe39b +#define USB_PID_KWORLD_395U_3 0xe395 +#define USB_PID_KWORLD_395U_4 0xe39a +#define USB_PID_KWORLD_MC810 0xc810 +#define USB_PID_KWORLD_PC160_2T 0xc160 +#define USB_PID_KWORLD_PC160_T 0xc161 +#define USB_PID_KWORLD_UB383_T 0xe383 +#define USB_PID_KWORLD_UB499_2T_T09 0xe409 +#define USB_PID_KWORLD_VSTREAM_COLD 0x17de +#define USB_PID_KWORLD_VSTREAM_WARM 0x17df +#define USB_PID_TERRATEC_CINERGY_T_USB_XE 0x0055 +#define USB_PID_TERRATEC_CINERGY_T_USB_XE_REV2 0x0069 +#define USB_PID_TERRATEC_CINERGY_T_STICK 0x0093 +#define USB_PID_TERRATEC_CINERGY_T_STICK_RC 0x0097 +#define USB_PID_TERRATEC_CINERGY_T_STICK_DUAL_RC 0x0099 +#define USB_PID_TERRATEC_CINERGY_T_STICK_BLACK_REV1 0x00a9 +#define USB_PID_TWINHAN_VP7041_COLD 0x3201 +#define USB_PID_TWINHAN_VP7041_WARM 0x3202 +#define USB_PID_TWINHAN_VP7020_COLD 0x3203 +#define USB_PID_TWINHAN_VP7020_WARM 0x3204 +#define USB_PID_TWINHAN_VP7045_COLD 0x3205 +#define USB_PID_TWINHAN_VP7045_WARM 0x3206 +#define USB_PID_TWINHAN_VP7021_COLD 0x3207 +#define USB_PID_TWINHAN_VP7021_WARM 0x3208 +#define USB_PID_TINYTWIN 0x3226 +#define USB_PID_TINYTWIN_2 0xe402 +#define USB_PID_TINYTWIN_3 0x9016 +#define USB_PID_DNTV_TINYUSB2_COLD 0x3223 +#define USB_PID_DNTV_TINYUSB2_WARM 0x3224 +#define USB_PID_ULTIMA_TVBOX_COLD 0x8105 +#define USB_PID_ULTIMA_TVBOX_WARM 0x8106 +#define USB_PID_ULTIMA_TVBOX_AN2235_COLD 0x8107 +#define USB_PID_ULTIMA_TVBOX_AN2235_WARM 0x8108 +#define USB_PID_ULTIMA_TVBOX_ANCHOR_COLD 0x2235 +#define USB_PID_ULTIMA_TVBOX_USB2_COLD 0x8109 +#define USB_PID_ULTIMA_TVBOX_USB2_WARM 0x810a +#define USB_PID_ARTEC_T14_COLD 0x810b +#define USB_PID_ARTEC_T14_WARM 0x810c +#define USB_PID_ARTEC_T14BR 0x810f +#define USB_PID_ULTIMA_TVBOX_USB2_FX_COLD 0x8613 +#define USB_PID_ULTIMA_TVBOX_USB2_FX_WARM 0x1002 +#define USB_PID_UNK_HYPER_PALTEK_COLD 0x005e +#define USB_PID_UNK_HYPER_PALTEK_WARM 0x005f +#define USB_PID_HANFTEK_UMT_010_COLD 0x0001 +#define USB_PID_HANFTEK_UMT_010_WARM 0x0015 +#define USB_PID_DTT200U_COLD 0x0201 +#define USB_PID_DTT200U_WARM 0x0301 +#define USB_PID_WT220U_ZAP250_COLD 0x0220 +#define USB_PID_WT220U_COLD 0x0222 +#define USB_PID_WT220U_WARM 0x0221 +#define USB_PID_WT220U_FC_COLD 0x0225 +#define USB_PID_WT220U_FC_WARM 0x0226 +#define USB_PID_WT220U_ZL0353_COLD 0x022a +#define USB_PID_WT220U_ZL0353_WARM 0x022b +#define USB_PID_WINTV_NOVA_T_USB2_COLD 0x9300 +#define USB_PID_WINTV_NOVA_T_USB2_WARM 0x9301 +#define USB_PID_HAUPPAUGE_NOVA_T_500 0x9941 +#define USB_PID_HAUPPAUGE_NOVA_T_500_2 0x9950 +#define USB_PID_HAUPPAUGE_NOVA_T_500_3 0x8400 +#define USB_PID_HAUPPAUGE_NOVA_T_STICK 0x7050 +#define USB_PID_HAUPPAUGE_NOVA_T_STICK_2 0x7060 +#define USB_PID_HAUPPAUGE_NOVA_T_STICK_3 0x7070 +#define USB_PID_HAUPPAUGE_MYTV_T 0x7080 +#define USB_PID_HAUPPAUGE_NOVA_TD_STICK 0x9580 +#define USB_PID_HAUPPAUGE_NOVA_TD_STICK_52009 0x5200 +#define USB_PID_HAUPPAUGE_TIGER_ATSC 0xb200 +#define USB_PID_HAUPPAUGE_TIGER_ATSC_B210 0xb210 +#define USB_PID_AVERMEDIA_EXPRESS 0xb568 +#define USB_PID_AVERMEDIA_VOLAR 0xa807 +#define USB_PID_AVERMEDIA_VOLAR_2 0xb808 +#define USB_PID_AVERMEDIA_VOLAR_A868R 0xa868 +#define USB_PID_AVERMEDIA_MCE_USB_M038 0x1228 +#define USB_PID_AVERMEDIA_HYBRID_ULTRA_USB_M039R 0x0039 +#define USB_PID_AVERMEDIA_HYBRID_ULTRA_USB_M039R_ATSC 0x1039 +#define USB_PID_AVERMEDIA_HYBRID_ULTRA_USB_M039R_DVBT 0x2039 +#define USB_PID_AVERMEDIA_VOLAR_X 0xa815 +#define USB_PID_AVERMEDIA_VOLAR_X_2 0x8150 +#define USB_PID_AVERMEDIA_A309 0xa309 +#define USB_PID_AVERMEDIA_A310 0xa310 +#define USB_PID_AVERMEDIA_A850 0x850a +#define USB_PID_AVERMEDIA_A850T 0x850b +#define USB_PID_AVERMEDIA_A805 0xa805 +#define USB_PID_AVERMEDIA_A815M 0x815a +#define USB_PID_AVERMEDIA_A835 0xa835 +#define USB_PID_AVERMEDIA_B835 0xb835 +#define USB_PID_AVERMEDIA_1867 0x1867 +#define USB_PID_AVERMEDIA_A867 0xa867 +#define USB_PID_AVERMEDIA_TWINSTAR 0x0825 +#define USB_PID_TECHNOTREND_CONNECT_S2400 0x3006 +#define USB_PID_TECHNOTREND_CONNECT_CT3650 0x300d +#define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY 0x005a +#define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY_2 0x0081 +#define USB_PID_TERRATEC_CINERGY_HT_USB_XE 0x0058 +#define USB_PID_TERRATEC_CINERGY_HT_EXPRESS 0x0060 +#define USB_PID_TERRATEC_CINERGY_T_EXPRESS 0x0062 +#define USB_PID_TERRATEC_CINERGY_T_XXS 0x0078 +#define USB_PID_TERRATEC_CINERGY_T_XXS_2 0x00ab +#define USB_PID_TERRATEC_H7 0x10b4 +#define USB_PID_TERRATEC_H7_2 0x10a3 +#define USB_PID_TERRATEC_T3 0x10a0 +#define USB_PID_TERRATEC_T5 0x10a1 +#define USB_PID_NOXON_DAB_STICK 0x00b3 +#define USB_PID_PINNACLE_EXPRESSCARD_320CX 0x022e +#define USB_PID_PINNACLE_PCTV2000E 0x022c +#define USB_PID_PINNACLE_PCTV_DVB_T_FLASH 0x0228 +#define USB_PID_PINNACLE_PCTV_DUAL_DIVERSITY_DVB_T 0x0229 +#define USB_PID_PINNACLE_PCTV71E 0x022b +#define USB_PID_PINNACLE_PCTV72E 0x0236 +#define USB_PID_PINNACLE_PCTV73E 0x0237 +#define USB_PID_PINNACLE_PCTV310E 0x3211 +#define USB_PID_PINNACLE_PCTV801E 0x023a +#define USB_PID_PINNACLE_PCTV801E_SE 0x023b +#define USB_PID_PINNACLE_PCTV340E 0x023d +#define USB_PID_PINNACLE_PCTV340E_SE 0x023e +#define USB_PID_PINNACLE_PCTV73A 0x0243 +#define USB_PID_PINNACLE_PCTV73ESE 0x0245 +#define USB_PID_PINNACLE_PCTV74E 0x0246 +#define USB_PID_PINNACLE_PCTV282E 0x0248 +#define USB_PID_PIXELVIEW_SBTVD 0x5010 +#define USB_PID_PCTV_200E 0x020e +#define USB_PID_PCTV_400E 0x020f +#define USB_PID_PCTV_450E 0x0222 +#define USB_PID_PCTV_452E 0x021f +#define USB_PID_REALTEK_RTL2831U 0x2831 +#define USB_PID_REALTEK_RTL2832U 0x2832 +#define USB_PID_TECHNOTREND_CONNECT_S2_3600 0x3007 +#define USB_PID_TECHNOTREND_CONNECT_S2_3650_CI 0x300a +#define USB_PID_NEBULA_DIGITV 0x0201 +#define USB_PID_DVICO_BLUEBIRD_LGDT 0xd820 +#define USB_PID_DVICO_BLUEBIRD_LG064F_COLD 0xd500 +#define USB_PID_DVICO_BLUEBIRD_LG064F_WARM 0xd501 +#define USB_PID_DVICO_BLUEBIRD_LGZ201_COLD 0xdb00 +#define USB_PID_DVICO_BLUEBIRD_LGZ201_WARM 0xdb01 +#define USB_PID_DVICO_BLUEBIRD_TH7579_COLD 0xdb10 +#define USB_PID_DVICO_BLUEBIRD_TH7579_WARM 0xdb11 +#define USB_PID_DVICO_BLUEBIRD_DUAL_1_COLD 0xdb50 +#define USB_PID_DVICO_BLUEBIRD_DUAL_1_WARM 0xdb51 +#define USB_PID_DVICO_BLUEBIRD_DUAL_2_COLD 0xdb58 +#define USB_PID_DVICO_BLUEBIRD_DUAL_2_WARM 0xdb59 +#define USB_PID_DVICO_BLUEBIRD_DUAL_4 0xdb78 +#define USB_PID_DVICO_BLUEBIRD_DUAL_4_REV_2 0xdb98 +#define USB_PID_DVICO_BLUEBIRD_DVB_T_NANO_2 0xdb70 +#define USB_PID_DVICO_BLUEBIRD_DVB_T_NANO_2_NFW_WARM 0xdb71 +#define USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_COLD 0xdb54 +#define USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_WARM 0xdb55 +#define USB_PID_MEDION_MD95700 0x0932 +#define USB_PID_MSI_MEGASKY580 0x5580 +#define USB_PID_MSI_MEGASKY580_55801 0x5581 +#define USB_PID_KYE_DVB_T_COLD 0x701e +#define USB_PID_KYE_DVB_T_WARM 0x701f +#define USB_PID_LITEON_DVB_T_COLD 0xf000 +#define USB_PID_LITEON_DVB_T_WARM 0xf001 +#define USB_PID_DIGIVOX_MINI_SL_COLD 0xe360 +#define USB_PID_DIGIVOX_MINI_SL_WARM 0xe361 +#define USB_PID_GRANDTEC_DVBT_USB2_COLD 0x0bc6 +#define USB_PID_GRANDTEC_DVBT_USB2_WARM 0x0bc7 +#define USB_PID_WINFAST_DTV2000DS 0x6a04 +#define USB_PID_WINFAST_DTV_DONGLE_COLD 0x6025 +#define USB_PID_WINFAST_DTV_DONGLE_WARM 0x6026 +#define USB_PID_WINFAST_DTV_DONGLE_STK7700P 0x6f00 +#define USB_PID_WINFAST_DTV_DONGLE_H 0x60f6 +#define USB_PID_WINFAST_DTV_DONGLE_STK7700P_2 0x6f01 +#define USB_PID_WINFAST_DTV_DONGLE_GOLD 0x6029 +#define USB_PID_GENPIX_8PSK_REV_1_COLD 0x0200 +#define USB_PID_GENPIX_8PSK_REV_1_WARM 0x0201 +#define USB_PID_GENPIX_8PSK_REV_2 0x0202 +#define USB_PID_GENPIX_SKYWALKER_1 0x0203 +#define USB_PID_GENPIX_SKYWALKER_CW3K 0x0204 +#define USB_PID_GENPIX_SKYWALKER_2 0x0206 +#define USB_PID_SIGMATEK_DVB_110 0x6610 +#define USB_PID_MSI_DIGI_VOX_MINI_II 0x1513 +#define USB_PID_MSI_DIGIVOX_DUO 0x8801 +#define USB_PID_OPERA1_COLD 0x2830 +#define USB_PID_OPERA1_WARM 0x3829 +#define USB_PID_LIFEVIEW_TV_WALKER_TWIN_COLD 0x0514 +#define USB_PID_LIFEVIEW_TV_WALKER_TWIN_WARM 0x0513 +#define USB_PID_GIGABYTE_U7000 0x7001 +#define USB_PID_GIGABYTE_U8000 0x7002 +#define USB_PID_ASUS_U3000 0x171f +#define USB_PID_ASUS_U3000H 0x1736 +#define USB_PID_ASUS_U3100 0x173f +#define USB_PID_ASUS_U3100MINI_PLUS 0x1779 +#define USB_PID_YUAN_EC372S 0x1edc +#define USB_PID_YUAN_STK7700PH 0x1f08 +#define USB_PID_YUAN_PD378S 0x2edc +#define USB_PID_YUAN_MC770 0x0871 +#define USB_PID_YUAN_STK7700D 0x1efc +#define USB_PID_YUAN_STK7700D_2 0x1e8c +#define USB_PID_DW2102 0x2102 +#define USB_PID_XTENSIONS_XD_380 0x0381 +#define USB_PID_TELESTAR_STARSTICK_2 0x8000 +#define USB_PID_MSI_DIGI_VOX_MINI_III 0x8807 +#define USB_PID_SONY_PLAYTV 0x0003 +#define USB_PID_MYGICA_D689 0xd811 +#define USB_PID_ELGATO_EYETV_DIVERSITY 0x0011 +#define USB_PID_ELGATO_EYETV_DTT 0x0021 +#define USB_PID_ELGATO_EYETV_DTT_2 0x003f +#define USB_PID_ELGATO_EYETV_DTT_Dlx 0x0020 +#define USB_PID_ELGATO_EYETV_SAT 0x002a +#define USB_PID_DVB_T_USB_STICK_HIGH_SPEED_COLD 0x5000 +#define USB_PID_DVB_T_USB_STICK_HIGH_SPEED_WARM 0x5001 +#define USB_PID_FRIIO_WHITE 0x0001 +#define USB_PID_TVWAY_PLUS 0x0002 +#define USB_PID_SVEON_STV20 0xe39d +#define USB_PID_SVEON_STV22 0xe401 +#define USB_PID_SVEON_STV22_IT9137 0xe411 +#define USB_PID_AZUREWAVE_AZ6027 0x3275 +#define USB_PID_TERRATEC_DVBS2CI_V1 0x10a4 +#define USB_PID_TERRATEC_DVBS2CI_V2 0x10ac +#define USB_PID_TECHNISAT_USB2_HDCI_V1 0x0001 +#define USB_PID_TECHNISAT_USB2_HDCI_V2 0x0002 +#define USB_PID_TECHNISAT_AIRSTAR_TELESTICK_2 0x0004 +#define USB_PID_TECHNISAT_USB2_DVB_S2 0x0500 +#endif diff --git a/drivers/media/dvb/dvb-core/dvb_ca_en50221.c b/drivers/media/dvb-core/dvb_ca_en50221.c similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_ca_en50221.c rename to drivers/media/dvb-core/dvb_ca_en50221.c diff --git a/drivers/media/dvb/dvb-core/dvb_ca_en50221.h b/drivers/media/dvb-core/dvb_ca_en50221.h similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_ca_en50221.h rename to drivers/media/dvb-core/dvb_ca_en50221.h diff --git a/drivers/media/dvb-core/dvb_demux.c b/drivers/media/dvb-core/dvb_demux.c new file mode 100644 index 000000000000..d319717eb535 --- /dev/null +++ b/drivers/media/dvb-core/dvb_demux.c @@ -0,0 +1,1318 @@ +/* + * dvb_demux.c - DVB kernel demux API + * + * Copyright (C) 2000-2001 Ralph Metzler + * & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dvb_demux.h" + +#define NOBUFS +/* +** #define DVB_DEMUX_SECTION_LOSS_LOG to monitor payload loss in the syslog +*/ +// #define DVB_DEMUX_SECTION_LOSS_LOG + +static int dvb_demux_tscheck; +module_param(dvb_demux_tscheck, int, 0644); +MODULE_PARM_DESC(dvb_demux_tscheck, + "enable transport stream continuity and TEI check"); + +static int dvb_demux_speedcheck; +module_param(dvb_demux_speedcheck, int, 0644); +MODULE_PARM_DESC(dvb_demux_speedcheck, + "enable transport stream speed check"); + +static int dvb_demux_feed_err_pkts = 1; +module_param(dvb_demux_feed_err_pkts, int, 0644); +MODULE_PARM_DESC(dvb_demux_feed_err_pkts, + "when set to 0, drop packets with the TEI bit set (1 by default)"); + +#define dprintk_tscheck(x...) do { \ + if (dvb_demux_tscheck && printk_ratelimit()) \ + printk(x); \ + } while (0) + +/****************************************************************************** + * static inlined helper functions + ******************************************************************************/ + +static inline u16 section_length(const u8 *buf) +{ + return 3 + ((buf[1] & 0x0f) << 8) + buf[2]; +} + +static inline u16 ts_pid(const u8 *buf) +{ + return ((buf[1] & 0x1f) << 8) + buf[2]; +} + +static inline u8 payload(const u8 *tsp) +{ + if (!(tsp[3] & 0x10)) // no payload? + return 0; + + if (tsp[3] & 0x20) { // adaptation field? + if (tsp[4] > 183) // corrupted data? + return 0; + else + return 184 - 1 - tsp[4]; + } + + return 184; +} + +static u32 dvb_dmx_crc32(struct dvb_demux_feed *f, const u8 *src, size_t len) +{ + return (f->feed.sec.crc_val = crc32_be(f->feed.sec.crc_val, src, len)); +} + +static void dvb_dmx_memcopy(struct dvb_demux_feed *f, u8 *d, const u8 *s, + size_t len) +{ + memcpy(d, s, len); +} + +/****************************************************************************** + * Software filter functions + ******************************************************************************/ + +static inline int dvb_dmx_swfilter_payload(struct dvb_demux_feed *feed, + const u8 *buf) +{ + int count = payload(buf); + int p; + //int ccok; + //u8 cc; + + if (count == 0) + return -1; + + p = 188 - count; + + /* + cc = buf[3] & 0x0f; + ccok = ((feed->cc + 1) & 0x0f) == cc; + feed->cc = cc; + if (!ccok) + printk("missed packet!\n"); + */ + + if (buf[1] & 0x40) // PUSI ? + feed->peslen = 0xfffa; + + feed->peslen += count; + + return feed->cb.ts(&buf[p], count, NULL, 0, &feed->feed.ts, DMX_OK); +} + +static int dvb_dmx_swfilter_sectionfilter(struct dvb_demux_feed *feed, + struct dvb_demux_filter *f) +{ + u8 neq = 0; + int i; + + for (i = 0; i < DVB_DEMUX_MASK_MAX; i++) { + u8 xor = f->filter.filter_value[i] ^ feed->feed.sec.secbuf[i]; + + if (f->maskandmode[i] & xor) + return 0; + + neq |= f->maskandnotmode[i] & xor; + } + + if (f->doneq && !neq) + return 0; + + return feed->cb.sec(feed->feed.sec.secbuf, feed->feed.sec.seclen, + NULL, 0, &f->filter, DMX_OK); +} + +static inline int dvb_dmx_swfilter_section_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct dvb_demux_filter *f = feed->filter; + struct dmx_section_feed *sec = &feed->feed.sec; + int section_syntax_indicator; + + if (!sec->is_filtering) + return 0; + + if (!f) + return 0; + + if (sec->check_crc) { + section_syntax_indicator = ((sec->secbuf[1] & 0x80) != 0); + if (section_syntax_indicator && + demux->check_crc32(feed, sec->secbuf, sec->seclen)) + return -1; + } + + do { + if (dvb_dmx_swfilter_sectionfilter(feed, f) < 0) + return -1; + } while ((f = f->next) && sec->is_filtering); + + sec->seclen = 0; + + return 0; +} + +static void dvb_dmx_swfilter_section_new(struct dvb_demux_feed *feed) +{ + struct dmx_section_feed *sec = &feed->feed.sec; + +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + if (sec->secbufp < sec->tsfeedp) { + int i, n = sec->tsfeedp - sec->secbufp; + + /* + * Section padding is done with 0xff bytes entirely. + * Due to speed reasons, we won't check all of them + * but just first and last. + */ + if (sec->secbuf[0] != 0xff || sec->secbuf[n - 1] != 0xff) { + printk("dvb_demux.c section ts padding loss: %d/%d\n", + n, sec->tsfeedp); + printk("dvb_demux.c pad data:"); + for (i = 0; i < n; i++) + printk(" %02x", sec->secbuf[i]); + printk("\n"); + } + } +#endif + + sec->tsfeedp = sec->secbufp = sec->seclen = 0; + sec->secbuf = sec->secbuf_base; +} + +/* + * Losless Section Demux 1.4.1 by Emard + * Valsecchi Patrick: + * - middle of section A (no PUSI) + * - end of section A and start of section B + * (with PUSI pointing to the start of the second section) + * + * In this case, without feed->pusi_seen you'll receive a garbage section + * consisting of the end of section A. Basically because tsfeedp + * is incemented and the use=0 condition is not raised + * when the second packet arrives. + * + * Fix: + * when demux is started, let feed->pusi_seen = 0 to + * prevent initial feeding of garbage from the end of + * previous section. When you for the first time see PUSI=1 + * then set feed->pusi_seen = 1 + */ +static int dvb_dmx_swfilter_section_copy_dump(struct dvb_demux_feed *feed, + const u8 *buf, u8 len) +{ + struct dvb_demux *demux = feed->demux; + struct dmx_section_feed *sec = &feed->feed.sec; + u16 limit, seclen, n; + + if (sec->tsfeedp >= DMX_MAX_SECFEED_SIZE) + return 0; + + if (sec->tsfeedp + len > DMX_MAX_SECFEED_SIZE) { +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + printk("dvb_demux.c section buffer full loss: %d/%d\n", + sec->tsfeedp + len - DMX_MAX_SECFEED_SIZE, + DMX_MAX_SECFEED_SIZE); +#endif + len = DMX_MAX_SECFEED_SIZE - sec->tsfeedp; + } + + if (len <= 0) + return 0; + + demux->memcopy(feed, sec->secbuf_base + sec->tsfeedp, buf, len); + sec->tsfeedp += len; + + /* + * Dump all the sections we can find in the data (Emard) + */ + limit = sec->tsfeedp; + if (limit > DMX_MAX_SECFEED_SIZE) + return -1; /* internal error should never happen */ + + /* to be sure always set secbuf */ + sec->secbuf = sec->secbuf_base + sec->secbufp; + + for (n = 0; sec->secbufp + 2 < limit; n++) { + seclen = section_length(sec->secbuf); + if (seclen <= 0 || seclen > DMX_MAX_SECTION_SIZE + || seclen + sec->secbufp > limit) + return 0; + sec->seclen = seclen; + sec->crc_val = ~0; + /* dump [secbuf .. secbuf+seclen) */ + if (feed->pusi_seen) + dvb_dmx_swfilter_section_feed(feed); +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + else + printk("dvb_demux.c pusi not seen, discarding section data\n"); +#endif + sec->secbufp += seclen; /* secbufp and secbuf moving together is */ + sec->secbuf += seclen; /* redundant but saves pointer arithmetic */ + } + + return 0; +} + +static int dvb_dmx_swfilter_section_packet(struct dvb_demux_feed *feed, + const u8 *buf) +{ + u8 p, count; + int ccok, dc_i = 0; + u8 cc; + + count = payload(buf); + + if (count == 0) /* count == 0 if no payload or out of range */ + return -1; + + p = 188 - count; /* payload start */ + + cc = buf[3] & 0x0f; + ccok = ((feed->cc + 1) & 0x0f) == cc; + feed->cc = cc; + + if (buf[3] & 0x20) { + /* adaption field present, check for discontinuity_indicator */ + if ((buf[4] > 0) && (buf[5] & 0x80)) + dc_i = 1; + } + + if (!ccok || dc_i) { +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + printk("dvb_demux.c discontinuity detected %d bytes lost\n", + count); + /* + * those bytes under sume circumstances will again be reported + * in the following dvb_dmx_swfilter_section_new + */ +#endif + /* + * Discontinuity detected. Reset pusi_seen = 0 to + * stop feeding of suspicious data until next PUSI=1 arrives + */ + feed->pusi_seen = 0; + dvb_dmx_swfilter_section_new(feed); + } + + if (buf[1] & 0x40) { + /* PUSI=1 (is set), section boundary is here */ + if (count > 1 && buf[p] < count) { + const u8 *before = &buf[p + 1]; + u8 before_len = buf[p]; + const u8 *after = &before[before_len]; + u8 after_len = count - 1 - before_len; + + dvb_dmx_swfilter_section_copy_dump(feed, before, + before_len); + /* before start of new section, set pusi_seen = 1 */ + feed->pusi_seen = 1; + dvb_dmx_swfilter_section_new(feed); + dvb_dmx_swfilter_section_copy_dump(feed, after, + after_len); + } +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + else if (count > 0) + printk("dvb_demux.c PUSI=1 but %d bytes lost\n", count); +#endif + } else { + /* PUSI=0 (is not set), no section boundary */ + dvb_dmx_swfilter_section_copy_dump(feed, &buf[p], count); + } + + return 0; +} + +static inline void dvb_dmx_swfilter_packet_type(struct dvb_demux_feed *feed, + const u8 *buf) +{ + switch (feed->type) { + case DMX_TYPE_TS: + if (!feed->feed.ts.is_filtering) + break; + if (feed->ts_type & TS_PACKET) { + if (feed->ts_type & TS_PAYLOAD_ONLY) + dvb_dmx_swfilter_payload(feed, buf); + else + feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, + DMX_OK); + } + if (feed->ts_type & TS_DECODER) + if (feed->demux->write_to_decoder) + feed->demux->write_to_decoder(feed, buf, 188); + break; + + case DMX_TYPE_SEC: + if (!feed->feed.sec.is_filtering) + break; + if (dvb_dmx_swfilter_section_packet(feed, buf) < 0) + feed->feed.sec.seclen = feed->feed.sec.secbufp = 0; + break; + + default: + break; + } +} + +#define DVR_FEED(f) \ + (((f)->type == DMX_TYPE_TS) && \ + ((f)->feed.ts.is_filtering) && \ + (((f)->ts_type & (TS_PACKET | TS_DEMUX)) == TS_PACKET)) + +static void dvb_dmx_swfilter_packet(struct dvb_demux *demux, const u8 *buf) +{ + struct dvb_demux_feed *feed; + u16 pid = ts_pid(buf); + int dvr_done = 0; + + if (dvb_demux_speedcheck) { + struct timespec cur_time, delta_time; + u64 speed_bytes, speed_timedelta; + + demux->speed_pkts_cnt++; + + /* show speed every SPEED_PKTS_INTERVAL packets */ + if (!(demux->speed_pkts_cnt % SPEED_PKTS_INTERVAL)) { + cur_time = current_kernel_time(); + + if (demux->speed_last_time.tv_sec != 0 && + demux->speed_last_time.tv_nsec != 0) { + delta_time = timespec_sub(cur_time, + demux->speed_last_time); + speed_bytes = (u64)demux->speed_pkts_cnt + * 188 * 8; + /* convert to 1024 basis */ + speed_bytes = 1000 * div64_u64(speed_bytes, + 1024); + speed_timedelta = + (u64)timespec_to_ns(&delta_time); + speed_timedelta = div64_u64(speed_timedelta, + 1000000); /* nsec -> usec */ + printk(KERN_INFO "TS speed %llu Kbits/sec \n", + div64_u64(speed_bytes, + speed_timedelta)); + } + + demux->speed_last_time = cur_time; + demux->speed_pkts_cnt = 0; + } + } + + if (buf[1] & 0x80) { + dprintk_tscheck("TEI detected. " + "PID=0x%x data1=0x%x\n", + pid, buf[1]); + /* data in this packet cant be trusted - drop it unless + * module option dvb_demux_feed_err_pkts is set */ + if (!dvb_demux_feed_err_pkts) + return; + } else /* if TEI bit is set, pid may be wrong- skip pkt counter */ + if (demux->cnt_storage && dvb_demux_tscheck) { + /* check pkt counter */ + if (pid < MAX_PID) { + if ((buf[3] & 0xf) != demux->cnt_storage[pid]) + dprintk_tscheck("TS packet counter mismatch. " + "PID=0x%x expected 0x%x " + "got 0x%x\n", + pid, demux->cnt_storage[pid], + buf[3] & 0xf); + + demux->cnt_storage[pid] = ((buf[3] & 0xf) + 1)&0xf; + } + /* end check */ + } + + list_for_each_entry(feed, &demux->feed_list, list_head) { + if ((feed->pid != pid) && (feed->pid != 0x2000)) + continue; + + /* copy each packet only once to the dvr device, even + * if a PID is in multiple filters (e.g. video + PCR) */ + if ((DVR_FEED(feed)) && (dvr_done++)) + continue; + + if (feed->pid == pid) + dvb_dmx_swfilter_packet_type(feed, buf); + else if (feed->pid == 0x2000) + feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, DMX_OK); + } +} + +void dvb_dmx_swfilter_packets(struct dvb_demux *demux, const u8 *buf, + size_t count) +{ + spin_lock(&demux->lock); + + while (count--) { + if (buf[0] == 0x47) + dvb_dmx_swfilter_packet(demux, buf); + buf += 188; + } + + spin_unlock(&demux->lock); +} + +EXPORT_SYMBOL(dvb_dmx_swfilter_packets); + +static inline int find_next_packet(const u8 *buf, int pos, size_t count, + const int pktsize) +{ + int start = pos, lost; + + while (pos < count) { + if (buf[pos] == 0x47 || + (pktsize == 204 && buf[pos] == 0xB8)) + break; + pos++; + } + + lost = pos - start; + if (lost) { + /* This garbage is part of a valid packet? */ + int backtrack = pos - pktsize; + if (backtrack >= 0 && (buf[backtrack] == 0x47 || + (pktsize == 204 && buf[backtrack] == 0xB8))) + return backtrack; + } + + return pos; +} + +/* Filter all pktsize= 188 or 204 sized packets and skip garbage. */ +static inline void _dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, + size_t count, const int pktsize) +{ + int p = 0, i, j; + const u8 *q; + + spin_lock(&demux->lock); + + if (demux->tsbufp) { /* tsbuf[0] is now 0x47. */ + i = demux->tsbufp; + j = pktsize - i; + if (count < j) { + memcpy(&demux->tsbuf[i], buf, count); + demux->tsbufp += count; + goto bailout; + } + memcpy(&demux->tsbuf[i], buf, j); + if (demux->tsbuf[0] == 0x47) /* double check */ + dvb_dmx_swfilter_packet(demux, demux->tsbuf); + demux->tsbufp = 0; + p += j; + } + + while (1) { + p = find_next_packet(buf, p, count, pktsize); + if (p >= count) + break; + if (count - p < pktsize) + break; + + q = &buf[p]; + + if (pktsize == 204 && (*q == 0xB8)) { + memcpy(demux->tsbuf, q, 188); + demux->tsbuf[0] = 0x47; + q = demux->tsbuf; + } + dvb_dmx_swfilter_packet(demux, q); + p += pktsize; + } + + i = count - p; + if (i) { + memcpy(demux->tsbuf, &buf[p], i); + demux->tsbufp = i; + if (pktsize == 204 && demux->tsbuf[0] == 0xB8) + demux->tsbuf[0] = 0x47; + } + +bailout: + spin_unlock(&demux->lock); +} + +void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count) +{ + _dvb_dmx_swfilter(demux, buf, count, 188); +} +EXPORT_SYMBOL(dvb_dmx_swfilter); + +void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, size_t count) +{ + _dvb_dmx_swfilter(demux, buf, count, 204); +} +EXPORT_SYMBOL(dvb_dmx_swfilter_204); + +void dvb_dmx_swfilter_raw(struct dvb_demux *demux, const u8 *buf, size_t count) +{ + spin_lock(&demux->lock); + + demux->feed->cb.ts(buf, count, NULL, 0, &demux->feed->feed.ts, DMX_OK); + + spin_unlock(&demux->lock); +} +EXPORT_SYMBOL(dvb_dmx_swfilter_raw); + +static struct dvb_demux_filter *dvb_dmx_filter_alloc(struct dvb_demux *demux) +{ + int i; + + for (i = 0; i < demux->filternum; i++) + if (demux->filter[i].state == DMX_STATE_FREE) + break; + + if (i == demux->filternum) + return NULL; + + demux->filter[i].state = DMX_STATE_ALLOCATED; + + return &demux->filter[i]; +} + +static struct dvb_demux_feed *dvb_dmx_feed_alloc(struct dvb_demux *demux) +{ + int i; + + for (i = 0; i < demux->feednum; i++) + if (demux->feed[i].state == DMX_STATE_FREE) + break; + + if (i == demux->feednum) + return NULL; + + demux->feed[i].state = DMX_STATE_ALLOCATED; + + return &demux->feed[i]; +} + +static int dvb_demux_feed_find(struct dvb_demux_feed *feed) +{ + struct dvb_demux_feed *entry; + + list_for_each_entry(entry, &feed->demux->feed_list, list_head) + if (entry == feed) + return 1; + + return 0; +} + +static void dvb_demux_feed_add(struct dvb_demux_feed *feed) +{ + spin_lock_irq(&feed->demux->lock); + if (dvb_demux_feed_find(feed)) { + printk(KERN_ERR "%s: feed already in list (type=%x state=%x pid=%x)\n", + __func__, feed->type, feed->state, feed->pid); + goto out; + } + + list_add(&feed->list_head, &feed->demux->feed_list); +out: + spin_unlock_irq(&feed->demux->lock); +} + +static void dvb_demux_feed_del(struct dvb_demux_feed *feed) +{ + spin_lock_irq(&feed->demux->lock); + if (!(dvb_demux_feed_find(feed))) { + printk(KERN_ERR "%s: feed not in list (type=%x state=%x pid=%x)\n", + __func__, feed->type, feed->state, feed->pid); + goto out; + } + + list_del(&feed->list_head); +out: + spin_unlock_irq(&feed->demux->lock); +} + +static int dmx_ts_feed_set(struct dmx_ts_feed *ts_feed, u16 pid, int ts_type, + enum dmx_ts_pes pes_type, + size_t circular_buffer_size, struct timespec timeout) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; + struct dvb_demux *demux = feed->demux; + + if (pid > DMX_MAX_PID) + return -EINVAL; + + if (mutex_lock_interruptible(&demux->mutex)) + return -ERESTARTSYS; + + if (ts_type & TS_DECODER) { + if (pes_type >= DMX_TS_PES_OTHER) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } + + if (demux->pesfilter[pes_type] && + demux->pesfilter[pes_type] != feed) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } + + demux->pesfilter[pes_type] = feed; + demux->pids[pes_type] = pid; + } + + dvb_demux_feed_add(feed); + + feed->pid = pid; + feed->buffer_size = circular_buffer_size; + feed->timeout = timeout; + feed->ts_type = ts_type; + feed->pes_type = pes_type; + + if (feed->buffer_size) { +#ifdef NOBUFS + feed->buffer = NULL; +#else + feed->buffer = vmalloc(feed->buffer_size); + if (!feed->buffer) { + mutex_unlock(&demux->mutex); + return -ENOMEM; + } +#endif + } + + feed->state = DMX_STATE_READY; + mutex_unlock(&demux->mutex); + + return 0; +} + +static int dmx_ts_feed_start_filtering(struct dmx_ts_feed *ts_feed) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; + struct dvb_demux *demux = feed->demux; + int ret; + + if (mutex_lock_interruptible(&demux->mutex)) + return -ERESTARTSYS; + + if (feed->state != DMX_STATE_READY || feed->type != DMX_TYPE_TS) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } + + if (!demux->start_feed) { + mutex_unlock(&demux->mutex); + return -ENODEV; + } + + if ((ret = demux->start_feed(feed)) < 0) { + mutex_unlock(&demux->mutex); + return ret; + } + + spin_lock_irq(&demux->lock); + ts_feed->is_filtering = 1; + feed->state = DMX_STATE_GO; + spin_unlock_irq(&demux->lock); + mutex_unlock(&demux->mutex); + + return 0; +} + +static int dmx_ts_feed_stop_filtering(struct dmx_ts_feed *ts_feed) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; + struct dvb_demux *demux = feed->demux; + int ret; + + mutex_lock(&demux->mutex); + + if (feed->state < DMX_STATE_GO) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } + + if (!demux->stop_feed) { + mutex_unlock(&demux->mutex); + return -ENODEV; + } + + ret = demux->stop_feed(feed); + + spin_lock_irq(&demux->lock); + ts_feed->is_filtering = 0; + feed->state = DMX_STATE_ALLOCATED; + spin_unlock_irq(&demux->lock); + mutex_unlock(&demux->mutex); + + return ret; +} + +static int dvbdmx_allocate_ts_feed(struct dmx_demux *dmx, + struct dmx_ts_feed **ts_feed, + dmx_ts_cb callback) +{ + struct dvb_demux *demux = (struct dvb_demux *)dmx; + struct dvb_demux_feed *feed; + + if (mutex_lock_interruptible(&demux->mutex)) + return -ERESTARTSYS; + + if (!(feed = dvb_dmx_feed_alloc(demux))) { + mutex_unlock(&demux->mutex); + return -EBUSY; + } + + feed->type = DMX_TYPE_TS; + feed->cb.ts = callback; + feed->demux = demux; + feed->pid = 0xffff; + feed->peslen = 0xfffa; + feed->buffer = NULL; + + (*ts_feed) = &feed->feed.ts; + (*ts_feed)->parent = dmx; + (*ts_feed)->priv = NULL; + (*ts_feed)->is_filtering = 0; + (*ts_feed)->start_filtering = dmx_ts_feed_start_filtering; + (*ts_feed)->stop_filtering = dmx_ts_feed_stop_filtering; + (*ts_feed)->set = dmx_ts_feed_set; + + if (!(feed->filter = dvb_dmx_filter_alloc(demux))) { + feed->state = DMX_STATE_FREE; + mutex_unlock(&demux->mutex); + return -EBUSY; + } + + feed->filter->type = DMX_TYPE_TS; + feed->filter->feed = feed; + feed->filter->state = DMX_STATE_READY; + + mutex_unlock(&demux->mutex); + + return 0; +} + +static int dvbdmx_release_ts_feed(struct dmx_demux *dmx, + struct dmx_ts_feed *ts_feed) +{ + struct dvb_demux *demux = (struct dvb_demux *)dmx; + struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; + + mutex_lock(&demux->mutex); + + if (feed->state == DMX_STATE_FREE) { + mutex_unlock(&demux->mutex); + return -EINVAL; + } +#ifndef NOBUFS + vfree(feed->buffer); + feed->buffer = NULL; +#endif + + feed->state = DMX_STATE_FREE; + feed->filter->state = DMX_STATE_FREE; + + dvb_demux_feed_del(feed); + + feed->pid = 0xffff; + + if (feed->ts_type & TS_DECODER && feed->pes_type < DMX_TS_PES_OTHER) + demux->pesfilter[feed->pes_type] = NULL; + + mutex_unlock(&demux->mutex); + return 0; +} + +/****************************************************************************** + * dmx_section_feed API calls + ******************************************************************************/ + +static int dmx_section_feed_allocate_filter(struct dmx_section_feed *feed, + struct dmx_section_filter **filter) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdemux = dvbdmxfeed->demux; + struct dvb_demux_filter *dvbdmxfilter; + + if (mutex_lock_interruptible(&dvbdemux->mutex)) + return -ERESTARTSYS; + + dvbdmxfilter = dvb_dmx_filter_alloc(dvbdemux); + if (!dvbdmxfilter) { + mutex_unlock(&dvbdemux->mutex); + return -EBUSY; + } + + spin_lock_irq(&dvbdemux->lock); + *filter = &dvbdmxfilter->filter; + (*filter)->parent = feed; + (*filter)->priv = NULL; + dvbdmxfilter->feed = dvbdmxfeed; + dvbdmxfilter->type = DMX_TYPE_SEC; + dvbdmxfilter->state = DMX_STATE_READY; + dvbdmxfilter->next = dvbdmxfeed->filter; + dvbdmxfeed->filter = dvbdmxfilter; + spin_unlock_irq(&dvbdemux->lock); + + mutex_unlock(&dvbdemux->mutex); + return 0; +} + +static int dmx_section_feed_set(struct dmx_section_feed *feed, + u16 pid, size_t circular_buffer_size, + int check_crc) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + + if (pid > 0x1fff) + return -EINVAL; + + if (mutex_lock_interruptible(&dvbdmx->mutex)) + return -ERESTARTSYS; + + dvb_demux_feed_add(dvbdmxfeed); + + dvbdmxfeed->pid = pid; + dvbdmxfeed->buffer_size = circular_buffer_size; + dvbdmxfeed->feed.sec.check_crc = check_crc; + +#ifdef NOBUFS + dvbdmxfeed->buffer = NULL; +#else + dvbdmxfeed->buffer = vmalloc(dvbdmxfeed->buffer_size); + if (!dvbdmxfeed->buffer) { + mutex_unlock(&dvbdmx->mutex); + return -ENOMEM; + } +#endif + + dvbdmxfeed->state = DMX_STATE_READY; + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +static void prepare_secfilters(struct dvb_demux_feed *dvbdmxfeed) +{ + int i; + struct dvb_demux_filter *f; + struct dmx_section_filter *sf; + u8 mask, mode, doneq; + + if (!(f = dvbdmxfeed->filter)) + return; + do { + sf = &f->filter; + doneq = 0; + for (i = 0; i < DVB_DEMUX_MASK_MAX; i++) { + mode = sf->filter_mode[i]; + mask = sf->filter_mask[i]; + f->maskandmode[i] = mask & mode; + doneq |= f->maskandnotmode[i] = mask & ~mode; + } + f->doneq = doneq ? 1 : 0; + } while ((f = f->next)); +} + +static int dmx_section_feed_start_filtering(struct dmx_section_feed *feed) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + int ret; + + if (mutex_lock_interruptible(&dvbdmx->mutex)) + return -ERESTARTSYS; + + if (feed->is_filtering) { + mutex_unlock(&dvbdmx->mutex); + return -EBUSY; + } + + if (!dvbdmxfeed->filter) { + mutex_unlock(&dvbdmx->mutex); + return -EINVAL; + } + + dvbdmxfeed->feed.sec.tsfeedp = 0; + dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base; + dvbdmxfeed->feed.sec.secbufp = 0; + dvbdmxfeed->feed.sec.seclen = 0; + + if (!dvbdmx->start_feed) { + mutex_unlock(&dvbdmx->mutex); + return -ENODEV; + } + + prepare_secfilters(dvbdmxfeed); + + if ((ret = dvbdmx->start_feed(dvbdmxfeed)) < 0) { + mutex_unlock(&dvbdmx->mutex); + return ret; + } + + spin_lock_irq(&dvbdmx->lock); + feed->is_filtering = 1; + dvbdmxfeed->state = DMX_STATE_GO; + spin_unlock_irq(&dvbdmx->lock); + + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +static int dmx_section_feed_stop_filtering(struct dmx_section_feed *feed) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + int ret; + + mutex_lock(&dvbdmx->mutex); + + if (!dvbdmx->stop_feed) { + mutex_unlock(&dvbdmx->mutex); + return -ENODEV; + } + + ret = dvbdmx->stop_feed(dvbdmxfeed); + + spin_lock_irq(&dvbdmx->lock); + dvbdmxfeed->state = DMX_STATE_READY; + feed->is_filtering = 0; + spin_unlock_irq(&dvbdmx->lock); + + mutex_unlock(&dvbdmx->mutex); + return ret; +} + +static int dmx_section_feed_release_filter(struct dmx_section_feed *feed, + struct dmx_section_filter *filter) +{ + struct dvb_demux_filter *dvbdmxfilter = (struct dvb_demux_filter *)filter, *f; + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + + mutex_lock(&dvbdmx->mutex); + + if (dvbdmxfilter->feed != dvbdmxfeed) { + mutex_unlock(&dvbdmx->mutex); + return -EINVAL; + } + + if (feed->is_filtering) + feed->stop_filtering(feed); + + spin_lock_irq(&dvbdmx->lock); + f = dvbdmxfeed->filter; + + if (f == dvbdmxfilter) { + dvbdmxfeed->filter = dvbdmxfilter->next; + } else { + while (f->next != dvbdmxfilter) + f = f->next; + f->next = f->next->next; + } + + dvbdmxfilter->state = DMX_STATE_FREE; + spin_unlock_irq(&dvbdmx->lock); + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +static int dvbdmx_allocate_section_feed(struct dmx_demux *demux, + struct dmx_section_feed **feed, + dmx_section_cb callback) +{ + struct dvb_demux *dvbdmx = (struct dvb_demux *)demux; + struct dvb_demux_feed *dvbdmxfeed; + + if (mutex_lock_interruptible(&dvbdmx->mutex)) + return -ERESTARTSYS; + + if (!(dvbdmxfeed = dvb_dmx_feed_alloc(dvbdmx))) { + mutex_unlock(&dvbdmx->mutex); + return -EBUSY; + } + + dvbdmxfeed->type = DMX_TYPE_SEC; + dvbdmxfeed->cb.sec = callback; + dvbdmxfeed->demux = dvbdmx; + dvbdmxfeed->pid = 0xffff; + dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base; + dvbdmxfeed->feed.sec.secbufp = dvbdmxfeed->feed.sec.seclen = 0; + dvbdmxfeed->feed.sec.tsfeedp = 0; + dvbdmxfeed->filter = NULL; + dvbdmxfeed->buffer = NULL; + + (*feed) = &dvbdmxfeed->feed.sec; + (*feed)->is_filtering = 0; + (*feed)->parent = demux; + (*feed)->priv = NULL; + + (*feed)->set = dmx_section_feed_set; + (*feed)->allocate_filter = dmx_section_feed_allocate_filter; + (*feed)->start_filtering = dmx_section_feed_start_filtering; + (*feed)->stop_filtering = dmx_section_feed_stop_filtering; + (*feed)->release_filter = dmx_section_feed_release_filter; + + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +static int dvbdmx_release_section_feed(struct dmx_demux *demux, + struct dmx_section_feed *feed) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; + struct dvb_demux *dvbdmx = (struct dvb_demux *)demux; + + mutex_lock(&dvbdmx->mutex); + + if (dvbdmxfeed->state == DMX_STATE_FREE) { + mutex_unlock(&dvbdmx->mutex); + return -EINVAL; + } +#ifndef NOBUFS + vfree(dvbdmxfeed->buffer); + dvbdmxfeed->buffer = NULL; +#endif + dvbdmxfeed->state = DMX_STATE_FREE; + + dvb_demux_feed_del(dvbdmxfeed); + + dvbdmxfeed->pid = 0xffff; + + mutex_unlock(&dvbdmx->mutex); + return 0; +} + +/****************************************************************************** + * dvb_demux kernel data API calls + ******************************************************************************/ + +static int dvbdmx_open(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if (dvbdemux->users >= MAX_DVB_DEMUX_USERS) + return -EUSERS; + + dvbdemux->users++; + return 0; +} + +static int dvbdmx_close(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if (dvbdemux->users == 0) + return -ENODEV; + + dvbdemux->users--; + //FIXME: release any unneeded resources if users==0 + return 0; +} + +static int dvbdmx_write(struct dmx_demux *demux, const char __user *buf, size_t count) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + void *p; + + if ((!demux->frontend) || (demux->frontend->source != DMX_MEMORY_FE)) + return -EINVAL; + + p = memdup_user(buf, count); + if (IS_ERR(p)) + return PTR_ERR(p); + if (mutex_lock_interruptible(&dvbdemux->mutex)) { + kfree(p); + return -ERESTARTSYS; + } + dvb_dmx_swfilter(dvbdemux, p, count); + kfree(p); + mutex_unlock(&dvbdemux->mutex); + + if (signal_pending(current)) + return -EINTR; + return count; +} + +static int dvbdmx_add_frontend(struct dmx_demux *demux, + struct dmx_frontend *frontend) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + struct list_head *head = &dvbdemux->frontend_list; + + list_add(&(frontend->connectivity_list), head); + + return 0; +} + +static int dvbdmx_remove_frontend(struct dmx_demux *demux, + struct dmx_frontend *frontend) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + struct list_head *pos, *n, *head = &dvbdemux->frontend_list; + + list_for_each_safe(pos, n, head) { + if (DMX_FE_ENTRY(pos) == frontend) { + list_del(pos); + return 0; + } + } + + return -ENODEV; +} + +static struct list_head *dvbdmx_get_frontends(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if (list_empty(&dvbdemux->frontend_list)) + return NULL; + + return &dvbdemux->frontend_list; +} + +static int dvbdmx_connect_frontend(struct dmx_demux *demux, + struct dmx_frontend *frontend) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + if (demux->frontend) + return -EINVAL; + + mutex_lock(&dvbdemux->mutex); + + demux->frontend = frontend; + mutex_unlock(&dvbdemux->mutex); + return 0; +} + +static int dvbdmx_disconnect_frontend(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + mutex_lock(&dvbdemux->mutex); + + demux->frontend = NULL; + mutex_unlock(&dvbdemux->mutex); + return 0; +} + +static int dvbdmx_get_pes_pids(struct dmx_demux *demux, u16 * pids) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; + + memcpy(pids, dvbdemux->pids, 5 * sizeof(u16)); + return 0; +} + +int dvb_dmx_init(struct dvb_demux *dvbdemux) +{ + int i; + struct dmx_demux *dmx = &dvbdemux->dmx; + + dvbdemux->cnt_storage = NULL; + dvbdemux->users = 0; + dvbdemux->filter = vmalloc(dvbdemux->filternum * sizeof(struct dvb_demux_filter)); + + if (!dvbdemux->filter) + return -ENOMEM; + + dvbdemux->feed = vmalloc(dvbdemux->feednum * sizeof(struct dvb_demux_feed)); + if (!dvbdemux->feed) { + vfree(dvbdemux->filter); + dvbdemux->filter = NULL; + return -ENOMEM; + } + for (i = 0; i < dvbdemux->filternum; i++) { + dvbdemux->filter[i].state = DMX_STATE_FREE; + dvbdemux->filter[i].index = i; + } + for (i = 0; i < dvbdemux->feednum; i++) { + dvbdemux->feed[i].state = DMX_STATE_FREE; + dvbdemux->feed[i].index = i; + } + + dvbdemux->cnt_storage = vmalloc(MAX_PID + 1); + if (!dvbdemux->cnt_storage) + printk(KERN_WARNING "Couldn't allocate memory for TS/TEI check. Disabling it\n"); + + INIT_LIST_HEAD(&dvbdemux->frontend_list); + + for (i = 0; i < DMX_TS_PES_OTHER; i++) { + dvbdemux->pesfilter[i] = NULL; + dvbdemux->pids[i] = 0xffff; + } + + INIT_LIST_HEAD(&dvbdemux->feed_list); + + dvbdemux->playing = 0; + dvbdemux->recording = 0; + dvbdemux->tsbufp = 0; + + if (!dvbdemux->check_crc32) + dvbdemux->check_crc32 = dvb_dmx_crc32; + + if (!dvbdemux->memcopy) + dvbdemux->memcopy = dvb_dmx_memcopy; + + dmx->frontend = NULL; + dmx->priv = dvbdemux; + dmx->open = dvbdmx_open; + dmx->close = dvbdmx_close; + dmx->write = dvbdmx_write; + dmx->allocate_ts_feed = dvbdmx_allocate_ts_feed; + dmx->release_ts_feed = dvbdmx_release_ts_feed; + dmx->allocate_section_feed = dvbdmx_allocate_section_feed; + dmx->release_section_feed = dvbdmx_release_section_feed; + + dmx->add_frontend = dvbdmx_add_frontend; + dmx->remove_frontend = dvbdmx_remove_frontend; + dmx->get_frontends = dvbdmx_get_frontends; + dmx->connect_frontend = dvbdmx_connect_frontend; + dmx->disconnect_frontend = dvbdmx_disconnect_frontend; + dmx->get_pes_pids = dvbdmx_get_pes_pids; + + mutex_init(&dvbdemux->mutex); + spin_lock_init(&dvbdemux->lock); + + return 0; +} + +EXPORT_SYMBOL(dvb_dmx_init); + +void dvb_dmx_release(struct dvb_demux *dvbdemux) +{ + vfree(dvbdemux->cnt_storage); + vfree(dvbdemux->filter); + vfree(dvbdemux->feed); +} + +EXPORT_SYMBOL(dvb_dmx_release); diff --git a/drivers/media/dvb/dvb-core/dvb_demux.h b/drivers/media/dvb-core/dvb_demux.h similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_demux.h rename to drivers/media/dvb-core/dvb_demux.h diff --git a/drivers/media/dvb/dvb-core/dvb_filter.c b/drivers/media/dvb-core/dvb_filter.c similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_filter.c rename to drivers/media/dvb-core/dvb_filter.c diff --git a/drivers/media/dvb/dvb-core/dvb_filter.h b/drivers/media/dvb-core/dvb_filter.h similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_filter.h rename to drivers/media/dvb-core/dvb_filter.h diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c new file mode 100644 index 000000000000..8f58f241c10d --- /dev/null +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -0,0 +1,2617 @@ +/* + * dvb_frontend.c: DVB frontend tuning interface/thread + * + * + * Copyright (C) 1999-2001 Ralph Metzler + * Marcus Metzler + * Holger Waechtler + * for convergence integrated media GmbH + * + * Copyright (C) 2004 Andrew de Quincey (tuning thread cleanup) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +/* Enables DVBv3 compatibility bits at the headers */ +#define __DVB_CORE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dvb_frontend.h" +#include "dvbdev.h" +#include + +static int dvb_frontend_debug; +static int dvb_shutdown_timeout; +static int dvb_force_auto_inversion; +static int dvb_override_tune_delay; +static int dvb_powerdown_on_sleep = 1; +static int dvb_mfe_wait_time = 5; + +module_param_named(frontend_debug, dvb_frontend_debug, int, 0644); +MODULE_PARM_DESC(frontend_debug, "Turn on/off frontend core debugging (default:off)."); +module_param(dvb_shutdown_timeout, int, 0644); +MODULE_PARM_DESC(dvb_shutdown_timeout, "wait seconds after close() before suspending hardware"); +module_param(dvb_force_auto_inversion, int, 0644); +MODULE_PARM_DESC(dvb_force_auto_inversion, "0: normal (default), 1: INVERSION_AUTO forced always"); +module_param(dvb_override_tune_delay, int, 0644); +MODULE_PARM_DESC(dvb_override_tune_delay, "0: normal (default), >0 => delay in milliseconds to wait for lock after a tune attempt"); +module_param(dvb_powerdown_on_sleep, int, 0644); +MODULE_PARM_DESC(dvb_powerdown_on_sleep, "0: do not power down, 1: turn LNB voltage off on sleep (default)"); +module_param(dvb_mfe_wait_time, int, 0644); +MODULE_PARM_DESC(dvb_mfe_wait_time, "Wait up to seconds on open() for multi-frontend to become available (default:5 seconds)"); + +#define FESTATE_IDLE 1 +#define FESTATE_RETUNE 2 +#define FESTATE_TUNING_FAST 4 +#define FESTATE_TUNING_SLOW 8 +#define FESTATE_TUNED 16 +#define FESTATE_ZIGZAG_FAST 32 +#define FESTATE_ZIGZAG_SLOW 64 +#define FESTATE_DISEQC 128 +#define FESTATE_ERROR 256 +#define FESTATE_WAITFORLOCK (FESTATE_TUNING_FAST | FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW | FESTATE_DISEQC) +#define FESTATE_SEARCHING_FAST (FESTATE_TUNING_FAST | FESTATE_ZIGZAG_FAST) +#define FESTATE_SEARCHING_SLOW (FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_SLOW) +#define FESTATE_LOSTLOCK (FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW) + +#define FE_ALGO_HW 1 +/* + * FESTATE_IDLE. No tuning parameters have been supplied and the loop is idling. + * FESTATE_RETUNE. Parameters have been supplied, but we have not yet performed the first tune. + * FESTATE_TUNING_FAST. Tuning parameters have been supplied and fast zigzag scan is in progress. + * FESTATE_TUNING_SLOW. Tuning parameters have been supplied. Fast zigzag failed, so we're trying again, but slower. + * FESTATE_TUNED. The frontend has successfully locked on. + * FESTATE_ZIGZAG_FAST. The lock has been lost, and a fast zigzag has been initiated to try and regain it. + * FESTATE_ZIGZAG_SLOW. The lock has been lost. Fast zigzag has been failed, so we're trying again, but slower. + * FESTATE_DISEQC. A DISEQC command has just been issued. + * FESTATE_WAITFORLOCK. When we're waiting for a lock. + * FESTATE_SEARCHING_FAST. When we're searching for a signal using a fast zigzag scan. + * FESTATE_SEARCHING_SLOW. When we're searching for a signal using a slow zigzag scan. + * FESTATE_LOSTLOCK. When the lock has been lost, and we're searching it again. + */ + +#define DVB_FE_NO_EXIT 0 +#define DVB_FE_NORMAL_EXIT 1 +#define DVB_FE_DEVICE_REMOVED 2 + +static DEFINE_MUTEX(frontend_mutex); + +struct dvb_frontend_private { + + /* thread/frontend values */ + struct dvb_device *dvbdev; + struct dvb_frontend_parameters parameters_out; + struct dvb_fe_events events; + struct semaphore sem; + struct list_head list_head; + wait_queue_head_t wait_queue; + struct task_struct *thread; + unsigned long release_jiffies; + unsigned int exit; + unsigned int wakeup; + fe_status_t status; + unsigned long tune_mode_flags; + unsigned int delay; + unsigned int reinitialise; + int tone; + int voltage; + + /* swzigzag values */ + unsigned int state; + unsigned int bending; + int lnb_drift; + unsigned int inversion; + unsigned int auto_step; + unsigned int auto_sub_step; + unsigned int started_auto_step; + unsigned int min_delay; + unsigned int max_drift; + unsigned int step_size; + int quality; + unsigned int check_wrapped; + enum dvbfe_search algo_status; +}; + +static void dvb_frontend_wakeup(struct dvb_frontend *fe); +static int dtv_get_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p_out); +static int dtv_property_legacy_params_sync(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p); + +static bool has_get_frontend(struct dvb_frontend *fe) +{ + return fe->ops.get_frontend != NULL; +} + +/* + * Due to DVBv3 API calls, a delivery system should be mapped into one of + * the 4 DVBv3 delivery systems (FE_QPSK, FE_QAM, FE_OFDM or FE_ATSC), + * otherwise, a DVBv3 call will fail. + */ +enum dvbv3_emulation_type { + DVBV3_UNKNOWN, + DVBV3_QPSK, + DVBV3_QAM, + DVBV3_OFDM, + DVBV3_ATSC, +}; + +static enum dvbv3_emulation_type dvbv3_type(u32 delivery_system) +{ + switch (delivery_system) { + case SYS_DVBC_ANNEX_A: + case SYS_DVBC_ANNEX_C: + return DVBV3_QAM; + case SYS_DVBS: + case SYS_DVBS2: + case SYS_TURBO: + case SYS_ISDBS: + case SYS_DSS: + return DVBV3_QPSK; + case SYS_DVBT: + case SYS_DVBT2: + case SYS_ISDBT: + case SYS_DTMB: + return DVBV3_OFDM; + case SYS_ATSC: + case SYS_ATSCMH: + case SYS_DVBC_ANNEX_B: + return DVBV3_ATSC; + case SYS_UNDEFINED: + case SYS_ISDBC: + case SYS_DVBH: + case SYS_DAB: + default: + /* + * Doesn't know how to emulate those types and/or + * there's no frontend driver from this type yet + * with some emulation code, so, we're not sure yet how + * to handle them, or they're not compatible with a DVBv3 call. + */ + return DVBV3_UNKNOWN; + } +} + +static void dvb_frontend_add_event(struct dvb_frontend *fe, fe_status_t status) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_fe_events *events = &fepriv->events; + struct dvb_frontend_event *e; + int wp; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if ((status & FE_HAS_LOCK) && has_get_frontend(fe)) + dtv_get_frontend(fe, &fepriv->parameters_out); + + mutex_lock(&events->mtx); + + wp = (events->eventw + 1) % MAX_EVENT; + if (wp == events->eventr) { + events->overflow = 1; + events->eventr = (events->eventr + 1) % MAX_EVENT; + } + + e = &events->events[events->eventw]; + e->status = status; + e->parameters = fepriv->parameters_out; + + events->eventw = wp; + + mutex_unlock(&events->mtx); + + wake_up_interruptible (&events->wait_queue); +} + +static int dvb_frontend_get_event(struct dvb_frontend *fe, + struct dvb_frontend_event *event, int flags) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_fe_events *events = &fepriv->events; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if (events->overflow) { + events->overflow = 0; + return -EOVERFLOW; + } + + if (events->eventw == events->eventr) { + int ret; + + if (flags & O_NONBLOCK) + return -EWOULDBLOCK; + + up(&fepriv->sem); + + ret = wait_event_interruptible (events->wait_queue, + events->eventw != events->eventr); + + if (down_interruptible (&fepriv->sem)) + return -ERESTARTSYS; + + if (ret < 0) + return ret; + } + + mutex_lock(&events->mtx); + *event = events->events[events->eventr]; + events->eventr = (events->eventr + 1) % MAX_EVENT; + mutex_unlock(&events->mtx); + + return 0; +} + +static void dvb_frontend_clear_events(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_fe_events *events = &fepriv->events; + + mutex_lock(&events->mtx); + events->eventr = events->eventw; + mutex_unlock(&events->mtx); +} + +static void dvb_frontend_init(struct dvb_frontend *fe) +{ + dev_dbg(fe->dvb->device, + "%s: initialising adapter %i frontend %i (%s)...\n", + __func__, fe->dvb->num, fe->id, fe->ops.info.name); + + if (fe->ops.init) + fe->ops.init(fe); + if (fe->ops.tuner_ops.init) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + fe->ops.tuner_ops.init(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } +} + +void dvb_frontend_reinitialise(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + fepriv->reinitialise = 1; + dvb_frontend_wakeup(fe); +} +EXPORT_SYMBOL(dvb_frontend_reinitialise); + +static void dvb_frontend_swzigzag_update_delay(struct dvb_frontend_private *fepriv, int locked) +{ + int q2; + struct dvb_frontend *fe = fepriv->dvbdev->priv; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if (locked) + (fepriv->quality) = (fepriv->quality * 220 + 36*256) / 256; + else + (fepriv->quality) = (fepriv->quality * 220 + 0) / 256; + + q2 = fepriv->quality - 128; + q2 *= q2; + + fepriv->delay = fepriv->min_delay + q2 * HZ / (128*128); +} + +/** + * Performs automatic twiddling of frontend parameters. + * + * @param fe The frontend concerned. + * @param check_wrapped Checks if an iteration has completed. DO NOT SET ON THE FIRST ATTEMPT + * @returns Number of complete iterations that have been performed. + */ +static int dvb_frontend_swzigzag_autotune(struct dvb_frontend *fe, int check_wrapped) +{ + int autoinversion; + int ready = 0; + int fe_set_err = 0; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache, tmp; + int original_inversion = c->inversion; + u32 original_frequency = c->frequency; + + /* are we using autoinversion? */ + autoinversion = ((!(fe->ops.info.caps & FE_CAN_INVERSION_AUTO)) && + (c->inversion == INVERSION_AUTO)); + + /* setup parameters correctly */ + while(!ready) { + /* calculate the lnb_drift */ + fepriv->lnb_drift = fepriv->auto_step * fepriv->step_size; + + /* wrap the auto_step if we've exceeded the maximum drift */ + if (fepriv->lnb_drift > fepriv->max_drift) { + fepriv->auto_step = 0; + fepriv->auto_sub_step = 0; + fepriv->lnb_drift = 0; + } + + /* perform inversion and +/- zigzag */ + switch(fepriv->auto_sub_step) { + case 0: + /* try with the current inversion and current drift setting */ + ready = 1; + break; + + case 1: + if (!autoinversion) break; + + fepriv->inversion = (fepriv->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF; + ready = 1; + break; + + case 2: + if (fepriv->lnb_drift == 0) break; + + fepriv->lnb_drift = -fepriv->lnb_drift; + ready = 1; + break; + + case 3: + if (fepriv->lnb_drift == 0) break; + if (!autoinversion) break; + + fepriv->inversion = (fepriv->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF; + fepriv->lnb_drift = -fepriv->lnb_drift; + ready = 1; + break; + + default: + fepriv->auto_step++; + fepriv->auto_sub_step = -1; /* it'll be incremented to 0 in a moment */ + break; + } + + if (!ready) fepriv->auto_sub_step++; + } + + /* if this attempt would hit where we started, indicate a complete + * iteration has occurred */ + if ((fepriv->auto_step == fepriv->started_auto_step) && + (fepriv->auto_sub_step == 0) && check_wrapped) { + return 1; + } + + dev_dbg(fe->dvb->device, "%s: drift:%i inversion:%i auto_step:%i " \ + "auto_sub_step:%i started_auto_step:%i\n", + __func__, fepriv->lnb_drift, fepriv->inversion, + fepriv->auto_step, fepriv->auto_sub_step, + fepriv->started_auto_step); + + /* set the frontend itself */ + c->frequency += fepriv->lnb_drift; + if (autoinversion) + c->inversion = fepriv->inversion; + tmp = *c; + if (fe->ops.set_frontend) + fe_set_err = fe->ops.set_frontend(fe); + *c = tmp; + if (fe_set_err < 0) { + fepriv->state = FESTATE_ERROR; + return fe_set_err; + } + + c->frequency = original_frequency; + c->inversion = original_inversion; + + fepriv->auto_sub_step++; + return 0; +} + +static void dvb_frontend_swzigzag(struct dvb_frontend *fe) +{ + fe_status_t s = 0; + int retval = 0; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache, tmp; + + /* if we've got no parameters, just keep idling */ + if (fepriv->state & FESTATE_IDLE) { + fepriv->delay = 3*HZ; + fepriv->quality = 0; + return; + } + + /* in SCAN mode, we just set the frontend when asked and leave it alone */ + if (fepriv->tune_mode_flags & FE_TUNE_MODE_ONESHOT) { + if (fepriv->state & FESTATE_RETUNE) { + tmp = *c; + if (fe->ops.set_frontend) + retval = fe->ops.set_frontend(fe); + *c = tmp; + if (retval < 0) + fepriv->state = FESTATE_ERROR; + else + fepriv->state = FESTATE_TUNED; + } + fepriv->delay = 3*HZ; + fepriv->quality = 0; + return; + } + + /* get the frontend status */ + if (fepriv->state & FESTATE_RETUNE) { + s = 0; + } else { + if (fe->ops.read_status) + fe->ops.read_status(fe, &s); + if (s != fepriv->status) { + dvb_frontend_add_event(fe, s); + fepriv->status = s; + } + } + + /* if we're not tuned, and we have a lock, move to the TUNED state */ + if ((fepriv->state & FESTATE_WAITFORLOCK) && (s & FE_HAS_LOCK)) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + fepriv->state = FESTATE_TUNED; + + /* if we're tuned, then we have determined the correct inversion */ + if ((!(fe->ops.info.caps & FE_CAN_INVERSION_AUTO)) && + (c->inversion == INVERSION_AUTO)) { + c->inversion = fepriv->inversion; + } + return; + } + + /* if we are tuned already, check we're still locked */ + if (fepriv->state & FESTATE_TUNED) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + + /* we're tuned, and the lock is still good... */ + if (s & FE_HAS_LOCK) { + return; + } else { /* if we _WERE_ tuned, but now don't have a lock */ + fepriv->state = FESTATE_ZIGZAG_FAST; + fepriv->started_auto_step = fepriv->auto_step; + fepriv->check_wrapped = 0; + } + } + + /* don't actually do anything if we're in the LOSTLOCK state, + * the frontend is set to FE_CAN_RECOVER, and the max_drift is 0 */ + if ((fepriv->state & FESTATE_LOSTLOCK) && + (fe->ops.info.caps & FE_CAN_RECOVER) && (fepriv->max_drift == 0)) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + return; + } + + /* don't do anything if we're in the DISEQC state, since this + * might be someone with a motorized dish controlled by DISEQC. + * If its actually a re-tune, there will be a SET_FRONTEND soon enough. */ + if (fepriv->state & FESTATE_DISEQC) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + return; + } + + /* if we're in the RETUNE state, set everything up for a brand + * new scan, keeping the current inversion setting, as the next + * tune is _very_ likely to require the same */ + if (fepriv->state & FESTATE_RETUNE) { + fepriv->lnb_drift = 0; + fepriv->auto_step = 0; + fepriv->auto_sub_step = 0; + fepriv->started_auto_step = 0; + fepriv->check_wrapped = 0; + } + + /* fast zigzag. */ + if ((fepriv->state & FESTATE_SEARCHING_FAST) || (fepriv->state & FESTATE_RETUNE)) { + fepriv->delay = fepriv->min_delay; + + /* perform a tune */ + retval = dvb_frontend_swzigzag_autotune(fe, + fepriv->check_wrapped); + if (retval < 0) { + return; + } else if (retval) { + /* OK, if we've run out of trials at the fast speed. + * Drop back to slow for the _next_ attempt */ + fepriv->state = FESTATE_SEARCHING_SLOW; + fepriv->started_auto_step = fepriv->auto_step; + return; + } + fepriv->check_wrapped = 1; + + /* if we've just retuned, enter the ZIGZAG_FAST state. + * This ensures we cannot return from an + * FE_SET_FRONTEND ioctl before the first frontend tune + * occurs */ + if (fepriv->state & FESTATE_RETUNE) { + fepriv->state = FESTATE_TUNING_FAST; + } + } + + /* slow zigzag */ + if (fepriv->state & FESTATE_SEARCHING_SLOW) { + dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); + + /* Note: don't bother checking for wrapping; we stay in this + * state until we get a lock */ + dvb_frontend_swzigzag_autotune(fe, 0); + } +} + +static int dvb_frontend_is_exiting(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + if (fepriv->exit != DVB_FE_NO_EXIT) + return 1; + + if (fepriv->dvbdev->writers == 1) + if (time_after_eq(jiffies, fepriv->release_jiffies + + dvb_shutdown_timeout * HZ)) + return 1; + + return 0; +} + +static int dvb_frontend_should_wakeup(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + if (fepriv->wakeup) { + fepriv->wakeup = 0; + return 1; + } + return dvb_frontend_is_exiting(fe); +} + +static void dvb_frontend_wakeup(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + fepriv->wakeup = 1; + wake_up_interruptible(&fepriv->wait_queue); +} + +static int dvb_frontend_thread(void *data) +{ + struct dvb_frontend *fe = data; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + fe_status_t s; + enum dvbfe_algo algo; + + bool re_tune = false; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + fepriv->check_wrapped = 0; + fepriv->quality = 0; + fepriv->delay = 3*HZ; + fepriv->status = 0; + fepriv->wakeup = 0; + fepriv->reinitialise = 0; + + dvb_frontend_init(fe); + + set_freezable(); + while (1) { + up(&fepriv->sem); /* is locked when we enter the thread... */ +restart: + wait_event_interruptible_timeout(fepriv->wait_queue, + dvb_frontend_should_wakeup(fe) || kthread_should_stop() + || freezing(current), + fepriv->delay); + + if (kthread_should_stop() || dvb_frontend_is_exiting(fe)) { + /* got signal or quitting */ + fepriv->exit = DVB_FE_NORMAL_EXIT; + break; + } + + if (try_to_freeze()) + goto restart; + + if (down_interruptible(&fepriv->sem)) + break; + + if (fepriv->reinitialise) { + dvb_frontend_init(fe); + if (fe->ops.set_tone && fepriv->tone != -1) + fe->ops.set_tone(fe, fepriv->tone); + if (fe->ops.set_voltage && fepriv->voltage != -1) + fe->ops.set_voltage(fe, fepriv->voltage); + fepriv->reinitialise = 0; + } + + /* do an iteration of the tuning loop */ + if (fe->ops.get_frontend_algo) { + algo = fe->ops.get_frontend_algo(fe); + switch (algo) { + case DVBFE_ALGO_HW: + dev_dbg(fe->dvb->device, "%s: Frontend ALGO = DVBFE_ALGO_HW\n", __func__); + + if (fepriv->state & FESTATE_RETUNE) { + dev_dbg(fe->dvb->device, "%s: Retune requested, FESTATE_RETUNE\n", __func__); + re_tune = true; + fepriv->state = FESTATE_TUNED; + } else { + re_tune = false; + } + + if (fe->ops.tune) + fe->ops.tune(fe, re_tune, fepriv->tune_mode_flags, &fepriv->delay, &s); + + if (s != fepriv->status && !(fepriv->tune_mode_flags & FE_TUNE_MODE_ONESHOT)) { + dev_dbg(fe->dvb->device, "%s: state changed, adding current state\n", __func__); + dvb_frontend_add_event(fe, s); + fepriv->status = s; + } + break; + case DVBFE_ALGO_SW: + dev_dbg(fe->dvb->device, "%s: Frontend ALGO = DVBFE_ALGO_SW\n", __func__); + dvb_frontend_swzigzag(fe); + break; + case DVBFE_ALGO_CUSTOM: + dev_dbg(fe->dvb->device, "%s: Frontend ALGO = DVBFE_ALGO_CUSTOM, state=%d\n", __func__, fepriv->state); + if (fepriv->state & FESTATE_RETUNE) { + dev_dbg(fe->dvb->device, "%s: Retune requested, FESTAT_RETUNE\n", __func__); + fepriv->state = FESTATE_TUNED; + } + /* Case where we are going to search for a carrier + * User asked us to retune again for some reason, possibly + * requesting a search with a new set of parameters + */ + if (fepriv->algo_status & DVBFE_ALGO_SEARCH_AGAIN) { + if (fe->ops.search) { + fepriv->algo_status = fe->ops.search(fe); + /* We did do a search as was requested, the flags are + * now unset as well and has the flags wrt to search. + */ + } else { + fepriv->algo_status &= ~DVBFE_ALGO_SEARCH_AGAIN; + } + } + /* Track the carrier if the search was successful */ + if (fepriv->algo_status != DVBFE_ALGO_SEARCH_SUCCESS) { + fepriv->algo_status |= DVBFE_ALGO_SEARCH_AGAIN; + fepriv->delay = HZ / 2; + } + dtv_property_legacy_params_sync(fe, &fepriv->parameters_out); + fe->ops.read_status(fe, &s); + if (s != fepriv->status) { + dvb_frontend_add_event(fe, s); /* update event list */ + fepriv->status = s; + if (!(s & FE_HAS_LOCK)) { + fepriv->delay = HZ / 10; + fepriv->algo_status |= DVBFE_ALGO_SEARCH_AGAIN; + } else { + fepriv->delay = 60 * HZ; + } + } + break; + default: + dev_dbg(fe->dvb->device, "%s: UNDEFINED ALGO !\n", __func__); + break; + } + } else { + dvb_frontend_swzigzag(fe); + } + } + + if (dvb_powerdown_on_sleep) { + if (fe->ops.set_voltage) + fe->ops.set_voltage(fe, SEC_VOLTAGE_OFF); + if (fe->ops.tuner_ops.sleep) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + fe->ops.tuner_ops.sleep(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + if (fe->ops.sleep) + fe->ops.sleep(fe); + } + + fepriv->thread = NULL; + if (kthread_should_stop()) + fepriv->exit = DVB_FE_DEVICE_REMOVED; + else + fepriv->exit = DVB_FE_NO_EXIT; + mb(); + + dvb_frontend_wakeup(fe); + return 0; +} + +static void dvb_frontend_stop(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + fepriv->exit = DVB_FE_NORMAL_EXIT; + mb(); + + if (!fepriv->thread) + return; + + kthread_stop(fepriv->thread); + + sema_init(&fepriv->sem, 1); + fepriv->state = FESTATE_IDLE; + + /* paranoia check in case a signal arrived */ + if (fepriv->thread) + dev_warn(fe->dvb->device, + "dvb_frontend_stop: warning: thread %p won't exit\n", + fepriv->thread); +} + +s32 timeval_usec_diff(struct timeval lasttime, struct timeval curtime) +{ + return ((curtime.tv_usec < lasttime.tv_usec) ? + 1000000 - lasttime.tv_usec + curtime.tv_usec : + curtime.tv_usec - lasttime.tv_usec); +} +EXPORT_SYMBOL(timeval_usec_diff); + +static inline void timeval_usec_add(struct timeval *curtime, u32 add_usec) +{ + curtime->tv_usec += add_usec; + if (curtime->tv_usec >= 1000000) { + curtime->tv_usec -= 1000000; + curtime->tv_sec++; + } +} + +/* + * Sleep until gettimeofday() > waketime + add_usec + * This needs to be as precise as possible, but as the delay is + * usually between 2ms and 32ms, it is done using a scheduled msleep + * followed by usleep (normally a busy-wait loop) for the remainder + */ +void dvb_frontend_sleep_until(struct timeval *waketime, u32 add_usec) +{ + struct timeval lasttime; + s32 delta, newdelta; + + timeval_usec_add(waketime, add_usec); + + do_gettimeofday(&lasttime); + delta = timeval_usec_diff(lasttime, *waketime); + if (delta > 2500) { + msleep((delta - 1500) / 1000); + do_gettimeofday(&lasttime); + newdelta = timeval_usec_diff(lasttime, *waketime); + delta = (newdelta > delta) ? 0 : newdelta; + } + if (delta > 0) + udelay(delta); +} +EXPORT_SYMBOL(dvb_frontend_sleep_until); + +static int dvb_frontend_start(struct dvb_frontend *fe) +{ + int ret; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct task_struct *fe_thread; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if (fepriv->thread) { + if (fepriv->exit == DVB_FE_NO_EXIT) + return 0; + else + dvb_frontend_stop (fe); + } + + if (signal_pending(current)) + return -EINTR; + if (down_interruptible (&fepriv->sem)) + return -EINTR; + + fepriv->state = FESTATE_IDLE; + fepriv->exit = DVB_FE_NO_EXIT; + fepriv->thread = NULL; + mb(); + + fe_thread = kthread_run(dvb_frontend_thread, fe, + "kdvb-ad-%i-fe-%i", fe->dvb->num,fe->id); + if (IS_ERR(fe_thread)) { + ret = PTR_ERR(fe_thread); + dev_warn(fe->dvb->device, + "dvb_frontend_start: failed to start kthread (%d)\n", + ret); + up(&fepriv->sem); + return ret; + } + fepriv->thread = fe_thread; + return 0; +} + +static void dvb_frontend_get_frequency_limits(struct dvb_frontend *fe, + u32 *freq_min, u32 *freq_max) +{ + *freq_min = max(fe->ops.info.frequency_min, fe->ops.tuner_ops.info.frequency_min); + + if (fe->ops.info.frequency_max == 0) + *freq_max = fe->ops.tuner_ops.info.frequency_max; + else if (fe->ops.tuner_ops.info.frequency_max == 0) + *freq_max = fe->ops.info.frequency_max; + else + *freq_max = min(fe->ops.info.frequency_max, fe->ops.tuner_ops.info.frequency_max); + + if (*freq_min == 0 || *freq_max == 0) + dev_warn(fe->dvb->device, "DVB: adapter %i frontend %u frequency limits undefined - fix the driver\n", + fe->dvb->num, fe->id); +} + +static int dvb_frontend_check_parameters(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u32 freq_min; + u32 freq_max; + + /* range check: frequency */ + dvb_frontend_get_frequency_limits(fe, &freq_min, &freq_max); + if ((freq_min && c->frequency < freq_min) || + (freq_max && c->frequency > freq_max)) { + dev_warn(fe->dvb->device, "DVB: adapter %i frontend %i frequency %u out of range (%u..%u)\n", + fe->dvb->num, fe->id, c->frequency, + freq_min, freq_max); + return -EINVAL; + } + + /* range check: symbol rate */ + switch (c->delivery_system) { + case SYS_DVBS: + case SYS_DVBS2: + case SYS_TURBO: + case SYS_DVBC_ANNEX_A: + case SYS_DVBC_ANNEX_C: + if ((fe->ops.info.symbol_rate_min && + c->symbol_rate < fe->ops.info.symbol_rate_min) || + (fe->ops.info.symbol_rate_max && + c->symbol_rate > fe->ops.info.symbol_rate_max)) { + dev_warn(fe->dvb->device, "DVB: adapter %i frontend %i symbol rate %u out of range (%u..%u)\n", + fe->dvb->num, fe->id, c->symbol_rate, + fe->ops.info.symbol_rate_min, + fe->ops.info.symbol_rate_max); + return -EINVAL; + } + default: + break; + } + + return 0; +} + +static int dvb_frontend_clear_cache(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int i; + u32 delsys; + + delsys = c->delivery_system; + memset(c, 0, sizeof(struct dtv_frontend_properties)); + c->delivery_system = delsys; + + c->state = DTV_CLEAR; + + dev_dbg(fe->dvb->device, "%s: Clearing cache for delivery system %d\n", + __func__, c->delivery_system); + + c->transmission_mode = TRANSMISSION_MODE_AUTO; + c->bandwidth_hz = 0; /* AUTO */ + c->guard_interval = GUARD_INTERVAL_AUTO; + c->hierarchy = HIERARCHY_AUTO; + c->symbol_rate = 0; + c->code_rate_HP = FEC_AUTO; + c->code_rate_LP = FEC_AUTO; + c->fec_inner = FEC_AUTO; + c->rolloff = ROLLOFF_AUTO; + c->voltage = SEC_VOLTAGE_OFF; + c->sectone = SEC_TONE_OFF; + c->pilot = PILOT_AUTO; + + c->isdbt_partial_reception = 0; + c->isdbt_sb_mode = 0; + c->isdbt_sb_subchannel = 0; + c->isdbt_sb_segment_idx = 0; + c->isdbt_sb_segment_count = 0; + c->isdbt_layer_enabled = 0; + for (i = 0; i < 3; i++) { + c->layer[i].fec = FEC_AUTO; + c->layer[i].modulation = QAM_AUTO; + c->layer[i].interleaving = 0; + c->layer[i].segment_count = 0; + } + + c->stream_id = NO_STREAM_ID_FILTER; + + switch (c->delivery_system) { + case SYS_DVBS: + case SYS_DVBS2: + case SYS_TURBO: + c->modulation = QPSK; /* implied for DVB-S in legacy API */ + c->rolloff = ROLLOFF_35;/* implied for DVB-S */ + break; + case SYS_ATSC: + c->modulation = VSB_8; + break; + default: + c->modulation = QAM_AUTO; + break; + } + + return 0; +} + +#define _DTV_CMD(n, s, b) \ +[n] = { \ + .name = #n, \ + .cmd = n, \ + .set = s,\ + .buffer = b \ +} + +static struct dtv_cmds_h dtv_cmds[DTV_MAX_COMMAND + 1] = { + _DTV_CMD(DTV_TUNE, 1, 0), + _DTV_CMD(DTV_CLEAR, 1, 0), + + /* Set */ + _DTV_CMD(DTV_FREQUENCY, 1, 0), + _DTV_CMD(DTV_BANDWIDTH_HZ, 1, 0), + _DTV_CMD(DTV_MODULATION, 1, 0), + _DTV_CMD(DTV_INVERSION, 1, 0), + _DTV_CMD(DTV_DISEQC_MASTER, 1, 1), + _DTV_CMD(DTV_SYMBOL_RATE, 1, 0), + _DTV_CMD(DTV_INNER_FEC, 1, 0), + _DTV_CMD(DTV_VOLTAGE, 1, 0), + _DTV_CMD(DTV_TONE, 1, 0), + _DTV_CMD(DTV_PILOT, 1, 0), + _DTV_CMD(DTV_ROLLOFF, 1, 0), + _DTV_CMD(DTV_DELIVERY_SYSTEM, 1, 0), + _DTV_CMD(DTV_HIERARCHY, 1, 0), + _DTV_CMD(DTV_CODE_RATE_HP, 1, 0), + _DTV_CMD(DTV_CODE_RATE_LP, 1, 0), + _DTV_CMD(DTV_GUARD_INTERVAL, 1, 0), + _DTV_CMD(DTV_TRANSMISSION_MODE, 1, 0), + _DTV_CMD(DTV_INTERLEAVING, 1, 0), + + _DTV_CMD(DTV_ISDBT_PARTIAL_RECEPTION, 1, 0), + _DTV_CMD(DTV_ISDBT_SOUND_BROADCASTING, 1, 0), + _DTV_CMD(DTV_ISDBT_SB_SUBCHANNEL_ID, 1, 0), + _DTV_CMD(DTV_ISDBT_SB_SEGMENT_IDX, 1, 0), + _DTV_CMD(DTV_ISDBT_SB_SEGMENT_COUNT, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYER_ENABLED, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERA_FEC, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERA_MODULATION, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERA_SEGMENT_COUNT, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERA_TIME_INTERLEAVING, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERB_FEC, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERB_MODULATION, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERB_SEGMENT_COUNT, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERB_TIME_INTERLEAVING, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERC_FEC, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERC_MODULATION, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERC_SEGMENT_COUNT, 1, 0), + _DTV_CMD(DTV_ISDBT_LAYERC_TIME_INTERLEAVING, 1, 0), + + _DTV_CMD(DTV_STREAM_ID, 1, 0), + _DTV_CMD(DTV_DVBT2_PLP_ID_LEGACY, 1, 0), + _DTV_CMD(DTV_LNA, 1, 0), + + /* Get */ + _DTV_CMD(DTV_DISEQC_SLAVE_REPLY, 0, 1), + _DTV_CMD(DTV_API_VERSION, 0, 0), + _DTV_CMD(DTV_CODE_RATE_HP, 0, 0), + _DTV_CMD(DTV_CODE_RATE_LP, 0, 0), + _DTV_CMD(DTV_GUARD_INTERVAL, 0, 0), + _DTV_CMD(DTV_TRANSMISSION_MODE, 0, 0), + _DTV_CMD(DTV_HIERARCHY, 0, 0), + _DTV_CMD(DTV_INTERLEAVING, 0, 0), + + _DTV_CMD(DTV_ENUM_DELSYS, 0, 0), + + _DTV_CMD(DTV_ATSCMH_PARADE_ID, 1, 0), + _DTV_CMD(DTV_ATSCMH_RS_FRAME_ENSEMBLE, 1, 0), + + _DTV_CMD(DTV_ATSCMH_FIC_VER, 0, 0), + _DTV_CMD(DTV_ATSCMH_PARADE_ID, 0, 0), + _DTV_CMD(DTV_ATSCMH_NOG, 0, 0), + _DTV_CMD(DTV_ATSCMH_TNOG, 0, 0), + _DTV_CMD(DTV_ATSCMH_SGN, 0, 0), + _DTV_CMD(DTV_ATSCMH_PRC, 0, 0), + _DTV_CMD(DTV_ATSCMH_RS_FRAME_MODE, 0, 0), + _DTV_CMD(DTV_ATSCMH_RS_FRAME_ENSEMBLE, 0, 0), + _DTV_CMD(DTV_ATSCMH_RS_CODE_MODE_PRI, 0, 0), + _DTV_CMD(DTV_ATSCMH_RS_CODE_MODE_SEC, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_BLOCK_MODE, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_A, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_B, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_C, 0, 0), + _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_D, 0, 0), +}; + +static void dtv_property_dump(struct dvb_frontend *fe, struct dtv_property *tvp) +{ + int i; + + if (tvp->cmd <= 0 || tvp->cmd > DTV_MAX_COMMAND) { + dev_warn(fe->dvb->device, "%s: tvp.cmd = 0x%08x undefined\n", + __func__, tvp->cmd); + return; + } + + dev_dbg(fe->dvb->device, "%s: tvp.cmd = 0x%08x (%s)\n", __func__, + tvp->cmd, dtv_cmds[tvp->cmd].name); + + if (dtv_cmds[tvp->cmd].buffer) { + dev_dbg(fe->dvb->device, "%s: tvp.u.buffer.len = 0x%02x\n", + __func__, tvp->u.buffer.len); + + for(i = 0; i < tvp->u.buffer.len; i++) + dev_dbg(fe->dvb->device, + "%s: tvp.u.buffer.data[0x%02x] = 0x%02x\n", + __func__, i, tvp->u.buffer.data[i]); + } else { + dev_dbg(fe->dvb->device, "%s: tvp.u.data = 0x%08x\n", __func__, + tvp->u.data); + } +} + +/* Synchronise the legacy tuning parameters into the cache, so that demodulator + * drivers can use a single set_frontend tuning function, regardless of whether + * it's being used for the legacy or new API, reducing code and complexity. + */ +static int dtv_property_cache_sync(struct dvb_frontend *fe, + struct dtv_frontend_properties *c, + const struct dvb_frontend_parameters *p) +{ + c->frequency = p->frequency; + c->inversion = p->inversion; + + switch (dvbv3_type(c->delivery_system)) { + case DVBV3_QPSK: + dev_dbg(fe->dvb->device, "%s: Preparing QPSK req\n", __func__); + c->symbol_rate = p->u.qpsk.symbol_rate; + c->fec_inner = p->u.qpsk.fec_inner; + break; + case DVBV3_QAM: + dev_dbg(fe->dvb->device, "%s: Preparing QAM req\n", __func__); + c->symbol_rate = p->u.qam.symbol_rate; + c->fec_inner = p->u.qam.fec_inner; + c->modulation = p->u.qam.modulation; + break; + case DVBV3_OFDM: + dev_dbg(fe->dvb->device, "%s: Preparing OFDM req\n", __func__); + + switch (p->u.ofdm.bandwidth) { + case BANDWIDTH_10_MHZ: + c->bandwidth_hz = 10000000; + break; + case BANDWIDTH_8_MHZ: + c->bandwidth_hz = 8000000; + break; + case BANDWIDTH_7_MHZ: + c->bandwidth_hz = 7000000; + break; + case BANDWIDTH_6_MHZ: + c->bandwidth_hz = 6000000; + break; + case BANDWIDTH_5_MHZ: + c->bandwidth_hz = 5000000; + break; + case BANDWIDTH_1_712_MHZ: + c->bandwidth_hz = 1712000; + break; + case BANDWIDTH_AUTO: + c->bandwidth_hz = 0; + } + + c->code_rate_HP = p->u.ofdm.code_rate_HP; + c->code_rate_LP = p->u.ofdm.code_rate_LP; + c->modulation = p->u.ofdm.constellation; + c->transmission_mode = p->u.ofdm.transmission_mode; + c->guard_interval = p->u.ofdm.guard_interval; + c->hierarchy = p->u.ofdm.hierarchy_information; + break; + case DVBV3_ATSC: + dev_dbg(fe->dvb->device, "%s: Preparing ATSC req\n", __func__); + c->modulation = p->u.vsb.modulation; + if (c->delivery_system == SYS_ATSCMH) + break; + if ((c->modulation == VSB_8) || (c->modulation == VSB_16)) + c->delivery_system = SYS_ATSC; + else + c->delivery_system = SYS_DVBC_ANNEX_B; + break; + case DVBV3_UNKNOWN: + dev_err(fe->dvb->device, + "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", + __func__, c->delivery_system); + return -EINVAL; + } + + return 0; +} + +/* Ensure the cached values are set correctly in the frontend + * legacy tuning structures, for the advanced tuning API. + */ +static int dtv_property_legacy_params_sync(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p) +{ + const struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + p->frequency = c->frequency; + p->inversion = c->inversion; + + switch (dvbv3_type(c->delivery_system)) { + case DVBV3_UNKNOWN: + dev_err(fe->dvb->device, + "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", + __func__, c->delivery_system); + return -EINVAL; + case DVBV3_QPSK: + dev_dbg(fe->dvb->device, "%s: Preparing QPSK req\n", __func__); + p->u.qpsk.symbol_rate = c->symbol_rate; + p->u.qpsk.fec_inner = c->fec_inner; + break; + case DVBV3_QAM: + dev_dbg(fe->dvb->device, "%s: Preparing QAM req\n", __func__); + p->u.qam.symbol_rate = c->symbol_rate; + p->u.qam.fec_inner = c->fec_inner; + p->u.qam.modulation = c->modulation; + break; + case DVBV3_OFDM: + dev_dbg(fe->dvb->device, "%s: Preparing OFDM req\n", __func__); + switch (c->bandwidth_hz) { + case 10000000: + p->u.ofdm.bandwidth = BANDWIDTH_10_MHZ; + break; + case 8000000: + p->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; + break; + case 7000000: + p->u.ofdm.bandwidth = BANDWIDTH_7_MHZ; + break; + case 6000000: + p->u.ofdm.bandwidth = BANDWIDTH_6_MHZ; + break; + case 5000000: + p->u.ofdm.bandwidth = BANDWIDTH_5_MHZ; + break; + case 1712000: + p->u.ofdm.bandwidth = BANDWIDTH_1_712_MHZ; + break; + case 0: + default: + p->u.ofdm.bandwidth = BANDWIDTH_AUTO; + } + p->u.ofdm.code_rate_HP = c->code_rate_HP; + p->u.ofdm.code_rate_LP = c->code_rate_LP; + p->u.ofdm.constellation = c->modulation; + p->u.ofdm.transmission_mode = c->transmission_mode; + p->u.ofdm.guard_interval = c->guard_interval; + p->u.ofdm.hierarchy_information = c->hierarchy; + break; + case DVBV3_ATSC: + dev_dbg(fe->dvb->device, "%s: Preparing VSB req\n", __func__); + p->u.vsb.modulation = c->modulation; + break; + } + return 0; +} + +/** + * dtv_get_frontend - calls a callback for retrieving DTV parameters + * @fe: struct dvb_frontend pointer + * @c: struct dtv_frontend_properties pointer (DVBv5 cache) + * @p_out struct dvb_frontend_parameters pointer (DVBv3 FE struct) + * + * This routine calls either the DVBv3 or DVBv5 get_frontend call. + * If c is not null, it will update the DVBv5 cache struct pointed by it. + * If p_out is not null, it will update the DVBv3 params pointed by it. + */ +static int dtv_get_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p_out) +{ + int r; + + if (fe->ops.get_frontend) { + r = fe->ops.get_frontend(fe); + if (unlikely(r < 0)) + return r; + if (p_out) + dtv_property_legacy_params_sync(fe, p_out); + return 0; + } + + /* As everything is in cache, get_frontend fops are always supported */ + return 0; +} + +static int dvb_frontend_ioctl_legacy(struct file *file, + unsigned int cmd, void *parg); +static int dvb_frontend_ioctl_properties(struct file *file, + unsigned int cmd, void *parg); + +static int dtv_property_process_get(struct dvb_frontend *fe, + const struct dtv_frontend_properties *c, + struct dtv_property *tvp, + struct file *file) +{ + int r, ncaps; + + switch(tvp->cmd) { + case DTV_ENUM_DELSYS: + ncaps = 0; + while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + tvp->u.buffer.data[ncaps] = fe->ops.delsys[ncaps]; + ncaps++; + } + tvp->u.buffer.len = ncaps; + break; + case DTV_FREQUENCY: + tvp->u.data = c->frequency; + break; + case DTV_MODULATION: + tvp->u.data = c->modulation; + break; + case DTV_BANDWIDTH_HZ: + tvp->u.data = c->bandwidth_hz; + break; + case DTV_INVERSION: + tvp->u.data = c->inversion; + break; + case DTV_SYMBOL_RATE: + tvp->u.data = c->symbol_rate; + break; + case DTV_INNER_FEC: + tvp->u.data = c->fec_inner; + break; + case DTV_PILOT: + tvp->u.data = c->pilot; + break; + case DTV_ROLLOFF: + tvp->u.data = c->rolloff; + break; + case DTV_DELIVERY_SYSTEM: + tvp->u.data = c->delivery_system; + break; + case DTV_VOLTAGE: + tvp->u.data = c->voltage; + break; + case DTV_TONE: + tvp->u.data = c->sectone; + break; + case DTV_API_VERSION: + tvp->u.data = (DVB_API_VERSION << 8) | DVB_API_VERSION_MINOR; + break; + case DTV_CODE_RATE_HP: + tvp->u.data = c->code_rate_HP; + break; + case DTV_CODE_RATE_LP: + tvp->u.data = c->code_rate_LP; + break; + case DTV_GUARD_INTERVAL: + tvp->u.data = c->guard_interval; + break; + case DTV_TRANSMISSION_MODE: + tvp->u.data = c->transmission_mode; + break; + case DTV_HIERARCHY: + tvp->u.data = c->hierarchy; + break; + case DTV_INTERLEAVING: + tvp->u.data = c->interleaving; + break; + + /* ISDB-T Support here */ + case DTV_ISDBT_PARTIAL_RECEPTION: + tvp->u.data = c->isdbt_partial_reception; + break; + case DTV_ISDBT_SOUND_BROADCASTING: + tvp->u.data = c->isdbt_sb_mode; + break; + case DTV_ISDBT_SB_SUBCHANNEL_ID: + tvp->u.data = c->isdbt_sb_subchannel; + break; + case DTV_ISDBT_SB_SEGMENT_IDX: + tvp->u.data = c->isdbt_sb_segment_idx; + break; + case DTV_ISDBT_SB_SEGMENT_COUNT: + tvp->u.data = c->isdbt_sb_segment_count; + break; + case DTV_ISDBT_LAYER_ENABLED: + tvp->u.data = c->isdbt_layer_enabled; + break; + case DTV_ISDBT_LAYERA_FEC: + tvp->u.data = c->layer[0].fec; + break; + case DTV_ISDBT_LAYERA_MODULATION: + tvp->u.data = c->layer[0].modulation; + break; + case DTV_ISDBT_LAYERA_SEGMENT_COUNT: + tvp->u.data = c->layer[0].segment_count; + break; + case DTV_ISDBT_LAYERA_TIME_INTERLEAVING: + tvp->u.data = c->layer[0].interleaving; + break; + case DTV_ISDBT_LAYERB_FEC: + tvp->u.data = c->layer[1].fec; + break; + case DTV_ISDBT_LAYERB_MODULATION: + tvp->u.data = c->layer[1].modulation; + break; + case DTV_ISDBT_LAYERB_SEGMENT_COUNT: + tvp->u.data = c->layer[1].segment_count; + break; + case DTV_ISDBT_LAYERB_TIME_INTERLEAVING: + tvp->u.data = c->layer[1].interleaving; + break; + case DTV_ISDBT_LAYERC_FEC: + tvp->u.data = c->layer[2].fec; + break; + case DTV_ISDBT_LAYERC_MODULATION: + tvp->u.data = c->layer[2].modulation; + break; + case DTV_ISDBT_LAYERC_SEGMENT_COUNT: + tvp->u.data = c->layer[2].segment_count; + break; + case DTV_ISDBT_LAYERC_TIME_INTERLEAVING: + tvp->u.data = c->layer[2].interleaving; + break; + + /* Multistream support */ + case DTV_STREAM_ID: + case DTV_DVBT2_PLP_ID_LEGACY: + tvp->u.data = c->stream_id; + break; + + /* ATSC-MH */ + case DTV_ATSCMH_FIC_VER: + tvp->u.data = fe->dtv_property_cache.atscmh_fic_ver; + break; + case DTV_ATSCMH_PARADE_ID: + tvp->u.data = fe->dtv_property_cache.atscmh_parade_id; + break; + case DTV_ATSCMH_NOG: + tvp->u.data = fe->dtv_property_cache.atscmh_nog; + break; + case DTV_ATSCMH_TNOG: + tvp->u.data = fe->dtv_property_cache.atscmh_tnog; + break; + case DTV_ATSCMH_SGN: + tvp->u.data = fe->dtv_property_cache.atscmh_sgn; + break; + case DTV_ATSCMH_PRC: + tvp->u.data = fe->dtv_property_cache.atscmh_prc; + break; + case DTV_ATSCMH_RS_FRAME_MODE: + tvp->u.data = fe->dtv_property_cache.atscmh_rs_frame_mode; + break; + case DTV_ATSCMH_RS_FRAME_ENSEMBLE: + tvp->u.data = fe->dtv_property_cache.atscmh_rs_frame_ensemble; + break; + case DTV_ATSCMH_RS_CODE_MODE_PRI: + tvp->u.data = fe->dtv_property_cache.atscmh_rs_code_mode_pri; + break; + case DTV_ATSCMH_RS_CODE_MODE_SEC: + tvp->u.data = fe->dtv_property_cache.atscmh_rs_code_mode_sec; + break; + case DTV_ATSCMH_SCCC_BLOCK_MODE: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_block_mode; + break; + case DTV_ATSCMH_SCCC_CODE_MODE_A: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_a; + break; + case DTV_ATSCMH_SCCC_CODE_MODE_B: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_b; + break; + case DTV_ATSCMH_SCCC_CODE_MODE_C: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_c; + break; + case DTV_ATSCMH_SCCC_CODE_MODE_D: + tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_d; + break; + + default: + return -EINVAL; + } + + /* Allow the frontend to override outgoing properties */ + if (fe->ops.get_property) { + r = fe->ops.get_property(fe, tvp); + if (r < 0) + return r; + } + + dtv_property_dump(fe, tvp); + + return 0; +} + +static int dtv_set_frontend(struct dvb_frontend *fe); + +static bool is_dvbv3_delsys(u32 delsys) +{ + bool status; + + status = (delsys == SYS_DVBT) || (delsys == SYS_DVBC_ANNEX_A) || + (delsys == SYS_DVBS) || (delsys == SYS_ATSC); + + return status; +} + +static int set_delivery_system(struct dvb_frontend *fe, u32 desired_system) +{ + int ncaps, i; + u32 delsys = SYS_UNDEFINED; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + enum dvbv3_emulation_type type; + + /* + * It was reported that some old DVBv5 applications were + * filling delivery_system with SYS_UNDEFINED. If this happens, + * assume that the application wants to use the first supported + * delivery system. + */ + if (c->delivery_system == SYS_UNDEFINED) + c->delivery_system = fe->ops.delsys[0]; + + if (desired_system == SYS_UNDEFINED) { + /* + * A DVBv3 call doesn't know what's the desired system. + * Also, DVBv3 applications don't know that ops.info->type + * could be changed, and they simply dies when it doesn't + * match. + * So, don't change the current delivery system, as it + * may be trying to do the wrong thing, like setting an + * ISDB-T frontend as DVB-T. Instead, find the closest + * DVBv3 system that matches the delivery system. + */ + if (is_dvbv3_delsys(c->delivery_system)) { + dev_dbg(fe->dvb->device, + "%s: Using delivery system to %d\n", + __func__, c->delivery_system); + return 0; + } + type = dvbv3_type(c->delivery_system); + switch (type) { + case DVBV3_QPSK: + desired_system = SYS_DVBS; + break; + case DVBV3_QAM: + desired_system = SYS_DVBC_ANNEX_A; + break; + case DVBV3_ATSC: + desired_system = SYS_ATSC; + break; + case DVBV3_OFDM: + desired_system = SYS_DVBT; + break; + default: + dev_dbg(fe->dvb->device, "%s: This frontend doesn't support DVBv3 calls\n", + __func__); + return -EINVAL; + } + /* + * Get a delivery system that is compatible with DVBv3 + * NOTE: in order for this to work with softwares like Kaffeine that + * uses a DVBv5 call for DVB-S2 and a DVBv3 call to go back to + * DVB-S, drivers that support both should put the SYS_DVBS entry + * before the SYS_DVBS2, otherwise it won't switch back to DVB-S. + * The real fix is that userspace applications should not use DVBv3 + * and not trust on calling FE_SET_FRONTEND to switch the delivery + * system. + */ + ncaps = 0; + while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + if (fe->ops.delsys[ncaps] == desired_system) { + delsys = desired_system; + break; + } + ncaps++; + } + if (delsys == SYS_UNDEFINED) { + dev_dbg(fe->dvb->device, "%s: Couldn't find a delivery system that matches %d\n", + __func__, desired_system); + } + } else { + /* + * This is a DVBv5 call. So, it likely knows the supported + * delivery systems. + */ + + /* Check if the desired delivery system is supported */ + ncaps = 0; + while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + if (fe->ops.delsys[ncaps] == desired_system) { + c->delivery_system = desired_system; + dev_dbg(fe->dvb->device, + "%s: Changing delivery system to %d\n", + __func__, desired_system); + return 0; + } + ncaps++; + } + type = dvbv3_type(desired_system); + + /* + * The delivery system is not supported. See if it can be + * emulated. + * The emulation only works if the desired system is one of the + * DVBv3 delivery systems + */ + if (!is_dvbv3_delsys(desired_system)) { + dev_dbg(fe->dvb->device, + "%s: can't use a DVBv3 FE_SET_FRONTEND call on this frontend\n", + __func__); + return -EINVAL; + } + + /* + * Get the last non-DVBv3 delivery system that has the same type + * of the desired system + */ + ncaps = 0; + while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { + if ((dvbv3_type(fe->ops.delsys[ncaps]) == type) && + !is_dvbv3_delsys(fe->ops.delsys[ncaps])) + delsys = fe->ops.delsys[ncaps]; + ncaps++; + } + /* There's nothing compatible with the desired delivery system */ + if (delsys == SYS_UNDEFINED) { + dev_dbg(fe->dvb->device, + "%s: Incompatible DVBv3 FE_SET_FRONTEND call for this frontend\n", + __func__); + return -EINVAL; + } + } + + c->delivery_system = delsys; + + /* + * The DVBv3 or DVBv5 call is requesting a different system. So, + * emulation is needed. + * + * Emulate newer delivery systems like ISDBT, DVBT and DTMB + * for older DVBv5 applications. The emulation will try to use + * the auto mode for most things, and will assume that the desired + * delivery system is the last one at the ops.delsys[] array + */ + dev_dbg(fe->dvb->device, + "%s: Using delivery system %d emulated as if it were a %d\n", + __func__, delsys, desired_system); + + /* + * For now, handles ISDB-T calls. More code may be needed here for the + * other emulated stuff + */ + if (type == DVBV3_OFDM) { + if (c->delivery_system == SYS_ISDBT) { + dev_dbg(fe->dvb->device, + "%s: Using defaults for SYS_ISDBT\n", + __func__); + + if (!c->bandwidth_hz) + c->bandwidth_hz = 6000000; + + c->isdbt_partial_reception = 0; + c->isdbt_sb_mode = 0; + c->isdbt_sb_subchannel = 0; + c->isdbt_sb_segment_idx = 0; + c->isdbt_sb_segment_count = 0; + c->isdbt_layer_enabled = 0; + for (i = 0; i < 3; i++) { + c->layer[i].fec = FEC_AUTO; + c->layer[i].modulation = QAM_AUTO; + c->layer[i].interleaving = 0; + c->layer[i].segment_count = 0; + } + } + } + dev_dbg(fe->dvb->device, "%s: change delivery system on cache to %d\n", + __func__, c->delivery_system); + + return 0; +} + +static int dtv_property_process_set(struct dvb_frontend *fe, + struct dtv_property *tvp, + struct file *file) +{ + int r = 0; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + /* Allow the frontend to validate incoming properties */ + if (fe->ops.set_property) { + r = fe->ops.set_property(fe, tvp); + if (r < 0) + return r; + } + + switch(tvp->cmd) { + case DTV_CLEAR: + /* + * Reset a cache of data specific to the frontend here. This does + * not effect hardware. + */ + dvb_frontend_clear_cache(fe); + break; + case DTV_TUNE: + /* interpret the cache of data, build either a traditional frontend + * tunerequest so we can pass validation in the FE_SET_FRONTEND + * ioctl. + */ + c->state = tvp->cmd; + dev_dbg(fe->dvb->device, "%s: Finalised property cache\n", + __func__); + + r = dtv_set_frontend(fe); + break; + case DTV_FREQUENCY: + c->frequency = tvp->u.data; + break; + case DTV_MODULATION: + c->modulation = tvp->u.data; + break; + case DTV_BANDWIDTH_HZ: + c->bandwidth_hz = tvp->u.data; + break; + case DTV_INVERSION: + c->inversion = tvp->u.data; + break; + case DTV_SYMBOL_RATE: + c->symbol_rate = tvp->u.data; + break; + case DTV_INNER_FEC: + c->fec_inner = tvp->u.data; + break; + case DTV_PILOT: + c->pilot = tvp->u.data; + break; + case DTV_ROLLOFF: + c->rolloff = tvp->u.data; + break; + case DTV_DELIVERY_SYSTEM: + r = set_delivery_system(fe, tvp->u.data); + break; + case DTV_VOLTAGE: + c->voltage = tvp->u.data; + r = dvb_frontend_ioctl_legacy(file, FE_SET_VOLTAGE, + (void *)c->voltage); + break; + case DTV_TONE: + c->sectone = tvp->u.data; + r = dvb_frontend_ioctl_legacy(file, FE_SET_TONE, + (void *)c->sectone); + break; + case DTV_CODE_RATE_HP: + c->code_rate_HP = tvp->u.data; + break; + case DTV_CODE_RATE_LP: + c->code_rate_LP = tvp->u.data; + break; + case DTV_GUARD_INTERVAL: + c->guard_interval = tvp->u.data; + break; + case DTV_TRANSMISSION_MODE: + c->transmission_mode = tvp->u.data; + break; + case DTV_HIERARCHY: + c->hierarchy = tvp->u.data; + break; + case DTV_INTERLEAVING: + c->interleaving = tvp->u.data; + break; + case DTV_LNA: + if (fe->ops.set_lna) + r = fe->ops.set_lna(fe, tvp->u.data); + break; + + /* ISDB-T Support here */ + case DTV_ISDBT_PARTIAL_RECEPTION: + c->isdbt_partial_reception = tvp->u.data; + break; + case DTV_ISDBT_SOUND_BROADCASTING: + c->isdbt_sb_mode = tvp->u.data; + break; + case DTV_ISDBT_SB_SUBCHANNEL_ID: + c->isdbt_sb_subchannel = tvp->u.data; + break; + case DTV_ISDBT_SB_SEGMENT_IDX: + c->isdbt_sb_segment_idx = tvp->u.data; + break; + case DTV_ISDBT_SB_SEGMENT_COUNT: + c->isdbt_sb_segment_count = tvp->u.data; + break; + case DTV_ISDBT_LAYER_ENABLED: + c->isdbt_layer_enabled = tvp->u.data; + break; + case DTV_ISDBT_LAYERA_FEC: + c->layer[0].fec = tvp->u.data; + break; + case DTV_ISDBT_LAYERA_MODULATION: + c->layer[0].modulation = tvp->u.data; + break; + case DTV_ISDBT_LAYERA_SEGMENT_COUNT: + c->layer[0].segment_count = tvp->u.data; + break; + case DTV_ISDBT_LAYERA_TIME_INTERLEAVING: + c->layer[0].interleaving = tvp->u.data; + break; + case DTV_ISDBT_LAYERB_FEC: + c->layer[1].fec = tvp->u.data; + break; + case DTV_ISDBT_LAYERB_MODULATION: + c->layer[1].modulation = tvp->u.data; + break; + case DTV_ISDBT_LAYERB_SEGMENT_COUNT: + c->layer[1].segment_count = tvp->u.data; + break; + case DTV_ISDBT_LAYERB_TIME_INTERLEAVING: + c->layer[1].interleaving = tvp->u.data; + break; + case DTV_ISDBT_LAYERC_FEC: + c->layer[2].fec = tvp->u.data; + break; + case DTV_ISDBT_LAYERC_MODULATION: + c->layer[2].modulation = tvp->u.data; + break; + case DTV_ISDBT_LAYERC_SEGMENT_COUNT: + c->layer[2].segment_count = tvp->u.data; + break; + case DTV_ISDBT_LAYERC_TIME_INTERLEAVING: + c->layer[2].interleaving = tvp->u.data; + break; + + /* Multistream support */ + case DTV_STREAM_ID: + case DTV_DVBT2_PLP_ID_LEGACY: + c->stream_id = tvp->u.data; + break; + + /* ATSC-MH */ + case DTV_ATSCMH_PARADE_ID: + fe->dtv_property_cache.atscmh_parade_id = tvp->u.data; + break; + case DTV_ATSCMH_RS_FRAME_ENSEMBLE: + fe->dtv_property_cache.atscmh_rs_frame_ensemble = tvp->u.data; + break; + + default: + return -EINVAL; + } + + return r; +} + +static int dvb_frontend_ioctl(struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int err = -ENOTTY; + + dev_dbg(fe->dvb->device, "%s: (%d)\n", __func__, _IOC_NR(cmd)); + if (fepriv->exit != DVB_FE_NO_EXIT) + return -ENODEV; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY && + (_IOC_DIR(cmd) != _IOC_READ || cmd == FE_GET_EVENT || + cmd == FE_DISEQC_RECV_SLAVE_REPLY)) + return -EPERM; + + if (down_interruptible (&fepriv->sem)) + return -ERESTARTSYS; + + if ((cmd == FE_SET_PROPERTY) || (cmd == FE_GET_PROPERTY)) + err = dvb_frontend_ioctl_properties(file, cmd, parg); + else { + c->state = DTV_UNDEFINED; + err = dvb_frontend_ioctl_legacy(file, cmd, parg); + } + + up(&fepriv->sem); + return err; +} + +static int dvb_frontend_ioctl_properties(struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int err = 0; + + struct dtv_properties *tvps = NULL; + struct dtv_property *tvp = NULL; + int i; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if(cmd == FE_SET_PROPERTY) { + tvps = (struct dtv_properties __user *)parg; + + dev_dbg(fe->dvb->device, "%s: properties.num = %d\n", __func__, tvps->num); + dev_dbg(fe->dvb->device, "%s: properties.props = %p\n", __func__, tvps->props); + + /* Put an arbitrary limit on the number of messages that can + * be sent at once */ + if ((tvps->num == 0) || (tvps->num > DTV_IOCTL_MAX_MSGS)) + return -EINVAL; + + tvp = kmalloc(tvps->num * sizeof(struct dtv_property), GFP_KERNEL); + if (!tvp) { + err = -ENOMEM; + goto out; + } + + if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + for (i = 0; i < tvps->num; i++) { + err = dtv_property_process_set(fe, tvp + i, file); + if (err < 0) + goto out; + (tvp + i)->result = err; + } + + if (c->state == DTV_TUNE) + dev_dbg(fe->dvb->device, "%s: Property cache is full, tuning\n", __func__); + + } else + if(cmd == FE_GET_PROPERTY) { + tvps = (struct dtv_properties __user *)parg; + + dev_dbg(fe->dvb->device, "%s: properties.num = %d\n", __func__, tvps->num); + dev_dbg(fe->dvb->device, "%s: properties.props = %p\n", __func__, tvps->props); + + /* Put an arbitrary limit on the number of messages that can + * be sent at once */ + if ((tvps->num == 0) || (tvps->num > DTV_IOCTL_MAX_MSGS)) + return -EINVAL; + + tvp = kmalloc(tvps->num * sizeof(struct dtv_property), GFP_KERNEL); + if (!tvp) { + err = -ENOMEM; + goto out; + } + + if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + /* + * Fills the cache out struct with the cache contents, plus + * the data retrieved from get_frontend, if the frontend + * is not idle. Otherwise, returns the cached content + */ + if (fepriv->state != FESTATE_IDLE) { + err = dtv_get_frontend(fe, NULL); + if (err < 0) + goto out; + } + for (i = 0; i < tvps->num; i++) { + err = dtv_property_process_get(fe, c, tvp + i, file); + if (err < 0) + goto out; + (tvp + i)->result = err; + } + + if (copy_to_user(tvps->props, tvp, tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + } else + err = -ENOTTY; + +out: + kfree(tvp); + return err; +} + +static int dtv_set_frontend(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_frontend_tune_settings fetunesettings; + u32 rolloff = 0; + + if (dvb_frontend_check_parameters(fe) < 0) + return -EINVAL; + + /* + * Initialize output parameters to match the values given by + * the user. FE_SET_FRONTEND triggers an initial frontend event + * with status = 0, which copies output parameters to userspace. + */ + dtv_property_legacy_params_sync(fe, &fepriv->parameters_out); + + /* + * Be sure that the bandwidth will be filled for all + * non-satellite systems, as tuners need to know what + * low pass/Nyquist half filter should be applied, in + * order to avoid inter-channel noise. + * + * ISDB-T and DVB-T/T2 already sets bandwidth. + * ATSC and DVB-C don't set, so, the core should fill it. + * + * On DVB-C Annex A and C, the bandwidth is a function of + * the roll-off and symbol rate. Annex B defines different + * roll-off factors depending on the modulation. Fortunately, + * Annex B is only used with 6MHz, so there's no need to + * calculate it. + * + * While not officially supported, a side effect of handling it at + * the cache level is that a program could retrieve the bandwidth + * via DTV_BANDWIDTH_HZ, which may be useful for test programs. + */ + switch (c->delivery_system) { + case SYS_ATSC: + case SYS_DVBC_ANNEX_B: + c->bandwidth_hz = 6000000; + break; + case SYS_DVBC_ANNEX_A: + rolloff = 115; + break; + case SYS_DVBC_ANNEX_C: + rolloff = 113; + break; + default: + break; + } + if (rolloff) + c->bandwidth_hz = (c->symbol_rate * rolloff) / 100; + + /* force auto frequency inversion if requested */ + if (dvb_force_auto_inversion) + c->inversion = INVERSION_AUTO; + + /* + * without hierarchical coding code_rate_LP is irrelevant, + * so we tolerate the otherwise invalid FEC_NONE setting + */ + if (c->hierarchy == HIERARCHY_NONE && c->code_rate_LP == FEC_NONE) + c->code_rate_LP = FEC_AUTO; + + /* get frontend-specific tuning settings */ + memset(&fetunesettings, 0, sizeof(struct dvb_frontend_tune_settings)); + if (fe->ops.get_tune_settings && (fe->ops.get_tune_settings(fe, &fetunesettings) == 0)) { + fepriv->min_delay = (fetunesettings.min_delay_ms * HZ) / 1000; + fepriv->max_drift = fetunesettings.max_drift; + fepriv->step_size = fetunesettings.step_size; + } else { + /* default values */ + switch (c->delivery_system) { + case SYS_DVBS: + case SYS_DVBS2: + case SYS_ISDBS: + case SYS_TURBO: + case SYS_DVBC_ANNEX_A: + case SYS_DVBC_ANNEX_C: + fepriv->min_delay = HZ / 20; + fepriv->step_size = c->symbol_rate / 16000; + fepriv->max_drift = c->symbol_rate / 2000; + break; + case SYS_DVBT: + case SYS_DVBT2: + case SYS_ISDBT: + case SYS_DTMB: + fepriv->min_delay = HZ / 20; + fepriv->step_size = fe->ops.info.frequency_stepsize * 2; + fepriv->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; + break; + default: + /* + * FIXME: This sounds wrong! if freqency_stepsize is + * defined by the frontend, why not use it??? + */ + fepriv->min_delay = HZ / 20; + fepriv->step_size = 0; /* no zigzag */ + fepriv->max_drift = 0; + break; + } + } + if (dvb_override_tune_delay > 0) + fepriv->min_delay = (dvb_override_tune_delay * HZ) / 1000; + + fepriv->state = FESTATE_RETUNE; + + /* Request the search algorithm to search */ + fepriv->algo_status |= DVBFE_ALGO_SEARCH_AGAIN; + + dvb_frontend_clear_events(fe); + dvb_frontend_add_event(fe, 0); + dvb_frontend_wakeup(fe); + fepriv->status = 0; + + return 0; +} + + +static int dvb_frontend_ioctl_legacy(struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int err = -ENOTTY; + + switch (cmd) { + case FE_GET_INFO: { + struct dvb_frontend_info* info = parg; + + memcpy(info, &fe->ops.info, sizeof(struct dvb_frontend_info)); + dvb_frontend_get_frequency_limits(fe, &info->frequency_min, &info->frequency_max); + + /* + * Associate the 4 delivery systems supported by DVBv3 + * API with their DVBv5 counterpart. For the other standards, + * use the closest type, assuming that it would hopefully + * work with a DVBv3 application. + * It should be noticed that, on multi-frontend devices with + * different types (terrestrial and cable, for example), + * a pure DVBv3 application won't be able to use all delivery + * systems. Yet, changing the DVBv5 cache to the other delivery + * system should be enough for making it work. + */ + switch (dvbv3_type(c->delivery_system)) { + case DVBV3_QPSK: + info->type = FE_QPSK; + break; + case DVBV3_ATSC: + info->type = FE_ATSC; + break; + case DVBV3_QAM: + info->type = FE_QAM; + break; + case DVBV3_OFDM: + info->type = FE_OFDM; + break; + default: + dev_err(fe->dvb->device, + "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", + __func__, c->delivery_system); + fe->ops.info.type = FE_OFDM; + } + dev_dbg(fe->dvb->device, "%s: current delivery system on cache: %d, V3 type: %d\n", + __func__, c->delivery_system, fe->ops.info.type); + + /* Force the CAN_INVERSION_AUTO bit on. If the frontend doesn't + * do it, it is done for it. */ + info->caps |= FE_CAN_INVERSION_AUTO; + err = 0; + break; + } + + case FE_READ_STATUS: { + fe_status_t* status = parg; + + /* if retune was requested but hasn't occurred yet, prevent + * that user get signal state from previous tuning */ + if (fepriv->state == FESTATE_RETUNE || + fepriv->state == FESTATE_ERROR) { + err=0; + *status = 0; + break; + } + + if (fe->ops.read_status) + err = fe->ops.read_status(fe, status); + break; + } + + case FE_READ_BER: + if (fe->ops.read_ber) { + if (fepriv->thread) + err = fe->ops.read_ber(fe, (__u32 *) parg); + else + err = -EAGAIN; + } + break; + + case FE_READ_SIGNAL_STRENGTH: + if (fe->ops.read_signal_strength) { + if (fepriv->thread) + err = fe->ops.read_signal_strength(fe, (__u16 *) parg); + else + err = -EAGAIN; + } + break; + + case FE_READ_SNR: + if (fe->ops.read_snr) { + if (fepriv->thread) + err = fe->ops.read_snr(fe, (__u16 *) parg); + else + err = -EAGAIN; + } + break; + + case FE_READ_UNCORRECTED_BLOCKS: + if (fe->ops.read_ucblocks) { + if (fepriv->thread) + err = fe->ops.read_ucblocks(fe, (__u32 *) parg); + else + err = -EAGAIN; + } + break; + + case FE_DISEQC_RESET_OVERLOAD: + if (fe->ops.diseqc_reset_overload) { + err = fe->ops.diseqc_reset_overload(fe); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISEQC_SEND_MASTER_CMD: + if (fe->ops.diseqc_send_master_cmd) { + err = fe->ops.diseqc_send_master_cmd(fe, (struct dvb_diseqc_master_cmd*) parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISEQC_SEND_BURST: + if (fe->ops.diseqc_send_burst) { + err = fe->ops.diseqc_send_burst(fe, (fe_sec_mini_cmd_t) parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_SET_TONE: + if (fe->ops.set_tone) { + err = fe->ops.set_tone(fe, (fe_sec_tone_mode_t) parg); + fepriv->tone = (fe_sec_tone_mode_t) parg; + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_SET_VOLTAGE: + if (fe->ops.set_voltage) { + err = fe->ops.set_voltage(fe, (fe_sec_voltage_t) parg); + fepriv->voltage = (fe_sec_voltage_t) parg; + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISHNETWORK_SEND_LEGACY_CMD: + if (fe->ops.dishnetwork_send_legacy_command) { + err = fe->ops.dishnetwork_send_legacy_command(fe, (unsigned long) parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } else if (fe->ops.set_voltage) { + /* + * NOTE: This is a fallback condition. Some frontends + * (stv0299 for instance) take longer than 8msec to + * respond to a set_voltage command. Those switches + * need custom routines to switch properly. For all + * other frontends, the following should work ok. + * Dish network legacy switches (as used by Dish500) + * are controlled by sending 9-bit command words + * spaced 8msec apart. + * the actual command word is switch/port dependent + * so it is up to the userspace application to send + * the right command. + * The command must always start with a '0' after + * initialization, so parg is 8 bits and does not + * include the initialization or start bit + */ + unsigned long swcmd = ((unsigned long) parg) << 1; + struct timeval nexttime; + struct timeval tv[10]; + int i; + u8 last = 1; + if (dvb_frontend_debug) + printk("%s switch command: 0x%04lx\n", __func__, swcmd); + do_gettimeofday(&nexttime); + if (dvb_frontend_debug) + memcpy(&tv[0], &nexttime, sizeof(struct timeval)); + /* before sending a command, initialize by sending + * a 32ms 18V to the switch + */ + fe->ops.set_voltage(fe, SEC_VOLTAGE_18); + dvb_frontend_sleep_until(&nexttime, 32000); + + for (i = 0; i < 9; i++) { + if (dvb_frontend_debug) + do_gettimeofday(&tv[i + 1]); + if ((swcmd & 0x01) != last) { + /* set voltage to (last ? 13V : 18V) */ + fe->ops.set_voltage(fe, (last) ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18); + last = (last) ? 0 : 1; + } + swcmd = swcmd >> 1; + if (i != 8) + dvb_frontend_sleep_until(&nexttime, 8000); + } + if (dvb_frontend_debug) { + printk("%s(%d): switch delay (should be 32k followed by all 8k\n", + __func__, fe->dvb->num); + for (i = 1; i < 10; i++) + printk("%d: %d\n", i, timeval_usec_diff(tv[i-1] , tv[i])); + } + err = 0; + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISEQC_RECV_SLAVE_REPLY: + if (fe->ops.diseqc_recv_slave_reply) + err = fe->ops.diseqc_recv_slave_reply(fe, (struct dvb_diseqc_slave_reply*) parg); + break; + + case FE_ENABLE_HIGH_LNB_VOLTAGE: + if (fe->ops.enable_high_lnb_voltage) + err = fe->ops.enable_high_lnb_voltage(fe, (long) parg); + break; + + case FE_SET_FRONTEND: + err = set_delivery_system(fe, SYS_UNDEFINED); + if (err) + break; + + err = dtv_property_cache_sync(fe, c, parg); + if (err) + break; + err = dtv_set_frontend(fe); + break; + case FE_GET_EVENT: + err = dvb_frontend_get_event (fe, parg, file->f_flags); + break; + + case FE_GET_FRONTEND: + err = dtv_get_frontend(fe, parg); + break; + + case FE_SET_FRONTEND_TUNE_MODE: + fepriv->tune_mode_flags = (unsigned long) parg; + err = 0; + break; + }; + + return err; +} + + +static unsigned int dvb_frontend_poll(struct file *file, struct poll_table_struct *wait) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + + dev_dbg_ratelimited(fe->dvb->device, "%s:\n", __func__); + + poll_wait (file, &fepriv->events.wait_queue, wait); + + if (fepriv->events.eventw != fepriv->events.eventr) + return (POLLIN | POLLRDNORM | POLLPRI); + + return 0; +} + +static int dvb_frontend_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_adapter *adapter = fe->dvb; + int ret; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + if (fepriv->exit == DVB_FE_DEVICE_REMOVED) + return -ENODEV; + + if (adapter->mfe_shared) { + mutex_lock (&adapter->mfe_lock); + + if (adapter->mfe_dvbdev == NULL) + adapter->mfe_dvbdev = dvbdev; + + else if (adapter->mfe_dvbdev != dvbdev) { + struct dvb_device + *mfedev = adapter->mfe_dvbdev; + struct dvb_frontend + *mfe = mfedev->priv; + struct dvb_frontend_private + *mfepriv = mfe->frontend_priv; + int mferetry = (dvb_mfe_wait_time << 1); + + mutex_unlock (&adapter->mfe_lock); + while (mferetry-- && (mfedev->users != -1 || + mfepriv->thread != NULL)) { + if(msleep_interruptible(500)) { + if(signal_pending(current)) + return -EINTR; + } + } + + mutex_lock (&adapter->mfe_lock); + if(adapter->mfe_dvbdev != dvbdev) { + mfedev = adapter->mfe_dvbdev; + mfe = mfedev->priv; + mfepriv = mfe->frontend_priv; + if (mfedev->users != -1 || + mfepriv->thread != NULL) { + mutex_unlock (&adapter->mfe_lock); + return -EBUSY; + } + adapter->mfe_dvbdev = dvbdev; + } + } + } + + if (dvbdev->users == -1 && fe->ops.ts_bus_ctrl) { + if ((ret = fe->ops.ts_bus_ctrl(fe, 1)) < 0) + goto err0; + + /* If we took control of the bus, we need to force + reinitialization. This is because many ts_bus_ctrl() + functions strobe the RESET pin on the demod, and if the + frontend thread already exists then the dvb_init() routine + won't get called (which is what usually does initial + register configuration). */ + fepriv->reinitialise = 1; + } + + if ((ret = dvb_generic_open (inode, file)) < 0) + goto err1; + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + /* normal tune mode when opened R/W */ + fepriv->tune_mode_flags &= ~FE_TUNE_MODE_ONESHOT; + fepriv->tone = -1; + fepriv->voltage = -1; + + ret = dvb_frontend_start (fe); + if (ret) + goto err2; + + /* empty event queue */ + fepriv->events.eventr = fepriv->events.eventw = 0; + } + + if (adapter->mfe_shared) + mutex_unlock (&adapter->mfe_lock); + return ret; + +err2: + dvb_generic_release(inode, file); +err1: + if (dvbdev->users == -1 && fe->ops.ts_bus_ctrl) + fe->ops.ts_bus_ctrl(fe, 0); +err0: + if (adapter->mfe_shared) + mutex_unlock (&adapter->mfe_lock); + return ret; +} + +static int dvb_frontend_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int ret; + + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + fepriv->release_jiffies = jiffies; + mb(); + } + + ret = dvb_generic_release (inode, file); + + if (dvbdev->users == -1) { + wake_up(&fepriv->wait_queue); + if (fepriv->exit != DVB_FE_NO_EXIT) { + fops_put(file->f_op); + file->f_op = NULL; + wake_up(&dvbdev->wait_queue); + } + if (fe->ops.ts_bus_ctrl) + fe->ops.ts_bus_ctrl(fe, 0); + } + + return ret; +} + +static const struct file_operations dvb_frontend_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dvb_generic_ioctl, + .poll = dvb_frontend_poll, + .open = dvb_frontend_open, + .release = dvb_frontend_release, + .llseek = noop_llseek, +}; + +int dvb_frontend_suspend(struct dvb_frontend *fe) +{ + int ret = 0; + + dev_dbg(fe->dvb->device, "%s: adap=%d fe=%d\n", __func__, fe->dvb->num, + fe->id); + + if (fe->ops.tuner_ops.sleep) + ret = fe->ops.tuner_ops.sleep(fe); + + if (fe->ops.sleep) + ret = fe->ops.sleep(fe); + + return ret; +} +EXPORT_SYMBOL(dvb_frontend_suspend); + +int dvb_frontend_resume(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int ret = 0; + + dev_dbg(fe->dvb->device, "%s: adap=%d fe=%d\n", __func__, fe->dvb->num, + fe->id); + + if (fe->ops.init) + ret = fe->ops.init(fe); + + if (fe->ops.tuner_ops.init) + ret = fe->ops.tuner_ops.init(fe); + + fepriv->state = FESTATE_RETUNE; + dvb_frontend_wakeup(fe); + + return ret; +} +EXPORT_SYMBOL(dvb_frontend_resume); + +int dvb_register_frontend(struct dvb_adapter* dvb, + struct dvb_frontend* fe) +{ + struct dvb_frontend_private *fepriv; + static const struct dvb_device dvbdev_template = { + .users = ~0, + .writers = 1, + .readers = (~0)-1, + .fops = &dvb_frontend_fops, + .kernel_ioctl = dvb_frontend_ioctl + }; + + dev_dbg(dvb->device, "%s:\n", __func__); + + if (mutex_lock_interruptible(&frontend_mutex)) + return -ERESTARTSYS; + + fe->frontend_priv = kzalloc(sizeof(struct dvb_frontend_private), GFP_KERNEL); + if (fe->frontend_priv == NULL) { + mutex_unlock(&frontend_mutex); + return -ENOMEM; + } + fepriv = fe->frontend_priv; + + sema_init(&fepriv->sem, 1); + init_waitqueue_head (&fepriv->wait_queue); + init_waitqueue_head (&fepriv->events.wait_queue); + mutex_init(&fepriv->events.mtx); + fe->dvb = dvb; + fepriv->inversion = INVERSION_OFF; + + dev_info(fe->dvb->device, + "DVB: registering adapter %i frontend %i (%s)...\n", + fe->dvb->num, fe->id, fe->ops.info.name); + + dvb_register_device (fe->dvb, &fepriv->dvbdev, &dvbdev_template, + fe, DVB_DEVICE_FRONTEND); + + /* + * Initialize the cache to the proper values according with the + * first supported delivery system (ops->delsys[0]) + */ + + fe->dtv_property_cache.delivery_system = fe->ops.delsys[0]; + dvb_frontend_clear_cache(fe); + + mutex_unlock(&frontend_mutex); + return 0; +} +EXPORT_SYMBOL(dvb_register_frontend); + +int dvb_unregister_frontend(struct dvb_frontend* fe) +{ + struct dvb_frontend_private *fepriv = fe->frontend_priv; + dev_dbg(fe->dvb->device, "%s:\n", __func__); + + mutex_lock(&frontend_mutex); + dvb_frontend_stop (fe); + mutex_unlock(&frontend_mutex); + + if (fepriv->dvbdev->users < -1) + wait_event(fepriv->dvbdev->wait_queue, + fepriv->dvbdev->users==-1); + + mutex_lock(&frontend_mutex); + dvb_unregister_device (fepriv->dvbdev); + + /* fe is invalid now */ + kfree(fepriv); + mutex_unlock(&frontend_mutex); + return 0; +} +EXPORT_SYMBOL(dvb_unregister_frontend); + +#ifdef CONFIG_MEDIA_ATTACH +void dvb_frontend_detach(struct dvb_frontend* fe) +{ + void *ptr; + + if (fe->ops.release_sec) { + fe->ops.release_sec(fe); + symbol_put_addr(fe->ops.release_sec); + } + if (fe->ops.tuner_ops.release) { + fe->ops.tuner_ops.release(fe); + symbol_put_addr(fe->ops.tuner_ops.release); + } + if (fe->ops.analog_ops.release) { + fe->ops.analog_ops.release(fe); + symbol_put_addr(fe->ops.analog_ops.release); + } + ptr = (void*)fe->ops.release; + if (ptr) { + fe->ops.release(fe); + symbol_put_addr(ptr); + } +} +#else +void dvb_frontend_detach(struct dvb_frontend* fe) +{ + if (fe->ops.release_sec) + fe->ops.release_sec(fe); + if (fe->ops.tuner_ops.release) + fe->ops.tuner_ops.release(fe); + if (fe->ops.analog_ops.release) + fe->ops.analog_ops.release(fe); + if (fe->ops.release) + fe->ops.release(fe); +} +#endif +EXPORT_SYMBOL(dvb_frontend_detach); diff --git a/drivers/media/dvb-core/dvb_frontend.h b/drivers/media/dvb-core/dvb_frontend.h new file mode 100644 index 000000000000..44a445cee74f --- /dev/null +++ b/drivers/media/dvb-core/dvb_frontend.h @@ -0,0 +1,425 @@ +/* + * dvb_frontend.h + * + * Copyright (C) 2001 convergence integrated media GmbH + * Copyright (C) 2004 convergence GmbH + * + * Written by Ralph Metzler + * Overhauled by Holger Waechtler + * Kernel I2C stuff by Michael Hunold + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser 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. + * + */ + +#ifndef _DVB_FRONTEND_H_ +#define _DVB_FRONTEND_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dvbdev.h" + +/* + * Maximum number of Delivery systems per frontend. It + * should be smaller or equal to 32 + */ +#define MAX_DELSYS 8 + +struct dvb_frontend_tune_settings { + int min_delay_ms; + int step_size; + int max_drift; +}; + +struct dvb_frontend; + +struct dvb_tuner_info { + char name[128]; + + u32 frequency_min; + u32 frequency_max; + u32 frequency_step; + + u32 bandwidth_min; + u32 bandwidth_max; + u32 bandwidth_step; +}; + +struct analog_parameters { + unsigned int frequency; + unsigned int mode; + unsigned int audmode; + u64 std; +}; + +enum dvbfe_modcod { + DVBFE_MODCOD_DUMMY_PLFRAME = 0, + DVBFE_MODCOD_QPSK_1_4, + DVBFE_MODCOD_QPSK_1_3, + DVBFE_MODCOD_QPSK_2_5, + DVBFE_MODCOD_QPSK_1_2, + DVBFE_MODCOD_QPSK_3_5, + DVBFE_MODCOD_QPSK_2_3, + DVBFE_MODCOD_QPSK_3_4, + DVBFE_MODCOD_QPSK_4_5, + DVBFE_MODCOD_QPSK_5_6, + DVBFE_MODCOD_QPSK_8_9, + DVBFE_MODCOD_QPSK_9_10, + DVBFE_MODCOD_8PSK_3_5, + DVBFE_MODCOD_8PSK_2_3, + DVBFE_MODCOD_8PSK_3_4, + DVBFE_MODCOD_8PSK_5_6, + DVBFE_MODCOD_8PSK_8_9, + DVBFE_MODCOD_8PSK_9_10, + DVBFE_MODCOD_16APSK_2_3, + DVBFE_MODCOD_16APSK_3_4, + DVBFE_MODCOD_16APSK_4_5, + DVBFE_MODCOD_16APSK_5_6, + DVBFE_MODCOD_16APSK_8_9, + DVBFE_MODCOD_16APSK_9_10, + DVBFE_MODCOD_32APSK_3_4, + DVBFE_MODCOD_32APSK_4_5, + DVBFE_MODCOD_32APSK_5_6, + DVBFE_MODCOD_32APSK_8_9, + DVBFE_MODCOD_32APSK_9_10, + DVBFE_MODCOD_RESERVED_1, + DVBFE_MODCOD_BPSK_1_3, + DVBFE_MODCOD_BPSK_1_4, + DVBFE_MODCOD_RESERVED_2 +}; + +enum tuner_param { + DVBFE_TUNER_FREQUENCY = (1 << 0), + DVBFE_TUNER_TUNERSTEP = (1 << 1), + DVBFE_TUNER_IFFREQ = (1 << 2), + DVBFE_TUNER_BANDWIDTH = (1 << 3), + DVBFE_TUNER_REFCLOCK = (1 << 4), + DVBFE_TUNER_IQSENSE = (1 << 5), + DVBFE_TUNER_DUMMY = (1 << 31) +}; + +/* + * ALGO_HW: (Hardware Algorithm) + * ---------------------------------------------------------------- + * Devices that support this algorithm do everything in hardware + * and no software support is needed to handle them. + * Requesting these devices to LOCK is the only thing required, + * device is supposed to do everything in the hardware. + * + * ALGO_SW: (Software Algorithm) + * ---------------------------------------------------------------- + * These are dumb devices, that require software to do everything + * + * ALGO_CUSTOM: (Customizable Agorithm) + * ---------------------------------------------------------------- + * Devices having this algorithm can be customized to have specific + * algorithms in the frontend driver, rather than simply doing a + * software zig-zag. In this case the zigzag maybe hardware assisted + * or it maybe completely done in hardware. In all cases, usage of + * this algorithm, in conjunction with the search and track + * callbacks, utilizes the driver specific algorithm. + * + * ALGO_RECOVERY: (Recovery Algorithm) + * ---------------------------------------------------------------- + * These devices have AUTO recovery capabilities from LOCK failure + */ +enum dvbfe_algo { + DVBFE_ALGO_HW = (1 << 0), + DVBFE_ALGO_SW = (1 << 1), + DVBFE_ALGO_CUSTOM = (1 << 2), + DVBFE_ALGO_RECOVERY = (1 << 31) +}; + +struct tuner_state { + u32 frequency; + u32 tunerstep; + u32 ifreq; + u32 bandwidth; + u32 iqsense; + u32 refclock; +}; + +/* + * search callback possible return status + * + * DVBFE_ALGO_SEARCH_SUCCESS + * The frontend search algorithm completed and returned successfully + * + * DVBFE_ALGO_SEARCH_ASLEEP + * The frontend search algorithm is sleeping + * + * DVBFE_ALGO_SEARCH_FAILED + * The frontend search for a signal failed + * + * DVBFE_ALGO_SEARCH_INVALID + * The frontend search algorith was probably supplied with invalid + * parameters and the search is an invalid one + * + * DVBFE_ALGO_SEARCH_ERROR + * The frontend search algorithm failed due to some error + * + * DVBFE_ALGO_SEARCH_AGAIN + * The frontend search algorithm was requested to search again + */ +enum dvbfe_search { + DVBFE_ALGO_SEARCH_SUCCESS = (1 << 0), + DVBFE_ALGO_SEARCH_ASLEEP = (1 << 1), + DVBFE_ALGO_SEARCH_FAILED = (1 << 2), + DVBFE_ALGO_SEARCH_INVALID = (1 << 3), + DVBFE_ALGO_SEARCH_AGAIN = (1 << 4), + DVBFE_ALGO_SEARCH_ERROR = (1 << 31), +}; + + +struct dvb_tuner_ops { + + struct dvb_tuner_info info; + + int (*release)(struct dvb_frontend *fe); + int (*init)(struct dvb_frontend *fe); + int (*sleep)(struct dvb_frontend *fe); + + /** This is for simple PLLs - set all parameters in one go. */ + int (*set_params)(struct dvb_frontend *fe); + int (*set_analog_params)(struct dvb_frontend *fe, struct analog_parameters *p); + + /** This is support for demods like the mt352 - fills out the supplied buffer with what to write. */ + int (*calc_regs)(struct dvb_frontend *fe, u8 *buf, int buf_len); + + /** This is to allow setting tuner-specific configs */ + int (*set_config)(struct dvb_frontend *fe, void *priv_cfg); + + int (*get_frequency)(struct dvb_frontend *fe, u32 *frequency); + int (*get_bandwidth)(struct dvb_frontend *fe, u32 *bandwidth); + int (*get_if_frequency)(struct dvb_frontend *fe, u32 *frequency); + +#define TUNER_STATUS_LOCKED 1 +#define TUNER_STATUS_STEREO 2 + int (*get_status)(struct dvb_frontend *fe, u32 *status); + int (*get_rf_strength)(struct dvb_frontend *fe, u16 *strength); + int (*get_afc)(struct dvb_frontend *fe, s32 *afc); + + /** These are provided separately from set_params in order to facilitate silicon + * tuners which require sophisticated tuning loops, controlling each parameter separately. */ + int (*set_frequency)(struct dvb_frontend *fe, u32 frequency); + int (*set_bandwidth)(struct dvb_frontend *fe, u32 bandwidth); + + /* + * These are provided separately from set_params in order to facilitate silicon + * tuners which require sophisticated tuning loops, controlling each parameter separately. + */ + int (*set_state)(struct dvb_frontend *fe, enum tuner_param param, struct tuner_state *state); + int (*get_state)(struct dvb_frontend *fe, enum tuner_param param, struct tuner_state *state); +}; + +struct analog_demod_info { + char *name; +}; + +struct analog_demod_ops { + + struct analog_demod_info info; + + void (*set_params)(struct dvb_frontend *fe, + struct analog_parameters *params); + int (*has_signal)(struct dvb_frontend *fe); + int (*get_afc)(struct dvb_frontend *fe); + void (*tuner_status)(struct dvb_frontend *fe); + void (*standby)(struct dvb_frontend *fe); + void (*release)(struct dvb_frontend *fe); + int (*i2c_gate_ctrl)(struct dvb_frontend *fe, int enable); + + /** This is to allow setting tuner-specific configuration */ + int (*set_config)(struct dvb_frontend *fe, void *priv_cfg); +}; + +struct dtv_frontend_properties; + +struct dvb_frontend_ops { + + struct dvb_frontend_info info; + + u8 delsys[MAX_DELSYS]; + + void (*release)(struct dvb_frontend* fe); + void (*release_sec)(struct dvb_frontend* fe); + + int (*init)(struct dvb_frontend* fe); + int (*sleep)(struct dvb_frontend* fe); + + int (*write)(struct dvb_frontend* fe, const u8 buf[], int len); + + /* if this is set, it overrides the default swzigzag */ + int (*tune)(struct dvb_frontend* fe, + bool re_tune, + unsigned int mode_flags, + unsigned int *delay, + fe_status_t *status); + /* get frontend tuning algorithm from the module */ + enum dvbfe_algo (*get_frontend_algo)(struct dvb_frontend *fe); + + /* these two are only used for the swzigzag code */ + int (*set_frontend)(struct dvb_frontend *fe); + int (*get_tune_settings)(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* settings); + + int (*get_frontend)(struct dvb_frontend *fe); + + int (*read_status)(struct dvb_frontend* fe, fe_status_t* status); + int (*read_ber)(struct dvb_frontend* fe, u32* ber); + int (*read_signal_strength)(struct dvb_frontend* fe, u16* strength); + int (*read_snr)(struct dvb_frontend* fe, u16* snr); + int (*read_ucblocks)(struct dvb_frontend* fe, u32* ucblocks); + + int (*diseqc_reset_overload)(struct dvb_frontend* fe); + int (*diseqc_send_master_cmd)(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd); + int (*diseqc_recv_slave_reply)(struct dvb_frontend* fe, struct dvb_diseqc_slave_reply* reply); + int (*diseqc_send_burst)(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd); + int (*set_tone)(struct dvb_frontend* fe, fe_sec_tone_mode_t tone); + int (*set_voltage)(struct dvb_frontend* fe, fe_sec_voltage_t voltage); + int (*enable_high_lnb_voltage)(struct dvb_frontend* fe, long arg); + int (*dishnetwork_send_legacy_command)(struct dvb_frontend* fe, unsigned long cmd); + int (*i2c_gate_ctrl)(struct dvb_frontend* fe, int enable); + int (*ts_bus_ctrl)(struct dvb_frontend* fe, int acquire); + int (*set_lna)(struct dvb_frontend *, int); + + /* These callbacks are for devices that implement their own + * tuning algorithms, rather than a simple swzigzag + */ + enum dvbfe_search (*search)(struct dvb_frontend *fe); + + struct dvb_tuner_ops tuner_ops; + struct analog_demod_ops analog_ops; + + int (*set_property)(struct dvb_frontend* fe, struct dtv_property* tvp); + int (*get_property)(struct dvb_frontend* fe, struct dtv_property* tvp); +}; + +#ifdef __DVB_CORE__ +#define MAX_EVENT 8 + +struct dvb_fe_events { + struct dvb_frontend_event events[MAX_EVENT]; + int eventw; + int eventr; + int overflow; + wait_queue_head_t wait_queue; + struct mutex mtx; +}; +#endif + +struct dtv_frontend_properties { + + /* Cache State */ + u32 state; + + u32 frequency; + fe_modulation_t modulation; + + fe_sec_voltage_t voltage; + fe_sec_tone_mode_t sectone; + fe_spectral_inversion_t inversion; + fe_code_rate_t fec_inner; + fe_transmit_mode_t transmission_mode; + u32 bandwidth_hz; /* 0 = AUTO */ + fe_guard_interval_t guard_interval; + fe_hierarchy_t hierarchy; + u32 symbol_rate; + fe_code_rate_t code_rate_HP; + fe_code_rate_t code_rate_LP; + + fe_pilot_t pilot; + fe_rolloff_t rolloff; + + fe_delivery_system_t delivery_system; + + enum fe_interleaving interleaving; + + /* ISDB-T specifics */ + u8 isdbt_partial_reception; + u8 isdbt_sb_mode; + u8 isdbt_sb_subchannel; + u32 isdbt_sb_segment_idx; + u32 isdbt_sb_segment_count; + u8 isdbt_layer_enabled; + struct { + u8 segment_count; + fe_code_rate_t fec; + fe_modulation_t modulation; + u8 interleaving; + } layer[3]; + + /* Multistream specifics */ + u32 stream_id; + + /* ATSC-MH specifics */ + u8 atscmh_fic_ver; + u8 atscmh_parade_id; + u8 atscmh_nog; + u8 atscmh_tnog; + u8 atscmh_sgn; + u8 atscmh_prc; + + u8 atscmh_rs_frame_mode; + u8 atscmh_rs_frame_ensemble; + u8 atscmh_rs_code_mode_pri; + u8 atscmh_rs_code_mode_sec; + u8 atscmh_sccc_block_mode; + u8 atscmh_sccc_code_mode_a; + u8 atscmh_sccc_code_mode_b; + u8 atscmh_sccc_code_mode_c; + u8 atscmh_sccc_code_mode_d; +}; + +struct dvb_frontend { + struct dvb_frontend_ops ops; + struct dvb_adapter *dvb; + void *demodulator_priv; + void *tuner_priv; + void *frontend_priv; + void *sec_priv; + void *analog_demod_priv; + struct dtv_frontend_properties dtv_property_cache; +#define DVB_FRONTEND_COMPONENT_TUNER 0 +#define DVB_FRONTEND_COMPONENT_DEMOD 1 + int (*callback)(void *adapter_priv, int component, int cmd, int arg); + int id; +}; + +extern int dvb_register_frontend(struct dvb_adapter *dvb, + struct dvb_frontend *fe); + +extern int dvb_unregister_frontend(struct dvb_frontend *fe); + +extern void dvb_frontend_detach(struct dvb_frontend *fe); + +extern void dvb_frontend_reinitialise(struct dvb_frontend *fe); +extern int dvb_frontend_suspend(struct dvb_frontend *fe); +extern int dvb_frontend_resume(struct dvb_frontend *fe); + +extern void dvb_frontend_sleep_until(struct timeval *waketime, u32 add_usec); +extern s32 timeval_usec_diff(struct timeval lasttime, struct timeval curtime); + +#endif diff --git a/drivers/media/dvb/dvb-core/dvb_math.c b/drivers/media/dvb-core/dvb_math.c similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_math.c rename to drivers/media/dvb-core/dvb_math.c diff --git a/drivers/media/dvb/dvb-core/dvb_math.h b/drivers/media/dvb-core/dvb_math.h similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_math.h rename to drivers/media/dvb-core/dvb_math.h diff --git a/drivers/media/dvb/dvb-core/dvb_net.c b/drivers/media/dvb-core/dvb_net.c similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_net.c rename to drivers/media/dvb-core/dvb_net.c diff --git a/drivers/media/dvb/dvb-core/dvb_net.h b/drivers/media/dvb-core/dvb_net.h similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_net.h rename to drivers/media/dvb-core/dvb_net.h diff --git a/drivers/media/dvb/dvb-core/dvb_ringbuffer.c b/drivers/media/dvb-core/dvb_ringbuffer.c similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_ringbuffer.c rename to drivers/media/dvb-core/dvb_ringbuffer.c diff --git a/drivers/media/dvb/dvb-core/dvb_ringbuffer.h b/drivers/media/dvb-core/dvb_ringbuffer.h similarity index 100% rename from drivers/media/dvb/dvb-core/dvb_ringbuffer.h rename to drivers/media/dvb-core/dvb_ringbuffer.h diff --git a/drivers/media/dvb-core/dvbdev.c b/drivers/media/dvb-core/dvbdev.c new file mode 100644 index 000000000000..d33101aaf0b5 --- /dev/null +++ b/drivers/media/dvb-core/dvbdev.c @@ -0,0 +1,507 @@ +/* + * dvbdev.c + * + * Copyright (C) 2000 Ralph Metzler + * & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dvbdev.h" + +static DEFINE_MUTEX(dvbdev_mutex); +static int dvbdev_debug; + +module_param(dvbdev_debug, int, 0644); +MODULE_PARM_DESC(dvbdev_debug, "Turn on/off device debugging (default:off)."); + +#define dprintk if (dvbdev_debug) printk + +static LIST_HEAD(dvb_adapter_list); +static DEFINE_MUTEX(dvbdev_register_lock); + +static const char * const dnames[] = { + "video", "audio", "sec", "frontend", "demux", "dvr", "ca", + "net", "osd" +}; + +#ifdef CONFIG_DVB_DYNAMIC_MINORS +#define MAX_DVB_MINORS 256 +#define DVB_MAX_IDS MAX_DVB_MINORS +#else +#define DVB_MAX_IDS 4 +#define nums2minor(num,type,id) ((num << 6) | (id << 4) | type) +#define MAX_DVB_MINORS (DVB_MAX_ADAPTERS*64) +#endif + +static struct class *dvb_class; + +static struct dvb_device *dvb_minors[MAX_DVB_MINORS]; +static DECLARE_RWSEM(minor_rwsem); + +static int dvb_device_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev; + + mutex_lock(&dvbdev_mutex); + down_read(&minor_rwsem); + dvbdev = dvb_minors[iminor(inode)]; + + if (dvbdev && dvbdev->fops) { + int err = 0; + const struct file_operations *old_fops; + + file->private_data = dvbdev; + old_fops = file->f_op; + file->f_op = fops_get(dvbdev->fops); + if (file->f_op == NULL) { + file->f_op = old_fops; + goto fail; + } + if(file->f_op->open) + err = file->f_op->open(inode,file); + if (err) { + fops_put(file->f_op); + file->f_op = fops_get(old_fops); + } + fops_put(old_fops); + up_read(&minor_rwsem); + mutex_unlock(&dvbdev_mutex); + return err; + } +fail: + up_read(&minor_rwsem); + mutex_unlock(&dvbdev_mutex); + return -ENODEV; +} + + +static const struct file_operations dvb_device_fops = +{ + .owner = THIS_MODULE, + .open = dvb_device_open, + .llseek = noop_llseek, +}; + +static struct cdev dvb_device_cdev; + +int dvb_generic_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if (!dvbdev->users) + return -EBUSY; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + if (!dvbdev->readers) + return -EBUSY; + dvbdev->readers--; + } else { + if (!dvbdev->writers) + return -EBUSY; + dvbdev->writers--; + } + + dvbdev->users--; + return 0; +} +EXPORT_SYMBOL(dvb_generic_open); + + +int dvb_generic_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + dvbdev->readers++; + } else { + dvbdev->writers++; + } + + dvbdev->users++; + return 0; +} +EXPORT_SYMBOL(dvb_generic_release); + + +long dvb_generic_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if (!dvbdev->kernel_ioctl) + return -EINVAL; + + return dvb_usercopy(file, cmd, arg, dvbdev->kernel_ioctl); +} +EXPORT_SYMBOL(dvb_generic_ioctl); + + +static int dvbdev_get_free_id (struct dvb_adapter *adap, int type) +{ + u32 id = 0; + + while (id < DVB_MAX_IDS) { + struct dvb_device *dev; + list_for_each_entry(dev, &adap->device_list, list_head) + if (dev->type == type && dev->id == id) + goto skip; + return id; +skip: + id++; + } + return -ENFILE; +} + + +int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, + const struct dvb_device *template, void *priv, int type) +{ + struct dvb_device *dvbdev; + struct file_operations *dvbdevfops; + struct device *clsdev; + int minor; + int id; + + mutex_lock(&dvbdev_register_lock); + + if ((id = dvbdev_get_free_id (adap, type)) < 0){ + mutex_unlock(&dvbdev_register_lock); + *pdvbdev = NULL; + printk(KERN_ERR "%s: couldn't find free device id\n", __func__); + return -ENFILE; + } + + *pdvbdev = dvbdev = kmalloc(sizeof(struct dvb_device), GFP_KERNEL); + + if (!dvbdev){ + mutex_unlock(&dvbdev_register_lock); + return -ENOMEM; + } + + dvbdevfops = kzalloc(sizeof(struct file_operations), GFP_KERNEL); + + if (!dvbdevfops){ + kfree (dvbdev); + mutex_unlock(&dvbdev_register_lock); + return -ENOMEM; + } + + memcpy(dvbdev, template, sizeof(struct dvb_device)); + dvbdev->type = type; + dvbdev->id = id; + dvbdev->adapter = adap; + dvbdev->priv = priv; + dvbdev->fops = dvbdevfops; + init_waitqueue_head (&dvbdev->wait_queue); + + memcpy(dvbdevfops, template->fops, sizeof(struct file_operations)); + dvbdevfops->owner = adap->module; + + list_add_tail (&dvbdev->list_head, &adap->device_list); + + down_write(&minor_rwsem); +#ifdef CONFIG_DVB_DYNAMIC_MINORS + for (minor = 0; minor < MAX_DVB_MINORS; minor++) + if (dvb_minors[minor] == NULL) + break; + + if (minor == MAX_DVB_MINORS) { + kfree(dvbdevfops); + kfree(dvbdev); + up_write(&minor_rwsem); + mutex_unlock(&dvbdev_register_lock); + return -EINVAL; + } +#else + minor = nums2minor(adap->num, type, id); +#endif + + dvbdev->minor = minor; + dvb_minors[minor] = dvbdev; + up_write(&minor_rwsem); + + mutex_unlock(&dvbdev_register_lock); + + clsdev = device_create(dvb_class, adap->device, + MKDEV(DVB_MAJOR, minor), + dvbdev, "dvb%d.%s%d", adap->num, dnames[type], id); + if (IS_ERR(clsdev)) { + printk(KERN_ERR "%s: failed to create device dvb%d.%s%d (%ld)\n", + __func__, adap->num, dnames[type], id, PTR_ERR(clsdev)); + return PTR_ERR(clsdev); + } + + dprintk(KERN_DEBUG "DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n", + adap->num, dnames[type], id, minor, minor); + + return 0; +} +EXPORT_SYMBOL(dvb_register_device); + + +void dvb_unregister_device(struct dvb_device *dvbdev) +{ + if (!dvbdev) + return; + + down_write(&minor_rwsem); + dvb_minors[dvbdev->minor] = NULL; + up_write(&minor_rwsem); + + device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor)); + + list_del (&dvbdev->list_head); + kfree (dvbdev->fops); + kfree (dvbdev); +} +EXPORT_SYMBOL(dvb_unregister_device); + +static int dvbdev_check_free_adapter_num(int num) +{ + struct list_head *entry; + list_for_each(entry, &dvb_adapter_list) { + struct dvb_adapter *adap; + adap = list_entry(entry, struct dvb_adapter, list_head); + if (adap->num == num) + return 0; + } + return 1; +} + +static int dvbdev_get_free_adapter_num (void) +{ + int num = 0; + + while (num < DVB_MAX_ADAPTERS) { + if (dvbdev_check_free_adapter_num(num)) + return num; + num++; + } + + return -ENFILE; +} + + +int dvb_register_adapter(struct dvb_adapter *adap, const char *name, + struct module *module, struct device *device, + short *adapter_nums) +{ + int i, num; + + mutex_lock(&dvbdev_register_lock); + + for (i = 0; i < DVB_MAX_ADAPTERS; ++i) { + num = adapter_nums[i]; + if (num >= 0 && num < DVB_MAX_ADAPTERS) { + /* use the one the driver asked for */ + if (dvbdev_check_free_adapter_num(num)) + break; + } else { + num = dvbdev_get_free_adapter_num(); + break; + } + num = -1; + } + + if (num < 0) { + mutex_unlock(&dvbdev_register_lock); + return -ENFILE; + } + + memset (adap, 0, sizeof(struct dvb_adapter)); + INIT_LIST_HEAD (&adap->device_list); + + printk(KERN_INFO "DVB: registering new adapter (%s)\n", name); + + adap->num = num; + adap->name = name; + adap->module = module; + adap->device = device; + adap->mfe_shared = 0; + adap->mfe_dvbdev = NULL; + mutex_init (&adap->mfe_lock); + + list_add_tail (&adap->list_head, &dvb_adapter_list); + + mutex_unlock(&dvbdev_register_lock); + + return num; +} +EXPORT_SYMBOL(dvb_register_adapter); + + +int dvb_unregister_adapter(struct dvb_adapter *adap) +{ + mutex_lock(&dvbdev_register_lock); + list_del (&adap->list_head); + mutex_unlock(&dvbdev_register_lock); + return 0; +} +EXPORT_SYMBOL(dvb_unregister_adapter); + +/* if the miracle happens and "generic_usercopy()" is included into + the kernel, then this can vanish. please don't make the mistake and + define this as video_usercopy(). this will introduce a dependecy + to the v4l "videodev.o" module, which is unnecessary for some + cards (ie. the budget dvb-cards don't need the v4l module...) */ +int dvb_usercopy(struct file *file, + unsigned int cmd, unsigned long arg, + int (*func)(struct file *file, + unsigned int cmd, void *arg)) +{ + char sbuf[128]; + void *mbuf = NULL; + void *parg = NULL; + int err = -EINVAL; + + /* Copy arguments into temp kernel buffer */ + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: + /* + * For this command, the pointer is actually an integer + * argument. + */ + parg = (void *) arg; + break; + case _IOC_READ: /* some v4l ioctls are marked wrong ... */ + case _IOC_WRITE: + case (_IOC_WRITE | _IOC_READ): + if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { + parg = sbuf; + } else { + /* too big to allocate from stack */ + mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL); + if (NULL == mbuf) + return -ENOMEM; + parg = mbuf; + } + + err = -EFAULT; + if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) + goto out; + break; + } + + /* call driver */ + mutex_lock(&dvbdev_mutex); + if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD) + err = -ENOTTY; + mutex_unlock(&dvbdev_mutex); + + if (err < 0) + goto out; + + /* Copy results into user buffer */ + switch (_IOC_DIR(cmd)) + { + case _IOC_READ: + case (_IOC_WRITE | _IOC_READ): + if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) + err = -EFAULT; + break; + } + +out: + kfree(mbuf); + return err; +} + +static int dvb_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct dvb_device *dvbdev = dev_get_drvdata(dev); + + add_uevent_var(env, "DVB_ADAPTER_NUM=%d", dvbdev->adapter->num); + add_uevent_var(env, "DVB_DEVICE_TYPE=%s", dnames[dvbdev->type]); + add_uevent_var(env, "DVB_DEVICE_NUM=%d", dvbdev->id); + return 0; +} + +static char *dvb_devnode(struct device *dev, umode_t *mode) +{ + struct dvb_device *dvbdev = dev_get_drvdata(dev); + + return kasprintf(GFP_KERNEL, "dvb/adapter%d/%s%d", + dvbdev->adapter->num, dnames[dvbdev->type], dvbdev->id); +} + + +static int __init init_dvbdev(void) +{ + int retval; + dev_t dev = MKDEV(DVB_MAJOR, 0); + + if ((retval = register_chrdev_region(dev, MAX_DVB_MINORS, "DVB")) != 0) { + printk(KERN_ERR "dvb-core: unable to get major %d\n", DVB_MAJOR); + return retval; + } + + cdev_init(&dvb_device_cdev, &dvb_device_fops); + if ((retval = cdev_add(&dvb_device_cdev, dev, MAX_DVB_MINORS)) != 0) { + printk(KERN_ERR "dvb-core: unable register character device\n"); + goto error; + } + + dvb_class = class_create(THIS_MODULE, "dvb"); + if (IS_ERR(dvb_class)) { + retval = PTR_ERR(dvb_class); + goto error; + } + dvb_class->dev_uevent = dvb_uevent; + dvb_class->devnode = dvb_devnode; + return 0; + +error: + cdev_del(&dvb_device_cdev); + unregister_chrdev_region(dev, MAX_DVB_MINORS); + return retval; +} + + +static void __exit exit_dvbdev(void) +{ + class_destroy(dvb_class); + cdev_del(&dvb_device_cdev); + unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS); +} + +subsys_initcall(init_dvbdev); +module_exit(exit_dvbdev); + +MODULE_DESCRIPTION("DVB Core Driver"); +MODULE_AUTHOR("Marcus Metzler, Ralph Metzler, Holger Waechtler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-core/dvbdev.h b/drivers/media/dvb-core/dvbdev.h new file mode 100644 index 000000000000..93a9470d3f0c --- /dev/null +++ b/drivers/media/dvb-core/dvbdev.h @@ -0,0 +1,146 @@ +/* + * dvbdev.h + * + * Copyright (C) 2000 Ralph Metzler & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Lesser Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser 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. + * + */ + +#ifndef _DVBDEV_H_ +#define _DVBDEV_H_ + +#include +#include +#include +#include + +#define DVB_MAJOR 212 + +#if defined(CONFIG_DVB_MAX_ADAPTERS) && CONFIG_DVB_MAX_ADAPTERS > 0 + #define DVB_MAX_ADAPTERS CONFIG_DVB_MAX_ADAPTERS +#else + #define DVB_MAX_ADAPTERS 8 +#endif + +#define DVB_UNSET (-1) + +#define DVB_DEVICE_VIDEO 0 +#define DVB_DEVICE_AUDIO 1 +#define DVB_DEVICE_SEC 2 +#define DVB_DEVICE_FRONTEND 3 +#define DVB_DEVICE_DEMUX 4 +#define DVB_DEVICE_DVR 5 +#define DVB_DEVICE_CA 6 +#define DVB_DEVICE_NET 7 +#define DVB_DEVICE_OSD 8 + +#define DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr) \ + static short adapter_nr[] = \ + {[0 ... (DVB_MAX_ADAPTERS - 1)] = DVB_UNSET }; \ + module_param_array(adapter_nr, short, NULL, 0444); \ + MODULE_PARM_DESC(adapter_nr, "DVB adapter numbers") + +struct dvb_frontend; + +struct dvb_adapter { + int num; + struct list_head list_head; + struct list_head device_list; + const char *name; + u8 proposed_mac [6]; + void* priv; + + struct device *device; + + struct module *module; + + int mfe_shared; /* indicates mutually exclusive frontends */ + struct dvb_device *mfe_dvbdev; /* frontend device in use */ + struct mutex mfe_lock; /* access lock for thread creation */ +}; + + +struct dvb_device { + struct list_head list_head; + const struct file_operations *fops; + struct dvb_adapter *adapter; + int type; + int minor; + u32 id; + + /* in theory, 'users' can vanish now, + but I don't want to change too much now... */ + int readers; + int writers; + int users; + + wait_queue_head_t wait_queue; + /* don't really need those !? -- FIXME: use video_usercopy */ + int (*kernel_ioctl)(struct file *file, unsigned int cmd, void *arg); + + void *priv; +}; + + +extern int dvb_register_adapter(struct dvb_adapter *adap, const char *name, + struct module *module, struct device *device, + short *adapter_nums); +extern int dvb_unregister_adapter (struct dvb_adapter *adap); + +extern int dvb_register_device (struct dvb_adapter *adap, + struct dvb_device **pdvbdev, + const struct dvb_device *template, + void *priv, + int type); + +extern void dvb_unregister_device (struct dvb_device *dvbdev); + +extern int dvb_generic_open (struct inode *inode, struct file *file); +extern int dvb_generic_release (struct inode *inode, struct file *file); +extern long dvb_generic_ioctl (struct file *file, + unsigned int cmd, unsigned long arg); + +/* we don't mess with video_usercopy() any more, +we simply define out own dvb_usercopy(), which will hopefully become +generic_usercopy() someday... */ + +extern int dvb_usercopy(struct file *file, unsigned int cmd, unsigned long arg, + int (*func)(struct file *file, unsigned int cmd, void *arg)); + +/** generic DVB attach function. */ +#ifdef CONFIG_MEDIA_ATTACH +#define dvb_attach(FUNCTION, ARGS...) ({ \ + void *__r = NULL; \ + typeof(&FUNCTION) __a = symbol_request(FUNCTION); \ + if (__a) { \ + __r = (void *) __a(ARGS); \ + if (__r == NULL) \ + symbol_put(FUNCTION); \ + } else { \ + printk(KERN_ERR "DVB: Unable to find symbol "#FUNCTION"()\n"); \ + } \ + __r; \ +}) + +#else +#define dvb_attach(FUNCTION, ARGS...) ({ \ + FUNCTION(ARGS); \ +}) + +#endif + +#endif /* #ifndef _DVBDEV_H_ */ diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig new file mode 100644 index 000000000000..5efec73a32d2 --- /dev/null +++ b/drivers/media/dvb-frontends/Kconfig @@ -0,0 +1,741 @@ +menu "Customise DVB Frontends" + visible if !MEDIA_SUBDRV_AUTOSELECT + +comment "Multistandard (satellite) frontends" + depends on DVB_CORE + +config DVB_STB0899 + tristate "STB0899 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S/S2/DSS Multistandard demodulator. Say Y when you want + to support this demodulator based frontends + +config DVB_STB6100 + tristate "STB6100 based tuners" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A Silicon tuner from ST used in conjunction with the STB0899 + demodulator. Say Y when you want to support this tuner. + +config DVB_STV090x + tristate "STV0900/STV0903(A/B) based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + DVB-S/S2/DSS Multistandard Professional/Broadcast demodulators. + Say Y when you want to support these frontends. + +config DVB_STV6110x + tristate "STV6110/(A) based tuners" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A Silicon tuner that supports DVB-S and DVB-S2 modes + +comment "Multistandard (cable + terrestrial) frontends" + depends on DVB_CORE + +config DVB_DRXK + tristate "Micronas DRXK based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Micronas DRX-K DVB-C/T demodulator. + + Say Y when you want to support this frontend. + +config DVB_TDA18271C2DD + tristate "NXP TDA18271C2 silicon tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + NXP TDA18271 silicon tuner. + + Say Y when you want to support this tuner. + +comment "DVB-S (satellite) frontends" + depends on DVB_CORE + +config DVB_CX24110 + tristate "Conexant CX24110 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_CX24123 + tristate "Conexant CX24123 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_MT312 + tristate "Zarlink VP310/MT312/ZL10313 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_ZL10036 + tristate "Zarlink ZL10036 silicon tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_ZL10039 + tristate "Zarlink ZL10039 silicon tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_S5H1420 + tristate "Samsung S5H1420 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_STV0288 + tristate "ST STV0288 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_STB6000 + tristate "ST STB6000 silicon tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S silicon tuner module. Say Y when you want to support this tuner. + +config DVB_STV0299 + tristate "ST STV0299 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_STV6110 + tristate "ST STV6110 silicon tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S silicon tuner module. Say Y when you want to support this tuner. + +config DVB_STV0900 + tristate "ST STV0900 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S/S2 demodulator. Say Y when you want to support this frontend. + +config DVB_TDA8083 + tristate "Philips TDA8083 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_TDA10086 + tristate "Philips TDA10086 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_TDA8261 + tristate "Philips TDA8261 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_VES1X93 + tristate "VLSI VES1893 or VES1993 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_TUNER_ITD1000 + tristate "Integrant ITD1000 Zero IF tuner for DVB-S/DSS" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_TUNER_CX24113 + tristate "Conexant CX24113/CX24128 tuner for DVB-S/DSS" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + + +config DVB_TDA826X + tristate "Philips TDA826X silicon tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S silicon tuner module. Say Y when you want to support this tuner. + +config DVB_TUA6100 + tristate "Infineon TUA6100 PLL" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S PLL chip. + +config DVB_CX24116 + tristate "Conexant CX24116 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S/S2 tuner module. Say Y when you want to support this frontend. + +config DVB_SI21XX + tristate "Silicon Labs SI21XX based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_DS3000 + tristate "Montage Tehnology DS3000 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S/S2 tuner module. Say Y when you want to support this frontend. + +config DVB_MB86A16 + tristate "Fujitsu MB86A16 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S/DSS Direct Conversion reveiver. + Say Y when you want to support this frontend. + +config DVB_TDA10071 + tristate "NXP TDA10071" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + +comment "DVB-T (terrestrial) frontends" + depends on DVB_CORE + +config DVB_SP8870 + tristate "Spase sp8870 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + + This driver needs external firmware. Please use the command + "/Documentation/dvb/get_dvb_firmware sp8870" to + download/extract it, and then copy it to /usr/lib/hotplug/firmware + or /lib/firmware (depending on configuration of firmware hotplug). + +config DVB_SP887X + tristate "Spase sp887x based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + + This driver needs external firmware. Please use the command + "/Documentation/dvb/get_dvb_firmware sp887x" to + download/extract it, and then copy it to /usr/lib/hotplug/firmware + or /lib/firmware (depending on configuration of firmware hotplug). + +config DVB_CX22700 + tristate "Conexant CX22700 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_CX22702 + tristate "Conexant cx22702 demodulator (OFDM)" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_S5H1432 + tristate "Samsung s5h1432 demodulator (OFDM)" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_DRXD + tristate "Micronas DRXD driver" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + + Note: this driver was based on vendor driver reference code (released + under the GPL) as opposed to the existing drx397xd driver, which + was written via reverse engineering. + +config DVB_L64781 + tristate "LSI L64781" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_TDA1004X + tristate "Philips TDA10045H/TDA10046H based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + + This driver needs external firmware. Please use the commands + "/Documentation/dvb/get_dvb_firmware tda10045", + "/Documentation/dvb/get_dvb_firmware tda10046" to + download/extract them, and then copy them to /usr/lib/hotplug/firmware + or /lib/firmware (depending on configuration of firmware hotplug). + +config DVB_NXT6000 + tristate "NxtWave Communications NXT6000 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_MT352 + tristate "Zarlink MT352 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_ZL10353 + tristate "Zarlink ZL10353 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_DIB3000MB + tristate "DiBcom 3000M-B" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Designed for mobile usage. Say Y when you want + to support this frontend. + +config DVB_DIB3000MC + tristate "DiBcom 3000P/M-C" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Designed for mobile usage. Say Y when you want + to support this frontend. + +config DVB_DIB7000M + tristate "DiBcom 7000MA/MB/PA/PB/MC" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Designed for mobile usage. Say Y when you want + to support this frontend. + +config DVB_DIB7000P + tristate "DiBcom 7000PC" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Designed for mobile usage. Say Y when you want + to support this frontend. + +config DVB_DIB9000 + tristate "DiBcom 9000" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Designed for mobile usage. Say Y when you want + to support this frontend. + +config DVB_TDA10048 + tristate "Philips TDA10048HN based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_AF9013 + tristate "Afatech AF9013 demodulator" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + +config DVB_EC100 + tristate "E3C EC100" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + +config DVB_HD29L2 + tristate "HDIC HD29L2" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + +config DVB_STV0367 + tristate "ST STV0367 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T/C tuner module. Say Y when you want to support this frontend. + +config DVB_CXD2820R + tristate "Sony CXD2820R" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + +config DVB_RTL2830 + tristate "Realtek RTL2830 DVB-T" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + +config DVB_RTL2832 + tristate "Realtek RTL2832 DVB-T" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + +comment "DVB-C (cable) frontends" + depends on DVB_CORE + +config DVB_VES1820 + tristate "VLSI VES1820 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-C tuner module. Say Y when you want to support this frontend. + +config DVB_TDA10021 + tristate "Philips TDA10021 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-C tuner module. Say Y when you want to support this frontend. + +config DVB_TDA10023 + tristate "Philips TDA10023 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-C tuner module. Say Y when you want to support this frontend. + +config DVB_STV0297 + tristate "ST STV0297 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-C tuner module. Say Y when you want to support this frontend. + +comment "ATSC (North American/Korean Terrestrial/Cable DTV) frontends" + depends on DVB_CORE + +config DVB_NXT200X + tristate "NxtWave Communications NXT2002/NXT2004 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want + to support this frontend. + + This driver needs external firmware. Please use the commands + "/Documentation/dvb/get_dvb_firmware nxt2002" and + "/Documentation/dvb/get_dvb_firmware nxt2004" to + download/extract them, and then copy them to /usr/lib/hotplug/firmware + or /lib/firmware (depending on configuration of firmware hotplug). + +config DVB_OR51211 + tristate "Oren OR51211 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB tuner module. Say Y when you want to support this frontend. + + This driver needs external firmware. Please use the command + "/Documentation/dvb/get_dvb_firmware or51211" to + download it, and then copy it to /usr/lib/hotplug/firmware + or /lib/firmware (depending on configuration of firmware hotplug). + +config DVB_OR51132 + tristate "Oren OR51132 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want + to support this frontend. + + This driver needs external firmware. Please use the commands + "/Documentation/dvb/get_dvb_firmware or51132_vsb" and/or + "/Documentation/dvb/get_dvb_firmware or51132_qam" to + download firmwares for 8VSB and QAM64/256, respectively. Copy them to + /usr/lib/hotplug/firmware or /lib/firmware (depending on + configuration of firmware hotplug). + +config DVB_BCM3510 + tristate "Broadcom BCM3510" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB/16VSB and QAM64/256 tuner module. Say Y when you want to + support this frontend. + +config DVB_LGDT330X + tristate "LG Electronics LGDT3302/LGDT3303 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want + to support this frontend. + +config DVB_LGDT3305 + tristate "LG Electronics LGDT3304 and LGDT3305 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want + to support this frontend. + +config DVB_LG2160 + tristate "LG Electronics LG216x based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC/MH demodulator module. Say Y when you want + to support this frontend. + +config DVB_S5H1409 + tristate "Samsung S5H1409 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want + to support this frontend. + +config DVB_AU8522 + depends on I2C + tristate + +config DVB_AU8522_DTV + tristate "Auvitek AU8522 based DTV demod" + depends on DVB_CORE && I2C + select DVB_AU8522 + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB, QAM64/256 & NTSC demodulator module. Say Y when + you want to enable DTV demodulation support for this frontend. + +config DVB_AU8522_V4L + tristate "Auvitek AU8522 based ATV demod" + depends on VIDEO_V4L2 && I2C + select DVB_AU8522 + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB, QAM64/256 & NTSC demodulator module. Say Y when + you want to enable ATV demodulation support for this frontend. + +config DVB_S5H1411 + tristate "Samsung S5H1411 based" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want + to support this frontend. + +comment "ISDB-T (terrestrial) frontends" + depends on DVB_CORE + +config DVB_S921 + tristate "Sharp S921 frontend" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + AN ISDB-T DQPSK, QPSK, 16QAM and 64QAM 1seg tuner module. + Say Y when you want to support this frontend. + +config DVB_DIB8000 + tristate "DiBcom 8000MB/MC" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A driver for DiBcom's DiB8000 ISDB-T/ISDB-Tsb demodulator. + Say Y when you want to support this frontend. + +config DVB_MB86A20S + tristate "Fujitsu mb86a20s" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator. + Say Y when you want to support this frontend. + +comment "Digital terrestrial only tuners/PLL" + depends on DVB_CORE + +config DVB_PLL + tristate "Generic I2C PLL based tuners" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + This module drives a number of tuners based on PLL chips with a + common I2C interface. Say Y when you want to support these tuners. + +config DVB_TUNER_DIB0070 + tristate "DiBcom DiB0070 silicon base-band tuner" + depends on I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A driver for the silicon baseband tuner DiB0070 from DiBcom. + This device is only used inside a SiP called together with a + demodulator for now. + +config DVB_TUNER_DIB0090 + tristate "DiBcom DiB0090 silicon base-band tuner" + depends on I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A driver for the silicon baseband tuner DiB0090 from DiBcom. + This device is only used inside a SiP called together with a + demodulator for now. + +comment "SEC control devices for DVB-S" + depends on DVB_CORE + +config DVB_LNBP21 + tristate "LNBP21/LNBH24 SEC controllers" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An SEC control chips. + +config DVB_LNBP22 + tristate "LNBP22 SEC controllers" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + LNB power supply and control voltage + regulator chip with step-up converter + and I2C interface. + Say Y when you want to support this chip. + +config DVB_ISL6405 + tristate "ISL6405 SEC controller" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An SEC control chip. + +config DVB_ISL6421 + tristate "ISL6421 SEC controller" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + An SEC control chip. + +config DVB_ISL6423 + tristate "ISL6423 SEC controller" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A SEC controller chip from Intersil + +config DVB_A8293 + tristate "Allegro A8293" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + +config DVB_LGS8GL5 + tristate "Silicon Legend LGS-8GL5 demodulator (OFDM)" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DMB-TH tuner module. Say Y when you want to support this frontend. + +config DVB_LGS8GXX + tristate "Legend Silicon LGS8913/LGS8GL5/LGS8GXX DMB-TH demodulator" + depends on DVB_CORE && I2C + select FW_LOADER + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DMB-TH tuner module. Say Y when you want to support this frontend. + +config DVB_ATBM8830 + tristate "AltoBeam ATBM8830/8831 DMB-TH demodulator" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DMB-TH tuner module. Say Y when you want to support this frontend. + +config DVB_TDA665x + tristate "TDA665x tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Support for tuner modules based on Philips TDA6650/TDA6651 chips. + Say Y when you want to support this chip. + + Currently supported tuners: + * Panasonic ENV57H12D5 (ET-50DT) + +config DVB_IX2505V + tristate "Sharp IX2505V silicon tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_IT913X_FE + tristate "it913x frontend and it9137 tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-T tuner module. + Say Y when you want to support this frontend. + +config DVB_M88RS2000 + tristate "M88RS2000 DVB-S demodulator and tuner" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + A DVB-S tuner module. + Say Y when you want to support this frontend. + +config DVB_AF9033 + tristate "Afatech AF9033 DVB-T demodulator" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + +comment "Tools to develop new frontends" + +config DVB_DUMMY_FE + tristate "Dummy frontend driver" + default n +endmenu diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile new file mode 100644 index 000000000000..7eb73bbd2e26 --- /dev/null +++ b/drivers/media/dvb-frontends/Makefile @@ -0,0 +1,105 @@ +# +# Makefile for the kernel DVB frontend device drivers. +# + +ccflags-y += -I$(srctree)/drivers/media/dvb-core/ +ccflags-y += -I$(srctree)/drivers/media/tuners/ + +stb0899-objs := stb0899_drv.o stb0899_algo.o +stv0900-objs := stv0900_core.o stv0900_sw.o +drxd-objs := drxd_firm.o drxd_hard.o +cxd2820r-objs := cxd2820r_core.o cxd2820r_c.o cxd2820r_t.o cxd2820r_t2.o +drxk-objs := drxk_hard.o + +obj-$(CONFIG_DVB_PLL) += dvb-pll.o +obj-$(CONFIG_DVB_STV0299) += stv0299.o +obj-$(CONFIG_DVB_STB0899) += stb0899.o +obj-$(CONFIG_DVB_STB6100) += stb6100.o +obj-$(CONFIG_DVB_SP8870) += sp8870.o +obj-$(CONFIG_DVB_CX22700) += cx22700.o +obj-$(CONFIG_DVB_S5H1432) += s5h1432.o +obj-$(CONFIG_DVB_CX24110) += cx24110.o +obj-$(CONFIG_DVB_TDA8083) += tda8083.o +obj-$(CONFIG_DVB_L64781) += l64781.o +obj-$(CONFIG_DVB_DIB3000MB) += dib3000mb.o +obj-$(CONFIG_DVB_DIB3000MC) += dib3000mc.o dibx000_common.o +obj-$(CONFIG_DVB_DIB7000M) += dib7000m.o dibx000_common.o +obj-$(CONFIG_DVB_DIB7000P) += dib7000p.o dibx000_common.o +obj-$(CONFIG_DVB_DIB8000) += dib8000.o dibx000_common.o +obj-$(CONFIG_DVB_DIB9000) += dib9000.o dibx000_common.o +obj-$(CONFIG_DVB_MT312) += mt312.o +obj-$(CONFIG_DVB_VES1820) += ves1820.o +obj-$(CONFIG_DVB_VES1X93) += ves1x93.o +obj-$(CONFIG_DVB_TDA1004X) += tda1004x.o +obj-$(CONFIG_DVB_SP887X) += sp887x.o +obj-$(CONFIG_DVB_NXT6000) += nxt6000.o +obj-$(CONFIG_DVB_MT352) += mt352.o +obj-$(CONFIG_DVB_ZL10036) += zl10036.o +obj-$(CONFIG_DVB_ZL10039) += zl10039.o +obj-$(CONFIG_DVB_ZL10353) += zl10353.o +obj-$(CONFIG_DVB_CX22702) += cx22702.o +obj-$(CONFIG_DVB_DRXD) += drxd.o +obj-$(CONFIG_DVB_TDA10021) += tda10021.o +obj-$(CONFIG_DVB_TDA10023) += tda10023.o +obj-$(CONFIG_DVB_STV0297) += stv0297.o +obj-$(CONFIG_DVB_NXT200X) += nxt200x.o +obj-$(CONFIG_DVB_OR51211) += or51211.o +obj-$(CONFIG_DVB_OR51132) += or51132.o +obj-$(CONFIG_DVB_BCM3510) += bcm3510.o +obj-$(CONFIG_DVB_S5H1420) += s5h1420.o +obj-$(CONFIG_DVB_LGDT330X) += lgdt330x.o +obj-$(CONFIG_DVB_LGDT3305) += lgdt3305.o +obj-$(CONFIG_DVB_LG2160) += lg2160.o +obj-$(CONFIG_DVB_CX24123) += cx24123.o +obj-$(CONFIG_DVB_LNBP21) += lnbp21.o +obj-$(CONFIG_DVB_LNBP22) += lnbp22.o +obj-$(CONFIG_DVB_ISL6405) += isl6405.o +obj-$(CONFIG_DVB_ISL6421) += isl6421.o +obj-$(CONFIG_DVB_TDA10086) += tda10086.o +obj-$(CONFIG_DVB_TDA826X) += tda826x.o +obj-$(CONFIG_DVB_TDA8261) += tda8261.o +obj-$(CONFIG_DVB_TUNER_DIB0070) += dib0070.o +obj-$(CONFIG_DVB_TUNER_DIB0090) += dib0090.o +obj-$(CONFIG_DVB_TUA6100) += tua6100.o +obj-$(CONFIG_DVB_S5H1409) += s5h1409.o +obj-$(CONFIG_DVB_TUNER_ITD1000) += itd1000.o +obj-$(CONFIG_DVB_AU8522) += au8522_common.o +obj-$(CONFIG_DVB_AU8522_DTV) += au8522_dig.o +obj-$(CONFIG_DVB_AU8522_V4L) += au8522_decoder.o +obj-$(CONFIG_DVB_TDA10048) += tda10048.o +obj-$(CONFIG_DVB_TUNER_CX24113) += cx24113.o +obj-$(CONFIG_DVB_S5H1411) += s5h1411.o +obj-$(CONFIG_DVB_LGS8GL5) += lgs8gl5.o +obj-$(CONFIG_DVB_TDA665x) += tda665x.o +obj-$(CONFIG_DVB_LGS8GXX) += lgs8gxx.o +obj-$(CONFIG_DVB_ATBM8830) += atbm8830.o +obj-$(CONFIG_DVB_DUMMY_FE) += dvb_dummy_fe.o +obj-$(CONFIG_DVB_AF9013) += af9013.o +obj-$(CONFIG_DVB_CX24116) += cx24116.o +obj-$(CONFIG_DVB_SI21XX) += si21xx.o +obj-$(CONFIG_DVB_STV0288) += stv0288.o +obj-$(CONFIG_DVB_STB6000) += stb6000.o +obj-$(CONFIG_DVB_S921) += s921.o +obj-$(CONFIG_DVB_STV6110) += stv6110.o +obj-$(CONFIG_DVB_STV0900) += stv0900.o +obj-$(CONFIG_DVB_STV090x) += stv090x.o +obj-$(CONFIG_DVB_STV6110x) += stv6110x.o +obj-$(CONFIG_DVB_ISL6423) += isl6423.o +obj-$(CONFIG_DVB_EC100) += ec100.o +obj-$(CONFIG_DVB_HD29L2) += hd29l2.o +obj-$(CONFIG_DVB_DS3000) += ds3000.o +obj-$(CONFIG_DVB_MB86A16) += mb86a16.o +obj-$(CONFIG_DVB_MB86A20S) += mb86a20s.o +obj-$(CONFIG_DVB_IX2505V) += ix2505v.o +obj-$(CONFIG_DVB_STV0367) += stv0367.o +obj-$(CONFIG_DVB_CXD2820R) += cxd2820r.o +obj-$(CONFIG_DVB_DRXK) += drxk.o +obj-$(CONFIG_DVB_TDA18271C2DD) += tda18271c2dd.o +obj-$(CONFIG_DVB_IT913X_FE) += it913x-fe.o +obj-$(CONFIG_DVB_A8293) += a8293.o +obj-$(CONFIG_DVB_TDA10071) += tda10071.o +obj-$(CONFIG_DVB_RTL2830) += rtl2830.o +obj-$(CONFIG_DVB_RTL2832) += rtl2832.o +obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o +obj-$(CONFIG_DVB_AF9033) += af9033.o + diff --git a/drivers/media/dvb/frontends/a8293.c b/drivers/media/dvb-frontends/a8293.c similarity index 100% rename from drivers/media/dvb/frontends/a8293.c rename to drivers/media/dvb-frontends/a8293.c diff --git a/drivers/media/dvb/frontends/a8293.h b/drivers/media/dvb-frontends/a8293.h similarity index 100% rename from drivers/media/dvb/frontends/a8293.h rename to drivers/media/dvb-frontends/a8293.h diff --git a/drivers/media/dvb-frontends/af9013.c b/drivers/media/dvb-frontends/af9013.c new file mode 100644 index 000000000000..e9f04a36577b --- /dev/null +++ b/drivers/media/dvb-frontends/af9013.c @@ -0,0 +1,1548 @@ +/* + * Afatech AF9013 demodulator driver + * + * Copyright (C) 2007 Antti Palosaari + * Copyright (C) 2011 Antti Palosaari + * + * Thanks to Afatech who kindly provided information. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "af9013_priv.h" + +struct af9013_state { + struct i2c_adapter *i2c; + struct dvb_frontend fe; + struct af9013_config config; + + /* tuner/demod RF and IF AGC limits used for signal strength calc */ + u8 signal_strength_en, rf_50, rf_80, if_50, if_80; + u16 signal_strength; + u32 ber; + u32 ucblocks; + u16 snr; + u32 bandwidth_hz; + fe_status_t fe_status; + unsigned long set_frontend_jiffies; + unsigned long read_status_jiffies; + bool first_tune; + bool i2c_gate_state; + unsigned int statistics_step:3; + struct delayed_work statistics_work; +}; + +/* write multiple registers */ +static int af9013_wr_regs_i2c(struct af9013_state *priv, u8 mbox, u16 reg, + const u8 *val, int len) +{ + int ret; + u8 buf[3+len]; + struct i2c_msg msg[1] = { + { + .addr = priv->config.i2c_addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + } + }; + + buf[0] = (reg >> 8) & 0xff; + buf[1] = (reg >> 0) & 0xff; + buf[2] = mbox; + memcpy(&buf[3], val, len); + + ret = i2c_transfer(priv->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c wr failed=%d reg=%04x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* read multiple registers */ +static int af9013_rd_regs_i2c(struct af9013_state *priv, u8 mbox, u16 reg, + u8 *val, int len) +{ + int ret; + u8 buf[3]; + struct i2c_msg msg[2] = { + { + .addr = priv->config.i2c_addr, + .flags = 0, + .len = 3, + .buf = buf, + }, { + .addr = priv->config.i2c_addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + } + }; + + buf[0] = (reg >> 8) & 0xff; + buf[1] = (reg >> 0) & 0xff; + buf[2] = mbox; + + ret = i2c_transfer(priv->i2c, msg, 2); + if (ret == 2) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c rd failed=%d reg=%04x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* write multiple registers */ +static int af9013_wr_regs(struct af9013_state *priv, u16 reg, const u8 *val, + int len) +{ + int ret, i; + u8 mbox = (0 << 7)|(0 << 6)|(1 << 1)|(1 << 0); + + if ((priv->config.ts_mode == AF9013_TS_USB) && + ((reg & 0xff00) != 0xff00) && ((reg & 0xff00) != 0xae00)) { + mbox |= ((len - 1) << 2); + ret = af9013_wr_regs_i2c(priv, mbox, reg, val, len); + } else { + for (i = 0; i < len; i++) { + ret = af9013_wr_regs_i2c(priv, mbox, reg+i, val+i, 1); + if (ret) + goto err; + } + } + +err: + return 0; +} + +/* read multiple registers */ +static int af9013_rd_regs(struct af9013_state *priv, u16 reg, u8 *val, int len) +{ + int ret, i; + u8 mbox = (0 << 7)|(0 << 6)|(1 << 1)|(0 << 0); + + if ((priv->config.ts_mode == AF9013_TS_USB) && + ((reg & 0xff00) != 0xff00) && ((reg & 0xff00) != 0xae00)) { + mbox |= ((len - 1) << 2); + ret = af9013_rd_regs_i2c(priv, mbox, reg, val, len); + } else { + for (i = 0; i < len; i++) { + ret = af9013_rd_regs_i2c(priv, mbox, reg+i, val+i, 1); + if (ret) + goto err; + } + } + +err: + return 0; +} + +/* write single register */ +static int af9013_wr_reg(struct af9013_state *priv, u16 reg, u8 val) +{ + return af9013_wr_regs(priv, reg, &val, 1); +} + +/* read single register */ +static int af9013_rd_reg(struct af9013_state *priv, u16 reg, u8 *val) +{ + return af9013_rd_regs(priv, reg, val, 1); +} + +static int af9013_write_ofsm_regs(struct af9013_state *state, u16 reg, u8 *val, + u8 len) +{ + u8 mbox = (1 << 7)|(1 << 6)|((len - 1) << 2)|(1 << 1)|(1 << 0); + return af9013_wr_regs_i2c(state, mbox, reg, val, len); +} + +static int af9013_wr_reg_bits(struct af9013_state *state, u16 reg, int pos, + int len, u8 val) +{ + int ret; + u8 tmp, mask; + + /* no need for read if whole reg is written */ + if (len != 8) { + ret = af9013_rd_reg(state, reg, &tmp); + if (ret) + return ret; + + mask = (0xff >> (8 - len)) << pos; + val <<= pos; + tmp &= ~mask; + val |= tmp; + } + + return af9013_wr_reg(state, reg, val); +} + +static int af9013_rd_reg_bits(struct af9013_state *state, u16 reg, int pos, + int len, u8 *val) +{ + int ret; + u8 tmp; + + ret = af9013_rd_reg(state, reg, &tmp); + if (ret) + return ret; + + *val = (tmp >> pos); + *val &= (0xff >> (8 - len)); + + return 0; +} + +static int af9013_set_gpio(struct af9013_state *state, u8 gpio, u8 gpioval) +{ + int ret; + u8 pos; + u16 addr; + + dev_dbg(&state->i2c->dev, "%s: gpio=%d gpioval=%02x\n", + __func__, gpio, gpioval); + + /* + * GPIO0 & GPIO1 0xd735 + * GPIO2 & GPIO3 0xd736 + */ + + switch (gpio) { + case 0: + case 1: + addr = 0xd735; + break; + case 2: + case 3: + addr = 0xd736; + break; + + default: + dev_err(&state->i2c->dev, "%s: invalid gpio=%d\n", + KBUILD_MODNAME, gpio); + ret = -EINVAL; + goto err; + }; + + switch (gpio) { + case 0: + case 2: + pos = 0; + break; + case 1: + case 3: + default: + pos = 4; + break; + }; + + ret = af9013_wr_reg_bits(state, addr, pos, 4, gpioval); + if (ret) + goto err; + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static u32 af9013_div(struct af9013_state *state, u32 a, u32 b, u32 x) +{ + u32 r = 0, c = 0, i; + + dev_dbg(&state->i2c->dev, "%s: a=%d b=%d x=%d\n", __func__, a, b, x); + + if (a > b) { + c = a / b; + a = a - c * b; + } + + for (i = 0; i < x; i++) { + if (a >= b) { + r += 1; + a -= b; + } + a <<= 1; + r <<= 1; + } + r = (c << (u32)x) + r; + + dev_dbg(&state->i2c->dev, "%s: a=%d b=%d x=%d r=%d r=%x\n", + __func__, a, b, x, r, r); + + return r; +} + +static int af9013_power_ctrl(struct af9013_state *state, u8 onoff) +{ + int ret, i; + u8 tmp; + + dev_dbg(&state->i2c->dev, "%s: onoff=%d\n", __func__, onoff); + + /* enable reset */ + ret = af9013_wr_reg_bits(state, 0xd417, 4, 1, 1); + if (ret) + goto err; + + /* start reset mechanism */ + ret = af9013_wr_reg(state, 0xaeff, 1); + if (ret) + goto err; + + /* wait reset performs */ + for (i = 0; i < 150; i++) { + ret = af9013_rd_reg_bits(state, 0xd417, 1, 1, &tmp); + if (ret) + goto err; + + if (tmp) + break; /* reset done */ + + usleep_range(5000, 25000); + } + + if (!tmp) + return -ETIMEDOUT; + + if (onoff) { + /* clear reset */ + ret = af9013_wr_reg_bits(state, 0xd417, 1, 1, 0); + if (ret) + goto err; + + /* disable reset */ + ret = af9013_wr_reg_bits(state, 0xd417, 4, 1, 0); + + /* power on */ + ret = af9013_wr_reg_bits(state, 0xd73a, 3, 1, 0); + } else { + /* power off */ + ret = af9013_wr_reg_bits(state, 0xd73a, 3, 1, 1); + } + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_statistics_ber_unc_start(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + + dev_dbg(&state->i2c->dev, "%s:\n", __func__); + + /* reset and start BER counter */ + ret = af9013_wr_reg_bits(state, 0xd391, 4, 1, 1); + if (ret) + goto err; + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_statistics_ber_unc_result(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + u8 buf[5]; + + dev_dbg(&state->i2c->dev, "%s:\n", __func__); + + /* check if error bit count is ready */ + ret = af9013_rd_reg_bits(state, 0xd391, 4, 1, &buf[0]); + if (ret) + goto err; + + if (!buf[0]) { + dev_dbg(&state->i2c->dev, "%s: not ready\n", __func__); + return 0; + } + + ret = af9013_rd_regs(state, 0xd387, buf, 5); + if (ret) + goto err; + + state->ber = (buf[2] << 16) | (buf[1] << 8) | buf[0]; + state->ucblocks += (buf[4] << 8) | buf[3]; + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_statistics_snr_start(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + + dev_dbg(&state->i2c->dev, "%s:\n", __func__); + + /* start SNR meas */ + ret = af9013_wr_reg_bits(state, 0xd2e1, 3, 1, 1); + if (ret) + goto err; + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_statistics_snr_result(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret, i, len; + u8 buf[3], tmp; + u32 snr_val; + const struct af9013_snr *uninitialized_var(snr_lut); + + dev_dbg(&state->i2c->dev, "%s:\n", __func__); + + /* check if SNR ready */ + ret = af9013_rd_reg_bits(state, 0xd2e1, 3, 1, &tmp); + if (ret) + goto err; + + if (!tmp) { + dev_dbg(&state->i2c->dev, "%s: not ready\n", __func__); + return 0; + } + + /* read value */ + ret = af9013_rd_regs(state, 0xd2e3, buf, 3); + if (ret) + goto err; + + snr_val = (buf[2] << 16) | (buf[1] << 8) | buf[0]; + + /* read current modulation */ + ret = af9013_rd_reg(state, 0xd3c1, &tmp); + if (ret) + goto err; + + switch ((tmp >> 6) & 3) { + case 0: + len = ARRAY_SIZE(qpsk_snr_lut); + snr_lut = qpsk_snr_lut; + break; + case 1: + len = ARRAY_SIZE(qam16_snr_lut); + snr_lut = qam16_snr_lut; + break; + case 2: + len = ARRAY_SIZE(qam64_snr_lut); + snr_lut = qam64_snr_lut; + break; + default: + goto err; + break; + } + + for (i = 0; i < len; i++) { + tmp = snr_lut[i].snr; + + if (snr_val < snr_lut[i].val) + break; + } + state->snr = tmp * 10; /* dB/10 */ + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_statistics_signal_strength(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret = 0; + u8 buf[2], rf_gain, if_gain; + int signal_strength; + + dev_dbg(&state->i2c->dev, "%s:\n", __func__); + + if (!state->signal_strength_en) + return 0; + + ret = af9013_rd_regs(state, 0xd07c, buf, 2); + if (ret) + goto err; + + rf_gain = buf[0]; + if_gain = buf[1]; + + signal_strength = (0xffff / \ + (9 * (state->rf_50 + state->if_50) - \ + 11 * (state->rf_80 + state->if_80))) * \ + (10 * (rf_gain + if_gain) - \ + 11 * (state->rf_80 + state->if_80)); + if (signal_strength < 0) + signal_strength = 0; + else if (signal_strength > 0xffff) + signal_strength = 0xffff; + + state->signal_strength = signal_strength; + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static void af9013_statistics_work(struct work_struct *work) +{ + struct af9013_state *state = container_of(work, + struct af9013_state, statistics_work.work); + unsigned int next_msec; + + /* update only signal strength when demod is not locked */ + if (!(state->fe_status & FE_HAS_LOCK)) { + state->statistics_step = 0; + state->ber = 0; + state->snr = 0; + } + + switch (state->statistics_step) { + default: + state->statistics_step = 0; + case 0: + af9013_statistics_signal_strength(&state->fe); + state->statistics_step++; + next_msec = 300; + break; + case 1: + af9013_statistics_snr_start(&state->fe); + state->statistics_step++; + next_msec = 200; + break; + case 2: + af9013_statistics_ber_unc_start(&state->fe); + state->statistics_step++; + next_msec = 1000; + break; + case 3: + af9013_statistics_snr_result(&state->fe); + state->statistics_step++; + next_msec = 400; + break; + case 4: + af9013_statistics_ber_unc_result(&state->fe); + state->statistics_step++; + next_msec = 100; + break; + } + + schedule_delayed_work(&state->statistics_work, + msecs_to_jiffies(next_msec)); +} + +static int af9013_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *fesettings) +{ + fesettings->min_delay_ms = 800; + fesettings->step_size = 0; + fesettings->max_drift = 0; + + return 0; +} + +static int af9013_set_frontend(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i, sampling_freq; + bool auto_mode, spec_inv; + u8 buf[6]; + u32 if_frequency, freq_cw; + + dev_dbg(&state->i2c->dev, "%s: frequency=%d bandwidth_hz=%d\n", + __func__, c->frequency, c->bandwidth_hz); + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + /* program CFOE coefficients */ + if (c->bandwidth_hz != state->bandwidth_hz) { + for (i = 0; i < ARRAY_SIZE(coeff_lut); i++) { + if (coeff_lut[i].clock == state->config.clock && + coeff_lut[i].bandwidth_hz == c->bandwidth_hz) { + break; + } + } + + ret = af9013_wr_regs(state, 0xae00, coeff_lut[i].val, + sizeof(coeff_lut[i].val)); + } + + /* program frequency control */ + if (c->bandwidth_hz != state->bandwidth_hz || state->first_tune) { + /* get used IF frequency */ + if (fe->ops.tuner_ops.get_if_frequency) + fe->ops.tuner_ops.get_if_frequency(fe, &if_frequency); + else + if_frequency = state->config.if_frequency; + + dev_dbg(&state->i2c->dev, "%s: if_frequency=%d\n", + __func__, if_frequency); + + sampling_freq = if_frequency; + + while (sampling_freq > (state->config.clock / 2)) + sampling_freq -= state->config.clock; + + if (sampling_freq < 0) { + sampling_freq *= -1; + spec_inv = state->config.spec_inv; + } else { + spec_inv = !state->config.spec_inv; + } + + freq_cw = af9013_div(state, sampling_freq, state->config.clock, + 23); + + if (spec_inv) + freq_cw = 0x800000 - freq_cw; + + buf[0] = (freq_cw >> 0) & 0xff; + buf[1] = (freq_cw >> 8) & 0xff; + buf[2] = (freq_cw >> 16) & 0x7f; + + freq_cw = 0x800000 - freq_cw; + + buf[3] = (freq_cw >> 0) & 0xff; + buf[4] = (freq_cw >> 8) & 0xff; + buf[5] = (freq_cw >> 16) & 0x7f; + + ret = af9013_wr_regs(state, 0xd140, buf, 3); + if (ret) + goto err; + + ret = af9013_wr_regs(state, 0x9be7, buf, 6); + if (ret) + goto err; + } + + /* clear TPS lock flag */ + ret = af9013_wr_reg_bits(state, 0xd330, 3, 1, 1); + if (ret) + goto err; + + /* clear MPEG2 lock flag */ + ret = af9013_wr_reg_bits(state, 0xd507, 6, 1, 0); + if (ret) + goto err; + + /* empty channel function */ + ret = af9013_wr_reg_bits(state, 0x9bfe, 0, 1, 0); + if (ret) + goto err; + + /* empty DVB-T channel function */ + ret = af9013_wr_reg_bits(state, 0x9bc2, 0, 1, 0); + if (ret) + goto err; + + /* transmission parameters */ + auto_mode = false; + memset(buf, 0, 3); + + switch (c->transmission_mode) { + case TRANSMISSION_MODE_AUTO: + auto_mode = 1; + break; + case TRANSMISSION_MODE_2K: + break; + case TRANSMISSION_MODE_8K: + buf[0] |= (1 << 0); + break; + default: + dev_dbg(&state->i2c->dev, "%s: invalid transmission_mode\n", + __func__); + auto_mode = 1; + } + + switch (c->guard_interval) { + case GUARD_INTERVAL_AUTO: + auto_mode = 1; + break; + case GUARD_INTERVAL_1_32: + break; + case GUARD_INTERVAL_1_16: + buf[0] |= (1 << 2); + break; + case GUARD_INTERVAL_1_8: + buf[0] |= (2 << 2); + break; + case GUARD_INTERVAL_1_4: + buf[0] |= (3 << 2); + break; + default: + dev_dbg(&state->i2c->dev, "%s: invalid guard_interval\n", + __func__); + auto_mode = 1; + } + + switch (c->hierarchy) { + case HIERARCHY_AUTO: + auto_mode = 1; + break; + case HIERARCHY_NONE: + break; + case HIERARCHY_1: + buf[0] |= (1 << 4); + break; + case HIERARCHY_2: + buf[0] |= (2 << 4); + break; + case HIERARCHY_4: + buf[0] |= (3 << 4); + break; + default: + dev_dbg(&state->i2c->dev, "%s: invalid hierarchy\n", __func__); + auto_mode = 1; + }; + + switch (c->modulation) { + case QAM_AUTO: + auto_mode = 1; + break; + case QPSK: + break; + case QAM_16: + buf[1] |= (1 << 6); + break; + case QAM_64: + buf[1] |= (2 << 6); + break; + default: + dev_dbg(&state->i2c->dev, "%s: invalid modulation\n", __func__); + auto_mode = 1; + } + + /* Use HP. How and which case we can switch to LP? */ + buf[1] |= (1 << 4); + + switch (c->code_rate_HP) { + case FEC_AUTO: + auto_mode = 1; + break; + case FEC_1_2: + break; + case FEC_2_3: + buf[2] |= (1 << 0); + break; + case FEC_3_4: + buf[2] |= (2 << 0); + break; + case FEC_5_6: + buf[2] |= (3 << 0); + break; + case FEC_7_8: + buf[2] |= (4 << 0); + break; + default: + dev_dbg(&state->i2c->dev, "%s: invalid code_rate_HP\n", + __func__); + auto_mode = 1; + } + + switch (c->code_rate_LP) { + case FEC_AUTO: + auto_mode = 1; + break; + case FEC_1_2: + break; + case FEC_2_3: + buf[2] |= (1 << 3); + break; + case FEC_3_4: + buf[2] |= (2 << 3); + break; + case FEC_5_6: + buf[2] |= (3 << 3); + break; + case FEC_7_8: + buf[2] |= (4 << 3); + break; + case FEC_NONE: + break; + default: + dev_dbg(&state->i2c->dev, "%s: invalid code_rate_LP\n", + __func__); + auto_mode = 1; + } + + switch (c->bandwidth_hz) { + case 6000000: + break; + case 7000000: + buf[1] |= (1 << 2); + break; + case 8000000: + buf[1] |= (2 << 2); + break; + default: + dev_dbg(&state->i2c->dev, "%s: invalid bandwidth_hz\n", + __func__); + ret = -EINVAL; + goto err; + } + + ret = af9013_wr_regs(state, 0xd3c0, buf, 3); + if (ret) + goto err; + + if (auto_mode) { + /* clear easy mode flag */ + ret = af9013_wr_reg(state, 0xaefd, 0); + if (ret) + goto err; + + dev_dbg(&state->i2c->dev, "%s: auto params\n", __func__); + } else { + /* set easy mode flag */ + ret = af9013_wr_reg(state, 0xaefd, 1); + if (ret) + goto err; + + ret = af9013_wr_reg(state, 0xaefe, 0); + if (ret) + goto err; + + dev_dbg(&state->i2c->dev, "%s: manual params\n", __func__); + } + + /* tune */ + ret = af9013_wr_reg(state, 0xffff, 0); + if (ret) + goto err; + + state->bandwidth_hz = c->bandwidth_hz; + state->set_frontend_jiffies = jiffies; + state->first_tune = false; + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct af9013_state *state = fe->demodulator_priv; + int ret; + u8 buf[3]; + + dev_dbg(&state->i2c->dev, "%s:\n", __func__); + + ret = af9013_rd_regs(state, 0xd3c0, buf, 3); + if (ret) + goto err; + + switch ((buf[1] >> 6) & 3) { + case 0: + c->modulation = QPSK; + break; + case 1: + c->modulation = QAM_16; + break; + case 2: + c->modulation = QAM_64; + break; + } + + switch ((buf[0] >> 0) & 3) { + case 0: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case 1: + c->transmission_mode = TRANSMISSION_MODE_8K; + } + + switch ((buf[0] >> 2) & 3) { + case 0: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + switch ((buf[0] >> 4) & 7) { + case 0: + c->hierarchy = HIERARCHY_NONE; + break; + case 1: + c->hierarchy = HIERARCHY_1; + break; + case 2: + c->hierarchy = HIERARCHY_2; + break; + case 3: + c->hierarchy = HIERARCHY_4; + break; + } + + switch ((buf[2] >> 0) & 7) { + case 0: + c->code_rate_HP = FEC_1_2; + break; + case 1: + c->code_rate_HP = FEC_2_3; + break; + case 2: + c->code_rate_HP = FEC_3_4; + break; + case 3: + c->code_rate_HP = FEC_5_6; + break; + case 4: + c->code_rate_HP = FEC_7_8; + break; + } + + switch ((buf[2] >> 3) & 7) { + case 0: + c->code_rate_LP = FEC_1_2; + break; + case 1: + c->code_rate_LP = FEC_2_3; + break; + case 2: + c->code_rate_LP = FEC_3_4; + break; + case 3: + c->code_rate_LP = FEC_5_6; + break; + case 4: + c->code_rate_LP = FEC_7_8; + break; + } + + switch ((buf[1] >> 2) & 3) { + case 0: + c->bandwidth_hz = 6000000; + break; + case 1: + c->bandwidth_hz = 7000000; + break; + case 2: + c->bandwidth_hz = 8000000; + break; + } + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + u8 tmp; + + /* + * Return status from the cache if it is younger than 2000ms with the + * exception of last tune is done during 4000ms. + */ + if (time_is_after_jiffies( + state->read_status_jiffies + msecs_to_jiffies(2000)) && + time_is_before_jiffies( + state->set_frontend_jiffies + msecs_to_jiffies(4000)) + ) { + *status = state->fe_status; + return 0; + } else { + *status = 0; + } + + /* MPEG2 lock */ + ret = af9013_rd_reg_bits(state, 0xd507, 6, 1, &tmp); + if (ret) + goto err; + + if (tmp) + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | + FE_HAS_SYNC | FE_HAS_LOCK; + + if (!*status) { + /* TPS lock */ + ret = af9013_rd_reg_bits(state, 0xd330, 3, 1, &tmp); + if (ret) + goto err; + + if (tmp) + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI; + } + + state->fe_status = *status; + state->read_status_jiffies = jiffies; + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct af9013_state *state = fe->demodulator_priv; + *snr = state->snr; + return 0; +} + +static int af9013_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct af9013_state *state = fe->demodulator_priv; + *strength = state->signal_strength; + return 0; +} + +static int af9013_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct af9013_state *state = fe->demodulator_priv; + *ber = state->ber; + return 0; +} + +static int af9013_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct af9013_state *state = fe->demodulator_priv; + *ucblocks = state->ucblocks; + return 0; +} + +static int af9013_init(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret, i, len; + u8 buf[3], tmp; + u32 adc_cw; + const struct af9013_reg_bit *init; + + dev_dbg(&state->i2c->dev, "%s:\n", __func__); + + /* power on */ + ret = af9013_power_ctrl(state, 1); + if (ret) + goto err; + + /* enable ADC */ + ret = af9013_wr_reg(state, 0xd73a, 0xa4); + if (ret) + goto err; + + /* write API version to firmware */ + ret = af9013_wr_regs(state, 0x9bf2, state->config.api_version, 4); + if (ret) + goto err; + + /* program ADC control */ + switch (state->config.clock) { + case 28800000: /* 28.800 MHz */ + tmp = 0; + break; + case 20480000: /* 20.480 MHz */ + tmp = 1; + break; + case 28000000: /* 28.000 MHz */ + tmp = 2; + break; + case 25000000: /* 25.000 MHz */ + tmp = 3; + break; + default: + dev_err(&state->i2c->dev, "%s: invalid clock\n", + KBUILD_MODNAME); + return -EINVAL; + } + + adc_cw = af9013_div(state, state->config.clock, 1000000ul, 19); + buf[0] = (adc_cw >> 0) & 0xff; + buf[1] = (adc_cw >> 8) & 0xff; + buf[2] = (adc_cw >> 16) & 0xff; + + ret = af9013_wr_regs(state, 0xd180, buf, 3); + if (ret) + goto err; + + ret = af9013_wr_reg_bits(state, 0x9bd2, 0, 4, tmp); + if (ret) + goto err; + + /* set I2C master clock */ + ret = af9013_wr_reg(state, 0xd416, 0x14); + if (ret) + goto err; + + /* set 16 embx */ + ret = af9013_wr_reg_bits(state, 0xd700, 1, 1, 1); + if (ret) + goto err; + + /* set no trigger */ + ret = af9013_wr_reg_bits(state, 0xd700, 2, 1, 0); + if (ret) + goto err; + + /* set read-update bit for constellation */ + ret = af9013_wr_reg_bits(state, 0xd371, 1, 1, 1); + if (ret) + goto err; + + /* settings for mp2if */ + if (state->config.ts_mode == AF9013_TS_USB) { + /* AF9015 split PSB to 1.5k + 0.5k */ + ret = af9013_wr_reg_bits(state, 0xd50b, 2, 1, 1); + if (ret) + goto err; + } else { + /* AF9013 change the output bit to data7 */ + ret = af9013_wr_reg_bits(state, 0xd500, 3, 1, 1); + if (ret) + goto err; + + /* AF9013 set mpeg to full speed */ + ret = af9013_wr_reg_bits(state, 0xd502, 4, 1, 1); + if (ret) + goto err; + } + + ret = af9013_wr_reg_bits(state, 0xd520, 4, 1, 1); + if (ret) + goto err; + + /* load OFSM settings */ + dev_dbg(&state->i2c->dev, "%s: load ofsm settings\n", __func__); + len = ARRAY_SIZE(ofsm_init); + init = ofsm_init; + for (i = 0; i < len; i++) { + ret = af9013_wr_reg_bits(state, init[i].addr, init[i].pos, + init[i].len, init[i].val); + if (ret) + goto err; + } + + /* load tuner specific settings */ + dev_dbg(&state->i2c->dev, "%s: load tuner specific settings\n", + __func__); + switch (state->config.tuner) { + case AF9013_TUNER_MXL5003D: + len = ARRAY_SIZE(tuner_init_mxl5003d); + init = tuner_init_mxl5003d; + break; + case AF9013_TUNER_MXL5005D: + case AF9013_TUNER_MXL5005R: + case AF9013_TUNER_MXL5007T: + len = ARRAY_SIZE(tuner_init_mxl5005); + init = tuner_init_mxl5005; + break; + case AF9013_TUNER_ENV77H11D5: + len = ARRAY_SIZE(tuner_init_env77h11d5); + init = tuner_init_env77h11d5; + break; + case AF9013_TUNER_MT2060: + len = ARRAY_SIZE(tuner_init_mt2060); + init = tuner_init_mt2060; + break; + case AF9013_TUNER_MC44S803: + len = ARRAY_SIZE(tuner_init_mc44s803); + init = tuner_init_mc44s803; + break; + case AF9013_TUNER_QT1010: + case AF9013_TUNER_QT1010A: + len = ARRAY_SIZE(tuner_init_qt1010); + init = tuner_init_qt1010; + break; + case AF9013_TUNER_MT2060_2: + len = ARRAY_SIZE(tuner_init_mt2060_2); + init = tuner_init_mt2060_2; + break; + case AF9013_TUNER_TDA18271: + case AF9013_TUNER_TDA18218: + len = ARRAY_SIZE(tuner_init_tda18271); + init = tuner_init_tda18271; + break; + case AF9013_TUNER_UNKNOWN: + default: + len = ARRAY_SIZE(tuner_init_unknown); + init = tuner_init_unknown; + break; + } + + for (i = 0; i < len; i++) { + ret = af9013_wr_reg_bits(state, init[i].addr, init[i].pos, + init[i].len, init[i].val); + if (ret) + goto err; + } + + /* TS mode */ + ret = af9013_wr_reg_bits(state, 0xd500, 1, 2, state->config.ts_mode); + if (ret) + goto err; + + /* enable lock led */ + ret = af9013_wr_reg_bits(state, 0xd730, 0, 1, 1); + if (ret) + goto err; + + /* check if we support signal strength */ + if (!state->signal_strength_en) { + ret = af9013_rd_reg_bits(state, 0x9bee, 0, 1, + &state->signal_strength_en); + if (ret) + goto err; + } + + /* read values needed for signal strength calculation */ + if (state->signal_strength_en && !state->rf_50) { + ret = af9013_rd_reg(state, 0x9bbd, &state->rf_50); + if (ret) + goto err; + + ret = af9013_rd_reg(state, 0x9bd0, &state->rf_80); + if (ret) + goto err; + + ret = af9013_rd_reg(state, 0x9be2, &state->if_50); + if (ret) + goto err; + + ret = af9013_rd_reg(state, 0x9be4, &state->if_80); + if (ret) + goto err; + } + + /* SNR */ + ret = af9013_wr_reg(state, 0xd2e2, 1); + if (ret) + goto err; + + /* BER / UCB */ + buf[0] = (10000 >> 0) & 0xff; + buf[1] = (10000 >> 8) & 0xff; + ret = af9013_wr_regs(state, 0xd385, buf, 2); + if (ret) + goto err; + + /* enable FEC monitor */ + ret = af9013_wr_reg_bits(state, 0xd392, 1, 1, 1); + if (ret) + goto err; + + state->first_tune = true; + schedule_delayed_work(&state->statistics_work, msecs_to_jiffies(400)); + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_sleep(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + + dev_dbg(&state->i2c->dev, "%s:\n", __func__); + + /* stop statistics polling */ + cancel_delayed_work_sync(&state->statistics_work); + + /* disable lock led */ + ret = af9013_wr_reg_bits(state, 0xd730, 0, 1, 0); + if (ret) + goto err; + + /* power off */ + ret = af9013_power_ctrl(state, 0); + if (ret) + goto err; + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int af9013_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + int ret; + struct af9013_state *state = fe->demodulator_priv; + + dev_dbg(&state->i2c->dev, "%s: enable=%d\n", __func__, enable); + + /* gate already open or close */ + if (state->i2c_gate_state == enable) + return 0; + + if (state->config.ts_mode == AF9013_TS_USB) + ret = af9013_wr_reg_bits(state, 0xd417, 3, 1, enable); + else + ret = af9013_wr_reg_bits(state, 0xd607, 2, 1, enable); + if (ret) + goto err; + + state->i2c_gate_state = enable; + + return ret; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static void af9013_release(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops af9013_ops; + +static int af9013_download_firmware(struct af9013_state *state) +{ + int i, len, remaining, ret; + const struct firmware *fw; + u16 checksum = 0; + u8 val; + u8 fw_params[4]; + u8 *fw_file = AF9013_FIRMWARE; + + msleep(100); + /* check whether firmware is already running */ + ret = af9013_rd_reg(state, 0x98be, &val); + if (ret) + goto err; + else + dev_dbg(&state->i2c->dev, "%s: firmware status=%02x\n", + __func__, val); + + if (val == 0x0c) /* fw is running, no need for download */ + goto exit; + + dev_info(&state->i2c->dev, "%s: found a '%s' in cold state, will try " \ + "to load a firmware\n", + KBUILD_MODNAME, af9013_ops.info.name); + + /* request the firmware, this will block and timeout */ + ret = request_firmware(&fw, fw_file, state->i2c->dev.parent); + if (ret) { + dev_info(&state->i2c->dev, "%s: did not find the firmware " \ + "file. (%s) Please see linux/Documentation/dvb/ for " \ + "more details on firmware-problems. (%d)\n", + KBUILD_MODNAME, fw_file, ret); + goto err; + } + + dev_info(&state->i2c->dev, "%s: downloading firmware from file '%s'\n", + KBUILD_MODNAME, fw_file); + + /* calc checksum */ + for (i = 0; i < fw->size; i++) + checksum += fw->data[i]; + + fw_params[0] = checksum >> 8; + fw_params[1] = checksum & 0xff; + fw_params[2] = fw->size >> 8; + fw_params[3] = fw->size & 0xff; + + /* write fw checksum & size */ + ret = af9013_write_ofsm_regs(state, 0x50fc, + fw_params, sizeof(fw_params)); + if (ret) + goto err_release; + + #define FW_ADDR 0x5100 /* firmware start address */ + #define LEN_MAX 16 /* max packet size */ + for (remaining = fw->size; remaining > 0; remaining -= LEN_MAX) { + len = remaining; + if (len > LEN_MAX) + len = LEN_MAX; + + ret = af9013_write_ofsm_regs(state, + FW_ADDR + fw->size - remaining, + (u8 *) &fw->data[fw->size - remaining], len); + if (ret) { + dev_err(&state->i2c->dev, + "%s: firmware download failed=%d\n", + KBUILD_MODNAME, ret); + goto err_release; + } + } + + /* request boot firmware */ + ret = af9013_wr_reg(state, 0xe205, 1); + if (ret) + goto err_release; + + for (i = 0; i < 15; i++) { + msleep(100); + + /* check firmware status */ + ret = af9013_rd_reg(state, 0x98be, &val); + if (ret) + goto err_release; + + dev_dbg(&state->i2c->dev, "%s: firmware status=%02x\n", + __func__, val); + + if (val == 0x0c || val == 0x04) /* success or fail */ + break; + } + + if (val == 0x04) { + dev_err(&state->i2c->dev, "%s: firmware did not run\n", + KBUILD_MODNAME); + ret = -ENODEV; + } else if (val != 0x0c) { + dev_err(&state->i2c->dev, "%s: firmware boot timeout\n", + KBUILD_MODNAME); + ret = -ENODEV; + } + +err_release: + release_firmware(fw); +err: +exit: + if (!ret) + dev_info(&state->i2c->dev, "%s: found a '%s' in warm state\n", + KBUILD_MODNAME, af9013_ops.info.name); + return ret; +} + +struct dvb_frontend *af9013_attach(const struct af9013_config *config, + struct i2c_adapter *i2c) +{ + int ret; + struct af9013_state *state = NULL; + u8 buf[4], i; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct af9013_state), GFP_KERNEL); + if (state == NULL) + goto err; + + /* setup the state */ + state->i2c = i2c; + memcpy(&state->config, config, sizeof(struct af9013_config)); + + /* download firmware */ + if (state->config.ts_mode != AF9013_TS_USB) { + ret = af9013_download_firmware(state); + if (ret) + goto err; + } + + /* firmware version */ + ret = af9013_rd_regs(state, 0x5103, buf, 4); + if (ret) + goto err; + + dev_info(&state->i2c->dev, "%s: firmware version %d.%d.%d.%d\n", + KBUILD_MODNAME, buf[0], buf[1], buf[2], buf[3]); + + /* set GPIOs */ + for (i = 0; i < sizeof(state->config.gpio); i++) { + ret = af9013_set_gpio(state, i, state->config.gpio[i]); + if (ret) + goto err; + } + + /* create dvb_frontend */ + memcpy(&state->fe.ops, &af9013_ops, + sizeof(struct dvb_frontend_ops)); + state->fe.demodulator_priv = state; + + INIT_DELAYED_WORK(&state->statistics_work, af9013_statistics_work); + + return &state->fe; +err: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(af9013_attach); + +static struct dvb_frontend_ops af9013_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "Afatech AF9013", + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 250000, + .frequency_tolerance = 0, + .caps = FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_64 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_RECOVER | + FE_CAN_MUTE_TS + }, + + .release = af9013_release, + + .init = af9013_init, + .sleep = af9013_sleep, + + .get_tune_settings = af9013_get_tune_settings, + .set_frontend = af9013_set_frontend, + .get_frontend = af9013_get_frontend, + + .read_status = af9013_read_status, + .read_snr = af9013_read_snr, + .read_signal_strength = af9013_read_signal_strength, + .read_ber = af9013_read_ber, + .read_ucblocks = af9013_read_ucblocks, + + .i2c_gate_ctrl = af9013_i2c_gate_ctrl, +}; + +MODULE_AUTHOR("Antti Palosaari "); +MODULE_DESCRIPTION("Afatech AF9013 DVB-T demodulator driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(AF9013_FIRMWARE); diff --git a/drivers/media/dvb-frontends/af9013.h b/drivers/media/dvb-frontends/af9013.h new file mode 100644 index 000000000000..dc837d91327a --- /dev/null +++ b/drivers/media/dvb-frontends/af9013.h @@ -0,0 +1,118 @@ +/* + * Afatech AF9013 demodulator driver + * + * Copyright (C) 2007 Antti Palosaari + * Copyright (C) 2011 Antti Palosaari + * + * Thanks to Afatech who kindly provided information. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef AF9013_H +#define AF9013_H + +#include + +/* AF9013/5 GPIOs (mostly guessed) + demod#1-gpio#0 - set demod#2 i2c-addr for dual devices + demod#1-gpio#1 - xtal setting (?) + demod#1-gpio#3 - tuner#1 + demod#2-gpio#0 - tuner#2 + demod#2-gpio#1 - xtal setting (?) +*/ + +struct af9013_config { + /* + * I2C address + */ + u8 i2c_addr; + + /* + * clock + * 20480000, 25000000, 28000000, 28800000 + */ + u32 clock; + + /* + * tuner + */ +#define AF9013_TUNER_MXL5003D 3 /* MaxLinear */ +#define AF9013_TUNER_MXL5005D 13 /* MaxLinear */ +#define AF9013_TUNER_MXL5005R 30 /* MaxLinear */ +#define AF9013_TUNER_ENV77H11D5 129 /* Panasonic */ +#define AF9013_TUNER_MT2060 130 /* Microtune */ +#define AF9013_TUNER_MC44S803 133 /* Freescale */ +#define AF9013_TUNER_QT1010 134 /* Quantek */ +#define AF9013_TUNER_UNKNOWN 140 /* for can tuners ? */ +#define AF9013_TUNER_MT2060_2 147 /* Microtune */ +#define AF9013_TUNER_TDA18271 156 /* NXP */ +#define AF9013_TUNER_QT1010A 162 /* Quantek */ +#define AF9013_TUNER_MXL5007T 177 /* MaxLinear */ +#define AF9013_TUNER_TDA18218 179 /* NXP */ + u8 tuner; + + /* + * IF frequency + */ + u32 if_frequency; + + /* + * TS settings + */ +#define AF9013_TS_USB 0 +#define AF9013_TS_PARALLEL 1 +#define AF9013_TS_SERIAL 2 + u8 ts_mode:2; + + /* + * input spectrum inversion + */ + bool spec_inv; + + /* + * firmware API version + */ + u8 api_version[4]; + + /* + * GPIOs + */ +#define AF9013_GPIO_ON (1 << 0) +#define AF9013_GPIO_EN (1 << 1) +#define AF9013_GPIO_O (1 << 2) +#define AF9013_GPIO_I (1 << 3) +#define AF9013_GPIO_LO (AF9013_GPIO_ON|AF9013_GPIO_EN) +#define AF9013_GPIO_HI (AF9013_GPIO_ON|AF9013_GPIO_EN|AF9013_GPIO_O) +#define AF9013_GPIO_TUNER_ON (AF9013_GPIO_ON|AF9013_GPIO_EN) +#define AF9013_GPIO_TUNER_OFF (AF9013_GPIO_ON|AF9013_GPIO_EN|AF9013_GPIO_O) + u8 gpio[4]; +}; + +#if defined(CONFIG_DVB_AF9013) || \ + (defined(CONFIG_DVB_AF9013_MODULE) && defined(MODULE)) +extern struct dvb_frontend *af9013_attach(const struct af9013_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *af9013_attach( +const struct af9013_config *config, struct i2c_adapter *i2c) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_AF9013 */ + +#endif /* AF9013_H */ diff --git a/drivers/media/dvb-frontends/af9013_priv.h b/drivers/media/dvb-frontends/af9013_priv.h new file mode 100644 index 000000000000..8b9392cfc00d --- /dev/null +++ b/drivers/media/dvb-frontends/af9013_priv.h @@ -0,0 +1,909 @@ +/* + * Afatech AF9013 demodulator driver + * + * Copyright (C) 2007 Antti Palosaari + * Copyright (C) 2011 Antti Palosaari + * + * Thanks to Afatech who kindly provided information. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef AF9013_PRIV_H +#define AF9013_PRIV_H + +#include "dvb_frontend.h" +#include "af9013.h" +#include + +#define AF9013_FIRMWARE "dvb-fe-af9013.fw" + +struct af9013_reg_bit { + u16 addr; + u8 pos:4; + u8 len:4; + u8 val; +}; + +struct af9013_snr { + u32 val; + u8 snr; +}; + +struct af9013_coeff { + u32 clock; + u32 bandwidth_hz; + u8 val[24]; +}; + +/* pre-calculated coeff lookup table */ +static const struct af9013_coeff coeff_lut[] = { + /* 28.800 MHz */ + { 28800000, 8000000, { 0x02, 0x8a, 0x28, 0xa3, 0x05, 0x14, + 0x51, 0x11, 0x00, 0xa2, 0x8f, 0x3d, 0x00, 0xa2, 0x8a, + 0x29, 0x00, 0xa2, 0x85, 0x14, 0x01, 0x45, 0x14, 0x14 } }, + { 28800000, 7000000, { 0x02, 0x38, 0xe3, 0x8e, 0x04, 0x71, + 0xc7, 0x07, 0x00, 0x8e, 0x3d, 0x55, 0x00, 0x8e, 0x38, + 0xe4, 0x00, 0x8e, 0x34, 0x72, 0x01, 0x1c, 0x71, 0x32 } }, + { 28800000, 6000000, { 0x01, 0xe7, 0x9e, 0x7a, 0x03, 0xcf, + 0x3c, 0x3d, 0x00, 0x79, 0xeb, 0x6e, 0x00, 0x79, 0xe7, + 0x9e, 0x00, 0x79, 0xe3, 0xcf, 0x00, 0xf3, 0xcf, 0x0f } }, + /* 20.480 MHz */ + { 20480000, 8000000, { 0x03, 0x92, 0x49, 0x26, 0x07, 0x24, + 0x92, 0x13, 0x00, 0xe4, 0x99, 0x6e, 0x00, 0xe4, 0x92, + 0x49, 0x00, 0xe4, 0x8b, 0x25, 0x01, 0xc9, 0x24, 0x25 } }, + { 20480000, 7000000, { 0x03, 0x20, 0x00, 0x01, 0x06, 0x40, + 0x00, 0x00, 0x00, 0xc8, 0x06, 0x40, 0x00, 0xc8, 0x00, + 0x00, 0x00, 0xc7, 0xf9, 0xc0, 0x01, 0x90, 0x00, 0x00 } }, + { 20480000, 6000000, { 0x02, 0xad, 0xb6, 0xdc, 0x05, 0x5b, + 0x6d, 0x2e, 0x00, 0xab, 0x73, 0x13, 0x00, 0xab, 0x6d, + 0xb7, 0x00, 0xab, 0x68, 0x5c, 0x01, 0x56, 0xdb, 0x1c } }, + /* 28.000 MHz */ + { 28000000, 8000000, { 0x02, 0x9c, 0xbc, 0x15, 0x05, 0x39, + 0x78, 0x0a, 0x00, 0xa7, 0x34, 0x3f, 0x00, 0xa7, 0x2f, + 0x05, 0x00, 0xa7, 0x29, 0xcc, 0x01, 0x4e, 0x5e, 0x03 } }, + { 28000000, 7000000, { 0x02, 0x49, 0x24, 0x92, 0x04, 0x92, + 0x49, 0x09, 0x00, 0x92, 0x4d, 0xb7, 0x00, 0x92, 0x49, + 0x25, 0x00, 0x92, 0x44, 0x92, 0x01, 0x24, 0x92, 0x12 } }, + { 28000000, 6000000, { 0x01, 0xf5, 0x8d, 0x10, 0x03, 0xeb, + 0x1a, 0x08, 0x00, 0x7d, 0x67, 0x2f, 0x00, 0x7d, 0x63, + 0x44, 0x00, 0x7d, 0x5f, 0x59, 0x00, 0xfa, 0xc6, 0x22 } }, + /* 25.000 MHz */ + { 25000000, 8000000, { 0x02, 0xec, 0xfb, 0x9d, 0x05, 0xd9, + 0xf7, 0x0e, 0x00, 0xbb, 0x44, 0xc1, 0x00, 0xbb, 0x3e, + 0xe7, 0x00, 0xbb, 0x39, 0x0d, 0x01, 0x76, 0x7d, 0x34 } }, + { 25000000, 7000000, { 0x02, 0x8f, 0x5c, 0x29, 0x05, 0x1e, + 0xb8, 0x14, 0x00, 0xa3, 0xdc, 0x29, 0x00, 0xa3, 0xd7, + 0x0a, 0x00, 0xa3, 0xd1, 0xec, 0x01, 0x47, 0xae, 0x05 } }, + { 25000000, 6000000, { 0x02, 0x31, 0xbc, 0xb5, 0x04, 0x63, + 0x79, 0x1b, 0x00, 0x8c, 0x73, 0x91, 0x00, 0x8c, 0x6f, + 0x2d, 0x00, 0x8c, 0x6a, 0xca, 0x01, 0x18, 0xde, 0x17 } }, +}; + +/* QPSK SNR lookup table */ +static const struct af9013_snr qpsk_snr_lut[] = { + { 0x000000, 0 }, + { 0x0b4771, 0 }, + { 0x0c1aed, 1 }, + { 0x0d0d27, 2 }, + { 0x0e4d19, 3 }, + { 0x0e5da8, 4 }, + { 0x107097, 5 }, + { 0x116975, 6 }, + { 0x1252d9, 7 }, + { 0x131fa4, 8 }, + { 0x13d5e1, 9 }, + { 0x148e53, 10 }, + { 0x15358b, 11 }, + { 0x15dd29, 12 }, + { 0x168112, 13 }, + { 0x170b61, 14 }, + { 0xffffff, 15 }, +}; + +/* QAM16 SNR lookup table */ +static const struct af9013_snr qam16_snr_lut[] = { + { 0x000000, 0 }, + { 0x05eb62, 5 }, + { 0x05fecf, 6 }, + { 0x060b80, 7 }, + { 0x062501, 8 }, + { 0x064865, 9 }, + { 0x069604, 10 }, + { 0x06f356, 11 }, + { 0x07706a, 12 }, + { 0x0804d3, 13 }, + { 0x089d1a, 14 }, + { 0x093e3d, 15 }, + { 0x09e35d, 16 }, + { 0x0a7c3c, 17 }, + { 0x0afaf8, 18 }, + { 0x0b719d, 19 }, + { 0xffffff, 20 }, +}; + +/* QAM64 SNR lookup table */ +static const struct af9013_snr qam64_snr_lut[] = { + { 0x000000, 0 }, + { 0x03109b, 12 }, + { 0x0310d4, 13 }, + { 0x031920, 14 }, + { 0x0322d0, 15 }, + { 0x0339fc, 16 }, + { 0x0364a1, 17 }, + { 0x038bcc, 18 }, + { 0x03c7d3, 19 }, + { 0x0408cc, 20 }, + { 0x043bed, 21 }, + { 0x048061, 22 }, + { 0x04be95, 23 }, + { 0x04fa7d, 24 }, + { 0x052405, 25 }, + { 0x05570d, 26 }, + { 0xffffff, 27 }, +}; + +static const struct af9013_reg_bit ofsm_init[] = { + { 0xd73a, 0, 8, 0xa1 }, + { 0xd73b, 0, 8, 0x1f }, + { 0xd73c, 4, 4, 0x0a }, + { 0xd732, 3, 1, 0x00 }, + { 0xd731, 4, 2, 0x03 }, + { 0xd73d, 7, 1, 0x01 }, + { 0xd740, 0, 1, 0x00 }, + { 0xd740, 1, 1, 0x00 }, + { 0xd740, 2, 1, 0x00 }, + { 0xd740, 3, 1, 0x01 }, + { 0xd3c1, 4, 1, 0x01 }, + { 0x9124, 0, 8, 0x58 }, + { 0x9125, 0, 2, 0x02 }, + { 0xd3a2, 0, 8, 0x00 }, + { 0xd3a3, 0, 8, 0x04 }, + { 0xd305, 0, 8, 0x32 }, + { 0xd306, 0, 8, 0x10 }, + { 0xd304, 0, 8, 0x04 }, + { 0x9112, 0, 1, 0x01 }, + { 0x911d, 0, 1, 0x01 }, + { 0x911a, 0, 1, 0x01 }, + { 0x911b, 0, 1, 0x01 }, + { 0x9bce, 0, 4, 0x02 }, + { 0x9116, 0, 1, 0x01 }, + { 0x9122, 0, 8, 0xd0 }, + { 0xd2e0, 0, 8, 0xd0 }, + { 0xd2e9, 0, 4, 0x0d }, + { 0xd38c, 0, 8, 0xfc }, + { 0xd38d, 0, 8, 0x00 }, + { 0xd38e, 0, 8, 0x7e }, + { 0xd38f, 0, 8, 0x00 }, + { 0xd390, 0, 8, 0x2f }, + { 0xd145, 4, 1, 0x01 }, + { 0xd1a9, 4, 1, 0x01 }, + { 0xd158, 5, 3, 0x01 }, + { 0xd159, 0, 6, 0x06 }, + { 0xd167, 0, 8, 0x00 }, + { 0xd168, 0, 4, 0x07 }, + { 0xd1c3, 5, 3, 0x00 }, + { 0xd1c4, 0, 6, 0x00 }, + { 0xd1c5, 0, 7, 0x10 }, + { 0xd1c6, 0, 3, 0x02 }, + { 0xd080, 2, 5, 0x03 }, + { 0xd081, 4, 4, 0x09 }, + { 0xd098, 4, 4, 0x0f }, + { 0xd098, 0, 4, 0x03 }, + { 0xdbc0, 4, 1, 0x01 }, + { 0xdbc7, 0, 8, 0x08 }, + { 0xdbc8, 4, 4, 0x00 }, + { 0xdbc9, 0, 5, 0x01 }, + { 0xd280, 0, 8, 0xe0 }, + { 0xd281, 0, 8, 0xff }, + { 0xd282, 0, 8, 0xff }, + { 0xd283, 0, 8, 0xc3 }, + { 0xd284, 0, 8, 0xff }, + { 0xd285, 0, 4, 0x01 }, + { 0xd0f0, 0, 7, 0x1a }, + { 0xd0f1, 4, 1, 0x01 }, + { 0xd0f2, 0, 8, 0x0c }, + { 0xd101, 5, 3, 0x06 }, + { 0xd103, 0, 4, 0x08 }, + { 0xd0f8, 0, 7, 0x20 }, + { 0xd111, 5, 1, 0x00 }, + { 0xd111, 6, 1, 0x00 }, + { 0x910b, 0, 8, 0x0a }, + { 0x9115, 0, 8, 0x02 }, + { 0x910c, 0, 8, 0x02 }, + { 0x910d, 0, 8, 0x08 }, + { 0x910e, 0, 8, 0x0a }, + { 0x9bf6, 0, 8, 0x06 }, + { 0x9bf8, 0, 8, 0x02 }, + { 0x9bf7, 0, 8, 0x05 }, + { 0x9bf9, 0, 8, 0x0f }, + { 0x9bfc, 0, 8, 0x13 }, + { 0x9bd3, 0, 8, 0xff }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, +}; + +/* Panasonic ENV77H11D5 tuner init + AF9013_TUNER_ENV77H11D5 = 129 */ +static const struct af9013_reg_bit tuner_init_env77h11d5[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x03 }, + { 0x9bbe, 0, 8, 0x01 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x00 }, + { 0x9be3, 0, 8, 0x00 }, + { 0xd015, 0, 8, 0x50 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0xdf }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x44 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0xeb }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0xf4 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bba, 0, 8, 0xf9 }, + { 0x9bc3, 0, 8, 0xdf }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0xeb }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bc9, 0, 8, 0x52 }, + { 0xd011, 0, 8, 0x3c }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0xf7 }, + { 0xd014, 0, 2, 0x02 }, + { 0xd040, 0, 8, 0x0b }, + { 0xd041, 0, 2, 0x02 }, + { 0xd042, 0, 8, 0x4d }, + { 0xd043, 0, 2, 0x00 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, +}; + +/* Microtune MT2060 tuner init + AF9013_TUNER_MT2060 = 130 */ +static const struct af9013_reg_bit tuner_init_mt2060[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x07 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x00 }, + { 0x9be3, 0, 8, 0x00 }, + { 0x9bbe, 0, 1, 0x00 }, + { 0x9bcc, 0, 1, 0x00 }, + { 0x9bb9, 0, 8, 0x75 }, + { 0x9bcd, 0, 8, 0x24 }, + { 0x9bff, 0, 8, 0x30 }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x32 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x36 }, + { 0xd00d, 0, 2, 0x03 }, + { 0xd00a, 0, 8, 0x35 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x07 }, + { 0x9bc8, 0, 8, 0x90 }, + { 0x9bc3, 0, 8, 0x0f }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x36 }, + { 0x9bc6, 0, 8, 0x03 }, + { 0x9bba, 0, 8, 0xc9 }, + { 0x9bc9, 0, 8, 0x79 }, + { 0xd011, 0, 8, 0x10 }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0x45 }, + { 0xd014, 0, 2, 0x03 }, + { 0xd040, 0, 8, 0x98 }, + { 0xd041, 0, 2, 0x00 }, + { 0xd042, 0, 8, 0xcf }, + { 0xd043, 0, 2, 0x03 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0xcc }, + { 0x9be4, 0, 8, 0xa0 }, + { 0x9bbd, 0, 8, 0x8e }, + { 0x9be2, 0, 8, 0x4d }, + { 0x9bee, 0, 1, 0x01 }, +}; + +/* Microtune MT2060 tuner init + AF9013_TUNER_MT2060_2 = 147 */ +static const struct af9013_reg_bit tuner_init_mt2060_2[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x06 }, + { 0x9bbe, 0, 8, 0x01 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x32 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x36 }, + { 0xd00d, 0, 2, 0x03 }, + { 0xd00a, 0, 8, 0x35 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x07 }, + { 0x9bc8, 0, 8, 0x90 }, + { 0x9bc3, 0, 8, 0x0f }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x36 }, + { 0x9bc6, 0, 8, 0x03 }, + { 0x9bba, 0, 8, 0xc9 }, + { 0x9bc9, 0, 8, 0x79 }, + { 0xd011, 0, 8, 0x10 }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0x45 }, + { 0xd014, 0, 2, 0x03 }, + { 0xd040, 0, 8, 0x98 }, + { 0xd041, 0, 2, 0x00 }, + { 0xd042, 0, 8, 0xcf }, + { 0xd043, 0, 2, 0x03 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 8, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x96 }, + { 0xd054, 0, 8, 0x46 }, + { 0xd045, 7, 1, 0x00 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, +}; + +/* MaxLinear MXL5003 tuner init + AF9013_TUNER_MXL5003D = 3 */ +static const struct af9013_reg_bit tuner_init_mxl5003d[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x09 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x00 }, + { 0x9be3, 0, 8, 0x00 }, + { 0x9bfc, 0, 8, 0x0f }, + { 0x9bf6, 0, 8, 0x01 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0xd015, 0, 8, 0x33 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x40 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x6c }, + { 0xd007, 0, 2, 0x00 }, + { 0xd00c, 0, 8, 0x3d }, + { 0xd00d, 0, 2, 0x00 }, + { 0xd00a, 0, 8, 0x45 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x07 }, + { 0x9bc8, 0, 8, 0x52 }, + { 0x9bc3, 0, 8, 0x0f }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x3d }, + { 0x9bc6, 0, 8, 0x00 }, + { 0x9bba, 0, 8, 0xa2 }, + { 0x9bc9, 0, 8, 0xa0 }, + { 0xd011, 0, 8, 0x56 }, + { 0xd012, 0, 2, 0x00 }, + { 0xd013, 0, 8, 0x50 }, + { 0xd014, 0, 2, 0x00 }, + { 0xd040, 0, 8, 0x56 }, + { 0xd041, 0, 2, 0x00 }, + { 0xd042, 0, 8, 0x50 }, + { 0xd043, 0, 2, 0x00 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 8, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, +}; + +/* MaxLinear MXL5005S & MXL5007T tuner init + AF9013_TUNER_MXL5005D = 13 + AF9013_TUNER_MXL5005R = 30 + AF9013_TUNER_MXL5007T = 177 */ +static const struct af9013_reg_bit tuner_init_mxl5005[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x07 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x01 }, + { 0x9be3, 0, 8, 0x01 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x28 }, + { 0x9bff, 0, 8, 0x24 }, + { 0xd015, 0, 8, 0x40 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x40 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x73 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0xfa }, + { 0xd00d, 0, 2, 0x01 }, + { 0xd00a, 0, 8, 0xff }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x23 }, + { 0x9bc8, 0, 8, 0x55 }, + { 0x9bc3, 0, 8, 0x01 }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0xfa }, + { 0x9bc6, 0, 8, 0x01 }, + { 0x9bba, 0, 8, 0xff }, + { 0x9bc9, 0, 8, 0xff }, + { 0x9bd3, 0, 8, 0x95 }, + { 0xd011, 0, 8, 0x70 }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0xfb }, + { 0xd014, 0, 2, 0x01 }, + { 0xd040, 0, 8, 0x70 }, + { 0xd041, 0, 2, 0x01 }, + { 0xd042, 0, 8, 0xfb }, + { 0xd043, 0, 2, 0x01 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0x93 }, + { 0x9be4, 0, 8, 0xfe }, + { 0x9bbd, 0, 8, 0x63 }, + { 0x9be2, 0, 8, 0xfe }, + { 0x9bee, 0, 1, 0x01 }, +}; + +/* Quantek QT1010 tuner init + AF9013_TUNER_QT1010 = 134 + AF9013_TUNER_QT1010A = 162 */ +static const struct af9013_reg_bit tuner_init_qt1010[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x09 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x01 }, + { 0x9be3, 0, 8, 0x01 }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x28 }, + { 0x9bff, 0, 8, 0x20 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x99 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x0f }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0x50 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x00 }, + { 0x9bc8, 0, 8, 0x00 }, + { 0x9bc3, 0, 8, 0x0f }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x0f }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bba, 0, 8, 0xc5 }, + { 0x9bc9, 0, 8, 0xff }, + { 0xd011, 0, 8, 0x58 }, + { 0xd012, 0, 2, 0x02 }, + { 0xd013, 0, 8, 0x89 }, + { 0xd014, 0, 2, 0x01 }, + { 0xd040, 0, 8, 0x58 }, + { 0xd041, 0, 2, 0x02 }, + { 0xd042, 0, 8, 0x89 }, + { 0xd043, 0, 2, 0x01 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0xcd }, + { 0x9be4, 0, 8, 0xbb }, + { 0x9bbd, 0, 8, 0x93 }, + { 0x9be2, 0, 8, 0x80 }, + { 0x9bee, 0, 1, 0x01 }, +}; + +/* Freescale MC44S803 tuner init + AF9013_TUNER_MC44S803 = 133 */ +static const struct af9013_reg_bit tuner_init_mc44s803[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x06 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x00 }, + { 0x9be3, 0, 8, 0x00 }, + { 0x9bf6, 0, 8, 0x01 }, + { 0x9bf8, 0, 8, 0x02 }, + { 0x9bf9, 0, 8, 0x02 }, + { 0x9bfc, 0, 8, 0x1f }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x24 }, + { 0x9bff, 0, 8, 0x24 }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x01 }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x7b }, + { 0xd007, 0, 2, 0x00 }, + { 0xd00c, 0, 8, 0x7c }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0xfe }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x08 }, + { 0x9bc8, 0, 8, 0x9a }, + { 0x9bc3, 0, 8, 0x01 }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x7c }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bba, 0, 8, 0xfc }, + { 0x9bc9, 0, 8, 0xaa }, + { 0xd011, 0, 8, 0x6b }, + { 0xd012, 0, 2, 0x00 }, + { 0xd013, 0, 8, 0x88 }, + { 0xd014, 0, 2, 0x02 }, + { 0xd040, 0, 8, 0x6b }, + { 0xd041, 0, 2, 0x00 }, + { 0xd042, 0, 8, 0x7c }, + { 0xd043, 0, 2, 0x02 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0x9e }, + { 0x9be4, 0, 8, 0xff }, + { 0x9bbd, 0, 8, 0x9e }, + { 0x9be2, 0, 8, 0x25 }, + { 0x9bee, 0, 1, 0x01 }, + { 0xd73b, 3, 1, 0x00 }, +}; + +/* unknown, probably for tin can tuner, tuner init + AF9013_TUNER_UNKNOWN = 140 */ +static const struct af9013_reg_bit tuner_init_unknown[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x02 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x01 }, + { 0x9be3, 0, 8, 0x01 }, + { 0xd1a0, 1, 1, 0x00 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x18 }, + { 0x9bff, 0, 8, 0x2c }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0xdf }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x44 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x00 }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0xf6 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bba, 0, 8, 0xf9 }, + { 0x9bc8, 0, 8, 0xaa }, + { 0x9bc3, 0, 8, 0xdf }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x00 }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bc9, 0, 8, 0xf0 }, + { 0xd011, 0, 8, 0x3c }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0xf7 }, + { 0xd014, 0, 2, 0x02 }, + { 0xd040, 0, 8, 0x0b }, + { 0xd041, 0, 2, 0x02 }, + { 0xd042, 0, 8, 0x4d }, + { 0xd043, 0, 2, 0x00 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, +}; + +/* NXP TDA18271 & TDA18218 tuner init + AF9013_TUNER_TDA18271 = 156 + AF9013_TUNER_TDA18218 = 179 */ +static const struct af9013_reg_bit tuner_init_tda18271[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x04 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x01 }, + { 0x9be3, 0, 8, 0x01 }, + { 0xd1a0, 1, 1, 0x00 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x18 }, + { 0x9bff, 0, 8, 0x2c }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0xdf }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x44 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x00 }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0xf6 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bba, 0, 8, 0xf9 }, + { 0x9bc8, 0, 8, 0xaa }, + { 0x9bc3, 0, 8, 0xdf }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x00 }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bc9, 0, 8, 0xf0 }, + { 0xd011, 0, 8, 0x3c }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0xf7 }, + { 0xd014, 0, 2, 0x02 }, + { 0xd040, 0, 8, 0x0b }, + { 0xd041, 0, 2, 0x02 }, + { 0xd042, 0, 8, 0x4d }, + { 0xd043, 0, 2, 0x00 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0xa8 }, + { 0x9be4, 0, 8, 0x7f }, + { 0x9bbd, 0, 8, 0xa8 }, + { 0x9be2, 0, 8, 0x20 }, + { 0x9bee, 0, 1, 0x01 }, +}; + +#endif /* AF9013_PRIV_H */ diff --git a/drivers/media/dvb-frontends/af9033.c b/drivers/media/dvb-frontends/af9033.c new file mode 100644 index 000000000000..8162d939c4b2 --- /dev/null +++ b/drivers/media/dvb-frontends/af9033.c @@ -0,0 +1,996 @@ +/* + * Afatech AF9033 demodulator driver + * + * Copyright (C) 2009 Antti Palosaari + * Copyright (C) 2012 Antti Palosaari + * + * 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. + */ + +#include "af9033_priv.h" + +struct af9033_state { + struct i2c_adapter *i2c; + struct dvb_frontend fe; + struct af9033_config cfg; + + u32 bandwidth_hz; + bool ts_mode_parallel; + bool ts_mode_serial; + + u32 ber; + u32 ucb; + unsigned long last_stat_check; +}; + +/* write multiple registers */ +static int af9033_wr_regs(struct af9033_state *state, u32 reg, const u8 *val, + int len) +{ + int ret; + u8 buf[3 + len]; + struct i2c_msg msg[1] = { + { + .addr = state->cfg.i2c_addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + } + }; + + buf[0] = (reg >> 16) & 0xff; + buf[1] = (reg >> 8) & 0xff; + buf[2] = (reg >> 0) & 0xff; + memcpy(&buf[3], val, len); + + ret = i2c_transfer(state->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&state->i2c->dev, "%s: i2c wr failed=%d reg=%06x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + + return ret; +} + +/* read multiple registers */ +static int af9033_rd_regs(struct af9033_state *state, u32 reg, u8 *val, int len) +{ + int ret; + u8 buf[3] = { (reg >> 16) & 0xff, (reg >> 8) & 0xff, + (reg >> 0) & 0xff }; + struct i2c_msg msg[2] = { + { + .addr = state->cfg.i2c_addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf + }, { + .addr = state->cfg.i2c_addr, + .flags = I2C_M_RD, + .len = len, + .buf = val + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + if (ret == 2) { + ret = 0; + } else { + dev_warn(&state->i2c->dev, "%s: i2c rd failed=%d reg=%06x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + + return ret; +} + + +/* write single register */ +static int af9033_wr_reg(struct af9033_state *state, u32 reg, u8 val) +{ + return af9033_wr_regs(state, reg, &val, 1); +} + +/* read single register */ +static int af9033_rd_reg(struct af9033_state *state, u32 reg, u8 *val) +{ + return af9033_rd_regs(state, reg, val, 1); +} + +/* write single register with mask */ +static int af9033_wr_reg_mask(struct af9033_state *state, u32 reg, u8 val, + u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = af9033_rd_regs(state, reg, &tmp, 1); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return af9033_wr_regs(state, reg, &val, 1); +} + +/* read single register with mask */ +static int af9033_rd_reg_mask(struct af9033_state *state, u32 reg, u8 *val, + u8 mask) +{ + int ret, i; + u8 tmp; + + ret = af9033_rd_regs(state, reg, &tmp, 1); + if (ret) + return ret; + + tmp &= mask; + + /* find position of the first bit */ + for (i = 0; i < 8; i++) { + if ((mask >> i) & 0x01) + break; + } + *val = tmp >> i; + + return 0; +} + +static u32 af9033_div(struct af9033_state *state, u32 a, u32 b, u32 x) +{ + u32 r = 0, c = 0, i; + + dev_dbg(&state->i2c->dev, "%s: a=%d b=%d x=%d\n", __func__, a, b, x); + + if (a > b) { + c = a / b; + a = a - c * b; + } + + for (i = 0; i < x; i++) { + if (a >= b) { + r += 1; + a -= b; + } + a <<= 1; + r <<= 1; + } + r = (c << (u32)x) + r; + + dev_dbg(&state->i2c->dev, "%s: a=%d b=%d x=%d r=%d r=%x\n", + __func__, a, b, x, r, r); + + return r; +} + +static void af9033_release(struct dvb_frontend *fe) +{ + struct af9033_state *state = fe->demodulator_priv; + + kfree(state); +} + +static int af9033_init(struct dvb_frontend *fe) +{ + struct af9033_state *state = fe->demodulator_priv; + int ret, i, len; + const struct reg_val *init; + u8 buf[4]; + u32 adc_cw, clock_cw; + struct reg_val_mask tab[] = { + { 0x80fb24, 0x00, 0x08 }, + { 0x80004c, 0x00, 0xff }, + { 0x00f641, state->cfg.tuner, 0xff }, + { 0x80f5ca, 0x01, 0x01 }, + { 0x80f715, 0x01, 0x01 }, + { 0x00f41f, 0x04, 0x04 }, + { 0x00f41a, 0x01, 0x01 }, + { 0x80f731, 0x00, 0x01 }, + { 0x00d91e, 0x00, 0x01 }, + { 0x00d919, 0x00, 0x01 }, + { 0x80f732, 0x00, 0x01 }, + { 0x00d91f, 0x00, 0x01 }, + { 0x00d91a, 0x00, 0x01 }, + { 0x80f730, 0x00, 0x01 }, + { 0x80f778, 0x00, 0xff }, + { 0x80f73c, 0x01, 0x01 }, + { 0x80f776, 0x00, 0x01 }, + { 0x00d8fd, 0x01, 0xff }, + { 0x00d830, 0x01, 0xff }, + { 0x00d831, 0x00, 0xff }, + { 0x00d832, 0x00, 0xff }, + { 0x80f985, state->ts_mode_serial, 0x01 }, + { 0x80f986, state->ts_mode_parallel, 0x01 }, + { 0x00d827, 0x00, 0xff }, + { 0x00d829, 0x00, 0xff }, + }; + + /* program clock control */ + clock_cw = af9033_div(state, state->cfg.clock, 1000000ul, 19ul); + buf[0] = (clock_cw >> 0) & 0xff; + buf[1] = (clock_cw >> 8) & 0xff; + buf[2] = (clock_cw >> 16) & 0xff; + buf[3] = (clock_cw >> 24) & 0xff; + + dev_dbg(&state->i2c->dev, "%s: clock=%d clock_cw=%08x\n", + __func__, state->cfg.clock, clock_cw); + + ret = af9033_wr_regs(state, 0x800025, buf, 4); + if (ret < 0) + goto err; + + /* program ADC control */ + for (i = 0; i < ARRAY_SIZE(clock_adc_lut); i++) { + if (clock_adc_lut[i].clock == state->cfg.clock) + break; + } + + adc_cw = af9033_div(state, clock_adc_lut[i].adc, 1000000ul, 19ul); + buf[0] = (adc_cw >> 0) & 0xff; + buf[1] = (adc_cw >> 8) & 0xff; + buf[2] = (adc_cw >> 16) & 0xff; + + dev_dbg(&state->i2c->dev, "%s: adc=%d adc_cw=%06x\n", + __func__, clock_adc_lut[i].adc, adc_cw); + + ret = af9033_wr_regs(state, 0x80f1cd, buf, 3); + if (ret < 0) + goto err; + + /* program register table */ + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = af9033_wr_reg_mask(state, tab[i].reg, tab[i].val, + tab[i].mask); + if (ret < 0) + goto err; + } + + /* settings for TS interface */ + if (state->cfg.ts_mode == AF9033_TS_MODE_USB) { + ret = af9033_wr_reg_mask(state, 0x80f9a5, 0x00, 0x01); + if (ret < 0) + goto err; + + ret = af9033_wr_reg_mask(state, 0x80f9b5, 0x01, 0x01); + if (ret < 0) + goto err; + } else { + ret = af9033_wr_reg_mask(state, 0x80f990, 0x00, 0x01); + if (ret < 0) + goto err; + + ret = af9033_wr_reg_mask(state, 0x80f9b5, 0x00, 0x01); + if (ret < 0) + goto err; + } + + /* load OFSM settings */ + dev_dbg(&state->i2c->dev, "%s: load ofsm settings\n", __func__); + len = ARRAY_SIZE(ofsm_init); + init = ofsm_init; + for (i = 0; i < len; i++) { + ret = af9033_wr_reg(state, init[i].reg, init[i].val); + if (ret < 0) + goto err; + } + + /* load tuner specific settings */ + dev_dbg(&state->i2c->dev, "%s: load tuner specific settings\n", + __func__); + switch (state->cfg.tuner) { + case AF9033_TUNER_TUA9001: + len = ARRAY_SIZE(tuner_init_tua9001); + init = tuner_init_tua9001; + break; + case AF9033_TUNER_FC0011: + len = ARRAY_SIZE(tuner_init_fc0011); + init = tuner_init_fc0011; + break; + case AF9033_TUNER_MXL5007T: + len = ARRAY_SIZE(tuner_init_mxl5007t); + init = tuner_init_mxl5007t; + break; + case AF9033_TUNER_TDA18218: + len = ARRAY_SIZE(tuner_init_tda18218); + init = tuner_init_tda18218; + break; + case AF9033_TUNER_FC2580: + len = ARRAY_SIZE(tuner_init_fc2580); + init = tuner_init_fc2580; + break; + default: + dev_dbg(&state->i2c->dev, "%s: unsupported tuner ID=%d\n", + __func__, state->cfg.tuner); + ret = -ENODEV; + goto err; + } + + for (i = 0; i < len; i++) { + ret = af9033_wr_reg(state, init[i].reg, init[i].val); + if (ret < 0) + goto err; + } + + state->bandwidth_hz = 0; /* force to program all parameters */ + + return 0; + +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9033_sleep(struct dvb_frontend *fe) +{ + struct af9033_state *state = fe->demodulator_priv; + int ret, i; + u8 tmp; + + ret = af9033_wr_reg(state, 0x80004c, 1); + if (ret < 0) + goto err; + + ret = af9033_wr_reg(state, 0x800000, 0); + if (ret < 0) + goto err; + + for (i = 100, tmp = 1; i && tmp; i--) { + ret = af9033_rd_reg(state, 0x80004c, &tmp); + if (ret < 0) + goto err; + + usleep_range(200, 10000); + } + + dev_dbg(&state->i2c->dev, "%s: loop=%d\n", __func__, i); + + if (i == 0) { + ret = -ETIMEDOUT; + goto err; + } + + ret = af9033_wr_reg_mask(state, 0x80fb24, 0x08, 0x08); + if (ret < 0) + goto err; + + /* prevent current leak (?) */ + if (state->cfg.ts_mode == AF9033_TS_MODE_SERIAL) { + /* enable parallel TS */ + ret = af9033_wr_reg_mask(state, 0x00d917, 0x00, 0x01); + if (ret < 0) + goto err; + + ret = af9033_wr_reg_mask(state, 0x00d916, 0x01, 0x01); + if (ret < 0) + goto err; + } + + return 0; + +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9033_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *fesettings) +{ + fesettings->min_delay_ms = 800; + fesettings->step_size = 0; + fesettings->max_drift = 0; + + return 0; +} + +static int af9033_set_frontend(struct dvb_frontend *fe) +{ + struct af9033_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i, spec_inv; + u8 tmp, buf[3], bandwidth_reg_val; + u32 if_frequency, freq_cw, adc_freq; + + dev_dbg(&state->i2c->dev, "%s: frequency=%d bandwidth_hz=%d\n", + __func__, c->frequency, c->bandwidth_hz); + + /* check bandwidth */ + switch (c->bandwidth_hz) { + case 6000000: + bandwidth_reg_val = 0x00; + break; + case 7000000: + bandwidth_reg_val = 0x01; + break; + case 8000000: + bandwidth_reg_val = 0x02; + break; + default: + dev_dbg(&state->i2c->dev, "%s: invalid bandwidth_hz\n", + __func__); + ret = -EINVAL; + goto err; + } + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + /* program CFOE coefficients */ + if (c->bandwidth_hz != state->bandwidth_hz) { + for (i = 0; i < ARRAY_SIZE(coeff_lut); i++) { + if (coeff_lut[i].clock == state->cfg.clock && + coeff_lut[i].bandwidth_hz == c->bandwidth_hz) { + break; + } + } + ret = af9033_wr_regs(state, 0x800001, + coeff_lut[i].val, sizeof(coeff_lut[i].val)); + } + + /* program frequency control */ + if (c->bandwidth_hz != state->bandwidth_hz) { + spec_inv = state->cfg.spec_inv ? -1 : 1; + + for (i = 0; i < ARRAY_SIZE(clock_adc_lut); i++) { + if (clock_adc_lut[i].clock == state->cfg.clock) + break; + } + adc_freq = clock_adc_lut[i].adc; + + /* get used IF frequency */ + if (fe->ops.tuner_ops.get_if_frequency) + fe->ops.tuner_ops.get_if_frequency(fe, &if_frequency); + else + if_frequency = 0; + + while (if_frequency > (adc_freq / 2)) + if_frequency -= adc_freq; + + if (if_frequency >= 0) + spec_inv *= -1; + else + if_frequency *= -1; + + freq_cw = af9033_div(state, if_frequency, adc_freq, 23ul); + + if (spec_inv == -1) + freq_cw *= -1; + + /* get adc multiplies */ + ret = af9033_rd_reg(state, 0x800045, &tmp); + if (ret < 0) + goto err; + + if (tmp == 1) + freq_cw /= 2; + + buf[0] = (freq_cw >> 0) & 0xff; + buf[1] = (freq_cw >> 8) & 0xff; + buf[2] = (freq_cw >> 16) & 0x7f; + ret = af9033_wr_regs(state, 0x800029, buf, 3); + if (ret < 0) + goto err; + + state->bandwidth_hz = c->bandwidth_hz; + } + + ret = af9033_wr_reg_mask(state, 0x80f904, bandwidth_reg_val, 0x03); + if (ret < 0) + goto err; + + ret = af9033_wr_reg(state, 0x800040, 0x00); + if (ret < 0) + goto err; + + ret = af9033_wr_reg(state, 0x800047, 0x00); + if (ret < 0) + goto err; + + ret = af9033_wr_reg_mask(state, 0x80f999, 0x00, 0x01); + if (ret < 0) + goto err; + + if (c->frequency <= 230000000) + tmp = 0x00; /* VHF */ + else + tmp = 0x01; /* UHF */ + + ret = af9033_wr_reg(state, 0x80004b, tmp); + if (ret < 0) + goto err; + + ret = af9033_wr_reg(state, 0x800000, 0x00); + if (ret < 0) + goto err; + + return 0; + +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9033_get_frontend(struct dvb_frontend *fe) +{ + struct af9033_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 buf[8]; + + dev_dbg(&state->i2c->dev, "%s:\n", __func__); + + /* read all needed registers */ + ret = af9033_rd_regs(state, 0x80f900, buf, sizeof(buf)); + if (ret < 0) + goto err; + + switch ((buf[0] >> 0) & 3) { + case 0: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case 1: + c->transmission_mode = TRANSMISSION_MODE_8K; + break; + } + + switch ((buf[1] >> 0) & 3) { + case 0: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + switch ((buf[2] >> 0) & 7) { + case 0: + c->hierarchy = HIERARCHY_NONE; + break; + case 1: + c->hierarchy = HIERARCHY_1; + break; + case 2: + c->hierarchy = HIERARCHY_2; + break; + case 3: + c->hierarchy = HIERARCHY_4; + break; + } + + switch ((buf[3] >> 0) & 3) { + case 0: + c->modulation = QPSK; + break; + case 1: + c->modulation = QAM_16; + break; + case 2: + c->modulation = QAM_64; + break; + } + + switch ((buf[4] >> 0) & 3) { + case 0: + c->bandwidth_hz = 6000000; + break; + case 1: + c->bandwidth_hz = 7000000; + break; + case 2: + c->bandwidth_hz = 8000000; + break; + } + + switch ((buf[6] >> 0) & 7) { + case 0: + c->code_rate_HP = FEC_1_2; + break; + case 1: + c->code_rate_HP = FEC_2_3; + break; + case 2: + c->code_rate_HP = FEC_3_4; + break; + case 3: + c->code_rate_HP = FEC_5_6; + break; + case 4: + c->code_rate_HP = FEC_7_8; + break; + case 5: + c->code_rate_HP = FEC_NONE; + break; + } + + switch ((buf[7] >> 0) & 7) { + case 0: + c->code_rate_LP = FEC_1_2; + break; + case 1: + c->code_rate_LP = FEC_2_3; + break; + case 2: + c->code_rate_LP = FEC_3_4; + break; + case 3: + c->code_rate_LP = FEC_5_6; + break; + case 4: + c->code_rate_LP = FEC_7_8; + break; + case 5: + c->code_rate_LP = FEC_NONE; + break; + } + + return 0; + +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9033_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct af9033_state *state = fe->demodulator_priv; + int ret; + u8 tmp; + + *status = 0; + + /* radio channel status, 0=no result, 1=has signal, 2=no signal */ + ret = af9033_rd_reg(state, 0x800047, &tmp); + if (ret < 0) + goto err; + + /* has signal */ + if (tmp == 0x01) + *status |= FE_HAS_SIGNAL; + + if (tmp != 0x02) { + /* TPS lock */ + ret = af9033_rd_reg_mask(state, 0x80f5a9, &tmp, 0x01); + if (ret < 0) + goto err; + + if (tmp) + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI; + + /* full lock */ + ret = af9033_rd_reg_mask(state, 0x80f999, &tmp, 0x01); + if (ret < 0) + goto err; + + if (tmp) + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | + FE_HAS_LOCK; + } + + return 0; + +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9033_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct af9033_state *state = fe->demodulator_priv; + int ret, i, len; + u8 buf[3], tmp; + u32 snr_val; + const struct val_snr *uninitialized_var(snr_lut); + + /* read value */ + ret = af9033_rd_regs(state, 0x80002c, buf, 3); + if (ret < 0) + goto err; + + snr_val = (buf[2] << 16) | (buf[1] << 8) | buf[0]; + + /* read current modulation */ + ret = af9033_rd_reg(state, 0x80f903, &tmp); + if (ret < 0) + goto err; + + switch ((tmp >> 0) & 3) { + case 0: + len = ARRAY_SIZE(qpsk_snr_lut); + snr_lut = qpsk_snr_lut; + break; + case 1: + len = ARRAY_SIZE(qam16_snr_lut); + snr_lut = qam16_snr_lut; + break; + case 2: + len = ARRAY_SIZE(qam64_snr_lut); + snr_lut = qam64_snr_lut; + break; + default: + goto err; + } + + for (i = 0; i < len; i++) { + tmp = snr_lut[i].snr; + + if (snr_val < snr_lut[i].val) + break; + } + + *snr = tmp * 10; /* dB/10 */ + + return 0; + +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9033_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct af9033_state *state = fe->demodulator_priv; + int ret; + u8 strength2; + + /* read signal strength of 0-100 scale */ + ret = af9033_rd_reg(state, 0x800048, &strength2); + if (ret < 0) + goto err; + + /* scale value to 0x0000-0xffff */ + *strength = strength2 * 0xffff / 100; + + return 0; + +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9033_update_ch_stat(struct af9033_state *state) +{ + int ret = 0; + u32 err_cnt, bit_cnt; + u16 abort_cnt; + u8 buf[7]; + + /* only update data every half second */ + if (time_after(jiffies, state->last_stat_check + msecs_to_jiffies(500))) { + ret = af9033_rd_regs(state, 0x800032, buf, sizeof(buf)); + if (ret < 0) + goto err; + /* in 8 byte packets? */ + abort_cnt = (buf[1] << 8) + buf[0]; + /* in bits */ + err_cnt = (buf[4] << 16) + (buf[3] << 8) + buf[2]; + /* in 8 byte packets? always(?) 0x2710 = 10000 */ + bit_cnt = (buf[6] << 8) + buf[5]; + + if (bit_cnt < abort_cnt) { + abort_cnt = 1000; + state->ber = 0xffffffff; + } else { + /* 8 byte packets, that have not been rejected already */ + bit_cnt -= (u32)abort_cnt; + if (bit_cnt == 0) { + state->ber = 0xffffffff; + } else { + err_cnt -= (u32)abort_cnt * 8 * 8; + bit_cnt *= 8 * 8; + state->ber = err_cnt * (0xffffffff / bit_cnt); + } + } + state->ucb += abort_cnt; + state->last_stat_check = jiffies; + } + + return 0; +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static int af9033_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct af9033_state *state = fe->demodulator_priv; + int ret; + + ret = af9033_update_ch_stat(state); + if (ret < 0) + return ret; + + *ber = state->ber; + + return 0; +} + +static int af9033_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct af9033_state *state = fe->demodulator_priv; + int ret; + + ret = af9033_update_ch_stat(state); + if (ret < 0) + return ret; + + *ucblocks = state->ucb; + + return 0; +} + +static int af9033_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct af9033_state *state = fe->demodulator_priv; + int ret; + + dev_dbg(&state->i2c->dev, "%s: enable=%d\n", __func__, enable); + + ret = af9033_wr_reg_mask(state, 0x00fa04, enable, 0x01); + if (ret < 0) + goto err; + + return 0; + +err: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + + return ret; +} + +static struct dvb_frontend_ops af9033_ops; + +struct dvb_frontend *af9033_attach(const struct af9033_config *config, + struct i2c_adapter *i2c) +{ + int ret; + struct af9033_state *state; + u8 buf[8]; + + dev_dbg(&i2c->dev, "%s:\n", __func__); + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct af9033_state), GFP_KERNEL); + if (state == NULL) + goto err; + + /* setup the state */ + state->i2c = i2c; + memcpy(&state->cfg, config, sizeof(struct af9033_config)); + + if (state->cfg.clock != 12000000) { + dev_err(&state->i2c->dev, "%s: af9033: unsupported clock=%d, " \ + "only 12000000 Hz is supported currently\n", + KBUILD_MODNAME, state->cfg.clock); + goto err; + } + + /* firmware version */ + ret = af9033_rd_regs(state, 0x0083e9, &buf[0], 4); + if (ret < 0) + goto err; + + ret = af9033_rd_regs(state, 0x804191, &buf[4], 4); + if (ret < 0) + goto err; + + dev_info(&state->i2c->dev, "%s: firmware version: LINK=%d.%d.%d.%d " \ + "OFDM=%d.%d.%d.%d\n", KBUILD_MODNAME, buf[0], buf[1], + buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]); + + /* sleep */ + ret = af9033_wr_reg(state, 0x80004c, 1); + if (ret < 0) + goto err; + + ret = af9033_wr_reg(state, 0x800000, 0); + if (ret < 0) + goto err; + + /* configure internal TS mode */ + switch (state->cfg.ts_mode) { + case AF9033_TS_MODE_PARALLEL: + state->ts_mode_parallel = true; + break; + case AF9033_TS_MODE_SERIAL: + state->ts_mode_serial = true; + break; + case AF9033_TS_MODE_USB: + /* usb mode for AF9035 */ + default: + break; + } + + /* create dvb_frontend */ + memcpy(&state->fe.ops, &af9033_ops, sizeof(struct dvb_frontend_ops)); + state->fe.demodulator_priv = state; + + return &state->fe; + +err: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(af9033_attach); + +static struct dvb_frontend_ops af9033_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "Afatech AF9033 (DVB-T)", + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 250000, + .frequency_tolerance = 0, + .caps = FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_64 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_RECOVER | + FE_CAN_MUTE_TS + }, + + .release = af9033_release, + + .init = af9033_init, + .sleep = af9033_sleep, + + .get_tune_settings = af9033_get_tune_settings, + .set_frontend = af9033_set_frontend, + .get_frontend = af9033_get_frontend, + + .read_status = af9033_read_status, + .read_snr = af9033_read_snr, + .read_signal_strength = af9033_read_signal_strength, + .read_ber = af9033_read_ber, + .read_ucblocks = af9033_read_ucblocks, + + .i2c_gate_ctrl = af9033_i2c_gate_ctrl, +}; + +MODULE_AUTHOR("Antti Palosaari "); +MODULE_DESCRIPTION("Afatech AF9033 DVB-T demodulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/af9033.h b/drivers/media/dvb-frontends/af9033.h new file mode 100644 index 000000000000..bfa4313fde21 --- /dev/null +++ b/drivers/media/dvb-frontends/af9033.h @@ -0,0 +1,76 @@ +/* + * Afatech AF9033 demodulator driver + * + * Copyright (C) 2009 Antti Palosaari + * Copyright (C) 2012 Antti Palosaari + * + * 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. + */ + +#ifndef AF9033_H +#define AF9033_H + +struct af9033_config { + /* + * I2C address + */ + u8 i2c_addr; + + /* + * clock Hz + * 12000000, 22000000, 24000000, 34000000, 32000000, 28000000, 26000000, + * 30000000, 36000000, 20480000, 16384000 + */ + u32 clock; + + /* + * tuner + */ +#define AF9033_TUNER_TUA9001 0x27 /* Infineon TUA 9001 */ +#define AF9033_TUNER_FC0011 0x28 /* Fitipower FC0011 */ +#define AF9033_TUNER_MXL5007T 0xa0 /* MaxLinear MxL5007T */ +#define AF9033_TUNER_TDA18218 0xa1 /* NXP TDA 18218HN */ +#define AF9033_TUNER_FC2580 0x32 /* FCI FC2580 */ + u8 tuner; + + /* + * TS settings + */ +#define AF9033_TS_MODE_USB 0 +#define AF9033_TS_MODE_PARALLEL 1 +#define AF9033_TS_MODE_SERIAL 2 + u8 ts_mode:2; + + /* + * input spectrum inversion + */ + bool spec_inv; +}; + + +#if defined(CONFIG_DVB_AF9033) || \ + (defined(CONFIG_DVB_AF9033_MODULE) && defined(MODULE)) +extern struct dvb_frontend *af9033_attach(const struct af9033_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *af9033_attach( + const struct af9033_config *config, struct i2c_adapter *i2c) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif /* AF9033_H */ diff --git a/drivers/media/dvb-frontends/af9033_priv.h b/drivers/media/dvb-frontends/af9033_priv.h new file mode 100644 index 000000000000..34dddcd77538 --- /dev/null +++ b/drivers/media/dvb-frontends/af9033_priv.h @@ -0,0 +1,507 @@ +/* + * Afatech AF9033 demodulator driver + * + * Copyright (C) 2009 Antti Palosaari + * Copyright (C) 2012 Antti Palosaari + * + * 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. + */ + +#ifndef AF9033_PRIV_H +#define AF9033_PRIV_H + +#include "dvb_frontend.h" +#include "af9033.h" + +struct reg_val { + u32 reg; + u8 val; +}; + +struct reg_val_mask { + u32 reg; + u8 val; + u8 mask; +}; + +struct coeff { + u32 clock; + u32 bandwidth_hz; + u8 val[36]; +}; + +struct clock_adc { + u32 clock; + u32 adc; +}; + +struct val_snr { + u32 val; + u8 snr; +}; + +/* Xtal clock vs. ADC clock lookup table */ +static const struct clock_adc clock_adc_lut[] = { + { 16384000, 20480000 }, + { 20480000, 20480000 }, + { 36000000, 20250000 }, + { 30000000, 20156250 }, + { 26000000, 20583333 }, + { 28000000, 20416667 }, + { 32000000, 20500000 }, + { 34000000, 20187500 }, + { 24000000, 20500000 }, + { 22000000, 20625000 }, + { 12000000, 20250000 }, +}; + +/* pre-calculated coeff lookup table */ +static const struct coeff coeff_lut[] = { + /* 12.000 MHz */ + { 12000000, 8000000, { + 0x01, 0xce, 0x55, 0xc9, 0x00, 0xe7, 0x2a, 0xe4, 0x00, 0x73, + 0x99, 0x0f, 0x00, 0x73, 0x95, 0x72, 0x00, 0x73, 0x91, 0xd5, + 0x00, 0x39, 0xca, 0xb9, 0x00, 0xe7, 0x2a, 0xe4, 0x00, 0x73, + 0x95, 0x72, 0x37, 0x02, 0xce, 0x01 } + }, + { 12000000, 7000000, { + 0x01, 0x94, 0x8b, 0x10, 0x00, 0xca, 0x45, 0x88, 0x00, 0x65, + 0x25, 0xed, 0x00, 0x65, 0x22, 0xc4, 0x00, 0x65, 0x1f, 0x9b, + 0x00, 0x32, 0x91, 0x62, 0x00, 0xca, 0x45, 0x88, 0x00, 0x65, + 0x22, 0xc4, 0x88, 0x02, 0x95, 0x01 } + }, + { 12000000, 6000000, { + 0x01, 0x5a, 0xc0, 0x56, 0x00, 0xad, 0x60, 0x2b, 0x00, 0x56, + 0xb2, 0xcb, 0x00, 0x56, 0xb0, 0x15, 0x00, 0x56, 0xad, 0x60, + 0x00, 0x2b, 0x58, 0x0b, 0x00, 0xad, 0x60, 0x2b, 0x00, 0x56, + 0xb0, 0x15, 0xf4, 0x02, 0x5b, 0x01 } + }, +}; + +/* QPSK SNR lookup table */ +static const struct val_snr qpsk_snr_lut[] = { + { 0x0b4771, 0 }, + { 0x0c1aed, 1 }, + { 0x0d0d27, 2 }, + { 0x0e4d19, 3 }, + { 0x0e5da8, 4 }, + { 0x107097, 5 }, + { 0x116975, 6 }, + { 0x1252d9, 7 }, + { 0x131fa4, 8 }, + { 0x13d5e1, 9 }, + { 0x148e53, 10 }, + { 0x15358b, 11 }, + { 0x15dd29, 12 }, + { 0x168112, 13 }, + { 0x170b61, 14 }, + { 0x17a532, 15 }, + { 0x180f94, 16 }, + { 0x186ed2, 17 }, + { 0x18b271, 18 }, + { 0x18e118, 19 }, + { 0x18ff4b, 20 }, + { 0x190af1, 21 }, + { 0x191451, 22 }, + { 0xffffff, 23 }, +}; + +/* QAM16 SNR lookup table */ +static const struct val_snr qam16_snr_lut[] = { + { 0x04f0d5, 0 }, + { 0x05387a, 1 }, + { 0x0573a4, 2 }, + { 0x05a99e, 3 }, + { 0x05cc80, 4 }, + { 0x05eb62, 5 }, + { 0x05fecf, 6 }, + { 0x060b80, 7 }, + { 0x062501, 8 }, + { 0x064865, 9 }, + { 0x069604, 10 }, + { 0x06f356, 11 }, + { 0x07706a, 12 }, + { 0x0804d3, 13 }, + { 0x089d1a, 14 }, + { 0x093e3d, 15 }, + { 0x09e35d, 16 }, + { 0x0a7c3c, 17 }, + { 0x0afaf8, 18 }, + { 0x0b719d, 19 }, + { 0x0bda6a, 20 }, + { 0x0c0c75, 21 }, + { 0x0c3f7d, 22 }, + { 0x0c5e62, 23 }, + { 0x0c6c31, 24 }, + { 0x0c7925, 25 }, + { 0xffffff, 26 }, +}; + +/* QAM64 SNR lookup table */ +static const struct val_snr qam64_snr_lut[] = { + { 0x0256d0, 0 }, + { 0x027a65, 1 }, + { 0x029873, 2 }, + { 0x02b7fe, 3 }, + { 0x02cf1e, 4 }, + { 0x02e234, 5 }, + { 0x02f409, 6 }, + { 0x030046, 7 }, + { 0x030844, 8 }, + { 0x030a02, 9 }, + { 0x030cde, 10 }, + { 0x031031, 11 }, + { 0x03144c, 12 }, + { 0x0315dd, 13 }, + { 0x031920, 14 }, + { 0x0322d0, 15 }, + { 0x0339fc, 16 }, + { 0x0364a1, 17 }, + { 0x038bcc, 18 }, + { 0x03c7d3, 19 }, + { 0x0408cc, 20 }, + { 0x043bed, 21 }, + { 0x048061, 22 }, + { 0x04be95, 23 }, + { 0x04fa7d, 24 }, + { 0x052405, 25 }, + { 0x05570d, 26 }, + { 0x059feb, 27 }, + { 0x05bf38, 28 }, + { 0xffffff, 29 }, +}; + +static const struct reg_val ofsm_init[] = { + { 0x800051, 0x01 }, + { 0x800070, 0x0a }, + { 0x80007e, 0x04 }, + { 0x800081, 0x0a }, + { 0x80008a, 0x01 }, + { 0x80008e, 0x01 }, + { 0x800092, 0x06 }, + { 0x800099, 0x01 }, + { 0x80009f, 0xe1 }, + { 0x8000a0, 0xcf }, + { 0x8000a3, 0x01 }, + { 0x8000a5, 0x01 }, + { 0x8000a6, 0x01 }, + { 0x8000a9, 0x00 }, + { 0x8000aa, 0x01 }, + { 0x8000ab, 0x01 }, + { 0x8000b0, 0x01 }, + { 0x8000c0, 0x05 }, + { 0x8000c4, 0x19 }, + { 0x80f000, 0x0f }, + { 0x80f016, 0x10 }, + { 0x80f017, 0x04 }, + { 0x80f018, 0x05 }, + { 0x80f019, 0x04 }, + { 0x80f01a, 0x05 }, + { 0x80f021, 0x03 }, + { 0x80f022, 0x0a }, + { 0x80f023, 0x0a }, + { 0x80f02b, 0x00 }, + { 0x80f02c, 0x01 }, + { 0x80f064, 0x03 }, + { 0x80f065, 0xf9 }, + { 0x80f066, 0x03 }, + { 0x80f067, 0x01 }, + { 0x80f06f, 0xe0 }, + { 0x80f070, 0x03 }, + { 0x80f072, 0x0f }, + { 0x80f073, 0x03 }, + { 0x80f078, 0x00 }, + { 0x80f087, 0x00 }, + { 0x80f09b, 0x3f }, + { 0x80f09c, 0x00 }, + { 0x80f09d, 0x20 }, + { 0x80f09e, 0x00 }, + { 0x80f09f, 0x0c }, + { 0x80f0a0, 0x00 }, + { 0x80f130, 0x04 }, + { 0x80f132, 0x04 }, + { 0x80f144, 0x1a }, + { 0x80f146, 0x00 }, + { 0x80f14a, 0x01 }, + { 0x80f14c, 0x00 }, + { 0x80f14d, 0x00 }, + { 0x80f14f, 0x04 }, + { 0x80f158, 0x7f }, + { 0x80f15a, 0x00 }, + { 0x80f15b, 0x08 }, + { 0x80f15d, 0x03 }, + { 0x80f15e, 0x05 }, + { 0x80f163, 0x05 }, + { 0x80f166, 0x01 }, + { 0x80f167, 0x40 }, + { 0x80f168, 0x0f }, + { 0x80f17a, 0x00 }, + { 0x80f17b, 0x00 }, + { 0x80f183, 0x01 }, + { 0x80f19d, 0x40 }, + { 0x80f1bc, 0x36 }, + { 0x80f1bd, 0x00 }, + { 0x80f1cb, 0xa0 }, + { 0x80f1cc, 0x01 }, + { 0x80f204, 0x10 }, + { 0x80f214, 0x00 }, + { 0x80f40e, 0x0a }, + { 0x80f40f, 0x40 }, + { 0x80f410, 0x08 }, + { 0x80f55f, 0x0a }, + { 0x80f561, 0x15 }, + { 0x80f562, 0x20 }, + { 0x80f5df, 0xfb }, + { 0x80f5e0, 0x00 }, + { 0x80f5e3, 0x09 }, + { 0x80f5e4, 0x01 }, + { 0x80f5e5, 0x01 }, + { 0x80f5f8, 0x01 }, + { 0x80f5fd, 0x01 }, + { 0x80f600, 0x05 }, + { 0x80f601, 0x08 }, + { 0x80f602, 0x0b }, + { 0x80f603, 0x0e }, + { 0x80f604, 0x11 }, + { 0x80f605, 0x14 }, + { 0x80f606, 0x17 }, + { 0x80f607, 0x1f }, + { 0x80f60e, 0x00 }, + { 0x80f60f, 0x04 }, + { 0x80f610, 0x32 }, + { 0x80f611, 0x10 }, + { 0x80f707, 0xfc }, + { 0x80f708, 0x00 }, + { 0x80f709, 0x37 }, + { 0x80f70a, 0x00 }, + { 0x80f78b, 0x01 }, + { 0x80f80f, 0x40 }, + { 0x80f810, 0x54 }, + { 0x80f811, 0x5a }, + { 0x80f905, 0x01 }, + { 0x80fb06, 0x03 }, + { 0x80fd8b, 0x00 }, +}; + +/* Infineon TUA 9001 tuner init + AF9033_TUNER_TUA9001 = 0x27 */ +static const struct reg_val tuner_init_tua9001[] = { + { 0x800046, 0x27 }, + { 0x800057, 0x00 }, + { 0x800058, 0x01 }, + { 0x80005f, 0x00 }, + { 0x800060, 0x00 }, + { 0x80006d, 0x00 }, + { 0x800071, 0x05 }, + { 0x800072, 0x02 }, + { 0x800074, 0x01 }, + { 0x800075, 0x03 }, + { 0x800076, 0x02 }, + { 0x800077, 0x00 }, + { 0x800078, 0x01 }, + { 0x800079, 0x00 }, + { 0x80007a, 0x7e }, + { 0x80007b, 0x3e }, + { 0x800093, 0x00 }, + { 0x800094, 0x01 }, + { 0x800095, 0x02 }, + { 0x800096, 0x01 }, + { 0x800098, 0x0a }, + { 0x80009b, 0x05 }, + { 0x80009c, 0x80 }, + { 0x8000b3, 0x00 }, + { 0x8000c1, 0x01 }, + { 0x8000c2, 0x00 }, + { 0x80f007, 0x00 }, + { 0x80f01f, 0x82 }, + { 0x80f020, 0x00 }, + { 0x80f029, 0x82 }, + { 0x80f02a, 0x00 }, + { 0x80f047, 0x00 }, + { 0x80f054, 0x00 }, + { 0x80f055, 0x00 }, + { 0x80f077, 0x01 }, + { 0x80f1e6, 0x00 }, +}; + +/* Fitipower fc0011 tuner init + AF9033_TUNER_FC0011 = 0x28 */ +static const struct reg_val tuner_init_fc0011[] = { + { 0x800046, AF9033_TUNER_FC0011 }, + { 0x800057, 0x00 }, + { 0x800058, 0x01 }, + { 0x80005f, 0x00 }, + { 0x800060, 0x00 }, + { 0x800068, 0xa5 }, + { 0x80006e, 0x01 }, + { 0x800071, 0x0A }, + { 0x800072, 0x02 }, + { 0x800074, 0x01 }, + { 0x800079, 0x01 }, + { 0x800093, 0x00 }, + { 0x800094, 0x00 }, + { 0x800095, 0x00 }, + { 0x800096, 0x00 }, + { 0x80009b, 0x2D }, + { 0x80009c, 0x60 }, + { 0x80009d, 0x23 }, + { 0x8000a4, 0x50 }, + { 0x8000ad, 0x50 }, + { 0x8000b3, 0x01 }, + { 0x8000b7, 0x88 }, + { 0x8000b8, 0xa6 }, + { 0x8000c3, 0x01 }, + { 0x8000c4, 0x01 }, + { 0x8000c7, 0x69 }, + { 0x80F007, 0x00 }, + { 0x80F00A, 0x1B }, + { 0x80F00B, 0x1B }, + { 0x80F00C, 0x1B }, + { 0x80F00D, 0x1B }, + { 0x80F00E, 0xFF }, + { 0x80F00F, 0x01 }, + { 0x80F010, 0x00 }, + { 0x80F011, 0x02 }, + { 0x80F012, 0xFF }, + { 0x80F013, 0x01 }, + { 0x80F014, 0x00 }, + { 0x80F015, 0x02 }, + { 0x80F01B, 0xEF }, + { 0x80F01C, 0x01 }, + { 0x80F01D, 0x0f }, + { 0x80F01E, 0x02 }, + { 0x80F01F, 0x6E }, + { 0x80F020, 0x00 }, + { 0x80F025, 0xDE }, + { 0x80F026, 0x00 }, + { 0x80F027, 0x0A }, + { 0x80F028, 0x03 }, + { 0x80F029, 0x6E }, + { 0x80F02A, 0x00 }, + { 0x80F047, 0x00 }, + { 0x80F054, 0x00 }, + { 0x80F055, 0x00 }, + { 0x80F077, 0x01 }, + { 0x80F1E6, 0x00 }, +}; + +/* MaxLinear MxL5007T tuner init + AF9033_TUNER_MXL5007T = 0xa0 */ +static const struct reg_val tuner_init_mxl5007t[] = { + { 0x800046, 0x1b }, + { 0x800057, 0x01 }, + { 0x800058, 0x01 }, + { 0x80005f, 0x00 }, + { 0x800060, 0x00 }, + { 0x800068, 0x96 }, + { 0x800071, 0x05 }, + { 0x800072, 0x02 }, + { 0x800074, 0x01 }, + { 0x800079, 0x01 }, + { 0x800093, 0x00 }, + { 0x800094, 0x00 }, + { 0x800095, 0x00 }, + { 0x800096, 0x00 }, + { 0x8000b3, 0x01 }, + { 0x8000c1, 0x01 }, + { 0x8000c2, 0x00 }, + { 0x80f007, 0x00 }, + { 0x80f00c, 0x19 }, + { 0x80f00d, 0x1a }, + { 0x80f012, 0xda }, + { 0x80f013, 0x00 }, + { 0x80f014, 0x00 }, + { 0x80f015, 0x02 }, + { 0x80f01f, 0x82 }, + { 0x80f020, 0x00 }, + { 0x80f029, 0x82 }, + { 0x80f02a, 0x00 }, + { 0x80f077, 0x02 }, + { 0x80f1e6, 0x00 }, +}; + +/* NXP TDA 18218HN tuner init + AF9033_TUNER_TDA18218 = 0xa1 */ +static const struct reg_val tuner_init_tda18218[] = { + {0x800046, 0xa1}, + {0x800057, 0x01}, + {0x800058, 0x01}, + {0x80005f, 0x00}, + {0x800060, 0x00}, + {0x800071, 0x05}, + {0x800072, 0x02}, + {0x800074, 0x01}, + {0x800079, 0x01}, + {0x800093, 0x00}, + {0x800094, 0x00}, + {0x800095, 0x00}, + {0x800096, 0x00}, + {0x8000b3, 0x01}, + {0x8000c3, 0x01}, + {0x8000c4, 0x00}, + {0x80f007, 0x00}, + {0x80f00c, 0x19}, + {0x80f00d, 0x1a}, + {0x80f012, 0xda}, + {0x80f013, 0x00}, + {0x80f014, 0x00}, + {0x80f015, 0x02}, + {0x80f01f, 0x82}, + {0x80f020, 0x00}, + {0x80f029, 0x82}, + {0x80f02a, 0x00}, + {0x80f077, 0x02}, + {0x80f1e6, 0x00}, +}; + +/* FCI FC2580 tuner init */ +static const struct reg_val tuner_init_fc2580[] = { + { 0x800046, 0x32 }, + { 0x800057, 0x01 }, + { 0x800058, 0x00 }, + { 0x80005f, 0x00 }, + { 0x800060, 0x00 }, + { 0x800071, 0x05 }, + { 0x800072, 0x02 }, + { 0x800074, 0x01 }, + { 0x800079, 0x01 }, + { 0x800093, 0x00 }, + { 0x800094, 0x00 }, + { 0x800095, 0x00 }, + { 0x800096, 0x05 }, + { 0x8000b3, 0x01 }, + { 0x8000c3, 0x01 }, + { 0x8000c4, 0x00 }, + { 0x80f007, 0x00 }, + { 0x80f00c, 0x19 }, + { 0x80f00d, 0x1A }, + { 0x80f00e, 0x00 }, + { 0x80f00f, 0x02 }, + { 0x80f010, 0x00 }, + { 0x80f011, 0x02 }, + { 0x80f012, 0x00 }, + { 0x80f013, 0x02 }, + { 0x80f014, 0x00 }, + { 0x80f015, 0x02 }, + { 0x80f01f, 0x96 }, + { 0x80f020, 0x00 }, + { 0x80f029, 0x96 }, + { 0x80f02a, 0x00 }, + { 0x80f077, 0x01 }, + { 0x80f1e6, 0x01 }, +}; + +#endif /* AF9033_PRIV_H */ + diff --git a/drivers/media/dvb-frontends/atbm8830.c b/drivers/media/dvb-frontends/atbm8830.c new file mode 100644 index 000000000000..4e11dc4b1335 --- /dev/null +++ b/drivers/media/dvb-frontends/atbm8830.c @@ -0,0 +1,508 @@ +/* + * Support for AltoBeam GB20600 (a.k.a DMB-TH) demodulator + * ATBM8830, ATBM8831 + * + * Copyright (C) 2009 David T.L. Wong + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include "dvb_frontend.h" + +#include "atbm8830.h" +#include "atbm8830_priv.h" + +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "atbm8830: " args); \ + } while (0) + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +static int atbm8830_write_reg(struct atbm_state *priv, u16 reg, u8 data) +{ + int ret = 0; + u8 dev_addr; + u8 buf1[] = { reg >> 8, reg & 0xFF }; + u8 buf2[] = { data }; + struct i2c_msg msg1 = { .flags = 0, .buf = buf1, .len = 2 }; + struct i2c_msg msg2 = { .flags = 0, .buf = buf2, .len = 1 }; + + dev_addr = priv->config->demod_address; + msg1.addr = dev_addr; + msg2.addr = dev_addr; + + if (debug >= 2) + dprintk("%s: reg=0x%04X, data=0x%02X\n", __func__, reg, data); + + ret = i2c_transfer(priv->i2c, &msg1, 1); + if (ret != 1) + return -EIO; + + ret = i2c_transfer(priv->i2c, &msg2, 1); + return (ret != 1) ? -EIO : 0; +} + +static int atbm8830_read_reg(struct atbm_state *priv, u16 reg, u8 *p_data) +{ + int ret; + u8 dev_addr; + + u8 buf1[] = { reg >> 8, reg & 0xFF }; + u8 buf2[] = { 0 }; + struct i2c_msg msg1 = { .flags = 0, .buf = buf1, .len = 2 }; + struct i2c_msg msg2 = { .flags = I2C_M_RD, .buf = buf2, .len = 1 }; + + dev_addr = priv->config->demod_address; + msg1.addr = dev_addr; + msg2.addr = dev_addr; + + ret = i2c_transfer(priv->i2c, &msg1, 1); + if (ret != 1) { + dprintk("%s: error reg=0x%04x, ret=%i\n", __func__, reg, ret); + return -EIO; + } + + ret = i2c_transfer(priv->i2c, &msg2, 1); + if (ret != 1) + return -EIO; + + *p_data = buf2[0]; + if (debug >= 2) + dprintk("%s: reg=0x%04X, data=0x%02X\n", + __func__, reg, buf2[0]); + + return 0; +} + +/* Lock register latch so that multi-register read is atomic */ +static inline int atbm8830_reglatch_lock(struct atbm_state *priv, int lock) +{ + return atbm8830_write_reg(priv, REG_READ_LATCH, lock ? 1 : 0); +} + +static int set_osc_freq(struct atbm_state *priv, u32 freq /*in kHz*/) +{ + u32 val; + u64 t; + + /* 0x100000 * freq / 30.4MHz */ + t = (u64)0x100000 * freq; + do_div(t, 30400); + val = t; + + atbm8830_write_reg(priv, REG_OSC_CLK, val); + atbm8830_write_reg(priv, REG_OSC_CLK + 1, val >> 8); + atbm8830_write_reg(priv, REG_OSC_CLK + 2, val >> 16); + + return 0; +} + +static int set_if_freq(struct atbm_state *priv, u32 freq /*in kHz*/) +{ + + u32 fs = priv->config->osc_clk_freq; + u64 t; + u32 val; + u8 dat; + + if (freq != 0) { + /* 2 * PI * (freq - fs) / fs * (2 ^ 22) */ + t = (u64) 2 * 31416 * (freq - fs); + t <<= 22; + do_div(t, fs); + do_div(t, 1000); + val = t; + + atbm8830_write_reg(priv, REG_TUNER_BASEBAND, 1); + atbm8830_write_reg(priv, REG_IF_FREQ, val); + atbm8830_write_reg(priv, REG_IF_FREQ+1, val >> 8); + atbm8830_write_reg(priv, REG_IF_FREQ+2, val >> 16); + + atbm8830_read_reg(priv, REG_ADC_CONFIG, &dat); + dat &= 0xFC; + atbm8830_write_reg(priv, REG_ADC_CONFIG, dat); + } else { + /* Zero IF */ + atbm8830_write_reg(priv, REG_TUNER_BASEBAND, 0); + + atbm8830_read_reg(priv, REG_ADC_CONFIG, &dat); + dat &= 0xFC; + dat |= 0x02; + atbm8830_write_reg(priv, REG_ADC_CONFIG, dat); + + if (priv->config->zif_swap_iq) + atbm8830_write_reg(priv, REG_SWAP_I_Q, 0x03); + else + atbm8830_write_reg(priv, REG_SWAP_I_Q, 0x01); + } + + return 0; +} + +static int is_locked(struct atbm_state *priv, u8 *locked) +{ + u8 status; + + atbm8830_read_reg(priv, REG_LOCK_STATUS, &status); + + if (locked != NULL) + *locked = (status == 1); + return 0; +} + +static int set_agc_config(struct atbm_state *priv, + u8 min, u8 max, u8 hold_loop) +{ + /* no effect if both min and max are zero */ + if (!min && !max) + return 0; + + atbm8830_write_reg(priv, REG_AGC_MIN, min); + atbm8830_write_reg(priv, REG_AGC_MAX, max); + atbm8830_write_reg(priv, REG_AGC_HOLD_LOOP, hold_loop); + + return 0; +} + +static int set_static_channel_mode(struct atbm_state *priv) +{ + int i; + + for (i = 0; i < 5; i++) + atbm8830_write_reg(priv, 0x099B + i, 0x08); + + atbm8830_write_reg(priv, 0x095B, 0x7F); + atbm8830_write_reg(priv, 0x09CB, 0x01); + atbm8830_write_reg(priv, 0x09CC, 0x7F); + atbm8830_write_reg(priv, 0x09CD, 0x7F); + atbm8830_write_reg(priv, 0x0E01, 0x20); + + /* For single carrier */ + atbm8830_write_reg(priv, 0x0B03, 0x0A); + atbm8830_write_reg(priv, 0x0935, 0x10); + atbm8830_write_reg(priv, 0x0936, 0x08); + atbm8830_write_reg(priv, 0x093E, 0x08); + atbm8830_write_reg(priv, 0x096E, 0x06); + + /* frame_count_max0 */ + atbm8830_write_reg(priv, 0x0B09, 0x00); + /* frame_count_max1 */ + atbm8830_write_reg(priv, 0x0B0A, 0x08); + + return 0; +} + +static int set_ts_config(struct atbm_state *priv) +{ + const struct atbm8830_config *cfg = priv->config; + + /*Set parallel/serial ts mode*/ + atbm8830_write_reg(priv, REG_TS_SERIAL, cfg->serial_ts ? 1 : 0); + atbm8830_write_reg(priv, REG_TS_CLK_MODE, cfg->serial_ts ? 1 : 0); + /*Set ts sampling edge*/ + atbm8830_write_reg(priv, REG_TS_SAMPLE_EDGE, + cfg->ts_sampling_edge ? 1 : 0); + /*Set ts clock freerun*/ + atbm8830_write_reg(priv, REG_TS_CLK_FREERUN, + cfg->ts_clk_gated ? 0 : 1); + + return 0; +} + +static int atbm8830_init(struct dvb_frontend *fe) +{ + struct atbm_state *priv = fe->demodulator_priv; + const struct atbm8830_config *cfg = priv->config; + + /*Set oscillator frequency*/ + set_osc_freq(priv, cfg->osc_clk_freq); + + /*Set IF frequency*/ + set_if_freq(priv, cfg->if_freq); + + /*Set AGC Config*/ + set_agc_config(priv, cfg->agc_min, cfg->agc_max, + cfg->agc_hold_loop); + + /*Set static channel mode*/ + set_static_channel_mode(priv); + + set_ts_config(priv); + /*Turn off DSP reset*/ + atbm8830_write_reg(priv, 0x000A, 0); + + /*SW version test*/ + atbm8830_write_reg(priv, 0x020C, 11); + + /* Run */ + atbm8830_write_reg(priv, REG_DEMOD_RUN, 1); + + return 0; +} + + +static void atbm8830_release(struct dvb_frontend *fe) +{ + struct atbm_state *state = fe->demodulator_priv; + dprintk("%s\n", __func__); + + kfree(state); +} + +static int atbm8830_set_fe(struct dvb_frontend *fe) +{ + struct atbm_state *priv = fe->demodulator_priv; + int i; + u8 locked = 0; + dprintk("%s\n", __func__); + + /* set frequency */ + if (fe->ops.tuner_ops.set_params) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + fe->ops.tuner_ops.set_params(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + /* start auto lock */ + for (i = 0; i < 10; i++) { + mdelay(100); + dprintk("Try %d\n", i); + is_locked(priv, &locked); + if (locked != 0) { + dprintk("ATBM8830 locked!\n"); + break; + } + } + + return 0; +} + +static int atbm8830_get_fe(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + dprintk("%s\n", __func__); + + /* TODO: get real readings from device */ + /* inversion status */ + c->inversion = INVERSION_OFF; + + /* bandwidth */ + c->bandwidth_hz = 8000000; + + c->code_rate_HP = FEC_AUTO; + c->code_rate_LP = FEC_AUTO; + + c->modulation = QAM_AUTO; + + /* transmission mode */ + c->transmission_mode = TRANSMISSION_MODE_AUTO; + + /* guard interval */ + c->guard_interval = GUARD_INTERVAL_AUTO; + + /* hierarchy */ + c->hierarchy = HIERARCHY_NONE; + + return 0; +} + +static int atbm8830_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *fesettings) +{ + fesettings->min_delay_ms = 0; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static int atbm8830_read_status(struct dvb_frontend *fe, fe_status_t *fe_status) +{ + struct atbm_state *priv = fe->demodulator_priv; + u8 locked = 0; + u8 agc_locked = 0; + + dprintk("%s\n", __func__); + *fe_status = 0; + + is_locked(priv, &locked); + if (locked) { + *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + } + dprintk("%s: fe_status=0x%x\n", __func__, *fe_status); + + atbm8830_read_reg(priv, REG_AGC_LOCK, &agc_locked); + dprintk("AGC Lock: %d\n", agc_locked); + + return 0; +} + +static int atbm8830_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct atbm_state *priv = fe->demodulator_priv; + u32 frame_err; + u8 t; + + dprintk("%s\n", __func__); + + atbm8830_reglatch_lock(priv, 1); + + atbm8830_read_reg(priv, REG_FRAME_ERR_CNT + 1, &t); + frame_err = t & 0x7F; + frame_err <<= 8; + atbm8830_read_reg(priv, REG_FRAME_ERR_CNT, &t); + frame_err |= t; + + atbm8830_reglatch_lock(priv, 0); + + *ber = frame_err * 100 / 32767; + + dprintk("%s: ber=0x%x\n", __func__, *ber); + return 0; +} + +static int atbm8830_read_signal_strength(struct dvb_frontend *fe, u16 *signal) +{ + struct atbm_state *priv = fe->demodulator_priv; + u32 pwm; + u8 t; + + dprintk("%s\n", __func__); + atbm8830_reglatch_lock(priv, 1); + + atbm8830_read_reg(priv, REG_AGC_PWM_VAL + 1, &t); + pwm = t & 0x03; + pwm <<= 8; + atbm8830_read_reg(priv, REG_AGC_PWM_VAL, &t); + pwm |= t; + + atbm8830_reglatch_lock(priv, 0); + + dprintk("AGC PWM = 0x%02X\n", pwm); + pwm = 0x400 - pwm; + + *signal = pwm * 0x10000 / 0x400; + + return 0; +} + +static int atbm8830_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + dprintk("%s\n", __func__); + *snr = 0; + return 0; +} + +static int atbm8830_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + dprintk("%s\n", __func__); + *ucblocks = 0; + return 0; +} + +static int atbm8830_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct atbm_state *priv = fe->demodulator_priv; + + return atbm8830_write_reg(priv, REG_I2C_GATE, enable ? 1 : 0); +} + +static struct dvb_frontend_ops atbm8830_ops = { + .delsys = { SYS_DTMB }, + .info = { + .name = "AltoBeam ATBM8830/8831 DMB-TH", + .frequency_min = 474000000, + .frequency_max = 858000000, + .frequency_stepsize = 10000, + .caps = + FE_CAN_FEC_AUTO | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO + }, + + .release = atbm8830_release, + + .init = atbm8830_init, + .sleep = NULL, + .write = NULL, + .i2c_gate_ctrl = atbm8830_i2c_gate_ctrl, + + .set_frontend = atbm8830_set_fe, + .get_frontend = atbm8830_get_fe, + .get_tune_settings = atbm8830_get_tune_settings, + + .read_status = atbm8830_read_status, + .read_ber = atbm8830_read_ber, + .read_signal_strength = atbm8830_read_signal_strength, + .read_snr = atbm8830_read_snr, + .read_ucblocks = atbm8830_read_ucblocks, +}; + +struct dvb_frontend *atbm8830_attach(const struct atbm8830_config *config, + struct i2c_adapter *i2c) +{ + struct atbm_state *priv = NULL; + u8 data = 0; + + dprintk("%s()\n", __func__); + + if (config == NULL || i2c == NULL) + return NULL; + + priv = kzalloc(sizeof(struct atbm_state), GFP_KERNEL); + if (priv == NULL) + goto error_out; + + priv->config = config; + priv->i2c = i2c; + + /* check if the demod is there */ + if (atbm8830_read_reg(priv, REG_CHIP_ID, &data) != 0) { + dprintk("%s atbm8830/8831 not found at i2c addr 0x%02X\n", + __func__, priv->config->demod_address); + goto error_out; + } + dprintk("atbm8830 chip id: 0x%02X\n", data); + + memcpy(&priv->frontend.ops, &atbm8830_ops, + sizeof(struct dvb_frontend_ops)); + priv->frontend.demodulator_priv = priv; + + atbm8830_init(&priv->frontend); + + atbm8830_i2c_gate_ctrl(&priv->frontend, 1); + + return &priv->frontend; + +error_out: + dprintk("%s() error_out\n", __func__); + kfree(priv); + return NULL; + +} +EXPORT_SYMBOL(atbm8830_attach); + +MODULE_DESCRIPTION("AltoBeam ATBM8830/8831 GB20600 demodulator driver"); +MODULE_AUTHOR("David T. L. Wong "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/atbm8830.h b/drivers/media/dvb-frontends/atbm8830.h similarity index 100% rename from drivers/media/dvb/frontends/atbm8830.h rename to drivers/media/dvb-frontends/atbm8830.h diff --git a/drivers/media/dvb/frontends/atbm8830_priv.h b/drivers/media/dvb-frontends/atbm8830_priv.h similarity index 100% rename from drivers/media/dvb/frontends/atbm8830_priv.h rename to drivers/media/dvb-frontends/atbm8830_priv.h diff --git a/drivers/media/dvb/frontends/au8522.h b/drivers/media/dvb-frontends/au8522.h similarity index 100% rename from drivers/media/dvb/frontends/au8522.h rename to drivers/media/dvb-frontends/au8522.h diff --git a/drivers/media/dvb-frontends/au8522_common.c b/drivers/media/dvb-frontends/au8522_common.c new file mode 100644 index 000000000000..3559ff230045 --- /dev/null +++ b/drivers/media/dvb-frontends/au8522_common.c @@ -0,0 +1,277 @@ +/* + Auvitek AU8522 QAM/8VSB demodulator driver + + Copyright (C) 2008 Steven Toth + Copyright (C) 2008 Devin Heitmueller + Copyright (C) 2005-2008 Auvitek International, Ltd. + Copyright (C) 2012 Michael Krufky + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include "dvb_frontend.h" +#include "au8522_priv.h" + +static int debug; + +#define dprintk(arg...)\ + do { if (debug)\ + printk(arg);\ + } while (0) + +/* Despite the name "hybrid_tuner", the framework works just as well for + hybrid demodulators as well... */ +static LIST_HEAD(hybrid_tuner_instance_list); +static DEFINE_MUTEX(au8522_list_mutex); + +/* 16 bit registers, 8 bit values */ +int au8522_writereg(struct au8522_state *state, u16 reg, u8 data) +{ + int ret; + u8 buf[] = { (reg >> 8) | 0x80, reg & 0xff, data }; + + struct i2c_msg msg = { .addr = state->config->demod_address, + .flags = 0, .buf = buf, .len = 3 }; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + printk("%s: writereg error (reg == 0x%02x, val == 0x%04x, " + "ret == %i)\n", __func__, reg, data, ret); + + return (ret != 1) ? -1 : 0; +} +EXPORT_SYMBOL(au8522_writereg); + +u8 au8522_readreg(struct au8522_state *state, u16 reg) +{ + int ret; + u8 b0[] = { (reg >> 8) | 0x40, reg & 0xff }; + u8 b1[] = { 0 }; + + struct i2c_msg msg[] = { + { .addr = state->config->demod_address, .flags = 0, + .buf = b0, .len = 2 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, + .buf = b1, .len = 1 } }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + printk(KERN_ERR "%s: readreg error (ret == %i)\n", + __func__, ret); + return b1[0]; +} +EXPORT_SYMBOL(au8522_readreg); + +int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct au8522_state *state = fe->demodulator_priv; + + dprintk("%s(%d)\n", __func__, enable); + + if (state->operational_mode == AU8522_ANALOG_MODE) { + /* We're being asked to manage the gate even though we're + not in digital mode. This can occur if we get switched + over to analog mode before the dvb_frontend kernel thread + has completely shutdown */ + return 0; + } + + if (enable) + return au8522_writereg(state, 0x106, 1); + else + return au8522_writereg(state, 0x106, 0); +} +EXPORT_SYMBOL(au8522_i2c_gate_ctrl); + +int au8522_analog_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct au8522_state *state = fe->demodulator_priv; + + dprintk("%s(%d)\n", __func__, enable); + + if (enable) + return au8522_writereg(state, 0x106, 1); + else + return au8522_writereg(state, 0x106, 0); +} +EXPORT_SYMBOL(au8522_analog_i2c_gate_ctrl); + +/* Reset the demod hardware and reset all of the configuration registers + to a default state. */ +int au8522_get_state(struct au8522_state **state, struct i2c_adapter *i2c, + u8 client_address) +{ + int ret; + + mutex_lock(&au8522_list_mutex); + ret = hybrid_tuner_request_state(struct au8522_state, (*state), + hybrid_tuner_instance_list, + i2c, client_address, "au8522"); + mutex_unlock(&au8522_list_mutex); + + return ret; +} +EXPORT_SYMBOL(au8522_get_state); + +void au8522_release_state(struct au8522_state *state) +{ + mutex_lock(&au8522_list_mutex); + if (state != NULL) + hybrid_tuner_release_state(state); + mutex_unlock(&au8522_list_mutex); +} +EXPORT_SYMBOL(au8522_release_state); + +static int au8522_led_gpio_enable(struct au8522_state *state, int onoff) +{ + struct au8522_led_config *led_config = state->config->led_cfg; + u8 val; + + /* bail out if we can't control an LED */ + if (!led_config || !led_config->gpio_output || + !led_config->gpio_output_enable || !led_config->gpio_output_disable) + return 0; + + val = au8522_readreg(state, 0x4000 | + (led_config->gpio_output & ~0xc000)); + if (onoff) { + /* enable GPIO output */ + val &= ~((led_config->gpio_output_enable >> 8) & 0xff); + val |= (led_config->gpio_output_enable & 0xff); + } else { + /* disable GPIO output */ + val &= ~((led_config->gpio_output_disable >> 8) & 0xff); + val |= (led_config->gpio_output_disable & 0xff); + } + return au8522_writereg(state, 0x8000 | + (led_config->gpio_output & ~0xc000), val); +} + +/* led = 0 | off + * led = 1 | signal ok + * led = 2 | signal strong + * led < 0 | only light led if leds are currently off + */ +int au8522_led_ctrl(struct au8522_state *state, int led) +{ + struct au8522_led_config *led_config = state->config->led_cfg; + int i, ret = 0; + + /* bail out if we can't control an LED */ + if (!led_config || !led_config->gpio_leds || + !led_config->num_led_states || !led_config->led_states) + return 0; + + if (led < 0) { + /* if LED is already lit, then leave it as-is */ + if (state->led_state) + return 0; + else + led *= -1; + } + + /* toggle LED if changing state */ + if (state->led_state != led) { + u8 val; + + dprintk("%s: %d\n", __func__, led); + + au8522_led_gpio_enable(state, 1); + + val = au8522_readreg(state, 0x4000 | + (led_config->gpio_leds & ~0xc000)); + + /* start with all leds off */ + for (i = 0; i < led_config->num_led_states; i++) + val &= ~led_config->led_states[i]; + + /* set selected LED state */ + if (led < led_config->num_led_states) + val |= led_config->led_states[led]; + else if (led_config->num_led_states) + val |= + led_config->led_states[led_config->num_led_states - 1]; + + ret = au8522_writereg(state, 0x8000 | + (led_config->gpio_leds & ~0xc000), val); + if (ret < 0) + return ret; + + state->led_state = led; + + if (led == 0) + au8522_led_gpio_enable(state, 0); + } + + return 0; +} +EXPORT_SYMBOL(au8522_led_ctrl); + +int au8522_init(struct dvb_frontend *fe) +{ + struct au8522_state *state = fe->demodulator_priv; + dprintk("%s()\n", __func__); + + state->operational_mode = AU8522_DIGITAL_MODE; + + /* Clear out any state associated with the digital side of the + chip, so that when it gets powered back up it won't think + that it is already tuned */ + state->current_frequency = 0; + + au8522_writereg(state, 0xa4, 1 << 5); + + au8522_i2c_gate_ctrl(fe, 1); + + return 0; +} +EXPORT_SYMBOL(au8522_init); + +int au8522_sleep(struct dvb_frontend *fe) +{ + struct au8522_state *state = fe->demodulator_priv; + dprintk("%s()\n", __func__); + + /* Only power down if the digital side is currently using the chip */ + if (state->operational_mode == AU8522_ANALOG_MODE) { + /* We're not in one of the expected power modes, which means + that the DVB thread is probably telling us to go to sleep + even though the analog frontend has already started using + the chip. So ignore the request */ + return 0; + } + + /* turn off led */ + au8522_led_ctrl(state, 0); + + /* Power down the chip */ + au8522_writereg(state, 0xa4, 1 << 5); + + state->current_frequency = 0; + + return 0; +} +EXPORT_SYMBOL(au8522_sleep); + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Enable verbose debug messages"); + +MODULE_DESCRIPTION("Auvitek AU8522 QAM-B/ATSC Demodulator driver"); +MODULE_AUTHOR("Steven Toth"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/au8522_decoder.c b/drivers/media/dvb-frontends/au8522_decoder.c new file mode 100644 index 000000000000..5243ba6295cc --- /dev/null +++ b/drivers/media/dvb-frontends/au8522_decoder.c @@ -0,0 +1,839 @@ +/* + * Auvitek AU8522 QAM/8VSB demodulator driver and video decoder + * + * Copyright (C) 2009 Devin Heitmueller + * Copyright (C) 2005-2008 Auvitek International, Ltd. + * + * 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. + */ + +/* Developer notes: + * + * VBI support is not yet working + * Enough is implemented here for CVBS and S-Video inputs, but the actual + * analog demodulator code isn't implemented (not needed for xc5000 since it + * has its own demodulator and outputs CVBS) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "au8522.h" +#include "au8522_priv.h" + +MODULE_AUTHOR("Devin Heitmueller"); +MODULE_LICENSE("GPL"); + +static int au8522_analog_debug; + + +module_param_named(analog_debug, au8522_analog_debug, int, 0644); + +MODULE_PARM_DESC(analog_debug, + "Analog debugging messages [0=Off (default) 1=On]"); + +struct au8522_register_config { + u16 reg_name; + u8 reg_val[8]; +}; + + +/* Video Decoder Filter Coefficients + The values are as follows from left to right + 0="ATV RF" 1="ATV RF13" 2="CVBS" 3="S-Video" 4="PAL" 5=CVBS13" 6="SVideo13" +*/ +static const struct au8522_register_config filter_coef[] = { + {AU8522_FILTER_COEF_R410, {0x25, 0x00, 0x25, 0x25, 0x00, 0x00, 0x00} }, + {AU8522_FILTER_COEF_R411, {0x20, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00} }, + {AU8522_FILTER_COEF_R412, {0x03, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00} }, + {AU8522_FILTER_COEF_R413, {0xe6, 0x00, 0xe6, 0xe6, 0x00, 0x00, 0x00} }, + {AU8522_FILTER_COEF_R414, {0x40, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00} }, + {AU8522_FILTER_COEF_R415, {0x1b, 0x00, 0x1b, 0x1b, 0x00, 0x00, 0x00} }, + {AU8522_FILTER_COEF_R416, {0xc0, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x00} }, + {AU8522_FILTER_COEF_R417, {0x04, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00} }, + {AU8522_FILTER_COEF_R418, {0x8c, 0x00, 0x8c, 0x8c, 0x00, 0x00, 0x00} }, + {AU8522_FILTER_COEF_R419, {0xa0, 0x40, 0xa0, 0xa0, 0x40, 0x40, 0x40} }, + {AU8522_FILTER_COEF_R41A, {0x21, 0x09, 0x21, 0x21, 0x09, 0x09, 0x09} }, + {AU8522_FILTER_COEF_R41B, {0x6c, 0x38, 0x6c, 0x6c, 0x38, 0x38, 0x38} }, + {AU8522_FILTER_COEF_R41C, {0x03, 0xff, 0x03, 0x03, 0xff, 0xff, 0xff} }, + {AU8522_FILTER_COEF_R41D, {0xbf, 0xc7, 0xbf, 0xbf, 0xc7, 0xc7, 0xc7} }, + {AU8522_FILTER_COEF_R41E, {0xa0, 0xdf, 0xa0, 0xa0, 0xdf, 0xdf, 0xdf} }, + {AU8522_FILTER_COEF_R41F, {0x10, 0x06, 0x10, 0x10, 0x06, 0x06, 0x06} }, + {AU8522_FILTER_COEF_R420, {0xae, 0x30, 0xae, 0xae, 0x30, 0x30, 0x30} }, + {AU8522_FILTER_COEF_R421, {0xc4, 0x01, 0xc4, 0xc4, 0x01, 0x01, 0x01} }, + {AU8522_FILTER_COEF_R422, {0x54, 0xdd, 0x54, 0x54, 0xdd, 0xdd, 0xdd} }, + {AU8522_FILTER_COEF_R423, {0xd0, 0xaf, 0xd0, 0xd0, 0xaf, 0xaf, 0xaf} }, + {AU8522_FILTER_COEF_R424, {0x1c, 0xf7, 0x1c, 0x1c, 0xf7, 0xf7, 0xf7} }, + {AU8522_FILTER_COEF_R425, {0x76, 0xdb, 0x76, 0x76, 0xdb, 0xdb, 0xdb} }, + {AU8522_FILTER_COEF_R426, {0x61, 0xc0, 0x61, 0x61, 0xc0, 0xc0, 0xc0} }, + {AU8522_FILTER_COEF_R427, {0xd1, 0x2f, 0xd1, 0xd1, 0x2f, 0x2f, 0x2f} }, + {AU8522_FILTER_COEF_R428, {0x84, 0xd8, 0x84, 0x84, 0xd8, 0xd8, 0xd8} }, + {AU8522_FILTER_COEF_R429, {0x06, 0xfb, 0x06, 0x06, 0xfb, 0xfb, 0xfb} }, + {AU8522_FILTER_COEF_R42A, {0x21, 0xd5, 0x21, 0x21, 0xd5, 0xd5, 0xd5} }, + {AU8522_FILTER_COEF_R42B, {0x0a, 0x3e, 0x0a, 0x0a, 0x3e, 0x3e, 0x3e} }, + {AU8522_FILTER_COEF_R42C, {0xe6, 0x15, 0xe6, 0xe6, 0x15, 0x15, 0x15} }, + {AU8522_FILTER_COEF_R42D, {0x01, 0x34, 0x01, 0x01, 0x34, 0x34, 0x34} }, + +}; +#define NUM_FILTER_COEF (sizeof(filter_coef)\ + / sizeof(struct au8522_register_config)) + + +/* Registers 0x060b through 0x0652 are the LP Filter coefficients + The values are as follows from left to right + 0="SIF" 1="ATVRF/ATVRF13" + Note: the "ATVRF/ATVRF13" mode has never been tested +*/ +static const struct au8522_register_config lpfilter_coef[] = { + {0x060b, {0x21, 0x0b} }, + {0x060c, {0xad, 0xad} }, + {0x060d, {0x70, 0xf0} }, + {0x060e, {0xea, 0xe9} }, + {0x060f, {0xdd, 0xdd} }, + {0x0610, {0x08, 0x64} }, + {0x0611, {0x60, 0x60} }, + {0x0612, {0xf8, 0xb2} }, + {0x0613, {0x01, 0x02} }, + {0x0614, {0xe4, 0xb4} }, + {0x0615, {0x19, 0x02} }, + {0x0616, {0xae, 0x2e} }, + {0x0617, {0xee, 0xc5} }, + {0x0618, {0x56, 0x56} }, + {0x0619, {0x30, 0x58} }, + {0x061a, {0xf9, 0xf8} }, + {0x061b, {0x24, 0x64} }, + {0x061c, {0x07, 0x07} }, + {0x061d, {0x30, 0x30} }, + {0x061e, {0xa9, 0xed} }, + {0x061f, {0x09, 0x0b} }, + {0x0620, {0x42, 0xc2} }, + {0x0621, {0x1d, 0x2a} }, + {0x0622, {0xd6, 0x56} }, + {0x0623, {0x95, 0x8b} }, + {0x0624, {0x2b, 0x2b} }, + {0x0625, {0x30, 0x24} }, + {0x0626, {0x3e, 0x3e} }, + {0x0627, {0x62, 0xe2} }, + {0x0628, {0xe9, 0xf5} }, + {0x0629, {0x99, 0x19} }, + {0x062a, {0xd4, 0x11} }, + {0x062b, {0x03, 0x04} }, + {0x062c, {0xb5, 0x85} }, + {0x062d, {0x1e, 0x20} }, + {0x062e, {0x2a, 0xea} }, + {0x062f, {0xd7, 0xd2} }, + {0x0630, {0x15, 0x15} }, + {0x0631, {0xa3, 0xa9} }, + {0x0632, {0x1f, 0x1f} }, + {0x0633, {0xf9, 0xd1} }, + {0x0634, {0xc0, 0xc3} }, + {0x0635, {0x4d, 0x8d} }, + {0x0636, {0x21, 0x31} }, + {0x0637, {0x83, 0x83} }, + {0x0638, {0x08, 0x8c} }, + {0x0639, {0x19, 0x19} }, + {0x063a, {0x45, 0xa5} }, + {0x063b, {0xef, 0xec} }, + {0x063c, {0x8a, 0x8a} }, + {0x063d, {0xf4, 0xf6} }, + {0x063e, {0x8f, 0x8f} }, + {0x063f, {0x44, 0x0c} }, + {0x0640, {0xef, 0xf0} }, + {0x0641, {0x66, 0x66} }, + {0x0642, {0xcc, 0xd2} }, + {0x0643, {0x41, 0x41} }, + {0x0644, {0x63, 0x93} }, + {0x0645, {0x8e, 0x8e} }, + {0x0646, {0xa2, 0x42} }, + {0x0647, {0x7b, 0x7b} }, + {0x0648, {0x04, 0x04} }, + {0x0649, {0x00, 0x00} }, + {0x064a, {0x40, 0x40} }, + {0x064b, {0x8c, 0x98} }, + {0x064c, {0x00, 0x00} }, + {0x064d, {0x63, 0xc3} }, + {0x064e, {0x04, 0x04} }, + {0x064f, {0x20, 0x20} }, + {0x0650, {0x00, 0x00} }, + {0x0651, {0x40, 0x40} }, + {0x0652, {0x01, 0x01} }, +}; +#define NUM_LPFILTER_COEF (sizeof(lpfilter_coef)\ + / sizeof(struct au8522_register_config)) + +static inline struct au8522_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct au8522_state, sd); +} + +static void setup_vbi(struct au8522_state *state, int aud_input) +{ + int i; + + /* These are set to zero regardless of what mode we're in */ + au8522_writereg(state, AU8522_TVDEC_VBI_CTRL_H_REG017H, 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_CTRL_L_REG018H, 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_TOTAL_BITS_REG019H, 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_TUNIT_H_REG01AH, 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_TUNIT_L_REG01BH, 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_THRESH1_REG01CH, 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_PAT2_REG01EH, 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_PAT1_REG01FH, 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_PAT0_REG020H, 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_MASK2_REG021H, + 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_MASK1_REG022H, + 0x00); + au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_MASK0_REG023H, + 0x00); + + /* Setup the VBI registers */ + for (i = 0x30; i < 0x60; i++) + au8522_writereg(state, i, 0x40); + + /* For some reason, every register is 0x40 except register 0x44 + (confirmed via the HVR-950q USB capture) */ + au8522_writereg(state, 0x44, 0x60); + + /* Enable VBI (we always do this regardless of whether the user is + viewing closed caption info) */ + au8522_writereg(state, AU8522_TVDEC_VBI_CTRL_H_REG017H, + AU8522_TVDEC_VBI_CTRL_H_REG017H_CCON); + +} + +static void setup_decoder_defaults(struct au8522_state *state, u8 input_mode) +{ + int i; + int filter_coef_type; + + /* Provide reasonable defaults for picture tuning values */ + au8522_writereg(state, AU8522_TVDEC_SHARPNESSREG009H, 0x07); + au8522_writereg(state, AU8522_TVDEC_BRIGHTNESS_REG00AH, 0xed); + state->brightness = 0xed - 128; + au8522_writereg(state, AU8522_TVDEC_CONTRAST_REG00BH, 0x79); + state->contrast = 0x79; + au8522_writereg(state, AU8522_TVDEC_SATURATION_CB_REG00CH, 0x80); + au8522_writereg(state, AU8522_TVDEC_SATURATION_CR_REG00DH, 0x80); + state->saturation = 0x80; + au8522_writereg(state, AU8522_TVDEC_HUE_H_REG00EH, 0x00); + au8522_writereg(state, AU8522_TVDEC_HUE_L_REG00FH, 0x00); + state->hue = 0x00; + + /* Other decoder registers */ + au8522_writereg(state, AU8522_TVDEC_INT_MASK_REG010H, 0x00); + + if (input_mode == 0x23) { + /* S-Video input mapping */ + au8522_writereg(state, AU8522_VIDEO_MODE_REG011H, 0x04); + } else { + /* All other modes (CVBS/ATVRF etc.) */ + au8522_writereg(state, AU8522_VIDEO_MODE_REG011H, 0x00); + } + + au8522_writereg(state, AU8522_TVDEC_PGA_REG012H, + AU8522_TVDEC_PGA_REG012H_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_MODE_REG015H, + AU8522_TVDEC_COMB_MODE_REG015H_CVBS); + au8522_writereg(state, AU8522_TVDED_DBG_MODE_REG060H, + AU8522_TVDED_DBG_MODE_REG060H_CVBS); + au8522_writereg(state, AU8522_TVDEC_FORMAT_CTRL1_REG061H, + AU8522_TVDEC_FORMAT_CTRL1_REG061H_FIELD_LEN_525 | + AU8522_TVDEC_FORMAT_CTRL1_REG061H_LINE_LEN_63_492 | + AU8522_TVDEC_FORMAT_CTRL1_REG061H_SUBCARRIER_NTSC_MN); + au8522_writereg(state, AU8522_TVDEC_FORMAT_CTRL2_REG062H, + AU8522_TVDEC_FORMAT_CTRL2_REG062H_STD_NTSC); + au8522_writereg(state, AU8522_TVDEC_VCR_DET_LLIM_REG063H, + AU8522_TVDEC_VCR_DET_LLIM_REG063H_CVBS); + au8522_writereg(state, AU8522_TVDEC_VCR_DET_HLIM_REG064H, + AU8522_TVDEC_VCR_DET_HLIM_REG064H_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_VDIF_THR1_REG065H, + AU8522_TVDEC_COMB_VDIF_THR1_REG065H_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_VDIF_THR2_REG066H, + AU8522_TVDEC_COMB_VDIF_THR2_REG066H_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_VDIF_THR3_REG067H, + AU8522_TVDEC_COMB_VDIF_THR3_REG067H_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_NOTCH_THR_REG068H, + AU8522_TVDEC_COMB_NOTCH_THR_REG068H_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_HDIF_THR1_REG069H, + AU8522_TVDEC_COMB_HDIF_THR1_REG069H_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_HDIF_THR2_REG06AH, + AU8522_TVDEC_COMB_HDIF_THR2_REG06AH_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_HDIF_THR3_REG06BH, + AU8522_TVDEC_COMB_HDIF_THR3_REG06BH_CVBS); + if (input_mode == AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13 || + input_mode == AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH24) { + au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH, + AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH_SVIDEO); + au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH, + AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH_SVIDEO); + } else { + au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH, + AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH, + AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH_CVBS); + } + au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR3_REG06EH, + AU8522_TVDEC_COMB_DCDIF_THR3_REG06EH_CVBS); + au8522_writereg(state, AU8522_TVDEC_UV_SEP_THR_REG06FH, + AU8522_TVDEC_UV_SEP_THR_REG06FH_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_DC_THR1_NTSC_REG070H, + AU8522_TVDEC_COMB_DC_THR1_NTSC_REG070H_CVBS); + au8522_writereg(state, AU8522_REG071H, AU8522_REG071H_CVBS); + au8522_writereg(state, AU8522_REG072H, AU8522_REG072H_CVBS); + au8522_writereg(state, AU8522_TVDEC_COMB_DC_THR2_NTSC_REG073H, + AU8522_TVDEC_COMB_DC_THR2_NTSC_REG073H_CVBS); + au8522_writereg(state, AU8522_REG074H, AU8522_REG074H_CVBS); + au8522_writereg(state, AU8522_REG075H, AU8522_REG075H_CVBS); + au8522_writereg(state, AU8522_TVDEC_DCAGC_CTRL_REG077H, + AU8522_TVDEC_DCAGC_CTRL_REG077H_CVBS); + au8522_writereg(state, AU8522_TVDEC_PIC_START_ADJ_REG078H, + AU8522_TVDEC_PIC_START_ADJ_REG078H_CVBS); + au8522_writereg(state, AU8522_TVDEC_AGC_HIGH_LIMIT_REG079H, + AU8522_TVDEC_AGC_HIGH_LIMIT_REG079H_CVBS); + au8522_writereg(state, AU8522_TVDEC_MACROVISION_SYNC_THR_REG07AH, + AU8522_TVDEC_MACROVISION_SYNC_THR_REG07AH_CVBS); + au8522_writereg(state, AU8522_TVDEC_INTRP_CTRL_REG07BH, + AU8522_TVDEC_INTRP_CTRL_REG07BH_CVBS); + au8522_writereg(state, AU8522_TVDEC_AGC_LOW_LIMIT_REG0E4H, + AU8522_TVDEC_AGC_LOW_LIMIT_REG0E4H_CVBS); + au8522_writereg(state, AU8522_TOREGAAGC_REG0E5H, + AU8522_TOREGAAGC_REG0E5H_CVBS); + au8522_writereg(state, AU8522_REG016H, AU8522_REG016H_CVBS); + + setup_vbi(state, 0); + + if (input_mode == AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13 || + input_mode == AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH24) { + /* Despite what the table says, for the HVR-950q we still need + to be in CVBS mode for the S-Video input (reason unknown). */ + /* filter_coef_type = 3; */ + filter_coef_type = 5; + } else { + filter_coef_type = 5; + } + + /* Load the Video Decoder Filter Coefficients */ + for (i = 0; i < NUM_FILTER_COEF; i++) { + au8522_writereg(state, filter_coef[i].reg_name, + filter_coef[i].reg_val[filter_coef_type]); + } + + /* It's not clear what these registers are for, but they are always + set to the same value regardless of what mode we're in */ + au8522_writereg(state, AU8522_REG42EH, 0x87); + au8522_writereg(state, AU8522_REG42FH, 0xa2); + au8522_writereg(state, AU8522_REG430H, 0xbf); + au8522_writereg(state, AU8522_REG431H, 0xcb); + au8522_writereg(state, AU8522_REG432H, 0xa1); + au8522_writereg(state, AU8522_REG433H, 0x41); + au8522_writereg(state, AU8522_REG434H, 0x88); + au8522_writereg(state, AU8522_REG435H, 0xc2); + au8522_writereg(state, AU8522_REG436H, 0x3c); +} + +static void au8522_setup_cvbs_mode(struct au8522_state *state) +{ + /* here we're going to try the pre-programmed route */ + au8522_writereg(state, AU8522_MODULE_CLOCK_CONTROL_REG0A3H, + AU8522_MODULE_CLOCK_CONTROL_REG0A3H_CVBS); + + /* PGA in automatic mode */ + au8522_writereg(state, AU8522_PGA_CONTROL_REG082H, 0x00); + + /* Enable clamping control */ + au8522_writereg(state, AU8522_CLAMPING_CONTROL_REG083H, 0x00); + + au8522_writereg(state, AU8522_INPUT_CONTROL_REG081H, + AU8522_INPUT_CONTROL_REG081H_CVBS_CH1); + + setup_decoder_defaults(state, AU8522_INPUT_CONTROL_REG081H_CVBS_CH1); + + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, + AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS); +} + +static void au8522_setup_cvbs_tuner_mode(struct au8522_state *state) +{ + /* here we're going to try the pre-programmed route */ + au8522_writereg(state, AU8522_MODULE_CLOCK_CONTROL_REG0A3H, + AU8522_MODULE_CLOCK_CONTROL_REG0A3H_CVBS); + + /* It's not clear why we have to have the PGA in automatic mode while + enabling clamp control, but it's what Windows does */ + au8522_writereg(state, AU8522_PGA_CONTROL_REG082H, 0x00); + + /* Enable clamping control */ + au8522_writereg(state, AU8522_CLAMPING_CONTROL_REG083H, 0x0e); + + /* Disable automatic PGA (since the CVBS is coming from the tuner) */ + au8522_writereg(state, AU8522_PGA_CONTROL_REG082H, 0x10); + + /* Set input mode to CVBS on channel 4 with SIF audio input enabled */ + au8522_writereg(state, AU8522_INPUT_CONTROL_REG081H, + AU8522_INPUT_CONTROL_REG081H_CVBS_CH4_SIF); + + setup_decoder_defaults(state, + AU8522_INPUT_CONTROL_REG081H_CVBS_CH4_SIF); + + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, + AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS); +} + +static void au8522_setup_svideo_mode(struct au8522_state *state) +{ + au8522_writereg(state, AU8522_MODULE_CLOCK_CONTROL_REG0A3H, + AU8522_MODULE_CLOCK_CONTROL_REG0A3H_SVIDEO); + + /* Set input to Y on Channe1, C on Channel 3 */ + au8522_writereg(state, AU8522_INPUT_CONTROL_REG081H, + AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13); + + /* PGA in automatic mode */ + au8522_writereg(state, AU8522_PGA_CONTROL_REG082H, 0x00); + + /* Enable clamping control */ + au8522_writereg(state, AU8522_CLAMPING_CONTROL_REG083H, 0x00); + + setup_decoder_defaults(state, + AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13); + + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, + AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS); +} + +/* ----------------------------------------------------------------------- */ + +static void disable_audio_input(struct au8522_state *state) +{ + au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x00); + au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x00); + au8522_writereg(state, AU8522_AUDIO_VOLUME_REG0F4H, 0x00); + + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H, 0x04); + au8522_writereg(state, AU8522_I2S_CTRL_2_REG112H, 0x02); + + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, + AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_SVIDEO); +} + +/* 0=disable, 1=SIF */ +static void set_audio_input(struct au8522_state *state, int aud_input) +{ + int i; + + /* Note that this function needs to be used in conjunction with setting + the input routing via register 0x81 */ + + if (aud_input == AU8522_AUDIO_NONE) { + disable_audio_input(state); + return; + } + + if (aud_input != AU8522_AUDIO_SIF) { + /* The caller asked for a mode we don't currently support */ + printk(KERN_ERR "Unsupported audio mode requested! mode=%d\n", + aud_input); + return; + } + + /* Load the Audio Decoder Filter Coefficients */ + for (i = 0; i < NUM_LPFILTER_COEF; i++) { + au8522_writereg(state, lpfilter_coef[i].reg_name, + lpfilter_coef[i].reg_val[0]); + } + + /* Setup audio */ + au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x00); + au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x00); + au8522_writereg(state, AU8522_AUDIO_VOLUME_REG0F4H, 0x00); + au8522_writereg(state, AU8522_I2C_CONTROL_REG1_REG091H, 0x80); + au8522_writereg(state, AU8522_I2C_CONTROL_REG0_REG090H, 0x84); + msleep(150); + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, 0x00); + msleep(1); + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, 0x9d); + msleep(50); + au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x7F); + au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x7F); + au8522_writereg(state, AU8522_AUDIO_VOLUME_REG0F4H, 0xff); + msleep(80); + au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x7F); + au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x7F); + au8522_writereg(state, AU8522_REG0F9H, AU8522_REG0F9H_AUDIO); + au8522_writereg(state, AU8522_AUDIO_MODE_REG0F1H, 0x82); + msleep(70); + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H, 0x09); + au8522_writereg(state, AU8522_AUDIOFREQ_REG606H, 0x03); + au8522_writereg(state, AU8522_I2S_CTRL_2_REG112H, 0xc2); +} + +/* ----------------------------------------------------------------------- */ + +static int au8522_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct au8522_state *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + state->brightness = ctrl->value; + au8522_writereg(state, AU8522_TVDEC_BRIGHTNESS_REG00AH, + ctrl->value - 128); + break; + case V4L2_CID_CONTRAST: + state->contrast = ctrl->value; + au8522_writereg(state, AU8522_TVDEC_CONTRAST_REG00BH, + ctrl->value); + break; + case V4L2_CID_SATURATION: + state->saturation = ctrl->value; + au8522_writereg(state, AU8522_TVDEC_SATURATION_CB_REG00CH, + ctrl->value); + au8522_writereg(state, AU8522_TVDEC_SATURATION_CR_REG00DH, + ctrl->value); + break; + case V4L2_CID_HUE: + state->hue = ctrl->value; + au8522_writereg(state, AU8522_TVDEC_HUE_H_REG00EH, + ctrl->value >> 8); + au8522_writereg(state, AU8522_TVDEC_HUE_L_REG00FH, + ctrl->value & 0xFF); + break; + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_MUTE: + /* Not yet implemented */ + default: + return -EINVAL; + } + + return 0; +} + +static int au8522_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct au8522_state *state = to_state(sd); + + /* Note that we are using values cached in the state structure instead + of reading the registers due to issues with i2c reads not working + properly/consistently yet on the HVR-950q */ + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = state->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = state->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = state->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = state->hue; + break; + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_MUTE: + /* Not yet supported */ + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int au8522_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct au8522_state *state = to_state(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + reg->val = au8522_readreg(state, reg->reg & 0xffff); + return 0; +} + +static int au8522_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct au8522_state *state = to_state(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + au8522_writereg(state, reg->reg, reg->val & 0xff); + return 0; +} +#endif + +static int au8522_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct au8522_state *state = to_state(sd); + + if (enable) { + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, + 0x01); + msleep(1); + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, + AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS); + } else { + /* This does not completely power down the device + (it only reduces it from around 140ma to 80ma) */ + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, + 1 << 5); + } + return 0; +} + +static int au8522_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) +{ + switch (qc->id) { + case V4L2_CID_CONTRAST: + return v4l2_ctrl_query_fill(qc, 0, 255, 1, + AU8522_TVDEC_CONTRAST_REG00BH_CVBS); + case V4L2_CID_BRIGHTNESS: + return v4l2_ctrl_query_fill(qc, 0, 255, 1, 109); + case V4L2_CID_SATURATION: + return v4l2_ctrl_query_fill(qc, 0, 255, 1, 128); + case V4L2_CID_HUE: + return v4l2_ctrl_query_fill(qc, -32768, 32768, 1, 0); + default: + break; + } + + qc->type = 0; + return -EINVAL; +} + +static int au8522_reset(struct v4l2_subdev *sd, u32 val) +{ + struct au8522_state *state = to_state(sd); + + state->operational_mode = AU8522_ANALOG_MODE; + + /* Clear out any state associated with the digital side of the + chip, so that when it gets powered back up it won't think + that it is already tuned */ + state->current_frequency = 0; + + au8522_writereg(state, 0xa4, 1 << 5); + + return 0; +} + +static int au8522_s_video_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct au8522_state *state = to_state(sd); + + au8522_reset(sd, 0); + + if (input == AU8522_COMPOSITE_CH1) { + au8522_setup_cvbs_mode(state); + } else if (input == AU8522_SVIDEO_CH13) { + au8522_setup_svideo_mode(state); + } else if (input == AU8522_COMPOSITE_CH4_SIF) { + au8522_setup_cvbs_tuner_mode(state); + } else { + printk(KERN_ERR "au8522 mode not currently supported\n"); + return -EINVAL; + } + return 0; +} + +static int au8522_s_audio_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct au8522_state *state = to_state(sd); + set_audio_input(state, input); + return 0; +} + +static int au8522_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + int val = 0; + struct au8522_state *state = to_state(sd); + u8 lock_status; + + /* Interrogate the decoder to see if we are getting a real signal */ + lock_status = au8522_readreg(state, 0x00); + if (lock_status == 0xa2) + vt->signal = 0xffff; + else + vt->signal = 0x00; + + vt->capability |= + V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + + val = V4L2_TUNER_SUB_MONO; + vt->rxsubchans = val; + vt->audmode = V4L2_TUNER_MODE_STEREO; + return 0; +} + +static int au8522_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + struct au8522_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return v4l2_chip_ident_i2c_client(client, chip, state->id, state->rev); +} + +static int au8522_log_status(struct v4l2_subdev *sd) +{ + /* FIXME: Add some status info here */ + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops au8522_core_ops = { + .log_status = au8522_log_status, + .g_chip_ident = au8522_g_chip_ident, + .g_ctrl = au8522_g_ctrl, + .s_ctrl = au8522_s_ctrl, + .queryctrl = au8522_queryctrl, + .reset = au8522_reset, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = au8522_g_register, + .s_register = au8522_s_register, +#endif +}; + +static const struct v4l2_subdev_tuner_ops au8522_tuner_ops = { + .g_tuner = au8522_g_tuner, +}; + +static const struct v4l2_subdev_audio_ops au8522_audio_ops = { + .s_routing = au8522_s_audio_routing, +}; + +static const struct v4l2_subdev_video_ops au8522_video_ops = { + .s_routing = au8522_s_video_routing, + .s_stream = au8522_s_stream, +}; + +static const struct v4l2_subdev_ops au8522_ops = { + .core = &au8522_core_ops, + .tuner = &au8522_tuner_ops, + .audio = &au8522_audio_ops, + .video = &au8522_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int au8522_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct au8522_state *state; + struct v4l2_subdev *sd; + int instance; + struct au8522_config *demod_config; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + return -EIO; + } + + /* allocate memory for the internal state */ + instance = au8522_get_state(&state, client->adapter, client->addr); + switch (instance) { + case 0: + printk(KERN_ERR "au8522_decoder allocation failed\n"); + return -EIO; + case 1: + /* new demod instance */ + printk(KERN_INFO "au8522_decoder creating new instance...\n"); + break; + default: + /* existing demod instance */ + printk(KERN_INFO "au8522_decoder attach existing instance.\n"); + break; + } + + demod_config = kzalloc(sizeof(struct au8522_config), GFP_KERNEL); + if (demod_config == NULL) { + if (instance == 1) + kfree(state); + return -ENOMEM; + } + demod_config->demod_address = 0x8e >> 1; + + state->config = demod_config; + state->i2c = client->adapter; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &au8522_ops); + + state->c = client; + state->vid_input = AU8522_COMPOSITE_CH1; + state->aud_input = AU8522_AUDIO_NONE; + state->id = 8522; + state->rev = 0; + + /* Jam open the i2c gate to the tuner */ + au8522_writereg(state, 0x106, 1); + + return 0; +} + +static int au8522_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + v4l2_device_unregister_subdev(sd); + au8522_release_state(to_state(sd)); + return 0; +} + +static const struct i2c_device_id au8522_id[] = { + {"au8522", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, au8522_id); + +static struct i2c_driver au8522_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "au8522", + }, + .probe = au8522_probe, + .remove = au8522_remove, + .id_table = au8522_id, +}; + +module_i2c_driver(au8522_driver); diff --git a/drivers/media/dvb-frontends/au8522_dig.c b/drivers/media/dvb-frontends/au8522_dig.c new file mode 100644 index 000000000000..a68974f6d708 --- /dev/null +++ b/drivers/media/dvb-frontends/au8522_dig.c @@ -0,0 +1,828 @@ +/* + Auvitek AU8522 QAM/8VSB demodulator driver + + Copyright (C) 2008 Steven Toth + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include +#include +#include +#include +#include "dvb_frontend.h" +#include "au8522.h" +#include "au8522_priv.h" + +static int debug; + +#define dprintk(arg...)\ + do { if (debug)\ + printk(arg);\ + } while (0) + +struct mse2snr_tab { + u16 val; + u16 data; +}; + +/* VSB SNR lookup table */ +static struct mse2snr_tab vsb_mse2snr_tab[] = { + { 0, 270 }, + { 2, 250 }, + { 3, 240 }, + { 5, 230 }, + { 7, 220 }, + { 9, 210 }, + { 12, 200 }, + { 13, 195 }, + { 15, 190 }, + { 17, 185 }, + { 19, 180 }, + { 21, 175 }, + { 24, 170 }, + { 27, 165 }, + { 31, 160 }, + { 32, 158 }, + { 33, 156 }, + { 36, 152 }, + { 37, 150 }, + { 39, 148 }, + { 40, 146 }, + { 41, 144 }, + { 43, 142 }, + { 44, 140 }, + { 48, 135 }, + { 50, 130 }, + { 43, 142 }, + { 53, 125 }, + { 56, 120 }, + { 256, 115 }, +}; + +/* QAM64 SNR lookup table */ +static struct mse2snr_tab qam64_mse2snr_tab[] = { + { 15, 0 }, + { 16, 290 }, + { 17, 288 }, + { 18, 286 }, + { 19, 284 }, + { 20, 282 }, + { 21, 281 }, + { 22, 279 }, + { 23, 277 }, + { 24, 275 }, + { 25, 273 }, + { 26, 271 }, + { 27, 269 }, + { 28, 268 }, + { 29, 266 }, + { 30, 264 }, + { 31, 262 }, + { 32, 260 }, + { 33, 259 }, + { 34, 258 }, + { 35, 256 }, + { 36, 255 }, + { 37, 254 }, + { 38, 252 }, + { 39, 251 }, + { 40, 250 }, + { 41, 249 }, + { 42, 248 }, + { 43, 246 }, + { 44, 245 }, + { 45, 244 }, + { 46, 242 }, + { 47, 241 }, + { 48, 240 }, + { 50, 239 }, + { 51, 238 }, + { 53, 237 }, + { 54, 236 }, + { 56, 235 }, + { 57, 234 }, + { 59, 233 }, + { 60, 232 }, + { 62, 231 }, + { 63, 230 }, + { 65, 229 }, + { 67, 228 }, + { 68, 227 }, + { 70, 226 }, + { 71, 225 }, + { 73, 224 }, + { 74, 223 }, + { 76, 222 }, + { 78, 221 }, + { 80, 220 }, + { 82, 219 }, + { 85, 218 }, + { 88, 217 }, + { 90, 216 }, + { 92, 215 }, + { 93, 214 }, + { 94, 212 }, + { 95, 211 }, + { 97, 210 }, + { 99, 209 }, + { 101, 208 }, + { 102, 207 }, + { 104, 206 }, + { 107, 205 }, + { 111, 204 }, + { 114, 203 }, + { 118, 202 }, + { 122, 201 }, + { 125, 200 }, + { 128, 199 }, + { 130, 198 }, + { 132, 197 }, + { 256, 190 }, +}; + +/* QAM256 SNR lookup table */ +static struct mse2snr_tab qam256_mse2snr_tab[] = { + { 15, 0 }, + { 16, 400 }, + { 17, 398 }, + { 18, 396 }, + { 19, 394 }, + { 20, 392 }, + { 21, 390 }, + { 22, 388 }, + { 23, 386 }, + { 24, 384 }, + { 25, 382 }, + { 26, 380 }, + { 27, 379 }, + { 28, 378 }, + { 29, 377 }, + { 30, 376 }, + { 31, 375 }, + { 32, 374 }, + { 33, 373 }, + { 34, 372 }, + { 35, 371 }, + { 36, 370 }, + { 37, 362 }, + { 38, 354 }, + { 39, 346 }, + { 40, 338 }, + { 41, 330 }, + { 42, 328 }, + { 43, 326 }, + { 44, 324 }, + { 45, 322 }, + { 46, 320 }, + { 47, 319 }, + { 48, 318 }, + { 49, 317 }, + { 50, 316 }, + { 51, 315 }, + { 52, 314 }, + { 53, 313 }, + { 54, 312 }, + { 55, 311 }, + { 56, 310 }, + { 57, 308 }, + { 58, 306 }, + { 59, 304 }, + { 60, 302 }, + { 61, 300 }, + { 62, 298 }, + { 65, 295 }, + { 68, 294 }, + { 70, 293 }, + { 73, 292 }, + { 76, 291 }, + { 78, 290 }, + { 79, 289 }, + { 81, 288 }, + { 82, 287 }, + { 83, 286 }, + { 84, 285 }, + { 85, 284 }, + { 86, 283 }, + { 88, 282 }, + { 89, 281 }, + { 256, 280 }, +}; + +static int au8522_mse2snr_lookup(struct mse2snr_tab *tab, int sz, int mse, + u16 *snr) +{ + int i, ret = -EINVAL; + dprintk("%s()\n", __func__); + + for (i = 0; i < sz; i++) { + if (mse < tab[i].val) { + *snr = tab[i].data; + ret = 0; + break; + } + } + dprintk("%s() snr=%d\n", __func__, *snr); + return ret; +} + +static int au8522_set_if(struct dvb_frontend *fe, enum au8522_if_freq if_freq) +{ + struct au8522_state *state = fe->demodulator_priv; + u8 r0b5, r0b6, r0b7; + char *ifmhz; + + switch (if_freq) { + case AU8522_IF_3_25MHZ: + ifmhz = "3.25"; + r0b5 = 0x00; + r0b6 = 0x3d; + r0b7 = 0xa0; + break; + case AU8522_IF_4MHZ: + ifmhz = "4.00"; + r0b5 = 0x00; + r0b6 = 0x4b; + r0b7 = 0xd9; + break; + case AU8522_IF_6MHZ: + ifmhz = "6.00"; + r0b5 = 0xfb; + r0b6 = 0x8e; + r0b7 = 0x39; + break; + default: + dprintk("%s() IF Frequency not supported\n", __func__); + return -EINVAL; + } + dprintk("%s() %s MHz\n", __func__, ifmhz); + au8522_writereg(state, 0x80b5, r0b5); + au8522_writereg(state, 0x80b6, r0b6); + au8522_writereg(state, 0x80b7, r0b7); + + return 0; +} + +/* VSB Modulation table */ +static struct { + u16 reg; + u16 data; +} VSB_mod_tab[] = { + { 0x8090, 0x84 }, + { 0x4092, 0x11 }, + { 0x2005, 0x00 }, + { 0x8091, 0x80 }, + { 0x80a3, 0x0c }, + { 0x80a4, 0xe8 }, + { 0x8081, 0xc4 }, + { 0x80a5, 0x40 }, + { 0x80a7, 0x40 }, + { 0x80a6, 0x67 }, + { 0x8262, 0x20 }, + { 0x821c, 0x30 }, + { 0x80d8, 0x1a }, + { 0x8227, 0xa0 }, + { 0x8121, 0xff }, + { 0x80a8, 0xf0 }, + { 0x80a9, 0x05 }, + { 0x80aa, 0x77 }, + { 0x80ab, 0xf0 }, + { 0x80ac, 0x05 }, + { 0x80ad, 0x77 }, + { 0x80ae, 0x41 }, + { 0x80af, 0x66 }, + { 0x821b, 0xcc }, + { 0x821d, 0x80 }, + { 0x80a4, 0xe8 }, + { 0x8231, 0x13 }, +}; + +/* QAM64 Modulation table */ +static struct { + u16 reg; + u16 data; +} QAM64_mod_tab[] = { + { 0x00a3, 0x09 }, + { 0x00a4, 0x00 }, + { 0x0081, 0xc4 }, + { 0x00a5, 0x40 }, + { 0x00aa, 0x77 }, + { 0x00ad, 0x77 }, + { 0x00a6, 0x67 }, + { 0x0262, 0x20 }, + { 0x021c, 0x30 }, + { 0x00b8, 0x3e }, + { 0x00b9, 0xf0 }, + { 0x00ba, 0x01 }, + { 0x00bb, 0x18 }, + { 0x00bc, 0x50 }, + { 0x00bd, 0x00 }, + { 0x00be, 0xea }, + { 0x00bf, 0xef }, + { 0x00c0, 0xfc }, + { 0x00c1, 0xbd }, + { 0x00c2, 0x1f }, + { 0x00c3, 0xfc }, + { 0x00c4, 0xdd }, + { 0x00c5, 0xaf }, + { 0x00c6, 0x00 }, + { 0x00c7, 0x38 }, + { 0x00c8, 0x30 }, + { 0x00c9, 0x05 }, + { 0x00ca, 0x4a }, + { 0x00cb, 0xd0 }, + { 0x00cc, 0x01 }, + { 0x00cd, 0xd9 }, + { 0x00ce, 0x6f }, + { 0x00cf, 0xf9 }, + { 0x00d0, 0x70 }, + { 0x00d1, 0xdf }, + { 0x00d2, 0xf7 }, + { 0x00d3, 0xc2 }, + { 0x00d4, 0xdf }, + { 0x00d5, 0x02 }, + { 0x00d6, 0x9a }, + { 0x00d7, 0xd0 }, + { 0x0250, 0x0d }, + { 0x0251, 0xcd }, + { 0x0252, 0xe0 }, + { 0x0253, 0x05 }, + { 0x0254, 0xa7 }, + { 0x0255, 0xff }, + { 0x0256, 0xed }, + { 0x0257, 0x5b }, + { 0x0258, 0xae }, + { 0x0259, 0xe6 }, + { 0x025a, 0x3d }, + { 0x025b, 0x0f }, + { 0x025c, 0x0d }, + { 0x025d, 0xea }, + { 0x025e, 0xf2 }, + { 0x025f, 0x51 }, + { 0x0260, 0xf5 }, + { 0x0261, 0x06 }, + { 0x021a, 0x00 }, + { 0x0546, 0x40 }, + { 0x0210, 0xc7 }, + { 0x0211, 0xaa }, + { 0x0212, 0xab }, + { 0x0213, 0x02 }, + { 0x0502, 0x00 }, + { 0x0121, 0x04 }, + { 0x0122, 0x04 }, + { 0x052e, 0x10 }, + { 0x00a4, 0xca }, + { 0x00a7, 0x40 }, + { 0x0526, 0x01 }, +}; + +/* QAM256 Modulation table */ +static struct { + u16 reg; + u16 data; +} QAM256_mod_tab[] = { + { 0x80a3, 0x09 }, + { 0x80a4, 0x00 }, + { 0x8081, 0xc4 }, + { 0x80a5, 0x40 }, + { 0x80aa, 0x77 }, + { 0x80ad, 0x77 }, + { 0x80a6, 0x67 }, + { 0x8262, 0x20 }, + { 0x821c, 0x30 }, + { 0x80b8, 0x3e }, + { 0x80b9, 0xf0 }, + { 0x80ba, 0x01 }, + { 0x80bb, 0x18 }, + { 0x80bc, 0x50 }, + { 0x80bd, 0x00 }, + { 0x80be, 0xea }, + { 0x80bf, 0xef }, + { 0x80c0, 0xfc }, + { 0x80c1, 0xbd }, + { 0x80c2, 0x1f }, + { 0x80c3, 0xfc }, + { 0x80c4, 0xdd }, + { 0x80c5, 0xaf }, + { 0x80c6, 0x00 }, + { 0x80c7, 0x38 }, + { 0x80c8, 0x30 }, + { 0x80c9, 0x05 }, + { 0x80ca, 0x4a }, + { 0x80cb, 0xd0 }, + { 0x80cc, 0x01 }, + { 0x80cd, 0xd9 }, + { 0x80ce, 0x6f }, + { 0x80cf, 0xf9 }, + { 0x80d0, 0x70 }, + { 0x80d1, 0xdf }, + { 0x80d2, 0xf7 }, + { 0x80d3, 0xc2 }, + { 0x80d4, 0xdf }, + { 0x80d5, 0x02 }, + { 0x80d6, 0x9a }, + { 0x80d7, 0xd0 }, + { 0x8250, 0x0d }, + { 0x8251, 0xcd }, + { 0x8252, 0xe0 }, + { 0x8253, 0x05 }, + { 0x8254, 0xa7 }, + { 0x8255, 0xff }, + { 0x8256, 0xed }, + { 0x8257, 0x5b }, + { 0x8258, 0xae }, + { 0x8259, 0xe6 }, + { 0x825a, 0x3d }, + { 0x825b, 0x0f }, + { 0x825c, 0x0d }, + { 0x825d, 0xea }, + { 0x825e, 0xf2 }, + { 0x825f, 0x51 }, + { 0x8260, 0xf5 }, + { 0x8261, 0x06 }, + { 0x821a, 0x00 }, + { 0x8546, 0x40 }, + { 0x8210, 0x26 }, + { 0x8211, 0xf6 }, + { 0x8212, 0x84 }, + { 0x8213, 0x02 }, + { 0x8502, 0x01 }, + { 0x8121, 0x04 }, + { 0x8122, 0x04 }, + { 0x852e, 0x10 }, + { 0x80a4, 0xca }, + { 0x80a7, 0x40 }, + { 0x8526, 0x01 }, +}; + +static int au8522_enable_modulation(struct dvb_frontend *fe, + fe_modulation_t m) +{ + struct au8522_state *state = fe->demodulator_priv; + int i; + + dprintk("%s(0x%08x)\n", __func__, m); + + switch (m) { + case VSB_8: + dprintk("%s() VSB_8\n", __func__); + for (i = 0; i < ARRAY_SIZE(VSB_mod_tab); i++) + au8522_writereg(state, + VSB_mod_tab[i].reg, + VSB_mod_tab[i].data); + au8522_set_if(fe, state->config->vsb_if); + break; + case QAM_64: + dprintk("%s() QAM 64\n", __func__); + for (i = 0; i < ARRAY_SIZE(QAM64_mod_tab); i++) + au8522_writereg(state, + QAM64_mod_tab[i].reg, + QAM64_mod_tab[i].data); + au8522_set_if(fe, state->config->qam_if); + break; + case QAM_256: + dprintk("%s() QAM 256\n", __func__); + for (i = 0; i < ARRAY_SIZE(QAM256_mod_tab); i++) + au8522_writereg(state, + QAM256_mod_tab[i].reg, + QAM256_mod_tab[i].data); + au8522_set_if(fe, state->config->qam_if); + break; + default: + dprintk("%s() Invalid modulation\n", __func__); + return -EINVAL; + } + + state->current_modulation = m; + + return 0; +} + +/* Talk to the demod, set the FEC, GUARD, QAM settings etc */ +static int au8522_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct au8522_state *state = fe->demodulator_priv; + int ret = -EINVAL; + + dprintk("%s(frequency=%d)\n", __func__, c->frequency); + + if ((state->current_frequency == c->frequency) && + (state->current_modulation == c->modulation)) + return 0; + + if (fe->ops.tuner_ops.set_params) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + ret = fe->ops.tuner_ops.set_params(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + if (ret < 0) + return ret; + + /* Allow the tuner to settle */ + msleep(100); + + au8522_enable_modulation(fe, c->modulation); + + state->current_frequency = c->frequency; + + return 0; +} + +static int au8522_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct au8522_state *state = fe->demodulator_priv; + u8 reg; + u32 tuner_status = 0; + + *status = 0; + + if (state->current_modulation == VSB_8) { + dprintk("%s() Checking VSB_8\n", __func__); + reg = au8522_readreg(state, 0x4088); + if ((reg & 0x03) == 0x03) + *status |= FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI; + } else { + dprintk("%s() Checking QAM\n", __func__); + reg = au8522_readreg(state, 0x4541); + if (reg & 0x80) + *status |= FE_HAS_VITERBI; + if (reg & 0x20) + *status |= FE_HAS_LOCK | FE_HAS_SYNC; + } + + switch (state->config->status_mode) { + case AU8522_DEMODLOCKING: + dprintk("%s() DEMODLOCKING\n", __func__); + if (*status & FE_HAS_VITERBI) + *status |= FE_HAS_CARRIER | FE_HAS_SIGNAL; + break; + case AU8522_TUNERLOCKING: + /* Get the tuner status */ + dprintk("%s() TUNERLOCKING\n", __func__); + if (fe->ops.tuner_ops.get_status) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + fe->ops.tuner_ops.get_status(fe, &tuner_status); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + if (tuner_status) + *status |= FE_HAS_CARRIER | FE_HAS_SIGNAL; + break; + } + state->fe_status = *status; + + if (*status & FE_HAS_LOCK) + /* turn on LED, if it isn't on already */ + au8522_led_ctrl(state, -1); + else + /* turn off LED */ + au8522_led_ctrl(state, 0); + + dprintk("%s() status 0x%08x\n", __func__, *status); + + return 0; +} + +static int au8522_led_status(struct au8522_state *state, const u16 *snr) +{ + struct au8522_led_config *led_config = state->config->led_cfg; + int led; + u16 strong; + + /* bail out if we can't control an LED */ + if (!led_config) + return 0; + + if (0 == (state->fe_status & FE_HAS_LOCK)) + return au8522_led_ctrl(state, 0); + else if (state->current_modulation == QAM_256) + strong = led_config->qam256_strong; + else if (state->current_modulation == QAM_64) + strong = led_config->qam64_strong; + else /* (state->current_modulation == VSB_8) */ + strong = led_config->vsb8_strong; + + if (*snr >= strong) + led = 2; + else + led = 1; + + if ((state->led_state) && + (((strong < *snr) ? (*snr - strong) : (strong - *snr)) <= 10)) + /* snr didn't change enough to bother + * changing the color of the led */ + return 0; + + return au8522_led_ctrl(state, led); +} + +static int au8522_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct au8522_state *state = fe->demodulator_priv; + int ret = -EINVAL; + + dprintk("%s()\n", __func__); + + if (state->current_modulation == QAM_256) + ret = au8522_mse2snr_lookup(qam256_mse2snr_tab, + ARRAY_SIZE(qam256_mse2snr_tab), + au8522_readreg(state, 0x4522), + snr); + else if (state->current_modulation == QAM_64) + ret = au8522_mse2snr_lookup(qam64_mse2snr_tab, + ARRAY_SIZE(qam64_mse2snr_tab), + au8522_readreg(state, 0x4522), + snr); + else /* VSB_8 */ + ret = au8522_mse2snr_lookup(vsb_mse2snr_tab, + ARRAY_SIZE(vsb_mse2snr_tab), + au8522_readreg(state, 0x4311), + snr); + + if (state->config->led_cfg) + au8522_led_status(state, snr); + + return ret; +} + +static int au8522_read_signal_strength(struct dvb_frontend *fe, + u16 *signal_strength) +{ + /* borrowed from lgdt330x.c + * + * Calculate strength from SNR up to 35dB + * Even though the SNR can go higher than 35dB, + * there is some comfort factor in having a range of + * strong signals that can show at 100% + */ + u16 snr; + u32 tmp; + int ret = au8522_read_snr(fe, &snr); + + *signal_strength = 0; + + if (0 == ret) { + /* The following calculation method was chosen + * purely for the sake of code re-use from the + * other demod drivers that use this method */ + + /* Convert from SNR in dB * 10 to 8.24 fixed-point */ + tmp = (snr * ((1 << 24) / 10)); + + /* Convert from 8.24 fixed-point to + * scale the range 0 - 35*2^24 into 0 - 65535*/ + if (tmp >= 8960 * 0x10000) + *signal_strength = 0xffff; + else + *signal_strength = tmp / 8960; + } + + return ret; +} + +static int au8522_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct au8522_state *state = fe->demodulator_priv; + + if (state->current_modulation == VSB_8) + *ucblocks = au8522_readreg(state, 0x4087); + else + *ucblocks = au8522_readreg(state, 0x4543); + + return 0; +} + +static int au8522_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + return au8522_read_ucblocks(fe, ber); +} + +static int au8522_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct au8522_state *state = fe->demodulator_priv; + + c->frequency = state->current_frequency; + c->modulation = state->current_modulation; + + return 0; +} + +static int au8522_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *tune) +{ + tune->min_delay_ms = 1000; + return 0; +} + +static struct dvb_frontend_ops au8522_ops; + + +static void au8522_release(struct dvb_frontend *fe) +{ + struct au8522_state *state = fe->demodulator_priv; + au8522_release_state(state); +} + +struct dvb_frontend *au8522_attach(const struct au8522_config *config, + struct i2c_adapter *i2c) +{ + struct au8522_state *state = NULL; + int instance; + + /* allocate memory for the internal state */ + instance = au8522_get_state(&state, i2c, config->demod_address); + switch (instance) { + case 0: + dprintk("%s state allocation failed\n", __func__); + break; + case 1: + /* new demod instance */ + dprintk("%s using new instance\n", __func__); + break; + default: + /* existing demod instance */ + dprintk("%s using existing instance\n", __func__); + break; + } + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->operational_mode = AU8522_DIGITAL_MODE; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &au8522_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + + state->frontend.ops.analog_ops.i2c_gate_ctrl = au8522_analog_i2c_gate_ctrl; + + if (au8522_init(&state->frontend) != 0) { + printk(KERN_ERR "%s: Failed to initialize correctly\n", + __func__); + goto error; + } + + /* Note: Leaving the I2C gate open here. */ + au8522_i2c_gate_ctrl(&state->frontend, 1); + + return &state->frontend; + +error: + au8522_release_state(state); + return NULL; +} +EXPORT_SYMBOL(au8522_attach); + +static struct dvb_frontend_ops au8522_ops = { + .delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B }, + .info = { + .name = "Auvitek AU8522 QAM/8VSB Frontend", + .frequency_min = 54000000, + .frequency_max = 858000000, + .frequency_stepsize = 62500, + .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB + }, + + .init = au8522_init, + .sleep = au8522_sleep, + .i2c_gate_ctrl = au8522_i2c_gate_ctrl, + .set_frontend = au8522_set_frontend, + .get_frontend = au8522_get_frontend, + .get_tune_settings = au8522_get_tune_settings, + .read_status = au8522_read_status, + .read_ber = au8522_read_ber, + .read_signal_strength = au8522_read_signal_strength, + .read_snr = au8522_read_snr, + .read_ucblocks = au8522_read_ucblocks, + .release = au8522_release, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Enable verbose debug messages"); + +MODULE_DESCRIPTION("Auvitek AU8522 QAM-B/ATSC Demodulator driver"); +MODULE_AUTHOR("Steven Toth"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/au8522_priv.h b/drivers/media/dvb-frontends/au8522_priv.h new file mode 100644 index 000000000000..0529699a27bd --- /dev/null +++ b/drivers/media/dvb-frontends/au8522_priv.h @@ -0,0 +1,446 @@ +/* + Auvitek AU8522 QAM/8VSB demodulator driver + + Copyright (C) 2008 Steven Toth + Copyright (C) 2008 Devin Heitmueller + Copyright (C) 2005-2008 Auvitek International, Ltd. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dvb_frontend.h" +#include "au8522.h" +#include "tuner-i2c.h" + +#define AU8522_ANALOG_MODE 0 +#define AU8522_DIGITAL_MODE 1 + +struct au8522_state { + struct i2c_client *c; + struct i2c_adapter *i2c; + + u8 operational_mode; + + /* Used for sharing of the state between analog and digital mode */ + struct tuner_i2c_props i2c_props; + struct list_head hybrid_tuner_instance_list; + + /* configuration settings */ + const struct au8522_config *config; + + struct dvb_frontend frontend; + + u32 current_frequency; + fe_modulation_t current_modulation; + + u32 fe_status; + unsigned int led_state; + + /* Analog settings */ + struct v4l2_subdev sd; + v4l2_std_id std; + int vid_input; + int aud_input; + u32 id; + u32 rev; + u8 brightness; + u8 contrast; + u8 saturation; + s16 hue; +}; + +/* These are routines shared by both the VSB/QAM demodulator and the analog + decoder */ +int au8522_writereg(struct au8522_state *state, u16 reg, u8 data); +u8 au8522_readreg(struct au8522_state *state, u16 reg); +int au8522_init(struct dvb_frontend *fe); +int au8522_sleep(struct dvb_frontend *fe); + +int au8522_get_state(struct au8522_state **state, struct i2c_adapter *i2c, + u8 client_address); +void au8522_release_state(struct au8522_state *state); +int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable); +int au8522_analog_i2c_gate_ctrl(struct dvb_frontend *fe, int enable); +int au8522_led_ctrl(struct au8522_state *state, int led); + +/* REGISTERS */ +#define AU8522_INPUT_CONTROL_REG081H 0x081 +#define AU8522_PGA_CONTROL_REG082H 0x082 +#define AU8522_CLAMPING_CONTROL_REG083H 0x083 + +#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H 0x0A3 +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H 0x0A4 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H 0x0A5 +#define AU8522_AGC_CONTROL_RANGE_REG0A6H 0x0A6 +#define AU8522_SYSTEM_GAIN_CONTROL_REG0A7H 0x0A7 +#define AU8522_TUNER_AGC_RF_STOP_REG0A8H 0x0A8 +#define AU8522_TUNER_AGC_RF_START_REG0A9H 0x0A9 +#define AU8522_TUNER_RF_AGC_DEFAULT_REG0AAH 0x0AA +#define AU8522_TUNER_AGC_IF_STOP_REG0ABH 0x0AB +#define AU8522_TUNER_AGC_IF_START_REG0ACH 0x0AC +#define AU8522_TUNER_AGC_IF_DEFAULT_REG0ADH 0x0AD +#define AU8522_TUNER_AGC_STEP_REG0AEH 0x0AE +#define AU8522_TUNER_GAIN_STEP_REG0AFH 0x0AF + +/* Receiver registers */ +#define AU8522_FRMREGTHRD1_REG0B0H 0x0B0 +#define AU8522_FRMREGAGC1H_REG0B1H 0x0B1 +#define AU8522_FRMREGSHIFT1_REG0B2H 0x0B2 +#define AU8522_TOREGAGC1_REG0B3H 0x0B3 +#define AU8522_TOREGASHIFT1_REG0B4H 0x0B4 +#define AU8522_FRMREGBBH_REG0B5H 0x0B5 +#define AU8522_FRMREGBBM_REG0B6H 0x0B6 +#define AU8522_FRMREGBBL_REG0B7H 0x0B7 +/* 0xB8 TO 0xD7 are the filter coefficients */ +#define AU8522_FRMREGTHRD2_REG0D8H 0x0D8 +#define AU8522_FRMREGAGC2H_REG0D9H 0x0D9 +#define AU8522_TOREGAGC2_REG0DAH 0x0DA +#define AU8522_TOREGSHIFT2_REG0DBH 0x0DB +#define AU8522_FRMREGPILOTH_REG0DCH 0x0DC +#define AU8522_FRMREGPILOTM_REG0DDH 0x0DD +#define AU8522_FRMREGPILOTL_REG0DEH 0x0DE +#define AU8522_TOREGFREQ_REG0DFH 0x0DF + +#define AU8522_RX_PGA_RFOUT_REG0EBH 0x0EB +#define AU8522_RX_PGA_IFOUT_REG0ECH 0x0EC +#define AU8522_RX_PGA_PGAOUT_REG0EDH 0x0ED + +#define AU8522_CHIP_MODE_REG0FEH 0x0FE + +/* I2C bus control registers */ +#define AU8522_I2C_CONTROL_REG0_REG090H 0x090 +#define AU8522_I2C_CONTROL_REG1_REG091H 0x091 +#define AU8522_I2C_STATUS_REG092H 0x092 +#define AU8522_I2C_WR_DATA0_REG093H 0x093 +#define AU8522_I2C_WR_DATA1_REG094H 0x094 +#define AU8522_I2C_WR_DATA2_REG095H 0x095 +#define AU8522_I2C_WR_DATA3_REG096H 0x096 +#define AU8522_I2C_WR_DATA4_REG097H 0x097 +#define AU8522_I2C_WR_DATA5_REG098H 0x098 +#define AU8522_I2C_WR_DATA6_REG099H 0x099 +#define AU8522_I2C_WR_DATA7_REG09AH 0x09A +#define AU8522_I2C_RD_DATA0_REG09BH 0x09B +#define AU8522_I2C_RD_DATA1_REG09CH 0x09C +#define AU8522_I2C_RD_DATA2_REG09DH 0x09D +#define AU8522_I2C_RD_DATA3_REG09EH 0x09E +#define AU8522_I2C_RD_DATA4_REG09FH 0x09F +#define AU8522_I2C_RD_DATA5_REG0A0H 0x0A0 +#define AU8522_I2C_RD_DATA6_REG0A1H 0x0A1 +#define AU8522_I2C_RD_DATA7_REG0A2H 0x0A2 + +#define AU8522_ENA_USB_REG101H 0x101 + +#define AU8522_I2S_CTRL_0_REG110H 0x110 +#define AU8522_I2S_CTRL_1_REG111H 0x111 +#define AU8522_I2S_CTRL_2_REG112H 0x112 + +#define AU8522_FRMREGFFECONTROL_REG121H 0x121 +#define AU8522_FRMREGDFECONTROL_REG122H 0x122 + +#define AU8522_CARRFREQOFFSET0_REG201H 0x201 +#define AU8522_CARRFREQOFFSET1_REG202H 0x202 + +#define AU8522_DECIMATION_GAIN_REG21AH 0x21A +#define AU8522_FRMREGIFSLP_REG21BH 0x21B +#define AU8522_FRMREGTHRDL2_REG21CH 0x21C +#define AU8522_FRMREGSTEP3DB_REG21DH 0x21D +#define AU8522_DAGC_GAIN_ADJUSTMENT_REG21EH 0x21E +#define AU8522_FRMREGPLLMODE_REG21FH 0x21F +#define AU8522_FRMREGCSTHRD_REG220H 0x220 +#define AU8522_FRMREGCRLOCKDMAX_REG221H 0x221 +#define AU8522_FRMREGCRPERIODMASK_REG222H 0x222 +#define AU8522_FRMREGCRLOCK0THH_REG223H 0x223 +#define AU8522_FRMREGCRLOCK1THH_REG224H 0x224 +#define AU8522_FRMREGCRLOCK0THL_REG225H 0x225 +#define AU8522_FRMREGCRLOCK1THL_REG226H 0x226 +#define AU_FRMREGPLLACQPHASESCL_REG227H 0x227 +#define AU8522_FRMREGFREQFBCTRL_REG228H 0x228 + +/* Analog TV Decoder */ +#define AU8522_TVDEC_STATUS_REG000H 0x000 +#define AU8522_TVDEC_INT_STATUS_REG001H 0x001 +#define AU8522_TVDEC_MACROVISION_STATUS_REG002H 0x002 +#define AU8522_TVDEC_SHARPNESSREG009H 0x009 +#define AU8522_TVDEC_BRIGHTNESS_REG00AH 0x00A +#define AU8522_TVDEC_CONTRAST_REG00BH 0x00B +#define AU8522_TVDEC_SATURATION_CB_REG00CH 0x00C +#define AU8522_TVDEC_SATURATION_CR_REG00DH 0x00D +#define AU8522_TVDEC_HUE_H_REG00EH 0x00E +#define AU8522_TVDEC_HUE_L_REG00FH 0x00F +#define AU8522_TVDEC_INT_MASK_REG010H 0x010 +#define AU8522_VIDEO_MODE_REG011H 0x011 +#define AU8522_TVDEC_PGA_REG012H 0x012 +#define AU8522_TVDEC_COMB_MODE_REG015H 0x015 +#define AU8522_REG016H 0x016 +#define AU8522_TVDED_DBG_MODE_REG060H 0x060 +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H 0x061 +#define AU8522_TVDEC_FORMAT_CTRL2_REG062H 0x062 +#define AU8522_TVDEC_VCR_DET_LLIM_REG063H 0x063 +#define AU8522_TVDEC_VCR_DET_HLIM_REG064H 0x064 +#define AU8522_TVDEC_COMB_VDIF_THR1_REG065H 0x065 +#define AU8522_TVDEC_COMB_VDIF_THR2_REG066H 0x066 +#define AU8522_TVDEC_COMB_VDIF_THR3_REG067H 0x067 +#define AU8522_TVDEC_COMB_NOTCH_THR_REG068H 0x068 +#define AU8522_TVDEC_COMB_HDIF_THR1_REG069H 0x069 +#define AU8522_TVDEC_COMB_HDIF_THR2_REG06AH 0x06A +#define AU8522_TVDEC_COMB_HDIF_THR3_REG06BH 0x06B +#define AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH 0x06C +#define AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH 0x06D +#define AU8522_TVDEC_COMB_DCDIF_THR3_REG06EH 0x06E +#define AU8522_TVDEC_UV_SEP_THR_REG06FH 0x06F +#define AU8522_TVDEC_COMB_DC_THR1_NTSC_REG070H 0x070 +#define AU8522_TVDEC_COMB_DC_THR2_NTSC_REG073H 0x073 +#define AU8522_TVDEC_DCAGC_CTRL_REG077H 0x077 +#define AU8522_TVDEC_PIC_START_ADJ_REG078H 0x078 +#define AU8522_TVDEC_AGC_HIGH_LIMIT_REG079H 0x079 +#define AU8522_TVDEC_MACROVISION_SYNC_THR_REG07AH 0x07A +#define AU8522_TVDEC_INTRP_CTRL_REG07BH 0x07B +#define AU8522_TVDEC_PLL_STATUS_REG07EH 0x07E +#define AU8522_TVDEC_FSC_FREQ_REG07FH 0x07F + +#define AU8522_TVDEC_AGC_LOW_LIMIT_REG0E4H 0x0E4 +#define AU8522_TOREGAAGC_REG0E5H 0x0E5 + +#define AU8522_TVDEC_CHROMA_AGC_REG401H 0x401 +#define AU8522_TVDEC_CHROMA_SFT_REG402H 0x402 +#define AU8522_FILTER_COEF_R410 0x410 +#define AU8522_FILTER_COEF_R411 0x411 +#define AU8522_FILTER_COEF_R412 0x412 +#define AU8522_FILTER_COEF_R413 0x413 +#define AU8522_FILTER_COEF_R414 0x414 +#define AU8522_FILTER_COEF_R415 0x415 +#define AU8522_FILTER_COEF_R416 0x416 +#define AU8522_FILTER_COEF_R417 0x417 +#define AU8522_FILTER_COEF_R418 0x418 +#define AU8522_FILTER_COEF_R419 0x419 +#define AU8522_FILTER_COEF_R41A 0x41A +#define AU8522_FILTER_COEF_R41B 0x41B +#define AU8522_FILTER_COEF_R41C 0x41C +#define AU8522_FILTER_COEF_R41D 0x41D +#define AU8522_FILTER_COEF_R41E 0x41E +#define AU8522_FILTER_COEF_R41F 0x41F +#define AU8522_FILTER_COEF_R420 0x420 +#define AU8522_FILTER_COEF_R421 0x421 +#define AU8522_FILTER_COEF_R422 0x422 +#define AU8522_FILTER_COEF_R423 0x423 +#define AU8522_FILTER_COEF_R424 0x424 +#define AU8522_FILTER_COEF_R425 0x425 +#define AU8522_FILTER_COEF_R426 0x426 +#define AU8522_FILTER_COEF_R427 0x427 +#define AU8522_FILTER_COEF_R428 0x428 +#define AU8522_FILTER_COEF_R429 0x429 +#define AU8522_FILTER_COEF_R42A 0x42A +#define AU8522_FILTER_COEF_R42B 0x42B +#define AU8522_FILTER_COEF_R42C 0x42C +#define AU8522_FILTER_COEF_R42D 0x42D + +/* VBI Control Registers */ +#define AU8522_TVDEC_VBI_RX_FIFO_CONTAIN_REG004H 0x004 +#define AU8522_TVDEC_VBI_TX_FIFO_CONTAIN_REG005H 0x005 +#define AU8522_TVDEC_VBI_RX_FIFO_READ_REG006H 0x006 +#define AU8522_TVDEC_VBI_FIFO_STATUS_REG007H 0x007 +#define AU8522_TVDEC_VBI_CTRL_H_REG017H 0x017 +#define AU8522_TVDEC_VBI_CTRL_L_REG018H 0x018 +#define AU8522_TVDEC_VBI_USER_TOTAL_BITS_REG019H 0x019 +#define AU8522_TVDEC_VBI_USER_TUNIT_H_REG01AH 0x01A +#define AU8522_TVDEC_VBI_USER_TUNIT_L_REG01BH 0x01B +#define AU8522_TVDEC_VBI_USER_THRESH1_REG01CH 0x01C +#define AU8522_TVDEC_VBI_USER_FRAME_PAT2_REG01EH 0x01E +#define AU8522_TVDEC_VBI_USER_FRAME_PAT1_REG01FH 0x01F +#define AU8522_TVDEC_VBI_USER_FRAME_PAT0_REG020H 0x020 +#define AU8522_TVDEC_VBI_USER_FRAME_MASK2_REG021H 0x021 +#define AU8522_TVDEC_VBI_USER_FRAME_MASK1_REG022H 0x022 +#define AU8522_TVDEC_VBI_USER_FRAME_MASK0_REG023H 0x023 + +#define AU8522_REG071H 0x071 +#define AU8522_REG072H 0x072 +#define AU8522_REG074H 0x074 +#define AU8522_REG075H 0x075 + +/* Digital Demodulator Registers */ +#define AU8522_FRAME_COUNT0_REG084H 0x084 +#define AU8522_RS_STATUS_G0_REG085H 0x085 +#define AU8522_RS_STATUS_B0_REG086H 0x086 +#define AU8522_RS_STATUS_E_REG087H 0x087 +#define AU8522_DEMODULATION_STATUS_REG088H 0x088 +#define AU8522_TOREGTRESTATUS_REG0E6H 0x0E6 +#define AU8522_TSPORT_CONTROL_REG10BH 0x10B +#define AU8522_TSTHES_REG10CH 0x10C +#define AU8522_FRMREGDFEKEEP_REG301H 0x301 +#define AU8522_DFE_AVERAGE_REG302H 0x302 +#define AU8522_FRMREGEQLERRWIN_REG303H 0x303 +#define AU8522_FRMREGFFEKEEP_REG304H 0x304 +#define AU8522_FRMREGDFECONTROL1_REG305H 0x305 +#define AU8522_FRMREGEQLERRLOW_REG306H 0x306 + +#define AU8522_REG42EH 0x42E +#define AU8522_REG42FH 0x42F +#define AU8522_REG430H 0x430 +#define AU8522_REG431H 0x431 +#define AU8522_REG432H 0x432 +#define AU8522_REG433H 0x433 +#define AU8522_REG434H 0x434 +#define AU8522_REG435H 0x435 +#define AU8522_REG436H 0x436 + +/* GPIO Registers */ +#define AU8522_GPIO_CONTROL_REG0E0H 0x0E0 +#define AU8522_GPIO_STATUS_REG0E1H 0x0E1 +#define AU8522_GPIO_DATA_REG0E2H 0x0E2 + +/* Audio Control Registers */ +#define AU8522_AUDIOAGC_REG0EEH 0x0EE +#define AU8522_AUDIO_STATUS_REG0F0H 0x0F0 +#define AU8522_AUDIO_MODE_REG0F1H 0x0F1 +#define AU8522_AUDIO_VOLUME_L_REG0F2H 0x0F2 +#define AU8522_AUDIO_VOLUME_R_REG0F3H 0x0F3 +#define AU8522_AUDIO_VOLUME_REG0F4H 0x0F4 +#define AU8522_FRMREGAUPHASE_REG0F7H 0x0F7 +#define AU8522_REG0F9H 0x0F9 + +#define AU8522_AUDIOAGC2_REG605H 0x605 +#define AU8522_AUDIOFREQ_REG606H 0x606 + + +/**************************************************************/ + +/* Format control 1 */ + +/* VCR Mode 7-6 */ +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_VCR_MODE_YES 0x80 +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_VCR_MODE_NO 0x40 +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_VCR_MODE_AUTO 0x00 +/* Field len 5-4 */ +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_FIELD_LEN_625 0x20 +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_FIELD_LEN_525 0x10 +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_FIELD_LEN_AUTO 0x00 +/* Line len (us) 3-2 */ +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_LINE_LEN_64_000 0x0b +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_LINE_LEN_63_492 0x08 +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_LINE_LEN_63_556 0x04 +/* Subcarrier freq 1-0 */ +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_SUBCARRIER_NTSC_AUTO 0x03 +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_SUBCARRIER_NTSC_443 0x02 +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_SUBCARRIER_NTSC_MN 0x01 +#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_SUBCARRIER_NTSC_50 0x00 + +/* Format control 2 */ +#define AU8522_TVDEC_FORMAT_CTRL2_REG062H_STD_AUTODETECT 0x00 +#define AU8522_TVDEC_FORMAT_CTRL2_REG062H_STD_NTSC 0x01 + + +#define AU8522_INPUT_CONTROL_REG081H_ATSC 0xC4 +#define AU8522_INPUT_CONTROL_REG081H_ATVRF 0xC4 +#define AU8522_INPUT_CONTROL_REG081H_ATVRF13 0xC4 +#define AU8522_INPUT_CONTROL_REG081H_J83B64 0xC4 +#define AU8522_INPUT_CONTROL_REG081H_J83B256 0xC4 +#define AU8522_INPUT_CONTROL_REG081H_CVBS 0x20 +#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH1 0xA2 +#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH2 0xA0 +#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH3 0x69 +#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH4 0x68 +#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH4_SIF 0x28 +/* CH1 AS Y,CH3 AS C */ +#define AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13 0x23 +/* CH2 AS Y,CH4 AS C */ +#define AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH24 0x20 +#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_ATSC 0x0C +#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_J83B64 0x09 +#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_J83B256 0x09 +#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_CVBS 0x12 +#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_ATVRF 0x1A +#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_ATVRF13 0x1A +#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_SVIDEO 0x02 + +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CLEAR 0x00 +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_SVIDEO 0x9C +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS 0x9D +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_ATSC 0xE8 +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_J83B256 0xCA +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_J83B64 0xCA +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_ATVRF 0xDD +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_ATVRF13 0xDD +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_PAL 0xDD +#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_FM 0xDD + +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_ATSC 0x80 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_J83B256 0x80 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_J83B64 0x80 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_DONGLE_ATSC 0x40 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_DONGLE_J83B256 0x40 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_DONGLE_J83B64 0x40 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_DONGLE_CLEAR 0x00 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_ATVRF 0x01 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_ATVRF13 0x01 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_SVIDEO 0x04 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_CVBS 0x01 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_PWM 0x03 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_IIS 0x09 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_PAL 0x01 +#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_FM 0x01 + +/* STILL NEED TO BE REFACTORED @@@@@@@@@@@@@@ */ +#define AU8522_TVDEC_CONTRAST_REG00BH_CVBS 0x79 +#define AU8522_TVDEC_SATURATION_CB_REG00CH_CVBS 0x80 +#define AU8522_TVDEC_SATURATION_CR_REG00DH_CVBS 0x80 +#define AU8522_TVDEC_HUE_H_REG00EH_CVBS 0x00 +#define AU8522_TVDEC_HUE_L_REG00FH_CVBS 0x00 +#define AU8522_TVDEC_PGA_REG012H_CVBS 0x0F +#define AU8522_TVDEC_COMB_MODE_REG015H_CVBS 0x00 +#define AU8522_REG016H_CVBS 0x00 +#define AU8522_TVDED_DBG_MODE_REG060H_CVBS 0x00 +#define AU8522_TVDEC_VCR_DET_LLIM_REG063H_CVBS 0x19 +#define AU8522_REG0F9H_AUDIO 0x20 +#define AU8522_TVDEC_VCR_DET_HLIM_REG064H_CVBS 0xA7 +#define AU8522_TVDEC_COMB_VDIF_THR1_REG065H_CVBS 0x0A +#define AU8522_TVDEC_COMB_VDIF_THR2_REG066H_CVBS 0x32 +#define AU8522_TVDEC_COMB_VDIF_THR3_REG067H_CVBS 0x19 +#define AU8522_TVDEC_COMB_NOTCH_THR_REG068H_CVBS 0x23 +#define AU8522_TVDEC_COMB_HDIF_THR1_REG069H_CVBS 0x41 +#define AU8522_TVDEC_COMB_HDIF_THR2_REG06AH_CVBS 0x0A +#define AU8522_TVDEC_COMB_HDIF_THR3_REG06BH_CVBS 0x32 +#define AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH_CVBS 0x34 +#define AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH_SVIDEO 0x2a +#define AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH_CVBS 0x05 +#define AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH_SVIDEO 0x15 +#define AU8522_TVDEC_COMB_DCDIF_THR3_REG06EH_CVBS 0x6E +#define AU8522_TVDEC_UV_SEP_THR_REG06FH_CVBS 0x0F +#define AU8522_TVDEC_COMB_DC_THR1_NTSC_REG070H_CVBS 0x80 +#define AU8522_REG071H_CVBS 0x18 +#define AU8522_REG072H_CVBS 0x30 +#define AU8522_TVDEC_COMB_DC_THR2_NTSC_REG073H_CVBS 0xF0 +#define AU8522_REG074H_CVBS 0x80 +#define AU8522_REG075H_CVBS 0xF0 +#define AU8522_TVDEC_DCAGC_CTRL_REG077H_CVBS 0xFB +#define AU8522_TVDEC_PIC_START_ADJ_REG078H_CVBS 0x04 +#define AU8522_TVDEC_AGC_HIGH_LIMIT_REG079H_CVBS 0x00 +#define AU8522_TVDEC_MACROVISION_SYNC_THR_REG07AH_CVBS 0x00 +#define AU8522_TVDEC_INTRP_CTRL_REG07BH_CVBS 0xEE +#define AU8522_TVDEC_AGC_LOW_LIMIT_REG0E4H_CVBS 0xFE +#define AU8522_TOREGAAGC_REG0E5H_CVBS 0x00 +#define AU8522_TVDEC_VBI6A_REG035H_CVBS 0x40 + +/* Enables Closed captioning */ +#define AU8522_TVDEC_VBI_CTRL_H_REG017H_CCON 0x21 diff --git a/drivers/media/dvb/frontends/bcm3510.c b/drivers/media/dvb-frontends/bcm3510.c similarity index 100% rename from drivers/media/dvb/frontends/bcm3510.c rename to drivers/media/dvb-frontends/bcm3510.c diff --git a/drivers/media/dvb/frontends/bcm3510.h b/drivers/media/dvb-frontends/bcm3510.h similarity index 100% rename from drivers/media/dvb/frontends/bcm3510.h rename to drivers/media/dvb-frontends/bcm3510.h diff --git a/drivers/media/dvb/frontends/bcm3510_priv.h b/drivers/media/dvb-frontends/bcm3510_priv.h similarity index 100% rename from drivers/media/dvb/frontends/bcm3510_priv.h rename to drivers/media/dvb-frontends/bcm3510_priv.h diff --git a/drivers/media/dvb/frontends/bsbe1-d01a.h b/drivers/media/dvb-frontends/bsbe1-d01a.h similarity index 100% rename from drivers/media/dvb/frontends/bsbe1-d01a.h rename to drivers/media/dvb-frontends/bsbe1-d01a.h diff --git a/drivers/media/dvb/frontends/bsbe1.h b/drivers/media/dvb-frontends/bsbe1.h similarity index 100% rename from drivers/media/dvb/frontends/bsbe1.h rename to drivers/media/dvb-frontends/bsbe1.h diff --git a/drivers/media/dvb/frontends/bsru6.h b/drivers/media/dvb-frontends/bsru6.h similarity index 100% rename from drivers/media/dvb/frontends/bsru6.h rename to drivers/media/dvb-frontends/bsru6.h diff --git a/drivers/media/dvb/frontends/cx22700.c b/drivers/media/dvb-frontends/cx22700.c similarity index 100% rename from drivers/media/dvb/frontends/cx22700.c rename to drivers/media/dvb-frontends/cx22700.c diff --git a/drivers/media/dvb/frontends/cx22700.h b/drivers/media/dvb-frontends/cx22700.h similarity index 100% rename from drivers/media/dvb/frontends/cx22700.h rename to drivers/media/dvb-frontends/cx22700.h diff --git a/drivers/media/dvb/frontends/cx22702.c b/drivers/media/dvb-frontends/cx22702.c similarity index 100% rename from drivers/media/dvb/frontends/cx22702.c rename to drivers/media/dvb-frontends/cx22702.c diff --git a/drivers/media/dvb/frontends/cx22702.h b/drivers/media/dvb-frontends/cx22702.h similarity index 100% rename from drivers/media/dvb/frontends/cx22702.h rename to drivers/media/dvb-frontends/cx22702.h diff --git a/drivers/media/dvb/frontends/cx24110.c b/drivers/media/dvb-frontends/cx24110.c similarity index 100% rename from drivers/media/dvb/frontends/cx24110.c rename to drivers/media/dvb-frontends/cx24110.c diff --git a/drivers/media/dvb/frontends/cx24110.h b/drivers/media/dvb-frontends/cx24110.h similarity index 100% rename from drivers/media/dvb/frontends/cx24110.h rename to drivers/media/dvb-frontends/cx24110.h diff --git a/drivers/media/dvb/frontends/cx24113.c b/drivers/media/dvb-frontends/cx24113.c similarity index 100% rename from drivers/media/dvb/frontends/cx24113.c rename to drivers/media/dvb-frontends/cx24113.c diff --git a/drivers/media/dvb/frontends/cx24113.h b/drivers/media/dvb-frontends/cx24113.h similarity index 100% rename from drivers/media/dvb/frontends/cx24113.h rename to drivers/media/dvb-frontends/cx24113.h diff --git a/drivers/media/dvb/frontends/cx24116.c b/drivers/media/dvb-frontends/cx24116.c similarity index 100% rename from drivers/media/dvb/frontends/cx24116.c rename to drivers/media/dvb-frontends/cx24116.c diff --git a/drivers/media/dvb/frontends/cx24116.h b/drivers/media/dvb-frontends/cx24116.h similarity index 100% rename from drivers/media/dvb/frontends/cx24116.h rename to drivers/media/dvb-frontends/cx24116.h diff --git a/drivers/media/dvb/frontends/cx24123.c b/drivers/media/dvb-frontends/cx24123.c similarity index 100% rename from drivers/media/dvb/frontends/cx24123.c rename to drivers/media/dvb-frontends/cx24123.c diff --git a/drivers/media/dvb/frontends/cx24123.h b/drivers/media/dvb-frontends/cx24123.h similarity index 100% rename from drivers/media/dvb/frontends/cx24123.h rename to drivers/media/dvb-frontends/cx24123.h diff --git a/drivers/media/dvb-frontends/cxd2820r.h b/drivers/media/dvb-frontends/cxd2820r.h new file mode 100644 index 000000000000..6acc21c581c5 --- /dev/null +++ b/drivers/media/dvb-frontends/cxd2820r.h @@ -0,0 +1,88 @@ +/* + * Sony CXD2820R demodulator driver + * + * Copyright (C) 2010 Antti Palosaari + * + * 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. + */ + + +#ifndef CXD2820R_H +#define CXD2820R_H + +#include + +#define CXD2820R_GPIO_D (0 << 0) /* disable */ +#define CXD2820R_GPIO_E (1 << 0) /* enable */ +#define CXD2820R_GPIO_O (0 << 1) /* output */ +#define CXD2820R_GPIO_I (1 << 1) /* input */ +#define CXD2820R_GPIO_L (0 << 2) /* output low */ +#define CXD2820R_GPIO_H (1 << 2) /* output high */ + +#define CXD2820R_TS_SERIAL 0x08 +#define CXD2820R_TS_SERIAL_MSB 0x28 +#define CXD2820R_TS_PARALLEL 0x30 +#define CXD2820R_TS_PARALLEL_MSB 0x70 + +struct cxd2820r_config { + /* Demodulator I2C address. + * Driver determines DVB-C slave I2C address automatically from master + * address. + * Default: none, must set + * Values: 0x6c, 0x6d + */ + u8 i2c_address; + + /* TS output mode. + * Default: none, must set. + * Values: + */ + u8 ts_mode; + + /* IF AGC polarity. + * Default: 0 + * Values: 0, 1 + */ + bool if_agc_polarity; + + /* Spectrum inversion. + * Default: 0 + * Values: 0, 1 + */ + bool spec_inv; +}; + + +#if defined(CONFIG_DVB_CXD2820R) || \ + (defined(CONFIG_DVB_CXD2820R_MODULE) && defined(MODULE)) +extern struct dvb_frontend *cxd2820r_attach( + const struct cxd2820r_config *config, + struct i2c_adapter *i2c, + int *gpio_chip_base +); +#else +static inline struct dvb_frontend *cxd2820r_attach( + const struct cxd2820r_config *config, + struct i2c_adapter *i2c, + int *gpio_chip_base +) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +#endif + +#endif /* CXD2820R_H */ diff --git a/drivers/media/dvb-frontends/cxd2820r_c.c b/drivers/media/dvb-frontends/cxd2820r_c.c new file mode 100644 index 000000000000..125a44041011 --- /dev/null +++ b/drivers/media/dvb-frontends/cxd2820r_c.c @@ -0,0 +1,343 @@ +/* + * Sony CXD2820R demodulator driver + * + * Copyright (C) 2010 Antti Palosaari + * + * 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. + */ + + +#include "cxd2820r_priv.h" + +int cxd2820r_set_frontend_c(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i; + u8 buf[2]; + u32 if_freq; + u16 if_ctl; + u64 num; + struct reg_val_mask tab[] = { + { 0x00080, 0x01, 0xff }, + { 0x00081, 0x05, 0xff }, + { 0x00085, 0x07, 0xff }, + { 0x00088, 0x01, 0xff }, + + { 0x00082, 0x20, 0x60 }, + { 0x1016a, 0x48, 0xff }, + { 0x100a5, 0x00, 0x01 }, + { 0x10020, 0x06, 0x07 }, + { 0x10059, 0x50, 0xff }, + { 0x10087, 0x0c, 0x3c }, + { 0x1008b, 0x07, 0xff }, + { 0x1001f, priv->cfg.if_agc_polarity << 7, 0x80 }, + { 0x10070, priv->cfg.ts_mode, 0xff }, + }; + + dev_dbg(&priv->i2c->dev, "%s: frequency=%d symbol_rate=%d\n", __func__, + c->frequency, c->symbol_rate); + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + if (priv->delivery_system != SYS_DVBC_ANNEX_A) { + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, + tab[i].val, tab[i].mask); + if (ret) + goto error; + } + } + + priv->delivery_system = SYS_DVBC_ANNEX_A; + priv->ber_running = 0; /* tune stops BER counter */ + + /* program IF frequency */ + if (fe->ops.tuner_ops.get_if_frequency) { + ret = fe->ops.tuner_ops.get_if_frequency(fe, &if_freq); + if (ret) + goto error; + } else + if_freq = 0; + + dev_dbg(&priv->i2c->dev, "%s: if_freq=%d\n", __func__, if_freq); + + num = if_freq / 1000; /* Hz => kHz */ + num *= 0x4000; + if_ctl = cxd2820r_div_u64_round_closest(num, 41000); + buf[0] = (if_ctl >> 8) & 0x3f; + buf[1] = (if_ctl >> 0) & 0xff; + + ret = cxd2820r_wr_regs(priv, 0x10042, buf, 2); + if (ret) + goto error; + + ret = cxd2820r_wr_reg(priv, 0x000ff, 0x08); + if (ret) + goto error; + + ret = cxd2820r_wr_reg(priv, 0x000fe, 0x01); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_get_frontend_c(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 buf[2]; + + ret = cxd2820r_rd_regs(priv, 0x1001a, buf, 2); + if (ret) + goto error; + + c->symbol_rate = 2500 * ((buf[0] & 0x0f) << 8 | buf[1]); + + ret = cxd2820r_rd_reg(priv, 0x10019, &buf[0]); + if (ret) + goto error; + + switch ((buf[0] >> 0) & 0x07) { + case 0: + c->modulation = QAM_16; + break; + case 1: + c->modulation = QAM_32; + break; + case 2: + c->modulation = QAM_64; + break; + case 3: + c->modulation = QAM_128; + break; + case 4: + c->modulation = QAM_256; + break; + } + + switch ((buf[0] >> 7) & 0x01) { + case 0: + c->inversion = INVERSION_OFF; + break; + case 1: + c->inversion = INVERSION_ON; + break; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_ber_c(struct dvb_frontend *fe, u32 *ber) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[3], start_ber = 0; + *ber = 0; + + if (priv->ber_running) { + ret = cxd2820r_rd_regs(priv, 0x10076, buf, sizeof(buf)); + if (ret) + goto error; + + if ((buf[2] >> 7) & 0x01 || (buf[2] >> 4) & 0x01) { + *ber = (buf[2] & 0x0f) << 16 | buf[1] << 8 | buf[0]; + start_ber = 1; + } + } else { + priv->ber_running = 1; + start_ber = 1; + } + + if (start_ber) { + /* (re)start BER */ + ret = cxd2820r_wr_reg(priv, 0x10079, 0x01); + if (ret) + goto error; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_signal_strength_c(struct dvb_frontend *fe, + u16 *strength) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + u16 tmp; + + ret = cxd2820r_rd_regs(priv, 0x10049, buf, sizeof(buf)); + if (ret) + goto error; + + tmp = (buf[0] & 0x03) << 8 | buf[1]; + tmp = (~tmp & 0x03ff); + + if (tmp == 512) + /* ~no signal */ + tmp = 0; + else if (tmp > 350) + tmp = 350; + + /* scale value to 0x0000-0xffff */ + *strength = tmp * 0xffff / (350-0); + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_snr_c(struct dvb_frontend *fe, u16 *snr) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 tmp; + unsigned int A, B; + /* report SNR in dB * 10 */ + + ret = cxd2820r_rd_reg(priv, 0x10019, &tmp); + if (ret) + goto error; + + if (((tmp >> 0) & 0x03) % 2) { + A = 875; + B = 650; + } else { + A = 950; + B = 760; + } + + ret = cxd2820r_rd_reg(priv, 0x1004d, &tmp); + if (ret) + goto error; + + #define CXD2820R_LOG2_E_24 24204406 /* log2(e) << 24 */ + if (tmp) + *snr = A * (intlog2(B / tmp) >> 5) / (CXD2820R_LOG2_E_24 >> 5) + / 10; + else + *snr = 0; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_ucblocks_c(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + /* no way to read ? */ + return 0; +} + +int cxd2820r_read_status_c(struct dvb_frontend *fe, fe_status_t *status) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + *status = 0; + + ret = cxd2820r_rd_regs(priv, 0x10088, buf, sizeof(buf)); + if (ret) + goto error; + + if (((buf[0] >> 0) & 0x01) == 1) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC; + + if (((buf[1] >> 3) & 0x01) == 1) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + } + } + + dev_dbg(&priv->i2c->dev, "%s: lock=%02x %02x\n", __func__, buf[0], + buf[1]); + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_init_c(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + ret = cxd2820r_wr_reg(priv, 0x00085, 0x07); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_sleep_c(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret, i; + struct reg_val_mask tab[] = { + { 0x000ff, 0x1f, 0xff }, + { 0x00085, 0x00, 0xff }, + { 0x00088, 0x01, 0xff }, + { 0x00081, 0x00, 0xff }, + { 0x00080, 0x00, 0xff }, + }; + + dev_dbg(&priv->i2c->dev, "%s\n", __func__); + + priv->delivery_system = SYS_UNDEFINED; + + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, tab[i].val, + tab[i].mask); + if (ret) + goto error; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_get_tune_settings_c(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 500; + s->step_size = 0; /* no zigzag */ + s->max_drift = 0; + + return 0; +} diff --git a/drivers/media/dvb-frontends/cxd2820r_core.c b/drivers/media/dvb-frontends/cxd2820r_core.c new file mode 100644 index 000000000000..42648643693e --- /dev/null +++ b/drivers/media/dvb-frontends/cxd2820r_core.c @@ -0,0 +1,757 @@ +/* + * Sony CXD2820R demodulator driver + * + * Copyright (C) 2010 Antti Palosaari + * + * 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. + */ + + +#include "cxd2820r_priv.h" + +/* write multiple registers */ +static int cxd2820r_wr_regs_i2c(struct cxd2820r_priv *priv, u8 i2c, u8 reg, + u8 *val, int len) +{ + int ret; + u8 buf[len+1]; + struct i2c_msg msg[1] = { + { + .addr = i2c, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + } + }; + + buf[0] = reg; + memcpy(&buf[1], val, len); + + ret = i2c_transfer(priv->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c wr failed=%d reg=%02x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* read multiple registers */ +static int cxd2820r_rd_regs_i2c(struct cxd2820r_priv *priv, u8 i2c, u8 reg, + u8 *val, int len) +{ + int ret; + u8 buf[len]; + struct i2c_msg msg[2] = { + { + .addr = i2c, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = i2c, + .flags = I2C_M_RD, + .len = sizeof(buf), + .buf = buf, + } + }; + + ret = i2c_transfer(priv->i2c, msg, 2); + if (ret == 2) { + memcpy(val, buf, len); + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c rd failed=%d reg=%02x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + + return ret; +} + +/* write multiple registers */ +int cxd2820r_wr_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, + int len) +{ + int ret; + u8 i2c_addr; + u8 reg = (reginfo >> 0) & 0xff; + u8 bank = (reginfo >> 8) & 0xff; + u8 i2c = (reginfo >> 16) & 0x01; + + /* select I2C */ + if (i2c) + i2c_addr = priv->cfg.i2c_address | (1 << 1); /* DVB-C */ + else + i2c_addr = priv->cfg.i2c_address; /* DVB-T/T2 */ + + /* switch bank if needed */ + if (bank != priv->bank[i2c]) { + ret = cxd2820r_wr_regs_i2c(priv, i2c_addr, 0x00, &bank, 1); + if (ret) + return ret; + priv->bank[i2c] = bank; + } + return cxd2820r_wr_regs_i2c(priv, i2c_addr, reg, val, len); +} + +/* read multiple registers */ +int cxd2820r_rd_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, + int len) +{ + int ret; + u8 i2c_addr; + u8 reg = (reginfo >> 0) & 0xff; + u8 bank = (reginfo >> 8) & 0xff; + u8 i2c = (reginfo >> 16) & 0x01; + + /* select I2C */ + if (i2c) + i2c_addr = priv->cfg.i2c_address | (1 << 1); /* DVB-C */ + else + i2c_addr = priv->cfg.i2c_address; /* DVB-T/T2 */ + + /* switch bank if needed */ + if (bank != priv->bank[i2c]) { + ret = cxd2820r_wr_regs_i2c(priv, i2c_addr, 0x00, &bank, 1); + if (ret) + return ret; + priv->bank[i2c] = bank; + } + return cxd2820r_rd_regs_i2c(priv, i2c_addr, reg, val, len); +} + +/* write single register */ +int cxd2820r_wr_reg(struct cxd2820r_priv *priv, u32 reg, u8 val) +{ + return cxd2820r_wr_regs(priv, reg, &val, 1); +} + +/* read single register */ +int cxd2820r_rd_reg(struct cxd2820r_priv *priv, u32 reg, u8 *val) +{ + return cxd2820r_rd_regs(priv, reg, val, 1); +} + +/* write single register with mask */ +int cxd2820r_wr_reg_mask(struct cxd2820r_priv *priv, u32 reg, u8 val, + u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = cxd2820r_rd_reg(priv, reg, &tmp); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return cxd2820r_wr_reg(priv, reg, val); +} + +int cxd2820r_gpio(struct dvb_frontend *fe, u8 *gpio) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret, i; + u8 tmp0, tmp1; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + /* update GPIOs only when needed */ + if (!memcmp(gpio, priv->gpio, sizeof(priv->gpio))) + return 0; + + tmp0 = 0x00; + tmp1 = 0x00; + for (i = 0; i < sizeof(priv->gpio); i++) { + /* enable / disable */ + if (gpio[i] & CXD2820R_GPIO_E) + tmp0 |= (2 << 6) >> (2 * i); + else + tmp0 |= (1 << 6) >> (2 * i); + + /* input / output */ + if (gpio[i] & CXD2820R_GPIO_I) + tmp1 |= (1 << (3 + i)); + else + tmp1 |= (0 << (3 + i)); + + /* high / low */ + if (gpio[i] & CXD2820R_GPIO_H) + tmp1 |= (1 << (0 + i)); + else + tmp1 |= (0 << (0 + i)); + + dev_dbg(&priv->i2c->dev, "%s: gpio i=%d %02x %02x\n", __func__, + i, tmp0, tmp1); + } + + dev_dbg(&priv->i2c->dev, "%s: wr gpio=%02x %02x\n", __func__, tmp0, + tmp1); + + /* write bits [7:2] */ + ret = cxd2820r_wr_reg_mask(priv, 0x00089, tmp0, 0xfc); + if (ret) + goto error; + + /* write bits [5:0] */ + ret = cxd2820r_wr_reg_mask(priv, 0x0008e, tmp1, 0x3f); + if (ret) + goto error; + + memcpy(priv->gpio, gpio, sizeof(priv->gpio)); + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +/* 64 bit div with round closest, like DIV_ROUND_CLOSEST but 64 bit */ +u32 cxd2820r_div_u64_round_closest(u64 dividend, u32 divisor) +{ + return div_u64(dividend + (divisor / 2), divisor); +} + +static int cxd2820r_set_frontend(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + switch (c->delivery_system) { + case SYS_DVBT: + ret = cxd2820r_init_t(fe); + if (ret < 0) + goto err; + ret = cxd2820r_set_frontend_t(fe); + if (ret < 0) + goto err; + break; + case SYS_DVBT2: + ret = cxd2820r_init_t(fe); + if (ret < 0) + goto err; + ret = cxd2820r_set_frontend_t2(fe); + if (ret < 0) + goto err; + break; + case SYS_DVBC_ANNEX_A: + ret = cxd2820r_init_c(fe); + if (ret < 0) + goto err; + ret = cxd2820r_set_frontend_c(fe); + if (ret < 0) + goto err; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: error state=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + ret = -EINVAL; + break; + } +err: + return ret; +} +static int cxd2820r_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2820r_read_status_t(fe, status); + break; + case SYS_DVBT2: + ret = cxd2820r_read_status_t2(fe, status); + break; + case SYS_DVBC_ANNEX_A: + ret = cxd2820r_read_status_c(fe, status); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int cxd2820r_get_frontend(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + if (priv->delivery_system == SYS_UNDEFINED) + return 0; + + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2820r_get_frontend_t(fe); + break; + case SYS_DVBT2: + ret = cxd2820r_get_frontend_t2(fe); + break; + case SYS_DVBC_ANNEX_A: + ret = cxd2820r_get_frontend_c(fe); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int cxd2820r_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2820r_read_ber_t(fe, ber); + break; + case SYS_DVBT2: + ret = cxd2820r_read_ber_t2(fe, ber); + break; + case SYS_DVBC_ANNEX_A: + ret = cxd2820r_read_ber_c(fe, ber); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int cxd2820r_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2820r_read_signal_strength_t(fe, strength); + break; + case SYS_DVBT2: + ret = cxd2820r_read_signal_strength_t2(fe, strength); + break; + case SYS_DVBC_ANNEX_A: + ret = cxd2820r_read_signal_strength_c(fe, strength); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int cxd2820r_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2820r_read_snr_t(fe, snr); + break; + case SYS_DVBT2: + ret = cxd2820r_read_snr_t2(fe, snr); + break; + case SYS_DVBC_ANNEX_A: + ret = cxd2820r_read_snr_c(fe, snr); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int cxd2820r_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2820r_read_ucblocks_t(fe, ucblocks); + break; + case SYS_DVBT2: + ret = cxd2820r_read_ucblocks_t2(fe, ucblocks); + break; + case SYS_DVBC_ANNEX_A: + ret = cxd2820r_read_ucblocks_c(fe, ucblocks); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int cxd2820r_init(struct dvb_frontend *fe) +{ + return 0; +} + +static int cxd2820r_sleep(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2820r_sleep_t(fe); + break; + case SYS_DVBT2: + ret = cxd2820r_sleep_t2(fe); + break; + case SYS_DVBC_ANNEX_A: + ret = cxd2820r_sleep_c(fe); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int cxd2820r_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + switch (fe->dtv_property_cache.delivery_system) { + case SYS_DVBT: + ret = cxd2820r_get_tune_settings_t(fe, s); + break; + case SYS_DVBT2: + ret = cxd2820r_get_tune_settings_t2(fe, s); + break; + case SYS_DVBC_ANNEX_A: + ret = cxd2820r_get_tune_settings_c(fe, s); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum dvbfe_search cxd2820r_search(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i; + fe_status_t status = 0; + + dev_dbg(&priv->i2c->dev, "%s: delsys=%d\n", __func__, + fe->dtv_property_cache.delivery_system); + + /* switch between DVB-T and DVB-T2 when tune fails */ + if (priv->last_tune_failed) { + if (priv->delivery_system == SYS_DVBT) { + ret = cxd2820r_sleep_t(fe); + if (ret) + goto error; + + c->delivery_system = SYS_DVBT2; + } else if (priv->delivery_system == SYS_DVBT2) { + ret = cxd2820r_sleep_t2(fe); + if (ret) + goto error; + + c->delivery_system = SYS_DVBT; + } + } + + /* set frontend */ + ret = cxd2820r_set_frontend(fe); + if (ret) + goto error; + + + /* frontend lock wait loop count */ + switch (priv->delivery_system) { + case SYS_DVBT: + case SYS_DVBC_ANNEX_A: + i = 20; + break; + case SYS_DVBT2: + i = 40; + break; + case SYS_UNDEFINED: + default: + i = 0; + break; + } + + /* wait frontend lock */ + for (; i > 0; i--) { + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + msleep(50); + ret = cxd2820r_read_status(fe, &status); + if (ret) + goto error; + + if (status & FE_HAS_LOCK) + break; + } + + /* check if we have a valid signal */ + if (status & FE_HAS_LOCK) { + priv->last_tune_failed = 0; + return DVBFE_ALGO_SEARCH_SUCCESS; + } else { + priv->last_tune_failed = 1; + return DVBFE_ALGO_SEARCH_AGAIN; + } + +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return DVBFE_ALGO_SEARCH_ERROR; +} + +static int cxd2820r_get_frontend_algo(struct dvb_frontend *fe) +{ + return DVBFE_ALGO_CUSTOM; +} + +static void cxd2820r_release(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int uninitialized_var(ret); /* silence compiler warning */ + + dev_dbg(&priv->i2c->dev, "%s\n", __func__); + +#ifdef CONFIG_GPIOLIB + /* remove GPIOs */ + if (priv->gpio_chip.label) { + ret = gpiochip_remove(&priv->gpio_chip); + if (ret) + dev_err(&priv->i2c->dev, "%s: gpiochip_remove() " \ + "failed=%d\n", KBUILD_MODNAME, ret); + } +#endif + kfree(priv); + return; +} + +static int cxd2820r_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + + dev_dbg(&priv->i2c->dev, "%s: %d\n", __func__, enable); + + /* Bit 0 of reg 0xdb in bank 0x00 controls I2C repeater */ + return cxd2820r_wr_reg_mask(priv, 0xdb, enable ? 1 : 0, 0x1); +} + +#ifdef CONFIG_GPIOLIB +static int cxd2820r_gpio_direction_output(struct gpio_chip *chip, unsigned nr, + int val) +{ + struct cxd2820r_priv *priv = + container_of(chip, struct cxd2820r_priv, gpio_chip); + u8 gpio[GPIO_COUNT]; + + dev_dbg(&priv->i2c->dev, "%s: nr=%d val=%d\n", __func__, nr, val); + + memcpy(gpio, priv->gpio, sizeof(gpio)); + gpio[nr] = CXD2820R_GPIO_E | CXD2820R_GPIO_O | (val << 2); + + return cxd2820r_gpio(&priv->fe, gpio); +} + +static void cxd2820r_gpio_set(struct gpio_chip *chip, unsigned nr, int val) +{ + struct cxd2820r_priv *priv = + container_of(chip, struct cxd2820r_priv, gpio_chip); + u8 gpio[GPIO_COUNT]; + + dev_dbg(&priv->i2c->dev, "%s: nr=%d val=%d\n", __func__, nr, val); + + memcpy(gpio, priv->gpio, sizeof(gpio)); + gpio[nr] = CXD2820R_GPIO_E | CXD2820R_GPIO_O | (val << 2); + + (void) cxd2820r_gpio(&priv->fe, gpio); + + return; +} + +static int cxd2820r_gpio_get(struct gpio_chip *chip, unsigned nr) +{ + struct cxd2820r_priv *priv = + container_of(chip, struct cxd2820r_priv, gpio_chip); + + dev_dbg(&priv->i2c->dev, "%s: nr=%d\n", __func__, nr); + + return (priv->gpio[nr] >> 2) & 0x01; +} +#endif + +static const struct dvb_frontend_ops cxd2820r_ops = { + .delsys = { SYS_DVBT, SYS_DVBT2, SYS_DVBC_ANNEX_A }, + /* default: DVB-T/T2 */ + .info = { + .name = "Sony CXD2820R", + + .caps = FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_MUTE_TS | + FE_CAN_2G_MODULATION + }, + + .release = cxd2820r_release, + .init = cxd2820r_init, + .sleep = cxd2820r_sleep, + + .get_tune_settings = cxd2820r_get_tune_settings, + .i2c_gate_ctrl = cxd2820r_i2c_gate_ctrl, + + .get_frontend = cxd2820r_get_frontend, + + .get_frontend_algo = cxd2820r_get_frontend_algo, + .search = cxd2820r_search, + + .read_status = cxd2820r_read_status, + .read_snr = cxd2820r_read_snr, + .read_ber = cxd2820r_read_ber, + .read_ucblocks = cxd2820r_read_ucblocks, + .read_signal_strength = cxd2820r_read_signal_strength, +}; + +struct dvb_frontend *cxd2820r_attach(const struct cxd2820r_config *cfg, + struct i2c_adapter *i2c, int *gpio_chip_base +) +{ + struct cxd2820r_priv *priv; + int ret; + u8 tmp, gpio[GPIO_COUNT]; + + priv = kzalloc(sizeof(struct cxd2820r_priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + dev_err(&i2c->dev, "%s: kzalloc() failed\n", + KBUILD_MODNAME); + goto error; + } + + priv->i2c = i2c; + memcpy(&priv->cfg, cfg, sizeof(struct cxd2820r_config)); + memcpy(&priv->fe.ops, &cxd2820r_ops, sizeof(struct dvb_frontend_ops)); + priv->fe.demodulator_priv = priv; + + priv->bank[0] = priv->bank[1] = 0xff; + ret = cxd2820r_rd_reg(priv, 0x000fd, &tmp); + dev_dbg(&priv->i2c->dev, "%s: chip id=%02x\n", __func__, tmp); + if (ret || tmp != 0xe1) + goto error; + + if (gpio_chip_base) { +#ifdef CONFIG_GPIOLIB + /* add GPIOs */ + priv->gpio_chip.label = KBUILD_MODNAME; + priv->gpio_chip.dev = &priv->i2c->dev; + priv->gpio_chip.owner = THIS_MODULE; + priv->gpio_chip.direction_output = + cxd2820r_gpio_direction_output; + priv->gpio_chip.set = cxd2820r_gpio_set; + priv->gpio_chip.get = cxd2820r_gpio_get; + priv->gpio_chip.base = -1; /* dynamic allocation */ + priv->gpio_chip.ngpio = GPIO_COUNT; + priv->gpio_chip.can_sleep = 1; + ret = gpiochip_add(&priv->gpio_chip); + if (ret) + goto error; + + dev_dbg(&priv->i2c->dev, "%s: gpio_chip.base=%d\n", __func__, + priv->gpio_chip.base); + + *gpio_chip_base = priv->gpio_chip.base; +#else + /* + * Use static GPIO configuration if GPIOLIB is undefined. + * This is fallback condition. + */ + gpio[0] = (*gpio_chip_base >> 0) & 0x07; + gpio[1] = (*gpio_chip_base >> 3) & 0x07; + gpio[2] = 0; + ret = cxd2820r_gpio(&priv->fe, gpio); + if (ret) + goto error; +#endif + } + + return &priv->fe; +error: + dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret); + kfree(priv); + return NULL; +} +EXPORT_SYMBOL(cxd2820r_attach); + +MODULE_AUTHOR("Antti Palosaari "); +MODULE_DESCRIPTION("Sony CXD2820R demodulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/cxd2820r_priv.h b/drivers/media/dvb-frontends/cxd2820r_priv.h new file mode 100644 index 000000000000..7ff5f60c83e1 --- /dev/null +++ b/drivers/media/dvb-frontends/cxd2820r_priv.h @@ -0,0 +1,148 @@ +/* + * Sony CXD2820R demodulator driver + * + * Copyright (C) 2010 Antti Palosaari + * + * 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. + */ + + +#ifndef CXD2820R_PRIV_H +#define CXD2820R_PRIV_H + +#include +#include "dvb_frontend.h" +#include "dvb_math.h" +#include "cxd2820r.h" +#include + +struct reg_val_mask { + u32 reg; + u8 val; + u8 mask; +}; + +struct cxd2820r_priv { + struct i2c_adapter *i2c; + struct dvb_frontend fe; + struct cxd2820r_config cfg; + + bool ber_running; + + u8 bank[2]; +#define GPIO_COUNT 3 + u8 gpio[GPIO_COUNT]; +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio_chip; +#endif + + fe_delivery_system_t delivery_system; + bool last_tune_failed; /* for switch between T and T2 tune */ +}; + +/* cxd2820r_core.c */ + +extern int cxd2820r_debug; + +int cxd2820r_gpio(struct dvb_frontend *fe, u8 *gpio); + +int cxd2820r_wr_reg_mask(struct cxd2820r_priv *priv, u32 reg, u8 val, + u8 mask); + +int cxd2820r_wr_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, + int len); + +u32 cxd2820r_div_u64_round_closest(u64 dividend, u32 divisor); + +int cxd2820r_wr_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, + int len); + +int cxd2820r_rd_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, + int len); + +int cxd2820r_wr_reg(struct cxd2820r_priv *priv, u32 reg, u8 val); + +int cxd2820r_rd_reg(struct cxd2820r_priv *priv, u32 reg, u8 *val); + +/* cxd2820r_c.c */ + +int cxd2820r_get_frontend_c(struct dvb_frontend *fe); + +int cxd2820r_set_frontend_c(struct dvb_frontend *fe); + +int cxd2820r_read_status_c(struct dvb_frontend *fe, fe_status_t *status); + +int cxd2820r_read_ber_c(struct dvb_frontend *fe, u32 *ber); + +int cxd2820r_read_signal_strength_c(struct dvb_frontend *fe, u16 *strength); + +int cxd2820r_read_snr_c(struct dvb_frontend *fe, u16 *snr); + +int cxd2820r_read_ucblocks_c(struct dvb_frontend *fe, u32 *ucblocks); + +int cxd2820r_init_c(struct dvb_frontend *fe); + +int cxd2820r_sleep_c(struct dvb_frontend *fe); + +int cxd2820r_get_tune_settings_c(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s); + +/* cxd2820r_t.c */ + +int cxd2820r_get_frontend_t(struct dvb_frontend *fe); + +int cxd2820r_set_frontend_t(struct dvb_frontend *fe); + +int cxd2820r_read_status_t(struct dvb_frontend *fe, fe_status_t *status); + +int cxd2820r_read_ber_t(struct dvb_frontend *fe, u32 *ber); + +int cxd2820r_read_signal_strength_t(struct dvb_frontend *fe, u16 *strength); + +int cxd2820r_read_snr_t(struct dvb_frontend *fe, u16 *snr); + +int cxd2820r_read_ucblocks_t(struct dvb_frontend *fe, u32 *ucblocks); + +int cxd2820r_init_t(struct dvb_frontend *fe); + +int cxd2820r_sleep_t(struct dvb_frontend *fe); + +int cxd2820r_get_tune_settings_t(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s); + +/* cxd2820r_t2.c */ + +int cxd2820r_get_frontend_t2(struct dvb_frontend *fe); + +int cxd2820r_set_frontend_t2(struct dvb_frontend *fe); + +int cxd2820r_read_status_t2(struct dvb_frontend *fe, fe_status_t *status); + +int cxd2820r_read_ber_t2(struct dvb_frontend *fe, u32 *ber); + +int cxd2820r_read_signal_strength_t2(struct dvb_frontend *fe, u16 *strength); + +int cxd2820r_read_snr_t2(struct dvb_frontend *fe, u16 *snr); + +int cxd2820r_read_ucblocks_t2(struct dvb_frontend *fe, u32 *ucblocks); + +int cxd2820r_init_t2(struct dvb_frontend *fe); + +int cxd2820r_sleep_t2(struct dvb_frontend *fe); + +int cxd2820r_get_tune_settings_t2(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s); + +#endif /* CXD2820R_PRIV_H */ diff --git a/drivers/media/dvb-frontends/cxd2820r_t.c b/drivers/media/dvb-frontends/cxd2820r_t.c new file mode 100644 index 000000000000..fa184ca2dd68 --- /dev/null +++ b/drivers/media/dvb-frontends/cxd2820r_t.c @@ -0,0 +1,449 @@ +/* + * Sony CXD2820R demodulator driver + * + * Copyright (C) 2010 Antti Palosaari + * + * 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. + */ + + +#include "cxd2820r_priv.h" + +int cxd2820r_set_frontend_t(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i, bw_i; + u32 if_freq, if_ctl; + u64 num; + u8 buf[3], bw_param; + u8 bw_params1[][5] = { + { 0x17, 0xea, 0xaa, 0xaa, 0xaa }, /* 6 MHz */ + { 0x14, 0x80, 0x00, 0x00, 0x00 }, /* 7 MHz */ + { 0x11, 0xf0, 0x00, 0x00, 0x00 }, /* 8 MHz */ + }; + u8 bw_params2[][2] = { + { 0x1f, 0xdc }, /* 6 MHz */ + { 0x12, 0xf8 }, /* 7 MHz */ + { 0x01, 0xe0 }, /* 8 MHz */ + }; + struct reg_val_mask tab[] = { + { 0x00080, 0x00, 0xff }, + { 0x00081, 0x03, 0xff }, + { 0x00085, 0x07, 0xff }, + { 0x00088, 0x01, 0xff }, + + { 0x00070, priv->cfg.ts_mode, 0xff }, + { 0x000cb, priv->cfg.if_agc_polarity << 6, 0x40 }, + { 0x000a5, 0x00, 0x01 }, + { 0x00082, 0x20, 0x60 }, + { 0x000c2, 0xc3, 0xff }, + { 0x0016a, 0x50, 0xff }, + { 0x00427, 0x41, 0xff }, + }; + + dev_dbg(&priv->i2c->dev, "%s: frequency=%d bandwidth_hz=%d\n", __func__, + c->frequency, c->bandwidth_hz); + + switch (c->bandwidth_hz) { + case 6000000: + bw_i = 0; + bw_param = 2; + break; + case 7000000: + bw_i = 1; + bw_param = 1; + break; + case 8000000: + bw_i = 2; + bw_param = 0; + break; + default: + return -EINVAL; + } + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + if (priv->delivery_system != SYS_DVBT) { + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, + tab[i].val, tab[i].mask); + if (ret) + goto error; + } + } + + priv->delivery_system = SYS_DVBT; + priv->ber_running = 0; /* tune stops BER counter */ + + /* program IF frequency */ + if (fe->ops.tuner_ops.get_if_frequency) { + ret = fe->ops.tuner_ops.get_if_frequency(fe, &if_freq); + if (ret) + goto error; + } else + if_freq = 0; + + dev_dbg(&priv->i2c->dev, "%s: if_freq=%d\n", __func__, if_freq); + + num = if_freq / 1000; /* Hz => kHz */ + num *= 0x1000000; + if_ctl = cxd2820r_div_u64_round_closest(num, 41000); + buf[0] = ((if_ctl >> 16) & 0xff); + buf[1] = ((if_ctl >> 8) & 0xff); + buf[2] = ((if_ctl >> 0) & 0xff); + + ret = cxd2820r_wr_regs(priv, 0x000b6, buf, 3); + if (ret) + goto error; + + ret = cxd2820r_wr_regs(priv, 0x0009f, bw_params1[bw_i], 5); + if (ret) + goto error; + + ret = cxd2820r_wr_reg_mask(priv, 0x000d7, bw_param << 6, 0xc0); + if (ret) + goto error; + + ret = cxd2820r_wr_regs(priv, 0x000d9, bw_params2[bw_i], 2); + if (ret) + goto error; + + ret = cxd2820r_wr_reg(priv, 0x000ff, 0x08); + if (ret) + goto error; + + ret = cxd2820r_wr_reg(priv, 0x000fe, 0x01); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_get_frontend_t(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 buf[2]; + + ret = cxd2820r_rd_regs(priv, 0x0002f, buf, sizeof(buf)); + if (ret) + goto error; + + switch ((buf[0] >> 6) & 0x03) { + case 0: + c->modulation = QPSK; + break; + case 1: + c->modulation = QAM_16; + break; + case 2: + c->modulation = QAM_64; + break; + } + + switch ((buf[1] >> 1) & 0x03) { + case 0: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case 1: + c->transmission_mode = TRANSMISSION_MODE_8K; + break; + } + + switch ((buf[1] >> 3) & 0x03) { + case 0: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + switch ((buf[0] >> 3) & 0x07) { + case 0: + c->hierarchy = HIERARCHY_NONE; + break; + case 1: + c->hierarchy = HIERARCHY_1; + break; + case 2: + c->hierarchy = HIERARCHY_2; + break; + case 3: + c->hierarchy = HIERARCHY_4; + break; + } + + switch ((buf[0] >> 0) & 0x07) { + case 0: + c->code_rate_HP = FEC_1_2; + break; + case 1: + c->code_rate_HP = FEC_2_3; + break; + case 2: + c->code_rate_HP = FEC_3_4; + break; + case 3: + c->code_rate_HP = FEC_5_6; + break; + case 4: + c->code_rate_HP = FEC_7_8; + break; + } + + switch ((buf[1] >> 5) & 0x07) { + case 0: + c->code_rate_LP = FEC_1_2; + break; + case 1: + c->code_rate_LP = FEC_2_3; + break; + case 2: + c->code_rate_LP = FEC_3_4; + break; + case 3: + c->code_rate_LP = FEC_5_6; + break; + case 4: + c->code_rate_LP = FEC_7_8; + break; + } + + ret = cxd2820r_rd_reg(priv, 0x007c6, &buf[0]); + if (ret) + goto error; + + switch ((buf[0] >> 0) & 0x01) { + case 0: + c->inversion = INVERSION_OFF; + break; + case 1: + c->inversion = INVERSION_ON; + break; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_ber_t(struct dvb_frontend *fe, u32 *ber) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[3], start_ber = 0; + *ber = 0; + + if (priv->ber_running) { + ret = cxd2820r_rd_regs(priv, 0x00076, buf, sizeof(buf)); + if (ret) + goto error; + + if ((buf[2] >> 7) & 0x01 || (buf[2] >> 4) & 0x01) { + *ber = (buf[2] & 0x0f) << 16 | buf[1] << 8 | buf[0]; + start_ber = 1; + } + } else { + priv->ber_running = 1; + start_ber = 1; + } + + if (start_ber) { + /* (re)start BER */ + ret = cxd2820r_wr_reg(priv, 0x00079, 0x01); + if (ret) + goto error; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_signal_strength_t(struct dvb_frontend *fe, + u16 *strength) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + u16 tmp; + + ret = cxd2820r_rd_regs(priv, 0x00026, buf, sizeof(buf)); + if (ret) + goto error; + + tmp = (buf[0] & 0x0f) << 8 | buf[1]; + tmp = ~tmp & 0x0fff; + + /* scale value to 0x0000-0xffff from 0x0000-0x0fff */ + *strength = tmp * 0xffff / 0x0fff; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_snr_t(struct dvb_frontend *fe, u16 *snr) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + u16 tmp; + /* report SNR in dB * 10 */ + + ret = cxd2820r_rd_regs(priv, 0x00028, buf, sizeof(buf)); + if (ret) + goto error; + + tmp = (buf[0] & 0x1f) << 8 | buf[1]; + #define CXD2820R_LOG10_8_24 15151336 /* log10(8) << 24 */ + if (tmp) + *snr = (intlog10(tmp) - CXD2820R_LOG10_8_24) / ((1 << 24) + / 100); + else + *snr = 0; + + dev_dbg(&priv->i2c->dev, "%s: dBx10=%d val=%04x\n", __func__, *snr, + tmp); + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_ucblocks_t(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + /* no way to read ? */ + return 0; +} + +int cxd2820r_read_status_t(struct dvb_frontend *fe, fe_status_t *status) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[4]; + *status = 0; + + ret = cxd2820r_rd_reg(priv, 0x00010, &buf[0]); + if (ret) + goto error; + + if ((buf[0] & 0x07) == 6) { + ret = cxd2820r_rd_reg(priv, 0x00073, &buf[1]); + if (ret) + goto error; + + if (((buf[1] >> 3) & 0x01) == 1) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + } else { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC; + } + } else { + ret = cxd2820r_rd_reg(priv, 0x00014, &buf[2]); + if (ret) + goto error; + + if ((buf[2] & 0x0f) >= 4) { + ret = cxd2820r_rd_reg(priv, 0x00a14, &buf[3]); + if (ret) + goto error; + + if (((buf[3] >> 4) & 0x01) == 1) + *status |= FE_HAS_SIGNAL; + } + } + + dev_dbg(&priv->i2c->dev, "%s: lock=%*ph\n", __func__, 4, buf); + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_init_t(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + + ret = cxd2820r_wr_reg(priv, 0x00085, 0x07); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_sleep_t(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret, i; + struct reg_val_mask tab[] = { + { 0x000ff, 0x1f, 0xff }, + { 0x00085, 0x00, 0xff }, + { 0x00088, 0x01, 0xff }, + { 0x00081, 0x00, 0xff }, + { 0x00080, 0x00, 0xff }, + }; + + dev_dbg(&priv->i2c->dev, "%s\n", __func__); + + priv->delivery_system = SYS_UNDEFINED; + + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, tab[i].val, + tab[i].mask); + if (ret) + goto error; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_get_tune_settings_t(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 500; + s->step_size = fe->ops.info.frequency_stepsize * 2; + s->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; + + return 0; +} diff --git a/drivers/media/dvb-frontends/cxd2820r_t2.c b/drivers/media/dvb-frontends/cxd2820r_t2.c new file mode 100644 index 000000000000..e82d82a7a2eb --- /dev/null +++ b/drivers/media/dvb-frontends/cxd2820r_t2.c @@ -0,0 +1,423 @@ +/* + * Sony CXD2820R demodulator driver + * + * Copyright (C) 2010 Antti Palosaari + * + * 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. + */ + + +#include "cxd2820r_priv.h" + +int cxd2820r_set_frontend_t2(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i, bw_i; + u32 if_freq, if_ctl; + u64 num; + u8 buf[3], bw_param; + u8 bw_params1[][5] = { + { 0x1c, 0xb3, 0x33, 0x33, 0x33 }, /* 5 MHz */ + { 0x17, 0xea, 0xaa, 0xaa, 0xaa }, /* 6 MHz */ + { 0x14, 0x80, 0x00, 0x00, 0x00 }, /* 7 MHz */ + { 0x11, 0xf0, 0x00, 0x00, 0x00 }, /* 8 MHz */ + }; + struct reg_val_mask tab[] = { + { 0x00080, 0x02, 0xff }, + { 0x00081, 0x20, 0xff }, + { 0x00085, 0x07, 0xff }, + { 0x00088, 0x01, 0xff }, + { 0x02069, 0x01, 0xff }, + + { 0x0207f, 0x2a, 0xff }, + { 0x02082, 0x0a, 0xff }, + { 0x02083, 0x0a, 0xff }, + { 0x020cb, priv->cfg.if_agc_polarity << 6, 0x40 }, + { 0x02070, priv->cfg.ts_mode, 0xff }, + { 0x020b5, priv->cfg.spec_inv << 4, 0x10 }, + { 0x02567, 0x07, 0x0f }, + { 0x02569, 0x03, 0x03 }, + { 0x02595, 0x1a, 0xff }, + { 0x02596, 0x50, 0xff }, + { 0x02a8c, 0x00, 0xff }, + { 0x02a8d, 0x34, 0xff }, + { 0x02a45, 0x06, 0x07 }, + { 0x03f10, 0x0d, 0xff }, + { 0x03f11, 0x02, 0xff }, + { 0x03f12, 0x01, 0xff }, + { 0x03f23, 0x2c, 0xff }, + { 0x03f51, 0x13, 0xff }, + { 0x03f52, 0x01, 0xff }, + { 0x03f53, 0x00, 0xff }, + { 0x027e6, 0x14, 0xff }, + { 0x02786, 0x02, 0x07 }, + { 0x02787, 0x40, 0xe0 }, + { 0x027ef, 0x10, 0x18 }, + }; + + dev_dbg(&priv->i2c->dev, "%s: frequency=%d bandwidth_hz=%d\n", __func__, + c->frequency, c->bandwidth_hz); + + switch (c->bandwidth_hz) { + case 5000000: + bw_i = 0; + bw_param = 3; + break; + case 6000000: + bw_i = 1; + bw_param = 2; + break; + case 7000000: + bw_i = 2; + bw_param = 1; + break; + case 8000000: + bw_i = 3; + bw_param = 0; + break; + default: + return -EINVAL; + } + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + if (priv->delivery_system != SYS_DVBT2) { + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, + tab[i].val, tab[i].mask); + if (ret) + goto error; + } + } + + priv->delivery_system = SYS_DVBT2; + + /* program IF frequency */ + if (fe->ops.tuner_ops.get_if_frequency) { + ret = fe->ops.tuner_ops.get_if_frequency(fe, &if_freq); + if (ret) + goto error; + } else + if_freq = 0; + + dev_dbg(&priv->i2c->dev, "%s: if_freq=%d\n", __func__, if_freq); + + num = if_freq / 1000; /* Hz => kHz */ + num *= 0x1000000; + if_ctl = cxd2820r_div_u64_round_closest(num, 41000); + buf[0] = ((if_ctl >> 16) & 0xff); + buf[1] = ((if_ctl >> 8) & 0xff); + buf[2] = ((if_ctl >> 0) & 0xff); + + ret = cxd2820r_wr_regs(priv, 0x020b6, buf, 3); + if (ret) + goto error; + + ret = cxd2820r_wr_regs(priv, 0x0209f, bw_params1[bw_i], 5); + if (ret) + goto error; + + ret = cxd2820r_wr_reg_mask(priv, 0x020d7, bw_param << 6, 0xc0); + if (ret) + goto error; + + ret = cxd2820r_wr_reg(priv, 0x000ff, 0x08); + if (ret) + goto error; + + ret = cxd2820r_wr_reg(priv, 0x000fe, 0x01); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; + +} + +int cxd2820r_get_frontend_t2(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 buf[2]; + + ret = cxd2820r_rd_regs(priv, 0x0205c, buf, 2); + if (ret) + goto error; + + switch ((buf[0] >> 0) & 0x07) { + case 0: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case 1: + c->transmission_mode = TRANSMISSION_MODE_8K; + break; + case 2: + c->transmission_mode = TRANSMISSION_MODE_4K; + break; + case 3: + c->transmission_mode = TRANSMISSION_MODE_1K; + break; + case 4: + c->transmission_mode = TRANSMISSION_MODE_16K; + break; + case 5: + c->transmission_mode = TRANSMISSION_MODE_32K; + break; + } + + switch ((buf[1] >> 4) & 0x07) { + case 0: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + case 4: + c->guard_interval = GUARD_INTERVAL_1_128; + break; + case 5: + c->guard_interval = GUARD_INTERVAL_19_128; + break; + case 6: + c->guard_interval = GUARD_INTERVAL_19_256; + break; + } + + ret = cxd2820r_rd_regs(priv, 0x0225b, buf, 2); + if (ret) + goto error; + + switch ((buf[0] >> 0) & 0x07) { + case 0: + c->fec_inner = FEC_1_2; + break; + case 1: + c->fec_inner = FEC_3_5; + break; + case 2: + c->fec_inner = FEC_2_3; + break; + case 3: + c->fec_inner = FEC_3_4; + break; + case 4: + c->fec_inner = FEC_4_5; + break; + case 5: + c->fec_inner = FEC_5_6; + break; + } + + switch ((buf[1] >> 0) & 0x07) { + case 0: + c->modulation = QPSK; + break; + case 1: + c->modulation = QAM_16; + break; + case 2: + c->modulation = QAM_64; + break; + case 3: + c->modulation = QAM_256; + break; + } + + ret = cxd2820r_rd_reg(priv, 0x020b5, &buf[0]); + if (ret) + goto error; + + switch ((buf[0] >> 4) & 0x01) { + case 0: + c->inversion = INVERSION_OFF; + break; + case 1: + c->inversion = INVERSION_ON; + break; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_status_t2(struct dvb_frontend *fe, fe_status_t *status) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[1]; + *status = 0; + + ret = cxd2820r_rd_reg(priv, 0x02010 , &buf[0]); + if (ret) + goto error; + + if ((buf[0] & 0x07) == 6) { + if (((buf[0] >> 5) & 0x01) == 1) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + } else { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC; + } + } + + dev_dbg(&priv->i2c->dev, "%s: lock=%02x\n", __func__, buf[0]); + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_ber_t2(struct dvb_frontend *fe, u32 *ber) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[4]; + unsigned int errbits; + *ber = 0; + /* FIXME: correct calculation */ + + ret = cxd2820r_rd_regs(priv, 0x02039, buf, sizeof(buf)); + if (ret) + goto error; + + if ((buf[0] >> 4) & 0x01) { + errbits = (buf[0] & 0x0f) << 24 | buf[1] << 16 | + buf[2] << 8 | buf[3]; + + if (errbits) + *ber = errbits * 64 / 16588800; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_signal_strength_t2(struct dvb_frontend *fe, + u16 *strength) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + u16 tmp; + + ret = cxd2820r_rd_regs(priv, 0x02026, buf, sizeof(buf)); + if (ret) + goto error; + + tmp = (buf[0] & 0x0f) << 8 | buf[1]; + tmp = ~tmp & 0x0fff; + + /* scale value to 0x0000-0xffff from 0x0000-0x0fff */ + *strength = tmp * 0xffff / 0x0fff; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_snr_t2(struct dvb_frontend *fe, u16 *snr) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + u16 tmp; + /* report SNR in dB * 10 */ + + ret = cxd2820r_rd_regs(priv, 0x02028, buf, sizeof(buf)); + if (ret) + goto error; + + tmp = (buf[0] & 0x0f) << 8 | buf[1]; + #define CXD2820R_LOG10_8_24 15151336 /* log10(8) << 24 */ + if (tmp) + *snr = (intlog10(tmp) - CXD2820R_LOG10_8_24) / ((1 << 24) + / 100); + else + *snr = 0; + + dev_dbg(&priv->i2c->dev, "%s: dBx10=%d val=%04x\n", __func__, *snr, + tmp); + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_read_ucblocks_t2(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + /* no way to read ? */ + return 0; +} + +int cxd2820r_sleep_t2(struct dvb_frontend *fe) +{ + struct cxd2820r_priv *priv = fe->demodulator_priv; + int ret, i; + struct reg_val_mask tab[] = { + { 0x000ff, 0x1f, 0xff }, + { 0x00085, 0x00, 0xff }, + { 0x00088, 0x01, 0xff }, + { 0x02069, 0x00, 0xff }, + { 0x00081, 0x00, 0xff }, + { 0x00080, 0x00, 0xff }, + }; + + dev_dbg(&priv->i2c->dev, "%s\n", __func__); + + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, tab[i].val, + tab[i].mask); + if (ret) + goto error; + } + + priv->delivery_system = SYS_UNDEFINED; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +int cxd2820r_get_tune_settings_t2(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 1500; + s->step_size = fe->ops.info.frequency_stepsize * 2; + s->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; + + return 0; +} diff --git a/drivers/media/dvb/frontends/dib0070.c b/drivers/media/dvb-frontends/dib0070.c similarity index 100% rename from drivers/media/dvb/frontends/dib0070.c rename to drivers/media/dvb-frontends/dib0070.c diff --git a/drivers/media/dvb/frontends/dib0070.h b/drivers/media/dvb-frontends/dib0070.h similarity index 100% rename from drivers/media/dvb/frontends/dib0070.h rename to drivers/media/dvb-frontends/dib0070.h diff --git a/drivers/media/dvb/frontends/dib0090.c b/drivers/media/dvb-frontends/dib0090.c similarity index 100% rename from drivers/media/dvb/frontends/dib0090.c rename to drivers/media/dvb-frontends/dib0090.c diff --git a/drivers/media/dvb/frontends/dib0090.h b/drivers/media/dvb-frontends/dib0090.h similarity index 100% rename from drivers/media/dvb/frontends/dib0090.h rename to drivers/media/dvb-frontends/dib0090.h diff --git a/drivers/media/dvb/frontends/dib3000.h b/drivers/media/dvb-frontends/dib3000.h similarity index 100% rename from drivers/media/dvb/frontends/dib3000.h rename to drivers/media/dvb-frontends/dib3000.h diff --git a/drivers/media/dvb/frontends/dib3000mb.c b/drivers/media/dvb-frontends/dib3000mb.c similarity index 100% rename from drivers/media/dvb/frontends/dib3000mb.c rename to drivers/media/dvb-frontends/dib3000mb.c diff --git a/drivers/media/dvb/frontends/dib3000mb_priv.h b/drivers/media/dvb-frontends/dib3000mb_priv.h similarity index 100% rename from drivers/media/dvb/frontends/dib3000mb_priv.h rename to drivers/media/dvb-frontends/dib3000mb_priv.h diff --git a/drivers/media/dvb/frontends/dib3000mc.c b/drivers/media/dvb-frontends/dib3000mc.c similarity index 100% rename from drivers/media/dvb/frontends/dib3000mc.c rename to drivers/media/dvb-frontends/dib3000mc.c diff --git a/drivers/media/dvb/frontends/dib3000mc.h b/drivers/media/dvb-frontends/dib3000mc.h similarity index 100% rename from drivers/media/dvb/frontends/dib3000mc.h rename to drivers/media/dvb-frontends/dib3000mc.h diff --git a/drivers/media/dvb/frontends/dib7000m.c b/drivers/media/dvb-frontends/dib7000m.c similarity index 100% rename from drivers/media/dvb/frontends/dib7000m.c rename to drivers/media/dvb-frontends/dib7000m.c diff --git a/drivers/media/dvb/frontends/dib7000m.h b/drivers/media/dvb-frontends/dib7000m.h similarity index 100% rename from drivers/media/dvb/frontends/dib7000m.h rename to drivers/media/dvb-frontends/dib7000m.h diff --git a/drivers/media/dvb/frontends/dib7000p.c b/drivers/media/dvb-frontends/dib7000p.c similarity index 100% rename from drivers/media/dvb/frontends/dib7000p.c rename to drivers/media/dvb-frontends/dib7000p.c diff --git a/drivers/media/dvb/frontends/dib7000p.h b/drivers/media/dvb-frontends/dib7000p.h similarity index 100% rename from drivers/media/dvb/frontends/dib7000p.h rename to drivers/media/dvb-frontends/dib7000p.h diff --git a/drivers/media/dvb/frontends/dib8000.c b/drivers/media/dvb-frontends/dib8000.c similarity index 100% rename from drivers/media/dvb/frontends/dib8000.c rename to drivers/media/dvb-frontends/dib8000.c diff --git a/drivers/media/dvb/frontends/dib8000.h b/drivers/media/dvb-frontends/dib8000.h similarity index 100% rename from drivers/media/dvb/frontends/dib8000.h rename to drivers/media/dvb-frontends/dib8000.h diff --git a/drivers/media/dvb/frontends/dib9000.c b/drivers/media/dvb-frontends/dib9000.c similarity index 100% rename from drivers/media/dvb/frontends/dib9000.c rename to drivers/media/dvb-frontends/dib9000.c diff --git a/drivers/media/dvb/frontends/dib9000.h b/drivers/media/dvb-frontends/dib9000.h similarity index 100% rename from drivers/media/dvb/frontends/dib9000.h rename to drivers/media/dvb-frontends/dib9000.h diff --git a/drivers/media/dvb/frontends/dibx000_common.c b/drivers/media/dvb-frontends/dibx000_common.c similarity index 100% rename from drivers/media/dvb/frontends/dibx000_common.c rename to drivers/media/dvb-frontends/dibx000_common.c diff --git a/drivers/media/dvb/frontends/dibx000_common.h b/drivers/media/dvb-frontends/dibx000_common.h similarity index 100% rename from drivers/media/dvb/frontends/dibx000_common.h rename to drivers/media/dvb-frontends/dibx000_common.h diff --git a/drivers/media/dvb/frontends/drxd.h b/drivers/media/dvb-frontends/drxd.h similarity index 100% rename from drivers/media/dvb/frontends/drxd.h rename to drivers/media/dvb-frontends/drxd.h diff --git a/drivers/media/dvb/frontends/drxd_firm.c b/drivers/media/dvb-frontends/drxd_firm.c similarity index 100% rename from drivers/media/dvb/frontends/drxd_firm.c rename to drivers/media/dvb-frontends/drxd_firm.c diff --git a/drivers/media/dvb/frontends/drxd_firm.h b/drivers/media/dvb-frontends/drxd_firm.h similarity index 100% rename from drivers/media/dvb/frontends/drxd_firm.h rename to drivers/media/dvb-frontends/drxd_firm.h diff --git a/drivers/media/dvb/frontends/drxd_hard.c b/drivers/media/dvb-frontends/drxd_hard.c similarity index 100% rename from drivers/media/dvb/frontends/drxd_hard.c rename to drivers/media/dvb-frontends/drxd_hard.c diff --git a/drivers/media/dvb/frontends/drxd_map_firm.h b/drivers/media/dvb-frontends/drxd_map_firm.h similarity index 100% rename from drivers/media/dvb/frontends/drxd_map_firm.h rename to drivers/media/dvb-frontends/drxd_map_firm.h diff --git a/drivers/media/dvb-frontends/drxk.h b/drivers/media/dvb-frontends/drxk.h new file mode 100644 index 000000000000..94fecfbf14c1 --- /dev/null +++ b/drivers/media/dvb-frontends/drxk.h @@ -0,0 +1,68 @@ +#ifndef _DRXK_H_ +#define _DRXK_H_ + +#include +#include + +/** + * struct drxk_config - Configure the initial parameters for DRX-K + * + * @adr: I2C Address of the DRX-K + * @parallel_ts: True means that the device uses parallel TS, + * Serial otherwise. + * @dynamic_clk: True means that the clock will be dynamically + * adjusted. Static clock otherwise. + * @enable_merr_cfg: Enable SIO_PDR_PERR_CFG/SIO_PDR_MVAL_CFG. + * @single_master: Device is on the single master mode + * @no_i2c_bridge: Don't switch the I2C bridge to talk with tuner + * @antenna_gpio: GPIO bit used to control the antenna + * @antenna_dvbt: GPIO bit for changing antenna to DVB-C. A value of 1 + * means that 1=DVBC, 0 = DVBT. Zero means the opposite. + * @mpeg_out_clk_strength: DRXK Mpeg output clock drive strength. + * @microcode_name: Name of the firmware file with the microcode + * @qam_demod_parameter_count: The number of parameters used for the command + * to set the demodulator parameters. All + * firmwares are using the 2-parameter commmand. + * An exception is the "drxk_a3.mc" firmware, + * which uses the 4-parameter command. + * A value of 0 (default) or lower indicates that + * the correct number of parameters will be + * automatically detected. + * @load_firmware_sync: Force the firmware load to be synchronous. + * + * On the *_gpio vars, bit 0 is UIO-1, bit 1 is UIO-2 and bit 2 is + * UIO-3. + */ +struct drxk_config { + u8 adr; + bool single_master; + bool no_i2c_bridge; + bool parallel_ts; + bool dynamic_clk; + bool enable_merr_cfg; + bool load_firmware_sync; + + bool antenna_dvbt; + u16 antenna_gpio; + + u8 mpeg_out_clk_strength; + int chunk_size; + + const char *microcode_name; + int qam_demod_parameter_count; +}; + +#if defined(CONFIG_DVB_DRXK) || (defined(CONFIG_DVB_DRXK_MODULE) \ + && defined(MODULE)) +extern struct dvb_frontend *drxk_attach(const struct drxk_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *drxk_attach(const struct drxk_config *config, + struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif diff --git a/drivers/media/dvb-frontends/drxk_hard.c b/drivers/media/dvb-frontends/drxk_hard.c new file mode 100644 index 000000000000..8b4c6d5f8f36 --- /dev/null +++ b/drivers/media/dvb-frontends/drxk_hard.c @@ -0,0 +1,6647 @@ +/* + * drxk_hard: DRX-K DVB-C/T demodulator driver + * + * Copyright (C) 2010-2011 Digital Devices GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * 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 + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dvb_frontend.h" +#include "drxk.h" +#include "drxk_hard.h" + +static int PowerDownDVBT(struct drxk_state *state, bool setPowerMode); +static int PowerDownQAM(struct drxk_state *state); +static int SetDVBTStandard(struct drxk_state *state, + enum OperationMode oMode); +static int SetQAMStandard(struct drxk_state *state, + enum OperationMode oMode); +static int SetQAM(struct drxk_state *state, u16 IntermediateFreqkHz, + s32 tunerFreqOffset); +static int SetDVBTStandard(struct drxk_state *state, + enum OperationMode oMode); +static int DVBTStart(struct drxk_state *state); +static int SetDVBT(struct drxk_state *state, u16 IntermediateFreqkHz, + s32 tunerFreqOffset); +static int GetQAMLockStatus(struct drxk_state *state, u32 *pLockStatus); +static int GetDVBTLockStatus(struct drxk_state *state, u32 *pLockStatus); +static int SwitchAntennaToQAM(struct drxk_state *state); +static int SwitchAntennaToDVBT(struct drxk_state *state); + +static bool IsDVBT(struct drxk_state *state) +{ + return state->m_OperationMode == OM_DVBT; +} + +static bool IsQAM(struct drxk_state *state) +{ + return state->m_OperationMode == OM_QAM_ITU_A || + state->m_OperationMode == OM_QAM_ITU_B || + state->m_OperationMode == OM_QAM_ITU_C; +} + +bool IsA1WithPatchCode(struct drxk_state *state) +{ + return state->m_DRXK_A1_PATCH_CODE; +} + +bool IsA1WithRomCode(struct drxk_state *state) +{ + return state->m_DRXK_A1_ROM_CODE; +} + +#define NOA1ROM 0 + +#define DRXDAP_FASI_SHORT_FORMAT(addr) (((addr) & 0xFC30FF80) == 0) +#define DRXDAP_FASI_LONG_FORMAT(addr) (((addr) & 0xFC30FF80) != 0) + +#define DEFAULT_MER_83 165 +#define DEFAULT_MER_93 250 + +#ifndef DRXK_MPEG_SERIAL_OUTPUT_PIN_DRIVE_STRENGTH +#define DRXK_MPEG_SERIAL_OUTPUT_PIN_DRIVE_STRENGTH (0x02) +#endif + +#ifndef DRXK_MPEG_PARALLEL_OUTPUT_PIN_DRIVE_STRENGTH +#define DRXK_MPEG_PARALLEL_OUTPUT_PIN_DRIVE_STRENGTH (0x03) +#endif + +#define DEFAULT_DRXK_MPEG_LOCK_TIMEOUT 700 +#define DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT 500 + +#ifndef DRXK_KI_RAGC_ATV +#define DRXK_KI_RAGC_ATV 4 +#endif +#ifndef DRXK_KI_IAGC_ATV +#define DRXK_KI_IAGC_ATV 6 +#endif +#ifndef DRXK_KI_DAGC_ATV +#define DRXK_KI_DAGC_ATV 7 +#endif + +#ifndef DRXK_KI_RAGC_QAM +#define DRXK_KI_RAGC_QAM 3 +#endif +#ifndef DRXK_KI_IAGC_QAM +#define DRXK_KI_IAGC_QAM 4 +#endif +#ifndef DRXK_KI_DAGC_QAM +#define DRXK_KI_DAGC_QAM 7 +#endif +#ifndef DRXK_KI_RAGC_DVBT +#define DRXK_KI_RAGC_DVBT (IsA1WithPatchCode(state) ? 3 : 2) +#endif +#ifndef DRXK_KI_IAGC_DVBT +#define DRXK_KI_IAGC_DVBT (IsA1WithPatchCode(state) ? 4 : 2) +#endif +#ifndef DRXK_KI_DAGC_DVBT +#define DRXK_KI_DAGC_DVBT (IsA1WithPatchCode(state) ? 10 : 7) +#endif + +#ifndef DRXK_AGC_DAC_OFFSET +#define DRXK_AGC_DAC_OFFSET (0x800) +#endif + +#ifndef DRXK_BANDWIDTH_8MHZ_IN_HZ +#define DRXK_BANDWIDTH_8MHZ_IN_HZ (0x8B8249L) +#endif + +#ifndef DRXK_BANDWIDTH_7MHZ_IN_HZ +#define DRXK_BANDWIDTH_7MHZ_IN_HZ (0x7A1200L) +#endif + +#ifndef DRXK_BANDWIDTH_6MHZ_IN_HZ +#define DRXK_BANDWIDTH_6MHZ_IN_HZ (0x68A1B6L) +#endif + +#ifndef DRXK_QAM_SYMBOLRATE_MAX +#define DRXK_QAM_SYMBOLRATE_MAX (7233000) +#endif + +#define DRXK_BL_ROM_OFFSET_TAPS_DVBT 56 +#define DRXK_BL_ROM_OFFSET_TAPS_ITU_A 64 +#define DRXK_BL_ROM_OFFSET_TAPS_ITU_C 0x5FE0 +#define DRXK_BL_ROM_OFFSET_TAPS_BG 24 +#define DRXK_BL_ROM_OFFSET_TAPS_DKILLP 32 +#define DRXK_BL_ROM_OFFSET_TAPS_NTSC 40 +#define DRXK_BL_ROM_OFFSET_TAPS_FM 48 +#define DRXK_BL_ROM_OFFSET_UCODE 0 + +#define DRXK_BLC_TIMEOUT 100 + +#define DRXK_BLCC_NR_ELEMENTS_TAPS 2 +#define DRXK_BLCC_NR_ELEMENTS_UCODE 6 + +#define DRXK_BLDC_NR_ELEMENTS_TAPS 28 + +#ifndef DRXK_OFDM_NE_NOTCH_WIDTH +#define DRXK_OFDM_NE_NOTCH_WIDTH (4) +#endif + +#define DRXK_QAM_SL_SIG_POWER_QAM16 (40960) +#define DRXK_QAM_SL_SIG_POWER_QAM32 (20480) +#define DRXK_QAM_SL_SIG_POWER_QAM64 (43008) +#define DRXK_QAM_SL_SIG_POWER_QAM128 (20992) +#define DRXK_QAM_SL_SIG_POWER_QAM256 (43520) + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); + +#define dprintk(level, fmt, arg...) do { \ +if (debug >= level) \ + printk(KERN_DEBUG "drxk: %s" fmt, __func__, ## arg); \ +} while (0) + + +static inline u32 MulDiv32(u32 a, u32 b, u32 c) +{ + u64 tmp64; + + tmp64 = (u64) a * (u64) b; + do_div(tmp64, c); + + return (u32) tmp64; +} + +inline u32 Frac28a(u32 a, u32 c) +{ + int i = 0; + u32 Q1 = 0; + u32 R0 = 0; + + R0 = (a % c) << 4; /* 32-28 == 4 shifts possible at max */ + Q1 = a / c; /* integer part, only the 4 least significant bits + will be visible in the result */ + + /* division using radix 16, 7 nibbles in the result */ + for (i = 0; i < 7; i++) { + Q1 = (Q1 << 4) | (R0 / c); + R0 = (R0 % c) << 4; + } + /* rounding */ + if ((R0 >> 3) >= c) + Q1++; + + return Q1; +} + +static u32 Log10Times100(u32 x) +{ + static const u8 scale = 15; + static const u8 indexWidth = 5; + u8 i = 0; + u32 y = 0; + u32 d = 0; + u32 k = 0; + u32 r = 0; + /* + log2lut[n] = (1< 0; k--) { + if (x & (((u32) 1) << scale)) + break; + x <<= 1; + } + } else { + for (k = scale; k < 31; k++) { + if ((x & (((u32) (-1)) << (scale + 1))) == 0) + break; + x >>= 1; + } + } + /* + Now x has binary point between bit[scale] and bit[scale-1] + and 1.0 <= x < 2.0 */ + + /* correction for divison: log(x) = log(x/y)+log(y) */ + y = k * ((((u32) 1) << scale) * 200); + + /* remove integer part */ + x &= ((((u32) 1) << scale) - 1); + /* get index */ + i = (u8) (x >> (scale - indexWidth)); + /* compute delta (x - a) */ + d = x & ((((u32) 1) << (scale - indexWidth)) - 1); + /* compute log, multiplication (d* (..)) must be within range ! */ + y += log2lut[i] + + ((d * (log2lut[i + 1] - log2lut[i])) >> (scale - indexWidth)); + /* Conver to log10() */ + y /= 108853; /* (log2(10) << scale) */ + r = (y >> 1); + /* rounding */ + if (y & ((u32) 1)) + r++; + return r; +} + +/****************************************************************************/ +/* I2C **********************************************************************/ +/****************************************************************************/ + +static int drxk_i2c_lock(struct drxk_state *state) +{ + i2c_lock_adapter(state->i2c); + state->drxk_i2c_exclusive_lock = true; + + return 0; +} + +static void drxk_i2c_unlock(struct drxk_state *state) +{ + if (!state->drxk_i2c_exclusive_lock) + return; + + i2c_unlock_adapter(state->i2c); + state->drxk_i2c_exclusive_lock = false; +} + +static int drxk_i2c_transfer(struct drxk_state *state, struct i2c_msg *msgs, + unsigned len) +{ + if (state->drxk_i2c_exclusive_lock) + return __i2c_transfer(state->i2c, msgs, len); + else + return i2c_transfer(state->i2c, msgs, len); +} + +static int i2c_read1(struct drxk_state *state, u8 adr, u8 *val) +{ + struct i2c_msg msgs[1] = { {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1} + }; + + return drxk_i2c_transfer(state, msgs, 1); +} + +static int i2c_write(struct drxk_state *state, u8 adr, u8 *data, int len) +{ + int status; + struct i2c_msg msg = { + .addr = adr, .flags = 0, .buf = data, .len = len }; + + dprintk(3, ":"); + if (debug > 2) { + int i; + for (i = 0; i < len; i++) + printk(KERN_CONT " %02x", data[i]); + printk(KERN_CONT "\n"); + } + status = drxk_i2c_transfer(state, &msg, 1); + if (status >= 0 && status != 1) + status = -EIO; + + if (status < 0) + printk(KERN_ERR "drxk: i2c write error at addr 0x%02x\n", adr); + + return status; +} + +static int i2c_read(struct drxk_state *state, + u8 adr, u8 *msg, int len, u8 *answ, int alen) +{ + int status; + struct i2c_msg msgs[2] = { + {.addr = adr, .flags = 0, + .buf = msg, .len = len}, + {.addr = adr, .flags = I2C_M_RD, + .buf = answ, .len = alen} + }; + + status = drxk_i2c_transfer(state, msgs, 2); + if (status != 2) { + if (debug > 2) + printk(KERN_CONT ": ERROR!\n"); + if (status >= 0) + status = -EIO; + + printk(KERN_ERR "drxk: i2c read error at addr 0x%02x\n", adr); + return status; + } + if (debug > 2) { + int i; + dprintk(2, ": read from"); + for (i = 0; i < len; i++) + printk(KERN_CONT " %02x", msg[i]); + printk(KERN_CONT ", value = "); + for (i = 0; i < alen; i++) + printk(KERN_CONT " %02x", answ[i]); + printk(KERN_CONT "\n"); + } + return 0; +} + +static int read16_flags(struct drxk_state *state, u32 reg, u16 *data, u8 flags) +{ + int status; + u8 adr = state->demod_address, mm1[4], mm2[2], len; + + if (state->single_master) + flags |= 0xC0; + + if (DRXDAP_FASI_LONG_FORMAT(reg) || (flags != 0)) { + mm1[0] = (((reg << 1) & 0xFF) | 0x01); + mm1[1] = ((reg >> 16) & 0xFF); + mm1[2] = ((reg >> 24) & 0xFF) | flags; + mm1[3] = ((reg >> 7) & 0xFF); + len = 4; + } else { + mm1[0] = ((reg << 1) & 0xFF); + mm1[1] = (((reg >> 16) & 0x0F) | ((reg >> 18) & 0xF0)); + len = 2; + } + dprintk(2, "(0x%08x, 0x%02x)\n", reg, flags); + status = i2c_read(state, adr, mm1, len, mm2, 2); + if (status < 0) + return status; + if (data) + *data = mm2[0] | (mm2[1] << 8); + + return 0; +} + +static int read16(struct drxk_state *state, u32 reg, u16 *data) +{ + return read16_flags(state, reg, data, 0); +} + +static int read32_flags(struct drxk_state *state, u32 reg, u32 *data, u8 flags) +{ + int status; + u8 adr = state->demod_address, mm1[4], mm2[4], len; + + if (state->single_master) + flags |= 0xC0; + + if (DRXDAP_FASI_LONG_FORMAT(reg) || (flags != 0)) { + mm1[0] = (((reg << 1) & 0xFF) | 0x01); + mm1[1] = ((reg >> 16) & 0xFF); + mm1[2] = ((reg >> 24) & 0xFF) | flags; + mm1[3] = ((reg >> 7) & 0xFF); + len = 4; + } else { + mm1[0] = ((reg << 1) & 0xFF); + mm1[1] = (((reg >> 16) & 0x0F) | ((reg >> 18) & 0xF0)); + len = 2; + } + dprintk(2, "(0x%08x, 0x%02x)\n", reg, flags); + status = i2c_read(state, adr, mm1, len, mm2, 4); + if (status < 0) + return status; + if (data) + *data = mm2[0] | (mm2[1] << 8) | + (mm2[2] << 16) | (mm2[3] << 24); + + return 0; +} + +static int read32(struct drxk_state *state, u32 reg, u32 *data) +{ + return read32_flags(state, reg, data, 0); +} + +static int write16_flags(struct drxk_state *state, u32 reg, u16 data, u8 flags) +{ + u8 adr = state->demod_address, mm[6], len; + + if (state->single_master) + flags |= 0xC0; + if (DRXDAP_FASI_LONG_FORMAT(reg) || (flags != 0)) { + mm[0] = (((reg << 1) & 0xFF) | 0x01); + mm[1] = ((reg >> 16) & 0xFF); + mm[2] = ((reg >> 24) & 0xFF) | flags; + mm[3] = ((reg >> 7) & 0xFF); + len = 4; + } else { + mm[0] = ((reg << 1) & 0xFF); + mm[1] = (((reg >> 16) & 0x0F) | ((reg >> 18) & 0xF0)); + len = 2; + } + mm[len] = data & 0xff; + mm[len + 1] = (data >> 8) & 0xff; + + dprintk(2, "(0x%08x, 0x%04x, 0x%02x)\n", reg, data, flags); + return i2c_write(state, adr, mm, len + 2); +} + +static int write16(struct drxk_state *state, u32 reg, u16 data) +{ + return write16_flags(state, reg, data, 0); +} + +static int write32_flags(struct drxk_state *state, u32 reg, u32 data, u8 flags) +{ + u8 adr = state->demod_address, mm[8], len; + + if (state->single_master) + flags |= 0xC0; + if (DRXDAP_FASI_LONG_FORMAT(reg) || (flags != 0)) { + mm[0] = (((reg << 1) & 0xFF) | 0x01); + mm[1] = ((reg >> 16) & 0xFF); + mm[2] = ((reg >> 24) & 0xFF) | flags; + mm[3] = ((reg >> 7) & 0xFF); + len = 4; + } else { + mm[0] = ((reg << 1) & 0xFF); + mm[1] = (((reg >> 16) & 0x0F) | ((reg >> 18) & 0xF0)); + len = 2; + } + mm[len] = data & 0xff; + mm[len + 1] = (data >> 8) & 0xff; + mm[len + 2] = (data >> 16) & 0xff; + mm[len + 3] = (data >> 24) & 0xff; + dprintk(2, "(0x%08x, 0x%08x, 0x%02x)\n", reg, data, flags); + + return i2c_write(state, adr, mm, len + 4); +} + +static int write32(struct drxk_state *state, u32 reg, u32 data) +{ + return write32_flags(state, reg, data, 0); +} + +static int write_block(struct drxk_state *state, u32 Address, + const int BlockSize, const u8 pBlock[]) +{ + int status = 0, BlkSize = BlockSize; + u8 Flags = 0; + + if (state->single_master) + Flags |= 0xC0; + + while (BlkSize > 0) { + int Chunk = BlkSize > state->m_ChunkSize ? + state->m_ChunkSize : BlkSize; + u8 *AdrBuf = &state->Chunk[0]; + u32 AdrLength = 0; + + if (DRXDAP_FASI_LONG_FORMAT(Address) || (Flags != 0)) { + AdrBuf[0] = (((Address << 1) & 0xFF) | 0x01); + AdrBuf[1] = ((Address >> 16) & 0xFF); + AdrBuf[2] = ((Address >> 24) & 0xFF); + AdrBuf[3] = ((Address >> 7) & 0xFF); + AdrBuf[2] |= Flags; + AdrLength = 4; + if (Chunk == state->m_ChunkSize) + Chunk -= 2; + } else { + AdrBuf[0] = ((Address << 1) & 0xFF); + AdrBuf[1] = (((Address >> 16) & 0x0F) | + ((Address >> 18) & 0xF0)); + AdrLength = 2; + } + memcpy(&state->Chunk[AdrLength], pBlock, Chunk); + dprintk(2, "(0x%08x, 0x%02x)\n", Address, Flags); + if (debug > 1) { + int i; + if (pBlock) + for (i = 0; i < Chunk; i++) + printk(KERN_CONT " %02x", pBlock[i]); + printk(KERN_CONT "\n"); + } + status = i2c_write(state, state->demod_address, + &state->Chunk[0], Chunk + AdrLength); + if (status < 0) { + printk(KERN_ERR "drxk: %s: i2c write error at addr 0x%02x\n", + __func__, Address); + break; + } + pBlock += Chunk; + Address += (Chunk >> 1); + BlkSize -= Chunk; + } + return status; +} + +#ifndef DRXK_MAX_RETRIES_POWERUP +#define DRXK_MAX_RETRIES_POWERUP 20 +#endif + +int PowerUpDevice(struct drxk_state *state) +{ + int status; + u8 data = 0; + u16 retryCount = 0; + + dprintk(1, "\n"); + + status = i2c_read1(state, state->demod_address, &data); + if (status < 0) { + do { + data = 0; + status = i2c_write(state, state->demod_address, + &data, 1); + msleep(10); + retryCount++; + if (status < 0) + continue; + status = i2c_read1(state, state->demod_address, + &data); + } while (status < 0 && + (retryCount < DRXK_MAX_RETRIES_POWERUP)); + if (status < 0 && retryCount >= DRXK_MAX_RETRIES_POWERUP) + goto error; + } + + /* Make sure all clk domains are active */ + status = write16(state, SIO_CC_PWD_MODE__A, SIO_CC_PWD_MODE_LEVEL_NONE); + if (status < 0) + goto error; + status = write16(state, SIO_CC_UPDATE__A, SIO_CC_UPDATE_KEY); + if (status < 0) + goto error; + /* Enable pll lock tests */ + status = write16(state, SIO_CC_PLL_LOCK__A, 1); + if (status < 0) + goto error; + + state->m_currentPowerMode = DRX_POWER_UP; + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + + +static int init_state(struct drxk_state *state) +{ + /* + * FIXME: most (all?) of the values bellow should be moved into + * struct drxk_config, as they are probably board-specific + */ + u32 ulVSBIfAgcMode = DRXK_AGC_CTRL_AUTO; + u32 ulVSBIfAgcOutputLevel = 0; + u32 ulVSBIfAgcMinLevel = 0; + u32 ulVSBIfAgcMaxLevel = 0x7FFF; + u32 ulVSBIfAgcSpeed = 3; + + u32 ulVSBRfAgcMode = DRXK_AGC_CTRL_AUTO; + u32 ulVSBRfAgcOutputLevel = 0; + u32 ulVSBRfAgcMinLevel = 0; + u32 ulVSBRfAgcMaxLevel = 0x7FFF; + u32 ulVSBRfAgcSpeed = 3; + u32 ulVSBRfAgcTop = 9500; + u32 ulVSBRfAgcCutOffCurrent = 4000; + + u32 ulATVIfAgcMode = DRXK_AGC_CTRL_AUTO; + u32 ulATVIfAgcOutputLevel = 0; + u32 ulATVIfAgcMinLevel = 0; + u32 ulATVIfAgcMaxLevel = 0; + u32 ulATVIfAgcSpeed = 3; + + u32 ulATVRfAgcMode = DRXK_AGC_CTRL_OFF; + u32 ulATVRfAgcOutputLevel = 0; + u32 ulATVRfAgcMinLevel = 0; + u32 ulATVRfAgcMaxLevel = 0; + u32 ulATVRfAgcTop = 9500; + u32 ulATVRfAgcCutOffCurrent = 4000; + u32 ulATVRfAgcSpeed = 3; + + u32 ulQual83 = DEFAULT_MER_83; + u32 ulQual93 = DEFAULT_MER_93; + + u32 ulMpegLockTimeOut = DEFAULT_DRXK_MPEG_LOCK_TIMEOUT; + u32 ulDemodLockTimeOut = DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT; + + /* io_pad_cfg register (8 bit reg.) MSB bit is 1 (default value) */ + /* io_pad_cfg_mode output mode is drive always */ + /* io_pad_cfg_drive is set to power 2 (23 mA) */ + u32 ulGPIOCfg = 0x0113; + u32 ulInvertTSClock = 0; + u32 ulTSDataStrength = DRXK_MPEG_SERIAL_OUTPUT_PIN_DRIVE_STRENGTH; + u32 ulDVBTBitrate = 50000000; + u32 ulDVBCBitrate = DRXK_QAM_SYMBOLRATE_MAX * 8; + + u32 ulInsertRSByte = 0; + + u32 ulRfMirror = 1; + u32 ulPowerDown = 0; + + dprintk(1, "\n"); + + state->m_hasLNA = false; + state->m_hasDVBT = false; + state->m_hasDVBC = false; + state->m_hasATV = false; + state->m_hasOOB = false; + state->m_hasAudio = false; + + if (!state->m_ChunkSize) + state->m_ChunkSize = 124; + + state->m_oscClockFreq = 0; + state->m_smartAntInverted = false; + state->m_bPDownOpenBridge = false; + + /* real system clock frequency in kHz */ + state->m_sysClockFreq = 151875; + /* Timing div, 250ns/Psys */ + /* Timing div, = (delay (nano seconds) * sysclk (kHz))/ 1000 */ + state->m_HICfgTimingDiv = ((state->m_sysClockFreq / 1000) * + HI_I2C_DELAY) / 1000; + /* Clipping */ + if (state->m_HICfgTimingDiv > SIO_HI_RA_RAM_PAR_2_CFG_DIV__M) + state->m_HICfgTimingDiv = SIO_HI_RA_RAM_PAR_2_CFG_DIV__M; + state->m_HICfgWakeUpKey = (state->demod_address << 1); + /* port/bridge/power down ctrl */ + state->m_HICfgCtrl = SIO_HI_RA_RAM_PAR_5_CFG_SLV0_SLAVE; + + state->m_bPowerDown = (ulPowerDown != 0); + + state->m_DRXK_A1_PATCH_CODE = false; + state->m_DRXK_A1_ROM_CODE = false; + state->m_DRXK_A2_ROM_CODE = false; + state->m_DRXK_A3_ROM_CODE = false; + state->m_DRXK_A2_PATCH_CODE = false; + state->m_DRXK_A3_PATCH_CODE = false; + + /* Init AGC and PGA parameters */ + /* VSB IF */ + state->m_vsbIfAgcCfg.ctrlMode = (ulVSBIfAgcMode); + state->m_vsbIfAgcCfg.outputLevel = (ulVSBIfAgcOutputLevel); + state->m_vsbIfAgcCfg.minOutputLevel = (ulVSBIfAgcMinLevel); + state->m_vsbIfAgcCfg.maxOutputLevel = (ulVSBIfAgcMaxLevel); + state->m_vsbIfAgcCfg.speed = (ulVSBIfAgcSpeed); + state->m_vsbPgaCfg = 140; + + /* VSB RF */ + state->m_vsbRfAgcCfg.ctrlMode = (ulVSBRfAgcMode); + state->m_vsbRfAgcCfg.outputLevel = (ulVSBRfAgcOutputLevel); + state->m_vsbRfAgcCfg.minOutputLevel = (ulVSBRfAgcMinLevel); + state->m_vsbRfAgcCfg.maxOutputLevel = (ulVSBRfAgcMaxLevel); + state->m_vsbRfAgcCfg.speed = (ulVSBRfAgcSpeed); + state->m_vsbRfAgcCfg.top = (ulVSBRfAgcTop); + state->m_vsbRfAgcCfg.cutOffCurrent = (ulVSBRfAgcCutOffCurrent); + state->m_vsbPreSawCfg.reference = 0x07; + state->m_vsbPreSawCfg.usePreSaw = true; + + state->m_Quality83percent = DEFAULT_MER_83; + state->m_Quality93percent = DEFAULT_MER_93; + if (ulQual93 <= 500 && ulQual83 < ulQual93) { + state->m_Quality83percent = ulQual83; + state->m_Quality93percent = ulQual93; + } + + /* ATV IF */ + state->m_atvIfAgcCfg.ctrlMode = (ulATVIfAgcMode); + state->m_atvIfAgcCfg.outputLevel = (ulATVIfAgcOutputLevel); + state->m_atvIfAgcCfg.minOutputLevel = (ulATVIfAgcMinLevel); + state->m_atvIfAgcCfg.maxOutputLevel = (ulATVIfAgcMaxLevel); + state->m_atvIfAgcCfg.speed = (ulATVIfAgcSpeed); + + /* ATV RF */ + state->m_atvRfAgcCfg.ctrlMode = (ulATVRfAgcMode); + state->m_atvRfAgcCfg.outputLevel = (ulATVRfAgcOutputLevel); + state->m_atvRfAgcCfg.minOutputLevel = (ulATVRfAgcMinLevel); + state->m_atvRfAgcCfg.maxOutputLevel = (ulATVRfAgcMaxLevel); + state->m_atvRfAgcCfg.speed = (ulATVRfAgcSpeed); + state->m_atvRfAgcCfg.top = (ulATVRfAgcTop); + state->m_atvRfAgcCfg.cutOffCurrent = (ulATVRfAgcCutOffCurrent); + state->m_atvPreSawCfg.reference = 0x04; + state->m_atvPreSawCfg.usePreSaw = true; + + + /* DVBT RF */ + state->m_dvbtRfAgcCfg.ctrlMode = DRXK_AGC_CTRL_OFF; + state->m_dvbtRfAgcCfg.outputLevel = 0; + state->m_dvbtRfAgcCfg.minOutputLevel = 0; + state->m_dvbtRfAgcCfg.maxOutputLevel = 0xFFFF; + state->m_dvbtRfAgcCfg.top = 0x2100; + state->m_dvbtRfAgcCfg.cutOffCurrent = 4000; + state->m_dvbtRfAgcCfg.speed = 1; + + + /* DVBT IF */ + state->m_dvbtIfAgcCfg.ctrlMode = DRXK_AGC_CTRL_AUTO; + state->m_dvbtIfAgcCfg.outputLevel = 0; + state->m_dvbtIfAgcCfg.minOutputLevel = 0; + state->m_dvbtIfAgcCfg.maxOutputLevel = 9000; + state->m_dvbtIfAgcCfg.top = 13424; + state->m_dvbtIfAgcCfg.cutOffCurrent = 0; + state->m_dvbtIfAgcCfg.speed = 3; + state->m_dvbtIfAgcCfg.FastClipCtrlDelay = 30; + state->m_dvbtIfAgcCfg.IngainTgtMax = 30000; + /* state->m_dvbtPgaCfg = 140; */ + + state->m_dvbtPreSawCfg.reference = 4; + state->m_dvbtPreSawCfg.usePreSaw = false; + + /* QAM RF */ + state->m_qamRfAgcCfg.ctrlMode = DRXK_AGC_CTRL_OFF; + state->m_qamRfAgcCfg.outputLevel = 0; + state->m_qamRfAgcCfg.minOutputLevel = 6023; + state->m_qamRfAgcCfg.maxOutputLevel = 27000; + state->m_qamRfAgcCfg.top = 0x2380; + state->m_qamRfAgcCfg.cutOffCurrent = 4000; + state->m_qamRfAgcCfg.speed = 3; + + /* QAM IF */ + state->m_qamIfAgcCfg.ctrlMode = DRXK_AGC_CTRL_AUTO; + state->m_qamIfAgcCfg.outputLevel = 0; + state->m_qamIfAgcCfg.minOutputLevel = 0; + state->m_qamIfAgcCfg.maxOutputLevel = 9000; + state->m_qamIfAgcCfg.top = 0x0511; + state->m_qamIfAgcCfg.cutOffCurrent = 0; + state->m_qamIfAgcCfg.speed = 3; + state->m_qamIfAgcCfg.IngainTgtMax = 5119; + state->m_qamIfAgcCfg.FastClipCtrlDelay = 50; + + state->m_qamPgaCfg = 140; + state->m_qamPreSawCfg.reference = 4; + state->m_qamPreSawCfg.usePreSaw = false; + + state->m_OperationMode = OM_NONE; + state->m_DrxkState = DRXK_UNINITIALIZED; + + /* MPEG output configuration */ + state->m_enableMPEGOutput = true; /* If TRUE; enable MPEG ouput */ + state->m_insertRSByte = false; /* If TRUE; insert RS byte */ + state->m_invertDATA = false; /* If TRUE; invert DATA signals */ + state->m_invertERR = false; /* If TRUE; invert ERR signal */ + state->m_invertSTR = false; /* If TRUE; invert STR signals */ + state->m_invertVAL = false; /* If TRUE; invert VAL signals */ + state->m_invertCLK = (ulInvertTSClock != 0); /* If TRUE; invert CLK signals */ + + /* If TRUE; static MPEG clockrate will be used; + otherwise clockrate will adapt to the bitrate of the TS */ + + state->m_DVBTBitrate = ulDVBTBitrate; + state->m_DVBCBitrate = ulDVBCBitrate; + + state->m_TSDataStrength = (ulTSDataStrength & 0x07); + + /* Maximum bitrate in b/s in case static clockrate is selected */ + state->m_mpegTsStaticBitrate = 19392658; + state->m_disableTEIhandling = false; + + if (ulInsertRSByte) + state->m_insertRSByte = true; + + state->m_MpegLockTimeOut = DEFAULT_DRXK_MPEG_LOCK_TIMEOUT; + if (ulMpegLockTimeOut < 10000) + state->m_MpegLockTimeOut = ulMpegLockTimeOut; + state->m_DemodLockTimeOut = DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT; + if (ulDemodLockTimeOut < 10000) + state->m_DemodLockTimeOut = ulDemodLockTimeOut; + + /* QAM defaults */ + state->m_Constellation = DRX_CONSTELLATION_AUTO; + state->m_qamInterleaveMode = DRXK_QAM_I12_J17; + state->m_fecRsPlen = 204 * 8; /* fecRsPlen annex A */ + state->m_fecRsPrescale = 1; + + state->m_sqiSpeed = DRXK_DVBT_SQI_SPEED_MEDIUM; + state->m_agcFastClipCtrlDelay = 0; + + state->m_GPIOCfg = (ulGPIOCfg); + + state->m_bPowerDown = false; + state->m_currentPowerMode = DRX_POWER_DOWN; + + state->m_rfmirror = (ulRfMirror == 0); + state->m_IfAgcPol = false; + return 0; +} + +static int DRXX_Open(struct drxk_state *state) +{ + int status = 0; + u32 jtag = 0; + u16 bid = 0; + u16 key = 0; + + dprintk(1, "\n"); + /* stop lock indicator process */ + status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); + if (status < 0) + goto error; + /* Check device id */ + status = read16(state, SIO_TOP_COMM_KEY__A, &key); + if (status < 0) + goto error; + status = write16(state, SIO_TOP_COMM_KEY__A, SIO_TOP_COMM_KEY_KEY); + if (status < 0) + goto error; + status = read32(state, SIO_TOP_JTAGID_LO__A, &jtag); + if (status < 0) + goto error; + status = read16(state, SIO_PDR_UIO_IN_HI__A, &bid); + if (status < 0) + goto error; + status = write16(state, SIO_TOP_COMM_KEY__A, key); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int GetDeviceCapabilities(struct drxk_state *state) +{ + u16 sioPdrOhwCfg = 0; + u32 sioTopJtagidLo = 0; + int status; + const char *spin = ""; + + dprintk(1, "\n"); + + /* driver 0.9.0 */ + /* stop lock indicator process */ + status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); + if (status < 0) + goto error; + status = write16(state, SIO_TOP_COMM_KEY__A, 0xFABA); + if (status < 0) + goto error; + status = read16(state, SIO_PDR_OHW_CFG__A, &sioPdrOhwCfg); + if (status < 0) + goto error; + status = write16(state, SIO_TOP_COMM_KEY__A, 0x0000); + if (status < 0) + goto error; + + switch ((sioPdrOhwCfg & SIO_PDR_OHW_CFG_FREF_SEL__M)) { + case 0: + /* ignore (bypass ?) */ + break; + case 1: + /* 27 MHz */ + state->m_oscClockFreq = 27000; + break; + case 2: + /* 20.25 MHz */ + state->m_oscClockFreq = 20250; + break; + case 3: + /* 4 MHz */ + state->m_oscClockFreq = 20250; + break; + default: + printk(KERN_ERR "drxk: Clock Frequency is unkonwn\n"); + return -EINVAL; + } + /* + Determine device capabilities + Based on pinning v14 + */ + status = read32(state, SIO_TOP_JTAGID_LO__A, &sioTopJtagidLo); + if (status < 0) + goto error; + + printk(KERN_INFO "drxk: status = 0x%08x\n", sioTopJtagidLo); + + /* driver 0.9.0 */ + switch ((sioTopJtagidLo >> 29) & 0xF) { + case 0: + state->m_deviceSpin = DRXK_SPIN_A1; + spin = "A1"; + break; + case 2: + state->m_deviceSpin = DRXK_SPIN_A2; + spin = "A2"; + break; + case 3: + state->m_deviceSpin = DRXK_SPIN_A3; + spin = "A3"; + break; + default: + state->m_deviceSpin = DRXK_SPIN_UNKNOWN; + status = -EINVAL; + printk(KERN_ERR "drxk: Spin %d unknown\n", + (sioTopJtagidLo >> 29) & 0xF); + goto error2; + } + switch ((sioTopJtagidLo >> 12) & 0xFF) { + case 0x13: + /* typeId = DRX3913K_TYPE_ID */ + state->m_hasLNA = false; + state->m_hasOOB = false; + state->m_hasATV = false; + state->m_hasAudio = false; + state->m_hasDVBT = true; + state->m_hasDVBC = true; + state->m_hasSAWSW = true; + state->m_hasGPIO2 = false; + state->m_hasGPIO1 = false; + state->m_hasIRQN = false; + break; + case 0x15: + /* typeId = DRX3915K_TYPE_ID */ + state->m_hasLNA = false; + state->m_hasOOB = false; + state->m_hasATV = true; + state->m_hasAudio = false; + state->m_hasDVBT = true; + state->m_hasDVBC = false; + state->m_hasSAWSW = true; + state->m_hasGPIO2 = true; + state->m_hasGPIO1 = true; + state->m_hasIRQN = false; + break; + case 0x16: + /* typeId = DRX3916K_TYPE_ID */ + state->m_hasLNA = false; + state->m_hasOOB = false; + state->m_hasATV = true; + state->m_hasAudio = false; + state->m_hasDVBT = true; + state->m_hasDVBC = false; + state->m_hasSAWSW = true; + state->m_hasGPIO2 = true; + state->m_hasGPIO1 = true; + state->m_hasIRQN = false; + break; + case 0x18: + /* typeId = DRX3918K_TYPE_ID */ + state->m_hasLNA = false; + state->m_hasOOB = false; + state->m_hasATV = true; + state->m_hasAudio = true; + state->m_hasDVBT = true; + state->m_hasDVBC = false; + state->m_hasSAWSW = true; + state->m_hasGPIO2 = true; + state->m_hasGPIO1 = true; + state->m_hasIRQN = false; + break; + case 0x21: + /* typeId = DRX3921K_TYPE_ID */ + state->m_hasLNA = false; + state->m_hasOOB = false; + state->m_hasATV = true; + state->m_hasAudio = true; + state->m_hasDVBT = true; + state->m_hasDVBC = true; + state->m_hasSAWSW = true; + state->m_hasGPIO2 = true; + state->m_hasGPIO1 = true; + state->m_hasIRQN = false; + break; + case 0x23: + /* typeId = DRX3923K_TYPE_ID */ + state->m_hasLNA = false; + state->m_hasOOB = false; + state->m_hasATV = true; + state->m_hasAudio = true; + state->m_hasDVBT = true; + state->m_hasDVBC = true; + state->m_hasSAWSW = true; + state->m_hasGPIO2 = true; + state->m_hasGPIO1 = true; + state->m_hasIRQN = false; + break; + case 0x25: + /* typeId = DRX3925K_TYPE_ID */ + state->m_hasLNA = false; + state->m_hasOOB = false; + state->m_hasATV = true; + state->m_hasAudio = true; + state->m_hasDVBT = true; + state->m_hasDVBC = true; + state->m_hasSAWSW = true; + state->m_hasGPIO2 = true; + state->m_hasGPIO1 = true; + state->m_hasIRQN = false; + break; + case 0x26: + /* typeId = DRX3926K_TYPE_ID */ + state->m_hasLNA = false; + state->m_hasOOB = false; + state->m_hasATV = true; + state->m_hasAudio = false; + state->m_hasDVBT = true; + state->m_hasDVBC = true; + state->m_hasSAWSW = true; + state->m_hasGPIO2 = true; + state->m_hasGPIO1 = true; + state->m_hasIRQN = false; + break; + default: + printk(KERN_ERR "drxk: DeviceID 0x%02x not supported\n", + ((sioTopJtagidLo >> 12) & 0xFF)); + status = -EINVAL; + goto error2; + } + + printk(KERN_INFO + "drxk: detected a drx-39%02xk, spin %s, xtal %d.%03d MHz\n", + ((sioTopJtagidLo >> 12) & 0xFF), spin, + state->m_oscClockFreq / 1000, + state->m_oscClockFreq % 1000); + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + +error2: + return status; +} + +static int HI_Command(struct drxk_state *state, u16 cmd, u16 *pResult) +{ + int status; + bool powerdown_cmd; + + dprintk(1, "\n"); + + /* Write command */ + status = write16(state, SIO_HI_RA_RAM_CMD__A, cmd); + if (status < 0) + goto error; + if (cmd == SIO_HI_RA_RAM_CMD_RESET) + msleep(1); + + powerdown_cmd = + (bool) ((cmd == SIO_HI_RA_RAM_CMD_CONFIG) && + ((state->m_HICfgCtrl) & + SIO_HI_RA_RAM_PAR_5_CFG_SLEEP__M) == + SIO_HI_RA_RAM_PAR_5_CFG_SLEEP_ZZZ); + if (powerdown_cmd == false) { + /* Wait until command rdy */ + u32 retryCount = 0; + u16 waitCmd; + + do { + msleep(1); + retryCount += 1; + status = read16(state, SIO_HI_RA_RAM_CMD__A, + &waitCmd); + } while ((status < 0) && (retryCount < DRXK_MAX_RETRIES) + && (waitCmd != 0)); + if (status < 0) + goto error; + status = read16(state, SIO_HI_RA_RAM_RES__A, pResult); + } +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +static int HI_CfgCommand(struct drxk_state *state) +{ + int status; + + dprintk(1, "\n"); + + mutex_lock(&state->mutex); + + status = write16(state, SIO_HI_RA_RAM_PAR_6__A, state->m_HICfgTimeout); + if (status < 0) + goto error; + status = write16(state, SIO_HI_RA_RAM_PAR_5__A, state->m_HICfgCtrl); + if (status < 0) + goto error; + status = write16(state, SIO_HI_RA_RAM_PAR_4__A, state->m_HICfgWakeUpKey); + if (status < 0) + goto error; + status = write16(state, SIO_HI_RA_RAM_PAR_3__A, state->m_HICfgBridgeDelay); + if (status < 0) + goto error; + status = write16(state, SIO_HI_RA_RAM_PAR_2__A, state->m_HICfgTimingDiv); + if (status < 0) + goto error; + status = write16(state, SIO_HI_RA_RAM_PAR_1__A, SIO_HI_RA_RAM_PAR_1_PAR1_SEC_KEY); + if (status < 0) + goto error; + status = HI_Command(state, SIO_HI_RA_RAM_CMD_CONFIG, 0); + if (status < 0) + goto error; + + state->m_HICfgCtrl &= ~SIO_HI_RA_RAM_PAR_5_CFG_SLEEP_ZZZ; +error: + mutex_unlock(&state->mutex); + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int InitHI(struct drxk_state *state) +{ + dprintk(1, "\n"); + + state->m_HICfgWakeUpKey = (state->demod_address << 1); + state->m_HICfgTimeout = 0x96FF; + /* port/bridge/power down ctrl */ + state->m_HICfgCtrl = SIO_HI_RA_RAM_PAR_5_CFG_SLV0_SLAVE; + + return HI_CfgCommand(state); +} + +static int MPEGTSConfigurePins(struct drxk_state *state, bool mpegEnable) +{ + int status = -1; + u16 sioPdrMclkCfg = 0; + u16 sioPdrMdxCfg = 0; + u16 err_cfg = 0; + + dprintk(1, ": mpeg %s, %s mode\n", + mpegEnable ? "enable" : "disable", + state->m_enableParallel ? "parallel" : "serial"); + + /* stop lock indicator process */ + status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); + if (status < 0) + goto error; + + /* MPEG TS pad configuration */ + status = write16(state, SIO_TOP_COMM_KEY__A, 0xFABA); + if (status < 0) + goto error; + + if (mpegEnable == false) { + /* Set MPEG TS pads to inputmode */ + status = write16(state, SIO_PDR_MSTRT_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MERR_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MCLK_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MVAL_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD0_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD1_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD2_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD3_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD4_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD5_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD6_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD7_CFG__A, 0x0000); + if (status < 0) + goto error; + } else { + /* Enable MPEG output */ + sioPdrMdxCfg = + ((state->m_TSDataStrength << + SIO_PDR_MD0_CFG_DRIVE__B) | 0x0003); + sioPdrMclkCfg = ((state->m_TSClockkStrength << + SIO_PDR_MCLK_CFG_DRIVE__B) | + 0x0003); + + status = write16(state, SIO_PDR_MSTRT_CFG__A, sioPdrMdxCfg); + if (status < 0) + goto error; + + if (state->enable_merr_cfg) + err_cfg = sioPdrMdxCfg; + + status = write16(state, SIO_PDR_MERR_CFG__A, err_cfg); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MVAL_CFG__A, err_cfg); + if (status < 0) + goto error; + + if (state->m_enableParallel == true) { + /* paralel -> enable MD1 to MD7 */ + status = write16(state, SIO_PDR_MD1_CFG__A, sioPdrMdxCfg); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD2_CFG__A, sioPdrMdxCfg); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD3_CFG__A, sioPdrMdxCfg); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD4_CFG__A, sioPdrMdxCfg); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD5_CFG__A, sioPdrMdxCfg); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD6_CFG__A, sioPdrMdxCfg); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD7_CFG__A, sioPdrMdxCfg); + if (status < 0) + goto error; + } else { + sioPdrMdxCfg = ((state->m_TSDataStrength << + SIO_PDR_MD0_CFG_DRIVE__B) + | 0x0003); + /* serial -> disable MD1 to MD7 */ + status = write16(state, SIO_PDR_MD1_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD2_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD3_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD4_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD5_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD6_CFG__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD7_CFG__A, 0x0000); + if (status < 0) + goto error; + } + status = write16(state, SIO_PDR_MCLK_CFG__A, sioPdrMclkCfg); + if (status < 0) + goto error; + status = write16(state, SIO_PDR_MD0_CFG__A, sioPdrMdxCfg); + if (status < 0) + goto error; + } + /* Enable MB output over MPEG pads and ctl input */ + status = write16(state, SIO_PDR_MON_CFG__A, 0x0000); + if (status < 0) + goto error; + /* Write nomagic word to enable pdr reg write */ + status = write16(state, SIO_TOP_COMM_KEY__A, 0x0000); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int MPEGTSDisable(struct drxk_state *state) +{ + dprintk(1, "\n"); + + return MPEGTSConfigurePins(state, false); +} + +static int BLChainCmd(struct drxk_state *state, + u16 romOffset, u16 nrOfElements, u32 timeOut) +{ + u16 blStatus = 0; + int status; + unsigned long end; + + dprintk(1, "\n"); + mutex_lock(&state->mutex); + status = write16(state, SIO_BL_MODE__A, SIO_BL_MODE_CHAIN); + if (status < 0) + goto error; + status = write16(state, SIO_BL_CHAIN_ADDR__A, romOffset); + if (status < 0) + goto error; + status = write16(state, SIO_BL_CHAIN_LEN__A, nrOfElements); + if (status < 0) + goto error; + status = write16(state, SIO_BL_ENABLE__A, SIO_BL_ENABLE_ON); + if (status < 0) + goto error; + + end = jiffies + msecs_to_jiffies(timeOut); + do { + msleep(1); + status = read16(state, SIO_BL_STATUS__A, &blStatus); + if (status < 0) + goto error; + } while ((blStatus == 0x1) && + ((time_is_after_jiffies(end)))); + + if (blStatus == 0x1) { + printk(KERN_ERR "drxk: SIO not ready\n"); + status = -EINVAL; + goto error2; + } +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); +error2: + mutex_unlock(&state->mutex); + return status; +} + + +static int DownloadMicrocode(struct drxk_state *state, + const u8 pMCImage[], u32 Length) +{ + const u8 *pSrc = pMCImage; + u32 Address; + u16 nBlocks; + u16 BlockSize; + u32 offset = 0; + u32 i; + int status = 0; + + dprintk(1, "\n"); + + /* down the drain (we don't care about MAGIC_WORD) */ +#if 0 + /* For future reference */ + Drain = (pSrc[0] << 8) | pSrc[1]; +#endif + pSrc += sizeof(u16); + offset += sizeof(u16); + nBlocks = (pSrc[0] << 8) | pSrc[1]; + pSrc += sizeof(u16); + offset += sizeof(u16); + + for (i = 0; i < nBlocks; i += 1) { + Address = (pSrc[0] << 24) | (pSrc[1] << 16) | + (pSrc[2] << 8) | pSrc[3]; + pSrc += sizeof(u32); + offset += sizeof(u32); + + BlockSize = ((pSrc[0] << 8) | pSrc[1]) * sizeof(u16); + pSrc += sizeof(u16); + offset += sizeof(u16); + +#if 0 + /* For future reference */ + Flags = (pSrc[0] << 8) | pSrc[1]; +#endif + pSrc += sizeof(u16); + offset += sizeof(u16); + +#if 0 + /* For future reference */ + BlockCRC = (pSrc[0] << 8) | pSrc[1]; +#endif + pSrc += sizeof(u16); + offset += sizeof(u16); + + if (offset + BlockSize > Length) { + printk(KERN_ERR "drxk: Firmware is corrupted.\n"); + return -EINVAL; + } + + status = write_block(state, Address, BlockSize, pSrc); + if (status < 0) { + printk(KERN_ERR "drxk: Error %d while loading firmware\n", status); + break; + } + pSrc += BlockSize; + offset += BlockSize; + } + return status; +} + +static int DVBTEnableOFDMTokenRing(struct drxk_state *state, bool enable) +{ + int status; + u16 data = 0; + u16 desiredCtrl = SIO_OFDM_SH_OFDM_RING_ENABLE_ON; + u16 desiredStatus = SIO_OFDM_SH_OFDM_RING_STATUS_ENABLED; + unsigned long end; + + dprintk(1, "\n"); + + if (enable == false) { + desiredCtrl = SIO_OFDM_SH_OFDM_RING_ENABLE_OFF; + desiredStatus = SIO_OFDM_SH_OFDM_RING_STATUS_DOWN; + } + + status = read16(state, SIO_OFDM_SH_OFDM_RING_STATUS__A, &data); + if (status >= 0 && data == desiredStatus) { + /* tokenring already has correct status */ + return status; + } + /* Disable/enable dvbt tokenring bridge */ + status = write16(state, SIO_OFDM_SH_OFDM_RING_ENABLE__A, desiredCtrl); + + end = jiffies + msecs_to_jiffies(DRXK_OFDM_TR_SHUTDOWN_TIMEOUT); + do { + status = read16(state, SIO_OFDM_SH_OFDM_RING_STATUS__A, &data); + if ((status >= 0 && data == desiredStatus) || time_is_after_jiffies(end)) + break; + msleep(1); + } while (1); + if (data != desiredStatus) { + printk(KERN_ERR "drxk: SIO not ready\n"); + return -EINVAL; + } + return status; +} + +static int MPEGTSStop(struct drxk_state *state) +{ + int status = 0; + u16 fecOcSncMode = 0; + u16 fecOcIprMode = 0; + + dprintk(1, "\n"); + + /* Gracefull shutdown (byte boundaries) */ + status = read16(state, FEC_OC_SNC_MODE__A, &fecOcSncMode); + if (status < 0) + goto error; + fecOcSncMode |= FEC_OC_SNC_MODE_SHUTDOWN__M; + status = write16(state, FEC_OC_SNC_MODE__A, fecOcSncMode); + if (status < 0) + goto error; + + /* Suppress MCLK during absence of data */ + status = read16(state, FEC_OC_IPR_MODE__A, &fecOcIprMode); + if (status < 0) + goto error; + fecOcIprMode |= FEC_OC_IPR_MODE_MCLK_DIS_DAT_ABS__M; + status = write16(state, FEC_OC_IPR_MODE__A, fecOcIprMode); + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +static int scu_command(struct drxk_state *state, + u16 cmd, u8 parameterLen, + u16 *parameter, u8 resultLen, u16 *result) +{ +#if (SCU_RAM_PARAM_0__A - SCU_RAM_PARAM_15__A) != 15 +#error DRXK register mapping no longer compatible with this routine! +#endif + u16 curCmd = 0; + int status = -EINVAL; + unsigned long end; + u8 buffer[34]; + int cnt = 0, ii; + const char *p; + char errname[30]; + + dprintk(1, "\n"); + + if ((cmd == 0) || ((parameterLen > 0) && (parameter == NULL)) || + ((resultLen > 0) && (result == NULL))) { + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; + } + + mutex_lock(&state->mutex); + + /* assume that the command register is ready + since it is checked afterwards */ + for (ii = parameterLen - 1; ii >= 0; ii -= 1) { + buffer[cnt++] = (parameter[ii] & 0xFF); + buffer[cnt++] = ((parameter[ii] >> 8) & 0xFF); + } + buffer[cnt++] = (cmd & 0xFF); + buffer[cnt++] = ((cmd >> 8) & 0xFF); + + write_block(state, SCU_RAM_PARAM_0__A - + (parameterLen - 1), cnt, buffer); + /* Wait until SCU has processed command */ + end = jiffies + msecs_to_jiffies(DRXK_MAX_WAITTIME); + do { + msleep(1); + status = read16(state, SCU_RAM_COMMAND__A, &curCmd); + if (status < 0) + goto error; + } while (!(curCmd == DRX_SCU_READY) && (time_is_after_jiffies(end))); + if (curCmd != DRX_SCU_READY) { + printk(KERN_ERR "drxk: SCU not ready\n"); + status = -EIO; + goto error2; + } + /* read results */ + if ((resultLen > 0) && (result != NULL)) { + s16 err; + int ii; + + for (ii = resultLen - 1; ii >= 0; ii -= 1) { + status = read16(state, SCU_RAM_PARAM_0__A - ii, &result[ii]); + if (status < 0) + goto error; + } + + /* Check if an error was reported by SCU */ + err = (s16)result[0]; + if (err >= 0) + goto error; + + /* check for the known error codes */ + switch (err) { + case SCU_RESULT_UNKCMD: + p = "SCU_RESULT_UNKCMD"; + break; + case SCU_RESULT_UNKSTD: + p = "SCU_RESULT_UNKSTD"; + break; + case SCU_RESULT_SIZE: + p = "SCU_RESULT_SIZE"; + break; + case SCU_RESULT_INVPAR: + p = "SCU_RESULT_INVPAR"; + break; + default: /* Other negative values are errors */ + sprintf(errname, "ERROR: %d\n", err); + p = errname; + } + printk(KERN_ERR "drxk: %s while sending cmd 0x%04x with params:", p, cmd); + print_hex_dump_bytes("drxk: ", DUMP_PREFIX_NONE, buffer, cnt); + status = -EINVAL; + goto error2; + } + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); +error2: + mutex_unlock(&state->mutex); + return status; +} + +static int SetIqmAf(struct drxk_state *state, bool active) +{ + u16 data = 0; + int status; + + dprintk(1, "\n"); + + /* Configure IQM */ + status = read16(state, IQM_AF_STDBY__A, &data); + if (status < 0) + goto error; + + if (!active) { + data |= (IQM_AF_STDBY_STDBY_ADC_STANDBY + | IQM_AF_STDBY_STDBY_AMP_STANDBY + | IQM_AF_STDBY_STDBY_PD_STANDBY + | IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY + | IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY); + } else { + data &= ((~IQM_AF_STDBY_STDBY_ADC_STANDBY) + & (~IQM_AF_STDBY_STDBY_AMP_STANDBY) + & (~IQM_AF_STDBY_STDBY_PD_STANDBY) + & (~IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY) + & (~IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY) + ); + } + status = write16(state, IQM_AF_STDBY__A, data); + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int CtrlPowerMode(struct drxk_state *state, enum DRXPowerMode *mode) +{ + int status = 0; + u16 sioCcPwdMode = 0; + + dprintk(1, "\n"); + + /* Check arguments */ + if (mode == NULL) + return -EINVAL; + + switch (*mode) { + case DRX_POWER_UP: + sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_NONE; + break; + case DRXK_POWER_DOWN_OFDM: + sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_OFDM; + break; + case DRXK_POWER_DOWN_CORE: + sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_CLOCK; + break; + case DRXK_POWER_DOWN_PLL: + sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_PLL; + break; + case DRX_POWER_DOWN: + sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_OSC; + break; + default: + /* Unknow sleep mode */ + return -EINVAL; + } + + /* If already in requested power mode, do nothing */ + if (state->m_currentPowerMode == *mode) + return 0; + + /* For next steps make sure to start from DRX_POWER_UP mode */ + if (state->m_currentPowerMode != DRX_POWER_UP) { + status = PowerUpDevice(state); + if (status < 0) + goto error; + status = DVBTEnableOFDMTokenRing(state, true); + if (status < 0) + goto error; + } + + if (*mode == DRX_POWER_UP) { + /* Restore analog & pin configuartion */ + } else { + /* Power down to requested mode */ + /* Backup some register settings */ + /* Set pins with possible pull-ups connected + to them in input mode */ + /* Analog power down */ + /* ADC power down */ + /* Power down device */ + /* stop all comm_exec */ + /* Stop and power down previous standard */ + switch (state->m_OperationMode) { + case OM_DVBT: + status = MPEGTSStop(state); + if (status < 0) + goto error; + status = PowerDownDVBT(state, false); + if (status < 0) + goto error; + break; + case OM_QAM_ITU_A: + case OM_QAM_ITU_C: + status = MPEGTSStop(state); + if (status < 0) + goto error; + status = PowerDownQAM(state); + if (status < 0) + goto error; + break; + default: + break; + } + status = DVBTEnableOFDMTokenRing(state, false); + if (status < 0) + goto error; + status = write16(state, SIO_CC_PWD_MODE__A, sioCcPwdMode); + if (status < 0) + goto error; + status = write16(state, SIO_CC_UPDATE__A, SIO_CC_UPDATE_KEY); + if (status < 0) + goto error; + + if (*mode != DRXK_POWER_DOWN_OFDM) { + state->m_HICfgCtrl |= + SIO_HI_RA_RAM_PAR_5_CFG_SLEEP_ZZZ; + status = HI_CfgCommand(state); + if (status < 0) + goto error; + } + } + state->m_currentPowerMode = *mode; + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +static int PowerDownDVBT(struct drxk_state *state, bool setPowerMode) +{ + enum DRXPowerMode powerMode = DRXK_POWER_DOWN_OFDM; + u16 cmdResult = 0; + u16 data = 0; + int status; + + dprintk(1, "\n"); + + status = read16(state, SCU_COMM_EXEC__A, &data); + if (status < 0) + goto error; + if (data == SCU_COMM_EXEC_ACTIVE) { + /* Send OFDM stop command */ + status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_STOP, 0, NULL, 1, &cmdResult); + if (status < 0) + goto error; + /* Send OFDM reset command */ + status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_RESET, 0, NULL, 1, &cmdResult); + if (status < 0) + goto error; + } + + /* Reset datapath for OFDM, processors first */ + status = write16(state, OFDM_SC_COMM_EXEC__A, OFDM_SC_COMM_EXEC_STOP); + if (status < 0) + goto error; + status = write16(state, OFDM_LC_COMM_EXEC__A, OFDM_LC_COMM_EXEC_STOP); + if (status < 0) + goto error; + status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_STOP); + if (status < 0) + goto error; + + /* powerdown AFE */ + status = SetIqmAf(state, false); + if (status < 0) + goto error; + + /* powerdown to OFDM mode */ + if (setPowerMode) { + status = CtrlPowerMode(state, &powerMode); + if (status < 0) + goto error; + } +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int SetOperationMode(struct drxk_state *state, + enum OperationMode oMode) +{ + int status = 0; + + dprintk(1, "\n"); + /* + Stop and power down previous standard + TODO investigate total power down instead of partial + power down depending on "previous" standard. + */ + + /* disable HW lock indicator */ + status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); + if (status < 0) + goto error; + + /* Device is already at the required mode */ + if (state->m_OperationMode == oMode) + return 0; + + switch (state->m_OperationMode) { + /* OM_NONE was added for start up */ + case OM_NONE: + break; + case OM_DVBT: + status = MPEGTSStop(state); + if (status < 0) + goto error; + status = PowerDownDVBT(state, true); + if (status < 0) + goto error; + state->m_OperationMode = OM_NONE; + break; + case OM_QAM_ITU_A: /* fallthrough */ + case OM_QAM_ITU_C: + status = MPEGTSStop(state); + if (status < 0) + goto error; + status = PowerDownQAM(state); + if (status < 0) + goto error; + state->m_OperationMode = OM_NONE; + break; + case OM_QAM_ITU_B: + default: + status = -EINVAL; + goto error; + } + + /* + Power up new standard + */ + switch (oMode) { + case OM_DVBT: + dprintk(1, ": DVB-T\n"); + state->m_OperationMode = oMode; + status = SetDVBTStandard(state, oMode); + if (status < 0) + goto error; + break; + case OM_QAM_ITU_A: /* fallthrough */ + case OM_QAM_ITU_C: + dprintk(1, ": DVB-C Annex %c\n", + (state->m_OperationMode == OM_QAM_ITU_A) ? 'A' : 'C'); + state->m_OperationMode = oMode; + status = SetQAMStandard(state, oMode); + if (status < 0) + goto error; + break; + case OM_QAM_ITU_B: + default: + status = -EINVAL; + } +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int Start(struct drxk_state *state, s32 offsetFreq, + s32 IntermediateFrequency) +{ + int status = -EINVAL; + + u16 IFreqkHz; + s32 OffsetkHz = offsetFreq / 1000; + + dprintk(1, "\n"); + if (state->m_DrxkState != DRXK_STOPPED && + state->m_DrxkState != DRXK_DTV_STARTED) + goto error; + + state->m_bMirrorFreqSpect = (state->props.inversion == INVERSION_ON); + + if (IntermediateFrequency < 0) { + state->m_bMirrorFreqSpect = !state->m_bMirrorFreqSpect; + IntermediateFrequency = -IntermediateFrequency; + } + + switch (state->m_OperationMode) { + case OM_QAM_ITU_A: + case OM_QAM_ITU_C: + IFreqkHz = (IntermediateFrequency / 1000); + status = SetQAM(state, IFreqkHz, OffsetkHz); + if (status < 0) + goto error; + state->m_DrxkState = DRXK_DTV_STARTED; + break; + case OM_DVBT: + IFreqkHz = (IntermediateFrequency / 1000); + status = MPEGTSStop(state); + if (status < 0) + goto error; + status = SetDVBT(state, IFreqkHz, OffsetkHz); + if (status < 0) + goto error; + status = DVBTStart(state); + if (status < 0) + goto error; + state->m_DrxkState = DRXK_DTV_STARTED; + break; + default: + break; + } +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int ShutDown(struct drxk_state *state) +{ + dprintk(1, "\n"); + + MPEGTSStop(state); + return 0; +} + +static int GetLockStatus(struct drxk_state *state, u32 *pLockStatus, + u32 Time) +{ + int status = -EINVAL; + + dprintk(1, "\n"); + + if (pLockStatus == NULL) + goto error; + + *pLockStatus = NOT_LOCKED; + + /* define the SCU command code */ + switch (state->m_OperationMode) { + case OM_QAM_ITU_A: + case OM_QAM_ITU_B: + case OM_QAM_ITU_C: + status = GetQAMLockStatus(state, pLockStatus); + break; + case OM_DVBT: + status = GetDVBTLockStatus(state, pLockStatus); + break; + default: + break; + } +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int MPEGTSStart(struct drxk_state *state) +{ + int status; + + u16 fecOcSncMode = 0; + + /* Allow OC to sync again */ + status = read16(state, FEC_OC_SNC_MODE__A, &fecOcSncMode); + if (status < 0) + goto error; + fecOcSncMode &= ~FEC_OC_SNC_MODE_SHUTDOWN__M; + status = write16(state, FEC_OC_SNC_MODE__A, fecOcSncMode); + if (status < 0) + goto error; + status = write16(state, FEC_OC_SNC_UNLOCK__A, 1); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int MPEGTSDtoInit(struct drxk_state *state) +{ + int status; + + dprintk(1, "\n"); + + /* Rate integration settings */ + status = write16(state, FEC_OC_RCN_CTL_STEP_LO__A, 0x0000); + if (status < 0) + goto error; + status = write16(state, FEC_OC_RCN_CTL_STEP_HI__A, 0x000C); + if (status < 0) + goto error; + status = write16(state, FEC_OC_RCN_GAIN__A, 0x000A); + if (status < 0) + goto error; + status = write16(state, FEC_OC_AVR_PARM_A__A, 0x0008); + if (status < 0) + goto error; + status = write16(state, FEC_OC_AVR_PARM_B__A, 0x0006); + if (status < 0) + goto error; + status = write16(state, FEC_OC_TMD_HI_MARGIN__A, 0x0680); + if (status < 0) + goto error; + status = write16(state, FEC_OC_TMD_LO_MARGIN__A, 0x0080); + if (status < 0) + goto error; + status = write16(state, FEC_OC_TMD_COUNT__A, 0x03F4); + if (status < 0) + goto error; + + /* Additional configuration */ + status = write16(state, FEC_OC_OCR_INVERT__A, 0); + if (status < 0) + goto error; + status = write16(state, FEC_OC_SNC_LWM__A, 2); + if (status < 0) + goto error; + status = write16(state, FEC_OC_SNC_HWM__A, 12); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +static int MPEGTSDtoSetup(struct drxk_state *state, + enum OperationMode oMode) +{ + int status; + + u16 fecOcRegMode = 0; /* FEC_OC_MODE register value */ + u16 fecOcRegIprMode = 0; /* FEC_OC_IPR_MODE register value */ + u16 fecOcDtoMode = 0; /* FEC_OC_IPR_INVERT register value */ + u16 fecOcFctMode = 0; /* FEC_OC_IPR_INVERT register value */ + u16 fecOcDtoPeriod = 2; /* FEC_OC_IPR_INVERT register value */ + u16 fecOcDtoBurstLen = 188; /* FEC_OC_IPR_INVERT register value */ + u32 fecOcRcnCtlRate = 0; /* FEC_OC_IPR_INVERT register value */ + u16 fecOcTmdMode = 0; + u16 fecOcTmdIntUpdRate = 0; + u32 maxBitRate = 0; + bool staticCLK = false; + + dprintk(1, "\n"); + + /* Check insertion of the Reed-Solomon parity bytes */ + status = read16(state, FEC_OC_MODE__A, &fecOcRegMode); + if (status < 0) + goto error; + status = read16(state, FEC_OC_IPR_MODE__A, &fecOcRegIprMode); + if (status < 0) + goto error; + fecOcRegMode &= (~FEC_OC_MODE_PARITY__M); + fecOcRegIprMode &= (~FEC_OC_IPR_MODE_MVAL_DIS_PAR__M); + if (state->m_insertRSByte == true) { + /* enable parity symbol forward */ + fecOcRegMode |= FEC_OC_MODE_PARITY__M; + /* MVAL disable during parity bytes */ + fecOcRegIprMode |= FEC_OC_IPR_MODE_MVAL_DIS_PAR__M; + /* TS burst length to 204 */ + fecOcDtoBurstLen = 204; + } + + /* Check serial or parrallel output */ + fecOcRegIprMode &= (~(FEC_OC_IPR_MODE_SERIAL__M)); + if (state->m_enableParallel == false) { + /* MPEG data output is serial -> set ipr_mode[0] */ + fecOcRegIprMode |= FEC_OC_IPR_MODE_SERIAL__M; + } + + switch (oMode) { + case OM_DVBT: + maxBitRate = state->m_DVBTBitrate; + fecOcTmdMode = 3; + fecOcRcnCtlRate = 0xC00000; + staticCLK = state->m_DVBTStaticCLK; + break; + case OM_QAM_ITU_A: /* fallthrough */ + case OM_QAM_ITU_C: + fecOcTmdMode = 0x0004; + fecOcRcnCtlRate = 0xD2B4EE; /* good for >63 Mb/s */ + maxBitRate = state->m_DVBCBitrate; + staticCLK = state->m_DVBCStaticCLK; + break; + default: + status = -EINVAL; + } /* switch (standard) */ + if (status < 0) + goto error; + + /* Configure DTO's */ + if (staticCLK) { + u32 bitRate = 0; + + /* Rational DTO for MCLK source (static MCLK rate), + Dynamic DTO for optimal grouping + (avoid intra-packet gaps), + DTO offset enable to sync TS burst with MSTRT */ + fecOcDtoMode = (FEC_OC_DTO_MODE_DYNAMIC__M | + FEC_OC_DTO_MODE_OFFSET_ENABLE__M); + fecOcFctMode = (FEC_OC_FCT_MODE_RAT_ENA__M | + FEC_OC_FCT_MODE_VIRT_ENA__M); + + /* Check user defined bitrate */ + bitRate = maxBitRate; + if (bitRate > 75900000UL) { /* max is 75.9 Mb/s */ + bitRate = 75900000UL; + } + /* Rational DTO period: + dto_period = (Fsys / bitrate) - 2 + + Result should be floored, + to make sure >= requested bitrate + */ + fecOcDtoPeriod = (u16) (((state->m_sysClockFreq) + * 1000) / bitRate); + if (fecOcDtoPeriod <= 2) + fecOcDtoPeriod = 0; + else + fecOcDtoPeriod -= 2; + fecOcTmdIntUpdRate = 8; + } else { + /* (commonAttr->staticCLK == false) => dynamic mode */ + fecOcDtoMode = FEC_OC_DTO_MODE_DYNAMIC__M; + fecOcFctMode = FEC_OC_FCT_MODE__PRE; + fecOcTmdIntUpdRate = 5; + } + + /* Write appropriate registers with requested configuration */ + status = write16(state, FEC_OC_DTO_BURST_LEN__A, fecOcDtoBurstLen); + if (status < 0) + goto error; + status = write16(state, FEC_OC_DTO_PERIOD__A, fecOcDtoPeriod); + if (status < 0) + goto error; + status = write16(state, FEC_OC_DTO_MODE__A, fecOcDtoMode); + if (status < 0) + goto error; + status = write16(state, FEC_OC_FCT_MODE__A, fecOcFctMode); + if (status < 0) + goto error; + status = write16(state, FEC_OC_MODE__A, fecOcRegMode); + if (status < 0) + goto error; + status = write16(state, FEC_OC_IPR_MODE__A, fecOcRegIprMode); + if (status < 0) + goto error; + + /* Rate integration settings */ + status = write32(state, FEC_OC_RCN_CTL_RATE_LO__A, fecOcRcnCtlRate); + if (status < 0) + goto error; + status = write16(state, FEC_OC_TMD_INT_UPD_RATE__A, fecOcTmdIntUpdRate); + if (status < 0) + goto error; + status = write16(state, FEC_OC_TMD_MODE__A, fecOcTmdMode); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int MPEGTSConfigurePolarity(struct drxk_state *state) +{ + u16 fecOcRegIprInvert = 0; + + /* Data mask for the output data byte */ + u16 InvertDataMask = + FEC_OC_IPR_INVERT_MD7__M | FEC_OC_IPR_INVERT_MD6__M | + FEC_OC_IPR_INVERT_MD5__M | FEC_OC_IPR_INVERT_MD4__M | + FEC_OC_IPR_INVERT_MD3__M | FEC_OC_IPR_INVERT_MD2__M | + FEC_OC_IPR_INVERT_MD1__M | FEC_OC_IPR_INVERT_MD0__M; + + dprintk(1, "\n"); + + /* Control selective inversion of output bits */ + fecOcRegIprInvert &= (~(InvertDataMask)); + if (state->m_invertDATA == true) + fecOcRegIprInvert |= InvertDataMask; + fecOcRegIprInvert &= (~(FEC_OC_IPR_INVERT_MERR__M)); + if (state->m_invertERR == true) + fecOcRegIprInvert |= FEC_OC_IPR_INVERT_MERR__M; + fecOcRegIprInvert &= (~(FEC_OC_IPR_INVERT_MSTRT__M)); + if (state->m_invertSTR == true) + fecOcRegIprInvert |= FEC_OC_IPR_INVERT_MSTRT__M; + fecOcRegIprInvert &= (~(FEC_OC_IPR_INVERT_MVAL__M)); + if (state->m_invertVAL == true) + fecOcRegIprInvert |= FEC_OC_IPR_INVERT_MVAL__M; + fecOcRegIprInvert &= (~(FEC_OC_IPR_INVERT_MCLK__M)); + if (state->m_invertCLK == true) + fecOcRegIprInvert |= FEC_OC_IPR_INVERT_MCLK__M; + + return write16(state, FEC_OC_IPR_INVERT__A, fecOcRegIprInvert); +} + +#define SCU_RAM_AGC_KI_INV_RF_POL__M 0x4000 + +static int SetAgcRf(struct drxk_state *state, + struct SCfgAgc *pAgcCfg, bool isDTV) +{ + int status = -EINVAL; + u16 data = 0; + struct SCfgAgc *pIfAgcSettings; + + dprintk(1, "\n"); + + if (pAgcCfg == NULL) + goto error; + + switch (pAgcCfg->ctrlMode) { + case DRXK_AGC_CTRL_AUTO: + /* Enable RF AGC DAC */ + status = read16(state, IQM_AF_STDBY__A, &data); + if (status < 0) + goto error; + data &= ~IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY; + status = write16(state, IQM_AF_STDBY__A, data); + if (status < 0) + goto error; + status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); + if (status < 0) + goto error; + + /* Enable SCU RF AGC loop */ + data &= ~SCU_RAM_AGC_CONFIG_DISABLE_RF_AGC__M; + + /* Polarity */ + if (state->m_RfAgcPol) + data |= SCU_RAM_AGC_CONFIG_INV_RF_POL__M; + else + data &= ~SCU_RAM_AGC_CONFIG_INV_RF_POL__M; + status = write16(state, SCU_RAM_AGC_CONFIG__A, data); + if (status < 0) + goto error; + + /* Set speed (using complementary reduction value) */ + status = read16(state, SCU_RAM_AGC_KI_RED__A, &data); + if (status < 0) + goto error; + + data &= ~SCU_RAM_AGC_KI_RED_RAGC_RED__M; + data |= (~(pAgcCfg->speed << + SCU_RAM_AGC_KI_RED_RAGC_RED__B) + & SCU_RAM_AGC_KI_RED_RAGC_RED__M); + + status = write16(state, SCU_RAM_AGC_KI_RED__A, data); + if (status < 0) + goto error; + + if (IsDVBT(state)) + pIfAgcSettings = &state->m_dvbtIfAgcCfg; + else if (IsQAM(state)) + pIfAgcSettings = &state->m_qamIfAgcCfg; + else + pIfAgcSettings = &state->m_atvIfAgcCfg; + if (pIfAgcSettings == NULL) { + status = -EINVAL; + goto error; + } + + /* Set TOP, only if IF-AGC is in AUTO mode */ + if (pIfAgcSettings->ctrlMode == DRXK_AGC_CTRL_AUTO) + status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MAX__A, pAgcCfg->top); + if (status < 0) + goto error; + + /* Cut-Off current */ + status = write16(state, SCU_RAM_AGC_RF_IACCU_HI_CO__A, pAgcCfg->cutOffCurrent); + if (status < 0) + goto error; + + /* Max. output level */ + status = write16(state, SCU_RAM_AGC_RF_MAX__A, pAgcCfg->maxOutputLevel); + if (status < 0) + goto error; + + break; + + case DRXK_AGC_CTRL_USER: + /* Enable RF AGC DAC */ + status = read16(state, IQM_AF_STDBY__A, &data); + if (status < 0) + goto error; + data &= ~IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY; + status = write16(state, IQM_AF_STDBY__A, data); + if (status < 0) + goto error; + + /* Disable SCU RF AGC loop */ + status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); + if (status < 0) + goto error; + data |= SCU_RAM_AGC_CONFIG_DISABLE_RF_AGC__M; + if (state->m_RfAgcPol) + data |= SCU_RAM_AGC_CONFIG_INV_RF_POL__M; + else + data &= ~SCU_RAM_AGC_CONFIG_INV_RF_POL__M; + status = write16(state, SCU_RAM_AGC_CONFIG__A, data); + if (status < 0) + goto error; + + /* SCU c.o.c. to 0, enabling full control range */ + status = write16(state, SCU_RAM_AGC_RF_IACCU_HI_CO__A, 0); + if (status < 0) + goto error; + + /* Write value to output pin */ + status = write16(state, SCU_RAM_AGC_RF_IACCU_HI__A, pAgcCfg->outputLevel); + if (status < 0) + goto error; + break; + + case DRXK_AGC_CTRL_OFF: + /* Disable RF AGC DAC */ + status = read16(state, IQM_AF_STDBY__A, &data); + if (status < 0) + goto error; + data |= IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY; + status = write16(state, IQM_AF_STDBY__A, data); + if (status < 0) + goto error; + + /* Disable SCU RF AGC loop */ + status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); + if (status < 0) + goto error; + data |= SCU_RAM_AGC_CONFIG_DISABLE_RF_AGC__M; + status = write16(state, SCU_RAM_AGC_CONFIG__A, data); + if (status < 0) + goto error; + break; + + default: + status = -EINVAL; + + } +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +#define SCU_RAM_AGC_KI_INV_IF_POL__M 0x2000 + +static int SetAgcIf(struct drxk_state *state, + struct SCfgAgc *pAgcCfg, bool isDTV) +{ + u16 data = 0; + int status = 0; + struct SCfgAgc *pRfAgcSettings; + + dprintk(1, "\n"); + + switch (pAgcCfg->ctrlMode) { + case DRXK_AGC_CTRL_AUTO: + + /* Enable IF AGC DAC */ + status = read16(state, IQM_AF_STDBY__A, &data); + if (status < 0) + goto error; + data &= ~IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY; + status = write16(state, IQM_AF_STDBY__A, data); + if (status < 0) + goto error; + + status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); + if (status < 0) + goto error; + + /* Enable SCU IF AGC loop */ + data &= ~SCU_RAM_AGC_CONFIG_DISABLE_IF_AGC__M; + + /* Polarity */ + if (state->m_IfAgcPol) + data |= SCU_RAM_AGC_CONFIG_INV_IF_POL__M; + else + data &= ~SCU_RAM_AGC_CONFIG_INV_IF_POL__M; + status = write16(state, SCU_RAM_AGC_CONFIG__A, data); + if (status < 0) + goto error; + + /* Set speed (using complementary reduction value) */ + status = read16(state, SCU_RAM_AGC_KI_RED__A, &data); + if (status < 0) + goto error; + data &= ~SCU_RAM_AGC_KI_RED_IAGC_RED__M; + data |= (~(pAgcCfg->speed << + SCU_RAM_AGC_KI_RED_IAGC_RED__B) + & SCU_RAM_AGC_KI_RED_IAGC_RED__M); + + status = write16(state, SCU_RAM_AGC_KI_RED__A, data); + if (status < 0) + goto error; + + if (IsQAM(state)) + pRfAgcSettings = &state->m_qamRfAgcCfg; + else + pRfAgcSettings = &state->m_atvRfAgcCfg; + if (pRfAgcSettings == NULL) + return -1; + /* Restore TOP */ + status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MAX__A, pRfAgcSettings->top); + if (status < 0) + goto error; + break; + + case DRXK_AGC_CTRL_USER: + + /* Enable IF AGC DAC */ + status = read16(state, IQM_AF_STDBY__A, &data); + if (status < 0) + goto error; + data &= ~IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY; + status = write16(state, IQM_AF_STDBY__A, data); + if (status < 0) + goto error; + + status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); + if (status < 0) + goto error; + + /* Disable SCU IF AGC loop */ + data |= SCU_RAM_AGC_CONFIG_DISABLE_IF_AGC__M; + + /* Polarity */ + if (state->m_IfAgcPol) + data |= SCU_RAM_AGC_CONFIG_INV_IF_POL__M; + else + data &= ~SCU_RAM_AGC_CONFIG_INV_IF_POL__M; + status = write16(state, SCU_RAM_AGC_CONFIG__A, data); + if (status < 0) + goto error; + + /* Write value to output pin */ + status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MAX__A, pAgcCfg->outputLevel); + if (status < 0) + goto error; + break; + + case DRXK_AGC_CTRL_OFF: + + /* Disable If AGC DAC */ + status = read16(state, IQM_AF_STDBY__A, &data); + if (status < 0) + goto error; + data |= IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY; + status = write16(state, IQM_AF_STDBY__A, data); + if (status < 0) + goto error; + + /* Disable SCU IF AGC loop */ + status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); + if (status < 0) + goto error; + data |= SCU_RAM_AGC_CONFIG_DISABLE_IF_AGC__M; + status = write16(state, SCU_RAM_AGC_CONFIG__A, data); + if (status < 0) + goto error; + break; + } /* switch (agcSettingsIf->ctrlMode) */ + + /* always set the top to support + configurations without if-loop */ + status = write16(state, SCU_RAM_AGC_INGAIN_TGT_MIN__A, pAgcCfg->top); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int ReadIFAgc(struct drxk_state *state, u32 *pValue) +{ + u16 agcDacLvl; + int status; + u16 Level = 0; + + dprintk(1, "\n"); + + status = read16(state, IQM_AF_AGC_IF__A, &agcDacLvl); + if (status < 0) { + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; + } + + *pValue = 0; + + if (agcDacLvl > DRXK_AGC_DAC_OFFSET) + Level = agcDacLvl - DRXK_AGC_DAC_OFFSET; + if (Level < 14000) + *pValue = (14000 - Level) / 4; + else + *pValue = 0; + + return status; +} + +static int GetQAMSignalToNoise(struct drxk_state *state, + s32 *pSignalToNoise) +{ + int status = 0; + u16 qamSlErrPower = 0; /* accum. error between + raw and sliced symbols */ + u32 qamSlSigPower = 0; /* used for MER, depends of + QAM modulation */ + u32 qamSlMer = 0; /* QAM MER */ + + dprintk(1, "\n"); + + /* MER calculation */ + + /* get the register value needed for MER */ + status = read16(state, QAM_SL_ERR_POWER__A, &qamSlErrPower); + if (status < 0) { + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return -EINVAL; + } + + switch (state->props.modulation) { + case QAM_16: + qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM16 << 2; + break; + case QAM_32: + qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM32 << 2; + break; + case QAM_64: + qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM64 << 2; + break; + case QAM_128: + qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM128 << 2; + break; + default: + case QAM_256: + qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM256 << 2; + break; + } + + if (qamSlErrPower > 0) { + qamSlMer = Log10Times100(qamSlSigPower) - + Log10Times100((u32) qamSlErrPower); + } + *pSignalToNoise = qamSlMer; + + return status; +} + +static int GetDVBTSignalToNoise(struct drxk_state *state, + s32 *pSignalToNoise) +{ + int status; + u16 regData = 0; + u32 EqRegTdSqrErrI = 0; + u32 EqRegTdSqrErrQ = 0; + u16 EqRegTdSqrErrExp = 0; + u16 EqRegTdTpsPwrOfs = 0; + u16 EqRegTdReqSmbCnt = 0; + u32 tpsCnt = 0; + u32 SqrErrIQ = 0; + u32 a = 0; + u32 b = 0; + u32 c = 0; + u32 iMER = 0; + u16 transmissionParams = 0; + + dprintk(1, "\n"); + + status = read16(state, OFDM_EQ_TOP_TD_TPS_PWR_OFS__A, &EqRegTdTpsPwrOfs); + if (status < 0) + goto error; + status = read16(state, OFDM_EQ_TOP_TD_REQ_SMB_CNT__A, &EqRegTdReqSmbCnt); + if (status < 0) + goto error; + status = read16(state, OFDM_EQ_TOP_TD_SQR_ERR_EXP__A, &EqRegTdSqrErrExp); + if (status < 0) + goto error; + status = read16(state, OFDM_EQ_TOP_TD_SQR_ERR_I__A, ®Data); + if (status < 0) + goto error; + /* Extend SQR_ERR_I operational range */ + EqRegTdSqrErrI = (u32) regData; + if ((EqRegTdSqrErrExp > 11) && + (EqRegTdSqrErrI < 0x00000FFFUL)) { + EqRegTdSqrErrI += 0x00010000UL; + } + status = read16(state, OFDM_EQ_TOP_TD_SQR_ERR_Q__A, ®Data); + if (status < 0) + goto error; + /* Extend SQR_ERR_Q operational range */ + EqRegTdSqrErrQ = (u32) regData; + if ((EqRegTdSqrErrExp > 11) && + (EqRegTdSqrErrQ < 0x00000FFFUL)) + EqRegTdSqrErrQ += 0x00010000UL; + + status = read16(state, OFDM_SC_RA_RAM_OP_PARAM__A, &transmissionParams); + if (status < 0) + goto error; + + /* Check input data for MER */ + + /* MER calculation (in 0.1 dB) without math.h */ + if ((EqRegTdTpsPwrOfs == 0) || (EqRegTdReqSmbCnt == 0)) + iMER = 0; + else if ((EqRegTdSqrErrI + EqRegTdSqrErrQ) == 0) { + /* No error at all, this must be the HW reset value + * Apparently no first measurement yet + * Set MER to 0.0 */ + iMER = 0; + } else { + SqrErrIQ = (EqRegTdSqrErrI + EqRegTdSqrErrQ) << + EqRegTdSqrErrExp; + if ((transmissionParams & + OFDM_SC_RA_RAM_OP_PARAM_MODE__M) + == OFDM_SC_RA_RAM_OP_PARAM_MODE_2K) + tpsCnt = 17; + else + tpsCnt = 68; + + /* IMER = 100 * log10 (x) + where x = (EqRegTdTpsPwrOfs^2 * + EqRegTdReqSmbCnt * tpsCnt)/SqrErrIQ + + => IMER = a + b -c + where a = 100 * log10 (EqRegTdTpsPwrOfs^2) + b = 100 * log10 (EqRegTdReqSmbCnt * tpsCnt) + c = 100 * log10 (SqrErrIQ) + */ + + /* log(x) x = 9bits * 9bits->18 bits */ + a = Log10Times100(EqRegTdTpsPwrOfs * + EqRegTdTpsPwrOfs); + /* log(x) x = 16bits * 7bits->23 bits */ + b = Log10Times100(EqRegTdReqSmbCnt * tpsCnt); + /* log(x) x = (16bits + 16bits) << 15 ->32 bits */ + c = Log10Times100(SqrErrIQ); + + iMER = a + b; + /* No negative MER, clip to zero */ + if (iMER > c) + iMER -= c; + else + iMER = 0; + } + *pSignalToNoise = iMER; + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int GetSignalToNoise(struct drxk_state *state, s32 *pSignalToNoise) +{ + dprintk(1, "\n"); + + *pSignalToNoise = 0; + switch (state->m_OperationMode) { + case OM_DVBT: + return GetDVBTSignalToNoise(state, pSignalToNoise); + case OM_QAM_ITU_A: + case OM_QAM_ITU_C: + return GetQAMSignalToNoise(state, pSignalToNoise); + default: + break; + } + return 0; +} + +#if 0 +static int GetDVBTQuality(struct drxk_state *state, s32 *pQuality) +{ + /* SNR Values for quasi errorfree reception rom Nordig 2.2 */ + int status = 0; + + dprintk(1, "\n"); + + static s32 QE_SN[] = { + 51, /* QPSK 1/2 */ + 69, /* QPSK 2/3 */ + 79, /* QPSK 3/4 */ + 89, /* QPSK 5/6 */ + 97, /* QPSK 7/8 */ + 108, /* 16-QAM 1/2 */ + 131, /* 16-QAM 2/3 */ + 146, /* 16-QAM 3/4 */ + 156, /* 16-QAM 5/6 */ + 160, /* 16-QAM 7/8 */ + 165, /* 64-QAM 1/2 */ + 187, /* 64-QAM 2/3 */ + 202, /* 64-QAM 3/4 */ + 216, /* 64-QAM 5/6 */ + 225, /* 64-QAM 7/8 */ + }; + + *pQuality = 0; + + do { + s32 SignalToNoise = 0; + u16 Constellation = 0; + u16 CodeRate = 0; + u32 SignalToNoiseRel; + u32 BERQuality; + + status = GetDVBTSignalToNoise(state, &SignalToNoise); + if (status < 0) + break; + status = read16(state, OFDM_EQ_TOP_TD_TPS_CONST__A, &Constellation); + if (status < 0) + break; + Constellation &= OFDM_EQ_TOP_TD_TPS_CONST__M; + + status = read16(state, OFDM_EQ_TOP_TD_TPS_CODE_HP__A, &CodeRate); + if (status < 0) + break; + CodeRate &= OFDM_EQ_TOP_TD_TPS_CODE_HP__M; + + if (Constellation > OFDM_EQ_TOP_TD_TPS_CONST_64QAM || + CodeRate > OFDM_EQ_TOP_TD_TPS_CODE_LP_7_8) + break; + SignalToNoiseRel = SignalToNoise - + QE_SN[Constellation * 5 + CodeRate]; + BERQuality = 100; + + if (SignalToNoiseRel < -70) + *pQuality = 0; + else if (SignalToNoiseRel < 30) + *pQuality = ((SignalToNoiseRel + 70) * + BERQuality) / 100; + else + *pQuality = BERQuality; + } while (0); + return 0; +}; + +static int GetDVBCQuality(struct drxk_state *state, s32 *pQuality) +{ + int status = 0; + *pQuality = 0; + + dprintk(1, "\n"); + + do { + u32 SignalToNoise = 0; + u32 BERQuality = 100; + u32 SignalToNoiseRel = 0; + + status = GetQAMSignalToNoise(state, &SignalToNoise); + if (status < 0) + break; + + switch (state->props.modulation) { + case QAM_16: + SignalToNoiseRel = SignalToNoise - 200; + break; + case QAM_32: + SignalToNoiseRel = SignalToNoise - 230; + break; /* Not in NorDig */ + case QAM_64: + SignalToNoiseRel = SignalToNoise - 260; + break; + case QAM_128: + SignalToNoiseRel = SignalToNoise - 290; + break; + default: + case QAM_256: + SignalToNoiseRel = SignalToNoise - 320; + break; + } + + if (SignalToNoiseRel < -70) + *pQuality = 0; + else if (SignalToNoiseRel < 30) + *pQuality = ((SignalToNoiseRel + 70) * + BERQuality) / 100; + else + *pQuality = BERQuality; + } while (0); + + return status; +} + +static int GetQuality(struct drxk_state *state, s32 *pQuality) +{ + dprintk(1, "\n"); + + switch (state->m_OperationMode) { + case OM_DVBT: + return GetDVBTQuality(state, pQuality); + case OM_QAM_ITU_A: + return GetDVBCQuality(state, pQuality); + default: + break; + } + + return 0; +} +#endif + +/* Free data ram in SIO HI */ +#define SIO_HI_RA_RAM_USR_BEGIN__A 0x420040 +#define SIO_HI_RA_RAM_USR_END__A 0x420060 + +#define DRXK_HI_ATOMIC_BUF_START (SIO_HI_RA_RAM_USR_BEGIN__A) +#define DRXK_HI_ATOMIC_BUF_END (SIO_HI_RA_RAM_USR_BEGIN__A + 7) +#define DRXK_HI_ATOMIC_READ SIO_HI_RA_RAM_PAR_3_ACP_RW_READ +#define DRXK_HI_ATOMIC_WRITE SIO_HI_RA_RAM_PAR_3_ACP_RW_WRITE + +#define DRXDAP_FASI_ADDR2BLOCK(addr) (((addr) >> 22) & 0x3F) +#define DRXDAP_FASI_ADDR2BANK(addr) (((addr) >> 16) & 0x3F) +#define DRXDAP_FASI_ADDR2OFFSET(addr) ((addr) & 0x7FFF) + +static int ConfigureI2CBridge(struct drxk_state *state, bool bEnableBridge) +{ + int status = -EINVAL; + + dprintk(1, "\n"); + + if (state->m_DrxkState == DRXK_UNINITIALIZED) + return 0; + if (state->m_DrxkState == DRXK_POWERED_DOWN) + goto error; + + if (state->no_i2c_bridge) + return 0; + + status = write16(state, SIO_HI_RA_RAM_PAR_1__A, SIO_HI_RA_RAM_PAR_1_PAR1_SEC_KEY); + if (status < 0) + goto error; + if (bEnableBridge) { + status = write16(state, SIO_HI_RA_RAM_PAR_2__A, SIO_HI_RA_RAM_PAR_2_BRD_CFG_CLOSED); + if (status < 0) + goto error; + } else { + status = write16(state, SIO_HI_RA_RAM_PAR_2__A, SIO_HI_RA_RAM_PAR_2_BRD_CFG_OPEN); + if (status < 0) + goto error; + } + + status = HI_Command(state, SIO_HI_RA_RAM_CMD_BRDCTRL, 0); + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int SetPreSaw(struct drxk_state *state, + struct SCfgPreSaw *pPreSawCfg) +{ + int status = -EINVAL; + + dprintk(1, "\n"); + + if ((pPreSawCfg == NULL) + || (pPreSawCfg->reference > IQM_AF_PDREF__M)) + goto error; + + status = write16(state, IQM_AF_PDREF__A, pPreSawCfg->reference); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int BLDirectCmd(struct drxk_state *state, u32 targetAddr, + u16 romOffset, u16 nrOfElements, u32 timeOut) +{ + u16 blStatus = 0; + u16 offset = (u16) ((targetAddr >> 0) & 0x00FFFF); + u16 blockbank = (u16) ((targetAddr >> 16) & 0x000FFF); + int status; + unsigned long end; + + dprintk(1, "\n"); + + mutex_lock(&state->mutex); + status = write16(state, SIO_BL_MODE__A, SIO_BL_MODE_DIRECT); + if (status < 0) + goto error; + status = write16(state, SIO_BL_TGT_HDR__A, blockbank); + if (status < 0) + goto error; + status = write16(state, SIO_BL_TGT_ADDR__A, offset); + if (status < 0) + goto error; + status = write16(state, SIO_BL_SRC_ADDR__A, romOffset); + if (status < 0) + goto error; + status = write16(state, SIO_BL_SRC_LEN__A, nrOfElements); + if (status < 0) + goto error; + status = write16(state, SIO_BL_ENABLE__A, SIO_BL_ENABLE_ON); + if (status < 0) + goto error; + + end = jiffies + msecs_to_jiffies(timeOut); + do { + status = read16(state, SIO_BL_STATUS__A, &blStatus); + if (status < 0) + goto error; + } while ((blStatus == 0x1) && time_is_after_jiffies(end)); + if (blStatus == 0x1) { + printk(KERN_ERR "drxk: SIO not ready\n"); + status = -EINVAL; + goto error2; + } +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); +error2: + mutex_unlock(&state->mutex); + return status; + +} + +static int ADCSyncMeasurement(struct drxk_state *state, u16 *count) +{ + u16 data = 0; + int status; + + dprintk(1, "\n"); + + /* Start measurement */ + status = write16(state, IQM_AF_COMM_EXEC__A, IQM_AF_COMM_EXEC_ACTIVE); + if (status < 0) + goto error; + status = write16(state, IQM_AF_START_LOCK__A, 1); + if (status < 0) + goto error; + + *count = 0; + status = read16(state, IQM_AF_PHASE0__A, &data); + if (status < 0) + goto error; + if (data == 127) + *count = *count + 1; + status = read16(state, IQM_AF_PHASE1__A, &data); + if (status < 0) + goto error; + if (data == 127) + *count = *count + 1; + status = read16(state, IQM_AF_PHASE2__A, &data); + if (status < 0) + goto error; + if (data == 127) + *count = *count + 1; + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int ADCSynchronization(struct drxk_state *state) +{ + u16 count = 0; + int status; + + dprintk(1, "\n"); + + status = ADCSyncMeasurement(state, &count); + if (status < 0) + goto error; + + if (count == 1) { + /* Try sampling on a diffrent edge */ + u16 clkNeg = 0; + + status = read16(state, IQM_AF_CLKNEG__A, &clkNeg); + if (status < 0) + goto error; + if ((clkNeg & IQM_AF_CLKNEG_CLKNEGDATA__M) == + IQM_AF_CLKNEG_CLKNEGDATA_CLK_ADC_DATA_POS) { + clkNeg &= (~(IQM_AF_CLKNEG_CLKNEGDATA__M)); + clkNeg |= + IQM_AF_CLKNEG_CLKNEGDATA_CLK_ADC_DATA_NEG; + } else { + clkNeg &= (~(IQM_AF_CLKNEG_CLKNEGDATA__M)); + clkNeg |= + IQM_AF_CLKNEG_CLKNEGDATA_CLK_ADC_DATA_POS; + } + status = write16(state, IQM_AF_CLKNEG__A, clkNeg); + if (status < 0) + goto error; + status = ADCSyncMeasurement(state, &count); + if (status < 0) + goto error; + } + + if (count < 2) + status = -EINVAL; +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int SetFrequencyShifter(struct drxk_state *state, + u16 intermediateFreqkHz, + s32 tunerFreqOffset, bool isDTV) +{ + bool selectPosImage = false; + u32 rfFreqResidual = tunerFreqOffset; + u32 fmFrequencyShift = 0; + bool tunerMirror = !state->m_bMirrorFreqSpect; + u32 adcFreq; + bool adcFlip; + int status; + u32 ifFreqActual; + u32 samplingFrequency = (u32) (state->m_sysClockFreq / 3); + u32 frequencyShift; + bool imageToSelect; + + dprintk(1, "\n"); + + /* + Program frequency shifter + No need to account for mirroring on RF + */ + if (isDTV) { + if ((state->m_OperationMode == OM_QAM_ITU_A) || + (state->m_OperationMode == OM_QAM_ITU_C) || + (state->m_OperationMode == OM_DVBT)) + selectPosImage = true; + else + selectPosImage = false; + } + if (tunerMirror) + /* tuner doesn't mirror */ + ifFreqActual = intermediateFreqkHz + + rfFreqResidual + fmFrequencyShift; + else + /* tuner mirrors */ + ifFreqActual = intermediateFreqkHz - + rfFreqResidual - fmFrequencyShift; + if (ifFreqActual > samplingFrequency / 2) { + /* adc mirrors */ + adcFreq = samplingFrequency - ifFreqActual; + adcFlip = true; + } else { + /* adc doesn't mirror */ + adcFreq = ifFreqActual; + adcFlip = false; + } + + frequencyShift = adcFreq; + imageToSelect = state->m_rfmirror ^ tunerMirror ^ + adcFlip ^ selectPosImage; + state->m_IqmFsRateOfs = + Frac28a((frequencyShift), samplingFrequency); + + if (imageToSelect) + state->m_IqmFsRateOfs = ~state->m_IqmFsRateOfs + 1; + + /* Program frequency shifter with tuner offset compensation */ + /* frequencyShift += tunerFreqOffset; TODO */ + status = write32(state, IQM_FS_RATE_OFS_LO__A, + state->m_IqmFsRateOfs); + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int InitAGC(struct drxk_state *state, bool isDTV) +{ + u16 ingainTgt = 0; + u16 ingainTgtMin = 0; + u16 ingainTgtMax = 0; + u16 clpCyclen = 0; + u16 clpSumMin = 0; + u16 clpDirTo = 0; + u16 snsSumMin = 0; + u16 snsSumMax = 0; + u16 clpSumMax = 0; + u16 snsDirTo = 0; + u16 kiInnergainMin = 0; + u16 ifIaccuHiTgt = 0; + u16 ifIaccuHiTgtMin = 0; + u16 ifIaccuHiTgtMax = 0; + u16 data = 0; + u16 fastClpCtrlDelay = 0; + u16 clpCtrlMode = 0; + int status = 0; + + dprintk(1, "\n"); + + /* Common settings */ + snsSumMax = 1023; + ifIaccuHiTgtMin = 2047; + clpCyclen = 500; + clpSumMax = 1023; + + /* AGCInit() not available for DVBT; init done in microcode */ + if (!IsQAM(state)) { + printk(KERN_ERR "drxk: %s: mode %d is not DVB-C\n", __func__, state->m_OperationMode); + return -EINVAL; + } + + /* FIXME: Analog TV AGC require different settings */ + + /* Standard specific settings */ + clpSumMin = 8; + clpDirTo = (u16) -9; + clpCtrlMode = 0; + snsSumMin = 8; + snsDirTo = (u16) -9; + kiInnergainMin = (u16) -1030; + ifIaccuHiTgtMax = 0x2380; + ifIaccuHiTgt = 0x2380; + ingainTgtMin = 0x0511; + ingainTgt = 0x0511; + ingainTgtMax = 5119; + fastClpCtrlDelay = state->m_qamIfAgcCfg.FastClipCtrlDelay; + + status = write16(state, SCU_RAM_AGC_FAST_CLP_CTRL_DELAY__A, fastClpCtrlDelay); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_AGC_CLP_CTRL_MODE__A, clpCtrlMode); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_INGAIN_TGT__A, ingainTgt); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_INGAIN_TGT_MIN__A, ingainTgtMin); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_INGAIN_TGT_MAX__A, ingainTgtMax); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MIN__A, ifIaccuHiTgtMin); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MAX__A, ifIaccuHiTgtMax); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_IF_IACCU_HI__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_IF_IACCU_LO__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_RF_IACCU_HI__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_RF_IACCU_LO__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_CLP_SUM_MAX__A, clpSumMax); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_SNS_SUM_MAX__A, snsSumMax); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_AGC_KI_INNERGAIN_MIN__A, kiInnergainMin); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT__A, ifIaccuHiTgt); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_CLP_CYCLEN__A, clpCyclen); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_AGC_RF_SNS_DEV_MAX__A, 1023); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_RF_SNS_DEV_MIN__A, (u16) -1023); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_FAST_SNS_CTRL_DELAY__A, 50); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_AGC_KI_MAXMINGAIN_TH__A, 20); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_CLP_SUM_MIN__A, clpSumMin); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_SNS_SUM_MIN__A, snsSumMin); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_CLP_DIR_TO__A, clpDirTo); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_SNS_DIR_TO__A, snsDirTo); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_KI_MINGAIN__A, 0x7fff); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_KI_MAXGAIN__A, 0x0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_KI_MIN__A, 0x0117); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_KI_MAX__A, 0x0657); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_CLP_SUM__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_CLP_CYCCNT__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_CLP_DIR_WD__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_CLP_DIR_STP__A, 1); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_SNS_SUM__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_SNS_CYCCNT__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_SNS_DIR_WD__A, 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_SNS_DIR_STP__A, 1); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_SNS_CYCLEN__A, 500); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_KI_CYCLEN__A, 500); + if (status < 0) + goto error; + + /* Initialize inner-loop KI gain factors */ + status = read16(state, SCU_RAM_AGC_KI__A, &data); + if (status < 0) + goto error; + + data = 0x0657; + data &= ~SCU_RAM_AGC_KI_RF__M; + data |= (DRXK_KI_RAGC_QAM << SCU_RAM_AGC_KI_RF__B); + data &= ~SCU_RAM_AGC_KI_IF__M; + data |= (DRXK_KI_IAGC_QAM << SCU_RAM_AGC_KI_IF__B); + + status = write16(state, SCU_RAM_AGC_KI__A, data); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int DVBTQAMGetAccPktErr(struct drxk_state *state, u16 *packetErr) +{ + int status; + + dprintk(1, "\n"); + if (packetErr == NULL) + status = write16(state, SCU_RAM_FEC_ACCUM_PKT_FAILURES__A, 0); + else + status = read16(state, SCU_RAM_FEC_ACCUM_PKT_FAILURES__A, packetErr); + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int DVBTScCommand(struct drxk_state *state, + u16 cmd, u16 subcmd, + u16 param0, u16 param1, u16 param2, + u16 param3, u16 param4) +{ + u16 curCmd = 0; + u16 errCode = 0; + u16 retryCnt = 0; + u16 scExec = 0; + int status; + + dprintk(1, "\n"); + status = read16(state, OFDM_SC_COMM_EXEC__A, &scExec); + if (scExec != 1) { + /* SC is not running */ + status = -EINVAL; + } + if (status < 0) + goto error; + + /* Wait until sc is ready to receive command */ + retryCnt = 0; + do { + msleep(1); + status = read16(state, OFDM_SC_RA_RAM_CMD__A, &curCmd); + retryCnt++; + } while ((curCmd != 0) && (retryCnt < DRXK_MAX_RETRIES)); + if (retryCnt >= DRXK_MAX_RETRIES && (status < 0)) + goto error; + + /* Write sub-command */ + switch (cmd) { + /* All commands using sub-cmd */ + case OFDM_SC_RA_RAM_CMD_PROC_START: + case OFDM_SC_RA_RAM_CMD_SET_PREF_PARAM: + case OFDM_SC_RA_RAM_CMD_PROGRAM_PARAM: + status = write16(state, OFDM_SC_RA_RAM_CMD_ADDR__A, subcmd); + if (status < 0) + goto error; + break; + default: + /* Do nothing */ + break; + } + + /* Write needed parameters and the command */ + switch (cmd) { + /* All commands using 5 parameters */ + /* All commands using 4 parameters */ + /* All commands using 3 parameters */ + /* All commands using 2 parameters */ + case OFDM_SC_RA_RAM_CMD_PROC_START: + case OFDM_SC_RA_RAM_CMD_SET_PREF_PARAM: + case OFDM_SC_RA_RAM_CMD_PROGRAM_PARAM: + status = write16(state, OFDM_SC_RA_RAM_PARAM1__A, param1); + /* All commands using 1 parameters */ + case OFDM_SC_RA_RAM_CMD_SET_ECHO_TIMING: + case OFDM_SC_RA_RAM_CMD_USER_IO: + status = write16(state, OFDM_SC_RA_RAM_PARAM0__A, param0); + /* All commands using 0 parameters */ + case OFDM_SC_RA_RAM_CMD_GET_OP_PARAM: + case OFDM_SC_RA_RAM_CMD_NULL: + /* Write command */ + status = write16(state, OFDM_SC_RA_RAM_CMD__A, cmd); + break; + default: + /* Unknown command */ + status = -EINVAL; + } + if (status < 0) + goto error; + + /* Wait until sc is ready processing command */ + retryCnt = 0; + do { + msleep(1); + status = read16(state, OFDM_SC_RA_RAM_CMD__A, &curCmd); + retryCnt++; + } while ((curCmd != 0) && (retryCnt < DRXK_MAX_RETRIES)); + if (retryCnt >= DRXK_MAX_RETRIES && (status < 0)) + goto error; + + /* Check for illegal cmd */ + status = read16(state, OFDM_SC_RA_RAM_CMD_ADDR__A, &errCode); + if (errCode == 0xFFFF) { + /* illegal command */ + status = -EINVAL; + } + if (status < 0) + goto error; + + /* Retreive results parameters from SC */ + switch (cmd) { + /* All commands yielding 5 results */ + /* All commands yielding 4 results */ + /* All commands yielding 3 results */ + /* All commands yielding 2 results */ + /* All commands yielding 1 result */ + case OFDM_SC_RA_RAM_CMD_USER_IO: + case OFDM_SC_RA_RAM_CMD_GET_OP_PARAM: + status = read16(state, OFDM_SC_RA_RAM_PARAM0__A, &(param0)); + /* All commands yielding 0 results */ + case OFDM_SC_RA_RAM_CMD_SET_ECHO_TIMING: + case OFDM_SC_RA_RAM_CMD_SET_TIMER: + case OFDM_SC_RA_RAM_CMD_PROC_START: + case OFDM_SC_RA_RAM_CMD_SET_PREF_PARAM: + case OFDM_SC_RA_RAM_CMD_PROGRAM_PARAM: + case OFDM_SC_RA_RAM_CMD_NULL: + break; + default: + /* Unknown command */ + status = -EINVAL; + break; + } /* switch (cmd->cmd) */ +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int PowerUpDVBT(struct drxk_state *state) +{ + enum DRXPowerMode powerMode = DRX_POWER_UP; + int status; + + dprintk(1, "\n"); + status = CtrlPowerMode(state, &powerMode); + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int DVBTCtrlSetIncEnable(struct drxk_state *state, bool *enabled) +{ + int status; + + dprintk(1, "\n"); + if (*enabled == true) + status = write16(state, IQM_CF_BYPASSDET__A, 0); + else + status = write16(state, IQM_CF_BYPASSDET__A, 1); + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +#define DEFAULT_FR_THRES_8K 4000 +static int DVBTCtrlSetFrEnable(struct drxk_state *state, bool *enabled) +{ + + int status; + + dprintk(1, "\n"); + if (*enabled == true) { + /* write mask to 1 */ + status = write16(state, OFDM_SC_RA_RAM_FR_THRES_8K__A, + DEFAULT_FR_THRES_8K); + } else { + /* write mask to 0 */ + status = write16(state, OFDM_SC_RA_RAM_FR_THRES_8K__A, 0); + } + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +static int DVBTCtrlSetEchoThreshold(struct drxk_state *state, + struct DRXKCfgDvbtEchoThres_t *echoThres) +{ + u16 data = 0; + int status; + + dprintk(1, "\n"); + status = read16(state, OFDM_SC_RA_RAM_ECHO_THRES__A, &data); + if (status < 0) + goto error; + + switch (echoThres->fftMode) { + case DRX_FFTMODE_2K: + data &= ~OFDM_SC_RA_RAM_ECHO_THRES_2K__M; + data |= ((echoThres->threshold << + OFDM_SC_RA_RAM_ECHO_THRES_2K__B) + & (OFDM_SC_RA_RAM_ECHO_THRES_2K__M)); + break; + case DRX_FFTMODE_8K: + data &= ~OFDM_SC_RA_RAM_ECHO_THRES_8K__M; + data |= ((echoThres->threshold << + OFDM_SC_RA_RAM_ECHO_THRES_8K__B) + & (OFDM_SC_RA_RAM_ECHO_THRES_8K__M)); + break; + default: + return -EINVAL; + } + + status = write16(state, OFDM_SC_RA_RAM_ECHO_THRES__A, data); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int DVBTCtrlSetSqiSpeed(struct drxk_state *state, + enum DRXKCfgDvbtSqiSpeed *speed) +{ + int status = -EINVAL; + + dprintk(1, "\n"); + + switch (*speed) { + case DRXK_DVBT_SQI_SPEED_FAST: + case DRXK_DVBT_SQI_SPEED_MEDIUM: + case DRXK_DVBT_SQI_SPEED_SLOW: + break; + default: + goto error; + } + status = write16(state, SCU_RAM_FEC_PRE_RS_BER_FILTER_SH__A, + (u16) *speed); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +/*============================================================================*/ + +/** +* \brief Activate DVBT specific presets +* \param demod instance of demodulator. +* \return DRXStatus_t. +* +* Called in DVBTSetStandard +* +*/ +static int DVBTActivatePresets(struct drxk_state *state) +{ + int status; + bool setincenable = false; + bool setfrenable = true; + + struct DRXKCfgDvbtEchoThres_t echoThres2k = { 0, DRX_FFTMODE_2K }; + struct DRXKCfgDvbtEchoThres_t echoThres8k = { 0, DRX_FFTMODE_8K }; + + dprintk(1, "\n"); + status = DVBTCtrlSetIncEnable(state, &setincenable); + if (status < 0) + goto error; + status = DVBTCtrlSetFrEnable(state, &setfrenable); + if (status < 0) + goto error; + status = DVBTCtrlSetEchoThreshold(state, &echoThres2k); + if (status < 0) + goto error; + status = DVBTCtrlSetEchoThreshold(state, &echoThres8k); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_AGC_INGAIN_TGT_MAX__A, state->m_dvbtIfAgcCfg.IngainTgtMax); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +/*============================================================================*/ + +/** +* \brief Initialize channelswitch-independent settings for DVBT. +* \param demod instance of demodulator. +* \return DRXStatus_t. +* +* For ROM code channel filter taps are loaded from the bootloader. For microcode +* the DVB-T taps from the drxk_filters.h are used. +*/ +static int SetDVBTStandard(struct drxk_state *state, + enum OperationMode oMode) +{ + u16 cmdResult = 0; + u16 data = 0; + int status; + + dprintk(1, "\n"); + + PowerUpDVBT(state); + /* added antenna switch */ + SwitchAntennaToDVBT(state); + /* send OFDM reset command */ + status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_RESET, 0, NULL, 1, &cmdResult); + if (status < 0) + goto error; + + /* send OFDM setenv command */ + status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_SET_ENV, 0, NULL, 1, &cmdResult); + if (status < 0) + goto error; + + /* reset datapath for OFDM, processors first */ + status = write16(state, OFDM_SC_COMM_EXEC__A, OFDM_SC_COMM_EXEC_STOP); + if (status < 0) + goto error; + status = write16(state, OFDM_LC_COMM_EXEC__A, OFDM_LC_COMM_EXEC_STOP); + if (status < 0) + goto error; + status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_STOP); + if (status < 0) + goto error; + + /* IQM setup */ + /* synchronize on ofdstate->m_festart */ + status = write16(state, IQM_AF_UPD_SEL__A, 1); + if (status < 0) + goto error; + /* window size for clipping ADC detection */ + status = write16(state, IQM_AF_CLP_LEN__A, 0); + if (status < 0) + goto error; + /* window size for for sense pre-SAW detection */ + status = write16(state, IQM_AF_SNS_LEN__A, 0); + if (status < 0) + goto error; + /* sense threshold for sense pre-SAW detection */ + status = write16(state, IQM_AF_AMUX__A, IQM_AF_AMUX_SIGNAL2ADC); + if (status < 0) + goto error; + status = SetIqmAf(state, true); + if (status < 0) + goto error; + + status = write16(state, IQM_AF_AGC_RF__A, 0); + if (status < 0) + goto error; + + /* Impulse noise cruncher setup */ + status = write16(state, IQM_AF_INC_LCT__A, 0); /* crunch in IQM_CF */ + if (status < 0) + goto error; + status = write16(state, IQM_CF_DET_LCT__A, 0); /* detect in IQM_CF */ + if (status < 0) + goto error; + status = write16(state, IQM_CF_WND_LEN__A, 3); /* peak detector window length */ + if (status < 0) + goto error; + + status = write16(state, IQM_RC_STRETCH__A, 16); + if (status < 0) + goto error; + status = write16(state, IQM_CF_OUT_ENA__A, 0x4); /* enable output 2 */ + if (status < 0) + goto error; + status = write16(state, IQM_CF_DS_ENA__A, 0x4); /* decimate output 2 */ + if (status < 0) + goto error; + status = write16(state, IQM_CF_SCALE__A, 1600); + if (status < 0) + goto error; + status = write16(state, IQM_CF_SCALE_SH__A, 0); + if (status < 0) + goto error; + + /* virtual clipping threshold for clipping ADC detection */ + status = write16(state, IQM_AF_CLP_TH__A, 448); + if (status < 0) + goto error; + status = write16(state, IQM_CF_DATATH__A, 495); /* crunching threshold */ + if (status < 0) + goto error; + + status = BLChainCmd(state, DRXK_BL_ROM_OFFSET_TAPS_DVBT, DRXK_BLCC_NR_ELEMENTS_TAPS, DRXK_BLC_TIMEOUT); + if (status < 0) + goto error; + + status = write16(state, IQM_CF_PKDTH__A, 2); /* peak detector threshold */ + if (status < 0) + goto error; + status = write16(state, IQM_CF_POW_MEAS_LEN__A, 2); + if (status < 0) + goto error; + /* enable power measurement interrupt */ + status = write16(state, IQM_CF_COMM_INT_MSK__A, 1); + if (status < 0) + goto error; + status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_ACTIVE); + if (status < 0) + goto error; + + /* IQM will not be reset from here, sync ADC and update/init AGC */ + status = ADCSynchronization(state); + if (status < 0) + goto error; + status = SetPreSaw(state, &state->m_dvbtPreSawCfg); + if (status < 0) + goto error; + + /* Halt SCU to enable safe non-atomic accesses */ + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_HOLD); + if (status < 0) + goto error; + + status = SetAgcRf(state, &state->m_dvbtRfAgcCfg, true); + if (status < 0) + goto error; + status = SetAgcIf(state, &state->m_dvbtIfAgcCfg, true); + if (status < 0) + goto error; + + /* Set Noise Estimation notch width and enable DC fix */ + status = read16(state, OFDM_SC_RA_RAM_CONFIG__A, &data); + if (status < 0) + goto error; + data |= OFDM_SC_RA_RAM_CONFIG_NE_FIX_ENABLE__M; + status = write16(state, OFDM_SC_RA_RAM_CONFIG__A, data); + if (status < 0) + goto error; + + /* Activate SCU to enable SCU commands */ + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); + if (status < 0) + goto error; + + if (!state->m_DRXK_A3_ROM_CODE) { + /* AGCInit() is not done for DVBT, so set agcFastClipCtrlDelay */ + status = write16(state, SCU_RAM_AGC_FAST_CLP_CTRL_DELAY__A, state->m_dvbtIfAgcCfg.FastClipCtrlDelay); + if (status < 0) + goto error; + } + + /* OFDM_SC setup */ +#ifdef COMPILE_FOR_NONRT + status = write16(state, OFDM_SC_RA_RAM_BE_OPT_DELAY__A, 1); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_BE_OPT_INIT_DELAY__A, 2); + if (status < 0) + goto error; +#endif + + /* FEC setup */ + status = write16(state, FEC_DI_INPUT_CTL__A, 1); /* OFDM input */ + if (status < 0) + goto error; + + +#ifdef COMPILE_FOR_NONRT + status = write16(state, FEC_RS_MEASUREMENT_PERIOD__A, 0x400); + if (status < 0) + goto error; +#else + status = write16(state, FEC_RS_MEASUREMENT_PERIOD__A, 0x1000); + if (status < 0) + goto error; +#endif + status = write16(state, FEC_RS_MEASUREMENT_PRESCALE__A, 0x0001); + if (status < 0) + goto error; + + /* Setup MPEG bus */ + status = MPEGTSDtoSetup(state, OM_DVBT); + if (status < 0) + goto error; + /* Set DVBT Presets */ + status = DVBTActivatePresets(state); + if (status < 0) + goto error; + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +/*============================================================================*/ +/** +* \brief Start dvbt demodulating for channel. +* \param demod instance of demodulator. +* \return DRXStatus_t. +*/ +static int DVBTStart(struct drxk_state *state) +{ + u16 param1; + int status; + /* DRXKOfdmScCmd_t scCmd; */ + + dprintk(1, "\n"); + /* Start correct processes to get in lock */ + /* DRXK: OFDM_SC_RA_RAM_PROC_LOCKTRACK is no longer in mapfile! */ + param1 = OFDM_SC_RA_RAM_LOCKTRACK_MIN; + status = DVBTScCommand(state, OFDM_SC_RA_RAM_CMD_PROC_START, 0, OFDM_SC_RA_RAM_SW_EVENT_RUN_NMASK__M, param1, 0, 0, 0); + if (status < 0) + goto error; + /* Start FEC OC */ + status = MPEGTSStart(state); + if (status < 0) + goto error; + status = write16(state, FEC_COMM_EXEC__A, FEC_COMM_EXEC_ACTIVE); + if (status < 0) + goto error; +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + + +/*============================================================================*/ + +/** +* \brief Set up dvbt demodulator for channel. +* \param demod instance of demodulator. +* \return DRXStatus_t. +* // original DVBTSetChannel() +*/ +static int SetDVBT(struct drxk_state *state, u16 IntermediateFreqkHz, + s32 tunerFreqOffset) +{ + u16 cmdResult = 0; + u16 transmissionParams = 0; + u16 operationMode = 0; + u32 iqmRcRateOfs = 0; + u32 bandwidth = 0; + u16 param1; + int status; + + dprintk(1, "IF =%d, TFO = %d\n", IntermediateFreqkHz, tunerFreqOffset); + + status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_STOP, 0, NULL, 1, &cmdResult); + if (status < 0) + goto error; + + /* Halt SCU to enable safe non-atomic accesses */ + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_HOLD); + if (status < 0) + goto error; + + /* Stop processors */ + status = write16(state, OFDM_SC_COMM_EXEC__A, OFDM_SC_COMM_EXEC_STOP); + if (status < 0) + goto error; + status = write16(state, OFDM_LC_COMM_EXEC__A, OFDM_LC_COMM_EXEC_STOP); + if (status < 0) + goto error; + + /* Mandatory fix, always stop CP, required to set spl offset back to + hardware default (is set to 0 by ucode during pilot detection */ + status = write16(state, OFDM_CP_COMM_EXEC__A, OFDM_CP_COMM_EXEC_STOP); + if (status < 0) + goto error; + + /*== Write channel settings to device =====================================*/ + + /* mode */ + switch (state->props.transmission_mode) { + case TRANSMISSION_MODE_AUTO: + default: + operationMode |= OFDM_SC_RA_RAM_OP_AUTO_MODE__M; + /* fall through , try first guess DRX_FFTMODE_8K */ + case TRANSMISSION_MODE_8K: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_MODE_8K; + break; + case TRANSMISSION_MODE_2K: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_MODE_2K; + break; + } + + /* guard */ + switch (state->props.guard_interval) { + default: + case GUARD_INTERVAL_AUTO: + operationMode |= OFDM_SC_RA_RAM_OP_AUTO_GUARD__M; + /* fall through , try first guess DRX_GUARD_1DIV4 */ + case GUARD_INTERVAL_1_4: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_GUARD_4; + break; + case GUARD_INTERVAL_1_32: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_GUARD_32; + break; + case GUARD_INTERVAL_1_16: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_GUARD_16; + break; + case GUARD_INTERVAL_1_8: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_GUARD_8; + break; + } + + /* hierarchy */ + switch (state->props.hierarchy) { + case HIERARCHY_AUTO: + case HIERARCHY_NONE: + default: + operationMode |= OFDM_SC_RA_RAM_OP_AUTO_HIER__M; + /* fall through , try first guess SC_RA_RAM_OP_PARAM_HIER_NO */ + /* transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_HIER_NO; */ + /* break; */ + case HIERARCHY_1: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_HIER_A1; + break; + case HIERARCHY_2: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_HIER_A2; + break; + case HIERARCHY_4: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_HIER_A4; + break; + } + + + /* modulation */ + switch (state->props.modulation) { + case QAM_AUTO: + default: + operationMode |= OFDM_SC_RA_RAM_OP_AUTO_CONST__M; + /* fall through , try first guess DRX_CONSTELLATION_QAM64 */ + case QAM_64: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_CONST_QAM64; + break; + case QPSK: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_CONST_QPSK; + break; + case QAM_16: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_CONST_QAM16; + break; + } +#if 0 + /* No hierachical channels support in BDA */ + /* Priority (only for hierarchical channels) */ + switch (channel->priority) { + case DRX_PRIORITY_LOW: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_PRIO_LO; + WR16(devAddr, OFDM_EC_SB_PRIOR__A, + OFDM_EC_SB_PRIOR_LO); + break; + case DRX_PRIORITY_HIGH: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_PRIO_HI; + WR16(devAddr, OFDM_EC_SB_PRIOR__A, + OFDM_EC_SB_PRIOR_HI)); + break; + case DRX_PRIORITY_UNKNOWN: /* fall through */ + default: + status = -EINVAL; + goto error; + } +#else + /* Set Priorty high */ + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_PRIO_HI; + status = write16(state, OFDM_EC_SB_PRIOR__A, OFDM_EC_SB_PRIOR_HI); + if (status < 0) + goto error; +#endif + + /* coderate */ + switch (state->props.code_rate_HP) { + case FEC_AUTO: + default: + operationMode |= OFDM_SC_RA_RAM_OP_AUTO_RATE__M; + /* fall through , try first guess DRX_CODERATE_2DIV3 */ + case FEC_2_3: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_2_3; + break; + case FEC_1_2: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_1_2; + break; + case FEC_3_4: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_3_4; + break; + case FEC_5_6: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_5_6; + break; + case FEC_7_8: + transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_7_8; + break; + } + + /* SAW filter selection: normaly not necesarry, but if wanted + the application can select a SAW filter via the driver by using UIOs */ + /* First determine real bandwidth (Hz) */ + /* Also set delay for impulse noise cruncher */ + /* Also set parameters for EC_OC fix, note EC_OC_REG_TMD_HIL_MAR is changed + by SC for fix for some 8K,1/8 guard but is restored by InitEC and ResetEC + functions */ + switch (state->props.bandwidth_hz) { + case 0: + state->props.bandwidth_hz = 8000000; + /* fall though */ + case 8000000: + bandwidth = DRXK_BANDWIDTH_8MHZ_IN_HZ; + status = write16(state, OFDM_SC_RA_RAM_SRMM_FIX_FACT_8K__A, 3052); + if (status < 0) + goto error; + /* cochannel protection for PAL 8 MHz */ + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_LEFT__A, 7); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_RIGHT__A, 7); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_LEFT__A, 7); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_RIGHT__A, 1); + if (status < 0) + goto error; + break; + case 7000000: + bandwidth = DRXK_BANDWIDTH_7MHZ_IN_HZ; + status = write16(state, OFDM_SC_RA_RAM_SRMM_FIX_FACT_8K__A, 3491); + if (status < 0) + goto error; + /* cochannel protection for PAL 7 MHz */ + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_LEFT__A, 8); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_RIGHT__A, 8); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_LEFT__A, 4); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_RIGHT__A, 1); + if (status < 0) + goto error; + break; + case 6000000: + bandwidth = DRXK_BANDWIDTH_6MHZ_IN_HZ; + status = write16(state, OFDM_SC_RA_RAM_SRMM_FIX_FACT_8K__A, 4073); + if (status < 0) + goto error; + /* cochannel protection for NTSC 6 MHz */ + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_LEFT__A, 19); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_RIGHT__A, 19); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_LEFT__A, 14); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_RIGHT__A, 1); + if (status < 0) + goto error; + break; + default: + status = -EINVAL; + goto error; + } + + if (iqmRcRateOfs == 0) { + /* Now compute IQM_RC_RATE_OFS + (((SysFreq/BandWidth)/2)/2) -1) * 2^23) + => + ((SysFreq / BandWidth) * (2^21)) - (2^23) + */ + /* (SysFreq / BandWidth) * (2^28) */ + /* assert (MAX(sysClk)/MIN(bandwidth) < 16) + => assert(MAX(sysClk) < 16*MIN(bandwidth)) + => assert(109714272 > 48000000) = true so Frac 28 can be used */ + iqmRcRateOfs = Frac28a((u32) + ((state->m_sysClockFreq * + 1000) / 3), bandwidth); + /* (SysFreq / BandWidth) * (2^21), rounding before truncating */ + if ((iqmRcRateOfs & 0x7fL) >= 0x40) + iqmRcRateOfs += 0x80L; + iqmRcRateOfs = iqmRcRateOfs >> 7; + /* ((SysFreq / BandWidth) * (2^21)) - (2^23) */ + iqmRcRateOfs = iqmRcRateOfs - (1 << 23); + } + + iqmRcRateOfs &= + ((((u32) IQM_RC_RATE_OFS_HI__M) << + IQM_RC_RATE_OFS_LO__W) | IQM_RC_RATE_OFS_LO__M); + status = write32(state, IQM_RC_RATE_OFS_LO__A, iqmRcRateOfs); + if (status < 0) + goto error; + + /* Bandwidth setting done */ + +#if 0 + status = DVBTSetFrequencyShift(demod, channel, tunerOffset); + if (status < 0) + goto error; +#endif + status = SetFrequencyShifter(state, IntermediateFreqkHz, tunerFreqOffset, true); + if (status < 0) + goto error; + + /*== Start SC, write channel settings to SC ===============================*/ + + /* Activate SCU to enable SCU commands */ + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); + if (status < 0) + goto error; + + /* Enable SC after setting all other parameters */ + status = write16(state, OFDM_SC_COMM_STATE__A, 0); + if (status < 0) + goto error; + status = write16(state, OFDM_SC_COMM_EXEC__A, 1); + if (status < 0) + goto error; + + + status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_START, 0, NULL, 1, &cmdResult); + if (status < 0) + goto error; + + /* Write SC parameter registers, set all AUTO flags in operation mode */ + param1 = (OFDM_SC_RA_RAM_OP_AUTO_MODE__M | + OFDM_SC_RA_RAM_OP_AUTO_GUARD__M | + OFDM_SC_RA_RAM_OP_AUTO_CONST__M | + OFDM_SC_RA_RAM_OP_AUTO_HIER__M | + OFDM_SC_RA_RAM_OP_AUTO_RATE__M); + status = DVBTScCommand(state, OFDM_SC_RA_RAM_CMD_SET_PREF_PARAM, + 0, transmissionParams, param1, 0, 0, 0); + if (status < 0) + goto error; + + if (!state->m_DRXK_A3_ROM_CODE) + status = DVBTCtrlSetSqiSpeed(state, &state->m_sqiSpeed); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + + +/*============================================================================*/ + +/** +* \brief Retreive lock status . +* \param demod Pointer to demodulator instance. +* \param lockStat Pointer to lock status structure. +* \return DRXStatus_t. +* +*/ +static int GetDVBTLockStatus(struct drxk_state *state, u32 *pLockStatus) +{ + int status; + const u16 mpeg_lock_mask = (OFDM_SC_RA_RAM_LOCK_MPEG__M | + OFDM_SC_RA_RAM_LOCK_FEC__M); + const u16 fec_lock_mask = (OFDM_SC_RA_RAM_LOCK_FEC__M); + const u16 demod_lock_mask = OFDM_SC_RA_RAM_LOCK_DEMOD__M; + + u16 ScRaRamLock = 0; + u16 ScCommExec = 0; + + dprintk(1, "\n"); + + *pLockStatus = NOT_LOCKED; + /* driver 0.9.0 */ + /* Check if SC is running */ + status = read16(state, OFDM_SC_COMM_EXEC__A, &ScCommExec); + if (status < 0) + goto end; + if (ScCommExec == OFDM_SC_COMM_EXEC_STOP) + goto end; + + status = read16(state, OFDM_SC_RA_RAM_LOCK__A, &ScRaRamLock); + if (status < 0) + goto end; + + if ((ScRaRamLock & mpeg_lock_mask) == mpeg_lock_mask) + *pLockStatus = MPEG_LOCK; + else if ((ScRaRamLock & fec_lock_mask) == fec_lock_mask) + *pLockStatus = FEC_LOCK; + else if ((ScRaRamLock & demod_lock_mask) == demod_lock_mask) + *pLockStatus = DEMOD_LOCK; + else if (ScRaRamLock & OFDM_SC_RA_RAM_LOCK_NODVBT__M) + *pLockStatus = NEVER_LOCK; +end: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +static int PowerUpQAM(struct drxk_state *state) +{ + enum DRXPowerMode powerMode = DRXK_POWER_DOWN_OFDM; + int status; + + dprintk(1, "\n"); + status = CtrlPowerMode(state, &powerMode); + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + + +/** Power Down QAM */ +static int PowerDownQAM(struct drxk_state *state) +{ + u16 data = 0; + u16 cmdResult; + int status = 0; + + dprintk(1, "\n"); + status = read16(state, SCU_COMM_EXEC__A, &data); + if (status < 0) + goto error; + if (data == SCU_COMM_EXEC_ACTIVE) { + /* + STOP demodulator + QAM and HW blocks + */ + /* stop all comstate->m_exec */ + status = write16(state, QAM_COMM_EXEC__A, QAM_COMM_EXEC_STOP); + if (status < 0) + goto error; + status = scu_command(state, SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_STOP, 0, NULL, 1, &cmdResult); + if (status < 0) + goto error; + } + /* powerdown AFE */ + status = SetIqmAf(state, false); + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +/*============================================================================*/ + +/** +* \brief Setup of the QAM Measurement intervals for signal quality +* \param demod instance of demod. +* \param modulation current modulation. +* \return DRXStatus_t. +* +* NOTE: +* Take into account that for certain settings the errorcounters can overflow. +* The implementation does not check this. +* +*/ +static int SetQAMMeasurement(struct drxk_state *state, + enum EDrxkConstellation modulation, + u32 symbolRate) +{ + u32 fecBitsDesired = 0; /* BER accounting period */ + u32 fecRsPeriodTotal = 0; /* Total period */ + u16 fecRsPrescale = 0; /* ReedSolomon Measurement Prescale */ + u16 fecRsPeriod = 0; /* Value for corresponding I2C register */ + int status = 0; + + dprintk(1, "\n"); + + fecRsPrescale = 1; + /* fecBitsDesired = symbolRate [kHz] * + FrameLenght [ms] * + (modulation + 1) * + SyncLoss (== 1) * + ViterbiLoss (==1) + */ + switch (modulation) { + case DRX_CONSTELLATION_QAM16: + fecBitsDesired = 4 * symbolRate; + break; + case DRX_CONSTELLATION_QAM32: + fecBitsDesired = 5 * symbolRate; + break; + case DRX_CONSTELLATION_QAM64: + fecBitsDesired = 6 * symbolRate; + break; + case DRX_CONSTELLATION_QAM128: + fecBitsDesired = 7 * symbolRate; + break; + case DRX_CONSTELLATION_QAM256: + fecBitsDesired = 8 * symbolRate; + break; + default: + status = -EINVAL; + } + if (status < 0) + goto error; + + fecBitsDesired /= 1000; /* symbolRate [Hz] -> symbolRate [kHz] */ + fecBitsDesired *= 500; /* meas. period [ms] */ + + /* Annex A/C: bits/RsPeriod = 204 * 8 = 1632 */ + /* fecRsPeriodTotal = fecBitsDesired / 1632 */ + fecRsPeriodTotal = (fecBitsDesired / 1632UL) + 1; /* roughly ceil */ + + /* fecRsPeriodTotal = fecRsPrescale * fecRsPeriod */ + fecRsPrescale = 1 + (u16) (fecRsPeriodTotal >> 16); + if (fecRsPrescale == 0) { + /* Divide by zero (though impossible) */ + status = -EINVAL; + if (status < 0) + goto error; + } + fecRsPeriod = + ((u16) fecRsPeriodTotal + + (fecRsPrescale >> 1)) / fecRsPrescale; + + /* write corresponding registers */ + status = write16(state, FEC_RS_MEASUREMENT_PERIOD__A, fecRsPeriod); + if (status < 0) + goto error; + status = write16(state, FEC_RS_MEASUREMENT_PRESCALE__A, fecRsPrescale); + if (status < 0) + goto error; + status = write16(state, FEC_OC_SNC_FAIL_PERIOD__A, fecRsPeriod); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int SetQAM16(struct drxk_state *state) +{ + int status = 0; + + dprintk(1, "\n"); + /* QAM Equalizer Setup */ + /* Equalizer */ + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 13517); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 13517); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 13517); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 13517); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 13517); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 13517); + if (status < 0) + goto error; + /* Decision Feedback Equalizer */ + status = write16(state, QAM_DQ_QUAL_FUN0__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN1__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN2__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN3__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN4__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); + if (status < 0) + goto error; + + status = write16(state, QAM_SY_SYNC_HWM__A, 5); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_AWM__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_LWM__A, 3); + if (status < 0) + goto error; + + /* QAM Slicer Settings */ + status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM16); + if (status < 0) + goto error; + + /* QAM Loop Controller Coeficients */ + status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 20); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 80); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 20); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 50); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 32); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 10); + if (status < 0) + goto error; + + + /* QAM State Machine (FSM) Thresholds */ + + status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 140); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 50); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 95); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 120); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 230); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 105); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 4); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 24); + if (status < 0) + goto error; + + + /* QAM FSM Tracking Parameters */ + + status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 220); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) 25); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) 6); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) -24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) -65); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -127); + if (status < 0) + goto error; + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +/*============================================================================*/ + +/** +* \brief QAM32 specific setup +* \param demod instance of demod. +* \return DRXStatus_t. +*/ +static int SetQAM32(struct drxk_state *state) +{ + int status = 0; + + dprintk(1, "\n"); + + /* QAM Equalizer Setup */ + /* Equalizer */ + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 6707); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 6707); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 6707); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 6707); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 6707); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 6707); + if (status < 0) + goto error; + + /* Decision Feedback Equalizer */ + status = write16(state, QAM_DQ_QUAL_FUN0__A, 3); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN1__A, 3); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN2__A, 3); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN3__A, 3); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN4__A, 3); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); + if (status < 0) + goto error; + + status = write16(state, QAM_SY_SYNC_HWM__A, 6); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_AWM__A, 5); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_LWM__A, 3); + if (status < 0) + goto error; + + /* QAM Slicer Settings */ + + status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM32); + if (status < 0) + goto error; + + + /* QAM Loop Controller Coeficients */ + + status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 20); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 80); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 20); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 50); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 0); + if (status < 0) + goto error; + + + /* QAM State Machine (FSM) Thresholds */ + + status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 90); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 50); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 80); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 100); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 170); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 100); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 4); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 10); + if (status < 0) + goto error; + + + /* QAM FSM Tracking Parameters */ + + status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 140); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) -8); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) -16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) -26); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) -56); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -86); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +/*============================================================================*/ + +/** +* \brief QAM64 specific setup +* \param demod instance of demod. +* \return DRXStatus_t. +*/ +static int SetQAM64(struct drxk_state *state) +{ + int status = 0; + + dprintk(1, "\n"); + /* QAM Equalizer Setup */ + /* Equalizer */ + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 13336); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 12618); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 11988); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 13809); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 13809); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 15609); + if (status < 0) + goto error; + + /* Decision Feedback Equalizer */ + status = write16(state, QAM_DQ_QUAL_FUN0__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN1__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN2__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN3__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN4__A, 3); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); + if (status < 0) + goto error; + + status = write16(state, QAM_SY_SYNC_HWM__A, 5); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_AWM__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_LWM__A, 3); + if (status < 0) + goto error; + + /* QAM Slicer Settings */ + status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM64); + if (status < 0) + goto error; + + + /* QAM Loop Controller Coeficients */ + + status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 30); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 100); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 30); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 50); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 25); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 48); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 10); + if (status < 0) + goto error; + + + /* QAM State Machine (FSM) Thresholds */ + + status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 100); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 60); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 80); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 110); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 200); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 95); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 4); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 15); + if (status < 0) + goto error; + + + /* QAM FSM Tracking Parameters */ + + status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 141); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) 7); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) -15); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) -45); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -80); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +/*============================================================================*/ + +/** +* \brief QAM128 specific setup +* \param demod: instance of demod. +* \return DRXStatus_t. +*/ +static int SetQAM128(struct drxk_state *state) +{ + int status = 0; + + dprintk(1, "\n"); + /* QAM Equalizer Setup */ + /* Equalizer */ + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 6564); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 6598); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 6394); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 6409); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 6656); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 7238); + if (status < 0) + goto error; + + /* Decision Feedback Equalizer */ + status = write16(state, QAM_DQ_QUAL_FUN0__A, 6); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN1__A, 6); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN2__A, 6); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN3__A, 6); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN4__A, 5); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); + if (status < 0) + goto error; + + status = write16(state, QAM_SY_SYNC_HWM__A, 6); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_AWM__A, 5); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_LWM__A, 3); + if (status < 0) + goto error; + + + /* QAM Slicer Settings */ + + status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM128); + if (status < 0) + goto error; + + + /* QAM Loop Controller Coeficients */ + + status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 120); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 60); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 25); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 64); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 0); + if (status < 0) + goto error; + + + /* QAM State Machine (FSM) Thresholds */ + + status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 50); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 60); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 80); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 100); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 140); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 100); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 5); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 12); + if (status < 0) + goto error; + + /* QAM FSM Tracking Parameters */ + + status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 8); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 65); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) 3); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) -1); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) -12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -23); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +/*============================================================================*/ + +/** +* \brief QAM256 specific setup +* \param demod: instance of demod. +* \return DRXStatus_t. +*/ +static int SetQAM256(struct drxk_state *state) +{ + int status = 0; + + dprintk(1, "\n"); + /* QAM Equalizer Setup */ + /* Equalizer */ + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 11502); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 12084); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 12543); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 12931); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 13629); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 15385); + if (status < 0) + goto error; + + /* Decision Feedback Equalizer */ + status = write16(state, QAM_DQ_QUAL_FUN0__A, 8); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN1__A, 8); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN2__A, 8); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN3__A, 8); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN4__A, 6); + if (status < 0) + goto error; + status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); + if (status < 0) + goto error; + + status = write16(state, QAM_SY_SYNC_HWM__A, 5); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_AWM__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_SY_SYNC_LWM__A, 3); + if (status < 0) + goto error; + + /* QAM Slicer Settings */ + + status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM256); + if (status < 0) + goto error; + + + /* QAM Loop Controller Coeficients */ + + status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 50); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 250); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 50); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 125); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 25); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 48); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 10); + if (status < 0) + goto error; + + + /* QAM State Machine (FSM) Thresholds */ + + status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 50); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 60); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 80); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 100); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 150); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 110); + if (status < 0) + goto error; + + status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 4); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 12); + if (status < 0) + goto error; + + + /* QAM FSM Tracking Parameters */ + + status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 8); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 74); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) 18); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) 13); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) 7); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) 0); + if (status < 0) + goto error; + status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -8); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + + +/*============================================================================*/ +/** +* \brief Reset QAM block. +* \param demod: instance of demod. +* \param channel: pointer to channel data. +* \return DRXStatus_t. +*/ +static int QAMResetQAM(struct drxk_state *state) +{ + int status; + u16 cmdResult; + + dprintk(1, "\n"); + /* Stop QAM comstate->m_exec */ + status = write16(state, QAM_COMM_EXEC__A, QAM_COMM_EXEC_STOP); + if (status < 0) + goto error; + + status = scu_command(state, SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_RESET, 0, NULL, 1, &cmdResult); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +/*============================================================================*/ + +/** +* \brief Set QAM symbolrate. +* \param demod: instance of demod. +* \param channel: pointer to channel data. +* \return DRXStatus_t. +*/ +static int QAMSetSymbolrate(struct drxk_state *state) +{ + u32 adcFrequency = 0; + u32 symbFreq = 0; + u32 iqmRcRate = 0; + u16 ratesel = 0; + u32 lcSymbRate = 0; + int status; + + dprintk(1, "\n"); + /* Select & calculate correct IQM rate */ + adcFrequency = (state->m_sysClockFreq * 1000) / 3; + ratesel = 0; + /* printk(KERN_DEBUG "drxk: SR %d\n", state->props.symbol_rate); */ + if (state->props.symbol_rate <= 1188750) + ratesel = 3; + else if (state->props.symbol_rate <= 2377500) + ratesel = 2; + else if (state->props.symbol_rate <= 4755000) + ratesel = 1; + status = write16(state, IQM_FD_RATESEL__A, ratesel); + if (status < 0) + goto error; + + /* + IqmRcRate = ((Fadc / (symbolrate * (4<props.symbol_rate * (1 << ratesel); + if (symbFreq == 0) { + /* Divide by zero */ + status = -EINVAL; + goto error; + } + iqmRcRate = (adcFrequency / symbFreq) * (1 << 21) + + (Frac28a((adcFrequency % symbFreq), symbFreq) >> 7) - + (1 << 23); + status = write32(state, IQM_RC_RATE_OFS_LO__A, iqmRcRate); + if (status < 0) + goto error; + state->m_iqmRcRate = iqmRcRate; + /* + LcSymbFreq = round (.125 * symbolrate / adcFreq * (1<<15)) + */ + symbFreq = state->props.symbol_rate; + if (adcFrequency == 0) { + /* Divide by zero */ + status = -EINVAL; + goto error; + } + lcSymbRate = (symbFreq / adcFrequency) * (1 << 12) + + (Frac28a((symbFreq % adcFrequency), adcFrequency) >> + 16); + if (lcSymbRate > 511) + lcSymbRate = 511; + status = write16(state, QAM_LC_SYMBOL_FREQ__A, (u16) lcSymbRate); + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +/*============================================================================*/ + +/** +* \brief Get QAM lock status. +* \param demod: instance of demod. +* \param channel: pointer to channel data. +* \return DRXStatus_t. +*/ + +static int GetQAMLockStatus(struct drxk_state *state, u32 *pLockStatus) +{ + int status; + u16 Result[2] = { 0, 0 }; + + dprintk(1, "\n"); + *pLockStatus = NOT_LOCKED; + status = scu_command(state, + SCU_RAM_COMMAND_STANDARD_QAM | + SCU_RAM_COMMAND_CMD_DEMOD_GET_LOCK, 0, NULL, 2, + Result); + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + if (Result[1] < SCU_RAM_QAM_LOCKED_LOCKED_DEMOD_LOCKED) { + /* 0x0000 NOT LOCKED */ + } else if (Result[1] < SCU_RAM_QAM_LOCKED_LOCKED_LOCKED) { + /* 0x4000 DEMOD LOCKED */ + *pLockStatus = DEMOD_LOCK; + } else if (Result[1] < SCU_RAM_QAM_LOCKED_LOCKED_NEVER_LOCK) { + /* 0x8000 DEMOD + FEC LOCKED (system lock) */ + *pLockStatus = MPEG_LOCK; + } else { + /* 0xC000 NEVER LOCKED */ + /* (system will never be able to lock to the signal) */ + /* TODO: check this, intermediate & standard specific lock states are not + taken into account here */ + *pLockStatus = NEVER_LOCK; + } + return status; +} + +#define QAM_MIRROR__M 0x03 +#define QAM_MIRROR_NORMAL 0x00 +#define QAM_MIRRORED 0x01 +#define QAM_MIRROR_AUTO_ON 0x02 +#define QAM_LOCKRANGE__M 0x10 +#define QAM_LOCKRANGE_NORMAL 0x10 + +static int QAMDemodulatorCommand(struct drxk_state *state, + int numberOfParameters) +{ + int status; + u16 cmdResult; + u16 setParamParameters[4] = { 0, 0, 0, 0 }; + + setParamParameters[0] = state->m_Constellation; /* modulation */ + setParamParameters[1] = DRXK_QAM_I12_J17; /* interleave mode */ + + if (numberOfParameters == 2) { + u16 setEnvParameters[1] = { 0 }; + + if (state->m_OperationMode == OM_QAM_ITU_C) + setEnvParameters[0] = QAM_TOP_ANNEX_C; + else + setEnvParameters[0] = QAM_TOP_ANNEX_A; + + status = scu_command(state, + SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_SET_ENV, + 1, setEnvParameters, 1, &cmdResult); + if (status < 0) + goto error; + + status = scu_command(state, + SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_SET_PARAM, + numberOfParameters, setParamParameters, + 1, &cmdResult); + } else if (numberOfParameters == 4) { + if (state->m_OperationMode == OM_QAM_ITU_C) + setParamParameters[2] = QAM_TOP_ANNEX_C; + else + setParamParameters[2] = QAM_TOP_ANNEX_A; + + setParamParameters[3] |= (QAM_MIRROR_AUTO_ON); + /* Env parameters */ + /* check for LOCKRANGE Extented */ + /* setParamParameters[3] |= QAM_LOCKRANGE_NORMAL; */ + + status = scu_command(state, + SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_SET_PARAM, + numberOfParameters, setParamParameters, + 1, &cmdResult); + } else { + printk(KERN_WARNING "drxk: Unknown QAM demodulator parameter " + "count %d\n", numberOfParameters); + } + +error: + if (status < 0) + printk(KERN_WARNING "drxk: Warning %d on %s\n", + status, __func__); + return status; +} + +static int SetQAM(struct drxk_state *state, u16 IntermediateFreqkHz, + s32 tunerFreqOffset) +{ + int status; + u16 cmdResult; + int qamDemodParamCount = state->qam_demod_parameter_count; + + dprintk(1, "\n"); + /* + * STEP 1: reset demodulator + * resets FEC DI and FEC RS + * resets QAM block + * resets SCU variables + */ + status = write16(state, FEC_DI_COMM_EXEC__A, FEC_DI_COMM_EXEC_STOP); + if (status < 0) + goto error; + status = write16(state, FEC_RS_COMM_EXEC__A, FEC_RS_COMM_EXEC_STOP); + if (status < 0) + goto error; + status = QAMResetQAM(state); + if (status < 0) + goto error; + + /* + * STEP 2: configure demodulator + * -set params; resets IQM,QAM,FEC HW; initializes some + * SCU variables + */ + status = QAMSetSymbolrate(state); + if (status < 0) + goto error; + + /* Set params */ + switch (state->props.modulation) { + case QAM_256: + state->m_Constellation = DRX_CONSTELLATION_QAM256; + break; + case QAM_AUTO: + case QAM_64: + state->m_Constellation = DRX_CONSTELLATION_QAM64; + break; + case QAM_16: + state->m_Constellation = DRX_CONSTELLATION_QAM16; + break; + case QAM_32: + state->m_Constellation = DRX_CONSTELLATION_QAM32; + break; + case QAM_128: + state->m_Constellation = DRX_CONSTELLATION_QAM128; + break; + default: + status = -EINVAL; + break; + } + if (status < 0) + goto error; + + /* Use the 4-parameter if it's requested or we're probing for + * the correct command. */ + if (state->qam_demod_parameter_count == 4 + || !state->qam_demod_parameter_count) { + qamDemodParamCount = 4; + status = QAMDemodulatorCommand(state, qamDemodParamCount); + } + + /* Use the 2-parameter command if it was requested or if we're + * probing for the correct command and the 4-parameter command + * failed. */ + if (state->qam_demod_parameter_count == 2 + || (!state->qam_demod_parameter_count && status < 0)) { + qamDemodParamCount = 2; + status = QAMDemodulatorCommand(state, qamDemodParamCount); + } + + if (status < 0) { + dprintk(1, "Could not set demodulator parameters. Make " + "sure qam_demod_parameter_count (%d) is correct for " + "your firmware (%s).\n", + state->qam_demod_parameter_count, + state->microcode_name); + goto error; + } else if (!state->qam_demod_parameter_count) { + dprintk(1, "Auto-probing the correct QAM demodulator command " + "parameters was successful - using %d parameters.\n", + qamDemodParamCount); + + /* + * One of our commands was successful. We don't need to + * auto-probe anymore, now that we got the correct command. + */ + state->qam_demod_parameter_count = qamDemodParamCount; + } + + /* + * STEP 3: enable the system in a mode where the ADC provides valid + * signal setup modulation independent registers + */ +#if 0 + status = SetFrequency(channel, tunerFreqOffset)); + if (status < 0) + goto error; +#endif + status = SetFrequencyShifter(state, IntermediateFreqkHz, tunerFreqOffset, true); + if (status < 0) + goto error; + + /* Setup BER measurement */ + status = SetQAMMeasurement(state, state->m_Constellation, state->props.symbol_rate); + if (status < 0) + goto error; + + /* Reset default values */ + status = write16(state, IQM_CF_SCALE_SH__A, IQM_CF_SCALE_SH__PRE); + if (status < 0) + goto error; + status = write16(state, QAM_SY_TIMEOUT__A, QAM_SY_TIMEOUT__PRE); + if (status < 0) + goto error; + + /* Reset default LC values */ + status = write16(state, QAM_LC_RATE_LIMIT__A, 3); + if (status < 0) + goto error; + status = write16(state, QAM_LC_LPF_FACTORP__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_LC_LPF_FACTORI__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_LC_MODE__A, 7); + if (status < 0) + goto error; + + status = write16(state, QAM_LC_QUAL_TAB0__A, 1); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB1__A, 1); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB2__A, 1); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB3__A, 1); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB4__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB5__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB6__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB8__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB9__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB10__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB12__A, 2); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB15__A, 3); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB16__A, 3); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB20__A, 4); + if (status < 0) + goto error; + status = write16(state, QAM_LC_QUAL_TAB25__A, 4); + if (status < 0) + goto error; + + /* Mirroring, QAM-block starting point not inverted */ + status = write16(state, QAM_SY_SP_INV__A, QAM_SY_SP_INV_SPECTRUM_INV_DIS); + if (status < 0) + goto error; + + /* Halt SCU to enable safe non-atomic accesses */ + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_HOLD); + if (status < 0) + goto error; + + /* STEP 4: modulation specific setup */ + switch (state->props.modulation) { + case QAM_16: + status = SetQAM16(state); + break; + case QAM_32: + status = SetQAM32(state); + break; + case QAM_AUTO: + case QAM_64: + status = SetQAM64(state); + break; + case QAM_128: + status = SetQAM128(state); + break; + case QAM_256: + status = SetQAM256(state); + break; + default: + status = -EINVAL; + break; + } + if (status < 0) + goto error; + + /* Activate SCU to enable SCU commands */ + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); + if (status < 0) + goto error; + + /* Re-configure MPEG output, requires knowledge of channel bitrate */ + /* extAttr->currentChannel.modulation = channel->modulation; */ + /* extAttr->currentChannel.symbolrate = channel->symbolrate; */ + status = MPEGTSDtoSetup(state, state->m_OperationMode); + if (status < 0) + goto error; + + /* Start processes */ + status = MPEGTSStart(state); + if (status < 0) + goto error; + status = write16(state, FEC_COMM_EXEC__A, FEC_COMM_EXEC_ACTIVE); + if (status < 0) + goto error; + status = write16(state, QAM_COMM_EXEC__A, QAM_COMM_EXEC_ACTIVE); + if (status < 0) + goto error; + status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_ACTIVE); + if (status < 0) + goto error; + + /* STEP 5: start QAM demodulator (starts FEC, QAM and IQM HW) */ + status = scu_command(state, SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_START, 0, NULL, 1, &cmdResult); + if (status < 0) + goto error; + + /* update global DRXK data container */ +/*? extAttr->qamInterleaveMode = DRXK_QAM_I12_J17; */ + +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int SetQAMStandard(struct drxk_state *state, + enum OperationMode oMode) +{ + int status; +#ifdef DRXK_QAM_TAPS +#define DRXK_QAMA_TAPS_SELECT +#include "drxk_filters.h" +#undef DRXK_QAMA_TAPS_SELECT +#endif + + dprintk(1, "\n"); + + /* added antenna switch */ + SwitchAntennaToQAM(state); + + /* Ensure correct power-up mode */ + status = PowerUpQAM(state); + if (status < 0) + goto error; + /* Reset QAM block */ + status = QAMResetQAM(state); + if (status < 0) + goto error; + + /* Setup IQM */ + + status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_STOP); + if (status < 0) + goto error; + status = write16(state, IQM_AF_AMUX__A, IQM_AF_AMUX_SIGNAL2ADC); + if (status < 0) + goto error; + + /* Upload IQM Channel Filter settings by + boot loader from ROM table */ + switch (oMode) { + case OM_QAM_ITU_A: + status = BLChainCmd(state, DRXK_BL_ROM_OFFSET_TAPS_ITU_A, DRXK_BLCC_NR_ELEMENTS_TAPS, DRXK_BLC_TIMEOUT); + break; + case OM_QAM_ITU_C: + status = BLDirectCmd(state, IQM_CF_TAP_RE0__A, DRXK_BL_ROM_OFFSET_TAPS_ITU_C, DRXK_BLDC_NR_ELEMENTS_TAPS, DRXK_BLC_TIMEOUT); + if (status < 0) + goto error; + status = BLDirectCmd(state, IQM_CF_TAP_IM0__A, DRXK_BL_ROM_OFFSET_TAPS_ITU_C, DRXK_BLDC_NR_ELEMENTS_TAPS, DRXK_BLC_TIMEOUT); + break; + default: + status = -EINVAL; + } + if (status < 0) + goto error; + + status = write16(state, IQM_CF_OUT_ENA__A, (1 << IQM_CF_OUT_ENA_QAM__B)); + if (status < 0) + goto error; + status = write16(state, IQM_CF_SYMMETRIC__A, 0); + if (status < 0) + goto error; + status = write16(state, IQM_CF_MIDTAP__A, ((1 << IQM_CF_MIDTAP_RE__B) | (1 << IQM_CF_MIDTAP_IM__B))); + if (status < 0) + goto error; + + status = write16(state, IQM_RC_STRETCH__A, 21); + if (status < 0) + goto error; + status = write16(state, IQM_AF_CLP_LEN__A, 0); + if (status < 0) + goto error; + status = write16(state, IQM_AF_CLP_TH__A, 448); + if (status < 0) + goto error; + status = write16(state, IQM_AF_SNS_LEN__A, 0); + if (status < 0) + goto error; + status = write16(state, IQM_CF_POW_MEAS_LEN__A, 0); + if (status < 0) + goto error; + + status = write16(state, IQM_FS_ADJ_SEL__A, 1); + if (status < 0) + goto error; + status = write16(state, IQM_RC_ADJ_SEL__A, 1); + if (status < 0) + goto error; + status = write16(state, IQM_CF_ADJ_SEL__A, 1); + if (status < 0) + goto error; + status = write16(state, IQM_AF_UPD_SEL__A, 0); + if (status < 0) + goto error; + + /* IQM Impulse Noise Processing Unit */ + status = write16(state, IQM_CF_CLP_VAL__A, 500); + if (status < 0) + goto error; + status = write16(state, IQM_CF_DATATH__A, 1000); + if (status < 0) + goto error; + status = write16(state, IQM_CF_BYPASSDET__A, 1); + if (status < 0) + goto error; + status = write16(state, IQM_CF_DET_LCT__A, 0); + if (status < 0) + goto error; + status = write16(state, IQM_CF_WND_LEN__A, 1); + if (status < 0) + goto error; + status = write16(state, IQM_CF_PKDTH__A, 1); + if (status < 0) + goto error; + status = write16(state, IQM_AF_INC_BYPASS__A, 1); + if (status < 0) + goto error; + + /* turn on IQMAF. Must be done before setAgc**() */ + status = SetIqmAf(state, true); + if (status < 0) + goto error; + status = write16(state, IQM_AF_START_LOCK__A, 0x01); + if (status < 0) + goto error; + + /* IQM will not be reset from here, sync ADC and update/init AGC */ + status = ADCSynchronization(state); + if (status < 0) + goto error; + + /* Set the FSM step period */ + status = write16(state, SCU_RAM_QAM_FSM_STEP_PERIOD__A, 2000); + if (status < 0) + goto error; + + /* Halt SCU to enable safe non-atomic accesses */ + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_HOLD); + if (status < 0) + goto error; + + /* No more resets of the IQM, current standard correctly set => + now AGCs can be configured. */ + + status = InitAGC(state, true); + if (status < 0) + goto error; + status = SetPreSaw(state, &(state->m_qamPreSawCfg)); + if (status < 0) + goto error; + + /* Configure AGC's */ + status = SetAgcRf(state, &(state->m_qamRfAgcCfg), true); + if (status < 0) + goto error; + status = SetAgcIf(state, &(state->m_qamIfAgcCfg), true); + if (status < 0) + goto error; + + /* Activate SCU to enable SCU commands */ + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int WriteGPIO(struct drxk_state *state) +{ + int status; + u16 value = 0; + + dprintk(1, "\n"); + /* stop lock indicator process */ + status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); + if (status < 0) + goto error; + + /* Write magic word to enable pdr reg write */ + status = write16(state, SIO_TOP_COMM_KEY__A, SIO_TOP_COMM_KEY_KEY); + if (status < 0) + goto error; + + if (state->m_hasSAWSW) { + if (state->UIO_mask & 0x0001) { /* UIO-1 */ + /* write to io pad configuration register - output mode */ + status = write16(state, SIO_PDR_SMA_TX_CFG__A, state->m_GPIOCfg); + if (status < 0) + goto error; + + /* use corresponding bit in io data output registar */ + status = read16(state, SIO_PDR_UIO_OUT_LO__A, &value); + if (status < 0) + goto error; + if ((state->m_GPIO & 0x0001) == 0) + value &= 0x7FFF; /* write zero to 15th bit - 1st UIO */ + else + value |= 0x8000; /* write one to 15th bit - 1st UIO */ + /* write back to io data output register */ + status = write16(state, SIO_PDR_UIO_OUT_LO__A, value); + if (status < 0) + goto error; + } + if (state->UIO_mask & 0x0002) { /* UIO-2 */ + /* write to io pad configuration register - output mode */ + status = write16(state, SIO_PDR_SMA_RX_CFG__A, state->m_GPIOCfg); + if (status < 0) + goto error; + + /* use corresponding bit in io data output registar */ + status = read16(state, SIO_PDR_UIO_OUT_LO__A, &value); + if (status < 0) + goto error; + if ((state->m_GPIO & 0x0002) == 0) + value &= 0xBFFF; /* write zero to 14th bit - 2st UIO */ + else + value |= 0x4000; /* write one to 14th bit - 2st UIO */ + /* write back to io data output register */ + status = write16(state, SIO_PDR_UIO_OUT_LO__A, value); + if (status < 0) + goto error; + } + if (state->UIO_mask & 0x0004) { /* UIO-3 */ + /* write to io pad configuration register - output mode */ + status = write16(state, SIO_PDR_GPIO_CFG__A, state->m_GPIOCfg); + if (status < 0) + goto error; + + /* use corresponding bit in io data output registar */ + status = read16(state, SIO_PDR_UIO_OUT_LO__A, &value); + if (status < 0) + goto error; + if ((state->m_GPIO & 0x0004) == 0) + value &= 0xFFFB; /* write zero to 2nd bit - 3rd UIO */ + else + value |= 0x0004; /* write one to 2nd bit - 3rd UIO */ + /* write back to io data output register */ + status = write16(state, SIO_PDR_UIO_OUT_LO__A, value); + if (status < 0) + goto error; + } + } + /* Write magic word to disable pdr reg write */ + status = write16(state, SIO_TOP_COMM_KEY__A, 0x0000); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int SwitchAntennaToQAM(struct drxk_state *state) +{ + int status = 0; + bool gpio_state; + + dprintk(1, "\n"); + + if (!state->antenna_gpio) + return 0; + + gpio_state = state->m_GPIO & state->antenna_gpio; + + if (state->antenna_dvbt ^ gpio_state) { + /* Antenna is on DVB-T mode. Switch */ + if (state->antenna_dvbt) + state->m_GPIO &= ~state->antenna_gpio; + else + state->m_GPIO |= state->antenna_gpio; + status = WriteGPIO(state); + } + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + +static int SwitchAntennaToDVBT(struct drxk_state *state) +{ + int status = 0; + bool gpio_state; + + dprintk(1, "\n"); + + if (!state->antenna_gpio) + return 0; + + gpio_state = state->m_GPIO & state->antenna_gpio; + + if (!(state->antenna_dvbt ^ gpio_state)) { + /* Antenna is on DVB-C mode. Switch */ + if (state->antenna_dvbt) + state->m_GPIO |= state->antenna_gpio; + else + state->m_GPIO &= ~state->antenna_gpio; + status = WriteGPIO(state); + } + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + return status; +} + + +static int PowerDownDevice(struct drxk_state *state) +{ + /* Power down to requested mode */ + /* Backup some register settings */ + /* Set pins with possible pull-ups connected to them in input mode */ + /* Analog power down */ + /* ADC power down */ + /* Power down device */ + int status; + + dprintk(1, "\n"); + if (state->m_bPDownOpenBridge) { + /* Open I2C bridge before power down of DRXK */ + status = ConfigureI2CBridge(state, true); + if (status < 0) + goto error; + } + /* driver 0.9.0 */ + status = DVBTEnableOFDMTokenRing(state, false); + if (status < 0) + goto error; + + status = write16(state, SIO_CC_PWD_MODE__A, SIO_CC_PWD_MODE_LEVEL_CLOCK); + if (status < 0) + goto error; + status = write16(state, SIO_CC_UPDATE__A, SIO_CC_UPDATE_KEY); + if (status < 0) + goto error; + state->m_HICfgCtrl |= SIO_HI_RA_RAM_PAR_5_CFG_SLEEP_ZZZ; + status = HI_CfgCommand(state); +error: + if (status < 0) + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + + return status; +} + +static int init_drxk(struct drxk_state *state) +{ + int status = 0, n = 0; + enum DRXPowerMode powerMode = DRXK_POWER_DOWN_OFDM; + u16 driverVersion; + + dprintk(1, "\n"); + if ((state->m_DrxkState == DRXK_UNINITIALIZED)) { + drxk_i2c_lock(state); + status = PowerUpDevice(state); + if (status < 0) + goto error; + status = DRXX_Open(state); + if (status < 0) + goto error; + /* Soft reset of OFDM-, sys- and osc-clockdomain */ + status = write16(state, SIO_CC_SOFT_RST__A, SIO_CC_SOFT_RST_OFDM__M | SIO_CC_SOFT_RST_SYS__M | SIO_CC_SOFT_RST_OSC__M); + if (status < 0) + goto error; + status = write16(state, SIO_CC_UPDATE__A, SIO_CC_UPDATE_KEY); + if (status < 0) + goto error; + /* TODO is this needed, if yes how much delay in worst case scenario */ + msleep(1); + state->m_DRXK_A3_PATCH_CODE = true; + status = GetDeviceCapabilities(state); + if (status < 0) + goto error; + + /* Bridge delay, uses oscilator clock */ + /* Delay = (delay (nano seconds) * oscclk (kHz))/ 1000 */ + /* SDA brdige delay */ + state->m_HICfgBridgeDelay = + (u16) ((state->m_oscClockFreq / 1000) * + HI_I2C_BRIDGE_DELAY) / 1000; + /* Clipping */ + if (state->m_HICfgBridgeDelay > + SIO_HI_RA_RAM_PAR_3_CFG_DBL_SDA__M) { + state->m_HICfgBridgeDelay = + SIO_HI_RA_RAM_PAR_3_CFG_DBL_SDA__M; + } + /* SCL bridge delay, same as SDA for now */ + state->m_HICfgBridgeDelay += + state->m_HICfgBridgeDelay << + SIO_HI_RA_RAM_PAR_3_CFG_DBL_SCL__B; + + status = InitHI(state); + if (status < 0) + goto error; + /* disable various processes */ +#if NOA1ROM + if (!(state->m_DRXK_A1_ROM_CODE) + && !(state->m_DRXK_A2_ROM_CODE)) +#endif + { + status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); + if (status < 0) + goto error; + } + + /* disable MPEG port */ + status = MPEGTSDisable(state); + if (status < 0) + goto error; + + /* Stop AUD and SCU */ + status = write16(state, AUD_COMM_EXEC__A, AUD_COMM_EXEC_STOP); + if (status < 0) + goto error; + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_STOP); + if (status < 0) + goto error; + + /* enable token-ring bus through OFDM block for possible ucode upload */ + status = write16(state, SIO_OFDM_SH_OFDM_RING_ENABLE__A, SIO_OFDM_SH_OFDM_RING_ENABLE_ON); + if (status < 0) + goto error; + + /* include boot loader section */ + status = write16(state, SIO_BL_COMM_EXEC__A, SIO_BL_COMM_EXEC_ACTIVE); + if (status < 0) + goto error; + status = BLChainCmd(state, 0, 6, 100); + if (status < 0) + goto error; + + if (state->fw) { + status = DownloadMicrocode(state, state->fw->data, + state->fw->size); + if (status < 0) + goto error; + } + + /* disable token-ring bus through OFDM block for possible ucode upload */ + status = write16(state, SIO_OFDM_SH_OFDM_RING_ENABLE__A, SIO_OFDM_SH_OFDM_RING_ENABLE_OFF); + if (status < 0) + goto error; + + /* Run SCU for a little while to initialize microcode version numbers */ + status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); + if (status < 0) + goto error; + status = DRXX_Open(state); + if (status < 0) + goto error; + /* added for test */ + msleep(30); + + powerMode = DRXK_POWER_DOWN_OFDM; + status = CtrlPowerMode(state, &powerMode); + if (status < 0) + goto error; + + /* Stamp driver version number in SCU data RAM in BCD code + Done to enable field application engineers to retreive drxdriver version + via I2C from SCU RAM. + Not using SCU command interface for SCU register access since no + microcode may be present. + */ + driverVersion = + (((DRXK_VERSION_MAJOR / 100) % 10) << 12) + + (((DRXK_VERSION_MAJOR / 10) % 10) << 8) + + ((DRXK_VERSION_MAJOR % 10) << 4) + + (DRXK_VERSION_MINOR % 10); + status = write16(state, SCU_RAM_DRIVER_VER_HI__A, driverVersion); + if (status < 0) + goto error; + driverVersion = + (((DRXK_VERSION_PATCH / 1000) % 10) << 12) + + (((DRXK_VERSION_PATCH / 100) % 10) << 8) + + (((DRXK_VERSION_PATCH / 10) % 10) << 4) + + (DRXK_VERSION_PATCH % 10); + status = write16(state, SCU_RAM_DRIVER_VER_LO__A, driverVersion); + if (status < 0) + goto error; + + printk(KERN_INFO "DRXK driver version %d.%d.%d\n", + DRXK_VERSION_MAJOR, DRXK_VERSION_MINOR, + DRXK_VERSION_PATCH); + + /* Dirty fix of default values for ROM/PATCH microcode + Dirty because this fix makes it impossible to setup suitable values + before calling DRX_Open. This solution requires changes to RF AGC speed + to be done via the CTRL function after calling DRX_Open */ + + /* m_dvbtRfAgcCfg.speed = 3; */ + + /* Reset driver debug flags to 0 */ + status = write16(state, SCU_RAM_DRIVER_DEBUG__A, 0); + if (status < 0) + goto error; + /* driver 0.9.0 */ + /* Setup FEC OC: + NOTE: No more full FEC resets allowed afterwards!! */ + status = write16(state, FEC_COMM_EXEC__A, FEC_COMM_EXEC_STOP); + if (status < 0) + goto error; + /* MPEGTS functions are still the same */ + status = MPEGTSDtoInit(state); + if (status < 0) + goto error; + status = MPEGTSStop(state); + if (status < 0) + goto error; + status = MPEGTSConfigurePolarity(state); + if (status < 0) + goto error; + status = MPEGTSConfigurePins(state, state->m_enableMPEGOutput); + if (status < 0) + goto error; + /* added: configure GPIO */ + status = WriteGPIO(state); + if (status < 0) + goto error; + + state->m_DrxkState = DRXK_STOPPED; + + if (state->m_bPowerDown) { + status = PowerDownDevice(state); + if (status < 0) + goto error; + state->m_DrxkState = DRXK_POWERED_DOWN; + } else + state->m_DrxkState = DRXK_STOPPED; + + /* Initialize the supported delivery systems */ + n = 0; + if (state->m_hasDVBC) { + state->frontend.ops.delsys[n++] = SYS_DVBC_ANNEX_A; + state->frontend.ops.delsys[n++] = SYS_DVBC_ANNEX_C; + strlcat(state->frontend.ops.info.name, " DVB-C", + sizeof(state->frontend.ops.info.name)); + } + if (state->m_hasDVBT) { + state->frontend.ops.delsys[n++] = SYS_DVBT; + strlcat(state->frontend.ops.info.name, " DVB-T", + sizeof(state->frontend.ops.info.name)); + } + drxk_i2c_unlock(state); + } +error: + if (status < 0) { + state->m_DrxkState = DRXK_NO_DEV; + drxk_i2c_unlock(state); + printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); + } + + return status; +} + +static void load_firmware_cb(const struct firmware *fw, + void *context) +{ + struct drxk_state *state = context; + + dprintk(1, ": %s\n", fw ? "firmware loaded" : "firmware not loaded"); + if (!fw) { + printk(KERN_ERR + "drxk: Could not load firmware file %s.\n", + state->microcode_name); + printk(KERN_INFO + "drxk: Copy %s to your hotplug directory!\n", + state->microcode_name); + state->microcode_name = NULL; + + /* + * As firmware is now load asynchronous, it is not possible + * anymore to fail at frontend attach. We might silently + * return here, and hope that the driver won't crash. + * We might also change all DVB callbacks to return -ENODEV + * if the device is not initialized. + * As the DRX-K devices have their own internal firmware, + * let's just hope that it will match a firmware revision + * compatible with this driver and proceed. + */ + } + state->fw = fw; + + init_drxk(state); +} + +static void drxk_release(struct dvb_frontend *fe) +{ + struct drxk_state *state = fe->demodulator_priv; + + dprintk(1, "\n"); + if (state->fw) + release_firmware(state->fw); + + kfree(state); +} + +static int drxk_sleep(struct dvb_frontend *fe) +{ + struct drxk_state *state = fe->demodulator_priv; + + dprintk(1, "\n"); + + if (state->m_DrxkState == DRXK_NO_DEV) + return -ENODEV; + if (state->m_DrxkState == DRXK_UNINITIALIZED) + return 0; + + ShutDown(state); + return 0; +} + +static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct drxk_state *state = fe->demodulator_priv; + + dprintk(1, ": %s\n", enable ? "enable" : "disable"); + + if (state->m_DrxkState == DRXK_NO_DEV) + return -ENODEV; + + return ConfigureI2CBridge(state, enable ? true : false); +} + +static int drxk_set_parameters(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + u32 delsys = p->delivery_system, old_delsys; + struct drxk_state *state = fe->demodulator_priv; + u32 IF; + + dprintk(1, "\n"); + + if (state->m_DrxkState == DRXK_NO_DEV) + return -ENODEV; + + if (state->m_DrxkState == DRXK_UNINITIALIZED) + return -EAGAIN; + + if (!fe->ops.tuner_ops.get_if_frequency) { + printk(KERN_ERR + "drxk: Error: get_if_frequency() not defined at tuner. Can't work without it!\n"); + return -EINVAL; + } + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + old_delsys = state->props.delivery_system; + state->props = *p; + + if (old_delsys != delsys) { + ShutDown(state); + switch (delsys) { + case SYS_DVBC_ANNEX_A: + case SYS_DVBC_ANNEX_C: + if (!state->m_hasDVBC) + return -EINVAL; + state->m_itut_annex_c = (delsys == SYS_DVBC_ANNEX_C) ? true : false; + if (state->m_itut_annex_c) + SetOperationMode(state, OM_QAM_ITU_C); + else + SetOperationMode(state, OM_QAM_ITU_A); + break; + case SYS_DVBT: + if (!state->m_hasDVBT) + return -EINVAL; + SetOperationMode(state, OM_DVBT); + break; + default: + return -EINVAL; + } + } + + fe->ops.tuner_ops.get_if_frequency(fe, &IF); + Start(state, 0, IF); + + /* printk(KERN_DEBUG "drxk: %s IF=%d done\n", __func__, IF); */ + + return 0; +} + +static int drxk_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct drxk_state *state = fe->demodulator_priv; + u32 stat; + + dprintk(1, "\n"); + + if (state->m_DrxkState == DRXK_NO_DEV) + return -ENODEV; + if (state->m_DrxkState == DRXK_UNINITIALIZED) + return -EAGAIN; + + *status = 0; + GetLockStatus(state, &stat, 0); + if (stat == MPEG_LOCK) + *status |= 0x1f; + if (stat == FEC_LOCK) + *status |= 0x0f; + if (stat == DEMOD_LOCK) + *status |= 0x07; + return 0; +} + +static int drxk_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct drxk_state *state = fe->demodulator_priv; + + dprintk(1, "\n"); + + if (state->m_DrxkState == DRXK_NO_DEV) + return -ENODEV; + if (state->m_DrxkState == DRXK_UNINITIALIZED) + return -EAGAIN; + + *ber = 0; + return 0; +} + +static int drxk_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + struct drxk_state *state = fe->demodulator_priv; + u32 val = 0; + + dprintk(1, "\n"); + + if (state->m_DrxkState == DRXK_NO_DEV) + return -ENODEV; + if (state->m_DrxkState == DRXK_UNINITIALIZED) + return -EAGAIN; + + ReadIFAgc(state, &val); + *strength = val & 0xffff; + return 0; +} + +static int drxk_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct drxk_state *state = fe->demodulator_priv; + s32 snr2; + + dprintk(1, "\n"); + + if (state->m_DrxkState == DRXK_NO_DEV) + return -ENODEV; + if (state->m_DrxkState == DRXK_UNINITIALIZED) + return -EAGAIN; + + GetSignalToNoise(state, &snr2); + *snr = snr2 & 0xffff; + return 0; +} + +static int drxk_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct drxk_state *state = fe->demodulator_priv; + u16 err; + + dprintk(1, "\n"); + + if (state->m_DrxkState == DRXK_NO_DEV) + return -ENODEV; + if (state->m_DrxkState == DRXK_UNINITIALIZED) + return -EAGAIN; + + DVBTQAMGetAccPktErr(state, &err); + *ucblocks = (u32) err; + return 0; +} + +static int drxk_get_tune_settings(struct dvb_frontend *fe, struct dvb_frontend_tune_settings + *sets) +{ + struct drxk_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + + dprintk(1, "\n"); + + if (state->m_DrxkState == DRXK_NO_DEV) + return -ENODEV; + if (state->m_DrxkState == DRXK_UNINITIALIZED) + return -EAGAIN; + + switch (p->delivery_system) { + case SYS_DVBC_ANNEX_A: + case SYS_DVBC_ANNEX_C: + case SYS_DVBT: + sets->min_delay_ms = 3000; + sets->max_drift = 0; + sets->step_size = 0; + return 0; + default: + return -EINVAL; + } +} + +static struct dvb_frontend_ops drxk_ops = { + /* .delsys will be filled dynamically */ + .info = { + .name = "DRXK", + .frequency_min = 47000000, + .frequency_max = 865000000, + /* For DVB-C */ + .symbol_rate_min = 870000, + .symbol_rate_max = 11700000, + /* For DVB-T */ + .frequency_stepsize = 166667, + + .caps = FE_CAN_QAM_16 | FE_CAN_QAM_32 | FE_CAN_QAM_64 | + FE_CAN_QAM_128 | FE_CAN_QAM_256 | FE_CAN_FEC_AUTO | + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_MUTE_TS | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_RECOVER | + FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO + }, + + .release = drxk_release, + .sleep = drxk_sleep, + .i2c_gate_ctrl = drxk_gate_ctrl, + + .set_frontend = drxk_set_parameters, + .get_tune_settings = drxk_get_tune_settings, + + .read_status = drxk_read_status, + .read_ber = drxk_read_ber, + .read_signal_strength = drxk_read_signal_strength, + .read_snr = drxk_read_snr, + .read_ucblocks = drxk_read_ucblocks, +}; + +struct dvb_frontend *drxk_attach(const struct drxk_config *config, + struct i2c_adapter *i2c) +{ + struct drxk_state *state = NULL; + u8 adr = config->adr; + int status; + + dprintk(1, "\n"); + state = kzalloc(sizeof(struct drxk_state), GFP_KERNEL); + if (!state) + return NULL; + + state->i2c = i2c; + state->demod_address = adr; + state->single_master = config->single_master; + state->microcode_name = config->microcode_name; + state->qam_demod_parameter_count = config->qam_demod_parameter_count; + state->no_i2c_bridge = config->no_i2c_bridge; + state->antenna_gpio = config->antenna_gpio; + state->antenna_dvbt = config->antenna_dvbt; + state->m_ChunkSize = config->chunk_size; + state->enable_merr_cfg = config->enable_merr_cfg; + + if (config->dynamic_clk) { + state->m_DVBTStaticCLK = 0; + state->m_DVBCStaticCLK = 0; + } else { + state->m_DVBTStaticCLK = 1; + state->m_DVBCStaticCLK = 1; + } + + + if (config->mpeg_out_clk_strength) + state->m_TSClockkStrength = config->mpeg_out_clk_strength & 0x07; + else + state->m_TSClockkStrength = 0x06; + + if (config->parallel_ts) + state->m_enableParallel = true; + else + state->m_enableParallel = false; + + /* NOTE: as more UIO bits will be used, add them to the mask */ + state->UIO_mask = config->antenna_gpio; + + /* Default gpio to DVB-C */ + if (!state->antenna_dvbt && state->antenna_gpio) + state->m_GPIO |= state->antenna_gpio; + else + state->m_GPIO &= ~state->antenna_gpio; + + mutex_init(&state->mutex); + + memcpy(&state->frontend.ops, &drxk_ops, sizeof(drxk_ops)); + state->frontend.demodulator_priv = state; + + init_state(state); + + /* Load firmware and initialize DRX-K */ + if (state->microcode_name) { + if (config->load_firmware_sync) { + const struct firmware *fw = NULL; + + status = request_firmware(&fw, state->microcode_name, + state->i2c->dev.parent); + if (status < 0) + fw = NULL; + load_firmware_cb(fw, state); + } else { + status = request_firmware_nowait(THIS_MODULE, 1, + state->microcode_name, + state->i2c->dev.parent, + GFP_KERNEL, + state, load_firmware_cb); + if (status < 0) { + printk(KERN_ERR + "drxk: failed to request a firmware\n"); + return NULL; + } + } + } else if (init_drxk(state) < 0) + goto error; + + printk(KERN_INFO "drxk: frontend initialized.\n"); + return &state->frontend; + +error: + printk(KERN_ERR "drxk: not found\n"); + kfree(state); + return NULL; +} +EXPORT_SYMBOL(drxk_attach); + +MODULE_DESCRIPTION("DRX-K driver"); +MODULE_AUTHOR("Ralph Metzler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/drxk_hard.h b/drivers/media/dvb-frontends/drxk_hard.h similarity index 100% rename from drivers/media/dvb/frontends/drxk_hard.h rename to drivers/media/dvb-frontends/drxk_hard.h diff --git a/drivers/media/dvb/frontends/drxk_map.h b/drivers/media/dvb-frontends/drxk_map.h similarity index 100% rename from drivers/media/dvb/frontends/drxk_map.h rename to drivers/media/dvb-frontends/drxk_map.h diff --git a/drivers/media/dvb/frontends/ds3000.c b/drivers/media/dvb-frontends/ds3000.c similarity index 100% rename from drivers/media/dvb/frontends/ds3000.c rename to drivers/media/dvb-frontends/ds3000.c diff --git a/drivers/media/dvb/frontends/ds3000.h b/drivers/media/dvb-frontends/ds3000.h similarity index 100% rename from drivers/media/dvb/frontends/ds3000.h rename to drivers/media/dvb-frontends/ds3000.h diff --git a/drivers/media/dvb-frontends/dvb-pll.c b/drivers/media/dvb-frontends/dvb-pll.c new file mode 100644 index 000000000000..6d8fe8843237 --- /dev/null +++ b/drivers/media/dvb-frontends/dvb-pll.c @@ -0,0 +1,820 @@ +/* + * descriptions + helper functions for simple dvb plls. + * + * (c) 2004 Gerd Knorr [SuSE Labs] + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "dvb-pll.h" + +struct dvb_pll_priv { + /* pll number */ + int nr; + + /* i2c details */ + int pll_i2c_address; + struct i2c_adapter *i2c; + + /* the PLL descriptor */ + struct dvb_pll_desc *pll_desc; + + /* cached frequency/bandwidth */ + u32 frequency; + u32 bandwidth; +}; + +#define DVB_PLL_MAX 64 + +static unsigned int dvb_pll_devcount; + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable verbose debug messages"); + +static unsigned int id[DVB_PLL_MAX] = + { [ 0 ... (DVB_PLL_MAX-1) ] = DVB_PLL_UNDEFINED }; +module_param_array(id, int, NULL, 0644); +MODULE_PARM_DESC(id, "force pll id to use (DEBUG ONLY)"); + +/* ----------------------------------------------------------- */ + +struct dvb_pll_desc { + char *name; + u32 min; + u32 max; + u32 iffreq; + void (*set)(struct dvb_frontend *fe, u8 *buf); + u8 *initdata; + u8 *initdata2; + u8 *sleepdata; + int count; + struct { + u32 limit; + u32 stepsize; + u8 config; + u8 cb; + } entries[12]; +}; + +/* ----------------------------------------------------------- */ +/* descriptions */ + +static struct dvb_pll_desc dvb_pll_thomson_dtt7579 = { + .name = "Thomson dtt7579", + .min = 177000000, + .max = 858000000, + .iffreq= 36166667, + .sleepdata = (u8[]){ 2, 0xb4, 0x03 }, + .count = 4, + .entries = { + { 443250000, 166667, 0xb4, 0x02 }, + { 542000000, 166667, 0xb4, 0x08 }, + { 771000000, 166667, 0xbc, 0x08 }, + { 999999999, 166667, 0xf4, 0x08 }, + }, +}; + +static void thomson_dtt759x_bw(struct dvb_frontend *fe, u8 *buf) +{ + u32 bw = fe->dtv_property_cache.bandwidth_hz; + if (bw == 7000000) + buf[3] |= 0x10; +} + +static struct dvb_pll_desc dvb_pll_thomson_dtt759x = { + .name = "Thomson dtt759x", + .min = 177000000, + .max = 896000000, + .set = thomson_dtt759x_bw, + .iffreq= 36166667, + .sleepdata = (u8[]){ 2, 0x84, 0x03 }, + .count = 5, + .entries = { + { 264000000, 166667, 0xb4, 0x02 }, + { 470000000, 166667, 0xbc, 0x02 }, + { 735000000, 166667, 0xbc, 0x08 }, + { 835000000, 166667, 0xf4, 0x08 }, + { 999999999, 166667, 0xfc, 0x08 }, + }, +}; + +static void thomson_dtt7520x_bw(struct dvb_frontend *fe, u8 *buf) +{ + u32 bw = fe->dtv_property_cache.bandwidth_hz; + if (bw == 8000000) + buf[3] ^= 0x10; +} + +static struct dvb_pll_desc dvb_pll_thomson_dtt7520x = { + .name = "Thomson dtt7520x", + .min = 185000000, + .max = 900000000, + .set = thomson_dtt7520x_bw, + .iffreq = 36166667, + .count = 7, + .entries = { + { 305000000, 166667, 0xb4, 0x12 }, + { 405000000, 166667, 0xbc, 0x12 }, + { 445000000, 166667, 0xbc, 0x12 }, + { 465000000, 166667, 0xf4, 0x18 }, + { 735000000, 166667, 0xfc, 0x18 }, + { 835000000, 166667, 0xbc, 0x18 }, + { 999999999, 166667, 0xfc, 0x18 }, + }, +}; + +static struct dvb_pll_desc dvb_pll_lg_z201 = { + .name = "LG z201", + .min = 174000000, + .max = 862000000, + .iffreq= 36166667, + .sleepdata = (u8[]){ 2, 0xbc, 0x03 }, + .count = 5, + .entries = { + { 157500000, 166667, 0xbc, 0x01 }, + { 443250000, 166667, 0xbc, 0x02 }, + { 542000000, 166667, 0xbc, 0x04 }, + { 830000000, 166667, 0xf4, 0x04 }, + { 999999999, 166667, 0xfc, 0x04 }, + }, +}; + +static struct dvb_pll_desc dvb_pll_unknown_1 = { + .name = "unknown 1", /* used by dntv live dvb-t */ + .min = 174000000, + .max = 862000000, + .iffreq= 36166667, + .count = 9, + .entries = { + { 150000000, 166667, 0xb4, 0x01 }, + { 173000000, 166667, 0xbc, 0x01 }, + { 250000000, 166667, 0xb4, 0x02 }, + { 400000000, 166667, 0xbc, 0x02 }, + { 420000000, 166667, 0xf4, 0x02 }, + { 470000000, 166667, 0xfc, 0x02 }, + { 600000000, 166667, 0xbc, 0x08 }, + { 730000000, 166667, 0xf4, 0x08 }, + { 999999999, 166667, 0xfc, 0x08 }, + }, +}; + +/* Infineon TUA6010XS + * used in Thomson Cable Tuner + */ +static struct dvb_pll_desc dvb_pll_tua6010xs = { + .name = "Infineon TUA6010XS", + .min = 44250000, + .max = 858000000, + .iffreq= 36125000, + .count = 3, + .entries = { + { 115750000, 62500, 0x8e, 0x03 }, + { 403250000, 62500, 0x8e, 0x06 }, + { 999999999, 62500, 0x8e, 0x85 }, + }, +}; + +/* Panasonic env57h1xd5 (some Philips PLL ?) */ +static struct dvb_pll_desc dvb_pll_env57h1xd5 = { + .name = "Panasonic ENV57H1XD5", + .min = 44250000, + .max = 858000000, + .iffreq= 36125000, + .count = 4, + .entries = { + { 153000000, 166667, 0xc2, 0x41 }, + { 470000000, 166667, 0xc2, 0x42 }, + { 526000000, 166667, 0xc2, 0x84 }, + { 999999999, 166667, 0xc2, 0xa4 }, + }, +}; + +/* Philips TDA6650/TDA6651 + * used in Panasonic ENV77H11D5 + */ +static void tda665x_bw(struct dvb_frontend *fe, u8 *buf) +{ + u32 bw = fe->dtv_property_cache.bandwidth_hz; + if (bw == 8000000) + buf[3] |= 0x08; +} + +static struct dvb_pll_desc dvb_pll_tda665x = { + .name = "Philips TDA6650/TDA6651", + .min = 44250000, + .max = 858000000, + .set = tda665x_bw, + .iffreq= 36166667, + .initdata = (u8[]){ 4, 0x0b, 0xf5, 0x85, 0xab }, + .count = 12, + .entries = { + { 93834000, 166667, 0xca, 0x61 /* 011 0 0 0 01 */ }, + { 123834000, 166667, 0xca, 0xa1 /* 101 0 0 0 01 */ }, + { 161000000, 166667, 0xca, 0xa1 /* 101 0 0 0 01 */ }, + { 163834000, 166667, 0xca, 0xc2 /* 110 0 0 0 10 */ }, + { 253834000, 166667, 0xca, 0x62 /* 011 0 0 0 10 */ }, + { 383834000, 166667, 0xca, 0xa2 /* 101 0 0 0 10 */ }, + { 443834000, 166667, 0xca, 0xc2 /* 110 0 0 0 10 */ }, + { 444000000, 166667, 0xca, 0xc4 /* 110 0 0 1 00 */ }, + { 583834000, 166667, 0xca, 0x64 /* 011 0 0 1 00 */ }, + { 793834000, 166667, 0xca, 0xa4 /* 101 0 0 1 00 */ }, + { 444834000, 166667, 0xca, 0xc4 /* 110 0 0 1 00 */ }, + { 861000000, 166667, 0xca, 0xe4 /* 111 0 0 1 00 */ }, + } +}; + +/* Infineon TUA6034 + * used in LG TDTP E102P + */ +static void tua6034_bw(struct dvb_frontend *fe, u8 *buf) +{ + u32 bw = fe->dtv_property_cache.bandwidth_hz; + if (bw == 7000000) + buf[3] |= 0x08; +} + +static struct dvb_pll_desc dvb_pll_tua6034 = { + .name = "Infineon TUA6034", + .min = 44250000, + .max = 858000000, + .iffreq= 36166667, + .count = 3, + .set = tua6034_bw, + .entries = { + { 174500000, 62500, 0xce, 0x01 }, + { 230000000, 62500, 0xce, 0x02 }, + { 999999999, 62500, 0xce, 0x04 }, + }, +}; + +/* ALPS TDED4 + * used in Nebula-Cards and USB boxes + */ +static void tded4_bw(struct dvb_frontend *fe, u8 *buf) +{ + u32 bw = fe->dtv_property_cache.bandwidth_hz; + if (bw == 8000000) + buf[3] |= 0x04; +} + +static struct dvb_pll_desc dvb_pll_tded4 = { + .name = "ALPS TDED4", + .min = 47000000, + .max = 863000000, + .iffreq= 36166667, + .set = tded4_bw, + .count = 4, + .entries = { + { 153000000, 166667, 0x85, 0x01 }, + { 470000000, 166667, 0x85, 0x02 }, + { 823000000, 166667, 0x85, 0x08 }, + { 999999999, 166667, 0x85, 0x88 }, + } +}; + +/* ALPS TDHU2 + * used in AverTVHD MCE A180 + */ +static struct dvb_pll_desc dvb_pll_tdhu2 = { + .name = "ALPS TDHU2", + .min = 54000000, + .max = 864000000, + .iffreq= 44000000, + .count = 4, + .entries = { + { 162000000, 62500, 0x85, 0x01 }, + { 426000000, 62500, 0x85, 0x02 }, + { 782000000, 62500, 0x85, 0x08 }, + { 999999999, 62500, 0x85, 0x88 }, + } +}; + +/* Samsung TBMV30111IN / TBMV30712IN1 + * used in Air2PC ATSC - 2nd generation (nxt2002) + */ +static struct dvb_pll_desc dvb_pll_samsung_tbmv = { + .name = "Samsung TBMV30111IN / TBMV30712IN1", + .min = 54000000, + .max = 860000000, + .iffreq= 44000000, + .count = 6, + .entries = { + { 172000000, 166667, 0xb4, 0x01 }, + { 214000000, 166667, 0xb4, 0x02 }, + { 467000000, 166667, 0xbc, 0x02 }, + { 721000000, 166667, 0xbc, 0x08 }, + { 841000000, 166667, 0xf4, 0x08 }, + { 999999999, 166667, 0xfc, 0x02 }, + } +}; + +/* + * Philips SD1878 Tuner. + */ +static struct dvb_pll_desc dvb_pll_philips_sd1878_tda8261 = { + .name = "Philips SD1878", + .min = 950000, + .max = 2150000, + .iffreq= 249, /* zero-IF, offset 249 is to round up */ + .count = 4, + .entries = { + { 1250000, 500, 0xc4, 0x00}, + { 1450000, 500, 0xc4, 0x40}, + { 2050000, 500, 0xc4, 0x80}, + { 2150000, 500, 0xc4, 0xc0}, + }, +}; + +static void opera1_bw(struct dvb_frontend *fe, u8 *buf) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_pll_priv *priv = fe->tuner_priv; + u32 b_w = (c->symbol_rate * 27) / 32000; + struct i2c_msg msg = { + .addr = priv->pll_i2c_address, + .flags = 0, + .buf = buf, + .len = 4 + }; + int result; + u8 lpf; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + result = i2c_transfer(priv->i2c, &msg, 1); + if (result != 1) + printk(KERN_ERR "%s: i2c_transfer failed:%d", + __func__, result); + + if (b_w <= 10000) + lpf = 0xc; + else if (b_w <= 12000) + lpf = 0x2; + else if (b_w <= 14000) + lpf = 0xa; + else if (b_w <= 16000) + lpf = 0x6; + else if (b_w <= 18000) + lpf = 0xe; + else if (b_w <= 20000) + lpf = 0x1; + else if (b_w <= 22000) + lpf = 0x9; + else if (b_w <= 24000) + lpf = 0x5; + else if (b_w <= 26000) + lpf = 0xd; + else if (b_w <= 28000) + lpf = 0x3; + else + lpf = 0xb; + buf[2] ^= 0x1c; /* Flip bits 3-5 */ + /* Set lpf */ + buf[2] |= ((lpf >> 2) & 0x3) << 3; + buf[3] |= (lpf & 0x3) << 2; + + return; +} + +static struct dvb_pll_desc dvb_pll_opera1 = { + .name = "Opera Tuner", + .min = 900000, + .max = 2250000, + .initdata = (u8[]){ 4, 0x08, 0xe5, 0xe1, 0x00 }, + .initdata2 = (u8[]){ 4, 0x08, 0xe5, 0xe5, 0x00 }, + .iffreq= 0, + .set = opera1_bw, + .count = 8, + .entries = { + { 1064000, 500, 0xf9, 0xc2 }, + { 1169000, 500, 0xf9, 0xe2 }, + { 1299000, 500, 0xf9, 0x20 }, + { 1444000, 500, 0xf9, 0x40 }, + { 1606000, 500, 0xf9, 0x60 }, + { 1777000, 500, 0xf9, 0x80 }, + { 1941000, 500, 0xf9, 0xa0 }, + { 2250000, 500, 0xf9, 0xc0 }, + } +}; + +static void samsung_dtos403ih102a_set(struct dvb_frontend *fe, u8 *buf) +{ + struct dvb_pll_priv *priv = fe->tuner_priv; + struct i2c_msg msg = { + .addr = priv->pll_i2c_address, + .flags = 0, + .buf = buf, + .len = 4 + }; + int result; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + result = i2c_transfer(priv->i2c, &msg, 1); + if (result != 1) + printk(KERN_ERR "%s: i2c_transfer failed:%d", + __func__, result); + + buf[2] = 0x9e; + buf[3] = 0x90; + + return; +} + +/* unknown pll used in Samsung DTOS403IH102A DVB-C tuner */ +static struct dvb_pll_desc dvb_pll_samsung_dtos403ih102a = { + .name = "Samsung DTOS403IH102A", + .min = 44250000, + .max = 858000000, + .iffreq = 36125000, + .count = 8, + .set = samsung_dtos403ih102a_set, + .entries = { + { 135000000, 62500, 0xbe, 0x01 }, + { 177000000, 62500, 0xf6, 0x01 }, + { 370000000, 62500, 0xbe, 0x02 }, + { 450000000, 62500, 0xf6, 0x02 }, + { 466000000, 62500, 0xfe, 0x02 }, + { 538000000, 62500, 0xbe, 0x08 }, + { 826000000, 62500, 0xf6, 0x08 }, + { 999999999, 62500, 0xfe, 0x08 }, + } +}; + +/* Samsung TDTC9251DH0 DVB-T NIM, as used on AirStar 2 */ +static struct dvb_pll_desc dvb_pll_samsung_tdtc9251dh0 = { + .name = "Samsung TDTC9251DH0", + .min = 48000000, + .max = 863000000, + .iffreq = 36166667, + .count = 3, + .entries = { + { 157500000, 166667, 0xcc, 0x09 }, + { 443000000, 166667, 0xcc, 0x0a }, + { 863000000, 166667, 0xcc, 0x08 }, + } +}; + +/* Samsung TBDU18132 DVB-S NIM with TSA5059 PLL, used in SkyStar2 DVB-S 2.3 */ +static struct dvb_pll_desc dvb_pll_samsung_tbdu18132 = { + .name = "Samsung TBDU18132", + .min = 950000, + .max = 2150000, /* guesses */ + .iffreq = 0, + .count = 2, + .entries = { + { 1550000, 125, 0x84, 0x82 }, + { 4095937, 125, 0x84, 0x80 }, + } + /* TSA5059 PLL has a 17 bit divisor rather than the 15 bits supported + * by this driver. The two extra bits are 0x60 in the third byte. 15 + * bits is enough for over 4 GHz, which is enough to cover the range + * of this tuner. We could use the additional divisor bits by adding + * more entries, e.g. + { 0x0ffff * 125 + 125/2, 125, 0x84 | 0x20, }, + { 0x17fff * 125 + 125/2, 125, 0x84 | 0x40, }, + { 0x1ffff * 125 + 125/2, 125, 0x84 | 0x60, }, */ +}; + +/* Samsung TBMU24112 DVB-S NIM with SL1935 zero-IF tuner */ +static struct dvb_pll_desc dvb_pll_samsung_tbmu24112 = { + .name = "Samsung TBMU24112", + .min = 950000, + .max = 2150000, /* guesses */ + .iffreq = 0, + .count = 2, + .entries = { + { 1500000, 125, 0x84, 0x18 }, + { 9999999, 125, 0x84, 0x08 }, + } +}; + +/* Alps TDEE4 DVB-C NIM, used on Cablestar 2 */ +/* byte 4 : 1 * * AGD R3 R2 R1 R0 + * byte 5 : C1 * RE RTS BS4 BS3 BS2 BS1 + * AGD = 1, R3 R2 R1 R0 = 0 1 0 1 => byte 4 = 1**10101 = 0x95 + * Range(MHz) C1 * RE RTS BS4 BS3 BS2 BS1 Byte 5 + * 47 - 153 0 * 0 0 0 0 0 1 0x01 + * 153 - 430 0 * 0 0 0 0 1 0 0x02 + * 430 - 822 0 * 0 0 1 0 0 0 0x08 + * 822 - 862 1 * 0 0 1 0 0 0 0x88 */ +static struct dvb_pll_desc dvb_pll_alps_tdee4 = { + .name = "ALPS TDEE4", + .min = 47000000, + .max = 862000000, + .iffreq = 36125000, + .count = 4, + .entries = { + { 153000000, 62500, 0x95, 0x01 }, + { 430000000, 62500, 0x95, 0x02 }, + { 822000000, 62500, 0x95, 0x08 }, + { 999999999, 62500, 0x95, 0x88 }, + } +}; + +/* ----------------------------------------------------------- */ + +static struct dvb_pll_desc *pll_list[] = { + [DVB_PLL_UNDEFINED] = NULL, + [DVB_PLL_THOMSON_DTT7579] = &dvb_pll_thomson_dtt7579, + [DVB_PLL_THOMSON_DTT759X] = &dvb_pll_thomson_dtt759x, + [DVB_PLL_THOMSON_DTT7520X] = &dvb_pll_thomson_dtt7520x, + [DVB_PLL_LG_Z201] = &dvb_pll_lg_z201, + [DVB_PLL_UNKNOWN_1] = &dvb_pll_unknown_1, + [DVB_PLL_TUA6010XS] = &dvb_pll_tua6010xs, + [DVB_PLL_ENV57H1XD5] = &dvb_pll_env57h1xd5, + [DVB_PLL_TUA6034] = &dvb_pll_tua6034, + [DVB_PLL_TDA665X] = &dvb_pll_tda665x, + [DVB_PLL_TDED4] = &dvb_pll_tded4, + [DVB_PLL_TDEE4] = &dvb_pll_alps_tdee4, + [DVB_PLL_TDHU2] = &dvb_pll_tdhu2, + [DVB_PLL_SAMSUNG_TBMV] = &dvb_pll_samsung_tbmv, + [DVB_PLL_PHILIPS_SD1878_TDA8261] = &dvb_pll_philips_sd1878_tda8261, + [DVB_PLL_OPERA1] = &dvb_pll_opera1, + [DVB_PLL_SAMSUNG_DTOS403IH102A] = &dvb_pll_samsung_dtos403ih102a, + [DVB_PLL_SAMSUNG_TDTC9251DH0] = &dvb_pll_samsung_tdtc9251dh0, + [DVB_PLL_SAMSUNG_TBDU18132] = &dvb_pll_samsung_tbdu18132, + [DVB_PLL_SAMSUNG_TBMU24112] = &dvb_pll_samsung_tbmu24112, +}; + +/* ----------------------------------------------------------- */ +/* code */ + +static int dvb_pll_configure(struct dvb_frontend *fe, u8 *buf, + const u32 frequency) +{ + struct dvb_pll_priv *priv = fe->tuner_priv; + struct dvb_pll_desc *desc = priv->pll_desc; + u32 div; + int i; + + if (frequency && (frequency < desc->min || frequency > desc->max)) + return -EINVAL; + + for (i = 0; i < desc->count; i++) { + if (frequency > desc->entries[i].limit) + continue; + break; + } + + if (debug) + printk("pll: %s: freq=%d | i=%d/%d\n", desc->name, + frequency, i, desc->count); + if (i == desc->count) + return -EINVAL; + + div = (frequency + desc->iffreq + + desc->entries[i].stepsize/2) / desc->entries[i].stepsize; + buf[0] = div >> 8; + buf[1] = div & 0xff; + buf[2] = desc->entries[i].config; + buf[3] = desc->entries[i].cb; + + if (desc->set) + desc->set(fe, buf); + + if (debug) + printk("pll: %s: div=%d | buf=0x%02x,0x%02x,0x%02x,0x%02x\n", + desc->name, div, buf[0], buf[1], buf[2], buf[3]); + + // calculate the frequency we set it to + return (div * desc->entries[i].stepsize) - desc->iffreq; +} + +static int dvb_pll_release(struct dvb_frontend *fe) +{ + kfree(fe->tuner_priv); + fe->tuner_priv = NULL; + return 0; +} + +static int dvb_pll_sleep(struct dvb_frontend *fe) +{ + struct dvb_pll_priv *priv = fe->tuner_priv; + + if (priv->i2c == NULL) + return -EINVAL; + + if (priv->pll_desc->sleepdata) { + struct i2c_msg msg = { .flags = 0, + .addr = priv->pll_i2c_address, + .buf = priv->pll_desc->sleepdata + 1, + .len = priv->pll_desc->sleepdata[0] }; + + int result; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if ((result = i2c_transfer(priv->i2c, &msg, 1)) != 1) { + return result; + } + return 0; + } + /* Shouldn't be called when initdata is NULL, maybe BUG()? */ + return -EINVAL; +} + +static int dvb_pll_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_pll_priv *priv = fe->tuner_priv; + u8 buf[4]; + struct i2c_msg msg = + { .addr = priv->pll_i2c_address, .flags = 0, + .buf = buf, .len = sizeof(buf) }; + int result; + u32 frequency = 0; + + if (priv->i2c == NULL) + return -EINVAL; + + result = dvb_pll_configure(fe, buf, c->frequency); + if (result < 0) + return result; + else + frequency = result; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if ((result = i2c_transfer(priv->i2c, &msg, 1)) != 1) { + return result; + } + + priv->frequency = frequency; + priv->bandwidth = c->bandwidth_hz; + + return 0; +} + +static int dvb_pll_calc_regs(struct dvb_frontend *fe, + u8 *buf, int buf_len) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_pll_priv *priv = fe->tuner_priv; + int result; + u32 frequency = 0; + + if (buf_len < 5) + return -EINVAL; + + result = dvb_pll_configure(fe, buf + 1, c->frequency); + if (result < 0) + return result; + else + frequency = result; + + buf[0] = priv->pll_i2c_address; + + priv->frequency = frequency; + priv->bandwidth = c->bandwidth_hz; + + return 5; +} + +static int dvb_pll_get_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct dvb_pll_priv *priv = fe->tuner_priv; + *frequency = priv->frequency; + return 0; +} + +static int dvb_pll_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) +{ + struct dvb_pll_priv *priv = fe->tuner_priv; + *bandwidth = priv->bandwidth; + return 0; +} + +static int dvb_pll_init(struct dvb_frontend *fe) +{ + struct dvb_pll_priv *priv = fe->tuner_priv; + + if (priv->i2c == NULL) + return -EINVAL; + + if (priv->pll_desc->initdata) { + struct i2c_msg msg = { .flags = 0, + .addr = priv->pll_i2c_address, + .buf = priv->pll_desc->initdata + 1, + .len = priv->pll_desc->initdata[0] }; + + int result; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + result = i2c_transfer(priv->i2c, &msg, 1); + if (result != 1) + return result; + if (priv->pll_desc->initdata2) { + msg.buf = priv->pll_desc->initdata2 + 1; + msg.len = priv->pll_desc->initdata2[0]; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + result = i2c_transfer(priv->i2c, &msg, 1); + if (result != 1) + return result; + } + return 0; + } + /* Shouldn't be called when initdata is NULL, maybe BUG()? */ + return -EINVAL; +} + +static struct dvb_tuner_ops dvb_pll_tuner_ops = { + .release = dvb_pll_release, + .sleep = dvb_pll_sleep, + .init = dvb_pll_init, + .set_params = dvb_pll_set_params, + .calc_regs = dvb_pll_calc_regs, + .get_frequency = dvb_pll_get_frequency, + .get_bandwidth = dvb_pll_get_bandwidth, +}; + +struct dvb_frontend *dvb_pll_attach(struct dvb_frontend *fe, int pll_addr, + struct i2c_adapter *i2c, + unsigned int pll_desc_id) +{ + u8 b1 [] = { 0 }; + struct i2c_msg msg = { .addr = pll_addr, .flags = I2C_M_RD, + .buf = b1, .len = 1 }; + struct dvb_pll_priv *priv = NULL; + int ret; + struct dvb_pll_desc *desc; + + if ((id[dvb_pll_devcount] > DVB_PLL_UNDEFINED) && + (id[dvb_pll_devcount] < ARRAY_SIZE(pll_list))) + pll_desc_id = id[dvb_pll_devcount]; + + BUG_ON(pll_desc_id < 1 || pll_desc_id >= ARRAY_SIZE(pll_list)); + + desc = pll_list[pll_desc_id]; + + if (i2c != NULL) { + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + ret = i2c_transfer (i2c, &msg, 1); + if (ret != 1) + return NULL; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + priv = kzalloc(sizeof(struct dvb_pll_priv), GFP_KERNEL); + if (priv == NULL) + return NULL; + + priv->pll_i2c_address = pll_addr; + priv->i2c = i2c; + priv->pll_desc = desc; + priv->nr = dvb_pll_devcount++; + + memcpy(&fe->ops.tuner_ops, &dvb_pll_tuner_ops, + sizeof(struct dvb_tuner_ops)); + + strncpy(fe->ops.tuner_ops.info.name, desc->name, + sizeof(fe->ops.tuner_ops.info.name)); + fe->ops.tuner_ops.info.frequency_min = desc->min; + fe->ops.tuner_ops.info.frequency_max = desc->max; + if (!desc->initdata) + fe->ops.tuner_ops.init = NULL; + if (!desc->sleepdata) + fe->ops.tuner_ops.sleep = NULL; + + fe->tuner_priv = priv; + + if ((debug) || (id[priv->nr] == pll_desc_id)) { + printk("dvb-pll[%d]", priv->nr); + if (i2c != NULL) + printk(" %d-%04x", i2c_adapter_id(i2c), pll_addr); + printk(": id# %d (%s) attached, %s\n", pll_desc_id, desc->name, + id[priv->nr] == pll_desc_id ? + "insmod option" : "autodetected"); + } + + return fe; +} +EXPORT_SYMBOL(dvb_pll_attach); + +MODULE_DESCRIPTION("dvb pll library"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/dvb-pll.h b/drivers/media/dvb-frontends/dvb-pll.h new file mode 100644 index 000000000000..4de754f76ce9 --- /dev/null +++ b/drivers/media/dvb-frontends/dvb-pll.h @@ -0,0 +1,57 @@ +/* + * descriptions + helper functions for simple dvb plls. + */ + +#ifndef __DVB_PLL_H__ +#define __DVB_PLL_H__ + +#include +#include "dvb_frontend.h" + +#define DVB_PLL_UNDEFINED 0 +#define DVB_PLL_THOMSON_DTT7579 1 +#define DVB_PLL_THOMSON_DTT759X 2 +#define DVB_PLL_LG_Z201 3 +#define DVB_PLL_UNKNOWN_1 4 +#define DVB_PLL_TUA6010XS 5 +#define DVB_PLL_ENV57H1XD5 6 +#define DVB_PLL_TUA6034 7 +#define DVB_PLL_TDA665X 8 +#define DVB_PLL_TDED4 9 +#define DVB_PLL_TDHU2 10 +#define DVB_PLL_SAMSUNG_TBMV 11 +#define DVB_PLL_PHILIPS_SD1878_TDA8261 12 +#define DVB_PLL_OPERA1 13 +#define DVB_PLL_SAMSUNG_DTOS403IH102A 14 +#define DVB_PLL_SAMSUNG_TDTC9251DH0 15 +#define DVB_PLL_SAMSUNG_TBDU18132 16 +#define DVB_PLL_SAMSUNG_TBMU24112 17 +#define DVB_PLL_TDEE4 18 +#define DVB_PLL_THOMSON_DTT7520X 19 + +/** + * Attach a dvb-pll to the supplied frontend structure. + * + * @param fe Frontend to attach to. + * @param pll_addr i2c address of the PLL (if used). + * @param i2c i2c adapter to use (set to NULL if not used). + * @param pll_desc_id dvb_pll_desc to use. + * @return Frontend pointer on success, NULL on failure + */ +#if defined(CONFIG_DVB_PLL) || (defined(CONFIG_DVB_PLL_MODULE) && defined(MODULE)) +extern struct dvb_frontend *dvb_pll_attach(struct dvb_frontend *fe, + int pll_addr, + struct i2c_adapter *i2c, + unsigned int pll_desc_id); +#else +static inline struct dvb_frontend *dvb_pll_attach(struct dvb_frontend *fe, + int pll_addr, + struct i2c_adapter *i2c, + unsigned int pll_desc_id) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif diff --git a/drivers/media/dvb/frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c similarity index 100% rename from drivers/media/dvb/frontends/dvb_dummy_fe.c rename to drivers/media/dvb-frontends/dvb_dummy_fe.c diff --git a/drivers/media/dvb/frontends/dvb_dummy_fe.h b/drivers/media/dvb-frontends/dvb_dummy_fe.h similarity index 100% rename from drivers/media/dvb/frontends/dvb_dummy_fe.h rename to drivers/media/dvb-frontends/dvb_dummy_fe.h diff --git a/drivers/media/dvb-frontends/ec100.c b/drivers/media/dvb-frontends/ec100.c new file mode 100644 index 000000000000..9d424809d06b --- /dev/null +++ b/drivers/media/dvb-frontends/ec100.c @@ -0,0 +1,345 @@ +/* + * E3C EC100 demodulator driver + * + * Copyright (C) 2009 Antti Palosaari + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "dvb_frontend.h" +#include "ec100.h" + +struct ec100_state { + struct i2c_adapter *i2c; + struct dvb_frontend frontend; + struct ec100_config config; + + u16 ber; +}; + +/* write single register */ +static int ec100_write_reg(struct ec100_state *state, u8 reg, u8 val) +{ + int ret; + u8 buf[2] = {reg, val}; + struct i2c_msg msg[1] = { + { + .addr = state->config.demod_address, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + } + }; + + ret = i2c_transfer(state->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&state->i2c->dev, "%s: i2c wr failed=%d reg=%02x\n", + KBUILD_MODNAME, ret, reg); + ret = -EREMOTEIO; + } + + return ret; +} + +/* read single register */ +static int ec100_read_reg(struct ec100_state *state, u8 reg, u8 *val) +{ + int ret; + struct i2c_msg msg[2] = { + { + .addr = state->config.demod_address, + .flags = 0, + .len = 1, + .buf = ® + }, { + .addr = state->config.demod_address, + .flags = I2C_M_RD, + .len = 1, + .buf = val + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + if (ret == 2) { + ret = 0; + } else { + dev_warn(&state->i2c->dev, "%s: i2c rd failed=%d reg=%02x\n", + KBUILD_MODNAME, ret, reg); + ret = -EREMOTEIO; + } + + return ret; +} + +static int ec100_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct ec100_state *state = fe->demodulator_priv; + int ret; + u8 tmp, tmp2; + + dev_dbg(&state->i2c->dev, "%s: frequency=%d bandwidth_hz=%d\n", + __func__, c->frequency, c->bandwidth_hz); + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + ret = ec100_write_reg(state, 0x04, 0x06); + if (ret) + goto error; + ret = ec100_write_reg(state, 0x67, 0x58); + if (ret) + goto error; + ret = ec100_write_reg(state, 0x05, 0x18); + if (ret) + goto error; + + /* reg/bw | 6 | 7 | 8 + -------+------+------+------ + A 0x1b | 0xa1 | 0xe7 | 0x2c + A 0x1c | 0x55 | 0x63 | 0x72 + -------+------+------+------ + B 0x1b | 0xb7 | 0x00 | 0x49 + B 0x1c | 0x55 | 0x64 | 0x72 */ + + switch (c->bandwidth_hz) { + case 6000000: + tmp = 0xb7; + tmp2 = 0x55; + break; + case 7000000: + tmp = 0x00; + tmp2 = 0x64; + break; + case 8000000: + default: + tmp = 0x49; + tmp2 = 0x72; + } + + ret = ec100_write_reg(state, 0x1b, tmp); + if (ret) + goto error; + ret = ec100_write_reg(state, 0x1c, tmp2); + if (ret) + goto error; + + ret = ec100_write_reg(state, 0x0c, 0xbb); /* if freq */ + if (ret) + goto error; + ret = ec100_write_reg(state, 0x0d, 0x31); /* if freq */ + if (ret) + goto error; + + ret = ec100_write_reg(state, 0x08, 0x24); + if (ret) + goto error; + + ret = ec100_write_reg(state, 0x00, 0x00); /* go */ + if (ret) + goto error; + ret = ec100_write_reg(state, 0x00, 0x20); /* go */ + if (ret) + goto error; + + return ret; +error: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int ec100_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *fesettings) +{ + fesettings->min_delay_ms = 300; + fesettings->step_size = 0; + fesettings->max_drift = 0; + + return 0; +} + +static int ec100_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct ec100_state *state = fe->demodulator_priv; + int ret; + u8 tmp; + *status = 0; + + ret = ec100_read_reg(state, 0x42, &tmp); + if (ret) + goto error; + + if (tmp & 0x80) { + /* bit7 set - have lock */ + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | + FE_HAS_SYNC | FE_HAS_LOCK; + } else { + ret = ec100_read_reg(state, 0x01, &tmp); + if (ret) + goto error; + + if (tmp & 0x10) { + /* bit4 set - have signal */ + *status |= FE_HAS_SIGNAL; + if (!(tmp & 0x01)) { + /* bit0 clear - have ~valid signal */ + *status |= FE_HAS_CARRIER | FE_HAS_VITERBI; + } + } + } + + return ret; +error: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int ec100_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct ec100_state *state = fe->demodulator_priv; + int ret; + u8 tmp, tmp2; + u16 ber2; + + *ber = 0; + + ret = ec100_read_reg(state, 0x65, &tmp); + if (ret) + goto error; + ret = ec100_read_reg(state, 0x66, &tmp2); + if (ret) + goto error; + + ber2 = (tmp2 << 8) | tmp; + + /* if counter overflow or clear */ + if (ber2 < state->ber) + *ber = ber2; + else + *ber = ber2 - state->ber; + + state->ber = ber2; + + return ret; +error: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int ec100_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct ec100_state *state = fe->demodulator_priv; + int ret; + u8 tmp; + + ret = ec100_read_reg(state, 0x24, &tmp); + if (ret) { + *strength = 0; + goto error; + } + + *strength = ((tmp << 8) | tmp); + + return ret; +error: + dev_dbg(&state->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int ec100_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + *snr = 0; + return 0; +} + +static int ec100_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + return 0; +} + +static void ec100_release(struct dvb_frontend *fe) +{ + struct ec100_state *state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops ec100_ops; + +struct dvb_frontend *ec100_attach(const struct ec100_config *config, + struct i2c_adapter *i2c) +{ + int ret; + struct ec100_state *state = NULL; + u8 tmp; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct ec100_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->i2c = i2c; + memcpy(&state->config, config, sizeof(struct ec100_config)); + + /* check if the demod is there */ + ret = ec100_read_reg(state, 0x33, &tmp); + if (ret || tmp != 0x0b) + goto error; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &ec100_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + + return &state->frontend; +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(ec100_attach); + +static struct dvb_frontend_ops ec100_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "E3C EC100 DVB-T", + .caps = + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | + FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_MUTE_TS + }, + + .release = ec100_release, + .set_frontend = ec100_set_frontend, + .get_tune_settings = ec100_get_tune_settings, + .read_status = ec100_read_status, + .read_ber = ec100_read_ber, + .read_signal_strength = ec100_read_signal_strength, + .read_snr = ec100_read_snr, + .read_ucblocks = ec100_read_ucblocks, +}; + +MODULE_AUTHOR("Antti Palosaari "); +MODULE_DESCRIPTION("E3C EC100 DVB-T demodulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/ec100.h b/drivers/media/dvb-frontends/ec100.h new file mode 100644 index 000000000000..b8479719d7f1 --- /dev/null +++ b/drivers/media/dvb-frontends/ec100.h @@ -0,0 +1,46 @@ +/* + * E3C EC100 demodulator driver + * + * Copyright (C) 2009 Antti Palosaari + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef EC100_H +#define EC100_H + +#include + +struct ec100_config { + /* demodulator's I2C address */ + u8 demod_address; +}; + + +#if defined(CONFIG_DVB_EC100) || \ + (defined(CONFIG_DVB_EC100_MODULE) && defined(MODULE)) +extern struct dvb_frontend *ec100_attach(const struct ec100_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *ec100_attach( + const struct ec100_config *config, struct i2c_adapter *i2c) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif /* EC100_H */ diff --git a/drivers/media/dvb/frontends/eds1547.h b/drivers/media/dvb-frontends/eds1547.h similarity index 100% rename from drivers/media/dvb/frontends/eds1547.h rename to drivers/media/dvb-frontends/eds1547.h diff --git a/drivers/media/dvb-frontends/hd29l2.c b/drivers/media/dvb-frontends/hd29l2.c new file mode 100644 index 000000000000..d7b9d549156d --- /dev/null +++ b/drivers/media/dvb-frontends/hd29l2.c @@ -0,0 +1,866 @@ +/* + * HDIC HD29L2 DMB-TH demodulator driver + * + * Copyright (C) 2011 Metropolia University of Applied Sciences, Electria R&D + * + * Author: Antti Palosaari + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "hd29l2_priv.h" + +/* write multiple registers */ +static int hd29l2_wr_regs(struct hd29l2_priv *priv, u8 reg, u8 *val, int len) +{ + int ret; + u8 buf[2 + len]; + struct i2c_msg msg[1] = { + { + .addr = priv->cfg.i2c_addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + } + }; + + buf[0] = 0x00; + buf[1] = reg; + memcpy(&buf[2], val, len); + + ret = i2c_transfer(priv->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, + "%s: i2c wr failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + + return ret; +} + +/* read multiple registers */ +static int hd29l2_rd_regs(struct hd29l2_priv *priv, u8 reg, u8 *val, int len) +{ + int ret; + u8 buf[2] = { 0x00, reg }; + struct i2c_msg msg[2] = { + { + .addr = priv->cfg.i2c_addr, + .flags = 0, + .len = 2, + .buf = buf, + }, { + .addr = priv->cfg.i2c_addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + } + }; + + ret = i2c_transfer(priv->i2c, msg, 2); + if (ret == 2) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, + "%s: i2c rd failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + + return ret; +} + +/* write single register */ +static int hd29l2_wr_reg(struct hd29l2_priv *priv, u8 reg, u8 val) +{ + return hd29l2_wr_regs(priv, reg, &val, 1); +} + +/* read single register */ +static int hd29l2_rd_reg(struct hd29l2_priv *priv, u8 reg, u8 *val) +{ + return hd29l2_rd_regs(priv, reg, val, 1); +} + +/* write single register with mask */ +static int hd29l2_wr_reg_mask(struct hd29l2_priv *priv, u8 reg, u8 val, u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = hd29l2_rd_regs(priv, reg, &tmp, 1); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return hd29l2_wr_regs(priv, reg, &val, 1); +} + +/* read single register with mask */ +int hd29l2_rd_reg_mask(struct hd29l2_priv *priv, u8 reg, u8 *val, u8 mask) +{ + int ret, i; + u8 tmp; + + ret = hd29l2_rd_regs(priv, reg, &tmp, 1); + if (ret) + return ret; + + tmp &= mask; + + /* find position of the first bit */ + for (i = 0; i < 8; i++) { + if ((mask >> i) & 0x01) + break; + } + *val = tmp >> i; + + return 0; +} + +static int hd29l2_soft_reset(struct hd29l2_priv *priv) +{ + int ret; + u8 tmp; + + ret = hd29l2_rd_reg(priv, 0x26, &tmp); + if (ret) + goto err; + + ret = hd29l2_wr_reg(priv, 0x26, 0x0d); + if (ret) + goto err; + + usleep_range(10000, 20000); + + ret = hd29l2_wr_reg(priv, 0x26, tmp); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int hd29l2_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + int ret, i; + struct hd29l2_priv *priv = fe->demodulator_priv; + u8 tmp; + + dev_dbg(&priv->i2c->dev, "%s: enable=%d\n", __func__, enable); + + /* set tuner address for demod */ + if (!priv->tuner_i2c_addr_programmed && enable) { + /* no need to set tuner address every time, once is enough */ + ret = hd29l2_wr_reg(priv, 0x9d, priv->cfg.tuner_i2c_addr << 1); + if (ret) + goto err; + + priv->tuner_i2c_addr_programmed = true; + } + + /* open / close gate */ + ret = hd29l2_wr_reg(priv, 0x9f, enable); + if (ret) + goto err; + + /* wait demod ready */ + for (i = 10; i; i--) { + ret = hd29l2_rd_reg(priv, 0x9e, &tmp); + if (ret) + goto err; + + if (tmp == enable) + break; + + usleep_range(5000, 10000); + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + return ret; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int hd29l2_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + int ret; + struct hd29l2_priv *priv = fe->demodulator_priv; + u8 buf[2]; + + *status = 0; + + ret = hd29l2_rd_reg(priv, 0x05, &buf[0]); + if (ret) + goto err; + + if (buf[0] & 0x01) { + /* full lock */ + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | + FE_HAS_SYNC | FE_HAS_LOCK; + } else { + ret = hd29l2_rd_reg(priv, 0x0d, &buf[1]); + if (ret) + goto err; + + if ((buf[1] & 0xfe) == 0x78) + /* partial lock */ + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC; + } + + priv->fe_status = *status; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int hd29l2_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + int ret; + struct hd29l2_priv *priv = fe->demodulator_priv; + u8 buf[2]; + u16 tmp; + + if (!(priv->fe_status & FE_HAS_LOCK)) { + *snr = 0; + ret = 0; + goto err; + } + + ret = hd29l2_rd_regs(priv, 0x0b, buf, 2); + if (ret) + goto err; + + tmp = (buf[0] << 8) | buf[1]; + + /* report SNR in dB * 10 */ + #define LOG10_20736_24 72422627 /* log10(20736) << 24 */ + if (tmp) + *snr = (LOG10_20736_24 - intlog10(tmp)) / ((1 << 24) / 100); + else + *snr = 0; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int hd29l2_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + int ret; + struct hd29l2_priv *priv = fe->demodulator_priv; + u8 buf[2]; + u16 tmp; + + *strength = 0; + + ret = hd29l2_rd_regs(priv, 0xd5, buf, 2); + if (ret) + goto err; + + tmp = buf[0] << 8 | buf[1]; + tmp = ~tmp & 0x0fff; + + /* scale value to 0x0000-0xffff from 0x0000-0x0fff */ + *strength = tmp * 0xffff / 0x0fff; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int hd29l2_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + int ret; + struct hd29l2_priv *priv = fe->demodulator_priv; + u8 buf[2]; + + if (!(priv->fe_status & FE_HAS_SYNC)) { + *ber = 0; + ret = 0; + goto err; + } + + ret = hd29l2_rd_regs(priv, 0xd9, buf, 2); + if (ret) { + *ber = 0; + goto err; + } + + /* LDPC BER */ + *ber = ((buf[0] & 0x0f) << 8) | buf[1]; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int hd29l2_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + /* no way to read? */ + *ucblocks = 0; + return 0; +} + +static enum dvbfe_search hd29l2_search(struct dvb_frontend *fe) +{ + int ret, i; + struct hd29l2_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u8 tmp, buf[3]; + u8 modulation, carrier, guard_interval, interleave, code_rate; + u64 num64; + u32 if_freq, if_ctl; + bool auto_mode; + + dev_dbg(&priv->i2c->dev, "%s: delivery_system=%d frequency=%d " \ + "bandwidth_hz=%d modulation=%d inversion=%d " \ + "fec_inner=%d guard_interval=%d\n", __func__, + c->delivery_system, c->frequency, c->bandwidth_hz, + c->modulation, c->inversion, c->fec_inner, + c->guard_interval); + + /* as for now we detect always params automatically */ + auto_mode = true; + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + /* get and program IF */ + if (fe->ops.tuner_ops.get_if_frequency) + fe->ops.tuner_ops.get_if_frequency(fe, &if_freq); + else + if_freq = 0; + + if (if_freq) { + /* normal IF */ + + /* calc IF control value */ + num64 = if_freq; + num64 *= 0x800000; + num64 = div_u64(num64, HD29L2_XTAL); + num64 -= 0x800000; + if_ctl = num64; + + tmp = 0xfc; /* tuner type normal */ + } else { + /* zero IF */ + if_ctl = 0; + tmp = 0xfe; /* tuner type Zero-IF */ + } + + buf[0] = ((if_ctl >> 0) & 0xff); + buf[1] = ((if_ctl >> 8) & 0xff); + buf[2] = ((if_ctl >> 16) & 0xff); + + /* program IF control */ + ret = hd29l2_wr_regs(priv, 0x14, buf, 3); + if (ret) + goto err; + + /* program tuner type */ + ret = hd29l2_wr_reg(priv, 0xab, tmp); + if (ret) + goto err; + + dev_dbg(&priv->i2c->dev, "%s: if_freq=%d if_ctl=%x\n", + __func__, if_freq, if_ctl); + + if (auto_mode) { + /* + * use auto mode + */ + + /* disable quick mode */ + ret = hd29l2_wr_reg_mask(priv, 0xac, 0 << 7, 0x80); + if (ret) + goto err; + + ret = hd29l2_wr_reg_mask(priv, 0x82, 1 << 1, 0x02); + if (ret) + goto err; + + /* enable auto mode */ + ret = hd29l2_wr_reg_mask(priv, 0x7d, 1 << 6, 0x40); + if (ret) + goto err; + + ret = hd29l2_wr_reg_mask(priv, 0x81, 1 << 3, 0x08); + if (ret) + goto err; + + /* soft reset */ + ret = hd29l2_soft_reset(priv); + if (ret) + goto err; + + /* detect modulation */ + for (i = 30; i; i--) { + msleep(100); + + ret = hd29l2_rd_reg(priv, 0x0d, &tmp); + if (ret) + goto err; + + if ((((tmp & 0xf0) >= 0x10) && + ((tmp & 0x0f) == 0x08)) || (tmp >= 0x2c)) + break; + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + if (i == 0) + /* detection failed */ + return DVBFE_ALGO_SEARCH_FAILED; + + /* read modulation */ + ret = hd29l2_rd_reg_mask(priv, 0x7d, &modulation, 0x07); + if (ret) + goto err; + } else { + /* + * use manual mode + */ + + modulation = HD29L2_QAM64; + carrier = HD29L2_CARRIER_MULTI; + guard_interval = HD29L2_PN945; + interleave = HD29L2_INTERLEAVER_420; + code_rate = HD29L2_CODE_RATE_08; + + tmp = (code_rate << 3) | modulation; + ret = hd29l2_wr_reg_mask(priv, 0x7d, tmp, 0x5f); + if (ret) + goto err; + + tmp = (carrier << 2) | guard_interval; + ret = hd29l2_wr_reg_mask(priv, 0x81, tmp, 0x0f); + if (ret) + goto err; + + tmp = interleave; + ret = hd29l2_wr_reg_mask(priv, 0x82, tmp, 0x03); + if (ret) + goto err; + } + + /* ensure modulation validy */ + /* 0=QAM4_NR, 1=QAM4, 2=QAM16, 3=QAM32, 4=QAM64 */ + if (modulation > (ARRAY_SIZE(reg_mod_vals_tab[0].val) - 1)) { + dev_dbg(&priv->i2c->dev, "%s: modulation=%d not valid\n", + __func__, modulation); + goto err; + } + + /* program registers according to modulation */ + for (i = 0; i < ARRAY_SIZE(reg_mod_vals_tab); i++) { + ret = hd29l2_wr_reg(priv, reg_mod_vals_tab[i].reg, + reg_mod_vals_tab[i].val[modulation]); + if (ret) + goto err; + } + + /* read guard interval */ + ret = hd29l2_rd_reg_mask(priv, 0x81, &guard_interval, 0x03); + if (ret) + goto err; + + /* read carrier mode */ + ret = hd29l2_rd_reg_mask(priv, 0x81, &carrier, 0x04); + if (ret) + goto err; + + dev_dbg(&priv->i2c->dev, + "%s: modulation=%d guard_interval=%d carrier=%d\n", + __func__, modulation, guard_interval, carrier); + + if ((carrier == HD29L2_CARRIER_MULTI) && (modulation == HD29L2_QAM64) && + (guard_interval == HD29L2_PN945)) { + dev_dbg(&priv->i2c->dev, "%s: C=3780 && QAM64 && PN945\n", + __func__); + + ret = hd29l2_wr_reg(priv, 0x42, 0x33); + if (ret) + goto err; + + ret = hd29l2_wr_reg(priv, 0xdd, 0x01); + if (ret) + goto err; + } + + usleep_range(10000, 20000); + + /* soft reset */ + ret = hd29l2_soft_reset(priv); + if (ret) + goto err; + + /* wait demod lock */ + for (i = 30; i; i--) { + msleep(100); + + /* read lock bit */ + ret = hd29l2_rd_reg_mask(priv, 0x05, &tmp, 0x01); + if (ret) + goto err; + + if (tmp) + break; + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + if (i == 0) + return DVBFE_ALGO_SEARCH_AGAIN; + + return DVBFE_ALGO_SEARCH_SUCCESS; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return DVBFE_ALGO_SEARCH_ERROR; +} + +static int hd29l2_get_frontend_algo(struct dvb_frontend *fe) +{ + return DVBFE_ALGO_CUSTOM; +} + +static int hd29l2_get_frontend(struct dvb_frontend *fe) +{ + int ret; + struct hd29l2_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u8 buf[3]; + u32 if_ctl; + char *str_constellation, *str_code_rate, *str_constellation_code_rate, + *str_guard_interval, *str_carrier, *str_guard_interval_carrier, + *str_interleave, *str_interleave_; + + ret = hd29l2_rd_reg(priv, 0x7d, &buf[0]); + if (ret) + goto err; + + ret = hd29l2_rd_regs(priv, 0x81, &buf[1], 2); + if (ret) + goto err; + + /* constellation, 0x7d[2:0] */ + switch ((buf[0] >> 0) & 0x07) { + case 0: /* QAM4NR */ + str_constellation = "QAM4NR"; + c->modulation = QAM_AUTO; /* FIXME */ + break; + case 1: /* QAM4 */ + str_constellation = "QAM4"; + c->modulation = QPSK; /* FIXME */ + break; + case 2: + str_constellation = "QAM16"; + c->modulation = QAM_16; + break; + case 3: + str_constellation = "QAM32"; + c->modulation = QAM_32; + break; + case 4: + str_constellation = "QAM64"; + c->modulation = QAM_64; + break; + default: + str_constellation = "?"; + } + + /* LDPC code rate, 0x7d[4:3] */ + switch ((buf[0] >> 3) & 0x03) { + case 0: /* 0.4 */ + str_code_rate = "0.4"; + c->fec_inner = FEC_AUTO; /* FIXME */ + break; + case 1: /* 0.6 */ + str_code_rate = "0.6"; + c->fec_inner = FEC_3_5; + break; + case 2: /* 0.8 */ + str_code_rate = "0.8"; + c->fec_inner = FEC_4_5; + break; + default: + str_code_rate = "?"; + } + + /* constellation & code rate set, 0x7d[6] */ + switch ((buf[0] >> 6) & 0x01) { + case 0: + str_constellation_code_rate = "manual"; + break; + case 1: + str_constellation_code_rate = "auto"; + break; + default: + str_constellation_code_rate = "?"; + } + + /* frame header, 0x81[1:0] */ + switch ((buf[1] >> 0) & 0x03) { + case 0: /* PN945 */ + str_guard_interval = "PN945"; + c->guard_interval = GUARD_INTERVAL_AUTO; /* FIXME */ + break; + case 1: /* PN595 */ + str_guard_interval = "PN595"; + c->guard_interval = GUARD_INTERVAL_AUTO; /* FIXME */ + break; + case 2: /* PN420 */ + str_guard_interval = "PN420"; + c->guard_interval = GUARD_INTERVAL_AUTO; /* FIXME */ + break; + default: + str_guard_interval = "?"; + } + + /* carrier, 0x81[2] */ + switch ((buf[1] >> 2) & 0x01) { + case 0: + str_carrier = "C=1"; + break; + case 1: + str_carrier = "C=3780"; + break; + default: + str_carrier = "?"; + } + + /* frame header & carrier set, 0x81[3] */ + switch ((buf[1] >> 3) & 0x01) { + case 0: + str_guard_interval_carrier = "manual"; + break; + case 1: + str_guard_interval_carrier = "auto"; + break; + default: + str_guard_interval_carrier = "?"; + } + + /* interleave, 0x82[0] */ + switch ((buf[2] >> 0) & 0x01) { + case 0: + str_interleave = "M=720"; + break; + case 1: + str_interleave = "M=240"; + break; + default: + str_interleave = "?"; + } + + /* interleave set, 0x82[1] */ + switch ((buf[2] >> 1) & 0x01) { + case 0: + str_interleave_ = "manual"; + break; + case 1: + str_interleave_ = "auto"; + break; + default: + str_interleave_ = "?"; + } + + /* + * We can read out current detected NCO and use that value next + * time instead of calculating new value from targed IF. + * I think it will not effect receiver sensitivity but gaining lock + * after tune could be easier... + */ + ret = hd29l2_rd_regs(priv, 0xb1, &buf[0], 3); + if (ret) + goto err; + + if_ctl = (buf[0] << 16) | ((buf[1] - 7) << 8) | buf[2]; + + dev_dbg(&priv->i2c->dev, "%s: %s %s %s | %s %s %s | %s %s | NCO=%06x\n", + __func__, str_constellation, str_code_rate, + str_constellation_code_rate, str_guard_interval, + str_carrier, str_guard_interval_carrier, str_interleave, + str_interleave_, if_ctl); + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int hd29l2_init(struct dvb_frontend *fe) +{ + int ret, i; + struct hd29l2_priv *priv = fe->demodulator_priv; + u8 tmp; + static const struct reg_val tab[] = { + { 0x3a, 0x06 }, + { 0x3b, 0x03 }, + { 0x3c, 0x04 }, + { 0xaf, 0x06 }, + { 0xb0, 0x1b }, + { 0x80, 0x64 }, + { 0x10, 0x38 }, + }; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + /* reset demod */ + /* it is recommended to HW reset chip using RST_N pin */ + if (fe->callback) { + ret = fe->callback(fe, DVB_FRONTEND_COMPONENT_DEMOD, 0, 0); + if (ret) + goto err; + + /* reprogramming needed because HW reset clears registers */ + priv->tuner_i2c_addr_programmed = false; + } + + /* init */ + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = hd29l2_wr_reg(priv, tab[i].reg, tab[i].val); + if (ret) + goto err; + } + + /* TS params */ + ret = hd29l2_rd_reg(priv, 0x36, &tmp); + if (ret) + goto err; + + tmp &= 0x1b; + tmp |= priv->cfg.ts_mode; + ret = hd29l2_wr_reg(priv, 0x36, tmp); + if (ret) + goto err; + + ret = hd29l2_rd_reg(priv, 0x31, &tmp); + tmp &= 0xef; + + if (!(priv->cfg.ts_mode >> 7)) + /* set b4 for serial TS */ + tmp |= 0x10; + + ret = hd29l2_wr_reg(priv, 0x31, tmp); + if (ret) + goto err; + + return ret; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static void hd29l2_release(struct dvb_frontend *fe) +{ + struct hd29l2_priv *priv = fe->demodulator_priv; + kfree(priv); +} + +static struct dvb_frontend_ops hd29l2_ops; + +struct dvb_frontend *hd29l2_attach(const struct hd29l2_config *config, + struct i2c_adapter *i2c) +{ + int ret; + struct hd29l2_priv *priv = NULL; + u8 tmp; + + /* allocate memory for the internal state */ + priv = kzalloc(sizeof(struct hd29l2_priv), GFP_KERNEL); + if (priv == NULL) + goto err; + + /* setup the state */ + priv->i2c = i2c; + memcpy(&priv->cfg, config, sizeof(struct hd29l2_config)); + + + /* check if the demod is there */ + ret = hd29l2_rd_reg(priv, 0x00, &tmp); + if (ret) + goto err; + + /* create dvb_frontend */ + memcpy(&priv->fe.ops, &hd29l2_ops, sizeof(struct dvb_frontend_ops)); + priv->fe.demodulator_priv = priv; + + return &priv->fe; +err: + kfree(priv); + return NULL; +} +EXPORT_SYMBOL(hd29l2_attach); + +static struct dvb_frontend_ops hd29l2_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "HDIC HD29L2 DMB-TH", + .frequency_min = 474000000, + .frequency_max = 858000000, + .frequency_stepsize = 10000, + .caps = FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_BANDWIDTH_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_RECOVER + }, + + .release = hd29l2_release, + + .init = hd29l2_init, + + .get_frontend_algo = hd29l2_get_frontend_algo, + .search = hd29l2_search, + .get_frontend = hd29l2_get_frontend, + + .read_status = hd29l2_read_status, + .read_snr = hd29l2_read_snr, + .read_signal_strength = hd29l2_read_signal_strength, + .read_ber = hd29l2_read_ber, + .read_ucblocks = hd29l2_read_ucblocks, + + .i2c_gate_ctrl = hd29l2_i2c_gate_ctrl, +}; + +MODULE_AUTHOR("Antti Palosaari "); +MODULE_DESCRIPTION("HDIC HD29L2 DMB-TH demodulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/hd29l2.h b/drivers/media/dvb-frontends/hd29l2.h new file mode 100644 index 000000000000..4ad00d79aa77 --- /dev/null +++ b/drivers/media/dvb-frontends/hd29l2.h @@ -0,0 +1,66 @@ +/* + * HDIC HD29L2 DMB-TH demodulator driver + * + * Copyright (C) 2011 Metropolia University of Applied Sciences, Electria R&D + * + * Author: Antti Palosaari + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef HD29L2_H +#define HD29L2_H + +#include + +struct hd29l2_config { + /* + * demodulator I2C address + */ + u8 i2c_addr; + + /* + * tuner I2C address + * only needed when tuner is behind demod I2C-gate + */ + u8 tuner_i2c_addr; + + /* + * TS settings + */ +#define HD29L2_TS_SERIAL 0x00 +#define HD29L2_TS_PARALLEL 0x80 +#define HD29L2_TS_CLK_NORMAL 0x40 +#define HD29L2_TS_CLK_INVERTED 0x00 +#define HD29L2_TS_CLK_GATED 0x20 +#define HD29L2_TS_CLK_FREE 0x00 + u8 ts_mode; +}; + + +#if defined(CONFIG_DVB_HD29L2) || \ + (defined(CONFIG_DVB_HD29L2_MODULE) && defined(MODULE)) +extern struct dvb_frontend *hd29l2_attach(const struct hd29l2_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *hd29l2_attach( +const struct hd29l2_config *config, struct i2c_adapter *i2c) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif /* HD29L2_H */ diff --git a/drivers/media/dvb-frontends/hd29l2_priv.h b/drivers/media/dvb-frontends/hd29l2_priv.h new file mode 100644 index 000000000000..4d571a2282d4 --- /dev/null +++ b/drivers/media/dvb-frontends/hd29l2_priv.h @@ -0,0 +1,301 @@ +/* + * HDIC HD29L2 DMB-TH demodulator driver + * + * Copyright (C) 2011 Metropolia University of Applied Sciences, Electria R&D + * + * Author: Antti Palosaari + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef HD29L2_PRIV +#define HD29L2_PRIV + +#include +#include "dvb_frontend.h" +#include "dvb_math.h" +#include "hd29l2.h" + +#define HD29L2_XTAL 30400000 /* Hz */ + + +#define HD29L2_QAM4NR 0x00 +#define HD29L2_QAM4 0x01 +#define HD29L2_QAM16 0x02 +#define HD29L2_QAM32 0x03 +#define HD29L2_QAM64 0x04 + +#define HD29L2_CODE_RATE_04 0x00 +#define HD29L2_CODE_RATE_06 0x08 +#define HD29L2_CODE_RATE_08 0x10 + +#define HD29L2_PN945 0x00 +#define HD29L2_PN595 0x01 +#define HD29L2_PN420 0x02 + +#define HD29L2_CARRIER_SINGLE 0x00 +#define HD29L2_CARRIER_MULTI 0x01 + +#define HD29L2_INTERLEAVER_720 0x00 +#define HD29L2_INTERLEAVER_420 0x01 + +struct reg_val { + u8 reg; + u8 val; +}; + +struct reg_mod_vals { + u8 reg; + u8 val[5]; +}; + +struct hd29l2_priv { + struct i2c_adapter *i2c; + struct dvb_frontend fe; + struct hd29l2_config cfg; + u8 tuner_i2c_addr_programmed:1; + + fe_status_t fe_status; +}; + +static const struct reg_mod_vals reg_mod_vals_tab[] = { + /* REG, QAM4NR, QAM4,QAM16,QAM32,QAM64 */ + { 0x01, { 0x10, 0x10, 0x10, 0x10, 0x10 } }, + { 0x02, { 0x07, 0x07, 0x07, 0x07, 0x07 } }, + { 0x03, { 0x10, 0x10, 0x10, 0x10, 0x10 } }, + { 0x04, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x05, { 0x61, 0x60, 0x60, 0x61, 0x60 } }, + { 0x06, { 0xff, 0xff, 0xff, 0xff, 0xff } }, + { 0x07, { 0xff, 0xff, 0xff, 0xff, 0xff } }, + { 0x08, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x09, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x0a, { 0x15, 0x15, 0x03, 0x03, 0x03 } }, + { 0x0d, { 0x78, 0x78, 0x88, 0x78, 0x78 } }, + { 0x0e, { 0xa0, 0x90, 0xa0, 0xa0, 0xa0 } }, + { 0x0f, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x10, { 0xa0, 0xa0, 0x58, 0x38, 0x38 } }, + { 0x11, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x12, { 0x5a, 0x5a, 0x5a, 0x5a, 0x5a } }, + { 0x13, { 0xa2, 0xa2, 0xa2, 0xa2, 0xa2 } }, + { 0x17, { 0x40, 0x40, 0x40, 0x40, 0x40 } }, + { 0x18, { 0x21, 0x21, 0x42, 0x52, 0x42 } }, + { 0x19, { 0x21, 0x21, 0x62, 0x72, 0x62 } }, + { 0x1a, { 0x32, 0x43, 0xa9, 0xb9, 0xa9 } }, + { 0x1b, { 0x32, 0x43, 0xb9, 0xd8, 0xb9 } }, + { 0x1c, { 0x02, 0x02, 0x03, 0x02, 0x03 } }, + { 0x1d, { 0x0c, 0x0c, 0x01, 0x02, 0x02 } }, + { 0x1e, { 0x02, 0x02, 0x02, 0x01, 0x02 } }, + { 0x1f, { 0x02, 0x02, 0x01, 0x02, 0x04 } }, + { 0x20, { 0x01, 0x02, 0x01, 0x01, 0x01 } }, + { 0x21, { 0x08, 0x08, 0x0a, 0x0a, 0x0a } }, + { 0x22, { 0x06, 0x06, 0x04, 0x05, 0x05 } }, + { 0x23, { 0x06, 0x06, 0x05, 0x03, 0x05 } }, + { 0x24, { 0x08, 0x08, 0x05, 0x07, 0x07 } }, + { 0x25, { 0x16, 0x10, 0x10, 0x0a, 0x10 } }, + { 0x26, { 0x14, 0x14, 0x04, 0x04, 0x04 } }, + { 0x27, { 0x58, 0x58, 0x58, 0x5c, 0x58 } }, + { 0x28, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, + { 0x29, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, + { 0x2a, { 0x08, 0x0a, 0x08, 0x08, 0x08 } }, + { 0x2b, { 0x08, 0x08, 0x08, 0x08, 0x08 } }, + { 0x2c, { 0x06, 0x06, 0x06, 0x06, 0x06 } }, + { 0x2d, { 0x05, 0x06, 0x06, 0x06, 0x06 } }, + { 0x2e, { 0x21, 0x21, 0x21, 0x21, 0x21 } }, + { 0x2f, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x30, { 0x14, 0x14, 0x14, 0x14, 0x14 } }, + { 0x33, { 0xb7, 0xb7, 0xb7, 0xb7, 0xb7 } }, + { 0x34, { 0x81, 0x81, 0x81, 0x81, 0x81 } }, + { 0x35, { 0x80, 0x80, 0x80, 0x80, 0x80 } }, + { 0x37, { 0x70, 0x70, 0x70, 0x70, 0x70 } }, + { 0x38, { 0x04, 0x04, 0x02, 0x02, 0x02 } }, + { 0x39, { 0x07, 0x07, 0x05, 0x05, 0x05 } }, + { 0x3a, { 0x06, 0x06, 0x06, 0x06, 0x06 } }, + { 0x3b, { 0x03, 0x03, 0x03, 0x03, 0x03 } }, + { 0x3c, { 0x07, 0x06, 0x04, 0x04, 0x04 } }, + { 0x3d, { 0xf0, 0xf0, 0xf0, 0xf0, 0x80 } }, + { 0x3e, { 0x60, 0x60, 0x60, 0x60, 0xff } }, + { 0x3f, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x40, { 0x5b, 0x5b, 0x5b, 0x57, 0x50 } }, + { 0x41, { 0x30, 0x30, 0x30, 0x30, 0x18 } }, + { 0x42, { 0x20, 0x20, 0x20, 0x00, 0x30 } }, + { 0x43, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x44, { 0x3f, 0x3f, 0x3f, 0x3f, 0x3f } }, + { 0x45, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x46, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, + { 0x47, { 0x00, 0x00, 0x95, 0x00, 0x95 } }, + { 0x48, { 0xc0, 0xc0, 0xc0, 0xc0, 0xc0 } }, + { 0x49, { 0xc0, 0xc0, 0xc0, 0xc0, 0xc0 } }, + { 0x4a, { 0x40, 0x40, 0x33, 0x11, 0x11 } }, + { 0x4b, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, + { 0x4c, { 0x40, 0x40, 0x99, 0x11, 0x11 } }, + { 0x4d, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, + { 0x4e, { 0x40, 0x40, 0x66, 0x77, 0x77 } }, + { 0x4f, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, + { 0x50, { 0x40, 0x40, 0x88, 0x33, 0x11 } }, + { 0x51, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, + { 0x52, { 0x40, 0x40, 0x88, 0x02, 0x02 } }, + { 0x53, { 0x40, 0x40, 0x00, 0x02, 0x02 } }, + { 0x54, { 0x00, 0x00, 0x88, 0x33, 0x33 } }, + { 0x55, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, + { 0x56, { 0x00, 0x00, 0x00, 0x0b, 0x00 } }, + { 0x57, { 0x40, 0x40, 0x0a, 0x0b, 0x0a } }, + { 0x58, { 0xaa, 0x00, 0x00, 0x00, 0x00 } }, + { 0x59, { 0x7a, 0x40, 0x02, 0x02, 0x02 } }, + { 0x5a, { 0x18, 0x18, 0x01, 0x01, 0x01 } }, + { 0x5b, { 0x18, 0x18, 0x01, 0x01, 0x01 } }, + { 0x5c, { 0x18, 0x18, 0x01, 0x01, 0x01 } }, + { 0x5d, { 0x18, 0x18, 0x01, 0x01, 0x01 } }, + { 0x5e, { 0xc0, 0xc0, 0xc0, 0xff, 0xc0 } }, + { 0x5f, { 0xc0, 0xc0, 0xc0, 0xff, 0xc0 } }, + { 0x60, { 0x40, 0x40, 0x00, 0x30, 0x30 } }, + { 0x61, { 0x40, 0x40, 0x10, 0x30, 0x30 } }, + { 0x62, { 0x40, 0x40, 0x00, 0x30, 0x30 } }, + { 0x63, { 0x40, 0x40, 0x05, 0x30, 0x30 } }, + { 0x64, { 0x40, 0x40, 0x06, 0x00, 0x30 } }, + { 0x65, { 0x40, 0x40, 0x06, 0x08, 0x30 } }, + { 0x66, { 0x40, 0x40, 0x00, 0x00, 0x20 } }, + { 0x67, { 0x40, 0x40, 0x01, 0x04, 0x20 } }, + { 0x68, { 0x00, 0x00, 0x30, 0x00, 0x20 } }, + { 0x69, { 0xa0, 0xa0, 0x00, 0x08, 0x20 } }, + { 0x6a, { 0x00, 0x00, 0x30, 0x00, 0x25 } }, + { 0x6b, { 0xa0, 0xa0, 0x00, 0x06, 0x25 } }, + { 0x6c, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x6d, { 0xa0, 0x60, 0x0c, 0x03, 0x0c } }, + { 0x6e, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x6f, { 0xa0, 0x60, 0x04, 0x01, 0x04 } }, + { 0x70, { 0x58, 0x58, 0xaa, 0xaa, 0xaa } }, + { 0x71, { 0x58, 0x58, 0xaa, 0xaa, 0xaa } }, + { 0x72, { 0x58, 0x58, 0xff, 0xff, 0xff } }, + { 0x73, { 0x58, 0x58, 0xff, 0xff, 0xff } }, + { 0x74, { 0x06, 0x06, 0x09, 0x05, 0x05 } }, + { 0x75, { 0x06, 0x06, 0x0a, 0x10, 0x10 } }, + { 0x76, { 0x10, 0x10, 0x06, 0x0a, 0x0a } }, + { 0x77, { 0x12, 0x18, 0x28, 0x10, 0x28 } }, + { 0x78, { 0xf8, 0xf8, 0xf8, 0xf8, 0xf8 } }, + { 0x79, { 0x15, 0x15, 0x03, 0x03, 0x03 } }, + { 0x7a, { 0x02, 0x02, 0x01, 0x04, 0x03 } }, + { 0x7b, { 0x01, 0x02, 0x03, 0x03, 0x03 } }, + { 0x7c, { 0x28, 0x28, 0x28, 0x28, 0x28 } }, + { 0x7f, { 0x25, 0x92, 0x5f, 0x17, 0x2d } }, + { 0x80, { 0x64, 0x64, 0x64, 0x74, 0x64 } }, + { 0x83, { 0x06, 0x03, 0x04, 0x04, 0x04 } }, + { 0x84, { 0xff, 0xff, 0xff, 0xff, 0xff } }, + { 0x85, { 0x05, 0x05, 0x05, 0x05, 0x05 } }, + { 0x86, { 0x00, 0x00, 0x11, 0x11, 0x11 } }, + { 0x87, { 0x03, 0x03, 0x03, 0x03, 0x03 } }, + { 0x88, { 0x09, 0x09, 0x09, 0x09, 0x09 } }, + { 0x89, { 0x20, 0x20, 0x30, 0x20, 0x20 } }, + { 0x8a, { 0x03, 0x03, 0x02, 0x03, 0x02 } }, + { 0x8b, { 0x00, 0x07, 0x09, 0x00, 0x09 } }, + { 0x8c, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x8d, { 0x4f, 0x4f, 0x4f, 0x3f, 0x4f } }, + { 0x8e, { 0xf0, 0xf0, 0x60, 0xf0, 0xa0 } }, + { 0x8f, { 0xe8, 0xe8, 0xe8, 0xe8, 0xe8 } }, + { 0x90, { 0x10, 0x10, 0x10, 0x10, 0x10 } }, + { 0x91, { 0x40, 0x40, 0x70, 0x70, 0x10 } }, + { 0x92, { 0x00, 0x00, 0x00, 0x00, 0x04 } }, + { 0x93, { 0x60, 0x60, 0x60, 0x60, 0x60 } }, + { 0x94, { 0x00, 0x00, 0x00, 0x00, 0x03 } }, + { 0x95, { 0x09, 0x09, 0x47, 0x47, 0x47 } }, + { 0x96, { 0x80, 0xa0, 0xa0, 0x40, 0xa0 } }, + { 0x97, { 0x60, 0x60, 0x60, 0x60, 0x60 } }, + { 0x98, { 0x50, 0x50, 0x50, 0x30, 0x50 } }, + { 0x99, { 0x10, 0x10, 0x10, 0x10, 0x10 } }, + { 0x9a, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x9b, { 0x40, 0x40, 0x40, 0x30, 0x40 } }, + { 0x9c, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xa0, { 0xf0, 0xf0, 0xf0, 0xf0, 0xf0 } }, + { 0xa1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xa2, { 0x30, 0x30, 0x00, 0x30, 0x00 } }, + { 0xa3, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xa4, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xa5, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xa6, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xa7, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xa8, { 0x77, 0x77, 0x77, 0x77, 0x77 } }, + { 0xa9, { 0x02, 0x02, 0x02, 0x02, 0x02 } }, + { 0xaa, { 0x40, 0x40, 0x40, 0x40, 0x40 } }, + { 0xac, { 0x1f, 0x1f, 0x1f, 0x1f, 0x1f } }, + { 0xad, { 0x14, 0x14, 0x14, 0x14, 0x14 } }, + { 0xae, { 0x78, 0x78, 0x78, 0x78, 0x78 } }, + { 0xaf, { 0x06, 0x06, 0x06, 0x06, 0x07 } }, + { 0xb0, { 0x1b, 0x1b, 0x1b, 0x19, 0x1b } }, + { 0xb1, { 0x18, 0x17, 0x17, 0x18, 0x17 } }, + { 0xb2, { 0x35, 0x82, 0x82, 0x38, 0x82 } }, + { 0xb3, { 0xb6, 0xce, 0xc7, 0x5c, 0xb0 } }, + { 0xb4, { 0x3f, 0x3e, 0x3e, 0x3f, 0x3e } }, + { 0xb5, { 0x70, 0x58, 0x50, 0x68, 0x50 } }, + { 0xb6, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xb7, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xb8, { 0x03, 0x03, 0x01, 0x01, 0x01 } }, + { 0xb9, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xba, { 0x06, 0x06, 0x0a, 0x05, 0x0a } }, + { 0xbb, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xbc, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xbd, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xbe, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xbf, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xc0, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xc1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xc2, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xc3, { 0x00, 0x00, 0x88, 0x66, 0x88 } }, + { 0xc4, { 0x10, 0x10, 0x00, 0x00, 0x00 } }, + { 0xc5, { 0x00, 0x00, 0x44, 0x60, 0x44 } }, + { 0xc6, { 0x10, 0x0a, 0x00, 0x00, 0x00 } }, + { 0xc7, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xc8, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xc9, { 0x90, 0x04, 0x00, 0x00, 0x00 } }, + { 0xca, { 0x90, 0x08, 0x01, 0x01, 0x01 } }, + { 0xcb, { 0xa0, 0x04, 0x00, 0x44, 0x00 } }, + { 0xcc, { 0xa0, 0x10, 0x03, 0x00, 0x03 } }, + { 0xcd, { 0x06, 0x06, 0x06, 0x05, 0x06 } }, + { 0xce, { 0x05, 0x05, 0x01, 0x01, 0x01 } }, + { 0xcf, { 0x40, 0x20, 0x18, 0x18, 0x18 } }, + { 0xd0, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xd1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xd2, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xd3, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xd4, { 0x05, 0x05, 0x05, 0x05, 0x05 } }, + { 0xd5, { 0x05, 0x05, 0x05, 0x03, 0x05 } }, + { 0xd6, { 0xac, 0x22, 0xca, 0x8f, 0xca } }, + { 0xd7, { 0x20, 0x20, 0x20, 0x20, 0x20 } }, + { 0xd8, { 0x01, 0x01, 0x01, 0x01, 0x01 } }, + { 0xd9, { 0x00, 0x00, 0x0f, 0x00, 0x0f } }, + { 0xda, { 0x00, 0xff, 0xff, 0x0e, 0xff } }, + { 0xdb, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, + { 0xdc, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, + { 0xdd, { 0x05, 0x05, 0x05, 0x05, 0x05 } }, + { 0xde, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, + { 0xdf, { 0x42, 0x42, 0x44, 0x44, 0x04 } }, + { 0xe0, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xe1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xe2, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xe3, { 0x00, 0x00, 0x26, 0x06, 0x26 } }, + { 0xe4, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xe5, { 0x01, 0x0a, 0x01, 0x01, 0x01 } }, + { 0xe6, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xe7, { 0x08, 0x08, 0x08, 0x08, 0x08 } }, + { 0xe8, { 0x63, 0x63, 0x63, 0x63, 0x63 } }, + { 0xe9, { 0x59, 0x59, 0x59, 0x59, 0x59 } }, + { 0xea, { 0x80, 0x80, 0x20, 0x80, 0x80 } }, + { 0xeb, { 0x37, 0x37, 0x78, 0x37, 0x77 } }, + { 0xec, { 0x1f, 0x1f, 0x25, 0x25, 0x25 } }, + { 0xed, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, + { 0xee, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0xef, { 0x70, 0x70, 0x58, 0x38, 0x58 } }, + { 0xf0, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, +}; + +#endif /* HD29L2_PRIV */ diff --git a/drivers/media/dvb/frontends/isl6405.c b/drivers/media/dvb-frontends/isl6405.c similarity index 100% rename from drivers/media/dvb/frontends/isl6405.c rename to drivers/media/dvb-frontends/isl6405.c diff --git a/drivers/media/dvb/frontends/isl6405.h b/drivers/media/dvb-frontends/isl6405.h similarity index 100% rename from drivers/media/dvb/frontends/isl6405.h rename to drivers/media/dvb-frontends/isl6405.h diff --git a/drivers/media/dvb/frontends/isl6421.c b/drivers/media/dvb-frontends/isl6421.c similarity index 100% rename from drivers/media/dvb/frontends/isl6421.c rename to drivers/media/dvb-frontends/isl6421.c diff --git a/drivers/media/dvb/frontends/isl6421.h b/drivers/media/dvb-frontends/isl6421.h similarity index 100% rename from drivers/media/dvb/frontends/isl6421.h rename to drivers/media/dvb-frontends/isl6421.h diff --git a/drivers/media/dvb/frontends/isl6423.c b/drivers/media/dvb-frontends/isl6423.c similarity index 100% rename from drivers/media/dvb/frontends/isl6423.c rename to drivers/media/dvb-frontends/isl6423.c diff --git a/drivers/media/dvb/frontends/isl6423.h b/drivers/media/dvb-frontends/isl6423.h similarity index 100% rename from drivers/media/dvb/frontends/isl6423.h rename to drivers/media/dvb-frontends/isl6423.h diff --git a/drivers/media/dvb/frontends/it913x-fe-priv.h b/drivers/media/dvb-frontends/it913x-fe-priv.h similarity index 100% rename from drivers/media/dvb/frontends/it913x-fe-priv.h rename to drivers/media/dvb-frontends/it913x-fe-priv.h diff --git a/drivers/media/dvb-frontends/it913x-fe.c b/drivers/media/dvb-frontends/it913x-fe.c new file mode 100644 index 000000000000..6e1c6eb340b7 --- /dev/null +++ b/drivers/media/dvb-frontends/it913x-fe.c @@ -0,0 +1,1045 @@ +/* + * Driver for it913x-fe Frontend + * + * with support for on chip it9137 integral tuner + * + * Copyright (C) 2011 Malcolm Priestley (tvboxspy@gmail.com) + * IT9137 Copyright (C) ITE Tech Inc. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA.= + */ + +#include +#include +#include +#include + +#include "dvb_frontend.h" +#include "it913x-fe.h" +#include "it913x-fe-priv.h" + +static int it913x_debug; + +module_param_named(debug, it913x_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); + +#define dprintk(level, args...) do { \ + if (level & it913x_debug) \ + printk(KERN_DEBUG "it913x-fe: " args); \ +} while (0) + +#define deb_info(args...) dprintk(0x01, args) +#define debug_data_snipet(level, name, p) \ + dprintk(level, name" (%02x%02x%02x%02x%02x%02x%02x%02x)", \ + *p, *(p+1), *(p+2), *(p+3), *(p+4), \ + *(p+5), *(p+6), *(p+7)); +#define info(format, arg...) \ + printk(KERN_INFO "it913x-fe: " format "\n" , ## arg) + +struct it913x_fe_state { + struct dvb_frontend frontend; + struct i2c_adapter *i2c_adap; + struct ite_config *config; + u8 i2c_addr; + u32 frequency; + fe_modulation_t constellation; + fe_transmit_mode_t transmission_mode; + u8 priority; + u32 crystalFrequency; + u32 adcFrequency; + u8 tuner_type; + struct adctable *table; + fe_status_t it913x_status; + u16 tun_xtal; + u8 tun_fdiv; + u8 tun_clk_mode; + u32 tun_fn_min; + u32 ucblocks; +}; + +static int it913x_read_reg(struct it913x_fe_state *state, + u32 reg, u8 *data, u8 count) +{ + int ret; + u8 pro = PRO_DMOD; /* All reads from demodulator */ + u8 b[4]; + struct i2c_msg msg[2] = { + { .addr = state->i2c_addr + (pro << 1), .flags = 0, + .buf = b, .len = sizeof(b) }, + { .addr = state->i2c_addr + (pro << 1), .flags = I2C_M_RD, + .buf = data, .len = count } + }; + b[0] = (u8) reg >> 24; + b[1] = (u8)(reg >> 16) & 0xff; + b[2] = (u8)(reg >> 8) & 0xff; + b[3] = (u8) reg & 0xff; + + ret = i2c_transfer(state->i2c_adap, msg, 2); + + return ret; +} + +static int it913x_read_reg_u8(struct it913x_fe_state *state, u32 reg) +{ + int ret; + u8 b[1]; + ret = it913x_read_reg(state, reg, &b[0], sizeof(b)); + return (ret < 0) ? -ENODEV : b[0]; +} + +static int it913x_write(struct it913x_fe_state *state, + u8 pro, u32 reg, u8 buf[], u8 count) +{ + u8 b[256]; + struct i2c_msg msg[1] = { + { .addr = state->i2c_addr + (pro << 1), .flags = 0, + .buf = b, .len = count + 4 } + }; + int ret; + + b[0] = (u8) reg >> 24; + b[1] = (u8)(reg >> 16) & 0xff; + b[2] = (u8)(reg >> 8) & 0xff; + b[3] = (u8) reg & 0xff; + memcpy(&b[4], buf, count); + + ret = i2c_transfer(state->i2c_adap, msg, 1); + + if (ret < 0) + return -EIO; + + return 0; +} + +static int it913x_write_reg(struct it913x_fe_state *state, + u8 pro, u32 reg, u32 data) +{ + int ret; + u8 b[4]; + u8 s; + + b[0] = data >> 24; + b[1] = (data >> 16) & 0xff; + b[2] = (data >> 8) & 0xff; + b[3] = data & 0xff; + /* expand write as needed */ + if (data < 0x100) + s = 3; + else if (data < 0x1000) + s = 2; + else if (data < 0x100000) + s = 1; + else + s = 0; + + ret = it913x_write(state, pro, reg, &b[s], sizeof(b) - s); + + return ret; +} + +static int it913x_fe_script_loader(struct it913x_fe_state *state, + struct it913xset *loadscript) +{ + int ret, i; + if (loadscript == NULL) + return -EINVAL; + + for (i = 0; i < 1000; ++i) { + if (loadscript[i].pro == 0xff) + break; + ret = it913x_write(state, loadscript[i].pro, + loadscript[i].address, + loadscript[i].reg, loadscript[i].count); + if (ret < 0) + return -ENODEV; + } + return 0; +} + +static int it913x_init_tuner(struct it913x_fe_state *state) +{ + int ret, i, reg; + u8 val, nv_val; + u8 nv[] = {48, 32, 24, 16, 12, 8, 6, 4, 2}; + u8 b[2]; + + reg = it913x_read_reg_u8(state, 0xec86); + switch (reg) { + case 0: + state->tun_clk_mode = reg; + state->tun_xtal = 2000; + state->tun_fdiv = 3; + val = 16; + break; + case -ENODEV: + return -ENODEV; + case 1: + default: + state->tun_clk_mode = reg; + state->tun_xtal = 640; + state->tun_fdiv = 1; + val = 6; + break; + } + + reg = it913x_read_reg_u8(state, 0xed03); + + if (reg < 0) + return -ENODEV; + else if (reg < ARRAY_SIZE(nv)) + nv_val = nv[reg]; + else + nv_val = 2; + + for (i = 0; i < 50; i++) { + ret = it913x_read_reg(state, 0xed23, &b[0], sizeof(b)); + reg = (b[1] << 8) + b[0]; + if (reg > 0) + break; + if (ret < 0) + return -ENODEV; + udelay(2000); + } + state->tun_fn_min = state->tun_xtal * reg; + state->tun_fn_min /= (state->tun_fdiv * nv_val); + deb_info("Tuner fn_min %d", state->tun_fn_min); + + if (state->config->chip_ver > 1) + msleep(50); + else { + for (i = 0; i < 50; i++) { + reg = it913x_read_reg_u8(state, 0xec82); + if (reg > 0) + break; + if (reg < 0) + return -ENODEV; + udelay(2000); + } + } + + return it913x_write_reg(state, PRO_DMOD, 0xed81, val); +} + +static int it9137_set_tuner(struct it913x_fe_state *state, + u32 bandwidth, u32 frequency_m) +{ + struct it913xset *set_tuner = set_it9137_template; + int ret, reg; + u32 frequency = frequency_m / 1000; + u32 freq, temp_f, tmp; + u16 iqik_m_cal; + u16 n_div; + u8 n; + u8 l_band; + u8 lna_band; + u8 bw; + + if (state->config->firmware_ver == 1) + set_tuner = set_it9135_template; + else + set_tuner = set_it9137_template; + + deb_info("Tuner Frequency %d Bandwidth %d", frequency, bandwidth); + + if (frequency >= 51000 && frequency <= 440000) { + l_band = 0; + lna_band = 0; + } else if (frequency > 440000 && frequency <= 484000) { + l_band = 1; + lna_band = 1; + } else if (frequency > 484000 && frequency <= 533000) { + l_band = 1; + lna_band = 2; + } else if (frequency > 533000 && frequency <= 587000) { + l_band = 1; + lna_band = 3; + } else if (frequency > 587000 && frequency <= 645000) { + l_band = 1; + lna_band = 4; + } else if (frequency > 645000 && frequency <= 710000) { + l_band = 1; + lna_band = 5; + } else if (frequency > 710000 && frequency <= 782000) { + l_band = 1; + lna_band = 6; + } else if (frequency > 782000 && frequency <= 860000) { + l_band = 1; + lna_band = 7; + } else if (frequency > 1450000 && frequency <= 1492000) { + l_band = 1; + lna_band = 0; + } else if (frequency > 1660000 && frequency <= 1685000) { + l_band = 1; + lna_band = 1; + } else + return -EINVAL; + set_tuner[0].reg[0] = lna_band; + + switch (bandwidth) { + case 5000000: + bw = 0; + break; + case 6000000: + bw = 2; + break; + case 7000000: + bw = 4; + break; + default: + case 8000000: + bw = 6; + break; + } + + set_tuner[1].reg[0] = bw; + set_tuner[2].reg[0] = 0xa0 | (l_band << 3); + + if (frequency > 53000 && frequency <= 74000) { + n_div = 48; + n = 0; + } else if (frequency > 74000 && frequency <= 111000) { + n_div = 32; + n = 1; + } else if (frequency > 111000 && frequency <= 148000) { + n_div = 24; + n = 2; + } else if (frequency > 148000 && frequency <= 222000) { + n_div = 16; + n = 3; + } else if (frequency > 222000 && frequency <= 296000) { + n_div = 12; + n = 4; + } else if (frequency > 296000 && frequency <= 445000) { + n_div = 8; + n = 5; + } else if (frequency > 445000 && frequency <= state->tun_fn_min) { + n_div = 6; + n = 6; + } else if (frequency > state->tun_fn_min && frequency <= 950000) { + n_div = 4; + n = 7; + } else if (frequency > 1450000 && frequency <= 1680000) { + n_div = 2; + n = 0; + } else + return -EINVAL; + + reg = it913x_read_reg_u8(state, 0xed81); + iqik_m_cal = (u16)reg * n_div; + + if (reg < 0x20) { + if (state->tun_clk_mode == 0) + iqik_m_cal = (iqik_m_cal * 9) >> 5; + else + iqik_m_cal >>= 1; + } else { + iqik_m_cal = 0x40 - iqik_m_cal; + if (state->tun_clk_mode == 0) + iqik_m_cal = ~((iqik_m_cal * 9) >> 5); + else + iqik_m_cal = ~(iqik_m_cal >> 1); + } + + temp_f = frequency * (u32)n_div * (u32)state->tun_fdiv; + freq = temp_f / state->tun_xtal; + tmp = freq * state->tun_xtal; + + if ((temp_f - tmp) >= (state->tun_xtal >> 1)) + freq++; + + freq += (u32) n << 13; + /* Frequency OMEGA_IQIK_M_CAL_MID*/ + temp_f = freq + (u32)iqik_m_cal; + + set_tuner[3].reg[0] = temp_f & 0xff; + set_tuner[4].reg[0] = (temp_f >> 8) & 0xff; + + deb_info("High Frequency = %04x", temp_f); + + /* Lower frequency */ + set_tuner[5].reg[0] = freq & 0xff; + set_tuner[6].reg[0] = (freq >> 8) & 0xff; + + deb_info("low Frequency = %04x", freq); + + ret = it913x_fe_script_loader(state, set_tuner); + + return (ret < 0) ? -ENODEV : 0; +} + +static int it913x_fe_select_bw(struct it913x_fe_state *state, + u32 bandwidth, u32 adcFrequency) +{ + int ret, i; + u8 buffer[256]; + u32 coeff[8]; + u16 bfsfcw_fftinx_ratio; + u16 fftinx_bfsfcw_ratio; + u8 count; + u8 bw; + u8 adcmultiplier; + + deb_info("Bandwidth %d Adc %d", bandwidth, adcFrequency); + + switch (bandwidth) { + case 5000000: + bw = 3; + break; + case 6000000: + bw = 0; + break; + case 7000000: + bw = 1; + break; + default: + case 8000000: + bw = 2; + break; + } + ret = it913x_write_reg(state, PRO_DMOD, REG_BW, bw); + + if (state->table == NULL) + return -EINVAL; + + /* In write order */ + coeff[0] = state->table[bw].coeff_1_2048; + coeff[1] = state->table[bw].coeff_2_2k; + coeff[2] = state->table[bw].coeff_1_8191; + coeff[3] = state->table[bw].coeff_1_8192; + coeff[4] = state->table[bw].coeff_1_8193; + coeff[5] = state->table[bw].coeff_2_8k; + coeff[6] = state->table[bw].coeff_1_4096; + coeff[7] = state->table[bw].coeff_2_4k; + bfsfcw_fftinx_ratio = state->table[bw].bfsfcw_fftinx_ratio; + fftinx_bfsfcw_ratio = state->table[bw].fftinx_bfsfcw_ratio; + + /* ADC multiplier */ + ret = it913x_read_reg_u8(state, ADC_X_2); + if (ret < 0) + return -EINVAL; + + adcmultiplier = ret; + + count = 0; + + /* Build Buffer for COEFF Registers */ + for (i = 0; i < 8; i++) { + if (adcmultiplier == 1) + coeff[i] /= 2; + buffer[count++] = (coeff[i] >> 24) & 0x3; + buffer[count++] = (coeff[i] >> 16) & 0xff; + buffer[count++] = (coeff[i] >> 8) & 0xff; + buffer[count++] = coeff[i] & 0xff; + } + + /* bfsfcw_fftinx_ratio register 0x21-0x22 */ + buffer[count++] = bfsfcw_fftinx_ratio & 0xff; + buffer[count++] = (bfsfcw_fftinx_ratio >> 8) & 0xff; + /* fftinx_bfsfcw_ratio register 0x23-0x24 */ + buffer[count++] = fftinx_bfsfcw_ratio & 0xff; + buffer[count++] = (fftinx_bfsfcw_ratio >> 8) & 0xff; + /* start at COEFF_1_2048 and write through to fftinx_bfsfcw_ratio*/ + ret = it913x_write(state, PRO_DMOD, COEFF_1_2048, buffer, count); + + for (i = 0; i < 42; i += 8) + debug_data_snipet(0x1, "Buffer", &buffer[i]); + + return ret; +} + + + +static int it913x_fe_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct it913x_fe_state *state = fe->demodulator_priv; + int ret, i; + fe_status_t old_status = state->it913x_status; + *status = 0; + + if (state->it913x_status == 0) { + ret = it913x_read_reg_u8(state, EMPTY_CHANNEL_STATUS); + if (ret == 0x1) { + *status |= FE_HAS_SIGNAL; + for (i = 0; i < 40; i++) { + ret = it913x_read_reg_u8(state, MP2IF_SYNC_LK); + if (ret == 0x1) + break; + msleep(25); + } + if (ret == 0x1) + *status |= FE_HAS_CARRIER + | FE_HAS_VITERBI + | FE_HAS_SYNC; + state->it913x_status = *status; + } + } + + if (state->it913x_status & FE_HAS_SYNC) { + ret = it913x_read_reg_u8(state, TPSD_LOCK); + if (ret == 0x1) + *status |= FE_HAS_LOCK + | state->it913x_status; + else + state->it913x_status = 0; + if (old_status != state->it913x_status) + ret = it913x_write_reg(state, PRO_LINK, GPIOH3_O, ret); + } + + return 0; +} + +/* FEC values based on fe_code_rate_t non supported values 0*/ +int it913x_qpsk_pval[] = {0, -93, -91, -90, 0, -89, -88}; +int it913x_16qam_pval[] = {0, -87, -85, -84, 0, -83, -82}; +int it913x_64qam_pval[] = {0, -82, -80, -78, 0, -77, -76}; + +static int it913x_get_signal_strength(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct it913x_fe_state *state = fe->demodulator_priv; + u8 code_rate; + int ret, temp; + u8 lna_gain_os; + + ret = it913x_read_reg_u8(state, VAR_P_INBAND); + if (ret < 0) + return ret; + + /* VHF/UHF gain offset */ + if (state->frequency < 300000000) + lna_gain_os = 7; + else + lna_gain_os = 14; + + temp = (ret - 100) - lna_gain_os; + + if (state->priority == PRIORITY_HIGH) + code_rate = p->code_rate_HP; + else + code_rate = p->code_rate_LP; + + if (code_rate >= ARRAY_SIZE(it913x_qpsk_pval)) + return -EINVAL; + + deb_info("Reg VAR_P_INBAND:%d Calc Offset Value:%d", ret, temp); + + /* Apply FEC offset values*/ + switch (p->modulation) { + case QPSK: + temp -= it913x_qpsk_pval[code_rate]; + break; + case QAM_16: + temp -= it913x_16qam_pval[code_rate]; + break; + case QAM_64: + temp -= it913x_64qam_pval[code_rate]; + break; + default: + return -EINVAL; + } + + if (temp < -15) + ret = 0; + else if ((-15 <= temp) && (temp < 0)) + ret = (2 * (temp + 15)) / 3; + else if ((0 <= temp) && (temp < 20)) + ret = 4 * temp + 10; + else if ((20 <= temp) && (temp < 35)) + ret = (2 * (temp - 20)) / 3 + 90; + else if (temp >= 35) + ret = 100; + + deb_info("Signal Strength :%d", ret); + + return ret; +} + +static int it913x_fe_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + struct it913x_fe_state *state = fe->demodulator_priv; + int ret = 0; + if (state->config->read_slevel) { + if (state->it913x_status & FE_HAS_SIGNAL) + ret = it913x_read_reg_u8(state, SIGNAL_LEVEL); + } else + ret = it913x_get_signal_strength(fe); + + if (ret >= 0) + *strength = (u16)((u32)ret * 0xffff / 0x64); + + return (ret < 0) ? -ENODEV : 0; +} + +static int it913x_fe_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct it913x_fe_state *state = fe->demodulator_priv; + int ret; + u8 reg[3]; + u32 snr_val, snr_min, snr_max; + u32 temp; + + ret = it913x_read_reg(state, 0x2c, reg, sizeof(reg)); + + snr_val = (u32)(reg[2] << 16) | (reg[1] << 8) | reg[0]; + + ret |= it913x_read_reg(state, 0xf78b, reg, 1); + if (reg[0]) + snr_val /= reg[0]; + + if (state->transmission_mode == TRANSMISSION_MODE_2K) + snr_val *= 4; + else if (state->transmission_mode == TRANSMISSION_MODE_4K) + snr_val *= 2; + + if (state->constellation == QPSK) { + snr_min = 0xb4711; + snr_max = 0x191451; + } else if (state->constellation == QAM_16) { + snr_min = 0x4f0d5; + snr_max = 0xc7925; + } else if (state->constellation == QAM_64) { + snr_min = 0x256d0; + snr_max = 0x626be; + } else + return -EINVAL; + + if (snr_val < snr_min) + *snr = 0; + else if (snr_val < snr_max) { + temp = (snr_val - snr_min) >> 5; + temp *= 0xffff; + temp /= (snr_max - snr_min) >> 5; + *snr = (u16)temp; + } else + *snr = 0xffff; + + return (ret < 0) ? -ENODEV : 0; +} + +static int it913x_fe_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct it913x_fe_state *state = fe->demodulator_priv; + u8 reg[5]; + /* Read Aborted Packets and Pre-Viterbi error rate 5 bytes */ + it913x_read_reg(state, RSD_ABORT_PKT_LSB, reg, sizeof(reg)); + state->ucblocks += (u32)(reg[1] << 8) | reg[0]; + *ber = (u32)(reg[4] << 16) | (reg[3] << 8) | reg[2]; + return 0; +} + +static int it913x_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct it913x_fe_state *state = fe->demodulator_priv; + int ret; + u8 reg[2]; + /* Aborted Packets */ + ret = it913x_read_reg(state, RSD_ABORT_PKT_LSB, reg, sizeof(reg)); + state->ucblocks += (u32)(reg[1] << 8) | reg[0]; + *ucblocks = state->ucblocks; + return ret; +} + +static int it913x_fe_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct it913x_fe_state *state = fe->demodulator_priv; + u8 reg[8]; + + it913x_read_reg(state, REG_TPSD_TX_MODE, reg, sizeof(reg)); + + if (reg[3] < 3) + p->modulation = fe_con[reg[3]]; + + if (reg[0] < 3) + p->transmission_mode = fe_mode[reg[0]]; + + if (reg[1] < 4) + p->guard_interval = fe_gi[reg[1]]; + + if (reg[2] < 4) + p->hierarchy = fe_hi[reg[2]]; + + state->priority = reg[5]; + + p->code_rate_HP = (reg[6] < 6) ? fe_code[reg[6]] : FEC_NONE; + p->code_rate_LP = (reg[7] < 6) ? fe_code[reg[7]] : FEC_NONE; + + /* Update internal state to reflect the autodetected props */ + state->constellation = p->modulation; + state->transmission_mode = p->transmission_mode; + + return 0; +} + +static int it913x_fe_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct it913x_fe_state *state = fe->demodulator_priv; + int i; + u8 empty_ch, last_ch; + + state->it913x_status = 0; + + /* Set bw*/ + it913x_fe_select_bw(state, p->bandwidth_hz, + state->adcFrequency); + + /* Training Mode Off */ + it913x_write_reg(state, PRO_LINK, TRAINING_MODE, 0x0); + + /* Clear Empty Channel */ + it913x_write_reg(state, PRO_DMOD, EMPTY_CHANNEL_STATUS, 0x0); + + /* Clear bits */ + it913x_write_reg(state, PRO_DMOD, MP2IF_SYNC_LK, 0x0); + /* LED on */ + it913x_write_reg(state, PRO_LINK, GPIOH3_O, 0x1); + /* Select Band*/ + if ((p->frequency >= 51000000) && (p->frequency <= 230000000)) + i = 0; + else if ((p->frequency >= 350000000) && (p->frequency <= 900000000)) + i = 1; + else if ((p->frequency >= 1450000000) && (p->frequency <= 1680000000)) + i = 2; + else + return -EOPNOTSUPP; + + it913x_write_reg(state, PRO_DMOD, FREE_BAND, i); + + deb_info("Frontend Set Tuner Type %02x", state->tuner_type); + switch (state->tuner_type) { + case IT9135_38: + case IT9135_51: + case IT9135_52: + case IT9135_60: + case IT9135_61: + case IT9135_62: + it9137_set_tuner(state, + p->bandwidth_hz, p->frequency); + break; + default: + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + break; + } + /* LED off */ + it913x_write_reg(state, PRO_LINK, GPIOH3_O, 0x0); + /* Trigger ofsm */ + it913x_write_reg(state, PRO_DMOD, TRIGGER_OFSM, 0x0); + last_ch = 2; + for (i = 0; i < 40; ++i) { + empty_ch = it913x_read_reg_u8(state, EMPTY_CHANNEL_STATUS); + if (last_ch == 1 && empty_ch == 1) + break; + if (last_ch == 2 && empty_ch == 2) + return 0; + last_ch = empty_ch; + msleep(25); + } + for (i = 0; i < 40; ++i) { + if (it913x_read_reg_u8(state, D_TPSD_LOCK) == 1) + break; + msleep(25); + } + + state->frequency = p->frequency; + return 0; +} + +static int it913x_fe_suspend(struct it913x_fe_state *state) +{ + int ret, i; + u8 b; + + ret = it913x_write_reg(state, PRO_DMOD, SUSPEND_FLAG, 0x1); + + ret |= it913x_write_reg(state, PRO_DMOD, TRIGGER_OFSM, 0x0); + + for (i = 0; i < 128; i++) { + ret = it913x_read_reg(state, SUSPEND_FLAG, &b, 1); + if (ret < 0) + return -ENODEV; + if (b == 0) + break; + + } + + ret |= it913x_write_reg(state, PRO_DMOD, AFE_MEM0, 0x8); + /* Turn LED off */ + ret |= it913x_write_reg(state, PRO_LINK, GPIOH3_O, 0x0); + + ret |= it913x_fe_script_loader(state, it9137_tuner_off); + + return (ret < 0) ? -ENODEV : 0; +} + +/* Power sequence */ +/* Power Up Tuner on -> Frontend suspend off -> Tuner clk on */ +/* Power Down Frontend suspend on -> Tuner clk off -> Tuner off */ + +static int it913x_fe_sleep(struct dvb_frontend *fe) +{ + struct it913x_fe_state *state = fe->demodulator_priv; + return it913x_fe_suspend(state); +} + +static u32 compute_div(u32 a, u32 b, u32 x) +{ + u32 res = 0; + u32 c = 0; + u32 i = 0; + + if (a > b) { + c = a / b; + a = a - c * b; + } + + for (i = 0; i < x; i++) { + if (a >= b) { + res += 1; + a -= b; + } + a <<= 1; + res <<= 1; + } + + res = (c << x) + res; + + return res; +} + +static int it913x_fe_start(struct it913x_fe_state *state) +{ + struct it913xset *set_lna; + struct it913xset *set_mode; + int ret; + u8 adf = (state->config->adf & 0xf); + u32 adc, xtal; + u8 b[4]; + + if (state->config->chip_ver == 1) + ret = it913x_init_tuner(state); + + info("ADF table value :%02x", adf); + + if (adf < 10) { + state->crystalFrequency = fe_clockTable[adf].xtal ; + state->table = fe_clockTable[adf].table; + state->adcFrequency = state->table->adcFrequency; + + adc = compute_div(state->adcFrequency, 1000000ul, 19ul); + xtal = compute_div(state->crystalFrequency, 1000000ul, 19ul); + + } else + return -EINVAL; + + /* Set LED indicator on GPIOH3 */ + ret = it913x_write_reg(state, PRO_LINK, GPIOH3_EN, 0x1); + ret |= it913x_write_reg(state, PRO_LINK, GPIOH3_ON, 0x1); + ret |= it913x_write_reg(state, PRO_LINK, GPIOH3_O, 0x1); + + ret |= it913x_write_reg(state, PRO_LINK, 0xf641, state->tuner_type); + ret |= it913x_write_reg(state, PRO_DMOD, 0xf5ca, 0x01); + ret |= it913x_write_reg(state, PRO_DMOD, 0xf715, 0x01); + + b[0] = xtal & 0xff; + b[1] = (xtal >> 8) & 0xff; + b[2] = (xtal >> 16) & 0xff; + b[3] = (xtal >> 24); + ret |= it913x_write(state, PRO_DMOD, XTAL_CLK, b , 4); + + b[0] = adc & 0xff; + b[1] = (adc >> 8) & 0xff; + b[2] = (adc >> 16) & 0xff; + ret |= it913x_write(state, PRO_DMOD, ADC_FREQ, b, 3); + + if (state->config->adc_x2) + ret |= it913x_write_reg(state, PRO_DMOD, ADC_X_2, 0x01); + b[0] = 0; + b[1] = 0; + b[2] = 0; + ret |= it913x_write(state, PRO_DMOD, 0x0029, b, 3); + + info("Crystal Frequency :%d Adc Frequency :%d ADC X2: %02x", + state->crystalFrequency, state->adcFrequency, + state->config->adc_x2); + deb_info("Xtal value :%04x Adc value :%04x", xtal, adc); + + if (ret < 0) + return -ENODEV; + + /* v1 or v2 tuner script */ + if (state->config->chip_ver > 1) + ret = it913x_fe_script_loader(state, it9135_v2); + else + ret = it913x_fe_script_loader(state, it9135_v1); + if (ret < 0) + return ret; + + /* LNA Scripts */ + switch (state->tuner_type) { + case IT9135_51: + set_lna = it9135_51; + break; + case IT9135_52: + set_lna = it9135_52; + break; + case IT9135_60: + set_lna = it9135_60; + break; + case IT9135_61: + set_lna = it9135_61; + break; + case IT9135_62: + set_lna = it9135_62; + break; + case IT9135_38: + default: + set_lna = it9135_38; + } + info("Tuner LNA type :%02x", state->tuner_type); + + ret = it913x_fe_script_loader(state, set_lna); + if (ret < 0) + return ret; + + if (state->config->chip_ver == 2) { + ret = it913x_write_reg(state, PRO_DMOD, TRIGGER_OFSM, 0x1); + ret |= it913x_write_reg(state, PRO_LINK, PADODPU, 0x0); + ret |= it913x_write_reg(state, PRO_LINK, AGC_O_D, 0x0); + ret |= it913x_init_tuner(state); + } + if (ret < 0) + return -ENODEV; + + /* Always solo frontend */ + set_mode = set_solo_fe; + ret |= it913x_fe_script_loader(state, set_mode); + + ret |= it913x_fe_suspend(state); + return (ret < 0) ? -ENODEV : 0; +} + +static int it913x_fe_init(struct dvb_frontend *fe) +{ + struct it913x_fe_state *state = fe->demodulator_priv; + int ret = 0; + /* Power Up Tuner - common all versions */ + ret = it913x_write_reg(state, PRO_DMOD, 0xec40, 0x1); + + ret |= it913x_fe_script_loader(state, init_1); + + ret |= it913x_write_reg(state, PRO_DMOD, AFE_MEM0, 0x0); + + ret |= it913x_write_reg(state, PRO_DMOD, 0xfba8, 0x0); + + return (ret < 0) ? -ENODEV : 0; +} + +static void it913x_fe_release(struct dvb_frontend *fe) +{ + struct it913x_fe_state *state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops it913x_fe_ofdm_ops; + +struct dvb_frontend *it913x_fe_attach(struct i2c_adapter *i2c_adap, + u8 i2c_addr, struct ite_config *config) +{ + struct it913x_fe_state *state = NULL; + int ret; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct it913x_fe_state), GFP_KERNEL); + if (state == NULL) + return NULL; + if (config == NULL) + goto error; + + state->i2c_adap = i2c_adap; + state->i2c_addr = i2c_addr; + state->config = config; + + switch (state->config->tuner_id_0) { + case IT9135_51: + case IT9135_52: + case IT9135_60: + case IT9135_61: + case IT9135_62: + state->tuner_type = state->config->tuner_id_0; + break; + default: + case IT9135_38: + state->tuner_type = IT9135_38; + } + + ret = it913x_fe_start(state); + if (ret < 0) + goto error; + + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &it913x_fe_ofdm_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + + return &state->frontend; +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(it913x_fe_attach); + +static struct dvb_frontend_ops it913x_fe_ofdm_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "it913x-fe DVB-T", + .frequency_min = 51000000, + .frequency_max = 1680000000, + .frequency_stepsize = 62500, + .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | FE_CAN_FEC_5_6 | FE_CAN_FEC_6_7 | + FE_CAN_FEC_7_8 | FE_CAN_FEC_8_9 | FE_CAN_FEC_AUTO | + FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = it913x_fe_release, + + .init = it913x_fe_init, + .sleep = it913x_fe_sleep, + + .set_frontend = it913x_fe_set_frontend, + .get_frontend = it913x_fe_get_frontend, + + .read_status = it913x_fe_read_status, + .read_signal_strength = it913x_fe_read_signal_strength, + .read_snr = it913x_fe_read_snr, + .read_ber = it913x_fe_read_ber, + .read_ucblocks = it913x_fe_read_ucblocks, +}; + +MODULE_DESCRIPTION("it913x Frontend and it9137 tuner"); +MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com"); +MODULE_VERSION("1.15"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/it913x-fe.h b/drivers/media/dvb-frontends/it913x-fe.h similarity index 100% rename from drivers/media/dvb/frontends/it913x-fe.h rename to drivers/media/dvb-frontends/it913x-fe.h diff --git a/drivers/media/dvb/frontends/itd1000.c b/drivers/media/dvb-frontends/itd1000.c similarity index 100% rename from drivers/media/dvb/frontends/itd1000.c rename to drivers/media/dvb-frontends/itd1000.c diff --git a/drivers/media/dvb/frontends/itd1000.h b/drivers/media/dvb-frontends/itd1000.h similarity index 100% rename from drivers/media/dvb/frontends/itd1000.h rename to drivers/media/dvb-frontends/itd1000.h diff --git a/drivers/media/dvb/frontends/itd1000_priv.h b/drivers/media/dvb-frontends/itd1000_priv.h similarity index 100% rename from drivers/media/dvb/frontends/itd1000_priv.h rename to drivers/media/dvb-frontends/itd1000_priv.h diff --git a/drivers/media/dvb/frontends/ix2505v.c b/drivers/media/dvb-frontends/ix2505v.c similarity index 100% rename from drivers/media/dvb/frontends/ix2505v.c rename to drivers/media/dvb-frontends/ix2505v.c diff --git a/drivers/media/dvb/frontends/ix2505v.h b/drivers/media/dvb-frontends/ix2505v.h similarity index 100% rename from drivers/media/dvb/frontends/ix2505v.h rename to drivers/media/dvb-frontends/ix2505v.h diff --git a/drivers/media/dvb/frontends/l64781.c b/drivers/media/dvb-frontends/l64781.c similarity index 100% rename from drivers/media/dvb/frontends/l64781.c rename to drivers/media/dvb-frontends/l64781.c diff --git a/drivers/media/dvb/frontends/l64781.h b/drivers/media/dvb-frontends/l64781.h similarity index 100% rename from drivers/media/dvb/frontends/l64781.h rename to drivers/media/dvb-frontends/l64781.h diff --git a/drivers/media/dvb/frontends/lg2160.c b/drivers/media/dvb-frontends/lg2160.c similarity index 100% rename from drivers/media/dvb/frontends/lg2160.c rename to drivers/media/dvb-frontends/lg2160.c diff --git a/drivers/media/dvb/frontends/lg2160.h b/drivers/media/dvb-frontends/lg2160.h similarity index 100% rename from drivers/media/dvb/frontends/lg2160.h rename to drivers/media/dvb-frontends/lg2160.h diff --git a/drivers/media/dvb/frontends/lgdt3305.c b/drivers/media/dvb-frontends/lgdt3305.c similarity index 100% rename from drivers/media/dvb/frontends/lgdt3305.c rename to drivers/media/dvb-frontends/lgdt3305.c diff --git a/drivers/media/dvb/frontends/lgdt3305.h b/drivers/media/dvb-frontends/lgdt3305.h similarity index 100% rename from drivers/media/dvb/frontends/lgdt3305.h rename to drivers/media/dvb-frontends/lgdt3305.h diff --git a/drivers/media/dvb/frontends/lgdt330x.c b/drivers/media/dvb-frontends/lgdt330x.c similarity index 100% rename from drivers/media/dvb/frontends/lgdt330x.c rename to drivers/media/dvb-frontends/lgdt330x.c diff --git a/drivers/media/dvb/frontends/lgdt330x.h b/drivers/media/dvb-frontends/lgdt330x.h similarity index 100% rename from drivers/media/dvb/frontends/lgdt330x.h rename to drivers/media/dvb-frontends/lgdt330x.h diff --git a/drivers/media/dvb/frontends/lgdt330x_priv.h b/drivers/media/dvb-frontends/lgdt330x_priv.h similarity index 100% rename from drivers/media/dvb/frontends/lgdt330x_priv.h rename to drivers/media/dvb-frontends/lgdt330x_priv.h diff --git a/drivers/media/dvb-frontends/lgs8gl5.c b/drivers/media/dvb-frontends/lgs8gl5.c new file mode 100644 index 000000000000..416cce3fefc7 --- /dev/null +++ b/drivers/media/dvb-frontends/lgs8gl5.c @@ -0,0 +1,453 @@ +/* + Legend Silicon LGS-8GL5 DMB-TH OFDM demodulator driver + + Copyright (C) 2008 Sirius International (Hong Kong) Limited + Timothy Lee + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include +#include +#include +#include +#include "dvb_frontend.h" +#include "lgs8gl5.h" + + +#define REG_RESET 0x02 +#define REG_RESET_OFF 0x01 +#define REG_03 0x03 +#define REG_04 0x04 +#define REG_07 0x07 +#define REG_09 0x09 +#define REG_0A 0x0a +#define REG_0B 0x0b +#define REG_0C 0x0c +#define REG_37 0x37 +#define REG_STRENGTH 0x4b +#define REG_STRENGTH_MASK 0x7f +#define REG_STRENGTH_CARRIER 0x80 +#define REG_INVERSION 0x7c +#define REG_INVERSION_ON 0x80 +#define REG_7D 0x7d +#define REG_7E 0x7e +#define REG_A2 0xa2 +#define REG_STATUS 0xa4 +#define REG_STATUS_SYNC 0x04 +#define REG_STATUS_LOCK 0x01 + + +struct lgs8gl5_state { + struct i2c_adapter *i2c; + const struct lgs8gl5_config *config; + struct dvb_frontend frontend; +}; + + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "lgs8gl5: " args); \ + } while (0) + + +/* Writes into demod's register */ +static int +lgs8gl5_write_reg(struct lgs8gl5_state *state, u8 reg, u8 data) +{ + int ret; + u8 buf[] = {reg, data}; + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = 2 + }; + + ret = i2c_transfer(state->i2c, &msg, 1); + if (ret != 1) + dprintk("%s: error (reg=0x%02x, val=0x%02x, ret=%i)\n", + __func__, reg, data, ret); + return (ret != 1) ? -1 : 0; +} + + +/* Reads from demod's register */ +static int +lgs8gl5_read_reg(struct lgs8gl5_state *state, u8 reg) +{ + int ret; + u8 b0[] = {reg}; + u8 b1[] = {0}; + struct i2c_msg msg[2] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = b0, + .len = 1 + }, + { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + if (ret != 2) + return -EIO; + + return b1[0]; +} + + +static int +lgs8gl5_update_reg(struct lgs8gl5_state *state, u8 reg, u8 data) +{ + lgs8gl5_read_reg(state, reg); + lgs8gl5_write_reg(state, reg, data); + return 0; +} + + +/* Writes into alternate device's register */ +/* TODO: Find out what that device is for! */ +static int +lgs8gl5_update_alt_reg(struct lgs8gl5_state *state, u8 reg, u8 data) +{ + int ret; + u8 b0[] = {reg}; + u8 b1[] = {0}; + u8 b2[] = {reg, data}; + struct i2c_msg msg[3] = { + { + .addr = state->config->demod_address + 2, + .flags = 0, + .buf = b0, + .len = 1 + }, + { + .addr = state->config->demod_address + 2, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + }, + { + .addr = state->config->demod_address + 2, + .flags = 0, + .buf = b2, + .len = 2 + }, + }; + + ret = i2c_transfer(state->i2c, msg, 3); + return (ret != 3) ? -1 : 0; +} + + +static void +lgs8gl5_soft_reset(struct lgs8gl5_state *state) +{ + u8 val; + + dprintk("%s\n", __func__); + + val = lgs8gl5_read_reg(state, REG_RESET); + lgs8gl5_write_reg(state, REG_RESET, val & ~REG_RESET_OFF); + lgs8gl5_write_reg(state, REG_RESET, val | REG_RESET_OFF); + msleep(5); +} + + +/* Starts demodulation */ +static void +lgs8gl5_start_demod(struct lgs8gl5_state *state) +{ + u8 val; + int n; + + dprintk("%s\n", __func__); + + lgs8gl5_update_alt_reg(state, 0xc2, 0x28); + lgs8gl5_soft_reset(state); + lgs8gl5_update_reg(state, REG_07, 0x10); + lgs8gl5_update_reg(state, REG_07, 0x10); + lgs8gl5_write_reg(state, REG_09, 0x0e); + lgs8gl5_write_reg(state, REG_0A, 0xe5); + lgs8gl5_write_reg(state, REG_0B, 0x35); + lgs8gl5_write_reg(state, REG_0C, 0x30); + + lgs8gl5_update_reg(state, REG_03, 0x00); + lgs8gl5_update_reg(state, REG_7E, 0x01); + lgs8gl5_update_alt_reg(state, 0xc5, 0x00); + lgs8gl5_update_reg(state, REG_04, 0x02); + lgs8gl5_update_reg(state, REG_37, 0x01); + lgs8gl5_soft_reset(state); + + /* Wait for carrier */ + for (n = 0; n < 10; n++) { + val = lgs8gl5_read_reg(state, REG_STRENGTH); + dprintk("Wait for carrier[%d] 0x%02X\n", n, val); + if (val & REG_STRENGTH_CARRIER) + break; + msleep(4); + } + if (!(val & REG_STRENGTH_CARRIER)) + return; + + /* Wait for lock */ + for (n = 0; n < 20; n++) { + val = lgs8gl5_read_reg(state, REG_STATUS); + dprintk("Wait for lock[%d] 0x%02X\n", n, val); + if (val & REG_STATUS_LOCK) + break; + msleep(12); + } + if (!(val & REG_STATUS_LOCK)) + return; + + lgs8gl5_write_reg(state, REG_7D, lgs8gl5_read_reg(state, REG_A2)); + lgs8gl5_soft_reset(state); +} + + +static int +lgs8gl5_init(struct dvb_frontend *fe) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + lgs8gl5_update_alt_reg(state, 0xc2, 0x28); + lgs8gl5_soft_reset(state); + lgs8gl5_update_reg(state, REG_07, 0x10); + lgs8gl5_update_reg(state, REG_07, 0x10); + lgs8gl5_write_reg(state, REG_09, 0x0e); + lgs8gl5_write_reg(state, REG_0A, 0xe5); + lgs8gl5_write_reg(state, REG_0B, 0x35); + lgs8gl5_write_reg(state, REG_0C, 0x30); + + return 0; +} + + +static int +lgs8gl5_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); + u8 flags = lgs8gl5_read_reg(state, REG_STATUS); + + *status = 0; + + if ((level & REG_STRENGTH_MASK) > 0) + *status |= FE_HAS_SIGNAL; + if (level & REG_STRENGTH_CARRIER) + *status |= FE_HAS_CARRIER; + if (flags & REG_STATUS_SYNC) + *status |= FE_HAS_SYNC; + if (flags & REG_STATUS_LOCK) + *status |= FE_HAS_LOCK; + + return 0; +} + + +static int +lgs8gl5_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + *ber = 0; + + return 0; +} + + +static int +lgs8gl5_read_signal_strength(struct dvb_frontend *fe, u16 *signal_strength) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); + *signal_strength = (level & REG_STRENGTH_MASK) << 8; + + return 0; +} + + +static int +lgs8gl5_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); + *snr = (level & REG_STRENGTH_MASK) << 8; + + return 0; +} + + +static int +lgs8gl5_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + + return 0; +} + + +static int +lgs8gl5_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct lgs8gl5_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + if (p->bandwidth_hz != 8000000) + return -EINVAL; + + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + /* lgs8gl5_set_inversion(state, p->inversion); */ + + lgs8gl5_start_demod(state); + + return 0; +} + + +static int +lgs8gl5_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct lgs8gl5_state *state = fe->demodulator_priv; + u8 inv = lgs8gl5_read_reg(state, REG_INVERSION); + + p->inversion = (inv & REG_INVERSION_ON) ? INVERSION_ON : INVERSION_OFF; + + p->code_rate_HP = FEC_1_2; + p->code_rate_LP = FEC_7_8; + p->guard_interval = GUARD_INTERVAL_1_32; + p->transmission_mode = TRANSMISSION_MODE_2K; + p->modulation = QAM_64; + p->hierarchy = HIERARCHY_NONE; + p->bandwidth_hz = 8000000; + + return 0; +} + + +static int +lgs8gl5_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *fesettings) +{ + fesettings->min_delay_ms = 240; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + + +static void +lgs8gl5_release(struct dvb_frontend *fe) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + kfree(state); +} + + +static struct dvb_frontend_ops lgs8gl5_ops; + + +struct dvb_frontend* +lgs8gl5_attach(const struct lgs8gl5_config *config, struct i2c_adapter *i2c) +{ + struct lgs8gl5_state *state = NULL; + + dprintk("%s\n", __func__); + + /* Allocate memory for the internal state */ + state = kzalloc(sizeof(struct lgs8gl5_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* Setup the state */ + state->config = config; + state->i2c = i2c; + + /* Check if the demod is there */ + if (lgs8gl5_read_reg(state, REG_RESET) < 0) + goto error; + + /* Create dvb_frontend */ + memcpy(&state->frontend.ops, &lgs8gl5_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(lgs8gl5_attach); + + +static struct dvb_frontend_ops lgs8gl5_ops = { + .delsys = { SYS_DTMB }, + .info = { + .name = "Legend Silicon LGS-8GL5 DMB-TH", + .frequency_min = 474000000, + .frequency_max = 858000000, + .frequency_stepsize = 10000, + .frequency_tolerance = 0, + .caps = FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_32 | + FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_BANDWIDTH_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_RECOVER + }, + + .release = lgs8gl5_release, + + .init = lgs8gl5_init, + + .set_frontend = lgs8gl5_set_frontend, + .get_frontend = lgs8gl5_get_frontend, + .get_tune_settings = lgs8gl5_get_tune_settings, + + .read_status = lgs8gl5_read_status, + .read_ber = lgs8gl5_read_ber, + .read_signal_strength = lgs8gl5_read_signal_strength, + .read_snr = lgs8gl5_read_snr, + .read_ucblocks = lgs8gl5_read_ucblocks, +}; + + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Legend Silicon LGS-8GL5 DMB-TH Demodulator driver"); +MODULE_AUTHOR("Timothy Lee"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/lgs8gl5.h b/drivers/media/dvb-frontends/lgs8gl5.h similarity index 100% rename from drivers/media/dvb/frontends/lgs8gl5.h rename to drivers/media/dvb-frontends/lgs8gl5.h diff --git a/drivers/media/dvb-frontends/lgs8gxx.c b/drivers/media/dvb-frontends/lgs8gxx.c new file mode 100644 index 000000000000..3c92f36ea5c7 --- /dev/null +++ b/drivers/media/dvb-frontends/lgs8gxx.c @@ -0,0 +1,1075 @@ +/* + * Support for Legend Silicon GB20600 (a.k.a DMB-TH) demodulator + * LGS8913, LGS8GL5, LGS8G75 + * experimental support LGS8G42, LGS8G52 + * + * Copyright (C) 2007-2009 David T.L. Wong + * Copyright (C) 2008 Sirius International (Hong Kong) Limited + * Timothy Lee (for initial work on LGS8GL5) + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +#include "dvb_frontend.h" + +#include "lgs8gxx.h" +#include "lgs8gxx_priv.h" + +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "lgs8gxx: " args); \ + } while (0) + +static int debug; +static int fake_signal_str = 1; + +#define LGS8GXX_FIRMWARE "lgs8g75.fw" + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +module_param(fake_signal_str, int, 0644); +MODULE_PARM_DESC(fake_signal_str, "fake signal strength for LGS8913." +"Signal strength calculation is slow.(default:on)."); + +/* LGS8GXX internal helper functions */ + +static int lgs8gxx_write_reg(struct lgs8gxx_state *priv, u8 reg, u8 data) +{ + int ret; + u8 buf[] = { reg, data }; + struct i2c_msg msg = { .flags = 0, .buf = buf, .len = 2 }; + + msg.addr = priv->config->demod_address; + if (priv->config->prod != LGS8GXX_PROD_LGS8G75 && reg >= 0xC0) + msg.addr += 0x02; + + if (debug >= 2) + dprintk("%s: reg=0x%02X, data=0x%02X\n", __func__, reg, data); + + ret = i2c_transfer(priv->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: error reg=0x%x, data=0x%x, ret=%i\n", + __func__, reg, data, ret); + + return (ret != 1) ? -1 : 0; +} + +static int lgs8gxx_read_reg(struct lgs8gxx_state *priv, u8 reg, u8 *p_data) +{ + int ret; + u8 dev_addr; + + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = { + { .flags = 0, .buf = b0, .len = 1 }, + { .flags = I2C_M_RD, .buf = b1, .len = 1 }, + }; + + dev_addr = priv->config->demod_address; + if (priv->config->prod != LGS8GXX_PROD_LGS8G75 && reg >= 0xC0) + dev_addr += 0x02; + msg[1].addr = msg[0].addr = dev_addr; + + ret = i2c_transfer(priv->i2c, msg, 2); + if (ret != 2) { + dprintk("%s: error reg=0x%x, ret=%i\n", __func__, reg, ret); + return -1; + } + + *p_data = b1[0]; + if (debug >= 2) + dprintk("%s: reg=0x%02X, data=0x%02X\n", __func__, reg, b1[0]); + return 0; +} + +static int lgs8gxx_soft_reset(struct lgs8gxx_state *priv) +{ + lgs8gxx_write_reg(priv, 0x02, 0x00); + msleep(1); + lgs8gxx_write_reg(priv, 0x02, 0x01); + msleep(100); + + return 0; +} + +static int wait_reg_mask(struct lgs8gxx_state *priv, u8 reg, u8 mask, + u8 val, u8 delay, u8 tries) +{ + u8 t; + int i; + + for (i = 0; i < tries; i++) { + lgs8gxx_read_reg(priv, reg, &t); + + if ((t & mask) == val) + return 0; + msleep(delay); + } + + return 1; +} + +static int lgs8gxx_set_ad_mode(struct lgs8gxx_state *priv) +{ + const struct lgs8gxx_config *config = priv->config; + u8 if_conf; + + if_conf = 0x10; /* AGC output on, RF_AGC output off; */ + + if_conf |= + ((config->ext_adc) ? 0x80 : 0x00) | + ((config->if_neg_center) ? 0x04 : 0x00) | + ((config->if_freq == 0) ? 0x08 : 0x00) | /* Baseband */ + ((config->adc_signed) ? 0x02 : 0x00) | + ((config->if_neg_edge) ? 0x01 : 0x00); + + if (config->ext_adc && + (config->prod == LGS8GXX_PROD_LGS8G52)) { + lgs8gxx_write_reg(priv, 0xBA, 0x40); + } + + lgs8gxx_write_reg(priv, 0x07, if_conf); + + return 0; +} + +static int lgs8gxx_set_if_freq(struct lgs8gxx_state *priv, u32 freq /*in kHz*/) +{ + u64 val; + u32 v32; + u32 if_clk; + + if_clk = priv->config->if_clk_freq; + + val = freq; + if (freq != 0) { + val <<= 32; + if (if_clk != 0) + do_div(val, if_clk); + v32 = val & 0xFFFFFFFF; + dprintk("Set IF Freq to %dkHz\n", freq); + } else { + v32 = 0; + dprintk("Set IF Freq to baseband\n"); + } + dprintk("AFC_INIT_FREQ = 0x%08X\n", v32); + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + lgs8gxx_write_reg(priv, 0x08, 0xFF & (v32)); + lgs8gxx_write_reg(priv, 0x09, 0xFF & (v32 >> 8)); + lgs8gxx_write_reg(priv, 0x0A, 0xFF & (v32 >> 16)); + lgs8gxx_write_reg(priv, 0x0B, 0xFF & (v32 >> 24)); + } else { + lgs8gxx_write_reg(priv, 0x09, 0xFF & (v32)); + lgs8gxx_write_reg(priv, 0x0A, 0xFF & (v32 >> 8)); + lgs8gxx_write_reg(priv, 0x0B, 0xFF & (v32 >> 16)); + lgs8gxx_write_reg(priv, 0x0C, 0xFF & (v32 >> 24)); + } + + return 0; +} + +static int lgs8gxx_get_afc_phase(struct lgs8gxx_state *priv) +{ + u64 val; + u32 v32 = 0; + u8 reg_addr, t; + int i; + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) + reg_addr = 0x23; + else + reg_addr = 0x48; + + for (i = 0; i < 4; i++) { + lgs8gxx_read_reg(priv, reg_addr, &t); + v32 <<= 8; + v32 |= t; + reg_addr--; + } + + val = v32; + val *= priv->config->if_clk_freq; + val >>= 32; + dprintk("AFC = %u kHz\n", (u32)val); + return 0; +} + +static int lgs8gxx_set_mode_auto(struct lgs8gxx_state *priv) +{ + u8 t; + u8 prod = priv->config->prod; + + if (prod == LGS8GXX_PROD_LGS8913) + lgs8gxx_write_reg(priv, 0xC6, 0x01); + + if (prod == LGS8GXX_PROD_LGS8G75) { + lgs8gxx_read_reg(priv, 0x0C, &t); + t &= (~0x04); + lgs8gxx_write_reg(priv, 0x0C, t | 0x80); + lgs8gxx_write_reg(priv, 0x39, 0x00); + lgs8gxx_write_reg(priv, 0x3D, 0x04); + } else if (prod == LGS8GXX_PROD_LGS8913 || + prod == LGS8GXX_PROD_LGS8GL5 || + prod == LGS8GXX_PROD_LGS8G42 || + prod == LGS8GXX_PROD_LGS8G52 || + prod == LGS8GXX_PROD_LGS8G54) { + lgs8gxx_read_reg(priv, 0x7E, &t); + lgs8gxx_write_reg(priv, 0x7E, t | 0x01); + + /* clear FEC self reset */ + lgs8gxx_read_reg(priv, 0xC5, &t); + lgs8gxx_write_reg(priv, 0xC5, t & 0xE0); + } + + if (prod == LGS8GXX_PROD_LGS8913) { + /* FEC auto detect */ + lgs8gxx_write_reg(priv, 0xC1, 0x03); + + lgs8gxx_read_reg(priv, 0x7C, &t); + t = (t & 0x8C) | 0x03; + lgs8gxx_write_reg(priv, 0x7C, t); + + /* BER test mode */ + lgs8gxx_read_reg(priv, 0xC3, &t); + t = (t & 0xEF) | 0x10; + lgs8gxx_write_reg(priv, 0xC3, t); + } + + if (priv->config->prod == LGS8GXX_PROD_LGS8G52) + lgs8gxx_write_reg(priv, 0xD9, 0x40); + + return 0; +} + +static int lgs8gxx_set_mode_manual(struct lgs8gxx_state *priv) +{ + u8 t; + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + u8 t2; + lgs8gxx_read_reg(priv, 0x0C, &t); + t &= (~0x80); + lgs8gxx_write_reg(priv, 0x0C, t); + + lgs8gxx_read_reg(priv, 0x0C, &t); + lgs8gxx_read_reg(priv, 0x19, &t2); + + if (((t&0x03) == 0x01) && (t2&0x01)) { + lgs8gxx_write_reg(priv, 0x6E, 0x05); + lgs8gxx_write_reg(priv, 0x39, 0x02); + lgs8gxx_write_reg(priv, 0x39, 0x03); + lgs8gxx_write_reg(priv, 0x3D, 0x05); + lgs8gxx_write_reg(priv, 0x3E, 0x28); + lgs8gxx_write_reg(priv, 0x53, 0x80); + } else { + lgs8gxx_write_reg(priv, 0x6E, 0x3F); + lgs8gxx_write_reg(priv, 0x39, 0x00); + lgs8gxx_write_reg(priv, 0x3D, 0x04); + } + + lgs8gxx_soft_reset(priv); + return 0; + } + + /* turn off auto-detect; manual settings */ + lgs8gxx_write_reg(priv, 0x7E, 0); + if (priv->config->prod == LGS8GXX_PROD_LGS8913) + lgs8gxx_write_reg(priv, 0xC1, 0); + + lgs8gxx_read_reg(priv, 0xC5, &t); + t = (t & 0xE0) | 0x06; + lgs8gxx_write_reg(priv, 0xC5, t); + + lgs8gxx_soft_reset(priv); + + return 0; +} + +static int lgs8gxx_is_locked(struct lgs8gxx_state *priv, u8 *locked) +{ + int ret = 0; + u8 t; + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) + ret = lgs8gxx_read_reg(priv, 0x13, &t); + else + ret = lgs8gxx_read_reg(priv, 0x4B, &t); + if (ret != 0) + return ret; + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) + *locked = ((t & 0x80) == 0x80) ? 1 : 0; + else + *locked = ((t & 0xC0) == 0xC0) ? 1 : 0; + return 0; +} + +/* Wait for Code Acquisition Lock */ +static int lgs8gxx_wait_ca_lock(struct lgs8gxx_state *priv, u8 *locked) +{ + int ret = 0; + u8 reg, mask, val; + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + reg = 0x13; + mask = 0x80; + val = 0x80; + } else { + reg = 0x4B; + mask = 0xC0; + val = 0xC0; + } + + ret = wait_reg_mask(priv, reg, mask, val, 50, 40); + *locked = (ret == 0) ? 1 : 0; + + return 0; +} + +static int lgs8gxx_is_autodetect_finished(struct lgs8gxx_state *priv, + u8 *finished) +{ + int ret = 0; + u8 reg, mask, val; + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + reg = 0x1f; + mask = 0xC0; + val = 0x80; + } else { + reg = 0xA4; + mask = 0x03; + val = 0x01; + } + + ret = wait_reg_mask(priv, reg, mask, val, 10, 20); + *finished = (ret == 0) ? 1 : 0; + + return 0; +} + +static int lgs8gxx_autolock_gi(struct lgs8gxx_state *priv, u8 gi, u8 cpn, + u8 *locked) +{ + int err = 0; + u8 ad_fini = 0; + u8 t1, t2; + + if (gi == GI_945) + dprintk("try GI 945\n"); + else if (gi == GI_595) + dprintk("try GI 595\n"); + else if (gi == GI_420) + dprintk("try GI 420\n"); + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + lgs8gxx_read_reg(priv, 0x0C, &t1); + lgs8gxx_read_reg(priv, 0x18, &t2); + t1 &= ~(GI_MASK); + t1 |= gi; + t2 &= 0xFE; + t2 |= cpn ? 0x01 : 0x00; + lgs8gxx_write_reg(priv, 0x0C, t1); + lgs8gxx_write_reg(priv, 0x18, t2); + } else { + lgs8gxx_write_reg(priv, 0x04, gi); + } + lgs8gxx_soft_reset(priv); + err = lgs8gxx_wait_ca_lock(priv, locked); + if (err || !(*locked)) + return err; + err = lgs8gxx_is_autodetect_finished(priv, &ad_fini); + if (err != 0) + return err; + if (ad_fini) { + dprintk("auto detect finished\n"); + } else + *locked = 0; + + return 0; +} + +static int lgs8gxx_auto_detect(struct lgs8gxx_state *priv, + u8 *detected_param, u8 *gi) +{ + int i, j; + int err = 0; + u8 locked = 0, tmp_gi; + + dprintk("%s\n", __func__); + + lgs8gxx_set_mode_auto(priv); + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + lgs8gxx_write_reg(priv, 0x67, 0xAA); + lgs8gxx_write_reg(priv, 0x6E, 0x3F); + } else { + /* Guard Interval */ + lgs8gxx_write_reg(priv, 0x03, 00); + } + + for (i = 0; i < 2; i++) { + for (j = 0; j < 2; j++) { + tmp_gi = GI_945; + err = lgs8gxx_autolock_gi(priv, GI_945, j, &locked); + if (err) + goto out; + if (locked) + goto locked; + } + for (j = 0; j < 2; j++) { + tmp_gi = GI_420; + err = lgs8gxx_autolock_gi(priv, GI_420, j, &locked); + if (err) + goto out; + if (locked) + goto locked; + } + tmp_gi = GI_595; + err = lgs8gxx_autolock_gi(priv, GI_595, 1, &locked); + if (err) + goto out; + if (locked) + goto locked; + } + +locked: + if ((err == 0) && (locked == 1)) { + u8 t; + + if (priv->config->prod != LGS8GXX_PROD_LGS8G75) { + lgs8gxx_read_reg(priv, 0xA2, &t); + *detected_param = t; + } else { + lgs8gxx_read_reg(priv, 0x1F, &t); + *detected_param = t & 0x3F; + } + + if (tmp_gi == GI_945) + dprintk("GI 945 locked\n"); + else if (tmp_gi == GI_595) + dprintk("GI 595 locked\n"); + else if (tmp_gi == GI_420) + dprintk("GI 420 locked\n"); + *gi = tmp_gi; + } + if (!locked) + err = -1; + +out: + return err; +} + +static void lgs8gxx_auto_lock(struct lgs8gxx_state *priv) +{ + s8 err; + u8 gi = 0x2; + u8 detected_param = 0; + + err = lgs8gxx_auto_detect(priv, &detected_param, &gi); + + if (err != 0) { + dprintk("lgs8gxx_auto_detect failed\n"); + } else + dprintk("detected param = 0x%02X\n", detected_param); + + /* Apply detected parameters */ + if (priv->config->prod == LGS8GXX_PROD_LGS8913) { + u8 inter_leave_len = detected_param & TIM_MASK ; + /* Fix 8913 time interleaver detection bug */ + inter_leave_len = (inter_leave_len == TIM_MIDDLE) ? 0x60 : 0x40; + detected_param &= CF_MASK | SC_MASK | LGS_FEC_MASK; + detected_param |= inter_leave_len; + } + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + u8 t; + lgs8gxx_read_reg(priv, 0x19, &t); + t &= 0x81; + t |= detected_param << 1; + lgs8gxx_write_reg(priv, 0x19, t); + } else { + lgs8gxx_write_reg(priv, 0x7D, detected_param); + if (priv->config->prod == LGS8GXX_PROD_LGS8913) + lgs8gxx_write_reg(priv, 0xC0, detected_param); + } + /* lgs8gxx_soft_reset(priv); */ + + /* Enter manual mode */ + lgs8gxx_set_mode_manual(priv); + + switch (gi) { + case GI_945: + priv->curr_gi = 945; break; + case GI_595: + priv->curr_gi = 595; break; + case GI_420: + priv->curr_gi = 420; break; + default: + priv->curr_gi = 945; break; + } +} + +static int lgs8gxx_set_mpeg_mode(struct lgs8gxx_state *priv, + u8 serial, u8 clk_pol, u8 clk_gated) +{ + int ret = 0; + u8 t, reg_addr; + + reg_addr = (priv->config->prod == LGS8GXX_PROD_LGS8G75) ? 0x30 : 0xC2; + ret = lgs8gxx_read_reg(priv, reg_addr, &t); + if (ret != 0) + return ret; + + t &= 0xF8; + t |= serial ? TS_SERIAL : TS_PARALLEL; + t |= clk_pol ? TS_CLK_INVERTED : TS_CLK_NORMAL; + t |= clk_gated ? TS_CLK_GATED : TS_CLK_FREERUN; + + ret = lgs8gxx_write_reg(priv, reg_addr, t); + if (ret != 0) + return ret; + + return 0; +} + +/* A/D input peak-to-peak voltage range */ +static int lgs8g75_set_adc_vpp(struct lgs8gxx_state *priv, + u8 sel) +{ + u8 r26 = 0x73, r27 = 0x90; + + if (priv->config->prod != LGS8GXX_PROD_LGS8G75) + return 0; + + r26 |= (sel & 0x01) << 7; + r27 |= (sel & 0x02) >> 1; + lgs8gxx_write_reg(priv, 0x26, r26); + lgs8gxx_write_reg(priv, 0x27, r27); + + return 0; +} + +/* LGS8913 demod frontend functions */ + +static int lgs8913_init(struct lgs8gxx_state *priv) +{ + u8 t; + + /* LGS8913 specific */ + lgs8gxx_write_reg(priv, 0xc1, 0x3); + + lgs8gxx_read_reg(priv, 0x7c, &t); + lgs8gxx_write_reg(priv, 0x7c, (t&0x8c) | 0x3); + + /* LGS8913 specific */ + lgs8gxx_read_reg(priv, 0xc3, &t); + lgs8gxx_write_reg(priv, 0xc3, t&0x10); + + + return 0; +} + +static int lgs8g75_init_data(struct lgs8gxx_state *priv) +{ + const struct firmware *fw; + int rc; + int i; + + rc = request_firmware(&fw, LGS8GXX_FIRMWARE, &priv->i2c->dev); + if (rc) + return rc; + + lgs8gxx_write_reg(priv, 0xC6, 0x40); + + lgs8gxx_write_reg(priv, 0x3D, 0x04); + lgs8gxx_write_reg(priv, 0x39, 0x00); + + lgs8gxx_write_reg(priv, 0x3A, 0x00); + lgs8gxx_write_reg(priv, 0x38, 0x00); + lgs8gxx_write_reg(priv, 0x3B, 0x00); + lgs8gxx_write_reg(priv, 0x38, 0x00); + + for (i = 0; i < fw->size; i++) { + lgs8gxx_write_reg(priv, 0x38, 0x00); + lgs8gxx_write_reg(priv, 0x3A, (u8)(i&0xff)); + lgs8gxx_write_reg(priv, 0x3B, (u8)(i>>8)); + lgs8gxx_write_reg(priv, 0x3C, fw->data[i]); + } + + lgs8gxx_write_reg(priv, 0x38, 0x00); + + release_firmware(fw); + return 0; +} + +static int lgs8gxx_init(struct dvb_frontend *fe) +{ + struct lgs8gxx_state *priv = + (struct lgs8gxx_state *)fe->demodulator_priv; + const struct lgs8gxx_config *config = priv->config; + u8 data = 0; + s8 err; + dprintk("%s\n", __func__); + + lgs8gxx_read_reg(priv, 0, &data); + dprintk("reg 0 = 0x%02X\n", data); + + if (config->prod == LGS8GXX_PROD_LGS8G75) + lgs8g75_set_adc_vpp(priv, config->adc_vpp); + + /* Setup MPEG output format */ + err = lgs8gxx_set_mpeg_mode(priv, config->serial_ts, + config->ts_clk_pol, + config->ts_clk_gated); + if (err != 0) + return -EIO; + + if (config->prod == LGS8GXX_PROD_LGS8913) + lgs8913_init(priv); + lgs8gxx_set_if_freq(priv, priv->config->if_freq); + lgs8gxx_set_ad_mode(priv); + + return 0; +} + +static void lgs8gxx_release(struct dvb_frontend *fe) +{ + struct lgs8gxx_state *state = fe->demodulator_priv; + dprintk("%s\n", __func__); + + kfree(state); +} + + +static int lgs8gxx_write(struct dvb_frontend *fe, const u8 buf[], int len) +{ + struct lgs8gxx_state *priv = fe->demodulator_priv; + + if (len != 2) + return -EINVAL; + + return lgs8gxx_write_reg(priv, buf[0], buf[1]); +} + +static int lgs8gxx_set_fe(struct dvb_frontend *fe) +{ + + struct lgs8gxx_state *priv = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + /* set frequency */ + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + /* start auto lock */ + lgs8gxx_auto_lock(priv); + + msleep(10); + + return 0; +} + +static int lgs8gxx_get_fe(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *fe_params = &fe->dtv_property_cache; + dprintk("%s\n", __func__); + + /* TODO: get real readings from device */ + /* inversion status */ + fe_params->inversion = INVERSION_OFF; + + /* bandwidth */ + fe_params->bandwidth_hz = 8000000; + + fe_params->code_rate_HP = FEC_AUTO; + fe_params->code_rate_LP = FEC_AUTO; + + fe_params->modulation = QAM_AUTO; + + /* transmission mode */ + fe_params->transmission_mode = TRANSMISSION_MODE_AUTO; + + /* guard interval */ + fe_params->guard_interval = GUARD_INTERVAL_AUTO; + + /* hierarchy */ + fe_params->hierarchy = HIERARCHY_NONE; + + return 0; +} + +static +int lgs8gxx_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *fesettings) +{ + /* FIXME: copy from tda1004x.c */ + fesettings->min_delay_ms = 800; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static int lgs8gxx_read_status(struct dvb_frontend *fe, fe_status_t *fe_status) +{ + struct lgs8gxx_state *priv = fe->demodulator_priv; + s8 ret; + u8 t, locked = 0; + + dprintk("%s\n", __func__); + *fe_status = 0; + + lgs8gxx_get_afc_phase(priv); + lgs8gxx_is_locked(priv, &locked); + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + if (locked) + *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + return 0; + } + + ret = lgs8gxx_read_reg(priv, 0x4B, &t); + if (ret != 0) + return -EIO; + + dprintk("Reg 0x4B: 0x%02X\n", t); + + *fe_status = 0; + if (priv->config->prod == LGS8GXX_PROD_LGS8913) { + if ((t & 0x40) == 0x40) + *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER; + if ((t & 0x80) == 0x80) + *fe_status |= FE_HAS_VITERBI | FE_HAS_SYNC | + FE_HAS_LOCK; + } else { + if ((t & 0x80) == 0x80) + *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + } + + /* success */ + dprintk("%s: fe_status=0x%x\n", __func__, *fe_status); + return 0; +} + +static int lgs8gxx_read_signal_agc(struct lgs8gxx_state *priv, u16 *signal) +{ + u16 v; + u8 agc_lvl[2], cat; + + dprintk("%s()\n", __func__); + lgs8gxx_read_reg(priv, 0x3F, &agc_lvl[0]); + lgs8gxx_read_reg(priv, 0x3E, &agc_lvl[1]); + + v = agc_lvl[0]; + v <<= 8; + v |= agc_lvl[1]; + + dprintk("agc_lvl: 0x%04X\n", v); + + if (v < 0x100) + cat = 0; + else if (v < 0x190) + cat = 5; + else if (v < 0x2A8) + cat = 4; + else if (v < 0x381) + cat = 3; + else if (v < 0x400) + cat = 2; + else if (v == 0x400) + cat = 1; + else + cat = 0; + + *signal = cat * 65535 / 5; + + return 0; +} + +static int lgs8913_read_signal_strength(struct lgs8gxx_state *priv, u16 *signal) +{ + u8 t; s8 ret; + s16 max_strength = 0; + u8 str; + u16 i, gi = priv->curr_gi; + + dprintk("%s\n", __func__); + + ret = lgs8gxx_read_reg(priv, 0x4B, &t); + if (ret != 0) + return -EIO; + + if (fake_signal_str) { + if ((t & 0xC0) == 0xC0) { + dprintk("Fake signal strength\n"); + *signal = 0x7FFF; + } else + *signal = 0; + return 0; + } + + dprintk("gi = %d\n", gi); + for (i = 0; i < gi; i++) { + + if ((i & 0xFF) == 0) + lgs8gxx_write_reg(priv, 0x84, 0x03 & (i >> 8)); + lgs8gxx_write_reg(priv, 0x83, i & 0xFF); + + lgs8gxx_read_reg(priv, 0x94, &str); + if (max_strength < str) + max_strength = str; + } + + *signal = max_strength; + dprintk("%s: signal=0x%02X\n", __func__, *signal); + + lgs8gxx_read_reg(priv, 0x95, &t); + dprintk("%s: AVG Noise=0x%02X\n", __func__, t); + + return 0; +} + +static int lgs8g75_read_signal_strength(struct lgs8gxx_state *priv, u16 *signal) +{ + u8 t; + s16 v = 0; + + dprintk("%s\n", __func__); + + lgs8gxx_read_reg(priv, 0xB1, &t); + v |= t; + v <<= 8; + lgs8gxx_read_reg(priv, 0xB0, &t); + v |= t; + + *signal = v; + dprintk("%s: signal=0x%02X\n", __func__, *signal); + + return 0; +} + +static int lgs8gxx_read_signal_strength(struct dvb_frontend *fe, u16 *signal) +{ + struct lgs8gxx_state *priv = fe->demodulator_priv; + + if (priv->config->prod == LGS8GXX_PROD_LGS8913) + return lgs8913_read_signal_strength(priv, signal); + else if (priv->config->prod == LGS8GXX_PROD_LGS8G75) + return lgs8g75_read_signal_strength(priv, signal); + else + return lgs8gxx_read_signal_agc(priv, signal); +} + +static int lgs8gxx_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct lgs8gxx_state *priv = fe->demodulator_priv; + u8 t; + *snr = 0; + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) + lgs8gxx_read_reg(priv, 0x34, &t); + else + lgs8gxx_read_reg(priv, 0x95, &t); + dprintk("AVG Noise=0x%02X\n", t); + *snr = 256 - t; + *snr <<= 8; + dprintk("snr=0x%x\n", *snr); + + return 0; +} + +static int lgs8gxx_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + dprintk("%s: ucblocks=0x%x\n", __func__, *ucblocks); + return 0; +} + +static void packet_counter_start(struct lgs8gxx_state *priv) +{ + u8 orig, t; + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + lgs8gxx_read_reg(priv, 0x30, &orig); + orig &= 0xE7; + t = orig | 0x10; + lgs8gxx_write_reg(priv, 0x30, t); + t = orig | 0x18; + lgs8gxx_write_reg(priv, 0x30, t); + t = orig | 0x10; + lgs8gxx_write_reg(priv, 0x30, t); + } else { + lgs8gxx_write_reg(priv, 0xC6, 0x01); + lgs8gxx_write_reg(priv, 0xC6, 0x41); + lgs8gxx_write_reg(priv, 0xC6, 0x01); + } +} + +static void packet_counter_stop(struct lgs8gxx_state *priv) +{ + u8 t; + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + lgs8gxx_read_reg(priv, 0x30, &t); + t &= 0xE7; + lgs8gxx_write_reg(priv, 0x30, t); + } else { + lgs8gxx_write_reg(priv, 0xC6, 0x81); + } +} + +static int lgs8gxx_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct lgs8gxx_state *priv = fe->demodulator_priv; + u8 reg_err, reg_total, t; + u32 total_cnt = 0, err_cnt = 0; + int i; + + dprintk("%s\n", __func__); + + packet_counter_start(priv); + msleep(200); + packet_counter_stop(priv); + + if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { + reg_total = 0x28; reg_err = 0x2C; + } else { + reg_total = 0xD0; reg_err = 0xD4; + } + + for (i = 0; i < 4; i++) { + total_cnt <<= 8; + lgs8gxx_read_reg(priv, reg_total+3-i, &t); + total_cnt |= t; + } + for (i = 0; i < 4; i++) { + err_cnt <<= 8; + lgs8gxx_read_reg(priv, reg_err+3-i, &t); + err_cnt |= t; + } + dprintk("error=%d total=%d\n", err_cnt, total_cnt); + + if (total_cnt == 0) + *ber = 0; + else + *ber = err_cnt * 100 / total_cnt; + + dprintk("%s: ber=0x%x\n", __func__, *ber); + return 0; +} + +static int lgs8gxx_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct lgs8gxx_state *priv = fe->demodulator_priv; + + if (priv->config->tuner_address == 0) + return 0; + if (enable) { + u8 v = 0x80 | priv->config->tuner_address; + return lgs8gxx_write_reg(priv, 0x01, v); + } + return lgs8gxx_write_reg(priv, 0x01, 0); +} + +static struct dvb_frontend_ops lgs8gxx_ops = { + .delsys = { SYS_DTMB }, + .info = { + .name = "Legend Silicon LGS8913/LGS8GXX DMB-TH", + .frequency_min = 474000000, + .frequency_max = 858000000, + .frequency_stepsize = 10000, + .caps = + FE_CAN_FEC_AUTO | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO + }, + + .release = lgs8gxx_release, + + .init = lgs8gxx_init, + .write = lgs8gxx_write, + .i2c_gate_ctrl = lgs8gxx_i2c_gate_ctrl, + + .set_frontend = lgs8gxx_set_fe, + .get_frontend = lgs8gxx_get_fe, + .get_tune_settings = lgs8gxx_get_tune_settings, + + .read_status = lgs8gxx_read_status, + .read_ber = lgs8gxx_read_ber, + .read_signal_strength = lgs8gxx_read_signal_strength, + .read_snr = lgs8gxx_read_snr, + .read_ucblocks = lgs8gxx_read_ucblocks, +}; + +struct dvb_frontend *lgs8gxx_attach(const struct lgs8gxx_config *config, + struct i2c_adapter *i2c) +{ + struct lgs8gxx_state *priv = NULL; + u8 data = 0; + + dprintk("%s()\n", __func__); + + if (config == NULL || i2c == NULL) + return NULL; + + priv = kzalloc(sizeof(struct lgs8gxx_state), GFP_KERNEL); + if (priv == NULL) + goto error_out; + + priv->config = config; + priv->i2c = i2c; + + /* check if the demod is there */ + if (lgs8gxx_read_reg(priv, 0, &data) != 0) { + dprintk("%s lgs8gxx not found at i2c addr 0x%02X\n", + __func__, priv->config->demod_address); + goto error_out; + } + + lgs8gxx_read_reg(priv, 1, &data); + + memcpy(&priv->frontend.ops, &lgs8gxx_ops, + sizeof(struct dvb_frontend_ops)); + priv->frontend.demodulator_priv = priv; + + if (config->prod == LGS8GXX_PROD_LGS8G75) + lgs8g75_init_data(priv); + + return &priv->frontend; + +error_out: + dprintk("%s() error_out\n", __func__); + kfree(priv); + return NULL; + +} +EXPORT_SYMBOL(lgs8gxx_attach); + +MODULE_DESCRIPTION("Legend Silicon LGS8913/LGS8GXX DMB-TH demodulator driver"); +MODULE_AUTHOR("David T. L. Wong "); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(LGS8GXX_FIRMWARE); diff --git a/drivers/media/dvb/frontends/lgs8gxx.h b/drivers/media/dvb-frontends/lgs8gxx.h similarity index 100% rename from drivers/media/dvb/frontends/lgs8gxx.h rename to drivers/media/dvb-frontends/lgs8gxx.h diff --git a/drivers/media/dvb/frontends/lgs8gxx_priv.h b/drivers/media/dvb-frontends/lgs8gxx_priv.h similarity index 100% rename from drivers/media/dvb/frontends/lgs8gxx_priv.h rename to drivers/media/dvb-frontends/lgs8gxx_priv.h diff --git a/drivers/media/dvb/frontends/lnbh24.h b/drivers/media/dvb-frontends/lnbh24.h similarity index 100% rename from drivers/media/dvb/frontends/lnbh24.h rename to drivers/media/dvb-frontends/lnbh24.h diff --git a/drivers/media/dvb/frontends/lnbp21.c b/drivers/media/dvb-frontends/lnbp21.c similarity index 100% rename from drivers/media/dvb/frontends/lnbp21.c rename to drivers/media/dvb-frontends/lnbp21.c diff --git a/drivers/media/dvb/frontends/lnbp21.h b/drivers/media/dvb-frontends/lnbp21.h similarity index 100% rename from drivers/media/dvb/frontends/lnbp21.h rename to drivers/media/dvb-frontends/lnbp21.h diff --git a/drivers/media/dvb/frontends/lnbp22.c b/drivers/media/dvb-frontends/lnbp22.c similarity index 100% rename from drivers/media/dvb/frontends/lnbp22.c rename to drivers/media/dvb-frontends/lnbp22.c diff --git a/drivers/media/dvb/frontends/lnbp22.h b/drivers/media/dvb-frontends/lnbp22.h similarity index 100% rename from drivers/media/dvb/frontends/lnbp22.h rename to drivers/media/dvb-frontends/lnbp22.h diff --git a/drivers/media/dvb-frontends/m88rs2000.c b/drivers/media/dvb-frontends/m88rs2000.c new file mode 100644 index 000000000000..633815ed90ca --- /dev/null +++ b/drivers/media/dvb-frontends/m88rs2000.c @@ -0,0 +1,919 @@ +/* + Driver for M88RS2000 demodulator and tuner + + Copyright (C) 2012 Malcolm Priestley (tvboxspy@gmail.com) + Beta Driver + + Include various calculation code from DS3000 driver. + Copyright (C) 2009 Konstantin Dimitrov. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +#include +#include +#include +#include +#include +#include +#include + + +#include "dvb_frontend.h" +#include "m88rs2000.h" + +struct m88rs2000_state { + struct i2c_adapter *i2c; + const struct m88rs2000_config *config; + struct dvb_frontend frontend; + u8 no_lock_count; + u32 tuner_frequency; + u32 symbol_rate; + fe_code_rate_t fec_inner; + u8 tuner_level; + int errmode; +}; + +static int m88rs2000_debug; + +module_param_named(debug, m88rs2000_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); + +#define dprintk(level, args...) do { \ + if (level & m88rs2000_debug) \ + printk(KERN_DEBUG "m88rs2000-fe: " args); \ +} while (0) + +#define deb_info(args...) dprintk(0x01, args) +#define info(format, arg...) \ + printk(KERN_INFO "m88rs2000-fe: " format "\n" , ## arg) + +static int m88rs2000_writereg(struct m88rs2000_state *state, u8 tuner, + u8 reg, u8 data) +{ + int ret; + u8 addr = (tuner == 0) ? state->config->tuner_addr : + state->config->demod_addr; + u8 buf[] = { reg, data }; + struct i2c_msg msg = { + .addr = addr, + .flags = 0, + .buf = buf, + .len = 2 + }; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + deb_info("%s: writereg error (reg == 0x%02x, val == 0x%02x, " + "ret == %i)\n", __func__, reg, data, ret); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static int m88rs2000_demod_write(struct m88rs2000_state *state, u8 reg, u8 data) +{ + return m88rs2000_writereg(state, 1, reg, data); +} + +static int m88rs2000_tuner_write(struct m88rs2000_state *state, u8 reg, u8 data) +{ + m88rs2000_demod_write(state, 0x81, 0x84); + udelay(10); + return m88rs2000_writereg(state, 0, reg, data); + +} + +static int m88rs2000_write(struct dvb_frontend *fe, const u8 buf[], int len) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + + if (len != 2) + return -EINVAL; + + return m88rs2000_writereg(state, 1, buf[0], buf[1]); +} + +static u8 m88rs2000_readreg(struct m88rs2000_state *state, u8 tuner, u8 reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + u8 addr = (tuner == 0) ? state->config->tuner_addr : + state->config->demod_addr; + struct i2c_msg msg[] = { + { + .addr = addr, + .flags = 0, + .buf = b0, + .len = 1 + }, { + .addr = addr, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + deb_info("%s: readreg error (reg == 0x%02x, ret == %i)\n", + __func__, reg, ret); + + return b1[0]; +} + +static u8 m88rs2000_demod_read(struct m88rs2000_state *state, u8 reg) +{ + return m88rs2000_readreg(state, 1, reg); +} + +static u8 m88rs2000_tuner_read(struct m88rs2000_state *state, u8 reg) +{ + m88rs2000_demod_write(state, 0x81, 0x85); + udelay(10); + return m88rs2000_readreg(state, 0, reg); +} + +static int m88rs2000_set_symbolrate(struct dvb_frontend *fe, u32 srate) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + int ret; + u32 temp; + u8 b[3]; + + if ((srate < 1000000) || (srate > 45000000)) + return -EINVAL; + + temp = srate / 1000; + temp *= 11831; + temp /= 68; + temp -= 3; + + b[0] = (u8) (temp >> 16) & 0xff; + b[1] = (u8) (temp >> 8) & 0xff; + b[2] = (u8) temp & 0xff; + ret = m88rs2000_demod_write(state, 0x93, b[2]); + ret |= m88rs2000_demod_write(state, 0x94, b[1]); + ret |= m88rs2000_demod_write(state, 0x95, b[0]); + + deb_info("m88rs2000: m88rs2000_set_symbolrate\n"); + return ret; +} + +static int m88rs2000_send_diseqc_msg(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *m) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + + int i; + u8 reg; + deb_info("%s\n", __func__); + m88rs2000_demod_write(state, 0x9a, 0x30); + reg = m88rs2000_demod_read(state, 0xb2); + reg &= 0x3f; + m88rs2000_demod_write(state, 0xb2, reg); + for (i = 0; i < m->msg_len; i++) + m88rs2000_demod_write(state, 0xb3 + i, m->msg[i]); + + reg = m88rs2000_demod_read(state, 0xb1); + reg &= 0x87; + reg |= ((m->msg_len - 1) << 3) | 0x07; + reg &= 0x7f; + m88rs2000_demod_write(state, 0xb1, reg); + + for (i = 0; i < 15; i++) { + if ((m88rs2000_demod_read(state, 0xb1) & 0x40) == 0x0) + break; + msleep(20); + } + + reg = m88rs2000_demod_read(state, 0xb1); + if ((reg & 0x40) > 0x0) { + reg &= 0x7f; + reg |= 0x40; + m88rs2000_demod_write(state, 0xb1, reg); + } + + reg = m88rs2000_demod_read(state, 0xb2); + reg &= 0x3f; + reg |= 0x80; + m88rs2000_demod_write(state, 0xb2, reg); + m88rs2000_demod_write(state, 0x9a, 0xb0); + + + return 0; +} + +static int m88rs2000_send_diseqc_burst(struct dvb_frontend *fe, + fe_sec_mini_cmd_t burst) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + u8 reg0, reg1; + deb_info("%s\n", __func__); + m88rs2000_demod_write(state, 0x9a, 0x30); + msleep(50); + reg0 = m88rs2000_demod_read(state, 0xb1); + reg1 = m88rs2000_demod_read(state, 0xb2); + /* TODO complete this section */ + m88rs2000_demod_write(state, 0xb2, reg1); + m88rs2000_demod_write(state, 0xb1, reg0); + m88rs2000_demod_write(state, 0x9a, 0xb0); + + return 0; +} + +static int m88rs2000_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + u8 reg0, reg1; + m88rs2000_demod_write(state, 0x9a, 0x30); + reg0 = m88rs2000_demod_read(state, 0xb1); + reg1 = m88rs2000_demod_read(state, 0xb2); + + reg1 &= 0x3f; + + switch (tone) { + case SEC_TONE_ON: + reg0 |= 0x4; + reg0 &= 0xbc; + break; + case SEC_TONE_OFF: + reg1 |= 0x80; + break; + default: + break; + } + m88rs2000_demod_write(state, 0xb2, reg1); + m88rs2000_demod_write(state, 0xb1, reg0); + m88rs2000_demod_write(state, 0x9a, 0xb0); + return 0; +} + +struct inittab { + u8 cmd; + u8 reg; + u8 val; +}; + +struct inittab m88rs2000_setup[] = { + {DEMOD_WRITE, 0x9a, 0x30}, + {DEMOD_WRITE, 0x00, 0x01}, + {WRITE_DELAY, 0x19, 0x00}, + {DEMOD_WRITE, 0x00, 0x00}, + {DEMOD_WRITE, 0x9a, 0xb0}, + {DEMOD_WRITE, 0x81, 0xc1}, + {TUNER_WRITE, 0x42, 0x73}, + {TUNER_WRITE, 0x05, 0x07}, + {TUNER_WRITE, 0x20, 0x27}, + {TUNER_WRITE, 0x07, 0x02}, + {TUNER_WRITE, 0x11, 0xff}, + {TUNER_WRITE, 0x60, 0xf9}, + {TUNER_WRITE, 0x08, 0x01}, + {TUNER_WRITE, 0x00, 0x41}, + {DEMOD_WRITE, 0x81, 0x81}, + {DEMOD_WRITE, 0x86, 0xc6}, + {DEMOD_WRITE, 0x9a, 0x30}, + {DEMOD_WRITE, 0xf0, 0x22}, + {DEMOD_WRITE, 0xf1, 0xbf}, + {DEMOD_WRITE, 0xb0, 0x45}, + {DEMOD_WRITE, 0xb2, 0x01}, /* set voltage pin always set 1*/ + {DEMOD_WRITE, 0x9a, 0xb0}, + {0xff, 0xaa, 0xff} +}; + +struct inittab m88rs2000_shutdown[] = { + {DEMOD_WRITE, 0x9a, 0x30}, + {DEMOD_WRITE, 0xb0, 0x00}, + {DEMOD_WRITE, 0xf1, 0x89}, + {DEMOD_WRITE, 0x00, 0x01}, + {DEMOD_WRITE, 0x9a, 0xb0}, + {TUNER_WRITE, 0x00, 0x40}, + {DEMOD_WRITE, 0x81, 0x81}, + {0xff, 0xaa, 0xff} +}; + +struct inittab tuner_reset[] = { + {TUNER_WRITE, 0x42, 0x73}, + {TUNER_WRITE, 0x05, 0x07}, + {TUNER_WRITE, 0x20, 0x27}, + {TUNER_WRITE, 0x07, 0x02}, + {TUNER_WRITE, 0x11, 0xff}, + {TUNER_WRITE, 0x60, 0xf9}, + {TUNER_WRITE, 0x08, 0x01}, + {TUNER_WRITE, 0x00, 0x41}, + {0xff, 0xaa, 0xff} +}; + +struct inittab fe_reset[] = { + {DEMOD_WRITE, 0x00, 0x01}, + {DEMOD_WRITE, 0xf1, 0xbf}, + {DEMOD_WRITE, 0x00, 0x01}, + {DEMOD_WRITE, 0x20, 0x81}, + {DEMOD_WRITE, 0x21, 0x80}, + {DEMOD_WRITE, 0x10, 0x33}, + {DEMOD_WRITE, 0x11, 0x44}, + {DEMOD_WRITE, 0x12, 0x07}, + {DEMOD_WRITE, 0x18, 0x20}, + {DEMOD_WRITE, 0x28, 0x04}, + {DEMOD_WRITE, 0x29, 0x8e}, + {DEMOD_WRITE, 0x3b, 0xff}, + {DEMOD_WRITE, 0x32, 0x10}, + {DEMOD_WRITE, 0x33, 0x02}, + {DEMOD_WRITE, 0x34, 0x30}, + {DEMOD_WRITE, 0x35, 0xff}, + {DEMOD_WRITE, 0x38, 0x50}, + {DEMOD_WRITE, 0x39, 0x68}, + {DEMOD_WRITE, 0x3c, 0x7f}, + {DEMOD_WRITE, 0x3d, 0x0f}, + {DEMOD_WRITE, 0x45, 0x20}, + {DEMOD_WRITE, 0x46, 0x24}, + {DEMOD_WRITE, 0x47, 0x7c}, + {DEMOD_WRITE, 0x48, 0x16}, + {DEMOD_WRITE, 0x49, 0x04}, + {DEMOD_WRITE, 0x4a, 0x01}, + {DEMOD_WRITE, 0x4b, 0x78}, + {DEMOD_WRITE, 0X4d, 0xd2}, + {DEMOD_WRITE, 0x4e, 0x6d}, + {DEMOD_WRITE, 0x50, 0x30}, + {DEMOD_WRITE, 0x51, 0x30}, + {DEMOD_WRITE, 0x54, 0x7b}, + {DEMOD_WRITE, 0x56, 0x09}, + {DEMOD_WRITE, 0x58, 0x59}, + {DEMOD_WRITE, 0x59, 0x37}, + {DEMOD_WRITE, 0x63, 0xfa}, + {0xff, 0xaa, 0xff} +}; + +struct inittab fe_trigger[] = { + {DEMOD_WRITE, 0x97, 0x04}, + {DEMOD_WRITE, 0x99, 0x77}, + {DEMOD_WRITE, 0x9b, 0x64}, + {DEMOD_WRITE, 0x9e, 0x00}, + {DEMOD_WRITE, 0x9f, 0xf8}, + {DEMOD_WRITE, 0xa0, 0x20}, + {DEMOD_WRITE, 0xa1, 0xe0}, + {DEMOD_WRITE, 0xa3, 0x38}, + {DEMOD_WRITE, 0x98, 0xff}, + {DEMOD_WRITE, 0xc0, 0x0f}, + {DEMOD_WRITE, 0x89, 0x01}, + {DEMOD_WRITE, 0x00, 0x00}, + {WRITE_DELAY, 0x0a, 0x00}, + {DEMOD_WRITE, 0x00, 0x01}, + {DEMOD_WRITE, 0x00, 0x00}, + {DEMOD_WRITE, 0x9a, 0xb0}, + {0xff, 0xaa, 0xff} +}; + +static int m88rs2000_tab_set(struct m88rs2000_state *state, + struct inittab *tab) +{ + int ret = 0; + u8 i; + if (tab == NULL) + return -EINVAL; + + for (i = 0; i < 255; i++) { + switch (tab[i].cmd) { + case 0x01: + ret = m88rs2000_demod_write(state, tab[i].reg, + tab[i].val); + break; + case 0x02: + ret = m88rs2000_tuner_write(state, tab[i].reg, + tab[i].val); + break; + case 0x10: + if (tab[i].reg > 0) + mdelay(tab[i].reg); + break; + case 0xff: + if (tab[i].reg == 0xaa && tab[i].val == 0xff) + return 0; + case 0x00: + break; + default: + return -EINVAL; + } + if (ret < 0) + return -ENODEV; + } + return 0; +} + +static int m88rs2000_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t volt) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + u8 data; + + data = m88rs2000_demod_read(state, 0xb2); + data |= 0x03; /* bit0 V/H, bit1 off/on */ + + switch (volt) { + case SEC_VOLTAGE_18: + data &= ~0x03; + break; + case SEC_VOLTAGE_13: + data &= ~0x03; + data |= 0x01; + break; + case SEC_VOLTAGE_OFF: + break; + } + + m88rs2000_demod_write(state, 0xb2, data); + + return 0; +} + +static int m88rs2000_startup(struct m88rs2000_state *state) +{ + int ret = 0; + u8 reg; + + reg = m88rs2000_tuner_read(state, 0x00); + if ((reg & 0x40) == 0) + ret = -ENODEV; + + return ret; +} + +static int m88rs2000_init(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + int ret; + + deb_info("m88rs2000: init chip\n"); + /* Setup frontend from shutdown/cold */ + ret = m88rs2000_tab_set(state, m88rs2000_setup); + + return ret; +} + +static int m88rs2000_sleep(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + int ret; + /* Shutdown the frondend */ + ret = m88rs2000_tab_set(state, m88rs2000_shutdown); + return ret; +} + +static int m88rs2000_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + u8 reg = m88rs2000_demod_read(state, 0x8c); + + *status = 0; + + if ((reg & 0x7) == 0x7) { + *status = FE_HAS_CARRIER | FE_HAS_SIGNAL | FE_HAS_VITERBI + | FE_HAS_SYNC | FE_HAS_LOCK; + if (state->config->set_ts_params) + state->config->set_ts_params(fe, CALL_IS_READ); + } + return 0; +} + +/* Extact code for these unknown but lmedm04 driver uses interupt callbacks */ + +static int m88rs2000_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + deb_info("m88rs2000_read_ber %d\n", *ber); + *ber = 0; + return 0; +} + +static int m88rs2000_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + *strength = 0; + return 0; +} + +static int m88rs2000_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + deb_info("m88rs2000_read_snr %d\n", *snr); + *snr = 0; + return 0; +} + +static int m88rs2000_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + deb_info("m88rs2000_read_ber %d\n", *ucblocks); + *ucblocks = 0; + return 0; +} + +static int m88rs2000_tuner_gate_ctrl(struct m88rs2000_state *state, u8 offset) +{ + int ret; + ret = m88rs2000_tuner_write(state, 0x51, 0x1f - offset); + ret |= m88rs2000_tuner_write(state, 0x51, 0x1f); + ret |= m88rs2000_tuner_write(state, 0x50, offset); + ret |= m88rs2000_tuner_write(state, 0x50, 0x00); + msleep(20); + return ret; +} + +static int m88rs2000_set_tuner_rf(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + int reg; + reg = m88rs2000_tuner_read(state, 0x3d); + reg &= 0x7f; + if (reg < 0x16) + reg = 0xa1; + else if (reg == 0x16) + reg = 0x99; + else + reg = 0xf9; + + m88rs2000_tuner_write(state, 0x60, reg); + reg = m88rs2000_tuner_gate_ctrl(state, 0x08); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + return reg; +} + +static int m88rs2000_set_tuner(struct dvb_frontend *fe, u16 *offset) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct m88rs2000_state *state = fe->demodulator_priv; + int ret; + u32 frequency = c->frequency; + s32 offset_khz; + s32 tmp; + u32 symbol_rate = (c->symbol_rate / 1000); + u32 f3db, gdiv28; + u16 value, ndiv, lpf_coeff; + u8 lpf_mxdiv, mlpf_max, mlpf_min, nlpf; + u8 lo = 0x01, div4 = 0x0; + + /* Reset Tuner */ + ret = m88rs2000_tab_set(state, tuner_reset); + + /* Calculate frequency divider */ + if (frequency < 1060000) { + lo |= 0x10; + div4 = 0x1; + ndiv = (frequency * 14 * 4) / FE_CRYSTAL_KHZ; + } else + ndiv = (frequency * 14 * 2) / FE_CRYSTAL_KHZ; + ndiv = ndiv + ndiv % 2; + ndiv = ndiv - 1024; + + ret = m88rs2000_tuner_write(state, 0x10, 0x80 | lo); + + /* Set frequency divider */ + ret |= m88rs2000_tuner_write(state, 0x01, (ndiv >> 8) & 0xf); + ret |= m88rs2000_tuner_write(state, 0x02, ndiv & 0xff); + + ret |= m88rs2000_tuner_write(state, 0x03, 0x06); + ret |= m88rs2000_tuner_gate_ctrl(state, 0x10); + if (ret < 0) + return -ENODEV; + + /* Tuner Frequency Range */ + ret = m88rs2000_tuner_write(state, 0x10, lo); + + ret |= m88rs2000_tuner_gate_ctrl(state, 0x08); + + /* Tuner RF */ + ret |= m88rs2000_set_tuner_rf(fe); + + gdiv28 = (FE_CRYSTAL_KHZ / 1000 * 1694 + 500) / 1000; + ret |= m88rs2000_tuner_write(state, 0x04, gdiv28 & 0xff); + ret |= m88rs2000_tuner_gate_ctrl(state, 0x04); + if (ret < 0) + return -ENODEV; + + value = m88rs2000_tuner_read(state, 0x26); + + f3db = (symbol_rate * 135) / 200 + 2000; + f3db += FREQ_OFFSET_LOW_SYM_RATE; + if (f3db < 7000) + f3db = 7000; + if (f3db > 40000) + f3db = 40000; + + gdiv28 = gdiv28 * 207 / (value * 2 + 151); + mlpf_max = gdiv28 * 135 / 100; + mlpf_min = gdiv28 * 78 / 100; + if (mlpf_max > 63) + mlpf_max = 63; + + lpf_coeff = 2766; + + nlpf = (f3db * gdiv28 * 2 / lpf_coeff / + (FE_CRYSTAL_KHZ / 1000) + 1) / 2; + if (nlpf > 23) + nlpf = 23; + if (nlpf < 1) + nlpf = 1; + + lpf_mxdiv = (nlpf * (FE_CRYSTAL_KHZ / 1000) + * lpf_coeff * 2 / f3db + 1) / 2; + + if (lpf_mxdiv < mlpf_min) { + nlpf++; + lpf_mxdiv = (nlpf * (FE_CRYSTAL_KHZ / 1000) + * lpf_coeff * 2 / f3db + 1) / 2; + } + + if (lpf_mxdiv > mlpf_max) + lpf_mxdiv = mlpf_max; + + ret = m88rs2000_tuner_write(state, 0x04, lpf_mxdiv); + ret |= m88rs2000_tuner_write(state, 0x06, nlpf); + + ret |= m88rs2000_tuner_gate_ctrl(state, 0x04); + + ret |= m88rs2000_tuner_gate_ctrl(state, 0x01); + + msleep(80); + /* calculate offset assuming 96000kHz*/ + offset_khz = (ndiv - ndiv % 2 + 1024) * FE_CRYSTAL_KHZ + / 14 / (div4 + 1) / 2; + + offset_khz -= frequency; + + tmp = offset_khz; + tmp *= 65536; + + tmp = (2 * tmp + 96000) / (2 * 96000); + if (tmp < 0) + tmp += 65536; + + *offset = tmp & 0xffff; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + return (ret < 0) ? -EINVAL : 0; +} + +static int m88rs2000_set_fec(struct m88rs2000_state *state, + fe_code_rate_t fec) +{ + u16 fec_set; + switch (fec) { + /* This is not confirmed kept for reference */ +/* case FEC_1_2: + fec_set = 0x88; + break; + case FEC_2_3: + fec_set = 0x68; + break; + case FEC_3_4: + fec_set = 0x48; + break; + case FEC_5_6: + fec_set = 0x28; + break; + case FEC_7_8: + fec_set = 0x18; + break; */ + case FEC_AUTO: + default: + fec_set = 0x08; + } + m88rs2000_demod_write(state, 0x76, fec_set); + + return 0; +} + + +static fe_code_rate_t m88rs2000_get_fec(struct m88rs2000_state *state) +{ + u8 reg; + m88rs2000_demod_write(state, 0x9a, 0x30); + reg = m88rs2000_demod_read(state, 0x76); + m88rs2000_demod_write(state, 0x9a, 0xb0); + + switch (reg) { + case 0x88: + return FEC_1_2; + case 0x68: + return FEC_2_3; + case 0x48: + return FEC_3_4; + case 0x28: + return FEC_5_6; + case 0x18: + return FEC_7_8; + case 0x08: + default: + break; + } + + return FEC_AUTO; +} + +static int m88rs2000_set_frontend(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + fe_status_t status; + int i, ret; + u16 offset = 0; + u8 reg; + + state->no_lock_count = 0; + + if (c->delivery_system != SYS_DVBS) { + deb_info("%s: unsupported delivery " + "system selected (%d)\n", + __func__, c->delivery_system); + return -EOPNOTSUPP; + } + + /* Set Tuner */ + ret = m88rs2000_set_tuner(fe, &offset); + if (ret < 0) + return -ENODEV; + + ret = m88rs2000_demod_write(state, 0x9a, 0x30); + /* Unknown usually 0xc6 sometimes 0xc1 */ + reg = m88rs2000_demod_read(state, 0x86); + ret |= m88rs2000_demod_write(state, 0x86, reg); + /* Offset lower nibble always 0 */ + ret |= m88rs2000_demod_write(state, 0x9c, (offset >> 8)); + ret |= m88rs2000_demod_write(state, 0x9d, offset & 0xf0); + + + /* Reset Demod */ + ret = m88rs2000_tab_set(state, fe_reset); + if (ret < 0) + return -ENODEV; + + /* Unknown */ + reg = m88rs2000_demod_read(state, 0x70); + ret = m88rs2000_demod_write(state, 0x70, reg); + + /* Set FEC */ + ret |= m88rs2000_set_fec(state, c->fec_inner); + ret |= m88rs2000_demod_write(state, 0x85, 0x1); + ret |= m88rs2000_demod_write(state, 0x8a, 0xbf); + ret |= m88rs2000_demod_write(state, 0x8d, 0x1e); + ret |= m88rs2000_demod_write(state, 0x90, 0xf1); + ret |= m88rs2000_demod_write(state, 0x91, 0x08); + + if (ret < 0) + return -ENODEV; + + /* Set Symbol Rate */ + ret = m88rs2000_set_symbolrate(fe, c->symbol_rate); + if (ret < 0) + return -ENODEV; + + /* Set up Demod */ + ret = m88rs2000_tab_set(state, fe_trigger); + if (ret < 0) + return -ENODEV; + + for (i = 0; i < 25; i++) { + reg = m88rs2000_demod_read(state, 0x8c); + if ((reg & 0x7) == 0x7) { + status = FE_HAS_LOCK; + break; + } + state->no_lock_count++; + if (state->no_lock_count == 15) { + reg = m88rs2000_demod_read(state, 0x70); + reg ^= 0x4; + m88rs2000_demod_write(state, 0x70, reg); + state->no_lock_count = 0; + } + if (state->no_lock_count == 20) + m88rs2000_set_tuner_rf(fe); + msleep(20); + } + + if (status & FE_HAS_LOCK) { + state->fec_inner = m88rs2000_get_fec(state); + /* Uknown suspect SNR level */ + reg = m88rs2000_demod_read(state, 0x65); + } + + state->tuner_frequency = c->frequency; + state->symbol_rate = c->symbol_rate; + return 0; +} + +static int m88rs2000_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct m88rs2000_state *state = fe->demodulator_priv; + c->fec_inner = state->fec_inner; + c->frequency = state->tuner_frequency; + c->symbol_rate = state->symbol_rate; + return 0; +} + +static int m88rs2000_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + + if (enable) + m88rs2000_demod_write(state, 0x81, 0x84); + else + m88rs2000_demod_write(state, 0x81, 0x81); + udelay(10); + return 0; +} + +static void m88rs2000_release(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops m88rs2000_ops = { + .delsys = { SYS_DVBS }, + .info = { + .name = "M88RS2000 DVB-S", + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 1000, /* kHz for QPSK frontends */ + .frequency_tolerance = 5000, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .symbol_rate_tolerance = 500, /* ppm */ + .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | + FE_CAN_QPSK | + FE_CAN_FEC_AUTO + }, + + .release = m88rs2000_release, + .init = m88rs2000_init, + .sleep = m88rs2000_sleep, + .write = m88rs2000_write, + .i2c_gate_ctrl = m88rs2000_i2c_gate_ctrl, + .read_status = m88rs2000_read_status, + .read_ber = m88rs2000_read_ber, + .read_signal_strength = m88rs2000_read_signal_strength, + .read_snr = m88rs2000_read_snr, + .read_ucblocks = m88rs2000_read_ucblocks, + .diseqc_send_master_cmd = m88rs2000_send_diseqc_msg, + .diseqc_send_burst = m88rs2000_send_diseqc_burst, + .set_tone = m88rs2000_set_tone, + .set_voltage = m88rs2000_set_voltage, + + .set_frontend = m88rs2000_set_frontend, + .get_frontend = m88rs2000_get_frontend, +}; + +struct dvb_frontend *m88rs2000_attach(const struct m88rs2000_config *config, + struct i2c_adapter *i2c) +{ + struct m88rs2000_state *state = NULL; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct m88rs2000_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->tuner_frequency = 0; + state->symbol_rate = 0; + state->fec_inner = 0; + + if (m88rs2000_startup(state) < 0) + goto error; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &m88rs2000_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + + return NULL; +} +EXPORT_SYMBOL(m88rs2000_attach); + +MODULE_DESCRIPTION("M88RS2000 DVB-S Demodulator driver"); +MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.13"); + diff --git a/drivers/media/dvb/frontends/m88rs2000.h b/drivers/media/dvb-frontends/m88rs2000.h similarity index 100% rename from drivers/media/dvb/frontends/m88rs2000.h rename to drivers/media/dvb-frontends/m88rs2000.h diff --git a/drivers/media/dvb/frontends/mb86a16.c b/drivers/media/dvb-frontends/mb86a16.c similarity index 100% rename from drivers/media/dvb/frontends/mb86a16.c rename to drivers/media/dvb-frontends/mb86a16.c diff --git a/drivers/media/dvb/frontends/mb86a16.h b/drivers/media/dvb-frontends/mb86a16.h similarity index 100% rename from drivers/media/dvb/frontends/mb86a16.h rename to drivers/media/dvb-frontends/mb86a16.h diff --git a/drivers/media/dvb/frontends/mb86a16_priv.h b/drivers/media/dvb-frontends/mb86a16_priv.h similarity index 100% rename from drivers/media/dvb/frontends/mb86a16_priv.h rename to drivers/media/dvb-frontends/mb86a16_priv.h diff --git a/drivers/media/dvb/frontends/mb86a20s.c b/drivers/media/dvb-frontends/mb86a20s.c similarity index 100% rename from drivers/media/dvb/frontends/mb86a20s.c rename to drivers/media/dvb-frontends/mb86a20s.c diff --git a/drivers/media/dvb/frontends/mb86a20s.h b/drivers/media/dvb-frontends/mb86a20s.h similarity index 100% rename from drivers/media/dvb/frontends/mb86a20s.h rename to drivers/media/dvb-frontends/mb86a20s.h diff --git a/drivers/media/dvb/frontends/mt312.c b/drivers/media/dvb-frontends/mt312.c similarity index 100% rename from drivers/media/dvb/frontends/mt312.c rename to drivers/media/dvb-frontends/mt312.c diff --git a/drivers/media/dvb/frontends/mt312.h b/drivers/media/dvb-frontends/mt312.h similarity index 100% rename from drivers/media/dvb/frontends/mt312.h rename to drivers/media/dvb-frontends/mt312.h diff --git a/drivers/media/dvb/frontends/mt312_priv.h b/drivers/media/dvb-frontends/mt312_priv.h similarity index 100% rename from drivers/media/dvb/frontends/mt312_priv.h rename to drivers/media/dvb-frontends/mt312_priv.h diff --git a/drivers/media/dvb/frontends/mt352.c b/drivers/media/dvb-frontends/mt352.c similarity index 100% rename from drivers/media/dvb/frontends/mt352.c rename to drivers/media/dvb-frontends/mt352.c diff --git a/drivers/media/dvb/frontends/mt352.h b/drivers/media/dvb-frontends/mt352.h similarity index 100% rename from drivers/media/dvb/frontends/mt352.h rename to drivers/media/dvb-frontends/mt352.h diff --git a/drivers/media/dvb/frontends/mt352_priv.h b/drivers/media/dvb-frontends/mt352_priv.h similarity index 100% rename from drivers/media/dvb/frontends/mt352_priv.h rename to drivers/media/dvb-frontends/mt352_priv.h diff --git a/drivers/media/dvb-frontends/nxt200x.c b/drivers/media/dvb-frontends/nxt200x.c new file mode 100644 index 000000000000..8e288940a61f --- /dev/null +++ b/drivers/media/dvb-frontends/nxt200x.c @@ -0,0 +1,1242 @@ +/* + * Support for NXT2002 and NXT2004 - VSB/QAM + * + * Copyright (C) 2005 Kirk Lapray + * Copyright (C) 2006 Michael Krufky + * based on nxt2002 by Taylor Jacob + * and nxt2004 by Jean-Francois Thibert + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * +*/ + +/* + * NOTES ABOUT THIS DRIVER + * + * This Linux driver supports: + * B2C2/BBTI Technisat Air2PC - ATSC (NXT2002) + * AverTVHD MCE A180 (NXT2004) + * ATI HDTV Wonder (NXT2004) + * + * This driver needs external firmware. Please use the command + * "/Documentation/dvb/get_dvb_firmware nxt2002" or + * "/Documentation/dvb/get_dvb_firmware nxt2004" to + * download/extract the appropriate firmware, and then copy it to + * /usr/lib/hotplug/firmware/ or /lib/firmware/ + * (depending on configuration of firmware hotplug). + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define NXT2002_DEFAULT_FIRMWARE "dvb-fe-nxt2002.fw" +#define NXT2004_DEFAULT_FIRMWARE "dvb-fe-nxt2004.fw" +#define CRC_CCIT_MASK 0x1021 + +#include +#include +#include +#include +#include + +#include "dvb_frontend.h" +#include "nxt200x.h" + +struct nxt200x_state { + + struct i2c_adapter* i2c; + const struct nxt200x_config* config; + struct dvb_frontend frontend; + + /* demodulator private data */ + nxt_chip_type demod_chip; + u8 initialised:1; +}; + +static int debug; +#define dprintk(args...) do { if (debug) pr_debug(args); } while (0) + +static int i2c_writebytes (struct nxt200x_state* state, u8 addr, u8 *buf, u8 len) +{ + int err; + struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = buf, .len = len }; + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + pr_warn("%s: i2c write error (addr 0x%02x, err == %i)\n", + __func__, addr, err); + return -EREMOTEIO; + } + return 0; +} + +static int i2c_readbytes(struct nxt200x_state *state, u8 addr, u8 *buf, u8 len) +{ + int err; + struct i2c_msg msg = { .addr = addr, .flags = I2C_M_RD, .buf = buf, .len = len }; + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + pr_warn("%s: i2c read error (addr 0x%02x, err == %i)\n", + __func__, addr, err); + return -EREMOTEIO; + } + return 0; +} + +static int nxt200x_writebytes (struct nxt200x_state* state, u8 reg, + const u8 *buf, u8 len) +{ + u8 buf2 [len+1]; + int err; + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = buf2, .len = len + 1 }; + + buf2[0] = reg; + memcpy(&buf2[1], buf, len); + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + pr_warn("%s: i2c write error (addr 0x%02x, err == %i)\n", + __func__, state->config->demod_address, err); + return -EREMOTEIO; + } + return 0; +} + +static int nxt200x_readbytes(struct nxt200x_state *state, u8 reg, u8 *buf, u8 len) +{ + u8 reg2 [] = { reg }; + + struct i2c_msg msg [] = { { .addr = state->config->demod_address, .flags = 0, .buf = reg2, .len = 1 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = buf, .len = len } }; + + int err; + + if ((err = i2c_transfer (state->i2c, msg, 2)) != 2) { + pr_warn("%s: i2c read error (addr 0x%02x, err == %i)\n", + __func__, state->config->demod_address, err); + return -EREMOTEIO; + } + return 0; +} + +static u16 nxt200x_crc(u16 crc, u8 c) +{ + u8 i; + u16 input = (u16) c & 0xFF; + + input<<=8; + for(i=0; i<8; i++) { + if((crc^input) & 0x8000) + crc=(crc<<1)^CRC_CCIT_MASK; + else + crc<<=1; + input<<=1; + } + return crc; +} + +static int nxt200x_writereg_multibyte (struct nxt200x_state* state, u8 reg, u8* data, u8 len) +{ + u8 attr, len2, buf; + dprintk("%s\n", __func__); + + /* set mutli register register */ + nxt200x_writebytes(state, 0x35, ®, 1); + + /* send the actual data */ + nxt200x_writebytes(state, 0x36, data, len); + + switch (state->demod_chip) { + case NXT2002: + len2 = len; + buf = 0x02; + break; + case NXT2004: + /* probably not right, but gives correct values */ + attr = 0x02; + if (reg & 0x80) { + attr = attr << 1; + if (reg & 0x04) + attr = attr >> 1; + } + /* set write bit */ + len2 = ((attr << 4) | 0x10) | len; + buf = 0x80; + break; + default: + return -EINVAL; + break; + } + + /* set multi register length */ + nxt200x_writebytes(state, 0x34, &len2, 1); + + /* toggle the multireg write bit */ + nxt200x_writebytes(state, 0x21, &buf, 1); + + nxt200x_readbytes(state, 0x21, &buf, 1); + + switch (state->demod_chip) { + case NXT2002: + if ((buf & 0x02) == 0) + return 0; + break; + case NXT2004: + if (buf == 0) + return 0; + break; + default: + return -EINVAL; + break; + } + + pr_warn("Error writing multireg register 0x%02X\n", reg); + + return 0; +} + +static int nxt200x_readreg_multibyte (struct nxt200x_state* state, u8 reg, u8* data, u8 len) +{ + int i; + u8 buf, len2, attr; + dprintk("%s\n", __func__); + + /* set mutli register register */ + nxt200x_writebytes(state, 0x35, ®, 1); + + switch (state->demod_chip) { + case NXT2002: + /* set multi register length */ + len2 = len & 0x80; + nxt200x_writebytes(state, 0x34, &len2, 1); + + /* read the actual data */ + nxt200x_readbytes(state, reg, data, len); + return 0; + break; + case NXT2004: + /* probably not right, but gives correct values */ + attr = 0x02; + if (reg & 0x80) { + attr = attr << 1; + if (reg & 0x04) + attr = attr >> 1; + } + + /* set multi register length */ + len2 = (attr << 4) | len; + nxt200x_writebytes(state, 0x34, &len2, 1); + + /* toggle the multireg bit*/ + buf = 0x80; + nxt200x_writebytes(state, 0x21, &buf, 1); + + /* read the actual data */ + for(i = 0; i < len; i++) { + nxt200x_readbytes(state, 0x36 + i, &data[i], 1); + } + return 0; + break; + default: + return -EINVAL; + break; + } +} + +static void nxt200x_microcontroller_stop (struct nxt200x_state* state) +{ + u8 buf, stopval, counter = 0; + dprintk("%s\n", __func__); + + /* set correct stop value */ + switch (state->demod_chip) { + case NXT2002: + stopval = 0x40; + break; + case NXT2004: + stopval = 0x10; + break; + default: + stopval = 0; + break; + } + + buf = 0x80; + nxt200x_writebytes(state, 0x22, &buf, 1); + + while (counter < 20) { + nxt200x_readbytes(state, 0x31, &buf, 1); + if (buf & stopval) + return; + msleep(10); + counter++; + } + + pr_warn("Timeout waiting for nxt200x to stop. This is ok after " + "firmware upload.\n"); + return; +} + +static void nxt200x_microcontroller_start (struct nxt200x_state* state) +{ + u8 buf; + dprintk("%s\n", __func__); + + buf = 0x00; + nxt200x_writebytes(state, 0x22, &buf, 1); +} + +static void nxt2004_microcontroller_init (struct nxt200x_state* state) +{ + u8 buf[9]; + u8 counter = 0; + dprintk("%s\n", __func__); + + buf[0] = 0x00; + nxt200x_writebytes(state, 0x2b, buf, 1); + buf[0] = 0x70; + nxt200x_writebytes(state, 0x34, buf, 1); + buf[0] = 0x04; + nxt200x_writebytes(state, 0x35, buf, 1); + buf[0] = 0x01; buf[1] = 0x23; buf[2] = 0x45; buf[3] = 0x67; buf[4] = 0x89; + buf[5] = 0xAB; buf[6] = 0xCD; buf[7] = 0xEF; buf[8] = 0xC0; + nxt200x_writebytes(state, 0x36, buf, 9); + buf[0] = 0x80; + nxt200x_writebytes(state, 0x21, buf, 1); + + while (counter < 20) { + nxt200x_readbytes(state, 0x21, buf, 1); + if (buf[0] == 0) + return; + msleep(10); + counter++; + } + + pr_warn("Timeout waiting for nxt2004 to init.\n"); + + return; +} + +static int nxt200x_writetuner (struct nxt200x_state* state, u8* data) +{ + u8 buf, count = 0; + + dprintk("%s\n", __func__); + + dprintk("Tuner Bytes: %*ph\n", 4, data + 1); + + /* if NXT2004, write directly to tuner. if NXT2002, write through NXT chip. + * direct write is required for Philips TUV1236D and ALPS TDHU2 */ + switch (state->demod_chip) { + case NXT2004: + if (i2c_writebytes(state, data[0], data+1, 4)) + pr_warn("error writing to tuner\n"); + /* wait until we have a lock */ + while (count < 20) { + i2c_readbytes(state, data[0], &buf, 1); + if (buf & 0x40) + return 0; + msleep(100); + count++; + } + pr_warn("timeout waiting for tuner lock\n"); + break; + case NXT2002: + /* set the i2c transfer speed to the tuner */ + buf = 0x03; + nxt200x_writebytes(state, 0x20, &buf, 1); + + /* setup to transfer 4 bytes via i2c */ + buf = 0x04; + nxt200x_writebytes(state, 0x34, &buf, 1); + + /* write actual tuner bytes */ + nxt200x_writebytes(state, 0x36, data+1, 4); + + /* set tuner i2c address */ + buf = data[0] << 1; + nxt200x_writebytes(state, 0x35, &buf, 1); + + /* write UC Opmode to begin transfer */ + buf = 0x80; + nxt200x_writebytes(state, 0x21, &buf, 1); + + while (count < 20) { + nxt200x_readbytes(state, 0x21, &buf, 1); + if ((buf & 0x80)== 0x00) + return 0; + msleep(100); + count++; + } + pr_warn("timeout error writing to tuner\n"); + break; + default: + return -EINVAL; + break; + } + return 0; +} + +static void nxt200x_agc_reset(struct nxt200x_state* state) +{ + u8 buf; + dprintk("%s\n", __func__); + + switch (state->demod_chip) { + case NXT2002: + buf = 0x08; + nxt200x_writebytes(state, 0x08, &buf, 1); + buf = 0x00; + nxt200x_writebytes(state, 0x08, &buf, 1); + break; + case NXT2004: + nxt200x_readreg_multibyte(state, 0x08, &buf, 1); + buf = 0x08; + nxt200x_writereg_multibyte(state, 0x08, &buf, 1); + buf = 0x00; + nxt200x_writereg_multibyte(state, 0x08, &buf, 1); + break; + default: + break; + } + return; +} + +static int nxt2002_load_firmware (struct dvb_frontend* fe, const struct firmware *fw) +{ + + struct nxt200x_state* state = fe->demodulator_priv; + u8 buf[3], written = 0, chunkpos = 0; + u16 rambase, position, crc = 0; + + dprintk("%s\n", __func__); + dprintk("Firmware is %zu bytes\n", fw->size); + + /* Get the RAM base for this nxt2002 */ + nxt200x_readbytes(state, 0x10, buf, 1); + + if (buf[0] & 0x10) + rambase = 0x1000; + else + rambase = 0x0000; + + dprintk("rambase on this nxt2002 is %04X\n", rambase); + + /* Hold the micro in reset while loading firmware */ + buf[0] = 0x80; + nxt200x_writebytes(state, 0x2B, buf, 1); + + for (position = 0; position < fw->size; position++) { + if (written == 0) { + crc = 0; + chunkpos = 0x28; + buf[0] = ((rambase + position) >> 8); + buf[1] = (rambase + position) & 0xFF; + buf[2] = 0x81; + /* write starting address */ + nxt200x_writebytes(state, 0x29, buf, 3); + } + written++; + chunkpos++; + + if ((written % 4) == 0) + nxt200x_writebytes(state, chunkpos, &fw->data[position-3], 4); + + crc = nxt200x_crc(crc, fw->data[position]); + + if ((written == 255) || (position+1 == fw->size)) { + /* write remaining bytes of firmware */ + nxt200x_writebytes(state, chunkpos+4-(written %4), + &fw->data[position-(written %4) + 1], + written %4); + buf[0] = crc << 8; + buf[1] = crc & 0xFF; + + /* write crc */ + nxt200x_writebytes(state, 0x2C, buf, 2); + + /* do a read to stop things */ + nxt200x_readbytes(state, 0x2A, buf, 1); + + /* set transfer mode to complete */ + buf[0] = 0x80; + nxt200x_writebytes(state, 0x2B, buf, 1); + + written = 0; + } + } + + return 0; +}; + +static int nxt2004_load_firmware (struct dvb_frontend* fe, const struct firmware *fw) +{ + + struct nxt200x_state* state = fe->demodulator_priv; + u8 buf[3]; + u16 rambase, position, crc=0; + + dprintk("%s\n", __func__); + dprintk("Firmware is %zu bytes\n", fw->size); + + /* set rambase */ + rambase = 0x1000; + + /* hold the micro in reset while loading firmware */ + buf[0] = 0x80; + nxt200x_writebytes(state, 0x2B, buf,1); + + /* calculate firmware CRC */ + for (position = 0; position < fw->size; position++) { + crc = nxt200x_crc(crc, fw->data[position]); + } + + buf[0] = rambase >> 8; + buf[1] = rambase & 0xFF; + buf[2] = 0x81; + /* write starting address */ + nxt200x_writebytes(state,0x29,buf,3); + + for (position = 0; position < fw->size;) { + nxt200x_writebytes(state, 0x2C, &fw->data[position], + fw->size-position > 255 ? 255 : fw->size-position); + position += (fw->size-position > 255 ? 255 : fw->size-position); + } + buf[0] = crc >> 8; + buf[1] = crc & 0xFF; + + dprintk("firmware crc is 0x%02X 0x%02X\n", buf[0], buf[1]); + + /* write crc */ + nxt200x_writebytes(state, 0x2C, buf,2); + + /* do a read to stop things */ + nxt200x_readbytes(state, 0x2C, buf, 1); + + /* set transfer mode to complete */ + buf[0] = 0x80; + nxt200x_writebytes(state, 0x2B, buf,1); + + return 0; +}; + +static int nxt200x_setup_frontend_parameters(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct nxt200x_state* state = fe->demodulator_priv; + u8 buf[5]; + + /* stop the micro first */ + nxt200x_microcontroller_stop(state); + + if (state->demod_chip == NXT2004) { + /* make sure demod is set to digital */ + buf[0] = 0x04; + nxt200x_writebytes(state, 0x14, buf, 1); + buf[0] = 0x00; + nxt200x_writebytes(state, 0x17, buf, 1); + } + + /* set additional params */ + switch (p->modulation) { + case QAM_64: + case QAM_256: + /* Set punctured clock for QAM */ + /* This is just a guess since I am unable to test it */ + if (state->config->set_ts_params) + state->config->set_ts_params(fe, 1); + break; + case VSB_8: + /* Set non-punctured clock for VSB */ + if (state->config->set_ts_params) + state->config->set_ts_params(fe, 0); + break; + default: + return -EINVAL; + break; + } + + if (fe->ops.tuner_ops.calc_regs) { + /* get tuning information */ + fe->ops.tuner_ops.calc_regs(fe, buf, 5); + + /* write frequency information */ + nxt200x_writetuner(state, buf); + } + + /* reset the agc now that tuning has been completed */ + nxt200x_agc_reset(state); + + /* set target power level */ + switch (p->modulation) { + case QAM_64: + case QAM_256: + buf[0] = 0x74; + break; + case VSB_8: + buf[0] = 0x70; + break; + default: + return -EINVAL; + break; + } + nxt200x_writebytes(state, 0x42, buf, 1); + + /* configure sdm */ + switch (state->demod_chip) { + case NXT2002: + buf[0] = 0x87; + break; + case NXT2004: + buf[0] = 0x07; + break; + default: + return -EINVAL; + break; + } + nxt200x_writebytes(state, 0x57, buf, 1); + + /* write sdm1 input */ + buf[0] = 0x10; + buf[1] = 0x00; + switch (state->demod_chip) { + case NXT2002: + nxt200x_writereg_multibyte(state, 0x58, buf, 2); + break; + case NXT2004: + nxt200x_writebytes(state, 0x58, buf, 2); + break; + default: + return -EINVAL; + break; + } + + /* write sdmx input */ + switch (p->modulation) { + case QAM_64: + buf[0] = 0x68; + break; + case QAM_256: + buf[0] = 0x64; + break; + case VSB_8: + buf[0] = 0x60; + break; + default: + return -EINVAL; + break; + } + buf[1] = 0x00; + switch (state->demod_chip) { + case NXT2002: + nxt200x_writereg_multibyte(state, 0x5C, buf, 2); + break; + case NXT2004: + nxt200x_writebytes(state, 0x5C, buf, 2); + break; + default: + return -EINVAL; + break; + } + + /* write adc power lpf fc */ + buf[0] = 0x05; + nxt200x_writebytes(state, 0x43, buf, 1); + + if (state->demod_chip == NXT2004) { + /* write ??? */ + buf[0] = 0x00; + buf[1] = 0x00; + nxt200x_writebytes(state, 0x46, buf, 2); + } + + /* write accumulator2 input */ + buf[0] = 0x80; + buf[1] = 0x00; + switch (state->demod_chip) { + case NXT2002: + nxt200x_writereg_multibyte(state, 0x4B, buf, 2); + break; + case NXT2004: + nxt200x_writebytes(state, 0x4B, buf, 2); + break; + default: + return -EINVAL; + break; + } + + /* write kg1 */ + buf[0] = 0x00; + nxt200x_writebytes(state, 0x4D, buf, 1); + + /* write sdm12 lpf fc */ + buf[0] = 0x44; + nxt200x_writebytes(state, 0x55, buf, 1); + + /* write agc control reg */ + buf[0] = 0x04; + nxt200x_writebytes(state, 0x41, buf, 1); + + if (state->demod_chip == NXT2004) { + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x24; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + + /* soft reset? */ + nxt200x_readreg_multibyte(state, 0x08, buf, 1); + buf[0] = 0x10; + nxt200x_writereg_multibyte(state, 0x08, buf, 1); + nxt200x_readreg_multibyte(state, 0x08, buf, 1); + buf[0] = 0x00; + nxt200x_writereg_multibyte(state, 0x08, buf, 1); + + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x04; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x00; + nxt200x_writereg_multibyte(state, 0x81, buf, 1); + buf[0] = 0x80; buf[1] = 0x00; buf[2] = 0x00; + nxt200x_writereg_multibyte(state, 0x82, buf, 3); + nxt200x_readreg_multibyte(state, 0x88, buf, 1); + buf[0] = 0x11; + nxt200x_writereg_multibyte(state, 0x88, buf, 1); + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x44; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + } + + /* write agc ucgp0 */ + switch (p->modulation) { + case QAM_64: + buf[0] = 0x02; + break; + case QAM_256: + buf[0] = 0x03; + break; + case VSB_8: + buf[0] = 0x00; + break; + default: + return -EINVAL; + break; + } + nxt200x_writebytes(state, 0x30, buf, 1); + + /* write agc control reg */ + buf[0] = 0x00; + nxt200x_writebytes(state, 0x41, buf, 1); + + /* write accumulator2 input */ + buf[0] = 0x80; + buf[1] = 0x00; + switch (state->demod_chip) { + case NXT2002: + nxt200x_writereg_multibyte(state, 0x49, buf, 2); + nxt200x_writereg_multibyte(state, 0x4B, buf, 2); + break; + case NXT2004: + nxt200x_writebytes(state, 0x49, buf, 2); + nxt200x_writebytes(state, 0x4B, buf, 2); + break; + default: + return -EINVAL; + break; + } + + /* write agc control reg */ + buf[0] = 0x04; + nxt200x_writebytes(state, 0x41, buf, 1); + + nxt200x_microcontroller_start(state); + + if (state->demod_chip == NXT2004) { + nxt2004_microcontroller_init(state); + + /* ???? */ + buf[0] = 0xF0; + buf[1] = 0x00; + nxt200x_writebytes(state, 0x5C, buf, 2); + } + + /* adjacent channel detection should be done here, but I don't + have any stations with this need so I cannot test it */ + + return 0; +} + +static int nxt200x_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct nxt200x_state* state = fe->demodulator_priv; + u8 lock; + nxt200x_readbytes(state, 0x31, &lock, 1); + + *status = 0; + if (lock & 0x20) { + *status |= FE_HAS_SIGNAL; + *status |= FE_HAS_CARRIER; + *status |= FE_HAS_VITERBI; + *status |= FE_HAS_SYNC; + *status |= FE_HAS_LOCK; + } + return 0; +} + +static int nxt200x_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct nxt200x_state* state = fe->demodulator_priv; + u8 b[3]; + + nxt200x_readreg_multibyte(state, 0xE6, b, 3); + + *ber = ((b[0] << 8) + b[1]) * 8; + + return 0; +} + +static int nxt200x_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct nxt200x_state* state = fe->demodulator_priv; + u8 b[2]; + u16 temp = 0; + + /* setup to read cluster variance */ + b[0] = 0x00; + nxt200x_writebytes(state, 0xA1, b, 1); + + /* get multreg val */ + nxt200x_readreg_multibyte(state, 0xA6, b, 2); + + temp = (b[0] << 8) | b[1]; + *strength = ((0x7FFF - temp) & 0x0FFF) * 16; + + return 0; +} + +static int nxt200x_read_snr(struct dvb_frontend* fe, u16* snr) +{ + + struct nxt200x_state* state = fe->demodulator_priv; + u8 b[2]; + u16 temp = 0, temp2; + u32 snrdb = 0; + + /* setup to read cluster variance */ + b[0] = 0x00; + nxt200x_writebytes(state, 0xA1, b, 1); + + /* get multreg val from 0xA6 */ + nxt200x_readreg_multibyte(state, 0xA6, b, 2); + + temp = (b[0] << 8) | b[1]; + temp2 = 0x7FFF - temp; + + /* snr will be in db */ + if (temp2 > 0x7F00) + snrdb = 1000*24 + ( 1000*(30-24) * ( temp2 - 0x7F00 ) / ( 0x7FFF - 0x7F00 ) ); + else if (temp2 > 0x7EC0) + snrdb = 1000*18 + ( 1000*(24-18) * ( temp2 - 0x7EC0 ) / ( 0x7F00 - 0x7EC0 ) ); + else if (temp2 > 0x7C00) + snrdb = 1000*12 + ( 1000*(18-12) * ( temp2 - 0x7C00 ) / ( 0x7EC0 - 0x7C00 ) ); + else + snrdb = 1000*0 + ( 1000*(12-0) * ( temp2 - 0 ) / ( 0x7C00 - 0 ) ); + + /* the value reported back from the frontend will be FFFF=32db 0000=0db */ + *snr = snrdb * (0xFFFF/32000); + + return 0; +} + +static int nxt200x_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct nxt200x_state* state = fe->demodulator_priv; + u8 b[3]; + + nxt200x_readreg_multibyte(state, 0xE6, b, 3); + *ucblocks = b[2]; + + return 0; +} + +static int nxt200x_sleep(struct dvb_frontend* fe) +{ + return 0; +} + +static int nxt2002_init(struct dvb_frontend* fe) +{ + struct nxt200x_state* state = fe->demodulator_priv; + const struct firmware *fw; + int ret; + u8 buf[2]; + + /* request the firmware, this will block until someone uploads it */ + pr_debug("%s: Waiting for firmware upload (%s)...\n", + __func__, NXT2002_DEFAULT_FIRMWARE); + ret = request_firmware(&fw, NXT2002_DEFAULT_FIRMWARE, + state->i2c->dev.parent); + pr_debug("%s: Waiting for firmware upload(2)...\n", __func__); + if (ret) { + pr_err("%s: No firmware uploaded (timeout or file not found?)" + "\n", __func__); + return ret; + } + + ret = nxt2002_load_firmware(fe, fw); + release_firmware(fw); + if (ret) { + pr_err("%s: Writing firmware to device failed\n", __func__); + return ret; + } + pr_info("%s: Firmware upload complete\n", __func__); + + /* Put the micro into reset */ + nxt200x_microcontroller_stop(state); + + /* ensure transfer is complete */ + buf[0]=0x00; + nxt200x_writebytes(state, 0x2B, buf, 1); + + /* Put the micro into reset for real this time */ + nxt200x_microcontroller_stop(state); + + /* soft reset everything (agc,frontend,eq,fec)*/ + buf[0] = 0x0F; + nxt200x_writebytes(state, 0x08, buf, 1); + buf[0] = 0x00; + nxt200x_writebytes(state, 0x08, buf, 1); + + /* write agc sdm configure */ + buf[0] = 0xF1; + nxt200x_writebytes(state, 0x57, buf, 1); + + /* write mod output format */ + buf[0] = 0x20; + nxt200x_writebytes(state, 0x09, buf, 1); + + /* write fec mpeg mode */ + buf[0] = 0x7E; + buf[1] = 0x00; + nxt200x_writebytes(state, 0xE9, buf, 2); + + /* write mux selection */ + buf[0] = 0x00; + nxt200x_writebytes(state, 0xCC, buf, 1); + + return 0; +} + +static int nxt2004_init(struct dvb_frontend* fe) +{ + struct nxt200x_state* state = fe->demodulator_priv; + const struct firmware *fw; + int ret; + u8 buf[3]; + + /* ??? */ + buf[0]=0x00; + nxt200x_writebytes(state, 0x1E, buf, 1); + + /* request the firmware, this will block until someone uploads it */ + pr_debug("%s: Waiting for firmware upload (%s)...\n", + __func__, NXT2004_DEFAULT_FIRMWARE); + ret = request_firmware(&fw, NXT2004_DEFAULT_FIRMWARE, + state->i2c->dev.parent); + pr_debug("%s: Waiting for firmware upload(2)...\n", __func__); + if (ret) { + pr_err("%s: No firmware uploaded (timeout or file not found?)" + "\n", __func__); + return ret; + } + + ret = nxt2004_load_firmware(fe, fw); + release_firmware(fw); + if (ret) { + pr_err("%s: Writing firmware to device failed\n", __func__); + return ret; + } + pr_info("%s: Firmware upload complete\n", __func__); + + /* ensure transfer is complete */ + buf[0] = 0x01; + nxt200x_writebytes(state, 0x19, buf, 1); + + nxt2004_microcontroller_init(state); + nxt200x_microcontroller_stop(state); + nxt200x_microcontroller_stop(state); + nxt2004_microcontroller_init(state); + nxt200x_microcontroller_stop(state); + + /* soft reset everything (agc,frontend,eq,fec)*/ + buf[0] = 0xFF; + nxt200x_writereg_multibyte(state, 0x08, buf, 1); + buf[0] = 0x00; + nxt200x_writereg_multibyte(state, 0x08, buf, 1); + + /* write agc sdm configure */ + buf[0] = 0xD7; + nxt200x_writebytes(state, 0x57, buf, 1); + + /* ???*/ + buf[0] = 0x07; + buf[1] = 0xfe; + nxt200x_writebytes(state, 0x35, buf, 2); + buf[0] = 0x12; + nxt200x_writebytes(state, 0x34, buf, 1); + buf[0] = 0x80; + nxt200x_writebytes(state, 0x21, buf, 1); + + /* ???*/ + buf[0] = 0x21; + nxt200x_writebytes(state, 0x0A, buf, 1); + + /* ???*/ + buf[0] = 0x01; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + + /* write fec mpeg mode */ + buf[0] = 0x7E; + buf[1] = 0x00; + nxt200x_writebytes(state, 0xE9, buf, 2); + + /* write mux selection */ + buf[0] = 0x00; + nxt200x_writebytes(state, 0xCC, buf, 1); + + /* ???*/ + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x00; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + + /* soft reset? */ + nxt200x_readreg_multibyte(state, 0x08, buf, 1); + buf[0] = 0x10; + nxt200x_writereg_multibyte(state, 0x08, buf, 1); + nxt200x_readreg_multibyte(state, 0x08, buf, 1); + buf[0] = 0x00; + nxt200x_writereg_multibyte(state, 0x08, buf, 1); + + /* ???*/ + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x01; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x70; + nxt200x_writereg_multibyte(state, 0x81, buf, 1); + buf[0] = 0x31; buf[1] = 0x5E; buf[2] = 0x66; + nxt200x_writereg_multibyte(state, 0x82, buf, 3); + + nxt200x_readreg_multibyte(state, 0x88, buf, 1); + buf[0] = 0x11; + nxt200x_writereg_multibyte(state, 0x88, buf, 1); + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x40; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + + nxt200x_readbytes(state, 0x10, buf, 1); + buf[0] = 0x10; + nxt200x_writebytes(state, 0x10, buf, 1); + nxt200x_readbytes(state, 0x0A, buf, 1); + buf[0] = 0x21; + nxt200x_writebytes(state, 0x0A, buf, 1); + + nxt2004_microcontroller_init(state); + + buf[0] = 0x21; + nxt200x_writebytes(state, 0x0A, buf, 1); + buf[0] = 0x7E; + nxt200x_writebytes(state, 0xE9, buf, 1); + buf[0] = 0x00; + nxt200x_writebytes(state, 0xEA, buf, 1); + + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x00; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x00; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + + /* soft reset? */ + nxt200x_readreg_multibyte(state, 0x08, buf, 1); + buf[0] = 0x10; + nxt200x_writereg_multibyte(state, 0x08, buf, 1); + nxt200x_readreg_multibyte(state, 0x08, buf, 1); + buf[0] = 0x00; + nxt200x_writereg_multibyte(state, 0x08, buf, 1); + + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x04; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x00; + nxt200x_writereg_multibyte(state, 0x81, buf, 1); + buf[0] = 0x80; buf[1] = 0x00; buf[2] = 0x00; + nxt200x_writereg_multibyte(state, 0x82, buf, 3); + + nxt200x_readreg_multibyte(state, 0x88, buf, 1); + buf[0] = 0x11; + nxt200x_writereg_multibyte(state, 0x88, buf, 1); + + nxt200x_readreg_multibyte(state, 0x80, buf, 1); + buf[0] = 0x44; + nxt200x_writereg_multibyte(state, 0x80, buf, 1); + + /* initialize tuner */ + nxt200x_readbytes(state, 0x10, buf, 1); + buf[0] = 0x12; + nxt200x_writebytes(state, 0x10, buf, 1); + buf[0] = 0x04; + nxt200x_writebytes(state, 0x13, buf, 1); + buf[0] = 0x00; + nxt200x_writebytes(state, 0x16, buf, 1); + buf[0] = 0x04; + nxt200x_writebytes(state, 0x14, buf, 1); + buf[0] = 0x00; + nxt200x_writebytes(state, 0x14, buf, 1); + nxt200x_writebytes(state, 0x17, buf, 1); + nxt200x_writebytes(state, 0x14, buf, 1); + nxt200x_writebytes(state, 0x17, buf, 1); + + return 0; +} + +static int nxt200x_init(struct dvb_frontend* fe) +{ + struct nxt200x_state* state = fe->demodulator_priv; + int ret = 0; + + if (!state->initialised) { + switch (state->demod_chip) { + case NXT2002: + ret = nxt2002_init(fe); + break; + case NXT2004: + ret = nxt2004_init(fe); + break; + default: + return -EINVAL; + break; + } + state->initialised = 1; + } + return ret; +} + +static int nxt200x_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 500; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static void nxt200x_release(struct dvb_frontend* fe) +{ + struct nxt200x_state* state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops nxt200x_ops; + +struct dvb_frontend* nxt200x_attach(const struct nxt200x_config* config, + struct i2c_adapter* i2c) +{ + struct nxt200x_state* state = NULL; + u8 buf [] = {0,0,0,0,0}; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct nxt200x_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->initialised = 0; + + /* read card id */ + nxt200x_readbytes(state, 0x00, buf, 5); + dprintk("NXT info: %*ph\n", 5, buf); + + /* set demod chip */ + switch (buf[0]) { + case 0x04: + state->demod_chip = NXT2002; + pr_info("NXT2002 Detected\n"); + break; + case 0x05: + state->demod_chip = NXT2004; + pr_info("NXT2004 Detected\n"); + break; + default: + goto error; + } + + /* make sure demod chip is supported */ + switch (state->demod_chip) { + case NXT2002: + if (buf[0] != 0x04) goto error; /* device id */ + if (buf[1] != 0x02) goto error; /* fab id */ + if (buf[2] != 0x11) goto error; /* month */ + if (buf[3] != 0x20) goto error; /* year msb */ + if (buf[4] != 0x00) goto error; /* year lsb */ + break; + case NXT2004: + if (buf[0] != 0x05) goto error; /* device id */ + break; + default: + goto error; + } + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &nxt200x_ops, sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + pr_err("Unknown/Unsupported NXT chip: %*ph\n", 5, buf); + return NULL; +} + +static struct dvb_frontend_ops nxt200x_ops = { + .delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B }, + .info = { + .name = "Nextwave NXT200X VSB/QAM frontend", + .frequency_min = 54000000, + .frequency_max = 860000000, + .frequency_stepsize = 166666, /* stepsize is just a guess */ + .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_8VSB | FE_CAN_QAM_64 | FE_CAN_QAM_256 + }, + + .release = nxt200x_release, + + .init = nxt200x_init, + .sleep = nxt200x_sleep, + + .set_frontend = nxt200x_setup_frontend_parameters, + .get_tune_settings = nxt200x_get_tune_settings, + + .read_status = nxt200x_read_status, + .read_ber = nxt200x_read_ber, + .read_signal_strength = nxt200x_read_signal_strength, + .read_snr = nxt200x_read_snr, + .read_ucblocks = nxt200x_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("NXT200X (ATSC 8VSB & ITU-T J.83 AnnexB 64/256 QAM) Demodulator Driver"); +MODULE_AUTHOR("Kirk Lapray, Michael Krufky, Jean-Francois Thibert, and Taylor Jacob"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(nxt200x_attach); + diff --git a/drivers/media/dvb/frontends/nxt200x.h b/drivers/media/dvb-frontends/nxt200x.h similarity index 100% rename from drivers/media/dvb/frontends/nxt200x.h rename to drivers/media/dvb-frontends/nxt200x.h diff --git a/drivers/media/dvb/frontends/nxt6000.c b/drivers/media/dvb-frontends/nxt6000.c similarity index 100% rename from drivers/media/dvb/frontends/nxt6000.c rename to drivers/media/dvb-frontends/nxt6000.c diff --git a/drivers/media/dvb/frontends/nxt6000.h b/drivers/media/dvb-frontends/nxt6000.h similarity index 100% rename from drivers/media/dvb/frontends/nxt6000.h rename to drivers/media/dvb-frontends/nxt6000.h diff --git a/drivers/media/dvb/frontends/nxt6000_priv.h b/drivers/media/dvb-frontends/nxt6000_priv.h similarity index 100% rename from drivers/media/dvb/frontends/nxt6000_priv.h rename to drivers/media/dvb-frontends/nxt6000_priv.h diff --git a/drivers/media/dvb/frontends/or51132.c b/drivers/media/dvb-frontends/or51132.c similarity index 100% rename from drivers/media/dvb/frontends/or51132.c rename to drivers/media/dvb-frontends/or51132.c diff --git a/drivers/media/dvb/frontends/or51132.h b/drivers/media/dvb-frontends/or51132.h similarity index 100% rename from drivers/media/dvb/frontends/or51132.h rename to drivers/media/dvb-frontends/or51132.h diff --git a/drivers/media/dvb/frontends/or51211.c b/drivers/media/dvb-frontends/or51211.c similarity index 100% rename from drivers/media/dvb/frontends/or51211.c rename to drivers/media/dvb-frontends/or51211.c diff --git a/drivers/media/dvb/frontends/or51211.h b/drivers/media/dvb-frontends/or51211.h similarity index 100% rename from drivers/media/dvb/frontends/or51211.h rename to drivers/media/dvb-frontends/or51211.h diff --git a/drivers/media/dvb-frontends/rtl2830.c b/drivers/media/dvb-frontends/rtl2830.c new file mode 100644 index 000000000000..b0f6ec03d1eb --- /dev/null +++ b/drivers/media/dvb-frontends/rtl2830.c @@ -0,0 +1,761 @@ +/* + * Realtek RTL2830 DVB-T demodulator driver + * + * Copyright (C) 2011 Antti Palosaari + * + * 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. + */ + + +/* + * Driver implements own I2C-adapter for tuner I2C access. That's since chip + * have unusual I2C-gate control which closes gate automatically after each + * I2C transfer. Using own I2C adapter we can workaround that. + */ + +#include "rtl2830_priv.h" + +/* write multiple hardware registers */ +static int rtl2830_wr(struct rtl2830_priv *priv, u8 reg, const u8 *val, int len) +{ + int ret; + u8 buf[1+len]; + struct i2c_msg msg[1] = { + { + .addr = priv->cfg.i2c_addr, + .flags = 0, + .len = 1+len, + .buf = buf, + } + }; + + buf[0] = reg; + memcpy(&buf[1], val, len); + + ret = i2c_transfer(priv->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c wr failed=%d reg=%02x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* read multiple hardware registers */ +static int rtl2830_rd(struct rtl2830_priv *priv, u8 reg, u8 *val, int len) +{ + int ret; + struct i2c_msg msg[2] = { + { + .addr = priv->cfg.i2c_addr, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = priv->cfg.i2c_addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + } + }; + + ret = i2c_transfer(priv->i2c, msg, 2); + if (ret == 2) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c rd failed=%d reg=%02x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* write multiple registers */ +static int rtl2830_wr_regs(struct rtl2830_priv *priv, u16 reg, const u8 *val, + int len) +{ + int ret; + u8 reg2 = (reg >> 0) & 0xff; + u8 page = (reg >> 8) & 0xff; + + /* switch bank if needed */ + if (page != priv->page) { + ret = rtl2830_wr(priv, 0x00, &page, 1); + if (ret) + return ret; + + priv->page = page; + } + + return rtl2830_wr(priv, reg2, val, len); +} + +/* read multiple registers */ +static int rtl2830_rd_regs(struct rtl2830_priv *priv, u16 reg, u8 *val, int len) +{ + int ret; + u8 reg2 = (reg >> 0) & 0xff; + u8 page = (reg >> 8) & 0xff; + + /* switch bank if needed */ + if (page != priv->page) { + ret = rtl2830_wr(priv, 0x00, &page, 1); + if (ret) + return ret; + + priv->page = page; + } + + return rtl2830_rd(priv, reg2, val, len); +} + +/* read single register */ +static int rtl2830_rd_reg(struct rtl2830_priv *priv, u16 reg, u8 *val) +{ + return rtl2830_rd_regs(priv, reg, val, 1); +} + +/* write single register with mask */ +int rtl2830_wr_reg_mask(struct rtl2830_priv *priv, u16 reg, u8 val, u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = rtl2830_rd_regs(priv, reg, &tmp, 1); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return rtl2830_wr_regs(priv, reg, &val, 1); +} + +/* read single register with mask */ +int rtl2830_rd_reg_mask(struct rtl2830_priv *priv, u16 reg, u8 *val, u8 mask) +{ + int ret, i; + u8 tmp; + + ret = rtl2830_rd_regs(priv, reg, &tmp, 1); + if (ret) + return ret; + + tmp &= mask; + + /* find position of the first bit */ + for (i = 0; i < 8; i++) { + if ((mask >> i) & 0x01) + break; + } + *val = tmp >> i; + + return 0; +} + +static int rtl2830_init(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + int ret, i; + struct rtl2830_reg_val_mask tab[] = { + { 0x00d, 0x01, 0x03 }, + { 0x00d, 0x10, 0x10 }, + { 0x104, 0x00, 0x1e }, + { 0x105, 0x80, 0x80 }, + { 0x110, 0x02, 0x03 }, + { 0x110, 0x08, 0x0c }, + { 0x17b, 0x00, 0x40 }, + { 0x17d, 0x05, 0x0f }, + { 0x17d, 0x50, 0xf0 }, + { 0x18c, 0x08, 0x0f }, + { 0x18d, 0x00, 0xc0 }, + { 0x188, 0x05, 0x0f }, + { 0x189, 0x00, 0xfc }, + { 0x2d5, 0x02, 0x02 }, + { 0x2f1, 0x02, 0x06 }, + { 0x2f1, 0x20, 0xf8 }, + { 0x16d, 0x00, 0x01 }, + { 0x1a6, 0x00, 0x80 }, + { 0x106, priv->cfg.vtop, 0x3f }, + { 0x107, priv->cfg.krf, 0x3f }, + { 0x112, 0x28, 0xff }, + { 0x103, priv->cfg.agc_targ_val, 0xff }, + { 0x00a, 0x02, 0x07 }, + { 0x140, 0x0c, 0x3c }, + { 0x140, 0x40, 0xc0 }, + { 0x15b, 0x05, 0x07 }, + { 0x15b, 0x28, 0x38 }, + { 0x15c, 0x05, 0x07 }, + { 0x15c, 0x28, 0x38 }, + { 0x115, priv->cfg.spec_inv, 0x01 }, + { 0x16f, 0x01, 0x07 }, + { 0x170, 0x18, 0x38 }, + { 0x172, 0x0f, 0x0f }, + { 0x173, 0x08, 0x38 }, + { 0x175, 0x01, 0x07 }, + { 0x176, 0x00, 0xc0 }, + }; + + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = rtl2830_wr_reg_mask(priv, tab[i].reg, tab[i].val, + tab[i].mask); + if (ret) + goto err; + } + + ret = rtl2830_wr_regs(priv, 0x18f, "\x28\x00", 2); + if (ret) + goto err; + + ret = rtl2830_wr_regs(priv, 0x195, + "\x04\x06\x0a\x12\x0a\x12\x1e\x28", 8); + if (ret) + goto err; + + /* TODO: spec init */ + + /* soft reset */ + ret = rtl2830_wr_reg_mask(priv, 0x101, 0x04, 0x04); + if (ret) + goto err; + + ret = rtl2830_wr_reg_mask(priv, 0x101, 0x00, 0x04); + if (ret) + goto err; + + priv->sleeping = false; + + return ret; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2830_sleep(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + priv->sleeping = true; + return 0; +} + +int rtl2830_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 500; + s->step_size = fe->ops.info.frequency_stepsize * 2; + s->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; + + return 0; +} + +static int rtl2830_set_frontend(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i; + u64 num; + u8 buf[3], tmp; + u32 if_ctl, if_frequency; + static const u8 bw_params1[3][34] = { + { + 0x1f, 0xf0, 0x1f, 0xf0, 0x1f, 0xfa, 0x00, 0x17, 0x00, 0x41, + 0x00, 0x64, 0x00, 0x67, 0x00, 0x38, 0x1f, 0xde, 0x1f, 0x7a, + 0x1f, 0x47, 0x1f, 0x7c, 0x00, 0x30, 0x01, 0x4b, 0x02, 0x82, + 0x03, 0x73, 0x03, 0xcf, /* 6 MHz */ + }, { + 0x1f, 0xfa, 0x1f, 0xda, 0x1f, 0xc1, 0x1f, 0xb3, 0x1f, 0xca, + 0x00, 0x07, 0x00, 0x4d, 0x00, 0x6d, 0x00, 0x40, 0x1f, 0xca, + 0x1f, 0x4d, 0x1f, 0x2a, 0x1f, 0xb2, 0x00, 0xec, 0x02, 0x7e, + 0x03, 0xd0, 0x04, 0x53, /* 7 MHz */ + }, { + 0x00, 0x10, 0x00, 0x0e, 0x1f, 0xf7, 0x1f, 0xc9, 0x1f, 0xa0, + 0x1f, 0xa6, 0x1f, 0xec, 0x00, 0x4e, 0x00, 0x7d, 0x00, 0x3a, + 0x1f, 0x98, 0x1f, 0x10, 0x1f, 0x40, 0x00, 0x75, 0x02, 0x5f, + 0x04, 0x24, 0x04, 0xdb, /* 8 MHz */ + }, + }; + static const u8 bw_params2[3][6] = { + {0xc3, 0x0c, 0x44, 0x33, 0x33, 0x30}, /* 6 MHz */ + {0xb8, 0xe3, 0x93, 0x99, 0x99, 0x98}, /* 7 MHz */ + {0xae, 0xba, 0xf3, 0x26, 0x66, 0x64}, /* 8 MHz */ + }; + + dev_dbg(&priv->i2c->dev, + "%s: frequency=%d bandwidth_hz=%d inversion=%d\n", + __func__, c->frequency, c->bandwidth_hz, c->inversion); + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + switch (c->bandwidth_hz) { + case 6000000: + i = 0; + break; + case 7000000: + i = 1; + break; + case 8000000: + i = 2; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid bandwidth\n", __func__); + return -EINVAL; + } + + ret = rtl2830_wr_reg_mask(priv, 0x008, i << 1, 0x06); + if (ret) + goto err; + + /* program if frequency */ + if (fe->ops.tuner_ops.get_if_frequency) + ret = fe->ops.tuner_ops.get_if_frequency(fe, &if_frequency); + else + ret = -EINVAL; + + if (ret < 0) + goto err; + + num = if_frequency % priv->cfg.xtal; + num *= 0x400000; + num = div_u64(num, priv->cfg.xtal); + num = -num; + if_ctl = num & 0x3fffff; + dev_dbg(&priv->i2c->dev, "%s: if_frequency=%d if_ctl=%08x\n", + __func__, if_frequency, if_ctl); + + ret = rtl2830_rd_reg_mask(priv, 0x119, &tmp, 0xc0); /* b[7:6] */ + if (ret) + goto err; + + buf[0] = tmp << 6; + buf[0] |= (if_ctl >> 16) & 0x3f; + buf[1] = (if_ctl >> 8) & 0xff; + buf[2] = (if_ctl >> 0) & 0xff; + + ret = rtl2830_wr_regs(priv, 0x119, buf, 3); + if (ret) + goto err; + + /* 1/2 split I2C write */ + ret = rtl2830_wr_regs(priv, 0x11c, &bw_params1[i][0], 17); + if (ret) + goto err; + + /* 2/2 split I2C write */ + ret = rtl2830_wr_regs(priv, 0x12d, &bw_params1[i][17], 17); + if (ret) + goto err; + + ret = rtl2830_wr_regs(priv, 0x19d, bw_params2[i], 6); + if (ret) + goto err; + + return ret; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2830_get_frontend(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 buf[3]; + + if (priv->sleeping) + return 0; + + ret = rtl2830_rd_regs(priv, 0x33c, buf, 2); + if (ret) + goto err; + + ret = rtl2830_rd_reg(priv, 0x351, &buf[2]); + if (ret) + goto err; + + dev_dbg(&priv->i2c->dev, "%s: TPS=%*ph\n", __func__, 3, buf); + + switch ((buf[0] >> 2) & 3) { + case 0: + c->modulation = QPSK; + break; + case 1: + c->modulation = QAM_16; + break; + case 2: + c->modulation = QAM_64; + break; + } + + switch ((buf[2] >> 2) & 1) { + case 0: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case 1: + c->transmission_mode = TRANSMISSION_MODE_8K; + } + + switch ((buf[2] >> 0) & 3) { + case 0: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + switch ((buf[0] >> 4) & 7) { + case 0: + c->hierarchy = HIERARCHY_NONE; + break; + case 1: + c->hierarchy = HIERARCHY_1; + break; + case 2: + c->hierarchy = HIERARCHY_2; + break; + case 3: + c->hierarchy = HIERARCHY_4; + break; + } + + switch ((buf[1] >> 3) & 7) { + case 0: + c->code_rate_HP = FEC_1_2; + break; + case 1: + c->code_rate_HP = FEC_2_3; + break; + case 2: + c->code_rate_HP = FEC_3_4; + break; + case 3: + c->code_rate_HP = FEC_5_6; + break; + case 4: + c->code_rate_HP = FEC_7_8; + break; + } + + switch ((buf[1] >> 0) & 7) { + case 0: + c->code_rate_LP = FEC_1_2; + break; + case 1: + c->code_rate_LP = FEC_2_3; + break; + case 2: + c->code_rate_LP = FEC_3_4; + break; + case 3: + c->code_rate_LP = FEC_5_6; + break; + case 4: + c->code_rate_LP = FEC_7_8; + break; + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2830_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + int ret; + u8 tmp; + *status = 0; + + if (priv->sleeping) + return 0; + + ret = rtl2830_rd_reg_mask(priv, 0x351, &tmp, 0x78); /* [6:3] */ + if (ret) + goto err; + + if (tmp == 11) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + } else if (tmp == 10) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI; + } + + return ret; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2830_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + int ret, hierarchy, constellation; + u8 buf[2], tmp; + u16 tmp16; +#define CONSTELLATION_NUM 3 +#define HIERARCHY_NUM 4 + static const u32 snr_constant[CONSTELLATION_NUM][HIERARCHY_NUM] = { + { 70705899, 70705899, 70705899, 70705899 }, + { 82433173, 82433173, 87483115, 94445660 }, + { 92888734, 92888734, 95487525, 99770748 }, + }; + + if (priv->sleeping) + return 0; + + /* reports SNR in resolution of 0.1 dB */ + + ret = rtl2830_rd_reg(priv, 0x33c, &tmp); + if (ret) + goto err; + + constellation = (tmp >> 2) & 0x03; /* [3:2] */ + if (constellation > CONSTELLATION_NUM - 1) + goto err; + + hierarchy = (tmp >> 4) & 0x07; /* [6:4] */ + if (hierarchy > HIERARCHY_NUM - 1) + goto err; + + ret = rtl2830_rd_regs(priv, 0x40c, buf, 2); + if (ret) + goto err; + + tmp16 = buf[0] << 8 | buf[1]; + + if (tmp16) + *snr = (snr_constant[constellation][hierarchy] - + intlog10(tmp16)) / ((1 << 24) / 100); + else + *snr = 0; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2830_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + + if (priv->sleeping) + return 0; + + ret = rtl2830_rd_regs(priv, 0x34e, buf, 2); + if (ret) + goto err; + + *ber = buf[0] << 8 | buf[1]; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2830_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + return 0; +} + +static int rtl2830_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + u16 if_agc_raw, if_agc; + + if (priv->sleeping) + return 0; + + ret = rtl2830_rd_regs(priv, 0x359, buf, 2); + if (ret) + goto err; + + if_agc_raw = (buf[0] << 8 | buf[1]) & 0x3fff; + + if (if_agc_raw & (1 << 9)) + if_agc = -(~(if_agc_raw - 1) & 0x1ff); + else + if_agc = if_agc_raw; + + *strength = (u8) (55 - if_agc / 182); + *strength |= *strength << 8; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static struct dvb_frontend_ops rtl2830_ops; + +static u32 rtl2830_tuner_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static int rtl2830_tuner_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msg[], int num) +{ + struct rtl2830_priv *priv = i2c_get_adapdata(i2c_adap); + int ret; + + /* open i2c-gate */ + ret = rtl2830_wr_reg_mask(priv, 0x101, 0x08, 0x08); + if (ret) + goto err; + + ret = i2c_transfer(priv->i2c, msg, num); + if (ret < 0) + dev_warn(&priv->i2c->dev, "%s: tuner i2c failed=%d\n", + KBUILD_MODNAME, ret); + + return ret; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static struct i2c_algorithm rtl2830_tuner_i2c_algo = { + .master_xfer = rtl2830_tuner_i2c_xfer, + .functionality = rtl2830_tuner_i2c_func, +}; + +struct i2c_adapter *rtl2830_get_tuner_i2c_adapter(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + return &priv->tuner_i2c_adapter; +} +EXPORT_SYMBOL(rtl2830_get_tuner_i2c_adapter); + +static void rtl2830_release(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + + i2c_del_adapter(&priv->tuner_i2c_adapter); + kfree(priv); +} + +struct dvb_frontend *rtl2830_attach(const struct rtl2830_config *cfg, + struct i2c_adapter *i2c) +{ + struct rtl2830_priv *priv = NULL; + int ret = 0; + u8 tmp; + + /* allocate memory for the internal state */ + priv = kzalloc(sizeof(struct rtl2830_priv), GFP_KERNEL); + if (priv == NULL) + goto err; + + /* setup the priv */ + priv->i2c = i2c; + memcpy(&priv->cfg, cfg, sizeof(struct rtl2830_config)); + + /* check if the demod is there */ + ret = rtl2830_rd_reg(priv, 0x000, &tmp); + if (ret) + goto err; + + /* create dvb_frontend */ + memcpy(&priv->fe.ops, &rtl2830_ops, sizeof(struct dvb_frontend_ops)); + priv->fe.demodulator_priv = priv; + + /* create tuner i2c adapter */ + strlcpy(priv->tuner_i2c_adapter.name, "RTL2830 tuner I2C adapter", + sizeof(priv->tuner_i2c_adapter.name)); + priv->tuner_i2c_adapter.algo = &rtl2830_tuner_i2c_algo; + priv->tuner_i2c_adapter.algo_data = NULL; + i2c_set_adapdata(&priv->tuner_i2c_adapter, priv); + if (i2c_add_adapter(&priv->tuner_i2c_adapter) < 0) { + dev_err(&i2c->dev, + "%s: tuner i2c bus could not be initialized\n", + KBUILD_MODNAME); + goto err; + } + + priv->sleeping = true; + + return &priv->fe; +err: + dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret); + kfree(priv); + return NULL; +} +EXPORT_SYMBOL(rtl2830_attach); + +static struct dvb_frontend_ops rtl2830_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "Realtek RTL2830 (DVB-T)", + .caps = FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_64 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_RECOVER | + FE_CAN_MUTE_TS + }, + + .release = rtl2830_release, + + .init = rtl2830_init, + .sleep = rtl2830_sleep, + + .get_tune_settings = rtl2830_get_tune_settings, + + .set_frontend = rtl2830_set_frontend, + .get_frontend = rtl2830_get_frontend, + + .read_status = rtl2830_read_status, + .read_snr = rtl2830_read_snr, + .read_ber = rtl2830_read_ber, + .read_ucblocks = rtl2830_read_ucblocks, + .read_signal_strength = rtl2830_read_signal_strength, +}; + +MODULE_AUTHOR("Antti Palosaari "); +MODULE_DESCRIPTION("Realtek RTL2830 DVB-T demodulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/rtl2830.h b/drivers/media/dvb-frontends/rtl2830.h new file mode 100644 index 000000000000..f4349a1fc03e --- /dev/null +++ b/drivers/media/dvb-frontends/rtl2830.h @@ -0,0 +1,90 @@ +/* + * Realtek RTL2830 DVB-T demodulator driver + * + * Copyright (C) 2011 Antti Palosaari + * + * 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. + */ + +#ifndef RTL2830_H +#define RTL2830_H + +#include + +struct rtl2830_config { + /* + * Demodulator I2C address. + */ + u8 i2c_addr; + + /* + * Xtal frequency. + * Hz + * 4000000, 16000000, 25000000, 28800000 + */ + u32 xtal; + + /* + * TS output mode. + */ + u8 ts_mode; + + /* + * Spectrum inversion. + */ + bool spec_inv; + + /* + */ + u8 vtop; + + /* + */ + u8 krf; + + /* + */ + u8 agc_targ_val; +}; + +#if defined(CONFIG_DVB_RTL2830) || \ + (defined(CONFIG_DVB_RTL2830_MODULE) && defined(MODULE)) +extern struct dvb_frontend *rtl2830_attach( + const struct rtl2830_config *config, + struct i2c_adapter *i2c +); + +extern struct i2c_adapter *rtl2830_get_tuner_i2c_adapter( + struct dvb_frontend *fe +); +#else +static inline struct dvb_frontend *rtl2830_attach( + const struct rtl2830_config *config, + struct i2c_adapter *i2c +) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +static inline struct i2c_adapter *rtl2830_get_tuner_i2c_adapter( + struct dvb_frontend *fe +) +{ + return NULL; +} +#endif + +#endif /* RTL2830_H */ diff --git a/drivers/media/dvb-frontends/rtl2830_priv.h b/drivers/media/dvb-frontends/rtl2830_priv.h new file mode 100644 index 000000000000..fab10ecb3c3b --- /dev/null +++ b/drivers/media/dvb-frontends/rtl2830_priv.h @@ -0,0 +1,45 @@ +/* + * Realtek RTL2830 DVB-T demodulator driver + * + * Copyright (C) 2011 Antti Palosaari + * + * 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. + */ + +#ifndef RTL2830_PRIV_H +#define RTL2830_PRIV_H + +#include "dvb_frontend.h" +#include "dvb_math.h" +#include "rtl2830.h" + +struct rtl2830_priv { + struct i2c_adapter *i2c; + struct dvb_frontend fe; + struct rtl2830_config cfg; + struct i2c_adapter tuner_i2c_adapter; + + bool sleeping; + + u8 page; /* active register page */ +}; + +struct rtl2830_reg_val_mask { + u16 reg; + u8 val; + u8 mask; +}; + +#endif /* RTL2830_PRIV_H */ diff --git a/drivers/media/dvb-frontends/rtl2832.c b/drivers/media/dvb-frontends/rtl2832.c new file mode 100644 index 000000000000..80c8e5f1182f --- /dev/null +++ b/drivers/media/dvb-frontends/rtl2832.c @@ -0,0 +1,932 @@ +/* + * Realtek RTL2832 DVB-T demodulator driver + * + * Copyright (C) 2012 Thomas Mair + * + * 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. + */ + +#include "rtl2832_priv.h" +#include "dvb_math.h" +#include + +int rtl2832_debug; +module_param_named(debug, rtl2832_debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +#define REG_MASK(b) (BIT(b + 1) - 1) + +static const struct rtl2832_reg_entry registers[] = { + [DVBT_SOFT_RST] = {0x1, 0x1, 2, 2}, + [DVBT_IIC_REPEAT] = {0x1, 0x1, 3, 3}, + [DVBT_TR_WAIT_MIN_8K] = {0x1, 0x88, 11, 2}, + [DVBT_RSD_BER_FAIL_VAL] = {0x1, 0x8f, 15, 0}, + [DVBT_EN_BK_TRK] = {0x1, 0xa6, 7, 7}, + [DVBT_AD_EN_REG] = {0x0, 0x8, 7, 7}, + [DVBT_AD_EN_REG1] = {0x0, 0x8, 6, 6}, + [DVBT_EN_BBIN] = {0x1, 0xb1, 0, 0}, + [DVBT_MGD_THD0] = {0x1, 0x95, 7, 0}, + [DVBT_MGD_THD1] = {0x1, 0x96, 7, 0}, + [DVBT_MGD_THD2] = {0x1, 0x97, 7, 0}, + [DVBT_MGD_THD3] = {0x1, 0x98, 7, 0}, + [DVBT_MGD_THD4] = {0x1, 0x99, 7, 0}, + [DVBT_MGD_THD5] = {0x1, 0x9a, 7, 0}, + [DVBT_MGD_THD6] = {0x1, 0x9b, 7, 0}, + [DVBT_MGD_THD7] = {0x1, 0x9c, 7, 0}, + [DVBT_EN_CACQ_NOTCH] = {0x1, 0x61, 4, 4}, + [DVBT_AD_AV_REF] = {0x0, 0x9, 6, 0}, + [DVBT_REG_PI] = {0x0, 0xa, 2, 0}, + [DVBT_PIP_ON] = {0x0, 0x21, 3, 3}, + [DVBT_SCALE1_B92] = {0x2, 0x92, 7, 0}, + [DVBT_SCALE1_B93] = {0x2, 0x93, 7, 0}, + [DVBT_SCALE1_BA7] = {0x2, 0xa7, 7, 0}, + [DVBT_SCALE1_BA9] = {0x2, 0xa9, 7, 0}, + [DVBT_SCALE1_BAA] = {0x2, 0xaa, 7, 0}, + [DVBT_SCALE1_BAB] = {0x2, 0xab, 7, 0}, + [DVBT_SCALE1_BAC] = {0x2, 0xac, 7, 0}, + [DVBT_SCALE1_BB0] = {0x2, 0xb0, 7, 0}, + [DVBT_SCALE1_BB1] = {0x2, 0xb1, 7, 0}, + [DVBT_KB_P1] = {0x1, 0x64, 3, 1}, + [DVBT_KB_P2] = {0x1, 0x64, 6, 4}, + [DVBT_KB_P3] = {0x1, 0x65, 2, 0}, + [DVBT_OPT_ADC_IQ] = {0x0, 0x6, 5, 4}, + [DVBT_AD_AVI] = {0x0, 0x9, 1, 0}, + [DVBT_AD_AVQ] = {0x0, 0x9, 3, 2}, + [DVBT_K1_CR_STEP12] = {0x2, 0xad, 9, 4}, + [DVBT_TRK_KS_P2] = {0x1, 0x6f, 2, 0}, + [DVBT_TRK_KS_I2] = {0x1, 0x70, 5, 3}, + [DVBT_TR_THD_SET2] = {0x1, 0x72, 3, 0}, + [DVBT_TRK_KC_P2] = {0x1, 0x73, 5, 3}, + [DVBT_TRK_KC_I2] = {0x1, 0x75, 2, 0}, + [DVBT_CR_THD_SET2] = {0x1, 0x76, 7, 6}, + [DVBT_PSET_IFFREQ] = {0x1, 0x19, 21, 0}, + [DVBT_SPEC_INV] = {0x1, 0x15, 0, 0}, + [DVBT_RSAMP_RATIO] = {0x1, 0x9f, 27, 2}, + [DVBT_CFREQ_OFF_RATIO] = {0x1, 0x9d, 23, 4}, + [DVBT_FSM_STAGE] = {0x3, 0x51, 6, 3}, + [DVBT_RX_CONSTEL] = {0x3, 0x3c, 3, 2}, + [DVBT_RX_HIER] = {0x3, 0x3c, 6, 4}, + [DVBT_RX_C_RATE_LP] = {0x3, 0x3d, 2, 0}, + [DVBT_RX_C_RATE_HP] = {0x3, 0x3d, 5, 3}, + [DVBT_GI_IDX] = {0x3, 0x51, 1, 0}, + [DVBT_FFT_MODE_IDX] = {0x3, 0x51, 2, 2}, + [DVBT_RSD_BER_EST] = {0x3, 0x4e, 15, 0}, + [DVBT_CE_EST_EVM] = {0x4, 0xc, 15, 0}, + [DVBT_RF_AGC_VAL] = {0x3, 0x5b, 13, 0}, + [DVBT_IF_AGC_VAL] = {0x3, 0x59, 13, 0}, + [DVBT_DAGC_VAL] = {0x3, 0x5, 7, 0}, + [DVBT_SFREQ_OFF] = {0x3, 0x18, 13, 0}, + [DVBT_CFREQ_OFF] = {0x3, 0x5f, 17, 0}, + [DVBT_POLAR_RF_AGC] = {0x0, 0xe, 1, 1}, + [DVBT_POLAR_IF_AGC] = {0x0, 0xe, 0, 0}, + [DVBT_AAGC_HOLD] = {0x1, 0x4, 5, 5}, + [DVBT_EN_RF_AGC] = {0x1, 0x4, 6, 6}, + [DVBT_EN_IF_AGC] = {0x1, 0x4, 7, 7}, + [DVBT_IF_AGC_MIN] = {0x1, 0x8, 7, 0}, + [DVBT_IF_AGC_MAX] = {0x1, 0x9, 7, 0}, + [DVBT_RF_AGC_MIN] = {0x1, 0xa, 7, 0}, + [DVBT_RF_AGC_MAX] = {0x1, 0xb, 7, 0}, + [DVBT_IF_AGC_MAN] = {0x1, 0xc, 6, 6}, + [DVBT_IF_AGC_MAN_VAL] = {0x1, 0xc, 13, 0}, + [DVBT_RF_AGC_MAN] = {0x1, 0xe, 6, 6}, + [DVBT_RF_AGC_MAN_VAL] = {0x1, 0xe, 13, 0}, + [DVBT_DAGC_TRG_VAL] = {0x1, 0x12, 7, 0}, + [DVBT_AGC_TARG_VAL_0] = {0x1, 0x2, 0, 0}, + [DVBT_AGC_TARG_VAL_8_1] = {0x1, 0x3, 7, 0}, + [DVBT_AAGC_LOOP_GAIN] = {0x1, 0xc7, 5, 1}, + [DVBT_LOOP_GAIN2_3_0] = {0x1, 0x4, 4, 1}, + [DVBT_LOOP_GAIN2_4] = {0x1, 0x5, 7, 7}, + [DVBT_LOOP_GAIN3] = {0x1, 0xc8, 4, 0}, + [DVBT_VTOP1] = {0x1, 0x6, 5, 0}, + [DVBT_VTOP2] = {0x1, 0xc9, 5, 0}, + [DVBT_VTOP3] = {0x1, 0xca, 5, 0}, + [DVBT_KRF1] = {0x1, 0xcb, 7, 0}, + [DVBT_KRF2] = {0x1, 0x7, 7, 0}, + [DVBT_KRF3] = {0x1, 0xcd, 7, 0}, + [DVBT_KRF4] = {0x1, 0xce, 7, 0}, + [DVBT_EN_GI_PGA] = {0x1, 0xe5, 0, 0}, + [DVBT_THD_LOCK_UP] = {0x1, 0xd9, 8, 0}, + [DVBT_THD_LOCK_DW] = {0x1, 0xdb, 8, 0}, + [DVBT_THD_UP1] = {0x1, 0xdd, 7, 0}, + [DVBT_THD_DW1] = {0x1, 0xde, 7, 0}, + [DVBT_INTER_CNT_LEN] = {0x1, 0xd8, 3, 0}, + [DVBT_GI_PGA_STATE] = {0x1, 0xe6, 3, 3}, + [DVBT_EN_AGC_PGA] = {0x1, 0xd7, 0, 0}, + [DVBT_CKOUTPAR] = {0x1, 0x7b, 5, 5}, + [DVBT_CKOUT_PWR] = {0x1, 0x7b, 6, 6}, + [DVBT_SYNC_DUR] = {0x1, 0x7b, 7, 7}, + [DVBT_ERR_DUR] = {0x1, 0x7c, 0, 0}, + [DVBT_SYNC_LVL] = {0x1, 0x7c, 1, 1}, + [DVBT_ERR_LVL] = {0x1, 0x7c, 2, 2}, + [DVBT_VAL_LVL] = {0x1, 0x7c, 3, 3}, + [DVBT_SERIAL] = {0x1, 0x7c, 4, 4}, + [DVBT_SER_LSB] = {0x1, 0x7c, 5, 5}, + [DVBT_CDIV_PH0] = {0x1, 0x7d, 3, 0}, + [DVBT_CDIV_PH1] = {0x1, 0x7d, 7, 4}, + [DVBT_MPEG_IO_OPT_2_2] = {0x0, 0x6, 7, 7}, + [DVBT_MPEG_IO_OPT_1_0] = {0x0, 0x7, 7, 6}, + [DVBT_CKOUTPAR_PIP] = {0x0, 0xb7, 4, 4}, + [DVBT_CKOUT_PWR_PIP] = {0x0, 0xb7, 3, 3}, + [DVBT_SYNC_LVL_PIP] = {0x0, 0xb7, 2, 2}, + [DVBT_ERR_LVL_PIP] = {0x0, 0xb7, 1, 1}, + [DVBT_VAL_LVL_PIP] = {0x0, 0xb7, 0, 0}, + [DVBT_CKOUTPAR_PID] = {0x0, 0xb9, 4, 4}, + [DVBT_CKOUT_PWR_PID] = {0x0, 0xb9, 3, 3}, + [DVBT_SYNC_LVL_PID] = {0x0, 0xb9, 2, 2}, + [DVBT_ERR_LVL_PID] = {0x0, 0xb9, 1, 1}, + [DVBT_VAL_LVL_PID] = {0x0, 0xb9, 0, 0}, + [DVBT_SM_PASS] = {0x1, 0x93, 11, 0}, + [DVBT_AD7_SETTING] = {0x0, 0x11, 15, 0}, + [DVBT_RSSI_R] = {0x3, 0x1, 6, 0}, + [DVBT_ACI_DET_IND] = {0x3, 0x12, 0, 0}, + [DVBT_REG_MON] = {0x0, 0xd, 1, 0}, + [DVBT_REG_MONSEL] = {0x0, 0xd, 2, 2}, + [DVBT_REG_GPE] = {0x0, 0xd, 7, 7}, + [DVBT_REG_GPO] = {0x0, 0x10, 0, 0}, + [DVBT_REG_4MSEL] = {0x0, 0x13, 0, 0}, +}; + +/* write multiple hardware registers */ +static int rtl2832_wr(struct rtl2832_priv *priv, u8 reg, u8 *val, int len) +{ + int ret; + u8 buf[1+len]; + struct i2c_msg msg[1] = { + { + .addr = priv->cfg.i2c_addr, + .flags = 0, + .len = 1+len, + .buf = buf, + } + }; + + buf[0] = reg; + memcpy(&buf[1], val, len); + + ret = i2c_transfer(priv->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c wr failed=%d reg=%02x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* read multiple hardware registers */ +static int rtl2832_rd(struct rtl2832_priv *priv, u8 reg, u8 *val, int len) +{ + int ret; + struct i2c_msg msg[2] = { + { + .addr = priv->cfg.i2c_addr, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = priv->cfg.i2c_addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + } + }; + + ret = i2c_transfer(priv->i2c, msg, 2); + if (ret == 2) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c rd failed=%d reg=%02x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* write multiple registers */ +static int rtl2832_wr_regs(struct rtl2832_priv *priv, u8 reg, u8 page, u8 *val, + int len) +{ + int ret; + + /* switch bank if needed */ + if (page != priv->page) { + ret = rtl2832_wr(priv, 0x00, &page, 1); + if (ret) + return ret; + + priv->page = page; +} + +return rtl2832_wr(priv, reg, val, len); +} + +/* read multiple registers */ +static int rtl2832_rd_regs(struct rtl2832_priv *priv, u8 reg, u8 page, u8 *val, + int len) +{ + int ret; + + /* switch bank if needed */ + if (page != priv->page) { + ret = rtl2832_wr(priv, 0x00, &page, 1); + if (ret) + return ret; + + priv->page = page; + } + + return rtl2832_rd(priv, reg, val, len); +} + +#if 0 /* currently not used */ +/* write single register */ +static int rtl2832_wr_reg(struct rtl2832_priv *priv, u8 reg, u8 page, u8 val) +{ + return rtl2832_wr_regs(priv, reg, page, &val, 1); +} +#endif + +/* read single register */ +static int rtl2832_rd_reg(struct rtl2832_priv *priv, u8 reg, u8 page, u8 *val) +{ + return rtl2832_rd_regs(priv, reg, page, val, 1); +} + +int rtl2832_rd_demod_reg(struct rtl2832_priv *priv, int reg, u32 *val) +{ + int ret; + + u8 reg_start_addr; + u8 msb, lsb; + u8 page; + u8 reading[4]; + u32 reading_tmp; + int i; + + u8 len; + u32 mask; + + reg_start_addr = registers[reg].start_address; + msb = registers[reg].msb; + lsb = registers[reg].lsb; + page = registers[reg].page; + + len = (msb >> 3) + 1; + mask = REG_MASK(msb - lsb); + + ret = rtl2832_rd_regs(priv, reg_start_addr, page, &reading[0], len); + if (ret) + goto err; + + reading_tmp = 0; + for (i = 0; i < len; i++) + reading_tmp |= reading[i] << ((len - 1 - i) * 8); + + *val = (reading_tmp >> lsb) & mask; + + return ret; + +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; + +} + +int rtl2832_wr_demod_reg(struct rtl2832_priv *priv, int reg, u32 val) +{ + int ret, i; + u8 len; + u8 reg_start_addr; + u8 msb, lsb; + u8 page; + u32 mask; + + + u8 reading[4]; + u8 writing[4]; + u32 reading_tmp; + u32 writing_tmp; + + + reg_start_addr = registers[reg].start_address; + msb = registers[reg].msb; + lsb = registers[reg].lsb; + page = registers[reg].page; + + len = (msb >> 3) + 1; + mask = REG_MASK(msb - lsb); + + + ret = rtl2832_rd_regs(priv, reg_start_addr, page, &reading[0], len); + if (ret) + goto err; + + reading_tmp = 0; + for (i = 0; i < len; i++) + reading_tmp |= reading[i] << ((len - 1 - i) * 8); + + writing_tmp = reading_tmp & ~(mask << lsb); + writing_tmp |= ((val & mask) << lsb); + + + for (i = 0; i < len; i++) + writing[i] = (writing_tmp >> ((len - 1 - i) * 8)) & 0xff; + + ret = rtl2832_wr_regs(priv, reg_start_addr, page, &writing[0], len); + if (ret) + goto err; + + return ret; + +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; + +} + +static int rtl2832_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + int ret; + struct rtl2832_priv *priv = fe->demodulator_priv; + + dev_dbg(&priv->i2c->dev, "%s: enable=%d\n", __func__, enable); + + /* gate already open or close */ + if (priv->i2c_gate_state == enable) + return 0; + + ret = rtl2832_wr_demod_reg(priv, DVBT_IIC_REPEAT, (enable ? 0x1 : 0x0)); + if (ret) + goto err; + + priv->i2c_gate_state = enable; + + return ret; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832_init(struct dvb_frontend *fe) +{ + struct rtl2832_priv *priv = fe->demodulator_priv; + int i, ret, len; + u8 en_bbin; + u64 pset_iffreq; + const struct rtl2832_reg_value *init; + + /* initialization values for the demodulator registers */ + struct rtl2832_reg_value rtl2832_initial_regs[] = { + {DVBT_AD_EN_REG, 0x1}, + {DVBT_AD_EN_REG1, 0x1}, + {DVBT_RSD_BER_FAIL_VAL, 0x2800}, + {DVBT_MGD_THD0, 0x10}, + {DVBT_MGD_THD1, 0x20}, + {DVBT_MGD_THD2, 0x20}, + {DVBT_MGD_THD3, 0x40}, + {DVBT_MGD_THD4, 0x22}, + {DVBT_MGD_THD5, 0x32}, + {DVBT_MGD_THD6, 0x37}, + {DVBT_MGD_THD7, 0x39}, + {DVBT_EN_BK_TRK, 0x0}, + {DVBT_EN_CACQ_NOTCH, 0x0}, + {DVBT_AD_AV_REF, 0x2a}, + {DVBT_REG_PI, 0x6}, + {DVBT_PIP_ON, 0x0}, + {DVBT_CDIV_PH0, 0x8}, + {DVBT_CDIV_PH1, 0x8}, + {DVBT_SCALE1_B92, 0x4}, + {DVBT_SCALE1_B93, 0xb0}, + {DVBT_SCALE1_BA7, 0x78}, + {DVBT_SCALE1_BA9, 0x28}, + {DVBT_SCALE1_BAA, 0x59}, + {DVBT_SCALE1_BAB, 0x83}, + {DVBT_SCALE1_BAC, 0xd4}, + {DVBT_SCALE1_BB0, 0x65}, + {DVBT_SCALE1_BB1, 0x43}, + {DVBT_KB_P1, 0x1}, + {DVBT_KB_P2, 0x4}, + {DVBT_KB_P3, 0x7}, + {DVBT_K1_CR_STEP12, 0xa}, + {DVBT_REG_GPE, 0x1}, + {DVBT_SERIAL, 0x0}, + {DVBT_CDIV_PH0, 0x9}, + {DVBT_CDIV_PH1, 0x9}, + {DVBT_MPEG_IO_OPT_2_2, 0x0}, + {DVBT_MPEG_IO_OPT_1_0, 0x0}, + {DVBT_TRK_KS_P2, 0x4}, + {DVBT_TRK_KS_I2, 0x7}, + {DVBT_TR_THD_SET2, 0x6}, + {DVBT_TRK_KC_I2, 0x5}, + {DVBT_CR_THD_SET2, 0x1}, + {DVBT_SPEC_INV, 0x0}, + }; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + en_bbin = (priv->cfg.if_dvbt == 0 ? 0x1 : 0x0); + + /* + * PSET_IFFREQ = - floor((IfFreqHz % CrystalFreqHz) * pow(2, 22) + * / CrystalFreqHz) + */ + pset_iffreq = priv->cfg.if_dvbt % priv->cfg.xtal; + pset_iffreq *= 0x400000; + pset_iffreq = div_u64(pset_iffreq, priv->cfg.xtal); + pset_iffreq = pset_iffreq & 0x3fffff; + + for (i = 0; i < ARRAY_SIZE(rtl2832_initial_regs); i++) { + ret = rtl2832_wr_demod_reg(priv, rtl2832_initial_regs[i].reg, + rtl2832_initial_regs[i].value); + if (ret) + goto err; + } + + /* load tuner specific settings */ + dev_dbg(&priv->i2c->dev, "%s: load settings for tuner=%02x\n", + __func__, priv->cfg.tuner); + switch (priv->cfg.tuner) { + case RTL2832_TUNER_FC0012: + case RTL2832_TUNER_FC0013: + len = ARRAY_SIZE(rtl2832_tuner_init_fc0012); + init = rtl2832_tuner_init_fc0012; + break; + case RTL2832_TUNER_TUA9001: + len = ARRAY_SIZE(rtl2832_tuner_init_tua9001); + init = rtl2832_tuner_init_tua9001; + break; + case RTL2832_TUNER_E4000: + len = ARRAY_SIZE(rtl2832_tuner_init_e4000); + init = rtl2832_tuner_init_e4000; + break; + default: + ret = -EINVAL; + goto err; + } + + for (i = 0; i < len; i++) { + ret = rtl2832_wr_demod_reg(priv, init[i].reg, init[i].value); + if (ret) + goto err; + } + + /* if frequency settings */ + ret = rtl2832_wr_demod_reg(priv, DVBT_EN_BBIN, en_bbin); + if (ret) + goto err; + + ret = rtl2832_wr_demod_reg(priv, DVBT_PSET_IFFREQ, pset_iffreq); + if (ret) + goto err; + + priv->sleeping = false; + + return ret; + +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832_sleep(struct dvb_frontend *fe) +{ + struct rtl2832_priv *priv = fe->demodulator_priv; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + priv->sleeping = true; + return 0; +} + +int rtl2832_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + struct rtl2832_priv *priv = fe->demodulator_priv; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + s->min_delay_ms = 1000; + s->step_size = fe->ops.info.frequency_stepsize * 2; + s->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; + return 0; +} + +static int rtl2832_set_frontend(struct dvb_frontend *fe) +{ + struct rtl2832_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i, j; + u64 bw_mode, num, num2; + u32 resamp_ratio, cfreq_off_ratio; + static u8 bw_params[3][32] = { + /* 6 MHz bandwidth */ + { + 0xf5, 0xff, 0x15, 0x38, 0x5d, 0x6d, 0x52, 0x07, 0xfa, 0x2f, + 0x53, 0xf5, 0x3f, 0xca, 0x0b, 0x91, 0xea, 0x30, 0x63, 0xb2, + 0x13, 0xda, 0x0b, 0xc4, 0x18, 0x7e, 0x16, 0x66, 0x08, 0x67, + 0x19, 0xe0, + }, + + /* 7 MHz bandwidth */ + { + 0xe7, 0xcc, 0xb5, 0xba, 0xe8, 0x2f, 0x67, 0x61, 0x00, 0xaf, + 0x86, 0xf2, 0xbf, 0x59, 0x04, 0x11, 0xb6, 0x33, 0xa4, 0x30, + 0x15, 0x10, 0x0a, 0x42, 0x18, 0xf8, 0x17, 0xd9, 0x07, 0x22, + 0x19, 0x10, + }, + + /* 8 MHz bandwidth */ + { + 0x09, 0xf6, 0xd2, 0xa7, 0x9a, 0xc9, 0x27, 0x77, 0x06, 0xbf, + 0xec, 0xf4, 0x4f, 0x0b, 0xfc, 0x01, 0x63, 0x35, 0x54, 0xa7, + 0x16, 0x66, 0x08, 0xb4, 0x19, 0x6e, 0x19, 0x65, 0x05, 0xc8, + 0x19, 0xe0, + }, + }; + + + dev_dbg(&priv->i2c->dev, "%s: frequency=%d bandwidth_hz=%d " \ + "inversion=%d\n", __func__, c->frequency, + c->bandwidth_hz, c->inversion); + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + switch (c->bandwidth_hz) { + case 6000000: + i = 0; + bw_mode = 48000000; + break; + case 7000000: + i = 1; + bw_mode = 56000000; + break; + case 8000000: + i = 2; + bw_mode = 64000000; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid bandwidth\n", __func__); + return -EINVAL; + } + + for (j = 0; j < sizeof(bw_params[0]); j++) { + ret = rtl2832_wr_regs(priv, 0x1c+j, 1, &bw_params[i][j], 1); + if (ret) + goto err; + } + + /* calculate and set resample ratio + * RSAMP_RATIO = floor(CrystalFreqHz * 7 * pow(2, 22) + * / ConstWithBandwidthMode) + */ + num = priv->cfg.xtal * 7; + num *= 0x400000; + num = div_u64(num, bw_mode); + resamp_ratio = num & 0x3ffffff; + ret = rtl2832_wr_demod_reg(priv, DVBT_RSAMP_RATIO, resamp_ratio); + if (ret) + goto err; + + /* calculate and set cfreq off ratio + * CFREQ_OFF_RATIO = - floor(ConstWithBandwidthMode * pow(2, 20) + * / (CrystalFreqHz * 7)) + */ + num = bw_mode << 20; + num2 = priv->cfg.xtal * 7; + num = div_u64(num, num2); + num = -num; + cfreq_off_ratio = num & 0xfffff; + ret = rtl2832_wr_demod_reg(priv, DVBT_CFREQ_OFF_RATIO, cfreq_off_ratio); + if (ret) + goto err; + + + /* soft reset */ + ret = rtl2832_wr_demod_reg(priv, DVBT_SOFT_RST, 0x1); + if (ret) + goto err; + + ret = rtl2832_wr_demod_reg(priv, DVBT_SOFT_RST, 0x0); + if (ret) + goto err; + + return ret; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832_get_frontend(struct dvb_frontend *fe) +{ + struct rtl2832_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + u8 buf[3]; + + if (priv->sleeping) + return 0; + + ret = rtl2832_rd_regs(priv, 0x3c, 3, buf, 2); + if (ret) + goto err; + + ret = rtl2832_rd_reg(priv, 0x51, 3, &buf[2]); + if (ret) + goto err; + + dev_dbg(&priv->i2c->dev, "%s: TPS=%*ph\n", __func__, 3, buf); + + switch ((buf[0] >> 2) & 3) { + case 0: + c->modulation = QPSK; + break; + case 1: + c->modulation = QAM_16; + break; + case 2: + c->modulation = QAM_64; + break; + } + + switch ((buf[2] >> 2) & 1) { + case 0: + c->transmission_mode = TRANSMISSION_MODE_2K; + break; + case 1: + c->transmission_mode = TRANSMISSION_MODE_8K; + } + + switch ((buf[2] >> 0) & 3) { + case 0: + c->guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + c->guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + c->guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + c->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + switch ((buf[0] >> 4) & 7) { + case 0: + c->hierarchy = HIERARCHY_NONE; + break; + case 1: + c->hierarchy = HIERARCHY_1; + break; + case 2: + c->hierarchy = HIERARCHY_2; + break; + case 3: + c->hierarchy = HIERARCHY_4; + break; + } + + switch ((buf[1] >> 3) & 7) { + case 0: + c->code_rate_HP = FEC_1_2; + break; + case 1: + c->code_rate_HP = FEC_2_3; + break; + case 2: + c->code_rate_HP = FEC_3_4; + break; + case 3: + c->code_rate_HP = FEC_5_6; + break; + case 4: + c->code_rate_HP = FEC_7_8; + break; + } + + switch ((buf[1] >> 0) & 7) { + case 0: + c->code_rate_LP = FEC_1_2; + break; + case 1: + c->code_rate_LP = FEC_2_3; + break; + case 2: + c->code_rate_LP = FEC_3_4; + break; + case 3: + c->code_rate_LP = FEC_5_6; + break; + case 4: + c->code_rate_LP = FEC_7_8; + break; + } + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct rtl2832_priv *priv = fe->demodulator_priv; + int ret; + u32 tmp; + *status = 0; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + if (priv->sleeping) + return 0; + + ret = rtl2832_rd_demod_reg(priv, DVBT_FSM_STAGE, &tmp); + if (ret) + goto err; + + if (tmp == 11) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + } + /* TODO find out if this is also true for rtl2832? */ + /*else if (tmp == 10) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI; + }*/ + + return ret; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct rtl2832_priv *priv = fe->demodulator_priv; + int ret, hierarchy, constellation; + u8 buf[2], tmp; + u16 tmp16; +#define CONSTELLATION_NUM 3 +#define HIERARCHY_NUM 4 + static const u32 snr_constant[CONSTELLATION_NUM][HIERARCHY_NUM] = { + { 85387325, 85387325, 85387325, 85387325 }, + { 86676178, 86676178, 87167949, 87795660 }, + { 87659938, 87659938, 87885178, 88241743 }, + }; + + /* reports SNR in resolution of 0.1 dB */ + + ret = rtl2832_rd_reg(priv, 0x3c, 3, &tmp); + if (ret) + goto err; + + constellation = (tmp >> 2) & 0x03; /* [3:2] */ + if (constellation > CONSTELLATION_NUM - 1) + goto err; + + hierarchy = (tmp >> 4) & 0x07; /* [6:4] */ + if (hierarchy > HIERARCHY_NUM - 1) + goto err; + + ret = rtl2832_rd_regs(priv, 0x0c, 4, buf, 2); + if (ret) + goto err; + + tmp16 = buf[0] << 8 | buf[1]; + + if (tmp16) + *snr = (snr_constant[constellation][hierarchy] - + intlog10(tmp16)) / ((1 << 24) / 100); + else + *snr = 0; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct rtl2832_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + + ret = rtl2832_rd_regs(priv, 0x4e, 3, buf, 2); + if (ret) + goto err; + + *ber = buf[0] << 8 | buf[1]; + + return 0; +err: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static struct dvb_frontend_ops rtl2832_ops; + +static void rtl2832_release(struct dvb_frontend *fe) +{ + struct rtl2832_priv *priv = fe->demodulator_priv; + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + kfree(priv); +} + +struct dvb_frontend *rtl2832_attach(const struct rtl2832_config *cfg, + struct i2c_adapter *i2c) +{ + struct rtl2832_priv *priv = NULL; + int ret = 0; + u8 tmp; + + dev_dbg(&i2c->dev, "%s:\n", __func__); + + /* allocate memory for the internal state */ + priv = kzalloc(sizeof(struct rtl2832_priv), GFP_KERNEL); + if (priv == NULL) + goto err; + + /* setup the priv */ + priv->i2c = i2c; + priv->tuner = cfg->tuner; + memcpy(&priv->cfg, cfg, sizeof(struct rtl2832_config)); + + /* check if the demod is there */ + ret = rtl2832_rd_reg(priv, 0x00, 0x0, &tmp); + if (ret) + goto err; + + /* create dvb_frontend */ + memcpy(&priv->fe.ops, &rtl2832_ops, sizeof(struct dvb_frontend_ops)); + priv->fe.demodulator_priv = priv; + + /* TODO implement sleep mode */ + priv->sleeping = true; + + return &priv->fe; +err: + dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret); + kfree(priv); + return NULL; +} +EXPORT_SYMBOL(rtl2832_attach); + +static struct dvb_frontend_ops rtl2832_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "Realtek RTL2832 (DVB-T)", + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 166667, + .caps = FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_64 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_RECOVER | + FE_CAN_MUTE_TS + }, + + .release = rtl2832_release, + + .init = rtl2832_init, + .sleep = rtl2832_sleep, + + .get_tune_settings = rtl2832_get_tune_settings, + + .set_frontend = rtl2832_set_frontend, + .get_frontend = rtl2832_get_frontend, + + .read_status = rtl2832_read_status, + .read_snr = rtl2832_read_snr, + .read_ber = rtl2832_read_ber, + + .i2c_gate_ctrl = rtl2832_i2c_gate_ctrl, +}; + +MODULE_AUTHOR("Thomas Mair "); +MODULE_DESCRIPTION("Realtek RTL2832 DVB-T demodulator driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.5"); diff --git a/drivers/media/dvb-frontends/rtl2832.h b/drivers/media/dvb-frontends/rtl2832.h new file mode 100644 index 000000000000..785a466eb065 --- /dev/null +++ b/drivers/media/dvb-frontends/rtl2832.h @@ -0,0 +1,75 @@ +/* + * Realtek RTL2832 DVB-T demodulator driver + * + * Copyright (C) 2012 Thomas Mair + * + * 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. + */ + +#ifndef RTL2832_H +#define RTL2832_H + +#include + +struct rtl2832_config { + /* + * Demodulator I2C address. + */ + u8 i2c_addr; + + /* + * Xtal frequency. + * Hz + * 4000000, 16000000, 25000000, 28800000 + */ + u32 xtal; + + /* + * IFs for all used modes. + * Hz + * 4570000, 4571429, 36000000, 36125000, 36166667, 44000000 + */ + u32 if_dvbt; + + /* + * tuner + * XXX: This must be keep sync with dvb_usb_rtl28xxu demod driver. + */ +#define RTL2832_TUNER_TUA9001 0x24 +#define RTL2832_TUNER_FC0012 0x26 +#define RTL2832_TUNER_E4000 0x27 +#define RTL2832_TUNER_FC0013 0x29 + u8 tuner; +}; + +#if defined(CONFIG_DVB_RTL2832) || \ + (defined(CONFIG_DVB_RTL2832_MODULE) && defined(MODULE)) +extern struct dvb_frontend *rtl2832_attach( + const struct rtl2832_config *cfg, + struct i2c_adapter *i2c +); +#else +static inline struct dvb_frontend *rtl2832_attach( + const struct rtl2832_config *config, + struct i2c_adapter *i2c +) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + + +#endif /* RTL2832_H */ diff --git a/drivers/media/dvb-frontends/rtl2832_priv.h b/drivers/media/dvb-frontends/rtl2832_priv.h new file mode 100644 index 000000000000..7d97ce9d2193 --- /dev/null +++ b/drivers/media/dvb-frontends/rtl2832_priv.h @@ -0,0 +1,342 @@ +/* + * Realtek RTL2832 DVB-T demodulator driver + * + * Copyright (C) 2012 Thomas Mair + * + * 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. + */ + +#ifndef RTL2832_PRIV_H +#define RTL2832_PRIV_H + +#include "dvb_frontend.h" +#include "rtl2832.h" + +struct rtl2832_priv { + struct i2c_adapter *i2c; + struct dvb_frontend fe; + struct rtl2832_config cfg; + + bool i2c_gate_state; + bool sleeping; + + u8 tuner; + u8 page; /* active register page */ +}; + +struct rtl2832_reg_entry { + u8 page; + u8 start_address; + u8 msb; + u8 lsb; +}; + +struct rtl2832_reg_value { + int reg; + u32 value; +}; + + +/* Demod register bit names */ +enum DVBT_REG_BIT_NAME { + DVBT_SOFT_RST, + DVBT_IIC_REPEAT, + DVBT_TR_WAIT_MIN_8K, + DVBT_RSD_BER_FAIL_VAL, + DVBT_EN_BK_TRK, + DVBT_REG_PI, + DVBT_REG_PFREQ_1_0, + DVBT_PD_DA8, + DVBT_LOCK_TH, + DVBT_BER_PASS_SCAL, + DVBT_CE_FFSM_BYPASS, + DVBT_ALPHAIIR_N, + DVBT_ALPHAIIR_DIF, + DVBT_EN_TRK_SPAN, + DVBT_LOCK_TH_LEN, + DVBT_CCI_THRE, + DVBT_CCI_MON_SCAL, + DVBT_CCI_M0, + DVBT_CCI_M1, + DVBT_CCI_M2, + DVBT_CCI_M3, + DVBT_SPEC_INIT_0, + DVBT_SPEC_INIT_1, + DVBT_SPEC_INIT_2, + DVBT_AD_EN_REG, + DVBT_AD_EN_REG1, + DVBT_EN_BBIN, + DVBT_MGD_THD0, + DVBT_MGD_THD1, + DVBT_MGD_THD2, + DVBT_MGD_THD3, + DVBT_MGD_THD4, + DVBT_MGD_THD5, + DVBT_MGD_THD6, + DVBT_MGD_THD7, + DVBT_EN_CACQ_NOTCH, + DVBT_AD_AV_REF, + DVBT_PIP_ON, + DVBT_SCALE1_B92, + DVBT_SCALE1_B93, + DVBT_SCALE1_BA7, + DVBT_SCALE1_BA9, + DVBT_SCALE1_BAA, + DVBT_SCALE1_BAB, + DVBT_SCALE1_BAC, + DVBT_SCALE1_BB0, + DVBT_SCALE1_BB1, + DVBT_KB_P1, + DVBT_KB_P2, + DVBT_KB_P3, + DVBT_OPT_ADC_IQ, + DVBT_AD_AVI, + DVBT_AD_AVQ, + DVBT_K1_CR_STEP12, + DVBT_TRK_KS_P2, + DVBT_TRK_KS_I2, + DVBT_TR_THD_SET2, + DVBT_TRK_KC_P2, + DVBT_TRK_KC_I2, + DVBT_CR_THD_SET2, + DVBT_PSET_IFFREQ, + DVBT_SPEC_INV, + DVBT_BW_INDEX, + DVBT_RSAMP_RATIO, + DVBT_CFREQ_OFF_RATIO, + DVBT_FSM_STAGE, + DVBT_RX_CONSTEL, + DVBT_RX_HIER, + DVBT_RX_C_RATE_LP, + DVBT_RX_C_RATE_HP, + DVBT_GI_IDX, + DVBT_FFT_MODE_IDX, + DVBT_RSD_BER_EST, + DVBT_CE_EST_EVM, + DVBT_RF_AGC_VAL, + DVBT_IF_AGC_VAL, + DVBT_DAGC_VAL, + DVBT_SFREQ_OFF, + DVBT_CFREQ_OFF, + DVBT_POLAR_RF_AGC, + DVBT_POLAR_IF_AGC, + DVBT_AAGC_HOLD, + DVBT_EN_RF_AGC, + DVBT_EN_IF_AGC, + DVBT_IF_AGC_MIN, + DVBT_IF_AGC_MAX, + DVBT_RF_AGC_MIN, + DVBT_RF_AGC_MAX, + DVBT_IF_AGC_MAN, + DVBT_IF_AGC_MAN_VAL, + DVBT_RF_AGC_MAN, + DVBT_RF_AGC_MAN_VAL, + DVBT_DAGC_TRG_VAL, + DVBT_AGC_TARG_VAL, + DVBT_LOOP_GAIN_3_0, + DVBT_LOOP_GAIN_4, + DVBT_VTOP, + DVBT_KRF, + DVBT_AGC_TARG_VAL_0, + DVBT_AGC_TARG_VAL_8_1, + DVBT_AAGC_LOOP_GAIN, + DVBT_LOOP_GAIN2_3_0, + DVBT_LOOP_GAIN2_4, + DVBT_LOOP_GAIN3, + DVBT_VTOP1, + DVBT_VTOP2, + DVBT_VTOP3, + DVBT_KRF1, + DVBT_KRF2, + DVBT_KRF3, + DVBT_KRF4, + DVBT_EN_GI_PGA, + DVBT_THD_LOCK_UP, + DVBT_THD_LOCK_DW, + DVBT_THD_UP1, + DVBT_THD_DW1, + DVBT_INTER_CNT_LEN, + DVBT_GI_PGA_STATE, + DVBT_EN_AGC_PGA, + DVBT_CKOUTPAR, + DVBT_CKOUT_PWR, + DVBT_SYNC_DUR, + DVBT_ERR_DUR, + DVBT_SYNC_LVL, + DVBT_ERR_LVL, + DVBT_VAL_LVL, + DVBT_SERIAL, + DVBT_SER_LSB, + DVBT_CDIV_PH0, + DVBT_CDIV_PH1, + DVBT_MPEG_IO_OPT_2_2, + DVBT_MPEG_IO_OPT_1_0, + DVBT_CKOUTPAR_PIP, + DVBT_CKOUT_PWR_PIP, + DVBT_SYNC_LVL_PIP, + DVBT_ERR_LVL_PIP, + DVBT_VAL_LVL_PIP, + DVBT_CKOUTPAR_PID, + DVBT_CKOUT_PWR_PID, + DVBT_SYNC_LVL_PID, + DVBT_ERR_LVL_PID, + DVBT_VAL_LVL_PID, + DVBT_SM_PASS, + DVBT_UPDATE_REG_2, + DVBT_BTHD_P3, + DVBT_BTHD_D3, + DVBT_FUNC4_REG0, + DVBT_FUNC4_REG1, + DVBT_FUNC4_REG2, + DVBT_FUNC4_REG3, + DVBT_FUNC4_REG4, + DVBT_FUNC4_REG5, + DVBT_FUNC4_REG6, + DVBT_FUNC4_REG7, + DVBT_FUNC4_REG8, + DVBT_FUNC4_REG9, + DVBT_FUNC4_REG10, + DVBT_FUNC5_REG0, + DVBT_FUNC5_REG1, + DVBT_FUNC5_REG2, + DVBT_FUNC5_REG3, + DVBT_FUNC5_REG4, + DVBT_FUNC5_REG5, + DVBT_FUNC5_REG6, + DVBT_FUNC5_REG7, + DVBT_FUNC5_REG8, + DVBT_FUNC5_REG9, + DVBT_FUNC5_REG10, + DVBT_FUNC5_REG11, + DVBT_FUNC5_REG12, + DVBT_FUNC5_REG13, + DVBT_FUNC5_REG14, + DVBT_FUNC5_REG15, + DVBT_FUNC5_REG16, + DVBT_FUNC5_REG17, + DVBT_FUNC5_REG18, + DVBT_AD7_SETTING, + DVBT_RSSI_R, + DVBT_ACI_DET_IND, + DVBT_REG_MON, + DVBT_REG_MONSEL, + DVBT_REG_GPE, + DVBT_REG_GPO, + DVBT_REG_4MSEL, + DVBT_TEST_REG_1, + DVBT_TEST_REG_2, + DVBT_TEST_REG_3, + DVBT_TEST_REG_4, + DVBT_REG_BIT_NAME_ITEM_TERMINATOR, +}; + +static const struct rtl2832_reg_value rtl2832_tuner_init_tua9001[] = { + {DVBT_DAGC_TRG_VAL, 0x39}, + {DVBT_AGC_TARG_VAL_0, 0x0}, + {DVBT_AGC_TARG_VAL_8_1, 0x5a}, + {DVBT_AAGC_LOOP_GAIN, 0x16}, + {DVBT_LOOP_GAIN2_3_0, 0x6}, + {DVBT_LOOP_GAIN2_4, 0x1}, + {DVBT_LOOP_GAIN3, 0x16}, + {DVBT_VTOP1, 0x35}, + {DVBT_VTOP2, 0x21}, + {DVBT_VTOP3, 0x21}, + {DVBT_KRF1, 0x0}, + {DVBT_KRF2, 0x40}, + {DVBT_KRF3, 0x10}, + {DVBT_KRF4, 0x10}, + {DVBT_IF_AGC_MIN, 0x80}, + {DVBT_IF_AGC_MAX, 0x7f}, + {DVBT_RF_AGC_MIN, 0x9c}, + {DVBT_RF_AGC_MAX, 0x7f}, + {DVBT_POLAR_RF_AGC, 0x0}, + {DVBT_POLAR_IF_AGC, 0x0}, + {DVBT_AD7_SETTING, 0xe9f4}, + {DVBT_OPT_ADC_IQ, 0x1}, + {DVBT_AD_AVI, 0x0}, + {DVBT_AD_AVQ, 0x0}, +}; + +static const struct rtl2832_reg_value rtl2832_tuner_init_fc0012[] = { + {DVBT_DAGC_TRG_VAL, 0x5a}, + {DVBT_AGC_TARG_VAL_0, 0x0}, + {DVBT_AGC_TARG_VAL_8_1, 0x5a}, + {DVBT_AAGC_LOOP_GAIN, 0x16}, + {DVBT_LOOP_GAIN2_3_0, 0x6}, + {DVBT_LOOP_GAIN2_4, 0x1}, + {DVBT_LOOP_GAIN3, 0x16}, + {DVBT_VTOP1, 0x35}, + {DVBT_VTOP2, 0x21}, + {DVBT_VTOP3, 0x21}, + {DVBT_KRF1, 0x0}, + {DVBT_KRF2, 0x40}, + {DVBT_KRF3, 0x10}, + {DVBT_KRF4, 0x10}, + {DVBT_IF_AGC_MIN, 0x80}, + {DVBT_IF_AGC_MAX, 0x7f}, + {DVBT_RF_AGC_MIN, 0x80}, + {DVBT_RF_AGC_MAX, 0x7f}, + {DVBT_POLAR_RF_AGC, 0x0}, + {DVBT_POLAR_IF_AGC, 0x0}, + {DVBT_AD7_SETTING, 0xe9bf}, + {DVBT_EN_GI_PGA, 0x0}, + {DVBT_THD_LOCK_UP, 0x0}, + {DVBT_THD_LOCK_DW, 0x0}, + {DVBT_THD_UP1, 0x11}, + {DVBT_THD_DW1, 0xef}, + {DVBT_INTER_CNT_LEN, 0xc}, + {DVBT_GI_PGA_STATE, 0x0}, + {DVBT_EN_AGC_PGA, 0x1}, + {DVBT_IF_AGC_MAN, 0x0}, +}; + +static const struct rtl2832_reg_value rtl2832_tuner_init_e4000[] = { + {DVBT_DAGC_TRG_VAL, 0x5a}, + {DVBT_AGC_TARG_VAL_0, 0x0}, + {DVBT_AGC_TARG_VAL_8_1, 0x5a}, + {DVBT_AAGC_LOOP_GAIN, 0x18}, + {DVBT_LOOP_GAIN2_3_0, 0x8}, + {DVBT_LOOP_GAIN2_4, 0x1}, + {DVBT_LOOP_GAIN3, 0x18}, + {DVBT_VTOP1, 0x35}, + {DVBT_VTOP2, 0x21}, + {DVBT_VTOP3, 0x21}, + {DVBT_KRF1, 0x0}, + {DVBT_KRF2, 0x40}, + {DVBT_KRF3, 0x10}, + {DVBT_KRF4, 0x10}, + {DVBT_IF_AGC_MIN, 0x80}, + {DVBT_IF_AGC_MAX, 0x7f}, + {DVBT_RF_AGC_MIN, 0x80}, + {DVBT_RF_AGC_MAX, 0x7f}, + {DVBT_POLAR_RF_AGC, 0x0}, + {DVBT_POLAR_IF_AGC, 0x0}, + {DVBT_AD7_SETTING, 0xe9d4}, + {DVBT_EN_GI_PGA, 0x0}, + {DVBT_THD_LOCK_UP, 0x0}, + {DVBT_THD_LOCK_DW, 0x0}, + {DVBT_THD_UP1, 0x14}, + {DVBT_THD_DW1, 0xec}, + {DVBT_INTER_CNT_LEN, 0xc}, + {DVBT_GI_PGA_STATE, 0x0}, + {DVBT_EN_AGC_PGA, 0x1}, + {DVBT_REG_GPE, 0x1}, + {DVBT_REG_GPO, 0x1}, + {DVBT_REG_MONSEL, 0x1}, + {DVBT_REG_MON, 0x1}, + {DVBT_REG_4MSEL, 0x0}, +}; + +#endif /* RTL2832_PRIV_H */ diff --git a/drivers/media/dvb/frontends/s5h1409.c b/drivers/media/dvb-frontends/s5h1409.c similarity index 100% rename from drivers/media/dvb/frontends/s5h1409.c rename to drivers/media/dvb-frontends/s5h1409.c diff --git a/drivers/media/dvb/frontends/s5h1409.h b/drivers/media/dvb-frontends/s5h1409.h similarity index 100% rename from drivers/media/dvb/frontends/s5h1409.h rename to drivers/media/dvb-frontends/s5h1409.h diff --git a/drivers/media/dvb/frontends/s5h1411.c b/drivers/media/dvb-frontends/s5h1411.c similarity index 100% rename from drivers/media/dvb/frontends/s5h1411.c rename to drivers/media/dvb-frontends/s5h1411.c diff --git a/drivers/media/dvb/frontends/s5h1411.h b/drivers/media/dvb-frontends/s5h1411.h similarity index 100% rename from drivers/media/dvb/frontends/s5h1411.h rename to drivers/media/dvb-frontends/s5h1411.h diff --git a/drivers/media/dvb/frontends/s5h1420.c b/drivers/media/dvb-frontends/s5h1420.c similarity index 100% rename from drivers/media/dvb/frontends/s5h1420.c rename to drivers/media/dvb-frontends/s5h1420.c diff --git a/drivers/media/dvb/frontends/s5h1420.h b/drivers/media/dvb-frontends/s5h1420.h similarity index 100% rename from drivers/media/dvb/frontends/s5h1420.h rename to drivers/media/dvb-frontends/s5h1420.h diff --git a/drivers/media/dvb/frontends/s5h1420_priv.h b/drivers/media/dvb-frontends/s5h1420_priv.h similarity index 100% rename from drivers/media/dvb/frontends/s5h1420_priv.h rename to drivers/media/dvb-frontends/s5h1420_priv.h diff --git a/drivers/media/dvb/frontends/s5h1432.c b/drivers/media/dvb-frontends/s5h1432.c similarity index 100% rename from drivers/media/dvb/frontends/s5h1432.c rename to drivers/media/dvb-frontends/s5h1432.c diff --git a/drivers/media/dvb/frontends/s5h1432.h b/drivers/media/dvb-frontends/s5h1432.h similarity index 100% rename from drivers/media/dvb/frontends/s5h1432.h rename to drivers/media/dvb-frontends/s5h1432.h diff --git a/drivers/media/dvb/frontends/s921.c b/drivers/media/dvb-frontends/s921.c similarity index 100% rename from drivers/media/dvb/frontends/s921.c rename to drivers/media/dvb-frontends/s921.c diff --git a/drivers/media/dvb/frontends/s921.h b/drivers/media/dvb-frontends/s921.h similarity index 100% rename from drivers/media/dvb/frontends/s921.h rename to drivers/media/dvb-frontends/s921.h diff --git a/drivers/media/dvb/frontends/si21xx.c b/drivers/media/dvb-frontends/si21xx.c similarity index 100% rename from drivers/media/dvb/frontends/si21xx.c rename to drivers/media/dvb-frontends/si21xx.c diff --git a/drivers/media/dvb/frontends/si21xx.h b/drivers/media/dvb-frontends/si21xx.h similarity index 100% rename from drivers/media/dvb/frontends/si21xx.h rename to drivers/media/dvb-frontends/si21xx.h diff --git a/drivers/media/dvb/frontends/sp8870.c b/drivers/media/dvb-frontends/sp8870.c similarity index 100% rename from drivers/media/dvb/frontends/sp8870.c rename to drivers/media/dvb-frontends/sp8870.c diff --git a/drivers/media/dvb/frontends/sp8870.h b/drivers/media/dvb-frontends/sp8870.h similarity index 100% rename from drivers/media/dvb/frontends/sp8870.h rename to drivers/media/dvb-frontends/sp8870.h diff --git a/drivers/media/dvb/frontends/sp887x.c b/drivers/media/dvb-frontends/sp887x.c similarity index 100% rename from drivers/media/dvb/frontends/sp887x.c rename to drivers/media/dvb-frontends/sp887x.c diff --git a/drivers/media/dvb/frontends/sp887x.h b/drivers/media/dvb-frontends/sp887x.h similarity index 100% rename from drivers/media/dvb/frontends/sp887x.h rename to drivers/media/dvb-frontends/sp887x.h diff --git a/drivers/media/dvb/frontends/stb0899_algo.c b/drivers/media/dvb-frontends/stb0899_algo.c similarity index 100% rename from drivers/media/dvb/frontends/stb0899_algo.c rename to drivers/media/dvb-frontends/stb0899_algo.c diff --git a/drivers/media/dvb/frontends/stb0899_cfg.h b/drivers/media/dvb-frontends/stb0899_cfg.h similarity index 100% rename from drivers/media/dvb/frontends/stb0899_cfg.h rename to drivers/media/dvb-frontends/stb0899_cfg.h diff --git a/drivers/media/dvb-frontends/stb0899_drv.c b/drivers/media/dvb-frontends/stb0899_drv.c new file mode 100644 index 000000000000..79e29de87fb7 --- /dev/null +++ b/drivers/media/dvb-frontends/stb0899_drv.c @@ -0,0 +1,1652 @@ +/* + STB0899 Multistandard Frontend driver + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + + Copyright (C) ST Microelectronics + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#include +#include + +#include +#include "dvb_frontend.h" + +#include "stb0899_drv.h" +#include "stb0899_priv.h" +#include "stb0899_reg.h" + +static unsigned int verbose = 0;//1; +module_param(verbose, int, 0644); + +/* C/N in dB/10, NIRM/NIRL */ +static const struct stb0899_tab stb0899_cn_tab[] = { + { 200, 2600 }, + { 190, 2700 }, + { 180, 2860 }, + { 170, 3020 }, + { 160, 3210 }, + { 150, 3440 }, + { 140, 3710 }, + { 130, 4010 }, + { 120, 4360 }, + { 110, 4740 }, + { 100, 5190 }, + { 90, 5670 }, + { 80, 6200 }, + { 70, 6770 }, + { 60, 7360 }, + { 50, 7970 }, + { 40, 8250 }, + { 30, 9000 }, + { 20, 9450 }, + { 15, 9600 }, +}; + +/* DVB-S AGCIQ_VALUE vs. signal level in dBm/10. + * As measured, connected to a modulator. + * -8.0 to -50.0 dBm directly connected, + * -52.0 to -74.8 with extra attenuation. + * Cut-off to AGCIQ_VALUE = 0x80 below -74.8dBm. + * Crude linear extrapolation below -84.8dBm and above -8.0dBm. + */ +static const struct stb0899_tab stb0899_dvbsrf_tab[] = { + { -750, -128 }, + { -748, -94 }, + { -745, -92 }, + { -735, -90 }, + { -720, -87 }, + { -670, -77 }, + { -640, -70 }, + { -610, -62 }, + { -600, -60 }, + { -590, -56 }, + { -560, -41 }, + { -540, -25 }, + { -530, -17 }, + { -520, -11 }, + { -500, 1 }, + { -490, 6 }, + { -480, 10 }, + { -440, 22 }, + { -420, 27 }, + { -400, 31 }, + { -380, 34 }, + { -340, 40 }, + { -320, 43 }, + { -280, 48 }, + { -250, 52 }, + { -230, 55 }, + { -180, 61 }, + { -140, 66 }, + { -90, 73 }, + { -80, 74 }, + { 500, 127 } +}; + +/* DVB-S2 IF_AGC_GAIN vs. signal level in dBm/10. + * As measured, connected to a modulator. + * -8.0 to -50.1 dBm directly connected, + * -53.0 to -76.6 with extra attenuation. + * Cut-off to IF_AGC_GAIN = 0x3fff below -76.6dBm. + * Crude linear extrapolation below -76.6dBm and above -8.0dBm. + */ +static const struct stb0899_tab stb0899_dvbs2rf_tab[] = { + { 700, 0 }, + { -80, 3217 }, + { -150, 3893 }, + { -190, 4217 }, + { -240, 4621 }, + { -280, 4945 }, + { -320, 5273 }, + { -350, 5545 }, + { -370, 5741 }, + { -410, 6147 }, + { -450, 6671 }, + { -490, 7413 }, + { -501, 7665 }, + { -530, 8767 }, + { -560, 10219 }, + { -580, 10939 }, + { -590, 11518 }, + { -600, 11723 }, + { -650, 12659 }, + { -690, 13219 }, + { -730, 13645 }, + { -750, 13909 }, + { -766, 14153 }, + { -950, 16383 } +}; + +/* DVB-S2 Es/N0 quant in dB/100 vs read value * 100*/ +static struct stb0899_tab stb0899_quant_tab[] = { + { 0, 0 }, + { 0, 100 }, + { 600, 200 }, + { 950, 299 }, + { 1200, 398 }, + { 1400, 501 }, + { 1560, 603 }, + { 1690, 700 }, + { 1810, 804 }, + { 1910, 902 }, + { 2000, 1000 }, + { 2080, 1096 }, + { 2160, 1202 }, + { 2230, 1303 }, + { 2350, 1496 }, + { 2410, 1603 }, + { 2460, 1698 }, + { 2510, 1799 }, + { 2600, 1995 }, + { 2650, 2113 }, + { 2690, 2213 }, + { 2720, 2291 }, + { 2760, 2399 }, + { 2800, 2512 }, + { 2860, 2692 }, + { 2930, 2917 }, + { 2960, 3020 }, + { 3010, 3199 }, + { 3040, 3311 }, + { 3060, 3388 }, + { 3120, 3631 }, + { 3190, 3936 }, + { 3400, 5012 }, + { 3610, 6383 }, + { 3800, 7943 }, + { 4210, 12735 }, + { 4500, 17783 }, + { 4690, 22131 }, + { 4810, 25410 } +}; + +/* DVB-S2 Es/N0 estimate in dB/100 vs read value */ +static struct stb0899_tab stb0899_est_tab[] = { + { 0, 0 }, + { 0, 1 }, + { 301, 2 }, + { 1204, 16 }, + { 1806, 64 }, + { 2408, 256 }, + { 2709, 512 }, + { 3010, 1023 }, + { 3311, 2046 }, + { 3612, 4093 }, + { 3823, 6653 }, + { 3913, 8185 }, + { 4010, 10233 }, + { 4107, 12794 }, + { 4214, 16368 }, + { 4266, 18450 }, + { 4311, 20464 }, + { 4353, 22542 }, + { 4391, 24604 }, + { 4425, 26607 }, + { 4457, 28642 }, + { 4487, 30690 }, + { 4515, 32734 }, + { 4612, 40926 }, + { 4692, 49204 }, + { 4816, 65464 }, + { 4913, 81846 }, + { 4993, 98401 }, + { 5060, 114815 }, + { 5118, 131220 }, + { 5200, 158489 }, + { 5300, 199526 }, + { 5400, 251189 }, + { 5500, 316228 }, + { 5600, 398107 }, + { 5720, 524807 }, + { 5721, 526017 }, +}; + +static int _stb0899_read_reg(struct stb0899_state *state, unsigned int reg) +{ + int ret; + + u8 b0[] = { reg >> 8, reg & 0xff }; + u8 buf; + + struct i2c_msg msg[] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = b0, + .len = 2 + },{ + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = &buf, + .len = 1 + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + if (ret != 2) { + if (ret != -ERESTARTSYS) + dprintk(state->verbose, FE_ERROR, 1, + "Read error, Reg=[0x%02x], Status=%d", + reg, ret); + + return ret < 0 ? ret : -EREMOTEIO; + } + if (unlikely(*state->verbose >= FE_DEBUGREG)) + dprintk(state->verbose, FE_ERROR, 1, "Reg=[0x%02x], data=%02x", + reg, buf); + + return (unsigned int)buf; +} + +int stb0899_read_reg(struct stb0899_state *state, unsigned int reg) +{ + int result; + + result = _stb0899_read_reg(state, reg); + /* + * Bug ID 9: + * access to 0xf2xx/0xf6xx + * must be followed by read from 0xf2ff/0xf6ff. + */ + if ((reg != 0xf2ff) && (reg != 0xf6ff) && + (((reg & 0xff00) == 0xf200) || ((reg & 0xff00) == 0xf600))) + _stb0899_read_reg(state, (reg | 0x00ff)); + + return result; +} + +u32 _stb0899_read_s2reg(struct stb0899_state *state, + u32 stb0899_i2cdev, + u32 stb0899_base_addr, + u16 stb0899_reg_offset) +{ + int status; + u32 data; + u8 buf[7] = { 0 }; + u16 tmpaddr; + + u8 buf_0[] = { + GETBYTE(stb0899_i2cdev, BYTE1), /* 0xf3 S2 Base Address (MSB) */ + GETBYTE(stb0899_i2cdev, BYTE0), /* 0xfc S2 Base Address (LSB) */ + GETBYTE(stb0899_base_addr, BYTE0), /* 0x00 Base Address (LSB) */ + GETBYTE(stb0899_base_addr, BYTE1), /* 0x04 Base Address (LSB) */ + GETBYTE(stb0899_base_addr, BYTE2), /* 0x00 Base Address (MSB) */ + GETBYTE(stb0899_base_addr, BYTE3), /* 0x00 Base Address (MSB) */ + }; + u8 buf_1[] = { + 0x00, /* 0xf3 Reg Offset */ + 0x00, /* 0x44 Reg Offset */ + }; + + struct i2c_msg msg_0 = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf_0, + .len = 6 + }; + + struct i2c_msg msg_1 = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf_1, + .len = 2 + }; + + struct i2c_msg msg_r = { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = buf, + .len = 4 + }; + + tmpaddr = stb0899_reg_offset & 0xff00; + if (!(stb0899_reg_offset & 0x8)) + tmpaddr = stb0899_reg_offset | 0x20; + + buf_1[0] = GETBYTE(tmpaddr, BYTE1); + buf_1[1] = GETBYTE(tmpaddr, BYTE0); + + status = i2c_transfer(state->i2c, &msg_0, 1); + if (status < 1) { + if (status != -ERESTARTSYS) + printk(KERN_ERR "%s ERR(1), Device=[0x%04x], Base address=[0x%08x], Offset=[0x%04x], Status=%d\n", + __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, status); + + goto err; + } + + /* Dummy */ + status = i2c_transfer(state->i2c, &msg_1, 1); + if (status < 1) + goto err; + + status = i2c_transfer(state->i2c, &msg_r, 1); + if (status < 1) + goto err; + + buf_1[0] = GETBYTE(stb0899_reg_offset, BYTE1); + buf_1[1] = GETBYTE(stb0899_reg_offset, BYTE0); + + /* Actual */ + status = i2c_transfer(state->i2c, &msg_1, 1); + if (status < 1) { + if (status != -ERESTARTSYS) + printk(KERN_ERR "%s ERR(2), Device=[0x%04x], Base address=[0x%08x], Offset=[0x%04x], Status=%d\n", + __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, status); + goto err; + } + + status = i2c_transfer(state->i2c, &msg_r, 1); + if (status < 1) { + if (status != -ERESTARTSYS) + printk(KERN_ERR "%s ERR(3), Device=[0x%04x], Base address=[0x%08x], Offset=[0x%04x], Status=%d\n", + __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, status); + return status < 0 ? status : -EREMOTEIO; + } + + data = MAKEWORD32(buf[3], buf[2], buf[1], buf[0]); + if (unlikely(*state->verbose >= FE_DEBUGREG)) + printk(KERN_DEBUG "%s Device=[0x%04x], Base address=[0x%08x], Offset=[0x%04x], Data=[0x%08x]\n", + __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, data); + + return data; + +err: + return status < 0 ? status : -EREMOTEIO; +} + +int stb0899_write_s2reg(struct stb0899_state *state, + u32 stb0899_i2cdev, + u32 stb0899_base_addr, + u16 stb0899_reg_offset, + u32 stb0899_data) +{ + int status; + + /* Base Address Setup */ + u8 buf_0[] = { + GETBYTE(stb0899_i2cdev, BYTE1), /* 0xf3 S2 Base Address (MSB) */ + GETBYTE(stb0899_i2cdev, BYTE0), /* 0xfc S2 Base Address (LSB) */ + GETBYTE(stb0899_base_addr, BYTE0), /* 0x00 Base Address (LSB) */ + GETBYTE(stb0899_base_addr, BYTE1), /* 0x04 Base Address (LSB) */ + GETBYTE(stb0899_base_addr, BYTE2), /* 0x00 Base Address (MSB) */ + GETBYTE(stb0899_base_addr, BYTE3), /* 0x00 Base Address (MSB) */ + }; + u8 buf_1[] = { + 0x00, /* 0xf3 Reg Offset */ + 0x00, /* 0x44 Reg Offset */ + 0x00, /* data */ + 0x00, /* data */ + 0x00, /* data */ + 0x00, /* data */ + }; + + struct i2c_msg msg_0 = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf_0, + .len = 6 + }; + + struct i2c_msg msg_1 = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf_1, + .len = 6 + }; + + buf_1[0] = GETBYTE(stb0899_reg_offset, BYTE1); + buf_1[1] = GETBYTE(stb0899_reg_offset, BYTE0); + buf_1[2] = GETBYTE(stb0899_data, BYTE0); + buf_1[3] = GETBYTE(stb0899_data, BYTE1); + buf_1[4] = GETBYTE(stb0899_data, BYTE2); + buf_1[5] = GETBYTE(stb0899_data, BYTE3); + + if (unlikely(*state->verbose >= FE_DEBUGREG)) + printk(KERN_DEBUG "%s Device=[0x%04x], Base Address=[0x%08x], Offset=[0x%04x], Data=[0x%08x]\n", + __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, stb0899_data); + + status = i2c_transfer(state->i2c, &msg_0, 1); + if (unlikely(status < 1)) { + if (status != -ERESTARTSYS) + printk(KERN_ERR "%s ERR (1), Device=[0x%04x], Base Address=[0x%08x], Offset=[0x%04x], Data=[0x%08x], status=%d\n", + __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, stb0899_data, status); + goto err; + } + status = i2c_transfer(state->i2c, &msg_1, 1); + if (unlikely(status < 1)) { + if (status != -ERESTARTSYS) + printk(KERN_ERR "%s ERR (2), Device=[0x%04x], Base Address=[0x%08x], Offset=[0x%04x], Data=[0x%08x], status=%d\n", + __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, stb0899_data, status); + + return status < 0 ? status : -EREMOTEIO; + } + + return 0; + +err: + return status < 0 ? status : -EREMOTEIO; +} + +int stb0899_read_regs(struct stb0899_state *state, unsigned int reg, u8 *buf, u32 count) +{ + int status; + + u8 b0[] = { reg >> 8, reg & 0xff }; + + struct i2c_msg msg[] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = b0, + .len = 2 + },{ + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = buf, + .len = count + } + }; + + status = i2c_transfer(state->i2c, msg, 2); + if (status != 2) { + if (status != -ERESTARTSYS) + printk(KERN_ERR "%s Read error, Reg=[0x%04x], Count=%u, Status=%d\n", + __func__, reg, count, status); + goto err; + } + /* + * Bug ID 9: + * access to 0xf2xx/0xf6xx + * must be followed by read from 0xf2ff/0xf6ff. + */ + if ((reg != 0xf2ff) && (reg != 0xf6ff) && + (((reg & 0xff00) == 0xf200) || ((reg & 0xff00) == 0xf600))) + _stb0899_read_reg(state, (reg | 0x00ff)); + + if (unlikely(*state->verbose >= FE_DEBUGREG)) { + int i; + + printk(KERN_DEBUG "%s [0x%04x]:", __func__, reg); + for (i = 0; i < count; i++) { + printk(" %02x", buf[i]); + } + printk("\n"); + } + + return 0; +err: + return status < 0 ? status : -EREMOTEIO; +} + +int stb0899_write_regs(struct stb0899_state *state, unsigned int reg, u8 *data, u32 count) +{ + int ret; + u8 buf[2 + count]; + struct i2c_msg i2c_msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = 2 + count + }; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + memcpy(&buf[2], data, count); + + if (unlikely(*state->verbose >= FE_DEBUGREG)) { + int i; + + printk(KERN_DEBUG "%s [0x%04x]:", __func__, reg); + for (i = 0; i < count; i++) + printk(" %02x", data[i]); + printk("\n"); + } + ret = i2c_transfer(state->i2c, &i2c_msg, 1); + + /* + * Bug ID 9: + * access to 0xf2xx/0xf6xx + * must be followed by read from 0xf2ff/0xf6ff. + */ + if ((((reg & 0xff00) == 0xf200) || ((reg & 0xff00) == 0xf600))) + stb0899_read_reg(state, (reg | 0x00ff)); + + if (ret != 1) { + if (ret != -ERESTARTSYS) + dprintk(state->verbose, FE_ERROR, 1, "Reg=[0x%04x], Data=[0x%02x ...], Count=%u, Status=%d", + reg, data[0], count, ret); + return ret < 0 ? ret : -EREMOTEIO; + } + + return 0; +} + +int stb0899_write_reg(struct stb0899_state *state, unsigned int reg, u8 data) +{ + return stb0899_write_regs(state, reg, &data, 1); +} + +/* + * stb0899_get_mclk + * Get STB0899 master clock frequency + * ExtClk: external clock frequency (Hz) + */ +static u32 stb0899_get_mclk(struct stb0899_state *state) +{ + u32 mclk = 0, div = 0; + + div = stb0899_read_reg(state, STB0899_NCOARSE); + mclk = (div + 1) * state->config->xtal_freq / 6; + dprintk(state->verbose, FE_DEBUG, 1, "div=%d, mclk=%d", div, mclk); + + return mclk; +} + +/* + * stb0899_set_mclk + * Set STB0899 master Clock frequency + * Mclk: demodulator master clock + * ExtClk: external clock frequency (Hz) + */ +static void stb0899_set_mclk(struct stb0899_state *state, u32 Mclk) +{ + struct stb0899_internal *internal = &state->internal; + u8 mdiv = 0; + + dprintk(state->verbose, FE_DEBUG, 1, "state->config=%p", state->config); + mdiv = ((6 * Mclk) / state->config->xtal_freq) - 1; + dprintk(state->verbose, FE_DEBUG, 1, "mdiv=%d", mdiv); + + stb0899_write_reg(state, STB0899_NCOARSE, mdiv); + internal->master_clk = stb0899_get_mclk(state); + + dprintk(state->verbose, FE_DEBUG, 1, "MasterCLOCK=%d", internal->master_clk); +} + +static int stb0899_postproc(struct stb0899_state *state, u8 ctl, int enable) +{ + struct stb0899_config *config = state->config; + const struct stb0899_postproc *postproc = config->postproc; + + /* post process event */ + if (postproc) { + if (enable) { + if (postproc[ctl].level == STB0899_GPIOPULLUP) + stb0899_write_reg(state, postproc[ctl].gpio, 0x02); + else + stb0899_write_reg(state, postproc[ctl].gpio, 0x82); + } else { + if (postproc[ctl].level == STB0899_GPIOPULLUP) + stb0899_write_reg(state, postproc[ctl].gpio, 0x82); + else + stb0899_write_reg(state, postproc[ctl].gpio, 0x02); + } + } + return 0; +} + +static void stb0899_release(struct dvb_frontend *fe) +{ + struct stb0899_state *state = fe->demodulator_priv; + + dprintk(state->verbose, FE_DEBUG, 1, "Release Frontend"); + /* post process event */ + stb0899_postproc(state, STB0899_POSTPROC_GPIO_POWER, 0); + kfree(state); +} + +/* + * stb0899_get_alpha + * return: rolloff + */ +static int stb0899_get_alpha(struct stb0899_state *state) +{ + u8 mode_coeff; + + mode_coeff = stb0899_read_reg(state, STB0899_DEMOD); + + if (STB0899_GETFIELD(MODECOEFF, mode_coeff) == 1) + return 20; + else + return 35; +} + +/* + * stb0899_init_calc + */ +static void stb0899_init_calc(struct stb0899_state *state) +{ + struct stb0899_internal *internal = &state->internal; + int master_clk; + u8 agc[2]; + u32 reg; + + /* Read registers (in burst mode) */ + stb0899_read_regs(state, STB0899_AGC1REF, agc, 2); /* AGC1R and AGC2O */ + + /* Initial calculations */ + master_clk = stb0899_get_mclk(state); + internal->t_agc1 = 0; + internal->t_agc2 = 0; + internal->master_clk = master_clk; + internal->mclk = master_clk / 65536L; + internal->rolloff = stb0899_get_alpha(state); + + /* DVBS2 Initial calculations */ + /* Set AGC value to the middle */ + internal->agc_gain = 8154; + reg = STB0899_READ_S2REG(STB0899_S2DEMOD, IF_AGC_CNTRL); + STB0899_SETFIELD_VAL(IF_GAIN_INIT, reg, internal->agc_gain); + stb0899_write_s2reg(state, STB0899_S2DEMOD, STB0899_BASE_IF_AGC_CNTRL, STB0899_OFF0_IF_AGC_CNTRL, reg); + + reg = STB0899_READ_S2REG(STB0899_S2DEMOD, RRC_ALPHA); + internal->rrc_alpha = STB0899_GETFIELD(RRC_ALPHA, reg); + + internal->center_freq = 0; + internal->av_frame_coarse = 10; + internal->av_frame_fine = 20; + internal->step_size = 2; +/* + if ((pParams->SpectralInv == FE_IQ_NORMAL) || (pParams->SpectralInv == FE_IQ_AUTO)) + pParams->IQLocked = 0; + else + pParams->IQLocked = 1; +*/ +} + +static int stb0899_wait_diseqc_fifo_empty(struct stb0899_state *state, int timeout) +{ + u8 reg = 0; + unsigned long start = jiffies; + + while (1) { + reg = stb0899_read_reg(state, STB0899_DISSTATUS); + if (!STB0899_GETFIELD(FIFOFULL, reg)) + break; + if ((jiffies - start) > timeout) { + dprintk(state->verbose, FE_ERROR, 1, "timed out !!"); + return -ETIMEDOUT; + } + } + + return 0; +} + +static int stb0899_send_diseqc_msg(struct dvb_frontend *fe, struct dvb_diseqc_master_cmd *cmd) +{ + struct stb0899_state *state = fe->demodulator_priv; + u8 reg, i; + + if (cmd->msg_len > 8) + return -EINVAL; + + /* enable FIFO precharge */ + reg = stb0899_read_reg(state, STB0899_DISCNTRL1); + STB0899_SETFIELD_VAL(DISPRECHARGE, reg, 1); + stb0899_write_reg(state, STB0899_DISCNTRL1, reg); + for (i = 0; i < cmd->msg_len; i++) { + /* wait for FIFO empty */ + if (stb0899_wait_diseqc_fifo_empty(state, 100) < 0) + return -ETIMEDOUT; + + stb0899_write_reg(state, STB0899_DISFIFO, cmd->msg[i]); + } + reg = stb0899_read_reg(state, STB0899_DISCNTRL1); + STB0899_SETFIELD_VAL(DISPRECHARGE, reg, 0); + stb0899_write_reg(state, STB0899_DISCNTRL1, reg); + msleep(100); + return 0; +} + +static int stb0899_wait_diseqc_rxidle(struct stb0899_state *state, int timeout) +{ + u8 reg = 0; + unsigned long start = jiffies; + + while (!STB0899_GETFIELD(RXEND, reg)) { + reg = stb0899_read_reg(state, STB0899_DISRX_ST0); + if (jiffies - start > timeout) { + dprintk(state->verbose, FE_ERROR, 1, "timed out!!"); + return -ETIMEDOUT; + } + msleep(10); + } + + return 0; +} + +static int stb0899_recv_slave_reply(struct dvb_frontend *fe, struct dvb_diseqc_slave_reply *reply) +{ + struct stb0899_state *state = fe->demodulator_priv; + u8 reg, length = 0, i; + int result; + + if (stb0899_wait_diseqc_rxidle(state, 100) < 0) + return -ETIMEDOUT; + + reg = stb0899_read_reg(state, STB0899_DISRX_ST0); + if (STB0899_GETFIELD(RXEND, reg)) { + + reg = stb0899_read_reg(state, STB0899_DISRX_ST1); + length = STB0899_GETFIELD(FIFOBYTENBR, reg); + + if (length > sizeof (reply->msg)) { + result = -EOVERFLOW; + goto exit; + } + reply->msg_len = length; + + /* extract data */ + for (i = 0; i < length; i++) + reply->msg[i] = stb0899_read_reg(state, STB0899_DISFIFO); + } + + return 0; +exit: + + return result; +} + +static int stb0899_wait_diseqc_txidle(struct stb0899_state *state, int timeout) +{ + u8 reg = 0; + unsigned long start = jiffies; + + while (!STB0899_GETFIELD(TXIDLE, reg)) { + reg = stb0899_read_reg(state, STB0899_DISSTATUS); + if (jiffies - start > timeout) { + dprintk(state->verbose, FE_ERROR, 1, "timed out!!"); + return -ETIMEDOUT; + } + msleep(10); + } + return 0; +} + +static int stb0899_send_diseqc_burst(struct dvb_frontend *fe, fe_sec_mini_cmd_t burst) +{ + struct stb0899_state *state = fe->demodulator_priv; + u8 reg, old_state; + + /* wait for diseqc idle */ + if (stb0899_wait_diseqc_txidle(state, 100) < 0) + return -ETIMEDOUT; + + reg = stb0899_read_reg(state, STB0899_DISCNTRL1); + old_state = reg; + /* set to burst mode */ + STB0899_SETFIELD_VAL(DISEQCMODE, reg, 0x03); + STB0899_SETFIELD_VAL(DISPRECHARGE, reg, 0x01); + stb0899_write_reg(state, STB0899_DISCNTRL1, reg); + switch (burst) { + case SEC_MINI_A: + /* unmodulated */ + stb0899_write_reg(state, STB0899_DISFIFO, 0x00); + break; + case SEC_MINI_B: + /* modulated */ + stb0899_write_reg(state, STB0899_DISFIFO, 0xff); + break; + } + reg = stb0899_read_reg(state, STB0899_DISCNTRL1); + STB0899_SETFIELD_VAL(DISPRECHARGE, reg, 0x00); + stb0899_write_reg(state, STB0899_DISCNTRL1, reg); + /* wait for diseqc idle */ + if (stb0899_wait_diseqc_txidle(state, 100) < 0) + return -ETIMEDOUT; + + /* restore state */ + stb0899_write_reg(state, STB0899_DISCNTRL1, old_state); + + return 0; +} + +static int stb0899_diseqc_init(struct stb0899_state *state) +{ +/* + struct dvb_diseqc_slave_reply rx_data; +*/ + u8 f22_tx, reg; + + u32 mclk, tx_freq = 22000;/* count = 0, i; */ + reg = stb0899_read_reg(state, STB0899_DISCNTRL2); + STB0899_SETFIELD_VAL(ONECHIP_TRX, reg, 0); + stb0899_write_reg(state, STB0899_DISCNTRL2, reg); + + /* disable Tx spy */ + reg = stb0899_read_reg(state, STB0899_DISCNTRL1); + STB0899_SETFIELD_VAL(DISEQCRESET, reg, 1); + stb0899_write_reg(state, STB0899_DISCNTRL1, reg); + + reg = stb0899_read_reg(state, STB0899_DISCNTRL1); + STB0899_SETFIELD_VAL(DISEQCRESET, reg, 0); + stb0899_write_reg(state, STB0899_DISCNTRL1, reg); + + mclk = stb0899_get_mclk(state); + f22_tx = mclk / (tx_freq * 32); + stb0899_write_reg(state, STB0899_DISF22, f22_tx); /* DiSEqC Tx freq */ + state->rx_freq = 20000; + + return 0; +} + +static int stb0899_sleep(struct dvb_frontend *fe) +{ + struct stb0899_state *state = fe->demodulator_priv; +/* + u8 reg; +*/ + dprintk(state->verbose, FE_DEBUG, 1, "Going to Sleep .. (Really tired .. :-))"); + /* post process event */ + stb0899_postproc(state, STB0899_POSTPROC_GPIO_POWER, 0); + + return 0; +} + +static int stb0899_wakeup(struct dvb_frontend *fe) +{ + int rc; + struct stb0899_state *state = fe->demodulator_priv; + + if ((rc = stb0899_write_reg(state, STB0899_SYNTCTRL, STB0899_SELOSCI))) + return rc; + /* Activate all clocks; DVB-S2 registers are inaccessible otherwise. */ + if ((rc = stb0899_write_reg(state, STB0899_STOPCLK1, 0x00))) + return rc; + if ((rc = stb0899_write_reg(state, STB0899_STOPCLK2, 0x00))) + return rc; + + /* post process event */ + stb0899_postproc(state, STB0899_POSTPROC_GPIO_POWER, 1); + + return 0; +} + +static int stb0899_init(struct dvb_frontend *fe) +{ + int i; + struct stb0899_state *state = fe->demodulator_priv; + struct stb0899_config *config = state->config; + + dprintk(state->verbose, FE_DEBUG, 1, "Initializing STB0899 ... "); + + /* init device */ + dprintk(state->verbose, FE_DEBUG, 1, "init device"); + for (i = 0; config->init_dev[i].address != 0xffff; i++) + stb0899_write_reg(state, config->init_dev[i].address, config->init_dev[i].data); + + dprintk(state->verbose, FE_DEBUG, 1, "init S2 demod"); + /* init S2 demod */ + for (i = 0; config->init_s2_demod[i].offset != 0xffff; i++) + stb0899_write_s2reg(state, STB0899_S2DEMOD, + config->init_s2_demod[i].base_address, + config->init_s2_demod[i].offset, + config->init_s2_demod[i].data); + + dprintk(state->verbose, FE_DEBUG, 1, "init S1 demod"); + /* init S1 demod */ + for (i = 0; config->init_s1_demod[i].address != 0xffff; i++) + stb0899_write_reg(state, config->init_s1_demod[i].address, config->init_s1_demod[i].data); + + dprintk(state->verbose, FE_DEBUG, 1, "init S2 FEC"); + /* init S2 fec */ + for (i = 0; config->init_s2_fec[i].offset != 0xffff; i++) + stb0899_write_s2reg(state, STB0899_S2FEC, + config->init_s2_fec[i].base_address, + config->init_s2_fec[i].offset, + config->init_s2_fec[i].data); + + dprintk(state->verbose, FE_DEBUG, 1, "init TST"); + /* init test */ + for (i = 0; config->init_tst[i].address != 0xffff; i++) + stb0899_write_reg(state, config->init_tst[i].address, config->init_tst[i].data); + + stb0899_init_calc(state); + stb0899_diseqc_init(state); + + return 0; +} + +static int stb0899_table_lookup(const struct stb0899_tab *tab, int max, int val) +{ + int res = 0; + int min = 0, med; + + if (val < tab[min].read) + res = tab[min].real; + else if (val >= tab[max].read) + res = tab[max].real; + else { + while ((max - min) > 1) { + med = (max + min) / 2; + if (val >= tab[min].read && val < tab[med].read) + max = med; + else + min = med; + } + res = ((val - tab[min].read) * + (tab[max].real - tab[min].real) / + (tab[max].read - tab[min].read)) + + tab[min].real; + } + + return res; +} + +static int stb0899_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct stb0899_state *state = fe->demodulator_priv; + struct stb0899_internal *internal = &state->internal; + + int val; + u32 reg; + *strength = 0; + switch (state->delsys) { + case SYS_DVBS: + case SYS_DSS: + if (internal->lock) { + reg = stb0899_read_reg(state, STB0899_VSTATUS); + if (STB0899_GETFIELD(VSTATUS_LOCKEDVIT, reg)) { + + reg = stb0899_read_reg(state, STB0899_AGCIQIN); + val = (s32)(s8)STB0899_GETFIELD(AGCIQVALUE, reg); + + *strength = stb0899_table_lookup(stb0899_dvbsrf_tab, ARRAY_SIZE(stb0899_dvbsrf_tab) - 1, val); + *strength += 750; + dprintk(state->verbose, FE_DEBUG, 1, "AGCIQVALUE = 0x%02x, C = %d * 0.1 dBm", + val & 0xff, *strength); + } + } + break; + case SYS_DVBS2: + if (internal->lock) { + reg = STB0899_READ_S2REG(STB0899_S2DEMOD, IF_AGC_GAIN); + val = STB0899_GETFIELD(IF_AGC_GAIN, reg); + + *strength = stb0899_table_lookup(stb0899_dvbs2rf_tab, ARRAY_SIZE(stb0899_dvbs2rf_tab) - 1, val); + *strength += 950; + dprintk(state->verbose, FE_DEBUG, 1, "IF_AGC_GAIN = 0x%04x, C = %d * 0.1 dBm", + val & 0x3fff, *strength); + } + break; + default: + dprintk(state->verbose, FE_DEBUG, 1, "Unsupported delivery system"); + return -EINVAL; + } + + return 0; +} + +static int stb0899_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct stb0899_state *state = fe->demodulator_priv; + struct stb0899_internal *internal = &state->internal; + + unsigned int val, quant, quantn = -1, est, estn = -1; + u8 buf[2]; + u32 reg; + + *snr = 0; + reg = stb0899_read_reg(state, STB0899_VSTATUS); + switch (state->delsys) { + case SYS_DVBS: + case SYS_DSS: + if (internal->lock) { + if (STB0899_GETFIELD(VSTATUS_LOCKEDVIT, reg)) { + + stb0899_read_regs(state, STB0899_NIRM, buf, 2); + val = MAKEWORD16(buf[0], buf[1]); + + *snr = stb0899_table_lookup(stb0899_cn_tab, ARRAY_SIZE(stb0899_cn_tab) - 1, val); + dprintk(state->verbose, FE_DEBUG, 1, "NIR = 0x%02x%02x = %u, C/N = %d * 0.1 dBm\n", + buf[0], buf[1], val, *snr); + } + } + break; + case SYS_DVBS2: + if (internal->lock) { + reg = STB0899_READ_S2REG(STB0899_S2DEMOD, UWP_CNTRL1); + quant = STB0899_GETFIELD(UWP_ESN0_QUANT, reg); + reg = STB0899_READ_S2REG(STB0899_S2DEMOD, UWP_STAT2); + est = STB0899_GETFIELD(ESN0_EST, reg); + if (est == 1) + val = 301; /* C/N = 30.1 dB */ + else if (est == 2) + val = 270; /* C/N = 27.0 dB */ + else { + /* quantn = 100 * log(quant^2) */ + quantn = stb0899_table_lookup(stb0899_quant_tab, ARRAY_SIZE(stb0899_quant_tab) - 1, quant * 100); + /* estn = 100 * log(est) */ + estn = stb0899_table_lookup(stb0899_est_tab, ARRAY_SIZE(stb0899_est_tab) - 1, est); + /* snr(dBm/10) = -10*(log(est)-log(quant^2)) => snr(dBm/10) = (100*log(quant^2)-100*log(est))/10 */ + val = (quantn - estn) / 10; + } + *snr = val; + dprintk(state->verbose, FE_DEBUG, 1, "Es/N0 quant = %d (%d) estimate = %u (%d), C/N = %d * 0.1 dBm", + quant, quantn, est, estn, val); + } + break; + default: + dprintk(state->verbose, FE_DEBUG, 1, "Unsupported delivery system"); + return -EINVAL; + } + + return 0; +} + +static int stb0899_read_status(struct dvb_frontend *fe, enum fe_status *status) +{ + struct stb0899_state *state = fe->demodulator_priv; + struct stb0899_internal *internal = &state->internal; + u8 reg; + *status = 0; + + switch (state->delsys) { + case SYS_DVBS: + case SYS_DSS: + dprintk(state->verbose, FE_DEBUG, 1, "Delivery system DVB-S/DSS"); + if (internal->lock) { + reg = stb0899_read_reg(state, STB0899_VSTATUS); + if (STB0899_GETFIELD(VSTATUS_LOCKEDVIT, reg)) { + dprintk(state->verbose, FE_DEBUG, 1, "--------> FE_HAS_CARRIER | FE_HAS_LOCK"); + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK; + + reg = stb0899_read_reg(state, STB0899_PLPARM); + if (STB0899_GETFIELD(VITCURPUN, reg)) { + dprintk(state->verbose, FE_DEBUG, 1, "--------> FE_HAS_VITERBI | FE_HAS_SYNC"); + *status |= FE_HAS_VITERBI | FE_HAS_SYNC; + /* post process event */ + stb0899_postproc(state, STB0899_POSTPROC_GPIO_LOCK, 1); + } + } + } + break; + case SYS_DVBS2: + dprintk(state->verbose, FE_DEBUG, 1, "Delivery system DVB-S2"); + if (internal->lock) { + reg = STB0899_READ_S2REG(STB0899_S2DEMOD, DMD_STAT2); + if (STB0899_GETFIELD(UWP_LOCK, reg) && STB0899_GETFIELD(CSM_LOCK, reg)) { + *status |= FE_HAS_CARRIER; + dprintk(state->verbose, FE_DEBUG, 1, + "UWP & CSM Lock ! ---> DVB-S2 FE_HAS_CARRIER"); + + reg = stb0899_read_reg(state, STB0899_CFGPDELSTATUS1); + if (STB0899_GETFIELD(CFGPDELSTATUS_LOCK, reg)) { + *status |= FE_HAS_LOCK; + dprintk(state->verbose, FE_DEBUG, 1, + "Packet Delineator Locked ! -----> DVB-S2 FE_HAS_LOCK"); + + } + if (STB0899_GETFIELD(CONTINUOUS_STREAM, reg)) { + *status |= FE_HAS_VITERBI; + dprintk(state->verbose, FE_DEBUG, 1, + "Packet Delineator found VITERBI ! -----> DVB-S2 FE_HAS_VITERBI"); + } + if (STB0899_GETFIELD(ACCEPTED_STREAM, reg)) { + *status |= FE_HAS_SYNC; + dprintk(state->verbose, FE_DEBUG, 1, + "Packet Delineator found SYNC ! -----> DVB-S2 FE_HAS_SYNC"); + /* post process event */ + stb0899_postproc(state, STB0899_POSTPROC_GPIO_LOCK, 1); + } + } + } + break; + default: + dprintk(state->verbose, FE_DEBUG, 1, "Unsupported delivery system"); + return -EINVAL; + } + return 0; +} + +/* + * stb0899_get_error + * viterbi error for DVB-S/DSS + * packet error for DVB-S2 + * Bit Error Rate or Packet Error Rate * 10 ^ 7 + */ +static int stb0899_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct stb0899_state *state = fe->demodulator_priv; + struct stb0899_internal *internal = &state->internal; + + u8 lsb, msb; + + *ber = 0; + + switch (state->delsys) { + case SYS_DVBS: + case SYS_DSS: + if (internal->lock) { + lsb = stb0899_read_reg(state, STB0899_ECNT1L); + msb = stb0899_read_reg(state, STB0899_ECNT1M); + *ber = MAKEWORD16(msb, lsb); + /* Viterbi Check */ + if (STB0899_GETFIELD(VSTATUS_PRFVIT, internal->v_status)) { + /* Error Rate */ + *ber *= 9766; + /* ber = ber * 10 ^ 7 */ + *ber /= (-1 + (1 << (2 * STB0899_GETFIELD(NOE, internal->err_ctrl)))); + *ber /= 8; + } + } + break; + case SYS_DVBS2: + if (internal->lock) { + lsb = stb0899_read_reg(state, STB0899_ECNT1L); + msb = stb0899_read_reg(state, STB0899_ECNT1M); + *ber = MAKEWORD16(msb, lsb); + /* ber = ber * 10 ^ 7 */ + *ber *= 10000000; + *ber /= (-1 + (1 << (4 + 2 * STB0899_GETFIELD(NOE, internal->err_ctrl)))); + } + break; + default: + dprintk(state->verbose, FE_DEBUG, 1, "Unsupported delivery system"); + return -EINVAL; + } + + return 0; +} + +static int stb0899_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) +{ + struct stb0899_state *state = fe->demodulator_priv; + + switch (voltage) { + case SEC_VOLTAGE_13: + stb0899_write_reg(state, STB0899_GPIO00CFG, 0x82); + stb0899_write_reg(state, STB0899_GPIO01CFG, 0x02); + stb0899_write_reg(state, STB0899_GPIO02CFG, 0x00); + break; + case SEC_VOLTAGE_18: + stb0899_write_reg(state, STB0899_GPIO00CFG, 0x02); + stb0899_write_reg(state, STB0899_GPIO01CFG, 0x02); + stb0899_write_reg(state, STB0899_GPIO02CFG, 0x82); + break; + case SEC_VOLTAGE_OFF: + stb0899_write_reg(state, STB0899_GPIO00CFG, 0x82); + stb0899_write_reg(state, STB0899_GPIO01CFG, 0x82); + stb0899_write_reg(state, STB0899_GPIO02CFG, 0x82); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int stb0899_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) +{ + struct stb0899_state *state = fe->demodulator_priv; + struct stb0899_internal *internal = &state->internal; + + u8 div, reg; + + /* wait for diseqc idle */ + if (stb0899_wait_diseqc_txidle(state, 100) < 0) + return -ETIMEDOUT; + + switch (tone) { + case SEC_TONE_ON: + div = (internal->master_clk / 100) / 5632; + div = (div + 5) / 10; + stb0899_write_reg(state, STB0899_DISEQCOCFG, 0x66); + reg = stb0899_read_reg(state, STB0899_ACRPRESC); + STB0899_SETFIELD_VAL(ACRPRESC, reg, 0x03); + stb0899_write_reg(state, STB0899_ACRPRESC, reg); + stb0899_write_reg(state, STB0899_ACRDIV1, div); + break; + case SEC_TONE_OFF: + stb0899_write_reg(state, STB0899_DISEQCOCFG, 0x20); + break; + default: + return -EINVAL; + } + return 0; +} + +int stb0899_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + int i2c_stat; + struct stb0899_state *state = fe->demodulator_priv; + + i2c_stat = stb0899_read_reg(state, STB0899_I2CRPT); + if (i2c_stat < 0) + goto err; + + if (enable) { + dprintk(state->verbose, FE_DEBUG, 1, "Enabling I2C Repeater ..."); + i2c_stat |= STB0899_I2CTON; + if (stb0899_write_reg(state, STB0899_I2CRPT, i2c_stat) < 0) + goto err; + } else { + dprintk(state->verbose, FE_DEBUG, 1, "Disabling I2C Repeater ..."); + i2c_stat &= ~STB0899_I2CTON; + if (stb0899_write_reg(state, STB0899_I2CRPT, i2c_stat) < 0) + goto err; + } + return 0; +err: + dprintk(state->verbose, FE_ERROR, 1, "I2C Repeater control failed"); + return -EREMOTEIO; +} + + +static inline void CONVERT32(u32 x, char *str) +{ + *str++ = (x >> 24) & 0xff; + *str++ = (x >> 16) & 0xff; + *str++ = (x >> 8) & 0xff; + *str++ = (x >> 0) & 0xff; + *str = '\0'; +} + +int stb0899_get_dev_id(struct stb0899_state *state) +{ + u8 chip_id, release; + u16 id; + u32 demod_ver = 0, fec_ver = 0; + char demod_str[5] = { 0 }; + char fec_str[5] = { 0 }; + + id = stb0899_read_reg(state, STB0899_DEV_ID); + dprintk(state->verbose, FE_DEBUG, 1, "ID reg=[0x%02x]", id); + chip_id = STB0899_GETFIELD(CHIP_ID, id); + release = STB0899_GETFIELD(CHIP_REL, id); + + dprintk(state->verbose, FE_ERROR, 1, "Device ID=[%d], Release=[%d]", + chip_id, release); + + CONVERT32(STB0899_READ_S2REG(STB0899_S2DEMOD, DMD_CORE_ID), (char *)&demod_str); + + demod_ver = STB0899_READ_S2REG(STB0899_S2DEMOD, DMD_VERSION_ID); + dprintk(state->verbose, FE_ERROR, 1, "Demodulator Core ID=[%s], Version=[%d]", (char *) &demod_str, demod_ver); + CONVERT32(STB0899_READ_S2REG(STB0899_S2FEC, FEC_CORE_ID_REG), (char *)&fec_str); + fec_ver = STB0899_READ_S2REG(STB0899_S2FEC, FEC_VER_ID_REG); + if (! (chip_id > 0)) { + dprintk(state->verbose, FE_ERROR, 1, "couldn't find a STB 0899"); + + return -ENODEV; + } + dprintk(state->verbose, FE_ERROR, 1, "FEC Core ID=[%s], Version=[%d]", (char*) &fec_str, fec_ver); + + return 0; +} + +static void stb0899_set_delivery(struct stb0899_state *state) +{ + u8 reg; + u8 stop_clk[2]; + + stop_clk[0] = stb0899_read_reg(state, STB0899_STOPCLK1); + stop_clk[1] = stb0899_read_reg(state, STB0899_STOPCLK2); + + switch (state->delsys) { + case SYS_DVBS: + dprintk(state->verbose, FE_DEBUG, 1, "Delivery System -- DVB-S"); + /* FECM/Viterbi ON */ + reg = stb0899_read_reg(state, STB0899_FECM); + STB0899_SETFIELD_VAL(FECM_RSVD0, reg, 0); + STB0899_SETFIELD_VAL(FECM_VITERBI_ON, reg, 1); + stb0899_write_reg(state, STB0899_FECM, reg); + + stb0899_write_reg(state, STB0899_RSULC, 0xb1); + stb0899_write_reg(state, STB0899_TSULC, 0x40); + stb0899_write_reg(state, STB0899_RSLLC, 0x42); + stb0899_write_reg(state, STB0899_TSLPL, 0x12); + + reg = stb0899_read_reg(state, STB0899_TSTRES); + STB0899_SETFIELD_VAL(FRESLDPC, reg, 1); + stb0899_write_reg(state, STB0899_TSTRES, reg); + + STB0899_SETFIELD_VAL(STOP_CHK8PSK, stop_clk[0], 1); + STB0899_SETFIELD_VAL(STOP_CKFEC108, stop_clk[0], 1); + STB0899_SETFIELD_VAL(STOP_CKFEC216, stop_clk[0], 1); + + STB0899_SETFIELD_VAL(STOP_CKPKDLIN108, stop_clk[1], 1); + STB0899_SETFIELD_VAL(STOP_CKPKDLIN216, stop_clk[1], 1); + + STB0899_SETFIELD_VAL(STOP_CKINTBUF216, stop_clk[0], 1); + STB0899_SETFIELD_VAL(STOP_CKCORE216, stop_clk[0], 0); + + STB0899_SETFIELD_VAL(STOP_CKS2DMD108, stop_clk[1], 1); + break; + case SYS_DVBS2: + /* FECM/Viterbi OFF */ + reg = stb0899_read_reg(state, STB0899_FECM); + STB0899_SETFIELD_VAL(FECM_RSVD0, reg, 0); + STB0899_SETFIELD_VAL(FECM_VITERBI_ON, reg, 0); + stb0899_write_reg(state, STB0899_FECM, reg); + + stb0899_write_reg(state, STB0899_RSULC, 0xb1); + stb0899_write_reg(state, STB0899_TSULC, 0x42); + stb0899_write_reg(state, STB0899_RSLLC, 0x40); + stb0899_write_reg(state, STB0899_TSLPL, 0x02); + + reg = stb0899_read_reg(state, STB0899_TSTRES); + STB0899_SETFIELD_VAL(FRESLDPC, reg, 0); + stb0899_write_reg(state, STB0899_TSTRES, reg); + + STB0899_SETFIELD_VAL(STOP_CHK8PSK, stop_clk[0], 1); + STB0899_SETFIELD_VAL(STOP_CKFEC108, stop_clk[0], 0); + STB0899_SETFIELD_VAL(STOP_CKFEC216, stop_clk[0], 0); + + STB0899_SETFIELD_VAL(STOP_CKPKDLIN108, stop_clk[1], 0); + STB0899_SETFIELD_VAL(STOP_CKPKDLIN216, stop_clk[1], 0); + + STB0899_SETFIELD_VAL(STOP_CKINTBUF216, stop_clk[0], 0); + STB0899_SETFIELD_VAL(STOP_CKCORE216, stop_clk[0], 0); + + STB0899_SETFIELD_VAL(STOP_CKS2DMD108, stop_clk[1], 0); + break; + case SYS_DSS: + /* FECM/Viterbi ON */ + reg = stb0899_read_reg(state, STB0899_FECM); + STB0899_SETFIELD_VAL(FECM_RSVD0, reg, 1); + STB0899_SETFIELD_VAL(FECM_VITERBI_ON, reg, 1); + stb0899_write_reg(state, STB0899_FECM, reg); + + stb0899_write_reg(state, STB0899_RSULC, 0xa1); + stb0899_write_reg(state, STB0899_TSULC, 0x61); + stb0899_write_reg(state, STB0899_RSLLC, 0x42); + + reg = stb0899_read_reg(state, STB0899_TSTRES); + STB0899_SETFIELD_VAL(FRESLDPC, reg, 1); + stb0899_write_reg(state, STB0899_TSTRES, reg); + + STB0899_SETFIELD_VAL(STOP_CHK8PSK, stop_clk[0], 1); + STB0899_SETFIELD_VAL(STOP_CKFEC108, stop_clk[0], 1); + STB0899_SETFIELD_VAL(STOP_CKFEC216, stop_clk[0], 1); + + STB0899_SETFIELD_VAL(STOP_CKPKDLIN108, stop_clk[1], 1); + STB0899_SETFIELD_VAL(STOP_CKPKDLIN216, stop_clk[1], 1); + + STB0899_SETFIELD_VAL(STOP_CKCORE216, stop_clk[0], 0); + + STB0899_SETFIELD_VAL(STOP_CKS2DMD108, stop_clk[1], 1); + break; + default: + dprintk(state->verbose, FE_ERROR, 1, "Unsupported delivery system"); + break; + } + STB0899_SETFIELD_VAL(STOP_CKADCI108, stop_clk[0], 0); + stb0899_write_regs(state, STB0899_STOPCLK1, stop_clk, 2); +} + +/* + * stb0899_set_iterations + * set the LDPC iteration scale function + */ +static void stb0899_set_iterations(struct stb0899_state *state) +{ + struct stb0899_internal *internal = &state->internal; + struct stb0899_config *config = state->config; + + s32 iter_scale; + u32 reg; + + iter_scale = 17 * (internal->master_clk / 1000); + iter_scale += 410000; + iter_scale /= (internal->srate / 1000000); + iter_scale /= 1000; + + if (iter_scale > config->ldpc_max_iter) + iter_scale = config->ldpc_max_iter; + + reg = STB0899_READ_S2REG(STB0899_S2FEC, MAX_ITER); + STB0899_SETFIELD_VAL(MAX_ITERATIONS, reg, iter_scale); + stb0899_write_s2reg(state, STB0899_S2FEC, STB0899_BASE_MAX_ITER, STB0899_OFF0_MAX_ITER, reg); +} + +static enum dvbfe_search stb0899_search(struct dvb_frontend *fe) +{ + struct stb0899_state *state = fe->demodulator_priv; + struct stb0899_params *i_params = &state->params; + struct stb0899_internal *internal = &state->internal; + struct stb0899_config *config = state->config; + struct dtv_frontend_properties *props = &fe->dtv_property_cache; + + u32 SearchRange, gain; + + i_params->freq = props->frequency; + i_params->srate = props->symbol_rate; + state->delsys = props->delivery_system; + dprintk(state->verbose, FE_DEBUG, 1, "delivery system=%d", state->delsys); + + SearchRange = 10000000; + dprintk(state->verbose, FE_DEBUG, 1, "Frequency=%d, Srate=%d", i_params->freq, i_params->srate); + /* checking Search Range is meaningless for a fixed 3 Mhz */ + if (INRANGE(i_params->srate, 1000000, 45000000)) { + dprintk(state->verbose, FE_DEBUG, 1, "Parameters IN RANGE"); + stb0899_set_delivery(state); + + if (state->config->tuner_set_rfsiggain) { + if (internal->srate > 15000000) + gain = 8; /* 15Mb < srate < 45Mb, gain = 8dB */ + else if (internal->srate > 5000000) + gain = 12; /* 5Mb < srate < 15Mb, gain = 12dB */ + else + gain = 14; /* 1Mb < srate < 5Mb, gain = 14db */ + state->config->tuner_set_rfsiggain(fe, gain); + } + + if (i_params->srate <= 5000000) + stb0899_set_mclk(state, config->lo_clk); + else + stb0899_set_mclk(state, config->hi_clk); + + switch (state->delsys) { + case SYS_DVBS: + case SYS_DSS: + dprintk(state->verbose, FE_DEBUG, 1, "DVB-S delivery system"); + internal->freq = i_params->freq; + internal->srate = i_params->srate; + /* + * search = user search range + + * 500Khz + + * 2 * Tuner_step_size + + * 10% of the symbol rate + */ + internal->srch_range = SearchRange + 1500000 + (i_params->srate / 5); + internal->derot_percent = 30; + + /* What to do for tuners having no bandwidth setup ? */ + /* enable tuner I/O */ + stb0899_i2c_gate_ctrl(&state->frontend, 1); + + if (state->config->tuner_set_bandwidth) + state->config->tuner_set_bandwidth(fe, (13 * (stb0899_carr_width(state) + SearchRange)) / 10); + if (state->config->tuner_get_bandwidth) + state->config->tuner_get_bandwidth(fe, &internal->tuner_bw); + + /* disable tuner I/O */ + stb0899_i2c_gate_ctrl(&state->frontend, 0); + + /* Set DVB-S1 AGC */ + stb0899_write_reg(state, STB0899_AGCRFCFG, 0x11); + + /* Run the search algorithm */ + dprintk(state->verbose, FE_DEBUG, 1, "running DVB-S search algo .."); + if (stb0899_dvbs_algo(state) == RANGEOK) { + internal->lock = 1; + dprintk(state->verbose, FE_DEBUG, 1, + "-------------------------------------> DVB-S LOCK !"); + +// stb0899_write_reg(state, STB0899_ERRCTRL1, 0x3d); /* Viterbi Errors */ +// internal->v_status = stb0899_read_reg(state, STB0899_VSTATUS); +// internal->err_ctrl = stb0899_read_reg(state, STB0899_ERRCTRL1); +// dprintk(state->verbose, FE_DEBUG, 1, "VSTATUS=0x%02x", internal->v_status); +// dprintk(state->verbose, FE_DEBUG, 1, "ERR_CTRL=0x%02x", internal->err_ctrl); + + return DVBFE_ALGO_SEARCH_SUCCESS; + } else { + internal->lock = 0; + + return DVBFE_ALGO_SEARCH_FAILED; + } + break; + case SYS_DVBS2: + internal->freq = i_params->freq; + internal->srate = i_params->srate; + internal->srch_range = SearchRange; + + /* enable tuner I/O */ + stb0899_i2c_gate_ctrl(&state->frontend, 1); + + if (state->config->tuner_set_bandwidth) + state->config->tuner_set_bandwidth(fe, (stb0899_carr_width(state) + SearchRange)); + if (state->config->tuner_get_bandwidth) + state->config->tuner_get_bandwidth(fe, &internal->tuner_bw); + + /* disable tuner I/O */ + stb0899_i2c_gate_ctrl(&state->frontend, 0); + +// pParams->SpectralInv = pSearch->IQ_Inversion; + + /* Set DVB-S2 AGC */ + stb0899_write_reg(state, STB0899_AGCRFCFG, 0x1c); + + /* Set IterScale =f(MCLK,SYMB) */ + stb0899_set_iterations(state); + + /* Run the search algorithm */ + dprintk(state->verbose, FE_DEBUG, 1, "running DVB-S2 search algo .."); + if (stb0899_dvbs2_algo(state) == DVBS2_FEC_LOCK) { + internal->lock = 1; + dprintk(state->verbose, FE_DEBUG, 1, + "-------------------------------------> DVB-S2 LOCK !"); + +// stb0899_write_reg(state, STB0899_ERRCTRL1, 0xb6); /* Packet Errors */ +// internal->v_status = stb0899_read_reg(state, STB0899_VSTATUS); +// internal->err_ctrl = stb0899_read_reg(state, STB0899_ERRCTRL1); + + return DVBFE_ALGO_SEARCH_SUCCESS; + } else { + internal->lock = 0; + + return DVBFE_ALGO_SEARCH_FAILED; + } + break; + default: + dprintk(state->verbose, FE_ERROR, 1, "Unsupported delivery system"); + return DVBFE_ALGO_SEARCH_INVALID; + } + } + + return DVBFE_ALGO_SEARCH_ERROR; +} + +static int stb0899_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct stb0899_state *state = fe->demodulator_priv; + struct stb0899_internal *internal = &state->internal; + + dprintk(state->verbose, FE_DEBUG, 1, "Get params"); + p->symbol_rate = internal->srate; + p->frequency = internal->freq; + + return 0; +} + +static enum dvbfe_algo stb0899_frontend_algo(struct dvb_frontend *fe) +{ + return DVBFE_ALGO_CUSTOM; +} + +static struct dvb_frontend_ops stb0899_ops = { + .delsys = { SYS_DVBS, SYS_DVBS2, SYS_DSS }, + .info = { + .name = "STB0899 Multistandard", + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 0, + .frequency_tolerance = 0, + .symbol_rate_min = 5000000, + .symbol_rate_max = 45000000, + + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_AUTO | + FE_CAN_2G_MODULATION | + FE_CAN_QPSK + }, + + .release = stb0899_release, + .init = stb0899_init, + .sleep = stb0899_sleep, +// .wakeup = stb0899_wakeup, + + .i2c_gate_ctrl = stb0899_i2c_gate_ctrl, + + .get_frontend_algo = stb0899_frontend_algo, + .search = stb0899_search, + .get_frontend = stb0899_get_frontend, + + + .read_status = stb0899_read_status, + .read_snr = stb0899_read_snr, + .read_signal_strength = stb0899_read_signal_strength, + .read_ber = stb0899_read_ber, + + .set_voltage = stb0899_set_voltage, + .set_tone = stb0899_set_tone, + + .diseqc_send_master_cmd = stb0899_send_diseqc_msg, + .diseqc_recv_slave_reply = stb0899_recv_slave_reply, + .diseqc_send_burst = stb0899_send_diseqc_burst, +}; + +struct dvb_frontend *stb0899_attach(struct stb0899_config *config, struct i2c_adapter *i2c) +{ + struct stb0899_state *state = NULL; + enum stb0899_inversion inversion; + + state = kzalloc(sizeof (struct stb0899_state), GFP_KERNEL); + if (state == NULL) + goto error; + + inversion = config->inversion; + state->verbose = &verbose; + state->config = config; + state->i2c = i2c; + state->frontend.ops = stb0899_ops; + state->frontend.demodulator_priv = state; + state->internal.inversion = inversion; + + stb0899_wakeup(&state->frontend); + if (stb0899_get_dev_id(state) == -ENODEV) { + printk("%s: Exiting .. !\n", __func__); + goto error; + } + + printk("%s: Attaching STB0899 \n", __func__); + return &state->frontend; + +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(stb0899_attach); +MODULE_PARM_DESC(verbose, "Set Verbosity level"); +MODULE_AUTHOR("Manu Abraham"); +MODULE_DESCRIPTION("STB0899 Multi-Std frontend"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/stb0899_drv.h b/drivers/media/dvb-frontends/stb0899_drv.h similarity index 100% rename from drivers/media/dvb/frontends/stb0899_drv.h rename to drivers/media/dvb-frontends/stb0899_drv.h diff --git a/drivers/media/dvb/frontends/stb0899_priv.h b/drivers/media/dvb-frontends/stb0899_priv.h similarity index 100% rename from drivers/media/dvb/frontends/stb0899_priv.h rename to drivers/media/dvb-frontends/stb0899_priv.h diff --git a/drivers/media/dvb/frontends/stb0899_reg.h b/drivers/media/dvb-frontends/stb0899_reg.h similarity index 100% rename from drivers/media/dvb/frontends/stb0899_reg.h rename to drivers/media/dvb-frontends/stb0899_reg.h diff --git a/drivers/media/dvb/frontends/stb6000.c b/drivers/media/dvb-frontends/stb6000.c similarity index 100% rename from drivers/media/dvb/frontends/stb6000.c rename to drivers/media/dvb-frontends/stb6000.c diff --git a/drivers/media/dvb/frontends/stb6000.h b/drivers/media/dvb-frontends/stb6000.h similarity index 100% rename from drivers/media/dvb/frontends/stb6000.h rename to drivers/media/dvb-frontends/stb6000.h diff --git a/drivers/media/dvb/frontends/stb6100.c b/drivers/media/dvb-frontends/stb6100.c similarity index 100% rename from drivers/media/dvb/frontends/stb6100.c rename to drivers/media/dvb-frontends/stb6100.c diff --git a/drivers/media/dvb/frontends/stb6100.h b/drivers/media/dvb-frontends/stb6100.h similarity index 100% rename from drivers/media/dvb/frontends/stb6100.h rename to drivers/media/dvb-frontends/stb6100.h diff --git a/drivers/media/dvb/frontends/stb6100_cfg.h b/drivers/media/dvb-frontends/stb6100_cfg.h similarity index 100% rename from drivers/media/dvb/frontends/stb6100_cfg.h rename to drivers/media/dvb-frontends/stb6100_cfg.h diff --git a/drivers/media/dvb/frontends/stb6100_proc.h b/drivers/media/dvb-frontends/stb6100_proc.h similarity index 100% rename from drivers/media/dvb/frontends/stb6100_proc.h rename to drivers/media/dvb-frontends/stb6100_proc.h diff --git a/drivers/media/dvb/frontends/stv0288.c b/drivers/media/dvb-frontends/stv0288.c similarity index 100% rename from drivers/media/dvb/frontends/stv0288.c rename to drivers/media/dvb-frontends/stv0288.c diff --git a/drivers/media/dvb/frontends/stv0288.h b/drivers/media/dvb-frontends/stv0288.h similarity index 100% rename from drivers/media/dvb/frontends/stv0288.h rename to drivers/media/dvb-frontends/stv0288.h diff --git a/drivers/media/dvb/frontends/stv0297.c b/drivers/media/dvb-frontends/stv0297.c similarity index 100% rename from drivers/media/dvb/frontends/stv0297.c rename to drivers/media/dvb-frontends/stv0297.c diff --git a/drivers/media/dvb/frontends/stv0297.h b/drivers/media/dvb-frontends/stv0297.h similarity index 100% rename from drivers/media/dvb/frontends/stv0297.h rename to drivers/media/dvb-frontends/stv0297.h diff --git a/drivers/media/dvb/frontends/stv0299.c b/drivers/media/dvb-frontends/stv0299.c similarity index 100% rename from drivers/media/dvb/frontends/stv0299.c rename to drivers/media/dvb-frontends/stv0299.c diff --git a/drivers/media/dvb/frontends/stv0299.h b/drivers/media/dvb-frontends/stv0299.h similarity index 100% rename from drivers/media/dvb/frontends/stv0299.h rename to drivers/media/dvb-frontends/stv0299.h diff --git a/drivers/media/dvb/frontends/stv0367.c b/drivers/media/dvb-frontends/stv0367.c similarity index 100% rename from drivers/media/dvb/frontends/stv0367.c rename to drivers/media/dvb-frontends/stv0367.c diff --git a/drivers/media/dvb/frontends/stv0367.h b/drivers/media/dvb-frontends/stv0367.h similarity index 100% rename from drivers/media/dvb/frontends/stv0367.h rename to drivers/media/dvb-frontends/stv0367.h diff --git a/drivers/media/dvb/frontends/stv0367_priv.h b/drivers/media/dvb-frontends/stv0367_priv.h similarity index 100% rename from drivers/media/dvb/frontends/stv0367_priv.h rename to drivers/media/dvb-frontends/stv0367_priv.h diff --git a/drivers/media/dvb/frontends/stv0367_regs.h b/drivers/media/dvb-frontends/stv0367_regs.h similarity index 100% rename from drivers/media/dvb/frontends/stv0367_regs.h rename to drivers/media/dvb-frontends/stv0367_regs.h diff --git a/drivers/media/dvb/frontends/stv0900.h b/drivers/media/dvb-frontends/stv0900.h similarity index 100% rename from drivers/media/dvb/frontends/stv0900.h rename to drivers/media/dvb-frontends/stv0900.h diff --git a/drivers/media/dvb/frontends/stv0900_core.c b/drivers/media/dvb-frontends/stv0900_core.c similarity index 100% rename from drivers/media/dvb/frontends/stv0900_core.c rename to drivers/media/dvb-frontends/stv0900_core.c diff --git a/drivers/media/dvb/frontends/stv0900_init.h b/drivers/media/dvb-frontends/stv0900_init.h similarity index 100% rename from drivers/media/dvb/frontends/stv0900_init.h rename to drivers/media/dvb-frontends/stv0900_init.h diff --git a/drivers/media/dvb/frontends/stv0900_priv.h b/drivers/media/dvb-frontends/stv0900_priv.h similarity index 100% rename from drivers/media/dvb/frontends/stv0900_priv.h rename to drivers/media/dvb-frontends/stv0900_priv.h diff --git a/drivers/media/dvb/frontends/stv0900_reg.h b/drivers/media/dvb-frontends/stv0900_reg.h similarity index 100% rename from drivers/media/dvb/frontends/stv0900_reg.h rename to drivers/media/dvb-frontends/stv0900_reg.h diff --git a/drivers/media/dvb/frontends/stv0900_sw.c b/drivers/media/dvb-frontends/stv0900_sw.c similarity index 100% rename from drivers/media/dvb/frontends/stv0900_sw.c rename to drivers/media/dvb-frontends/stv0900_sw.c diff --git a/drivers/media/dvb-frontends/stv090x.c b/drivers/media/dvb-frontends/stv090x.c new file mode 100644 index 000000000000..13caec013902 --- /dev/null +++ b/drivers/media/dvb-frontends/stv090x.c @@ -0,0 +1,4855 @@ +/* + STV0900/0903 Multistandard Broadcast Frontend driver + Copyright (C) Manu Abraham + + Copyright (C) ST Microelectronics + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include "dvb_frontend.h" + +#include "stv6110x.h" /* for demodulator internal modes */ + +#include "stv090x_reg.h" +#include "stv090x.h" +#include "stv090x_priv.h" + +static unsigned int verbose; +module_param(verbose, int, 0644); + +/* internal params node */ +struct stv090x_dev { + /* pointer for internal params, one for each pair of demods */ + struct stv090x_internal *internal; + struct stv090x_dev *next_dev; +}; + +/* first internal params */ +static struct stv090x_dev *stv090x_first_dev; + +/* find chip by i2c adapter and i2c address */ +static struct stv090x_dev *find_dev(struct i2c_adapter *i2c_adap, + u8 i2c_addr) +{ + struct stv090x_dev *temp_dev = stv090x_first_dev; + + /* + Search of the last stv0900 chip or + find it by i2c adapter and i2c address */ + while ((temp_dev != NULL) && + ((temp_dev->internal->i2c_adap != i2c_adap) || + (temp_dev->internal->i2c_addr != i2c_addr))) { + + temp_dev = temp_dev->next_dev; + } + + return temp_dev; +} + +/* deallocating chip */ +static void remove_dev(struct stv090x_internal *internal) +{ + struct stv090x_dev *prev_dev = stv090x_first_dev; + struct stv090x_dev *del_dev = find_dev(internal->i2c_adap, + internal->i2c_addr); + + if (del_dev != NULL) { + if (del_dev == stv090x_first_dev) { + stv090x_first_dev = del_dev->next_dev; + } else { + while (prev_dev->next_dev != del_dev) + prev_dev = prev_dev->next_dev; + + prev_dev->next_dev = del_dev->next_dev; + } + + kfree(del_dev); + } +} + +/* allocating new chip */ +static struct stv090x_dev *append_internal(struct stv090x_internal *internal) +{ + struct stv090x_dev *new_dev; + struct stv090x_dev *temp_dev; + + new_dev = kmalloc(sizeof(struct stv090x_dev), GFP_KERNEL); + if (new_dev != NULL) { + new_dev->internal = internal; + new_dev->next_dev = NULL; + + /* append to list */ + if (stv090x_first_dev == NULL) { + stv090x_first_dev = new_dev; + } else { + temp_dev = stv090x_first_dev; + while (temp_dev->next_dev != NULL) + temp_dev = temp_dev->next_dev; + + temp_dev->next_dev = new_dev; + } + } + + return new_dev; +} + + +/* DVBS1 and DSS C/N Lookup table */ +static const struct stv090x_tab stv090x_s1cn_tab[] = { + { 0, 8917 }, /* 0.0dB */ + { 5, 8801 }, /* 0.5dB */ + { 10, 8667 }, /* 1.0dB */ + { 15, 8522 }, /* 1.5dB */ + { 20, 8355 }, /* 2.0dB */ + { 25, 8175 }, /* 2.5dB */ + { 30, 7979 }, /* 3.0dB */ + { 35, 7763 }, /* 3.5dB */ + { 40, 7530 }, /* 4.0dB */ + { 45, 7282 }, /* 4.5dB */ + { 50, 7026 }, /* 5.0dB */ + { 55, 6781 }, /* 5.5dB */ + { 60, 6514 }, /* 6.0dB */ + { 65, 6241 }, /* 6.5dB */ + { 70, 5965 }, /* 7.0dB */ + { 75, 5690 }, /* 7.5dB */ + { 80, 5424 }, /* 8.0dB */ + { 85, 5161 }, /* 8.5dB */ + { 90, 4902 }, /* 9.0dB */ + { 95, 4654 }, /* 9.5dB */ + { 100, 4417 }, /* 10.0dB */ + { 105, 4186 }, /* 10.5dB */ + { 110, 3968 }, /* 11.0dB */ + { 115, 3757 }, /* 11.5dB */ + { 120, 3558 }, /* 12.0dB */ + { 125, 3366 }, /* 12.5dB */ + { 130, 3185 }, /* 13.0dB */ + { 135, 3012 }, /* 13.5dB */ + { 140, 2850 }, /* 14.0dB */ + { 145, 2698 }, /* 14.5dB */ + { 150, 2550 }, /* 15.0dB */ + { 160, 2283 }, /* 16.0dB */ + { 170, 2042 }, /* 17.0dB */ + { 180, 1827 }, /* 18.0dB */ + { 190, 1636 }, /* 19.0dB */ + { 200, 1466 }, /* 20.0dB */ + { 210, 1315 }, /* 21.0dB */ + { 220, 1181 }, /* 22.0dB */ + { 230, 1064 }, /* 23.0dB */ + { 240, 960 }, /* 24.0dB */ + { 250, 869 }, /* 25.0dB */ + { 260, 792 }, /* 26.0dB */ + { 270, 724 }, /* 27.0dB */ + { 280, 665 }, /* 28.0dB */ + { 290, 616 }, /* 29.0dB */ + { 300, 573 }, /* 30.0dB */ + { 310, 537 }, /* 31.0dB */ + { 320, 507 }, /* 32.0dB */ + { 330, 483 }, /* 33.0dB */ + { 400, 398 }, /* 40.0dB */ + { 450, 381 }, /* 45.0dB */ + { 500, 377 } /* 50.0dB */ +}; + +/* DVBS2 C/N Lookup table */ +static const struct stv090x_tab stv090x_s2cn_tab[] = { + { -30, 13348 }, /* -3.0dB */ + { -20, 12640 }, /* -2d.0B */ + { -10, 11883 }, /* -1.0dB */ + { 0, 11101 }, /* -0.0dB */ + { 5, 10718 }, /* 0.5dB */ + { 10, 10339 }, /* 1.0dB */ + { 15, 9947 }, /* 1.5dB */ + { 20, 9552 }, /* 2.0dB */ + { 25, 9183 }, /* 2.5dB */ + { 30, 8799 }, /* 3.0dB */ + { 35, 8422 }, /* 3.5dB */ + { 40, 8062 }, /* 4.0dB */ + { 45, 7707 }, /* 4.5dB */ + { 50, 7353 }, /* 5.0dB */ + { 55, 7025 }, /* 5.5dB */ + { 60, 6684 }, /* 6.0dB */ + { 65, 6331 }, /* 6.5dB */ + { 70, 6036 }, /* 7.0dB */ + { 75, 5727 }, /* 7.5dB */ + { 80, 5437 }, /* 8.0dB */ + { 85, 5164 }, /* 8.5dB */ + { 90, 4902 }, /* 9.0dB */ + { 95, 4653 }, /* 9.5dB */ + { 100, 4408 }, /* 10.0dB */ + { 105, 4187 }, /* 10.5dB */ + { 110, 3961 }, /* 11.0dB */ + { 115, 3751 }, /* 11.5dB */ + { 120, 3558 }, /* 12.0dB */ + { 125, 3368 }, /* 12.5dB */ + { 130, 3191 }, /* 13.0dB */ + { 135, 3017 }, /* 13.5dB */ + { 140, 2862 }, /* 14.0dB */ + { 145, 2710 }, /* 14.5dB */ + { 150, 2565 }, /* 15.0dB */ + { 160, 2300 }, /* 16.0dB */ + { 170, 2058 }, /* 17.0dB */ + { 180, 1849 }, /* 18.0dB */ + { 190, 1663 }, /* 19.0dB */ + { 200, 1495 }, /* 20.0dB */ + { 210, 1349 }, /* 21.0dB */ + { 220, 1222 }, /* 22.0dB */ + { 230, 1110 }, /* 23.0dB */ + { 240, 1011 }, /* 24.0dB */ + { 250, 925 }, /* 25.0dB */ + { 260, 853 }, /* 26.0dB */ + { 270, 789 }, /* 27.0dB */ + { 280, 734 }, /* 28.0dB */ + { 290, 690 }, /* 29.0dB */ + { 300, 650 }, /* 30.0dB */ + { 310, 619 }, /* 31.0dB */ + { 320, 593 }, /* 32.0dB */ + { 330, 571 }, /* 33.0dB */ + { 400, 498 }, /* 40.0dB */ + { 450, 484 }, /* 45.0dB */ + { 500, 481 } /* 50.0dB */ +}; + +/* RF level C/N lookup table */ +static const struct stv090x_tab stv090x_rf_tab[] = { + { -5, 0xcaa1 }, /* -5dBm */ + { -10, 0xc229 }, /* -10dBm */ + { -15, 0xbb08 }, /* -15dBm */ + { -20, 0xb4bc }, /* -20dBm */ + { -25, 0xad5a }, /* -25dBm */ + { -30, 0xa298 }, /* -30dBm */ + { -35, 0x98a8 }, /* -35dBm */ + { -40, 0x8389 }, /* -40dBm */ + { -45, 0x59be }, /* -45dBm */ + { -50, 0x3a14 }, /* -50dBm */ + { -55, 0x2d11 }, /* -55dBm */ + { -60, 0x210d }, /* -60dBm */ + { -65, 0xa14f }, /* -65dBm */ + { -70, 0x07aa } /* -70dBm */ +}; + + +static struct stv090x_reg stv0900_initval[] = { + + { STV090x_OUTCFG, 0x00 }, + { STV090x_MODECFG, 0xff }, + { STV090x_AGCRF1CFG, 0x11 }, + { STV090x_AGCRF2CFG, 0x13 }, + { STV090x_TSGENERAL1X, 0x14 }, + { STV090x_TSTTNR2, 0x21 }, + { STV090x_TSTTNR4, 0x21 }, + { STV090x_P2_DISTXCTL, 0x22 }, + { STV090x_P2_F22TX, 0xc0 }, + { STV090x_P2_F22RX, 0xc0 }, + { STV090x_P2_DISRXCTL, 0x00 }, + { STV090x_P2_DMDCFGMD, 0xF9 }, + { STV090x_P2_DEMOD, 0x08 }, + { STV090x_P2_DMDCFG3, 0xc4 }, + { STV090x_P2_CARFREQ, 0xed }, + { STV090x_P2_LDT, 0xd0 }, + { STV090x_P2_LDT2, 0xb8 }, + { STV090x_P2_TMGCFG, 0xd2 }, + { STV090x_P2_TMGTHRISE, 0x20 }, + { STV090x_P1_TMGCFG, 0xd2 }, + + { STV090x_P2_TMGTHFALL, 0x00 }, + { STV090x_P2_FECSPY, 0x88 }, + { STV090x_P2_FSPYDATA, 0x3a }, + { STV090x_P2_FBERCPT4, 0x00 }, + { STV090x_P2_FSPYBER, 0x10 }, + { STV090x_P2_ERRCTRL1, 0x35 }, + { STV090x_P2_ERRCTRL2, 0xc1 }, + { STV090x_P2_CFRICFG, 0xf8 }, + { STV090x_P2_NOSCFG, 0x1c }, + { STV090x_P2_DMDTOM, 0x20 }, + { STV090x_P2_CORRELMANT, 0x70 }, + { STV090x_P2_CORRELABS, 0x88 }, + { STV090x_P2_AGC2O, 0x5b }, + { STV090x_P2_AGC2REF, 0x38 }, + { STV090x_P2_CARCFG, 0xe4 }, + { STV090x_P2_ACLC, 0x1A }, + { STV090x_P2_BCLC, 0x09 }, + { STV090x_P2_CARHDR, 0x08 }, + { STV090x_P2_KREFTMG, 0xc1 }, + { STV090x_P2_SFRUPRATIO, 0xf0 }, + { STV090x_P2_SFRLOWRATIO, 0x70 }, + { STV090x_P2_SFRSTEP, 0x58 }, + { STV090x_P2_TMGCFG2, 0x01 }, + { STV090x_P2_CAR2CFG, 0x26 }, + { STV090x_P2_BCLC2S2Q, 0x86 }, + { STV090x_P2_BCLC2S28, 0x86 }, + { STV090x_P2_SMAPCOEF7, 0x77 }, + { STV090x_P2_SMAPCOEF6, 0x85 }, + { STV090x_P2_SMAPCOEF5, 0x77 }, + { STV090x_P2_TSCFGL, 0x20 }, + { STV090x_P2_DMDCFG2, 0x3b }, + { STV090x_P2_MODCODLST0, 0xff }, + { STV090x_P2_MODCODLST1, 0xff }, + { STV090x_P2_MODCODLST2, 0xff }, + { STV090x_P2_MODCODLST3, 0xff }, + { STV090x_P2_MODCODLST4, 0xff }, + { STV090x_P2_MODCODLST5, 0xff }, + { STV090x_P2_MODCODLST6, 0xff }, + { STV090x_P2_MODCODLST7, 0xcc }, + { STV090x_P2_MODCODLST8, 0xcc }, + { STV090x_P2_MODCODLST9, 0xcc }, + { STV090x_P2_MODCODLSTA, 0xcc }, + { STV090x_P2_MODCODLSTB, 0xcc }, + { STV090x_P2_MODCODLSTC, 0xcc }, + { STV090x_P2_MODCODLSTD, 0xcc }, + { STV090x_P2_MODCODLSTE, 0xcc }, + { STV090x_P2_MODCODLSTF, 0xcf }, + { STV090x_P1_DISTXCTL, 0x22 }, + { STV090x_P1_F22TX, 0xc0 }, + { STV090x_P1_F22RX, 0xc0 }, + { STV090x_P1_DISRXCTL, 0x00 }, + { STV090x_P1_DMDCFGMD, 0xf9 }, + { STV090x_P1_DEMOD, 0x08 }, + { STV090x_P1_DMDCFG3, 0xc4 }, + { STV090x_P1_DMDTOM, 0x20 }, + { STV090x_P1_CARFREQ, 0xed }, + { STV090x_P1_LDT, 0xd0 }, + { STV090x_P1_LDT2, 0xb8 }, + { STV090x_P1_TMGCFG, 0xd2 }, + { STV090x_P1_TMGTHRISE, 0x20 }, + { STV090x_P1_TMGTHFALL, 0x00 }, + { STV090x_P1_SFRUPRATIO, 0xf0 }, + { STV090x_P1_SFRLOWRATIO, 0x70 }, + { STV090x_P1_TSCFGL, 0x20 }, + { STV090x_P1_FECSPY, 0x88 }, + { STV090x_P1_FSPYDATA, 0x3a }, + { STV090x_P1_FBERCPT4, 0x00 }, + { STV090x_P1_FSPYBER, 0x10 }, + { STV090x_P1_ERRCTRL1, 0x35 }, + { STV090x_P1_ERRCTRL2, 0xc1 }, + { STV090x_P1_CFRICFG, 0xf8 }, + { STV090x_P1_NOSCFG, 0x1c }, + { STV090x_P1_CORRELMANT, 0x70 }, + { STV090x_P1_CORRELABS, 0x88 }, + { STV090x_P1_AGC2O, 0x5b }, + { STV090x_P1_AGC2REF, 0x38 }, + { STV090x_P1_CARCFG, 0xe4 }, + { STV090x_P1_ACLC, 0x1A }, + { STV090x_P1_BCLC, 0x09 }, + { STV090x_P1_CARHDR, 0x08 }, + { STV090x_P1_KREFTMG, 0xc1 }, + { STV090x_P1_SFRSTEP, 0x58 }, + { STV090x_P1_TMGCFG2, 0x01 }, + { STV090x_P1_CAR2CFG, 0x26 }, + { STV090x_P1_BCLC2S2Q, 0x86 }, + { STV090x_P1_BCLC2S28, 0x86 }, + { STV090x_P1_SMAPCOEF7, 0x77 }, + { STV090x_P1_SMAPCOEF6, 0x85 }, + { STV090x_P1_SMAPCOEF5, 0x77 }, + { STV090x_P1_DMDCFG2, 0x3b }, + { STV090x_P1_MODCODLST0, 0xff }, + { STV090x_P1_MODCODLST1, 0xff }, + { STV090x_P1_MODCODLST2, 0xff }, + { STV090x_P1_MODCODLST3, 0xff }, + { STV090x_P1_MODCODLST4, 0xff }, + { STV090x_P1_MODCODLST5, 0xff }, + { STV090x_P1_MODCODLST6, 0xff }, + { STV090x_P1_MODCODLST7, 0xcc }, + { STV090x_P1_MODCODLST8, 0xcc }, + { STV090x_P1_MODCODLST9, 0xcc }, + { STV090x_P1_MODCODLSTA, 0xcc }, + { STV090x_P1_MODCODLSTB, 0xcc }, + { STV090x_P1_MODCODLSTC, 0xcc }, + { STV090x_P1_MODCODLSTD, 0xcc }, + { STV090x_P1_MODCODLSTE, 0xcc }, + { STV090x_P1_MODCODLSTF, 0xcf }, + { STV090x_GENCFG, 0x1d }, + { STV090x_NBITER_NF4, 0x37 }, + { STV090x_NBITER_NF5, 0x29 }, + { STV090x_NBITER_NF6, 0x37 }, + { STV090x_NBITER_NF7, 0x33 }, + { STV090x_NBITER_NF8, 0x31 }, + { STV090x_NBITER_NF9, 0x2f }, + { STV090x_NBITER_NF10, 0x39 }, + { STV090x_NBITER_NF11, 0x3a }, + { STV090x_NBITER_NF12, 0x29 }, + { STV090x_NBITER_NF13, 0x37 }, + { STV090x_NBITER_NF14, 0x33 }, + { STV090x_NBITER_NF15, 0x2f }, + { STV090x_NBITER_NF16, 0x39 }, + { STV090x_NBITER_NF17, 0x3a }, + { STV090x_NBITERNOERR, 0x04 }, + { STV090x_GAINLLR_NF4, 0x0C }, + { STV090x_GAINLLR_NF5, 0x0F }, + { STV090x_GAINLLR_NF6, 0x11 }, + { STV090x_GAINLLR_NF7, 0x14 }, + { STV090x_GAINLLR_NF8, 0x17 }, + { STV090x_GAINLLR_NF9, 0x19 }, + { STV090x_GAINLLR_NF10, 0x20 }, + { STV090x_GAINLLR_NF11, 0x21 }, + { STV090x_GAINLLR_NF12, 0x0D }, + { STV090x_GAINLLR_NF13, 0x0F }, + { STV090x_GAINLLR_NF14, 0x13 }, + { STV090x_GAINLLR_NF15, 0x1A }, + { STV090x_GAINLLR_NF16, 0x1F }, + { STV090x_GAINLLR_NF17, 0x21 }, + { STV090x_RCCFGH, 0x20 }, + { STV090x_P1_FECM, 0x01 }, /* disable DSS modes */ + { STV090x_P2_FECM, 0x01 }, /* disable DSS modes */ + { STV090x_P1_PRVIT, 0x2F }, /* disable PR 6/7 */ + { STV090x_P2_PRVIT, 0x2F }, /* disable PR 6/7 */ +}; + +static struct stv090x_reg stv0903_initval[] = { + { STV090x_OUTCFG, 0x00 }, + { STV090x_AGCRF1CFG, 0x11 }, + { STV090x_STOPCLK1, 0x48 }, + { STV090x_STOPCLK2, 0x14 }, + { STV090x_TSTTNR1, 0x27 }, + { STV090x_TSTTNR2, 0x21 }, + { STV090x_P1_DISTXCTL, 0x22 }, + { STV090x_P1_F22TX, 0xc0 }, + { STV090x_P1_F22RX, 0xc0 }, + { STV090x_P1_DISRXCTL, 0x00 }, + { STV090x_P1_DMDCFGMD, 0xF9 }, + { STV090x_P1_DEMOD, 0x08 }, + { STV090x_P1_DMDCFG3, 0xc4 }, + { STV090x_P1_CARFREQ, 0xed }, + { STV090x_P1_TNRCFG2, 0x82 }, + { STV090x_P1_LDT, 0xd0 }, + { STV090x_P1_LDT2, 0xb8 }, + { STV090x_P1_TMGCFG, 0xd2 }, + { STV090x_P1_TMGTHRISE, 0x20 }, + { STV090x_P1_TMGTHFALL, 0x00 }, + { STV090x_P1_SFRUPRATIO, 0xf0 }, + { STV090x_P1_SFRLOWRATIO, 0x70 }, + { STV090x_P1_TSCFGL, 0x20 }, + { STV090x_P1_FECSPY, 0x88 }, + { STV090x_P1_FSPYDATA, 0x3a }, + { STV090x_P1_FBERCPT4, 0x00 }, + { STV090x_P1_FSPYBER, 0x10 }, + { STV090x_P1_ERRCTRL1, 0x35 }, + { STV090x_P1_ERRCTRL2, 0xc1 }, + { STV090x_P1_CFRICFG, 0xf8 }, + { STV090x_P1_NOSCFG, 0x1c }, + { STV090x_P1_DMDTOM, 0x20 }, + { STV090x_P1_CORRELMANT, 0x70 }, + { STV090x_P1_CORRELABS, 0x88 }, + { STV090x_P1_AGC2O, 0x5b }, + { STV090x_P1_AGC2REF, 0x38 }, + { STV090x_P1_CARCFG, 0xe4 }, + { STV090x_P1_ACLC, 0x1A }, + { STV090x_P1_BCLC, 0x09 }, + { STV090x_P1_CARHDR, 0x08 }, + { STV090x_P1_KREFTMG, 0xc1 }, + { STV090x_P1_SFRSTEP, 0x58 }, + { STV090x_P1_TMGCFG2, 0x01 }, + { STV090x_P1_CAR2CFG, 0x26 }, + { STV090x_P1_BCLC2S2Q, 0x86 }, + { STV090x_P1_BCLC2S28, 0x86 }, + { STV090x_P1_SMAPCOEF7, 0x77 }, + { STV090x_P1_SMAPCOEF6, 0x85 }, + { STV090x_P1_SMAPCOEF5, 0x77 }, + { STV090x_P1_DMDCFG2, 0x3b }, + { STV090x_P1_MODCODLST0, 0xff }, + { STV090x_P1_MODCODLST1, 0xff }, + { STV090x_P1_MODCODLST2, 0xff }, + { STV090x_P1_MODCODLST3, 0xff }, + { STV090x_P1_MODCODLST4, 0xff }, + { STV090x_P1_MODCODLST5, 0xff }, + { STV090x_P1_MODCODLST6, 0xff }, + { STV090x_P1_MODCODLST7, 0xcc }, + { STV090x_P1_MODCODLST8, 0xcc }, + { STV090x_P1_MODCODLST9, 0xcc }, + { STV090x_P1_MODCODLSTA, 0xcc }, + { STV090x_P1_MODCODLSTB, 0xcc }, + { STV090x_P1_MODCODLSTC, 0xcc }, + { STV090x_P1_MODCODLSTD, 0xcc }, + { STV090x_P1_MODCODLSTE, 0xcc }, + { STV090x_P1_MODCODLSTF, 0xcf }, + { STV090x_GENCFG, 0x1c }, + { STV090x_NBITER_NF4, 0x37 }, + { STV090x_NBITER_NF5, 0x29 }, + { STV090x_NBITER_NF6, 0x37 }, + { STV090x_NBITER_NF7, 0x33 }, + { STV090x_NBITER_NF8, 0x31 }, + { STV090x_NBITER_NF9, 0x2f }, + { STV090x_NBITER_NF10, 0x39 }, + { STV090x_NBITER_NF11, 0x3a }, + { STV090x_NBITER_NF12, 0x29 }, + { STV090x_NBITER_NF13, 0x37 }, + { STV090x_NBITER_NF14, 0x33 }, + { STV090x_NBITER_NF15, 0x2f }, + { STV090x_NBITER_NF16, 0x39 }, + { STV090x_NBITER_NF17, 0x3a }, + { STV090x_NBITERNOERR, 0x04 }, + { STV090x_GAINLLR_NF4, 0x0C }, + { STV090x_GAINLLR_NF5, 0x0F }, + { STV090x_GAINLLR_NF6, 0x11 }, + { STV090x_GAINLLR_NF7, 0x14 }, + { STV090x_GAINLLR_NF8, 0x17 }, + { STV090x_GAINLLR_NF9, 0x19 }, + { STV090x_GAINLLR_NF10, 0x20 }, + { STV090x_GAINLLR_NF11, 0x21 }, + { STV090x_GAINLLR_NF12, 0x0D }, + { STV090x_GAINLLR_NF13, 0x0F }, + { STV090x_GAINLLR_NF14, 0x13 }, + { STV090x_GAINLLR_NF15, 0x1A }, + { STV090x_GAINLLR_NF16, 0x1F }, + { STV090x_GAINLLR_NF17, 0x21 }, + { STV090x_RCCFGH, 0x20 }, + { STV090x_P1_FECM, 0x01 }, /*disable the DSS mode */ + { STV090x_P1_PRVIT, 0x2f } /*disable puncture rate 6/7*/ +}; + +static struct stv090x_reg stv0900_cut20_val[] = { + + { STV090x_P2_DMDCFG3, 0xe8 }, + { STV090x_P2_DMDCFG4, 0x10 }, + { STV090x_P2_CARFREQ, 0x38 }, + { STV090x_P2_CARHDR, 0x20 }, + { STV090x_P2_KREFTMG, 0x5a }, + { STV090x_P2_SMAPCOEF7, 0x06 }, + { STV090x_P2_SMAPCOEF6, 0x00 }, + { STV090x_P2_SMAPCOEF5, 0x04 }, + { STV090x_P2_NOSCFG, 0x0c }, + { STV090x_P1_DMDCFG3, 0xe8 }, + { STV090x_P1_DMDCFG4, 0x10 }, + { STV090x_P1_CARFREQ, 0x38 }, + { STV090x_P1_CARHDR, 0x20 }, + { STV090x_P1_KREFTMG, 0x5a }, + { STV090x_P1_SMAPCOEF7, 0x06 }, + { STV090x_P1_SMAPCOEF6, 0x00 }, + { STV090x_P1_SMAPCOEF5, 0x04 }, + { STV090x_P1_NOSCFG, 0x0c }, + { STV090x_GAINLLR_NF4, 0x21 }, + { STV090x_GAINLLR_NF5, 0x21 }, + { STV090x_GAINLLR_NF6, 0x20 }, + { STV090x_GAINLLR_NF7, 0x1F }, + { STV090x_GAINLLR_NF8, 0x1E }, + { STV090x_GAINLLR_NF9, 0x1E }, + { STV090x_GAINLLR_NF10, 0x1D }, + { STV090x_GAINLLR_NF11, 0x1B }, + { STV090x_GAINLLR_NF12, 0x20 }, + { STV090x_GAINLLR_NF13, 0x20 }, + { STV090x_GAINLLR_NF14, 0x20 }, + { STV090x_GAINLLR_NF15, 0x20 }, + { STV090x_GAINLLR_NF16, 0x20 }, + { STV090x_GAINLLR_NF17, 0x21 }, +}; + +static struct stv090x_reg stv0903_cut20_val[] = { + { STV090x_P1_DMDCFG3, 0xe8 }, + { STV090x_P1_DMDCFG4, 0x10 }, + { STV090x_P1_CARFREQ, 0x38 }, + { STV090x_P1_CARHDR, 0x20 }, + { STV090x_P1_KREFTMG, 0x5a }, + { STV090x_P1_SMAPCOEF7, 0x06 }, + { STV090x_P1_SMAPCOEF6, 0x00 }, + { STV090x_P1_SMAPCOEF5, 0x04 }, + { STV090x_P1_NOSCFG, 0x0c }, + { STV090x_GAINLLR_NF4, 0x21 }, + { STV090x_GAINLLR_NF5, 0x21 }, + { STV090x_GAINLLR_NF6, 0x20 }, + { STV090x_GAINLLR_NF7, 0x1F }, + { STV090x_GAINLLR_NF8, 0x1E }, + { STV090x_GAINLLR_NF9, 0x1E }, + { STV090x_GAINLLR_NF10, 0x1D }, + { STV090x_GAINLLR_NF11, 0x1B }, + { STV090x_GAINLLR_NF12, 0x20 }, + { STV090x_GAINLLR_NF13, 0x20 }, + { STV090x_GAINLLR_NF14, 0x20 }, + { STV090x_GAINLLR_NF15, 0x20 }, + { STV090x_GAINLLR_NF16, 0x20 }, + { STV090x_GAINLLR_NF17, 0x21 } +}; + +/* Cut 2.0 Long Frame Tracking CR loop */ +static struct stv090x_long_frame_crloop stv090x_s2_crl_cut20[] = { + /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ + { STV090x_QPSK_12, 0x1f, 0x3f, 0x1e, 0x3f, 0x3d, 0x1f, 0x3d, 0x3e, 0x3d, 0x1e }, + { STV090x_QPSK_35, 0x2f, 0x3f, 0x2e, 0x2f, 0x3d, 0x0f, 0x0e, 0x2e, 0x3d, 0x0e }, + { STV090x_QPSK_23, 0x2f, 0x3f, 0x2e, 0x2f, 0x0e, 0x0f, 0x0e, 0x1e, 0x3d, 0x3d }, + { STV090x_QPSK_34, 0x3f, 0x3f, 0x3e, 0x1f, 0x0e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, + { STV090x_QPSK_45, 0x3f, 0x3f, 0x3e, 0x1f, 0x0e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, + { STV090x_QPSK_56, 0x3f, 0x3f, 0x3e, 0x1f, 0x0e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, + { STV090x_QPSK_89, 0x3f, 0x3f, 0x3e, 0x1f, 0x1e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, + { STV090x_QPSK_910, 0x3f, 0x3f, 0x3e, 0x1f, 0x1e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, + { STV090x_8PSK_35, 0x3c, 0x3e, 0x1c, 0x2e, 0x0c, 0x1e, 0x2b, 0x2d, 0x1b, 0x1d }, + { STV090x_8PSK_23, 0x1d, 0x3e, 0x3c, 0x2e, 0x2c, 0x1e, 0x0c, 0x2d, 0x2b, 0x1d }, + { STV090x_8PSK_34, 0x0e, 0x3e, 0x3d, 0x2e, 0x0d, 0x1e, 0x2c, 0x2d, 0x0c, 0x1d }, + { STV090x_8PSK_56, 0x2e, 0x3e, 0x1e, 0x2e, 0x2d, 0x1e, 0x3c, 0x2d, 0x2c, 0x1d }, + { STV090x_8PSK_89, 0x3e, 0x3e, 0x1e, 0x2e, 0x3d, 0x1e, 0x0d, 0x2d, 0x3c, 0x1d }, + { STV090x_8PSK_910, 0x3e, 0x3e, 0x1e, 0x2e, 0x3d, 0x1e, 0x1d, 0x2d, 0x0d, 0x1d } +}; + +/* Cut 3.0 Long Frame Tracking CR loop */ +static struct stv090x_long_frame_crloop stv090x_s2_crl_cut30[] = { + /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ + { STV090x_QPSK_12, 0x3c, 0x2c, 0x0c, 0x2c, 0x1b, 0x2c, 0x1b, 0x1c, 0x0b, 0x3b }, + { STV090x_QPSK_35, 0x0d, 0x0d, 0x0c, 0x0d, 0x1b, 0x3c, 0x1b, 0x1c, 0x0b, 0x3b }, + { STV090x_QPSK_23, 0x1d, 0x0d, 0x0c, 0x1d, 0x2b, 0x3c, 0x1b, 0x1c, 0x0b, 0x3b }, + { STV090x_QPSK_34, 0x1d, 0x1d, 0x0c, 0x1d, 0x2b, 0x3c, 0x1b, 0x1c, 0x0b, 0x3b }, + { STV090x_QPSK_45, 0x2d, 0x1d, 0x1c, 0x1d, 0x2b, 0x3c, 0x2b, 0x0c, 0x1b, 0x3b }, + { STV090x_QPSK_56, 0x2d, 0x1d, 0x1c, 0x1d, 0x2b, 0x3c, 0x2b, 0x0c, 0x1b, 0x3b }, + { STV090x_QPSK_89, 0x3d, 0x2d, 0x1c, 0x1d, 0x3b, 0x3c, 0x2b, 0x0c, 0x1b, 0x3b }, + { STV090x_QPSK_910, 0x3d, 0x2d, 0x1c, 0x1d, 0x3b, 0x3c, 0x2b, 0x0c, 0x1b, 0x3b }, + { STV090x_8PSK_35, 0x39, 0x29, 0x39, 0x19, 0x19, 0x19, 0x19, 0x19, 0x09, 0x19 }, + { STV090x_8PSK_23, 0x2a, 0x39, 0x1a, 0x0a, 0x39, 0x0a, 0x29, 0x39, 0x29, 0x0a }, + { STV090x_8PSK_34, 0x2b, 0x3a, 0x1b, 0x1b, 0x3a, 0x1b, 0x1a, 0x0b, 0x1a, 0x3a }, + { STV090x_8PSK_56, 0x0c, 0x1b, 0x3b, 0x3b, 0x1b, 0x3b, 0x3a, 0x3b, 0x3a, 0x1b }, + { STV090x_8PSK_89, 0x0d, 0x3c, 0x2c, 0x2c, 0x2b, 0x0c, 0x0b, 0x3b, 0x0b, 0x1b }, + { STV090x_8PSK_910, 0x0d, 0x0d, 0x2c, 0x3c, 0x3b, 0x1c, 0x0b, 0x3b, 0x0b, 0x1b } +}; + +/* Cut 2.0 Long Frame Tracking CR Loop */ +static struct stv090x_long_frame_crloop stv090x_s2_apsk_crl_cut20[] = { + /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ + { STV090x_16APSK_23, 0x0c, 0x0c, 0x0c, 0x0c, 0x1d, 0x0c, 0x3c, 0x0c, 0x2c, 0x0c }, + { STV090x_16APSK_34, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x2d, 0x0c, 0x1d, 0x0c }, + { STV090x_16APSK_45, 0x0c, 0x0c, 0x0c, 0x0c, 0x1e, 0x0c, 0x3d, 0x0c, 0x2d, 0x0c }, + { STV090x_16APSK_56, 0x0c, 0x0c, 0x0c, 0x0c, 0x1e, 0x0c, 0x3d, 0x0c, 0x2d, 0x0c }, + { STV090x_16APSK_89, 0x0c, 0x0c, 0x0c, 0x0c, 0x2e, 0x0c, 0x0e, 0x0c, 0x3d, 0x0c }, + { STV090x_16APSK_910, 0x0c, 0x0c, 0x0c, 0x0c, 0x2e, 0x0c, 0x0e, 0x0c, 0x3d, 0x0c }, + { STV090x_32APSK_34, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c }, + { STV090x_32APSK_45, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c }, + { STV090x_32APSK_56, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c }, + { STV090x_32APSK_89, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c }, + { STV090x_32APSK_910, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c } +}; + +/* Cut 3.0 Long Frame Tracking CR Loop */ +static struct stv090x_long_frame_crloop stv090x_s2_apsk_crl_cut30[] = { + /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ + { STV090x_16APSK_23, 0x0a, 0x0a, 0x0a, 0x0a, 0x1a, 0x0a, 0x3a, 0x0a, 0x2a, 0x0a }, + { STV090x_16APSK_34, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0a, 0x3b, 0x0a, 0x1b, 0x0a }, + { STV090x_16APSK_45, 0x0a, 0x0a, 0x0a, 0x0a, 0x1b, 0x0a, 0x3b, 0x0a, 0x2b, 0x0a }, + { STV090x_16APSK_56, 0x0a, 0x0a, 0x0a, 0x0a, 0x1b, 0x0a, 0x3b, 0x0a, 0x2b, 0x0a }, + { STV090x_16APSK_89, 0x0a, 0x0a, 0x0a, 0x0a, 0x2b, 0x0a, 0x0c, 0x0a, 0x3b, 0x0a }, + { STV090x_16APSK_910, 0x0a, 0x0a, 0x0a, 0x0a, 0x2b, 0x0a, 0x0c, 0x0a, 0x3b, 0x0a }, + { STV090x_32APSK_34, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a }, + { STV090x_32APSK_45, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a }, + { STV090x_32APSK_56, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a }, + { STV090x_32APSK_89, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a }, + { STV090x_32APSK_910, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } +}; + +static struct stv090x_long_frame_crloop stv090x_s2_lowqpsk_crl_cut20[] = { + /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ + { STV090x_QPSK_14, 0x0f, 0x3f, 0x0e, 0x3f, 0x2d, 0x2f, 0x2d, 0x1f, 0x3d, 0x3e }, + { STV090x_QPSK_13, 0x0f, 0x3f, 0x0e, 0x3f, 0x2d, 0x2f, 0x3d, 0x0f, 0x3d, 0x2e }, + { STV090x_QPSK_25, 0x1f, 0x3f, 0x1e, 0x3f, 0x3d, 0x1f, 0x3d, 0x3e, 0x3d, 0x2e } +}; + +static struct stv090x_long_frame_crloop stv090x_s2_lowqpsk_crl_cut30[] = { + /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ + { STV090x_QPSK_14, 0x0c, 0x3c, 0x0b, 0x3c, 0x2a, 0x2c, 0x2a, 0x1c, 0x3a, 0x3b }, + { STV090x_QPSK_13, 0x0c, 0x3c, 0x0b, 0x3c, 0x2a, 0x2c, 0x3a, 0x0c, 0x3a, 0x2b }, + { STV090x_QPSK_25, 0x1c, 0x3c, 0x1b, 0x3c, 0x3a, 0x1c, 0x3a, 0x3b, 0x3a, 0x2b } +}; + +/* Cut 2.0 Short Frame Tracking CR Loop */ +static struct stv090x_short_frame_crloop stv090x_s2_short_crl_cut20[] = { + /* MODCOD 2M 5M 10M 20M 30M */ + { STV090x_QPSK, 0x2f, 0x2e, 0x0e, 0x0e, 0x3d }, + { STV090x_8PSK, 0x3e, 0x0e, 0x2d, 0x0d, 0x3c }, + { STV090x_16APSK, 0x1e, 0x1e, 0x1e, 0x3d, 0x2d }, + { STV090x_32APSK, 0x1e, 0x1e, 0x1e, 0x3d, 0x2d } +}; + +/* Cut 3.0 Short Frame Tracking CR Loop */ +static struct stv090x_short_frame_crloop stv090x_s2_short_crl_cut30[] = { + /* MODCOD 2M 5M 10M 20M 30M */ + { STV090x_QPSK, 0x2C, 0x2B, 0x0B, 0x0B, 0x3A }, + { STV090x_8PSK, 0x3B, 0x0B, 0x2A, 0x0A, 0x39 }, + { STV090x_16APSK, 0x1B, 0x1B, 0x1B, 0x3A, 0x2A }, + { STV090x_32APSK, 0x1B, 0x1B, 0x1B, 0x3A, 0x2A } +}; + +static inline s32 comp2(s32 __x, s32 __width) +{ + if (__width == 32) + return __x; + else + return (__x >= (1 << (__width - 1))) ? (__x - (1 << __width)) : __x; +} + +static int stv090x_read_reg(struct stv090x_state *state, unsigned int reg) +{ + const struct stv090x_config *config = state->config; + int ret; + + u8 b0[] = { reg >> 8, reg & 0xff }; + u8 buf; + + struct i2c_msg msg[] = { + { .addr = config->address, .flags = 0, .buf = b0, .len = 2 }, + { .addr = config->address, .flags = I2C_M_RD, .buf = &buf, .len = 1 } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + if (ret != 2) { + if (ret != -ERESTARTSYS) + dprintk(FE_ERROR, 1, + "Read error, Reg=[0x%02x], Status=%d", + reg, ret); + + return ret < 0 ? ret : -EREMOTEIO; + } + if (unlikely(*state->verbose >= FE_DEBUGREG)) + dprintk(FE_ERROR, 1, "Reg=[0x%02x], data=%02x", + reg, buf); + + return (unsigned int) buf; +} + +static int stv090x_write_regs(struct stv090x_state *state, unsigned int reg, u8 *data, u32 count) +{ + const struct stv090x_config *config = state->config; + int ret; + u8 buf[2 + count]; + struct i2c_msg i2c_msg = { .addr = config->address, .flags = 0, .buf = buf, .len = 2 + count }; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + memcpy(&buf[2], data, count); + + if (unlikely(*state->verbose >= FE_DEBUGREG)) { + int i; + + printk(KERN_DEBUG "%s [0x%04x]:", __func__, reg); + for (i = 0; i < count; i++) + printk(" %02x", data[i]); + printk("\n"); + } + + ret = i2c_transfer(state->i2c, &i2c_msg, 1); + if (ret != 1) { + if (ret != -ERESTARTSYS) + dprintk(FE_ERROR, 1, "Reg=[0x%04x], Data=[0x%02x ...], Count=%u, Status=%d", + reg, data[0], count, ret); + return ret < 0 ? ret : -EREMOTEIO; + } + + return 0; +} + +static int stv090x_write_reg(struct stv090x_state *state, unsigned int reg, u8 data) +{ + return stv090x_write_regs(state, reg, &data, 1); +} + +static int stv090x_i2c_gate_ctrl(struct stv090x_state *state, int enable) +{ + u32 reg; + + /* + * NOTE! A lock is used as a FSM to control the state in which + * access is serialized between two tuners on the same demod. + * This has nothing to do with a lock to protect a critical section + * which may in some other cases be confused with protecting I/O + * access to the demodulator gate. + * In case of any error, the lock is unlocked and exit within the + * relevant operations themselves. + */ + if (enable) { + if (state->config->tuner_i2c_lock) + state->config->tuner_i2c_lock(&state->frontend, 1); + else + mutex_lock(&state->internal->tuner_lock); + } + + reg = STV090x_READ_DEMOD(state, I2CRPT); + if (enable) { + dprintk(FE_DEBUG, 1, "Enable Gate"); + STV090x_SETFIELD_Px(reg, I2CT_ON_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, I2CRPT, reg) < 0) + goto err; + + } else { + dprintk(FE_DEBUG, 1, "Disable Gate"); + STV090x_SETFIELD_Px(reg, I2CT_ON_FIELD, 0); + if ((STV090x_WRITE_DEMOD(state, I2CRPT, reg)) < 0) + goto err; + } + + if (!enable) { + if (state->config->tuner_i2c_lock) + state->config->tuner_i2c_lock(&state->frontend, 0); + else + mutex_unlock(&state->internal->tuner_lock); + } + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + if (state->config->tuner_i2c_lock) + state->config->tuner_i2c_lock(&state->frontend, 0); + else + mutex_unlock(&state->internal->tuner_lock); + return -1; +} + +static void stv090x_get_lock_tmg(struct stv090x_state *state) +{ + switch (state->algo) { + case STV090x_BLIND_SEARCH: + dprintk(FE_DEBUG, 1, "Blind Search"); + if (state->srate <= 1500000) { /*10Msps< SR <=15Msps*/ + state->DemodTimeout = 1500; + state->FecTimeout = 400; + } else if (state->srate <= 5000000) { /*10Msps< SR <=15Msps*/ + state->DemodTimeout = 1000; + state->FecTimeout = 300; + } else { /*SR >20Msps*/ + state->DemodTimeout = 700; + state->FecTimeout = 100; + } + break; + + case STV090x_COLD_SEARCH: + case STV090x_WARM_SEARCH: + default: + dprintk(FE_DEBUG, 1, "Normal Search"); + if (state->srate <= 1000000) { /*SR <=1Msps*/ + state->DemodTimeout = 4500; + state->FecTimeout = 1700; + } else if (state->srate <= 2000000) { /*1Msps < SR <= 2Msps */ + state->DemodTimeout = 2500; + state->FecTimeout = 1100; + } else if (state->srate <= 5000000) { /*2Msps < SR <= 5Msps */ + state->DemodTimeout = 1000; + state->FecTimeout = 550; + } else if (state->srate <= 10000000) { /*5Msps < SR <= 10Msps */ + state->DemodTimeout = 700; + state->FecTimeout = 250; + } else if (state->srate <= 20000000) { /*10Msps < SR <= 20Msps */ + state->DemodTimeout = 400; + state->FecTimeout = 130; + } else { /*SR >20Msps*/ + state->DemodTimeout = 300; + state->FecTimeout = 100; + } + break; + } + + if (state->algo == STV090x_WARM_SEARCH) + state->DemodTimeout /= 2; +} + +static int stv090x_set_srate(struct stv090x_state *state, u32 srate) +{ + u32 sym; + + if (srate > 60000000) { + sym = (srate << 4); /* SR * 2^16 / master_clk */ + sym /= (state->internal->mclk >> 12); + } else if (srate > 6000000) { + sym = (srate << 6); + sym /= (state->internal->mclk >> 10); + } else { + sym = (srate << 9); + sym /= (state->internal->mclk >> 7); + } + + if (STV090x_WRITE_DEMOD(state, SFRINIT1, (sym >> 8) & 0x7f) < 0) /* MSB */ + goto err; + if (STV090x_WRITE_DEMOD(state, SFRINIT0, (sym & 0xff)) < 0) /* LSB */ + goto err; + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_set_max_srate(struct stv090x_state *state, u32 clk, u32 srate) +{ + u32 sym; + + srate = 105 * (srate / 100); + if (srate > 60000000) { + sym = (srate << 4); /* SR * 2^16 / master_clk */ + sym /= (state->internal->mclk >> 12); + } else if (srate > 6000000) { + sym = (srate << 6); + sym /= (state->internal->mclk >> 10); + } else { + sym = (srate << 9); + sym /= (state->internal->mclk >> 7); + } + + if (sym < 0x7fff) { + if (STV090x_WRITE_DEMOD(state, SFRUP1, (sym >> 8) & 0x7f) < 0) /* MSB */ + goto err; + if (STV090x_WRITE_DEMOD(state, SFRUP0, sym & 0xff) < 0) /* LSB */ + goto err; + } else { + if (STV090x_WRITE_DEMOD(state, SFRUP1, 0x7f) < 0) /* MSB */ + goto err; + if (STV090x_WRITE_DEMOD(state, SFRUP0, 0xff) < 0) /* LSB */ + goto err; + } + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_set_min_srate(struct stv090x_state *state, u32 clk, u32 srate) +{ + u32 sym; + + srate = 95 * (srate / 100); + if (srate > 60000000) { + sym = (srate << 4); /* SR * 2^16 / master_clk */ + sym /= (state->internal->mclk >> 12); + } else if (srate > 6000000) { + sym = (srate << 6); + sym /= (state->internal->mclk >> 10); + } else { + sym = (srate << 9); + sym /= (state->internal->mclk >> 7); + } + + if (STV090x_WRITE_DEMOD(state, SFRLOW1, ((sym >> 8) & 0x7f)) < 0) /* MSB */ + goto err; + if (STV090x_WRITE_DEMOD(state, SFRLOW0, (sym & 0xff)) < 0) /* LSB */ + goto err; + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static u32 stv090x_car_width(u32 srate, enum stv090x_rolloff rolloff) +{ + u32 ro; + + switch (rolloff) { + case STV090x_RO_20: + ro = 20; + break; + case STV090x_RO_25: + ro = 25; + break; + case STV090x_RO_35: + default: + ro = 35; + break; + } + + return srate + (srate * ro) / 100; +} + +static int stv090x_set_vit_thacq(struct stv090x_state *state) +{ + if (STV090x_WRITE_DEMOD(state, VTH12, 0x96) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH23, 0x64) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH34, 0x36) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH56, 0x23) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH67, 0x1e) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH78, 0x19) < 0) + goto err; + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_set_vit_thtracq(struct stv090x_state *state) +{ + if (STV090x_WRITE_DEMOD(state, VTH12, 0xd0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH23, 0x7d) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH34, 0x53) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH56, 0x2f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH67, 0x24) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VTH78, 0x1f) < 0) + goto err; + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_set_viterbi(struct stv090x_state *state) +{ + switch (state->search_mode) { + case STV090x_SEARCH_AUTO: + if (STV090x_WRITE_DEMOD(state, FECM, 0x10) < 0) /* DVB-S and DVB-S2 */ + goto err; + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x3f) < 0) /* all puncture rate */ + goto err; + break; + case STV090x_SEARCH_DVBS1: + if (STV090x_WRITE_DEMOD(state, FECM, 0x00) < 0) /* disable DSS */ + goto err; + switch (state->fec) { + case STV090x_PR12: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x01) < 0) + goto err; + break; + + case STV090x_PR23: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x02) < 0) + goto err; + break; + + case STV090x_PR34: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x04) < 0) + goto err; + break; + + case STV090x_PR56: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x08) < 0) + goto err; + break; + + case STV090x_PR78: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x20) < 0) + goto err; + break; + + default: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x2f) < 0) /* all */ + goto err; + break; + } + break; + case STV090x_SEARCH_DSS: + if (STV090x_WRITE_DEMOD(state, FECM, 0x80) < 0) + goto err; + switch (state->fec) { + case STV090x_PR12: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x01) < 0) + goto err; + break; + + case STV090x_PR23: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x02) < 0) + goto err; + break; + + case STV090x_PR67: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x10) < 0) + goto err; + break; + + default: + if (STV090x_WRITE_DEMOD(state, PRVIT, 0x13) < 0) /* 1/2, 2/3, 6/7 */ + goto err; + break; + } + break; + default: + break; + } + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_stop_modcod(struct stv090x_state *state) +{ + if (STV090x_WRITE_DEMOD(state, MODCODLST0, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST1, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST2, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST3, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST4, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST5, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST6, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST7, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST8, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST9, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTA, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTB, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTC, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTD, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTE, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTF, 0xff) < 0) + goto err; + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_activate_modcod(struct stv090x_state *state) +{ + if (STV090x_WRITE_DEMOD(state, MODCODLST0, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST1, 0xfc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST2, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST3, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST4, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST5, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST6, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST7, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST8, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST9, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTA, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTB, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTC, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTD, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTE, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTF, 0xcf) < 0) + goto err; + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_activate_modcod_single(struct stv090x_state *state) +{ + + if (STV090x_WRITE_DEMOD(state, MODCODLST0, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST1, 0xf0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST2, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST3, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST4, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST5, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST6, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST7, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST8, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST9, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTA, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTB, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTC, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTD, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTE, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTF, 0x0f) < 0) + goto err; + + return 0; + +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_vitclk_ctl(struct stv090x_state *state, int enable) +{ + u32 reg; + + switch (state->demod) { + case STV090x_DEMODULATOR_0: + mutex_lock(&state->internal->demod_lock); + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + STV090x_SETFIELD(reg, STOP_CLKVIT1_FIELD, enable); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + mutex_unlock(&state->internal->demod_lock); + break; + + case STV090x_DEMODULATOR_1: + mutex_lock(&state->internal->demod_lock); + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + STV090x_SETFIELD(reg, STOP_CLKVIT2_FIELD, enable); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + mutex_unlock(&state->internal->demod_lock); + break; + + default: + dprintk(FE_ERROR, 1, "Wrong demodulator!"); + break; + } + return 0; +err: + mutex_unlock(&state->internal->demod_lock); + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_dvbs_track_crl(struct stv090x_state *state) +{ + if (state->internal->dev_ver >= 0x30) { + /* Set ACLC BCLC optimised value vs SR */ + if (state->srate >= 15000000) { + if (STV090x_WRITE_DEMOD(state, ACLC, 0x2b) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, BCLC, 0x1a) < 0) + goto err; + } else if ((state->srate >= 7000000) && (15000000 > state->srate)) { + if (STV090x_WRITE_DEMOD(state, ACLC, 0x0c) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, BCLC, 0x1b) < 0) + goto err; + } else if (state->srate < 7000000) { + if (STV090x_WRITE_DEMOD(state, ACLC, 0x2c) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, BCLC, 0x1c) < 0) + goto err; + } + + } else { + /* Cut 2.0 */ + if (STV090x_WRITE_DEMOD(state, ACLC, 0x1a) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, BCLC, 0x09) < 0) + goto err; + } + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_delivery_search(struct stv090x_state *state) +{ + u32 reg; + + switch (state->search_mode) { + case STV090x_SEARCH_DVBS1: + case STV090x_SEARCH_DSS: + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); + STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + + /* Activate Viterbi decoder in legacy search, + * do not use FRESVIT1, might impact VITERBI2 + */ + if (stv090x_vitclk_ctl(state, 0) < 0) + goto err; + + if (stv090x_dvbs_track_crl(state) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x22) < 0) /* disable DVB-S2 */ + goto err; + + if (stv090x_set_vit_thacq(state) < 0) + goto err; + if (stv090x_set_viterbi(state) < 0) + goto err; + break; + + case STV090x_SEARCH_DVBS2: + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 0); + STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); + STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + + if (stv090x_vitclk_ctl(state, 1) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, ACLC, 0x1a) < 0) /* stop DVB-S CR loop */ + goto err; + if (STV090x_WRITE_DEMOD(state, BCLC, 0x09) < 0) + goto err; + + if (state->internal->dev_ver <= 0x20) { + /* enable S2 carrier loop */ + if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x26) < 0) + goto err; + } else { + /* > Cut 3: Stop carrier 3 */ + if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x66) < 0) + goto err; + } + + if (state->demod_mode != STV090x_SINGLE) { + /* Cut 2: enable link during search */ + if (stv090x_activate_modcod(state) < 0) + goto err; + } else { + /* Single demodulator + * Authorize SHORT and LONG frames, + * QPSK, 8PSK, 16APSK and 32APSK + */ + if (stv090x_activate_modcod_single(state) < 0) + goto err; + } + + if (stv090x_set_vit_thtracq(state) < 0) + goto err; + break; + + case STV090x_SEARCH_AUTO: + default: + /* enable DVB-S2 and DVB-S2 in Auto MODE */ + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 0); + STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); + STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + + if (stv090x_vitclk_ctl(state, 0) < 0) + goto err; + + if (stv090x_dvbs_track_crl(state) < 0) + goto err; + + if (state->internal->dev_ver <= 0x20) { + /* enable S2 carrier loop */ + if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x26) < 0) + goto err; + } else { + /* > Cut 3: Stop carrier 3 */ + if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x66) < 0) + goto err; + } + + if (state->demod_mode != STV090x_SINGLE) { + /* Cut 2: enable link during search */ + if (stv090x_activate_modcod(state) < 0) + goto err; + } else { + /* Single demodulator + * Authorize SHORT and LONG frames, + * QPSK, 8PSK, 16APSK and 32APSK + */ + if (stv090x_activate_modcod_single(state) < 0) + goto err; + } + + if (stv090x_set_vit_thacq(state) < 0) + goto err; + + if (stv090x_set_viterbi(state) < 0) + goto err; + break; + } + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_start_search(struct stv090x_state *state) +{ + u32 reg, freq_abs; + s16 freq; + + /* Reset demodulator */ + reg = STV090x_READ_DEMOD(state, DMDISTATE); + STV090x_SETFIELD_Px(reg, I2C_DEMOD_MODE_FIELD, 0x1f); + if (STV090x_WRITE_DEMOD(state, DMDISTATE, reg) < 0) + goto err; + + if (state->internal->dev_ver <= 0x20) { + if (state->srate <= 5000000) { + if (STV090x_WRITE_DEMOD(state, CARCFG, 0x44) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRUP1, 0x0f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRUP0, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRLOW1, 0xf0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRLOW0, 0x00) < 0) + goto err; + + /*enlarge the timing bandwidth for Low SR*/ + if (STV090x_WRITE_DEMOD(state, RTCS2, 0x68) < 0) + goto err; + } else { + /* If the symbol rate is >5 Msps + Set The carrier search up and low to auto mode */ + if (STV090x_WRITE_DEMOD(state, CARCFG, 0xc4) < 0) + goto err; + /*reduce the timing bandwidth for high SR*/ + if (STV090x_WRITE_DEMOD(state, RTCS2, 0x44) < 0) + goto err; + } + } else { + /* >= Cut 3 */ + if (state->srate <= 5000000) { + /* enlarge the timing bandwidth for Low SR */ + STV090x_WRITE_DEMOD(state, RTCS2, 0x68); + } else { + /* reduce timing bandwidth for high SR */ + STV090x_WRITE_DEMOD(state, RTCS2, 0x44); + } + + /* Set CFR min and max to manual mode */ + STV090x_WRITE_DEMOD(state, CARCFG, 0x46); + + if (state->algo == STV090x_WARM_SEARCH) { + /* WARM Start + * CFR min = -1MHz, + * CFR max = +1MHz + */ + freq_abs = 1000 << 16; + freq_abs /= (state->internal->mclk / 1000); + freq = (s16) freq_abs; + } else { + /* COLD Start + * CFR min =- (SearchRange / 2 + 600KHz) + * CFR max = +(SearchRange / 2 + 600KHz) + * (600KHz for the tuner step size) + */ + freq_abs = (state->search_range / 2000) + 600; + freq_abs = freq_abs << 16; + freq_abs /= (state->internal->mclk / 1000); + freq = (s16) freq_abs; + } + + if (STV090x_WRITE_DEMOD(state, CFRUP1, MSB(freq)) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRUP0, LSB(freq)) < 0) + goto err; + + freq *= -1; + + if (STV090x_WRITE_DEMOD(state, CFRLOW1, MSB(freq)) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRLOW0, LSB(freq)) < 0) + goto err; + + } + + if (STV090x_WRITE_DEMOD(state, CFRINIT1, 0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, 0) < 0) + goto err; + + if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, EQUALCFG, 0x41) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, FFECFG, 0x41) < 0) + goto err; + + if ((state->search_mode == STV090x_SEARCH_DVBS1) || + (state->search_mode == STV090x_SEARCH_DSS) || + (state->search_mode == STV090x_SEARCH_AUTO)) { + + if (STV090x_WRITE_DEMOD(state, VITSCALE, 0x82) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VAVSRVIT, 0x00) < 0) + goto err; + } + } + + if (STV090x_WRITE_DEMOD(state, SFRSTEP, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGTHRISE, 0xe0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGTHFALL, 0xc0) < 0) + goto err; + + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, SCAN_ENABLE_FIELD, 0); + STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + reg = STV090x_READ_DEMOD(state, DMDCFG2); + STV090x_SETFIELD_Px(reg, S1S2_SEQUENTIAL_FIELD, 0x0); + if (STV090x_WRITE_DEMOD(state, DMDCFG2, reg) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, RTC, 0x88) < 0) + goto err; + + if (state->internal->dev_ver >= 0x20) { + /*Frequency offset detector setting*/ + if (state->srate < 2000000) { + if (state->internal->dev_ver <= 0x20) { + /* Cut 2 */ + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x39) < 0) + goto err; + } else { + /* Cut 3 */ + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x89) < 0) + goto err; + } + if (STV090x_WRITE_DEMOD(state, CARHDR, 0x40) < 0) + goto err; + } else if (state->srate < 10000000) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x4c) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CARHDR, 0x20) < 0) + goto err; + } else { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x4b) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CARHDR, 0x20) < 0) + goto err; + } + } else { + if (state->srate < 10000000) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0xef) < 0) + goto err; + } else { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0xed) < 0) + goto err; + } + } + + switch (state->algo) { + case STV090x_WARM_SEARCH: + /* The symbol rate and the exact + * carrier Frequency are known + */ + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) + goto err; + break; + + case STV090x_COLD_SEARCH: + /* The symbol rate is known */ + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x15) < 0) + goto err; + break; + + default: + break; + } + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_get_agc2_min_level(struct stv090x_state *state) +{ + u32 agc2_min = 0xffff, agc2 = 0, freq_init, freq_step, reg; + s32 i, j, steps, dir; + + if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) + goto err; + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, SCAN_ENABLE_FIELD, 0); + STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, SFRUP1, 0x83) < 0) /* SR = 65 Msps Max */ + goto err; + if (STV090x_WRITE_DEMOD(state, SFRUP0, 0xc0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRLOW1, 0x82) < 0) /* SR= 400 ksps Min */ + goto err; + if (STV090x_WRITE_DEMOD(state, SFRLOW0, 0xa0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDTOM, 0x00) < 0) /* stop acq @ coarse carrier state */ + goto err; + if (stv090x_set_srate(state, 1000000) < 0) + goto err; + + steps = state->search_range / 1000000; + if (steps <= 0) + steps = 1; + + dir = 1; + freq_step = (1000000 * 256) / (state->internal->mclk / 256); + freq_init = 0; + + for (i = 0; i < steps; i++) { + if (dir > 0) + freq_init = freq_init + (freq_step * i); + else + freq_init = freq_init - (freq_step * i); + + dir *= -1; + + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x5c) < 0) /* Demod RESET */ + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT1, (freq_init >> 8) & 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, freq_init & 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x58) < 0) /* Demod RESET */ + goto err; + msleep(10); + + agc2 = 0; + for (j = 0; j < 10; j++) { + agc2 += (STV090x_READ_DEMOD(state, AGC2I1) << 8) | + STV090x_READ_DEMOD(state, AGC2I0); + } + agc2 /= 10; + if (agc2 < agc2_min) + agc2_min = agc2; + } + + return agc2_min; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static u32 stv090x_get_srate(struct stv090x_state *state, u32 clk) +{ + u8 r3, r2, r1, r0; + s32 srate, int_1, int_2, tmp_1, tmp_2; + + r3 = STV090x_READ_DEMOD(state, SFR3); + r2 = STV090x_READ_DEMOD(state, SFR2); + r1 = STV090x_READ_DEMOD(state, SFR1); + r0 = STV090x_READ_DEMOD(state, SFR0); + + srate = ((r3 << 24) | (r2 << 16) | (r1 << 8) | r0); + + int_1 = clk >> 16; + int_2 = srate >> 16; + + tmp_1 = clk % 0x10000; + tmp_2 = srate % 0x10000; + + srate = (int_1 * int_2) + + ((int_1 * tmp_2) >> 16) + + ((int_2 * tmp_1) >> 16); + + return srate; +} + +static u32 stv090x_srate_srch_coarse(struct stv090x_state *state) +{ + struct dvb_frontend *fe = &state->frontend; + + int tmg_lock = 0, i; + s32 tmg_cpt = 0, dir = 1, steps, cur_step = 0, freq; + u32 srate_coarse = 0, agc2 = 0, car_step = 1200, reg; + u32 agc2th; + + if (state->internal->dev_ver >= 0x30) + agc2th = 0x2e00; + else + agc2th = 0x1f00; + + reg = STV090x_READ_DEMOD(state, DMDISTATE); + STV090x_SETFIELD_Px(reg, I2C_DEMOD_MODE_FIELD, 0x1f); /* Demod RESET */ + if (STV090x_WRITE_DEMOD(state, DMDISTATE, reg) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGCFG, 0x12) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGTHRISE, 0xf0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGTHFALL, 0xe0) < 0) + goto err; + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, SCAN_ENABLE_FIELD, 1); + STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, SFRUP1, 0x83) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRUP0, 0xc0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRLOW1, 0x82) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRLOW0, 0xa0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDTOM, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x50) < 0) + goto err; + + if (state->internal->dev_ver >= 0x30) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x99) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRSTEP, 0x98) < 0) + goto err; + + } else if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x6a) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRSTEP, 0x95) < 0) + goto err; + } + + if (state->srate <= 2000000) + car_step = 1000; + else if (state->srate <= 5000000) + car_step = 2000; + else if (state->srate <= 12000000) + car_step = 3000; + else + car_step = 5000; + + steps = -1 + ((state->search_range / 1000) / car_step); + steps /= 2; + steps = (2 * steps) + 1; + if (steps < 0) + steps = 1; + else if (steps > 10) { + steps = 11; + car_step = (state->search_range / 1000) / 10; + } + cur_step = 0; + dir = 1; + freq = state->frequency; + + while ((!tmg_lock) && (cur_step < steps)) { + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x5f) < 0) /* Demod RESET */ + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT1, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRINIT1, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRINIT0, 0x00) < 0) + goto err; + /* trigger acquisition */ + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x40) < 0) + goto err; + msleep(50); + for (i = 0; i < 10; i++) { + reg = STV090x_READ_DEMOD(state, DSTATUS); + if (STV090x_GETFIELD_Px(reg, TMGLOCK_QUALITY_FIELD) >= 2) + tmg_cpt++; + agc2 += (STV090x_READ_DEMOD(state, AGC2I1) << 8) | + STV090x_READ_DEMOD(state, AGC2I0); + } + agc2 /= 10; + srate_coarse = stv090x_get_srate(state, state->internal->mclk); + cur_step++; + dir *= -1; + if ((tmg_cpt >= 5) && (agc2 < agc2th) && + (srate_coarse < 50000000) && (srate_coarse > 850000)) + tmg_lock = 1; + else if (cur_step < steps) { + if (dir > 0) + freq += cur_step * car_step; + else + freq -= cur_step * car_step; + + /* Setup tuner */ + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (state->config->tuner_set_frequency) { + if (state->config->tuner_set_frequency(fe, freq) < 0) + goto err_gateoff; + } + + if (state->config->tuner_set_bandwidth) { + if (state->config->tuner_set_bandwidth(fe, state->tuner_bw) < 0) + goto err_gateoff; + } + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + msleep(50); + + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (state->config->tuner_get_status) { + if (state->config->tuner_get_status(fe, ®) < 0) + goto err_gateoff; + } + + if (reg) + dprintk(FE_DEBUG, 1, "Tuner phase locked"); + else + dprintk(FE_DEBUG, 1, "Tuner unlocked"); + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + } + } + if (!tmg_lock) + srate_coarse = 0; + else + srate_coarse = stv090x_get_srate(state, state->internal->mclk); + + return srate_coarse; + +err_gateoff: + stv090x_i2c_gate_ctrl(state, 0); +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static u32 stv090x_srate_srch_fine(struct stv090x_state *state) +{ + u32 srate_coarse, freq_coarse, sym, reg; + + srate_coarse = stv090x_get_srate(state, state->internal->mclk); + freq_coarse = STV090x_READ_DEMOD(state, CFR2) << 8; + freq_coarse |= STV090x_READ_DEMOD(state, CFR1); + sym = 13 * (srate_coarse / 10); /* SFRUP = SFR + 30% */ + + if (sym < state->srate) + srate_coarse = 0; + else { + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) /* Demod RESET */ + goto err; + if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc1) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGTHRISE, 0x20) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGTHFALL, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGCFG, 0xd2) < 0) + goto err; + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0x00); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) + goto err; + + if (state->internal->dev_ver >= 0x30) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x79) < 0) + goto err; + } else if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x49) < 0) + goto err; + } + + if (srate_coarse > 3000000) { + sym = 13 * (srate_coarse / 10); /* SFRUP = SFR + 30% */ + sym = (sym / 1000) * 65536; + sym /= (state->internal->mclk / 1000); + if (STV090x_WRITE_DEMOD(state, SFRUP1, (sym >> 8) & 0x7f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRUP0, sym & 0xff) < 0) + goto err; + sym = 10 * (srate_coarse / 13); /* SFRLOW = SFR - 30% */ + sym = (sym / 1000) * 65536; + sym /= (state->internal->mclk / 1000); + if (STV090x_WRITE_DEMOD(state, SFRLOW1, (sym >> 8) & 0x7f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRLOW0, sym & 0xff) < 0) + goto err; + sym = (srate_coarse / 1000) * 65536; + sym /= (state->internal->mclk / 1000); + if (STV090x_WRITE_DEMOD(state, SFRINIT1, (sym >> 8) & 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRINIT0, sym & 0xff) < 0) + goto err; + } else { + sym = 13 * (srate_coarse / 10); /* SFRUP = SFR + 30% */ + sym = (sym / 100) * 65536; + sym /= (state->internal->mclk / 100); + if (STV090x_WRITE_DEMOD(state, SFRUP1, (sym >> 8) & 0x7f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRUP0, sym & 0xff) < 0) + goto err; + sym = 10 * (srate_coarse / 14); /* SFRLOW = SFR - 30% */ + sym = (sym / 100) * 65536; + sym /= (state->internal->mclk / 100); + if (STV090x_WRITE_DEMOD(state, SFRLOW1, (sym >> 8) & 0x7f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRLOW0, sym & 0xff) < 0) + goto err; + sym = (srate_coarse / 100) * 65536; + sym /= (state->internal->mclk / 100); + if (STV090x_WRITE_DEMOD(state, SFRINIT1, (sym >> 8) & 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, SFRINIT0, sym & 0xff) < 0) + goto err; + } + if (STV090x_WRITE_DEMOD(state, DMDTOM, 0x20) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT1, (freq_coarse >> 8) & 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, freq_coarse & 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x15) < 0) /* trigger acquisition */ + goto err; + } + + return srate_coarse; + +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_get_dmdlock(struct stv090x_state *state, s32 timeout) +{ + s32 timer = 0, lock = 0; + u32 reg; + u8 stat; + + while ((timer < timeout) && (!lock)) { + reg = STV090x_READ_DEMOD(state, DMDSTATE); + stat = STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD); + + switch (stat) { + case 0: /* searching */ + case 1: /* first PLH detected */ + default: + dprintk(FE_DEBUG, 1, "Demodulator searching .."); + lock = 0; + break; + case 2: /* DVB-S2 mode */ + case 3: /* DVB-S1/legacy mode */ + reg = STV090x_READ_DEMOD(state, DSTATUS); + lock = STV090x_GETFIELD_Px(reg, LOCK_DEFINITIF_FIELD); + break; + } + + if (!lock) + msleep(10); + else + dprintk(FE_DEBUG, 1, "Demodulator acquired LOCK"); + + timer += 10; + } + return lock; +} + +static int stv090x_blind_search(struct stv090x_state *state) +{ + u32 agc2, reg, srate_coarse; + s32 cpt_fail, agc2_ovflw, i; + u8 k_ref, k_max, k_min; + int coarse_fail = 0; + int lock; + + k_max = 110; + k_min = 10; + + agc2 = stv090x_get_agc2_min_level(state); + + if (agc2 > STV090x_SEARCH_AGC2_TH(state->internal->dev_ver)) { + lock = 0; + } else { + + if (state->internal->dev_ver <= 0x20) { + if (STV090x_WRITE_DEMOD(state, CARCFG, 0xc4) < 0) + goto err; + } else { + /* > Cut 3 */ + if (STV090x_WRITE_DEMOD(state, CARCFG, 0x06) < 0) + goto err; + } + + if (STV090x_WRITE_DEMOD(state, RTCS2, 0x44) < 0) + goto err; + + if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, EQUALCFG, 0x41) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, FFECFG, 0x41) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VITSCALE, 0x82) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VAVSRVIT, 0x00) < 0) /* set viterbi hysteresis */ + goto err; + } + + k_ref = k_max; + do { + if (STV090x_WRITE_DEMOD(state, KREFTMG, k_ref) < 0) + goto err; + if (stv090x_srate_srch_coarse(state) != 0) { + srate_coarse = stv090x_srate_srch_fine(state); + if (srate_coarse != 0) { + stv090x_get_lock_tmg(state); + lock = stv090x_get_dmdlock(state, + state->DemodTimeout); + } else { + lock = 0; + } + } else { + cpt_fail = 0; + agc2_ovflw = 0; + for (i = 0; i < 10; i++) { + agc2 += (STV090x_READ_DEMOD(state, AGC2I1) << 8) | + STV090x_READ_DEMOD(state, AGC2I0); + if (agc2 >= 0xff00) + agc2_ovflw++; + reg = STV090x_READ_DEMOD(state, DSTATUS2); + if ((STV090x_GETFIELD_Px(reg, CFR_OVERFLOW_FIELD) == 0x01) && + (STV090x_GETFIELD_Px(reg, DEMOD_DELOCK_FIELD) == 0x01)) + + cpt_fail++; + } + if ((cpt_fail > 7) || (agc2_ovflw > 7)) + coarse_fail = 1; + + lock = 0; + } + k_ref -= 20; + } while ((k_ref >= k_min) && (!lock) && (!coarse_fail)); + } + + return lock; + +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_chk_tmg(struct stv090x_state *state) +{ + u32 reg; + s32 tmg_cpt = 0, i; + u8 freq, tmg_thh, tmg_thl; + int tmg_lock = 0; + + freq = STV090x_READ_DEMOD(state, CARFREQ); + tmg_thh = STV090x_READ_DEMOD(state, TMGTHRISE); + tmg_thl = STV090x_READ_DEMOD(state, TMGTHFALL); + if (STV090x_WRITE_DEMOD(state, TMGTHRISE, 0x20) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGTHFALL, 0x00) < 0) + goto err; + + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0x00); /* stop carrier offset search */ + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, RTC, 0x80) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, RTCS2, 0x40) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x00) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, CFRINIT1, 0x00) < 0) /* set car ofset to 0 */ + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x65) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) /* trigger acquisition */ + goto err; + msleep(10); + + for (i = 0; i < 10; i++) { + reg = STV090x_READ_DEMOD(state, DSTATUS); + if (STV090x_GETFIELD_Px(reg, TMGLOCK_QUALITY_FIELD) >= 2) + tmg_cpt++; + msleep(1); + } + if (tmg_cpt >= 3) + tmg_lock = 1; + + if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, RTC, 0x88) < 0) /* DVB-S1 timing */ + goto err; + if (STV090x_WRITE_DEMOD(state, RTCS2, 0x68) < 0) /* DVB-S2 timing */ + goto err; + + if (STV090x_WRITE_DEMOD(state, CARFREQ, freq) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGTHRISE, tmg_thh) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGTHFALL, tmg_thl) < 0) + goto err; + + return tmg_lock; + +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_get_coldlock(struct stv090x_state *state, s32 timeout_dmd) +{ + struct dvb_frontend *fe = &state->frontend; + + u32 reg; + s32 car_step, steps, cur_step, dir, freq, timeout_lock; + int lock = 0; + + if (state->srate >= 10000000) + timeout_lock = timeout_dmd / 3; + else + timeout_lock = timeout_dmd / 2; + + lock = stv090x_get_dmdlock(state, timeout_lock); /* cold start wait */ + if (!lock) { + if (state->srate >= 10000000) { + if (stv090x_chk_tmg(state)) { + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x15) < 0) + goto err; + lock = stv090x_get_dmdlock(state, timeout_dmd); + } else { + lock = 0; + } + } else { + if (state->srate <= 4000000) + car_step = 1000; + else if (state->srate <= 7000000) + car_step = 2000; + else if (state->srate <= 10000000) + car_step = 3000; + else + car_step = 5000; + + steps = (state->search_range / 1000) / car_step; + steps /= 2; + steps = 2 * (steps + 1); + if (steps < 0) + steps = 2; + else if (steps > 12) + steps = 12; + + cur_step = 1; + dir = 1; + + if (!lock) { + freq = state->frequency; + state->tuner_bw = stv090x_car_width(state->srate, state->rolloff) + state->srate; + while ((cur_step <= steps) && (!lock)) { + if (dir > 0) + freq += cur_step * car_step; + else + freq -= cur_step * car_step; + + /* Setup tuner */ + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (state->config->tuner_set_frequency) { + if (state->config->tuner_set_frequency(fe, freq) < 0) + goto err_gateoff; + } + + if (state->config->tuner_set_bandwidth) { + if (state->config->tuner_set_bandwidth(fe, state->tuner_bw) < 0) + goto err_gateoff; + } + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + msleep(50); + + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (state->config->tuner_get_status) { + if (state->config->tuner_get_status(fe, ®) < 0) + goto err_gateoff; + } + + if (reg) + dprintk(FE_DEBUG, 1, "Tuner phase locked"); + else + dprintk(FE_DEBUG, 1, "Tuner unlocked"); + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1c); + if (STV090x_WRITE_DEMOD(state, CFRINIT1, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, 0x00) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x15) < 0) + goto err; + lock = stv090x_get_dmdlock(state, (timeout_dmd / 3)); + + dir *= -1; + cur_step++; + } + } + } + } + + return lock; + +err_gateoff: + stv090x_i2c_gate_ctrl(state, 0); +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_get_loop_params(struct stv090x_state *state, s32 *freq_inc, s32 *timeout_sw, s32 *steps) +{ + s32 timeout, inc, steps_max, srate, car_max; + + srate = state->srate; + car_max = state->search_range / 1000; + car_max += car_max / 10; + car_max = 65536 * (car_max / 2); + car_max /= (state->internal->mclk / 1000); + + if (car_max > 0x4000) + car_max = 0x4000 ; /* maxcarrier should be<= +-1/4 Mclk */ + + inc = srate; + inc /= state->internal->mclk / 1000; + inc *= 256; + inc *= 256; + inc /= 1000; + + switch (state->search_mode) { + case STV090x_SEARCH_DVBS1: + case STV090x_SEARCH_DSS: + inc *= 3; /* freq step = 3% of srate */ + timeout = 20; + break; + + case STV090x_SEARCH_DVBS2: + inc *= 4; + timeout = 25; + break; + + case STV090x_SEARCH_AUTO: + default: + inc *= 3; + timeout = 25; + break; + } + inc /= 100; + if ((inc > car_max) || (inc < 0)) + inc = car_max / 2; /* increment <= 1/8 Mclk */ + + timeout *= 27500; /* 27.5 Msps reference */ + if (srate > 0) + timeout /= (srate / 1000); + + if ((timeout > 100) || (timeout < 0)) + timeout = 100; + + steps_max = (car_max / inc) + 1; /* min steps = 3 */ + if ((steps_max > 100) || (steps_max < 0)) { + steps_max = 100; /* max steps <= 100 */ + inc = car_max / steps_max; + } + *freq_inc = inc; + *timeout_sw = timeout; + *steps = steps_max; + + return 0; +} + +static int stv090x_chk_signal(struct stv090x_state *state) +{ + s32 offst_car, agc2, car_max; + int no_signal; + + offst_car = STV090x_READ_DEMOD(state, CFR2) << 8; + offst_car |= STV090x_READ_DEMOD(state, CFR1); + offst_car = comp2(offst_car, 16); + + agc2 = STV090x_READ_DEMOD(state, AGC2I1) << 8; + agc2 |= STV090x_READ_DEMOD(state, AGC2I0); + car_max = state->search_range / 1000; + + car_max += (car_max / 10); /* 10% margin */ + car_max = (65536 * car_max / 2); + car_max /= state->internal->mclk / 1000; + + if (car_max > 0x4000) + car_max = 0x4000; + + if ((agc2 > 0x2000) || (offst_car > 2 * car_max) || (offst_car < -2 * car_max)) { + no_signal = 1; + dprintk(FE_DEBUG, 1, "No Signal"); + } else { + no_signal = 0; + dprintk(FE_DEBUG, 1, "Found Signal"); + } + + return no_signal; +} + +static int stv090x_search_car_loop(struct stv090x_state *state, s32 inc, s32 timeout, int zigzag, s32 steps_max) +{ + int no_signal, lock = 0; + s32 cpt_step = 0, offst_freq, car_max; + u32 reg; + + car_max = state->search_range / 1000; + car_max += (car_max / 10); + car_max = (65536 * car_max / 2); + car_max /= (state->internal->mclk / 1000); + if (car_max > 0x4000) + car_max = 0x4000; + + if (zigzag) + offst_freq = 0; + else + offst_freq = -car_max + inc; + + do { + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1c) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT1, ((offst_freq / 256) & 0xff)) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, offst_freq & 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) + goto err; + + reg = STV090x_READ_DEMOD(state, PDELCTRL1); + STV090x_SETFIELD_Px(reg, ALGOSWRST_FIELD, 0x1); /* stop DVB-S2 packet delin */ + if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) + goto err; + + if (zigzag) { + if (offst_freq >= 0) + offst_freq = -offst_freq - 2 * inc; + else + offst_freq = -offst_freq; + } else { + offst_freq += 2 * inc; + } + + cpt_step++; + + lock = stv090x_get_dmdlock(state, timeout); + no_signal = stv090x_chk_signal(state); + + } while ((!lock) && + (!no_signal) && + ((offst_freq - inc) < car_max) && + ((offst_freq + inc) > -car_max) && + (cpt_step < steps_max)); + + reg = STV090x_READ_DEMOD(state, PDELCTRL1); + STV090x_SETFIELD_Px(reg, ALGOSWRST_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) + goto err; + + return lock; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_sw_algo(struct stv090x_state *state) +{ + int no_signal, zigzag, lock = 0; + u32 reg; + + s32 dvbs2_fly_wheel; + s32 inc, timeout_step, trials, steps_max; + + /* get params */ + stv090x_get_loop_params(state, &inc, &timeout_step, &steps_max); + + switch (state->search_mode) { + case STV090x_SEARCH_DVBS1: + case STV090x_SEARCH_DSS: + /* accelerate the frequency detector */ + if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x3B) < 0) + goto err; + } + + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, 0x49) < 0) + goto err; + zigzag = 0; + break; + + case STV090x_SEARCH_DVBS2: + if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x79) < 0) + goto err; + } + + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, 0x89) < 0) + goto err; + zigzag = 1; + break; + + case STV090x_SEARCH_AUTO: + default: + /* accelerate the frequency detector */ + if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x3b) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x79) < 0) + goto err; + } + + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, 0xc9) < 0) + goto err; + zigzag = 0; + break; + } + + trials = 0; + do { + lock = stv090x_search_car_loop(state, inc, timeout_step, zigzag, steps_max); + no_signal = stv090x_chk_signal(state); + trials++; + + /*run the SW search 2 times maximum*/ + if (lock || no_signal || (trials == 2)) { + /*Check if the demod is not losing lock in DVBS2*/ + if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x49) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x9e) < 0) + goto err; + } + + reg = STV090x_READ_DEMOD(state, DMDSTATE); + if ((lock) && (STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD) == STV090x_DVBS2)) { + /*Check if the demod is not losing lock in DVBS2*/ + msleep(timeout_step); + reg = STV090x_READ_DEMOD(state, DMDFLYW); + dvbs2_fly_wheel = STV090x_GETFIELD_Px(reg, FLYWHEEL_CPT_FIELD); + if (dvbs2_fly_wheel < 0xd) { /*if correct frames is decrementing */ + msleep(timeout_step); + reg = STV090x_READ_DEMOD(state, DMDFLYW); + dvbs2_fly_wheel = STV090x_GETFIELD_Px(reg, FLYWHEEL_CPT_FIELD); + } + if (dvbs2_fly_wheel < 0xd) { + /*FALSE lock, The demod is losing lock */ + lock = 0; + if (trials < 2) { + if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x79) < 0) + goto err; + } + + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, 0x89) < 0) + goto err; + } + } + } + } + } while ((!lock) && (trials < 2) && (!no_signal)); + + return lock; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static enum stv090x_delsys stv090x_get_std(struct stv090x_state *state) +{ + u32 reg; + enum stv090x_delsys delsys; + + reg = STV090x_READ_DEMOD(state, DMDSTATE); + if (STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD) == 2) + delsys = STV090x_DVBS2; + else if (STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD) == 3) { + reg = STV090x_READ_DEMOD(state, FECM); + if (STV090x_GETFIELD_Px(reg, DSS_DVB_FIELD) == 1) + delsys = STV090x_DSS; + else + delsys = STV090x_DVBS1; + } else { + delsys = STV090x_ERROR; + } + + return delsys; +} + +/* in Hz */ +static s32 stv090x_get_car_freq(struct stv090x_state *state, u32 mclk) +{ + s32 derot, int_1, int_2, tmp_1, tmp_2; + + derot = STV090x_READ_DEMOD(state, CFR2) << 16; + derot |= STV090x_READ_DEMOD(state, CFR1) << 8; + derot |= STV090x_READ_DEMOD(state, CFR0); + + derot = comp2(derot, 24); + int_1 = mclk >> 12; + int_2 = derot >> 12; + + /* carrier_frequency = MasterClock * Reg / 2^24 */ + tmp_1 = mclk % 0x1000; + tmp_2 = derot % 0x1000; + + derot = (int_1 * int_2) + + ((int_1 * tmp_2) >> 12) + + ((int_2 * tmp_1) >> 12); + + return derot; +} + +static int stv090x_get_viterbi(struct stv090x_state *state) +{ + u32 reg, rate; + + reg = STV090x_READ_DEMOD(state, VITCURPUN); + rate = STV090x_GETFIELD_Px(reg, VIT_CURPUN_FIELD); + + switch (rate) { + case 13: + state->fec = STV090x_PR12; + break; + + case 18: + state->fec = STV090x_PR23; + break; + + case 21: + state->fec = STV090x_PR34; + break; + + case 24: + state->fec = STV090x_PR56; + break; + + case 25: + state->fec = STV090x_PR67; + break; + + case 26: + state->fec = STV090x_PR78; + break; + + default: + state->fec = STV090x_PRERR; + break; + } + + return 0; +} + +static enum stv090x_signal_state stv090x_get_sig_params(struct stv090x_state *state) +{ + struct dvb_frontend *fe = &state->frontend; + + u8 tmg; + u32 reg; + s32 i = 0, offst_freq; + + msleep(5); + + if (state->algo == STV090x_BLIND_SEARCH) { + tmg = STV090x_READ_DEMOD(state, TMGREG2); + STV090x_WRITE_DEMOD(state, SFRSTEP, 0x5c); + while ((i <= 50) && (tmg != 0) && (tmg != 0xff)) { + tmg = STV090x_READ_DEMOD(state, TMGREG2); + msleep(5); + i += 5; + } + } + state->delsys = stv090x_get_std(state); + + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (state->config->tuner_get_frequency) { + if (state->config->tuner_get_frequency(fe, &state->frequency) < 0) + goto err_gateoff; + } + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + offst_freq = stv090x_get_car_freq(state, state->internal->mclk) / 1000; + state->frequency += offst_freq; + + if (stv090x_get_viterbi(state) < 0) + goto err; + + reg = STV090x_READ_DEMOD(state, DMDMODCOD); + state->modcod = STV090x_GETFIELD_Px(reg, DEMOD_MODCOD_FIELD); + state->pilots = STV090x_GETFIELD_Px(reg, DEMOD_TYPE_FIELD) & 0x01; + state->frame_len = STV090x_GETFIELD_Px(reg, DEMOD_TYPE_FIELD) >> 1; + reg = STV090x_READ_DEMOD(state, TMGOBS); + state->rolloff = STV090x_GETFIELD_Px(reg, ROLLOFF_STATUS_FIELD); + reg = STV090x_READ_DEMOD(state, FECM); + state->inversion = STV090x_GETFIELD_Px(reg, IQINV_FIELD); + + if ((state->algo == STV090x_BLIND_SEARCH) || (state->srate < 10000000)) { + + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (state->config->tuner_get_frequency) { + if (state->config->tuner_get_frequency(fe, &state->frequency) < 0) + goto err_gateoff; + } + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + if (abs(offst_freq) <= ((state->search_range / 2000) + 500)) + return STV090x_RANGEOK; + else if (abs(offst_freq) <= (stv090x_car_width(state->srate, state->rolloff) / 2000)) + return STV090x_RANGEOK; + else + return STV090x_OUTOFRANGE; /* Out of Range */ + } else { + if (abs(offst_freq) <= ((state->search_range / 2000) + 500)) + return STV090x_RANGEOK; + else + return STV090x_OUTOFRANGE; + } + + return STV090x_OUTOFRANGE; + +err_gateoff: + stv090x_i2c_gate_ctrl(state, 0); +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static u32 stv090x_get_tmgoffst(struct stv090x_state *state, u32 srate) +{ + s32 offst_tmg; + + offst_tmg = STV090x_READ_DEMOD(state, TMGREG2) << 16; + offst_tmg |= STV090x_READ_DEMOD(state, TMGREG1) << 8; + offst_tmg |= STV090x_READ_DEMOD(state, TMGREG0); + + offst_tmg = comp2(offst_tmg, 24); /* 2's complement */ + if (!offst_tmg) + offst_tmg = 1; + + offst_tmg = ((s32) srate * 10) / ((s32) 0x1000000 / offst_tmg); + offst_tmg /= 320; + + return offst_tmg; +} + +static u8 stv090x_optimize_carloop(struct stv090x_state *state, enum stv090x_modcod modcod, s32 pilots) +{ + u8 aclc = 0x29; + s32 i; + struct stv090x_long_frame_crloop *car_loop, *car_loop_qpsk_low, *car_loop_apsk_low; + + if (state->internal->dev_ver == 0x20) { + car_loop = stv090x_s2_crl_cut20; + car_loop_qpsk_low = stv090x_s2_lowqpsk_crl_cut20; + car_loop_apsk_low = stv090x_s2_apsk_crl_cut20; + } else { + /* >= Cut 3 */ + car_loop = stv090x_s2_crl_cut30; + car_loop_qpsk_low = stv090x_s2_lowqpsk_crl_cut30; + car_loop_apsk_low = stv090x_s2_apsk_crl_cut30; + } + + if (modcod < STV090x_QPSK_12) { + i = 0; + while ((i < 3) && (modcod != car_loop_qpsk_low[i].modcod)) + i++; + + if (i >= 3) + i = 2; + + } else { + i = 0; + while ((i < 14) && (modcod != car_loop[i].modcod)) + i++; + + if (i >= 14) { + i = 0; + while ((i < 11) && (modcod != car_loop_apsk_low[i].modcod)) + i++; + + if (i >= 11) + i = 10; + } + } + + if (modcod <= STV090x_QPSK_25) { + if (pilots) { + if (state->srate <= 3000000) + aclc = car_loop_qpsk_low[i].crl_pilots_on_2; + else if (state->srate <= 7000000) + aclc = car_loop_qpsk_low[i].crl_pilots_on_5; + else if (state->srate <= 15000000) + aclc = car_loop_qpsk_low[i].crl_pilots_on_10; + else if (state->srate <= 25000000) + aclc = car_loop_qpsk_low[i].crl_pilots_on_20; + else + aclc = car_loop_qpsk_low[i].crl_pilots_on_30; + } else { + if (state->srate <= 3000000) + aclc = car_loop_qpsk_low[i].crl_pilots_off_2; + else if (state->srate <= 7000000) + aclc = car_loop_qpsk_low[i].crl_pilots_off_5; + else if (state->srate <= 15000000) + aclc = car_loop_qpsk_low[i].crl_pilots_off_10; + else if (state->srate <= 25000000) + aclc = car_loop_qpsk_low[i].crl_pilots_off_20; + else + aclc = car_loop_qpsk_low[i].crl_pilots_off_30; + } + + } else if (modcod <= STV090x_8PSK_910) { + if (pilots) { + if (state->srate <= 3000000) + aclc = car_loop[i].crl_pilots_on_2; + else if (state->srate <= 7000000) + aclc = car_loop[i].crl_pilots_on_5; + else if (state->srate <= 15000000) + aclc = car_loop[i].crl_pilots_on_10; + else if (state->srate <= 25000000) + aclc = car_loop[i].crl_pilots_on_20; + else + aclc = car_loop[i].crl_pilots_on_30; + } else { + if (state->srate <= 3000000) + aclc = car_loop[i].crl_pilots_off_2; + else if (state->srate <= 7000000) + aclc = car_loop[i].crl_pilots_off_5; + else if (state->srate <= 15000000) + aclc = car_loop[i].crl_pilots_off_10; + else if (state->srate <= 25000000) + aclc = car_loop[i].crl_pilots_off_20; + else + aclc = car_loop[i].crl_pilots_off_30; + } + } else { /* 16APSK and 32APSK */ + if (state->srate <= 3000000) + aclc = car_loop_apsk_low[i].crl_pilots_on_2; + else if (state->srate <= 7000000) + aclc = car_loop_apsk_low[i].crl_pilots_on_5; + else if (state->srate <= 15000000) + aclc = car_loop_apsk_low[i].crl_pilots_on_10; + else if (state->srate <= 25000000) + aclc = car_loop_apsk_low[i].crl_pilots_on_20; + else + aclc = car_loop_apsk_low[i].crl_pilots_on_30; + } + + return aclc; +} + +static u8 stv090x_optimize_carloop_short(struct stv090x_state *state) +{ + struct stv090x_short_frame_crloop *short_crl = NULL; + s32 index = 0; + u8 aclc = 0x0b; + + switch (state->modulation) { + case STV090x_QPSK: + default: + index = 0; + break; + case STV090x_8PSK: + index = 1; + break; + case STV090x_16APSK: + index = 2; + break; + case STV090x_32APSK: + index = 3; + break; + } + + if (state->internal->dev_ver >= 0x30) { + /* Cut 3.0 and up */ + short_crl = stv090x_s2_short_crl_cut30; + } else { + /* Cut 2.0 and up: we don't support cuts older than 2.0 */ + short_crl = stv090x_s2_short_crl_cut20; + } + + if (state->srate <= 3000000) + aclc = short_crl[index].crl_2; + else if (state->srate <= 7000000) + aclc = short_crl[index].crl_5; + else if (state->srate <= 15000000) + aclc = short_crl[index].crl_10; + else if (state->srate <= 25000000) + aclc = short_crl[index].crl_20; + else + aclc = short_crl[index].crl_30; + + return aclc; +} + +static int stv090x_optimize_track(struct stv090x_state *state) +{ + struct dvb_frontend *fe = &state->frontend; + + enum stv090x_modcod modcod; + + s32 srate, pilots, aclc, f_1, f_0, i = 0, blind_tune = 0; + u32 reg; + + srate = stv090x_get_srate(state, state->internal->mclk); + srate += stv090x_get_tmgoffst(state, srate); + + switch (state->delsys) { + case STV090x_DVBS1: + case STV090x_DSS: + if (state->search_mode == STV090x_SEARCH_AUTO) { + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); + STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + } + reg = STV090x_READ_DEMOD(state, DEMOD); + STV090x_SETFIELD_Px(reg, ROLLOFF_CONTROL_FIELD, state->rolloff); + STV090x_SETFIELD_Px(reg, MANUAL_SXROLLOFF_FIELD, 0x01); + if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) + goto err; + + if (state->internal->dev_ver >= 0x30) { + if (stv090x_get_viterbi(state) < 0) + goto err; + + if (state->fec == STV090x_PR12) { + if (STV090x_WRITE_DEMOD(state, GAUSSR0, 0x98) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CCIR0, 0x18) < 0) + goto err; + } else { + if (STV090x_WRITE_DEMOD(state, GAUSSR0, 0x18) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CCIR0, 0x18) < 0) + goto err; + } + } + + if (STV090x_WRITE_DEMOD(state, ERRCTRL1, 0x75) < 0) + goto err; + break; + + case STV090x_DVBS2: + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 0); + STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + if (state->internal->dev_ver >= 0x30) { + if (STV090x_WRITE_DEMOD(state, ACLC, 0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, BCLC, 0) < 0) + goto err; + } + if (state->frame_len == STV090x_LONG_FRAME) { + reg = STV090x_READ_DEMOD(state, DMDMODCOD); + modcod = STV090x_GETFIELD_Px(reg, DEMOD_MODCOD_FIELD); + pilots = STV090x_GETFIELD_Px(reg, DEMOD_TYPE_FIELD) & 0x01; + aclc = stv090x_optimize_carloop(state, modcod, pilots); + if (modcod <= STV090x_QPSK_910) { + STV090x_WRITE_DEMOD(state, ACLC2S2Q, aclc); + } else if (modcod <= STV090x_8PSK_910) { + if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, ACLC2S28, aclc) < 0) + goto err; + } + if ((state->demod_mode == STV090x_SINGLE) && (modcod > STV090x_8PSK_910)) { + if (modcod <= STV090x_16APSK_910) { + if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, ACLC2S216A, aclc) < 0) + goto err; + } else { + if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, ACLC2S232A, aclc) < 0) + goto err; + } + } + } else { + /*Carrier loop setting for short frame*/ + aclc = stv090x_optimize_carloop_short(state); + if (state->modulation == STV090x_QPSK) { + if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, aclc) < 0) + goto err; + } else if (state->modulation == STV090x_8PSK) { + if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, ACLC2S28, aclc) < 0) + goto err; + } else if (state->modulation == STV090x_16APSK) { + if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, ACLC2S216A, aclc) < 0) + goto err; + } else if (state->modulation == STV090x_32APSK) { + if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, ACLC2S232A, aclc) < 0) + goto err; + } + } + + STV090x_WRITE_DEMOD(state, ERRCTRL1, 0x67); /* PER */ + break; + + case STV090x_ERROR: + default: + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); + STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + break; + } + + f_1 = STV090x_READ_DEMOD(state, CFR2); + f_0 = STV090x_READ_DEMOD(state, CFR1); + reg = STV090x_READ_DEMOD(state, TMGOBS); + + if (state->algo == STV090x_BLIND_SEARCH) { + STV090x_WRITE_DEMOD(state, SFRSTEP, 0x00); + reg = STV090x_READ_DEMOD(state, DMDCFGMD); + STV090x_SETFIELD_Px(reg, SCAN_ENABLE_FIELD, 0x00); + STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0x00); + if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc1) < 0) + goto err; + + if (stv090x_set_srate(state, srate) < 0) + goto err; + blind_tune = 1; + + if (stv090x_dvbs_track_crl(state) < 0) + goto err; + } + + if (state->internal->dev_ver >= 0x20) { + if ((state->search_mode == STV090x_SEARCH_DVBS1) || + (state->search_mode == STV090x_SEARCH_DSS) || + (state->search_mode == STV090x_SEARCH_AUTO)) { + + if (STV090x_WRITE_DEMOD(state, VAVSRVIT, 0x0a) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, VITSCALE, 0x00) < 0) + goto err; + } + } + + if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) + goto err; + + /* AUTO tracking MODE */ + if (STV090x_WRITE_DEMOD(state, SFRUP1, 0x80) < 0) + goto err; + /* AUTO tracking MODE */ + if (STV090x_WRITE_DEMOD(state, SFRLOW1, 0x80) < 0) + goto err; + + if ((state->internal->dev_ver >= 0x20) || (blind_tune == 1) || + (state->srate < 10000000)) { + /* update initial carrier freq with the found freq offset */ + if (STV090x_WRITE_DEMOD(state, CFRINIT1, f_1) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, f_0) < 0) + goto err; + state->tuner_bw = stv090x_car_width(srate, state->rolloff) + 10000000; + + if ((state->internal->dev_ver >= 0x20) || (blind_tune == 1)) { + + if (state->algo != STV090x_WARM_SEARCH) { + + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (state->config->tuner_set_bandwidth) { + if (state->config->tuner_set_bandwidth(fe, state->tuner_bw) < 0) + goto err_gateoff; + } + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + } + } + if ((state->algo == STV090x_BLIND_SEARCH) || (state->srate < 10000000)) + msleep(50); /* blind search: wait 50ms for SR stabilization */ + else + msleep(5); + + stv090x_get_lock_tmg(state); + + if (!(stv090x_get_dmdlock(state, (state->DemodTimeout / 2)))) { + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT1, f_1) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, f_0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) + goto err; + + i = 0; + + while ((!(stv090x_get_dmdlock(state, (state->DemodTimeout / 2)))) && (i <= 2)) { + + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT1, f_1) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, CFRINIT0, f_0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) + goto err; + i++; + } + } + + } + + if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x49) < 0) + goto err; + } + + if ((state->delsys == STV090x_DVBS1) || (state->delsys == STV090x_DSS)) + stv090x_set_vit_thtracq(state); + + return 0; + +err_gateoff: + stv090x_i2c_gate_ctrl(state, 0); +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_get_feclock(struct stv090x_state *state, s32 timeout) +{ + s32 timer = 0, lock = 0, stat; + u32 reg; + + while ((timer < timeout) && (!lock)) { + reg = STV090x_READ_DEMOD(state, DMDSTATE); + stat = STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD); + + switch (stat) { + case 0: /* searching */ + case 1: /* first PLH detected */ + default: + lock = 0; + break; + + case 2: /* DVB-S2 mode */ + reg = STV090x_READ_DEMOD(state, PDELSTATUS1); + lock = STV090x_GETFIELD_Px(reg, PKTDELIN_LOCK_FIELD); + break; + + case 3: /* DVB-S1/legacy mode */ + reg = STV090x_READ_DEMOD(state, VSTATUSVIT); + lock = STV090x_GETFIELD_Px(reg, LOCKEDVIT_FIELD); + break; + } + if (!lock) { + msleep(10); + timer += 10; + } + } + return lock; +} + +static int stv090x_get_lock(struct stv090x_state *state, s32 timeout_dmd, s32 timeout_fec) +{ + u32 reg; + s32 timer = 0; + int lock; + + lock = stv090x_get_dmdlock(state, timeout_dmd); + if (lock) + lock = stv090x_get_feclock(state, timeout_fec); + + if (lock) { + lock = 0; + + while ((timer < timeout_fec) && (!lock)) { + reg = STV090x_READ_DEMOD(state, TSSTATUS); + lock = STV090x_GETFIELD_Px(reg, TSFIFO_LINEOK_FIELD); + msleep(1); + timer++; + } + } + + return lock; +} + +static int stv090x_set_s2rolloff(struct stv090x_state *state) +{ + u32 reg; + + if (state->internal->dev_ver <= 0x20) { + /* rolloff to auto mode if DVBS2 */ + reg = STV090x_READ_DEMOD(state, DEMOD); + STV090x_SETFIELD_Px(reg, MANUAL_SXROLLOFF_FIELD, 0x00); + if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) + goto err; + } else { + /* DVB-S2 rolloff to auto mode if DVBS2 */ + reg = STV090x_READ_DEMOD(state, DEMOD); + STV090x_SETFIELD_Px(reg, MANUAL_S2ROLLOFF_FIELD, 0x00); + if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) + goto err; + } + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + + +static enum stv090x_signal_state stv090x_algo(struct stv090x_state *state) +{ + struct dvb_frontend *fe = &state->frontend; + enum stv090x_signal_state signal_state = STV090x_NOCARRIER; + u32 reg; + s32 agc1_power, power_iq = 0, i; + int lock = 0, low_sr = 0; + + reg = STV090x_READ_DEMOD(state, TSCFGH); + STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 1); /* Stop path 1 stream merger */ + if (STV090x_WRITE_DEMOD(state, TSCFGH, reg) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x5c) < 0) /* Demod stop */ + goto err; + + if (state->internal->dev_ver >= 0x20) { + if (state->srate > 5000000) { + if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x9e) < 0) + goto err; + } else { + if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x82) < 0) + goto err; + } + } + + stv090x_get_lock_tmg(state); + + if (state->algo == STV090x_BLIND_SEARCH) { + state->tuner_bw = 2 * 36000000; /* wide bw for unknown srate */ + if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc0) < 0) /* wider srate scan */ + goto err; + if (STV090x_WRITE_DEMOD(state, CORRELMANT, 0x70) < 0) + goto err; + if (stv090x_set_srate(state, 1000000) < 0) /* initial srate = 1Msps */ + goto err; + } else { + /* known srate */ + if (STV090x_WRITE_DEMOD(state, DMDTOM, 0x20) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, TMGCFG, 0xd2) < 0) + goto err; + + if (state->srate < 2000000) { + /* SR < 2MSPS */ + if (STV090x_WRITE_DEMOD(state, CORRELMANT, 0x63) < 0) + goto err; + } else { + /* SR >= 2Msps */ + if (STV090x_WRITE_DEMOD(state, CORRELMANT, 0x70) < 0) + goto err; + } + + if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) + goto err; + + if (state->internal->dev_ver >= 0x20) { + if (STV090x_WRITE_DEMOD(state, KREFTMG, 0x5a) < 0) + goto err; + if (state->algo == STV090x_COLD_SEARCH) + state->tuner_bw = (15 * (stv090x_car_width(state->srate, state->rolloff) + 10000000)) / 10; + else if (state->algo == STV090x_WARM_SEARCH) + state->tuner_bw = stv090x_car_width(state->srate, state->rolloff) + 10000000; + } + + /* if cold start or warm (Symbolrate is known) + * use a Narrow symbol rate scan range + */ + if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc1) < 0) /* narrow srate scan */ + goto err; + + if (stv090x_set_srate(state, state->srate) < 0) + goto err; + + if (stv090x_set_max_srate(state, state->internal->mclk, + state->srate) < 0) + goto err; + if (stv090x_set_min_srate(state, state->internal->mclk, + state->srate) < 0) + goto err; + + if (state->srate >= 10000000) + low_sr = 0; + else + low_sr = 1; + } + + /* Setup tuner */ + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (state->config->tuner_set_bbgain) { + reg = state->config->tuner_bbgain; + if (reg == 0) + reg = 10; /* default: 10dB */ + if (state->config->tuner_set_bbgain(fe, reg) < 0) + goto err_gateoff; + } + + if (state->config->tuner_set_frequency) { + if (state->config->tuner_set_frequency(fe, state->frequency) < 0) + goto err_gateoff; + } + + if (state->config->tuner_set_bandwidth) { + if (state->config->tuner_set_bandwidth(fe, state->tuner_bw) < 0) + goto err_gateoff; + } + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + msleep(50); + + if (state->config->tuner_get_status) { + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + if (state->config->tuner_get_status(fe, ®) < 0) + goto err_gateoff; + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + if (reg) + dprintk(FE_DEBUG, 1, "Tuner phase locked"); + else { + dprintk(FE_DEBUG, 1, "Tuner unlocked"); + return STV090x_NOCARRIER; + } + } + + msleep(10); + agc1_power = MAKEWORD16(STV090x_READ_DEMOD(state, AGCIQIN1), + STV090x_READ_DEMOD(state, AGCIQIN0)); + + if (agc1_power == 0) { + /* If AGC1 integrator value is 0 + * then read POWERI, POWERQ + */ + for (i = 0; i < 5; i++) { + power_iq += (STV090x_READ_DEMOD(state, POWERI) + + STV090x_READ_DEMOD(state, POWERQ)) >> 1; + } + power_iq /= 5; + } + + if ((agc1_power == 0) && (power_iq < STV090x_IQPOWER_THRESHOLD)) { + dprintk(FE_ERROR, 1, "No Signal: POWER_IQ=0x%02x", power_iq); + lock = 0; + signal_state = STV090x_NOAGC1; + } else { + reg = STV090x_READ_DEMOD(state, DEMOD); + STV090x_SETFIELD_Px(reg, SPECINV_CONTROL_FIELD, state->inversion); + + if (state->internal->dev_ver <= 0x20) { + /* rolloff to auto mode if DVBS2 */ + STV090x_SETFIELD_Px(reg, MANUAL_SXROLLOFF_FIELD, 1); + } else { + /* DVB-S2 rolloff to auto mode if DVBS2 */ + STV090x_SETFIELD_Px(reg, MANUAL_S2ROLLOFF_FIELD, 1); + } + if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) + goto err; + + if (stv090x_delivery_search(state) < 0) + goto err; + + if (state->algo != STV090x_BLIND_SEARCH) { + if (stv090x_start_search(state) < 0) + goto err; + } + } + + if (signal_state == STV090x_NOAGC1) + return signal_state; + + if (state->algo == STV090x_BLIND_SEARCH) + lock = stv090x_blind_search(state); + + else if (state->algo == STV090x_COLD_SEARCH) + lock = stv090x_get_coldlock(state, state->DemodTimeout); + + else if (state->algo == STV090x_WARM_SEARCH) + lock = stv090x_get_dmdlock(state, state->DemodTimeout); + + if ((!lock) && (state->algo == STV090x_COLD_SEARCH)) { + if (!low_sr) { + if (stv090x_chk_tmg(state)) + lock = stv090x_sw_algo(state); + } + } + + if (lock) + signal_state = stv090x_get_sig_params(state); + + if ((lock) && (signal_state == STV090x_RANGEOK)) { /* signal within Range */ + stv090x_optimize_track(state); + + if (state->internal->dev_ver >= 0x20) { + /* >= Cut 2.0 :release TS reset after + * demod lock and optimized Tracking + */ + reg = STV090x_READ_DEMOD(state, TSCFGH); + STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0); /* release merger reset */ + if (STV090x_WRITE_DEMOD(state, TSCFGH, reg) < 0) + goto err; + + msleep(3); + + STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 1); /* merger reset */ + if (STV090x_WRITE_DEMOD(state, TSCFGH, reg) < 0) + goto err; + + STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0); /* release merger reset */ + if (STV090x_WRITE_DEMOD(state, TSCFGH, reg) < 0) + goto err; + } + + lock = stv090x_get_lock(state, state->FecTimeout, + state->FecTimeout); + if (lock) { + if (state->delsys == STV090x_DVBS2) { + stv090x_set_s2rolloff(state); + + reg = STV090x_READ_DEMOD(state, PDELCTRL2); + STV090x_SETFIELD_Px(reg, RESET_UPKO_COUNT, 1); + if (STV090x_WRITE_DEMOD(state, PDELCTRL2, reg) < 0) + goto err; + /* Reset DVBS2 packet delinator error counter */ + reg = STV090x_READ_DEMOD(state, PDELCTRL2); + STV090x_SETFIELD_Px(reg, RESET_UPKO_COUNT, 0); + if (STV090x_WRITE_DEMOD(state, PDELCTRL2, reg) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, ERRCTRL1, 0x67) < 0) /* PER */ + goto err; + } else { + if (STV090x_WRITE_DEMOD(state, ERRCTRL1, 0x75) < 0) + goto err; + } + /* Reset the Total packet counter */ + if (STV090x_WRITE_DEMOD(state, FBERCPT4, 0x00) < 0) + goto err; + /* Reset the packet Error counter2 */ + if (STV090x_WRITE_DEMOD(state, ERRCTRL2, 0xc1) < 0) + goto err; + } else { + signal_state = STV090x_NODATA; + stv090x_chk_signal(state); + } + } + return signal_state; + +err_gateoff: + stv090x_i2c_gate_ctrl(state, 0); +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_set_mis(struct stv090x_state *state, int mis) +{ + u32 reg; + + if (mis < 0 || mis > 255) { + dprintk(FE_DEBUG, 1, "Disable MIS filtering"); + reg = STV090x_READ_DEMOD(state, PDELCTRL1); + STV090x_SETFIELD_Px(reg, FILTER_EN_FIELD, 0x00); + if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) + goto err; + } else { + dprintk(FE_DEBUG, 1, "Enable MIS filtering - %d", mis); + reg = STV090x_READ_DEMOD(state, PDELCTRL1); + STV090x_SETFIELD_Px(reg, FILTER_EN_FIELD, 0x01); + if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, ISIENTRY, mis) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, ISIBITENA, 0xff) < 0) + goto err; + } + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static enum dvbfe_search stv090x_search(struct dvb_frontend *fe) +{ + struct stv090x_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *props = &fe->dtv_property_cache; + + if (props->frequency == 0) + return DVBFE_ALGO_SEARCH_INVALID; + + state->delsys = props->delivery_system; + state->frequency = props->frequency; + state->srate = props->symbol_rate; + state->search_mode = STV090x_SEARCH_AUTO; + state->algo = STV090x_COLD_SEARCH; + state->fec = STV090x_PRERR; + if (state->srate > 10000000) { + dprintk(FE_DEBUG, 1, "Search range: 10 MHz"); + state->search_range = 10000000; + } else { + dprintk(FE_DEBUG, 1, "Search range: 5 MHz"); + state->search_range = 5000000; + } + + stv090x_set_mis(state, props->stream_id); + + if (stv090x_algo(state) == STV090x_RANGEOK) { + dprintk(FE_DEBUG, 1, "Search success!"); + return DVBFE_ALGO_SEARCH_SUCCESS; + } else { + dprintk(FE_DEBUG, 1, "Search failed!"); + return DVBFE_ALGO_SEARCH_FAILED; + } + + return DVBFE_ALGO_SEARCH_ERROR; +} + +static int stv090x_read_status(struct dvb_frontend *fe, enum fe_status *status) +{ + struct stv090x_state *state = fe->demodulator_priv; + u32 reg, dstatus; + u8 search_state; + + *status = 0; + + dstatus = STV090x_READ_DEMOD(state, DSTATUS); + if (STV090x_GETFIELD_Px(dstatus, CAR_LOCK_FIELD)) + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER; + + reg = STV090x_READ_DEMOD(state, DMDSTATE); + search_state = STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD); + + switch (search_state) { + case 0: /* searching */ + case 1: /* first PLH detected */ + default: + dprintk(FE_DEBUG, 1, "Status: Unlocked (Searching ..)"); + break; + + case 2: /* DVB-S2 mode */ + dprintk(FE_DEBUG, 1, "Delivery system: DVB-S2"); + if (STV090x_GETFIELD_Px(dstatus, LOCK_DEFINITIF_FIELD)) { + reg = STV090x_READ_DEMOD(state, PDELSTATUS1); + if (STV090x_GETFIELD_Px(reg, PKTDELIN_LOCK_FIELD)) { + *status |= FE_HAS_VITERBI; + reg = STV090x_READ_DEMOD(state, TSSTATUS); + if (STV090x_GETFIELD_Px(reg, TSFIFO_LINEOK_FIELD)) + *status |= FE_HAS_SYNC | FE_HAS_LOCK; + } + } + break; + + case 3: /* DVB-S1/legacy mode */ + dprintk(FE_DEBUG, 1, "Delivery system: DVB-S"); + if (STV090x_GETFIELD_Px(dstatus, LOCK_DEFINITIF_FIELD)) { + reg = STV090x_READ_DEMOD(state, VSTATUSVIT); + if (STV090x_GETFIELD_Px(reg, LOCKEDVIT_FIELD)) { + *status |= FE_HAS_VITERBI; + reg = STV090x_READ_DEMOD(state, TSSTATUS); + if (STV090x_GETFIELD_Px(reg, TSFIFO_LINEOK_FIELD)) + *status |= FE_HAS_SYNC | FE_HAS_LOCK; + } + } + break; + } + + return 0; +} + +static int stv090x_read_per(struct dvb_frontend *fe, u32 *per) +{ + struct stv090x_state *state = fe->demodulator_priv; + + s32 count_4, count_3, count_2, count_1, count_0, count; + u32 reg, h, m, l; + enum fe_status status; + + stv090x_read_status(fe, &status); + if (!(status & FE_HAS_LOCK)) { + *per = 1 << 23; /* Max PER */ + } else { + /* Counter 2 */ + reg = STV090x_READ_DEMOD(state, ERRCNT22); + h = STV090x_GETFIELD_Px(reg, ERR_CNT2_FIELD); + + reg = STV090x_READ_DEMOD(state, ERRCNT21); + m = STV090x_GETFIELD_Px(reg, ERR_CNT21_FIELD); + + reg = STV090x_READ_DEMOD(state, ERRCNT20); + l = STV090x_GETFIELD_Px(reg, ERR_CNT20_FIELD); + + *per = ((h << 16) | (m << 8) | l); + + count_4 = STV090x_READ_DEMOD(state, FBERCPT4); + count_3 = STV090x_READ_DEMOD(state, FBERCPT3); + count_2 = STV090x_READ_DEMOD(state, FBERCPT2); + count_1 = STV090x_READ_DEMOD(state, FBERCPT1); + count_0 = STV090x_READ_DEMOD(state, FBERCPT0); + + if ((!count_4) && (!count_3)) { + count = (count_2 & 0xff) << 16; + count |= (count_1 & 0xff) << 8; + count |= count_0 & 0xff; + } else { + count = 1 << 24; + } + if (count == 0) + *per = 1; + } + if (STV090x_WRITE_DEMOD(state, FBERCPT4, 0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, ERRCTRL2, 0xc1) < 0) + goto err; + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_table_lookup(const struct stv090x_tab *tab, int max, int val) +{ + int res = 0; + int min = 0, med; + + if ((val >= tab[min].read && val < tab[max].read) || + (val >= tab[max].read && val < tab[min].read)) { + while ((max - min) > 1) { + med = (max + min) / 2; + if ((val >= tab[min].read && val < tab[med].read) || + (val >= tab[med].read && val < tab[min].read)) + max = med; + else + min = med; + } + res = ((val - tab[min].read) * + (tab[max].real - tab[min].real) / + (tab[max].read - tab[min].read)) + + tab[min].real; + } else { + if (tab[min].read < tab[max].read) { + if (val < tab[min].read) + res = tab[min].real; + else if (val >= tab[max].read) + res = tab[max].real; + } else { + if (val >= tab[min].read) + res = tab[min].real; + else if (val < tab[max].read) + res = tab[max].real; + } + } + + return res; +} + +static int stv090x_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct stv090x_state *state = fe->demodulator_priv; + u32 reg; + s32 agc_0, agc_1, agc; + s32 str; + + reg = STV090x_READ_DEMOD(state, AGCIQIN1); + agc_1 = STV090x_GETFIELD_Px(reg, AGCIQ_VALUE_FIELD); + reg = STV090x_READ_DEMOD(state, AGCIQIN0); + agc_0 = STV090x_GETFIELD_Px(reg, AGCIQ_VALUE_FIELD); + agc = MAKEWORD16(agc_1, agc_0); + + str = stv090x_table_lookup(stv090x_rf_tab, + ARRAY_SIZE(stv090x_rf_tab) - 1, agc); + if (agc > stv090x_rf_tab[0].read) + str = 0; + else if (agc < stv090x_rf_tab[ARRAY_SIZE(stv090x_rf_tab) - 1].read) + str = -100; + *strength = (str + 100) * 0xFFFF / 100; + + return 0; +} + +static int stv090x_read_cnr(struct dvb_frontend *fe, u16 *cnr) +{ + struct stv090x_state *state = fe->demodulator_priv; + u32 reg_0, reg_1, reg, i; + s32 val_0, val_1, val = 0; + u8 lock_f; + s32 div; + u32 last; + + switch (state->delsys) { + case STV090x_DVBS2: + reg = STV090x_READ_DEMOD(state, DSTATUS); + lock_f = STV090x_GETFIELD_Px(reg, LOCK_DEFINITIF_FIELD); + if (lock_f) { + msleep(5); + for (i = 0; i < 16; i++) { + reg_1 = STV090x_READ_DEMOD(state, NNOSPLHT1); + val_1 = STV090x_GETFIELD_Px(reg_1, NOSPLHT_NORMED_FIELD); + reg_0 = STV090x_READ_DEMOD(state, NNOSPLHT0); + val_0 = STV090x_GETFIELD_Px(reg_0, NOSPLHT_NORMED_FIELD); + val += MAKEWORD16(val_1, val_0); + msleep(1); + } + val /= 16; + last = ARRAY_SIZE(stv090x_s2cn_tab) - 1; + div = stv090x_s2cn_tab[0].read - + stv090x_s2cn_tab[last].read; + *cnr = 0xFFFF - ((val * 0xFFFF) / div); + } + break; + + case STV090x_DVBS1: + case STV090x_DSS: + reg = STV090x_READ_DEMOD(state, DSTATUS); + lock_f = STV090x_GETFIELD_Px(reg, LOCK_DEFINITIF_FIELD); + if (lock_f) { + msleep(5); + for (i = 0; i < 16; i++) { + reg_1 = STV090x_READ_DEMOD(state, NOSDATAT1); + val_1 = STV090x_GETFIELD_Px(reg_1, NOSDATAT_UNNORMED_FIELD); + reg_0 = STV090x_READ_DEMOD(state, NOSDATAT0); + val_0 = STV090x_GETFIELD_Px(reg_0, NOSDATAT_UNNORMED_FIELD); + val += MAKEWORD16(val_1, val_0); + msleep(1); + } + val /= 16; + last = ARRAY_SIZE(stv090x_s1cn_tab) - 1; + div = stv090x_s1cn_tab[0].read - + stv090x_s1cn_tab[last].read; + *cnr = 0xFFFF - ((val * 0xFFFF) / div); + } + break; + default: + break; + } + + return 0; +} + +static int stv090x_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) +{ + struct stv090x_state *state = fe->demodulator_priv; + u32 reg; + + reg = STV090x_READ_DEMOD(state, DISTXCTL); + switch (tone) { + case SEC_TONE_ON: + STV090x_SETFIELD_Px(reg, DISTX_MODE_FIELD, 0); + STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + break; + + case SEC_TONE_OFF: + STV090x_SETFIELD_Px(reg, DISTX_MODE_FIELD, 0); + STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + break; + default: + return -EINVAL; + } + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + + +static enum dvbfe_algo stv090x_frontend_algo(struct dvb_frontend *fe) +{ + return DVBFE_ALGO_CUSTOM; +} + +static int stv090x_send_diseqc_msg(struct dvb_frontend *fe, struct dvb_diseqc_master_cmd *cmd) +{ + struct stv090x_state *state = fe->demodulator_priv; + u32 reg, idle = 0, fifo_full = 1; + int i; + + reg = STV090x_READ_DEMOD(state, DISTXCTL); + + STV090x_SETFIELD_Px(reg, DISTX_MODE_FIELD, + (state->config->diseqc_envelope_mode) ? 4 : 2); + STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + + STV090x_SETFIELD_Px(reg, DIS_PRECHARGE_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + + for (i = 0; i < cmd->msg_len; i++) { + + while (fifo_full) { + reg = STV090x_READ_DEMOD(state, DISTXSTATUS); + fifo_full = STV090x_GETFIELD_Px(reg, FIFO_FULL_FIELD); + } + + if (STV090x_WRITE_DEMOD(state, DISTXDATA, cmd->msg[i]) < 0) + goto err; + } + reg = STV090x_READ_DEMOD(state, DISTXCTL); + STV090x_SETFIELD_Px(reg, DIS_PRECHARGE_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + + i = 0; + + while ((!idle) && (i < 10)) { + reg = STV090x_READ_DEMOD(state, DISTXSTATUS); + idle = STV090x_GETFIELD_Px(reg, TX_IDLE_FIELD); + msleep(10); + i++; + } + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_send_diseqc_burst(struct dvb_frontend *fe, fe_sec_mini_cmd_t burst) +{ + struct stv090x_state *state = fe->demodulator_priv; + u32 reg, idle = 0, fifo_full = 1; + u8 mode, value; + int i; + + reg = STV090x_READ_DEMOD(state, DISTXCTL); + + if (burst == SEC_MINI_A) { + mode = (state->config->diseqc_envelope_mode) ? 5 : 3; + value = 0x00; + } else { + mode = (state->config->diseqc_envelope_mode) ? 4 : 2; + value = 0xFF; + } + + STV090x_SETFIELD_Px(reg, DISTX_MODE_FIELD, mode); + STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + + STV090x_SETFIELD_Px(reg, DIS_PRECHARGE_FIELD, 1); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + + while (fifo_full) { + reg = STV090x_READ_DEMOD(state, DISTXSTATUS); + fifo_full = STV090x_GETFIELD_Px(reg, FIFO_FULL_FIELD); + } + + if (STV090x_WRITE_DEMOD(state, DISTXDATA, value) < 0) + goto err; + + reg = STV090x_READ_DEMOD(state, DISTXCTL); + STV090x_SETFIELD_Px(reg, DIS_PRECHARGE_FIELD, 0); + if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) + goto err; + + i = 0; + + while ((!idle) && (i < 10)) { + reg = STV090x_READ_DEMOD(state, DISTXSTATUS); + idle = STV090x_GETFIELD_Px(reg, TX_IDLE_FIELD); + msleep(10); + i++; + } + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_recv_slave_reply(struct dvb_frontend *fe, struct dvb_diseqc_slave_reply *reply) +{ + struct stv090x_state *state = fe->demodulator_priv; + u32 reg = 0, i = 0, rx_end = 0; + + while ((rx_end != 1) && (i < 10)) { + msleep(10); + i++; + reg = STV090x_READ_DEMOD(state, DISRX_ST0); + rx_end = STV090x_GETFIELD_Px(reg, RX_END_FIELD); + } + + if (rx_end) { + reply->msg_len = STV090x_GETFIELD_Px(reg, FIFO_BYTENBR_FIELD); + for (i = 0; i < reply->msg_len; i++) + reply->msg[i] = STV090x_READ_DEMOD(state, DISRXDATA); + } + + return 0; +} + +static int stv090x_sleep(struct dvb_frontend *fe) +{ + struct stv090x_state *state = fe->demodulator_priv; + u32 reg; + u8 full_standby = 0; + + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (state->config->tuner_sleep) { + if (state->config->tuner_sleep(fe) < 0) + goto err_gateoff; + } + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + dprintk(FE_DEBUG, 1, "Set %s(%d) to sleep", + state->device == STV0900 ? "STV0900" : "STV0903", + state->demod); + + mutex_lock(&state->internal->demod_lock); + + switch (state->demod) { + case STV090x_DEMODULATOR_0: + /* power off ADC 1 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR1); + STV090x_SETFIELD(reg, ADC1_PON_FIELD, 0); + if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) + goto err; + /* power off DiSEqC 1 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR2); + STV090x_SETFIELD(reg, DISEQC1_PON_FIELD, 0); + if (stv090x_write_reg(state, STV090x_TSTTNR2, reg) < 0) + goto err; + + /* check whether path 2 is already sleeping, that is when + ADC2 is off */ + reg = stv090x_read_reg(state, STV090x_TSTTNR3); + if (STV090x_GETFIELD(reg, ADC2_PON_FIELD) == 0) + full_standby = 1; + + /* stop clocks */ + reg = stv090x_read_reg(state, STV090x_STOPCLK1); + /* packet delineator 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKPKDT1_FIELD, 1); + /* ADC 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKADCI1_FIELD, 1); + /* FEC clock is shared between the two paths, only stop it + when full standby is possible */ + if (full_standby) + STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 1); + if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + /* sampling 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKSAMP1_FIELD, 1); + /* viterbi 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKVIT1_FIELD, 1); + /* TS clock is shared between the two paths, only stop it + when full standby is possible */ + if (full_standby) + STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 1); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + break; + + case STV090x_DEMODULATOR_1: + /* power off ADC 2 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR3); + STV090x_SETFIELD(reg, ADC2_PON_FIELD, 0); + if (stv090x_write_reg(state, STV090x_TSTTNR3, reg) < 0) + goto err; + /* power off DiSEqC 2 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR4); + STV090x_SETFIELD(reg, DISEQC2_PON_FIELD, 0); + if (stv090x_write_reg(state, STV090x_TSTTNR4, reg) < 0) + goto err; + + /* check whether path 1 is already sleeping, that is when + ADC1 is off */ + reg = stv090x_read_reg(state, STV090x_TSTTNR1); + if (STV090x_GETFIELD(reg, ADC1_PON_FIELD) == 0) + full_standby = 1; + + /* stop clocks */ + reg = stv090x_read_reg(state, STV090x_STOPCLK1); + /* packet delineator 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKPKDT2_FIELD, 1); + /* ADC 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKADCI2_FIELD, 1); + /* FEC clock is shared between the two paths, only stop it + when full standby is possible */ + if (full_standby) + STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 1); + if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + /* sampling 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKSAMP2_FIELD, 1); + /* viterbi 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKVIT2_FIELD, 1); + /* TS clock is shared between the two paths, only stop it + when full standby is possible */ + if (full_standby) + STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 1); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + break; + + default: + dprintk(FE_ERROR, 1, "Wrong demodulator!"); + break; + } + + if (full_standby) { + /* general power off */ + reg = stv090x_read_reg(state, STV090x_SYNTCTRL); + STV090x_SETFIELD(reg, STANDBY_FIELD, 0x01); + if (stv090x_write_reg(state, STV090x_SYNTCTRL, reg) < 0) + goto err; + } + + mutex_unlock(&state->internal->demod_lock); + return 0; + +err_gateoff: + stv090x_i2c_gate_ctrl(state, 0); +err: + mutex_unlock(&state->internal->demod_lock); + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_wakeup(struct dvb_frontend *fe) +{ + struct stv090x_state *state = fe->demodulator_priv; + u32 reg; + + dprintk(FE_DEBUG, 1, "Wake %s(%d) from standby", + state->device == STV0900 ? "STV0900" : "STV0903", + state->demod); + + mutex_lock(&state->internal->demod_lock); + + /* general power on */ + reg = stv090x_read_reg(state, STV090x_SYNTCTRL); + STV090x_SETFIELD(reg, STANDBY_FIELD, 0x00); + if (stv090x_write_reg(state, STV090x_SYNTCTRL, reg) < 0) + goto err; + + switch (state->demod) { + case STV090x_DEMODULATOR_0: + /* power on ADC 1 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR1); + STV090x_SETFIELD(reg, ADC1_PON_FIELD, 1); + if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) + goto err; + /* power on DiSEqC 1 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR2); + STV090x_SETFIELD(reg, DISEQC1_PON_FIELD, 1); + if (stv090x_write_reg(state, STV090x_TSTTNR2, reg) < 0) + goto err; + + /* activate clocks */ + reg = stv090x_read_reg(state, STV090x_STOPCLK1); + /* packet delineator 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKPKDT1_FIELD, 0); + /* ADC 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKADCI1_FIELD, 0); + /* FEC clock */ + STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 0); + if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + /* sampling 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKSAMP1_FIELD, 0); + /* viterbi 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKVIT1_FIELD, 0); + /* TS clock */ + STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 0); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + break; + + case STV090x_DEMODULATOR_1: + /* power on ADC 2 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR3); + STV090x_SETFIELD(reg, ADC2_PON_FIELD, 1); + if (stv090x_write_reg(state, STV090x_TSTTNR3, reg) < 0) + goto err; + /* power on DiSEqC 2 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR4); + STV090x_SETFIELD(reg, DISEQC2_PON_FIELD, 1); + if (stv090x_write_reg(state, STV090x_TSTTNR4, reg) < 0) + goto err; + + /* activate clocks */ + reg = stv090x_read_reg(state, STV090x_STOPCLK1); + /* packet delineator 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKPKDT2_FIELD, 0); + /* ADC 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKADCI2_FIELD, 0); + /* FEC clock */ + STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 0); + if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + /* sampling 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKSAMP2_FIELD, 0); + /* viterbi 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKVIT2_FIELD, 0); + /* TS clock */ + STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 0); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + break; + + default: + dprintk(FE_ERROR, 1, "Wrong demodulator!"); + break; + } + + mutex_unlock(&state->internal->demod_lock); + return 0; +err: + mutex_unlock(&state->internal->demod_lock); + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static void stv090x_release(struct dvb_frontend *fe) +{ + struct stv090x_state *state = fe->demodulator_priv; + + state->internal->num_used--; + if (state->internal->num_used <= 0) { + + dprintk(FE_ERROR, 1, "Actually removing"); + + remove_dev(state->internal); + kfree(state->internal); + } + + kfree(state); +} + +static int stv090x_ldpc_mode(struct stv090x_state *state, enum stv090x_mode ldpc_mode) +{ + u32 reg = 0; + + reg = stv090x_read_reg(state, STV090x_GENCFG); + + switch (ldpc_mode) { + case STV090x_DUAL: + default: + if ((state->demod_mode != STV090x_DUAL) || (STV090x_GETFIELD(reg, DDEMOD_FIELD) != 1)) { + /* set LDPC to dual mode */ + if (stv090x_write_reg(state, STV090x_GENCFG, 0x1d) < 0) + goto err; + + state->demod_mode = STV090x_DUAL; + + reg = stv090x_read_reg(state, STV090x_TSTRES0); + STV090x_SETFIELD(reg, FRESFEC_FIELD, 0x1); + if (stv090x_write_reg(state, STV090x_TSTRES0, reg) < 0) + goto err; + STV090x_SETFIELD(reg, FRESFEC_FIELD, 0x0); + if (stv090x_write_reg(state, STV090x_TSTRES0, reg) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, MODCODLST0, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST1, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST2, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST3, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST4, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST5, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST6, 0xff) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, MODCODLST7, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST8, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLST9, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTA, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTB, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTC, 0xcc) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTD, 0xcc) < 0) + goto err; + + if (STV090x_WRITE_DEMOD(state, MODCODLSTE, 0xff) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, MODCODLSTF, 0xcf) < 0) + goto err; + } + break; + + case STV090x_SINGLE: + if (stv090x_stop_modcod(state) < 0) + goto err; + if (stv090x_activate_modcod_single(state) < 0) + goto err; + + if (state->demod == STV090x_DEMODULATOR_1) { + if (stv090x_write_reg(state, STV090x_GENCFG, 0x06) < 0) /* path 2 */ + goto err; + } else { + if (stv090x_write_reg(state, STV090x_GENCFG, 0x04) < 0) /* path 1 */ + goto err; + } + + reg = stv090x_read_reg(state, STV090x_TSTRES0); + STV090x_SETFIELD(reg, FRESFEC_FIELD, 0x1); + if (stv090x_write_reg(state, STV090x_TSTRES0, reg) < 0) + goto err; + STV090x_SETFIELD(reg, FRESFEC_FIELD, 0x0); + if (stv090x_write_reg(state, STV090x_TSTRES0, reg) < 0) + goto err; + + reg = STV090x_READ_DEMOD(state, PDELCTRL1); + STV090x_SETFIELD_Px(reg, ALGOSWRST_FIELD, 0x01); + if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) + goto err; + STV090x_SETFIELD_Px(reg, ALGOSWRST_FIELD, 0x00); + if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) + goto err; + break; + } + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +/* return (Hz), clk in Hz*/ +static u32 stv090x_get_mclk(struct stv090x_state *state) +{ + const struct stv090x_config *config = state->config; + u32 div, reg; + u8 ratio; + + div = stv090x_read_reg(state, STV090x_NCOARSE); + reg = stv090x_read_reg(state, STV090x_SYNTCTRL); + ratio = STV090x_GETFIELD(reg, SELX1RATIO_FIELD) ? 4 : 6; + + return (div + 1) * config->xtal / ratio; /* kHz */ +} + +static int stv090x_set_mclk(struct stv090x_state *state, u32 mclk, u32 clk) +{ + const struct stv090x_config *config = state->config; + u32 reg, div, clk_sel; + + reg = stv090x_read_reg(state, STV090x_SYNTCTRL); + clk_sel = ((STV090x_GETFIELD(reg, SELX1RATIO_FIELD) == 1) ? 4 : 6); + + div = ((clk_sel * mclk) / config->xtal) - 1; + + reg = stv090x_read_reg(state, STV090x_NCOARSE); + STV090x_SETFIELD(reg, M_DIV_FIELD, div); + if (stv090x_write_reg(state, STV090x_NCOARSE, reg) < 0) + goto err; + + state->internal->mclk = stv090x_get_mclk(state); + + /*Set the DiseqC frequency to 22KHz */ + div = state->internal->mclk / 704000; + if (STV090x_WRITE_DEMOD(state, F22TX, div) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, F22RX, div) < 0) + goto err; + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_set_tspath(struct stv090x_state *state) +{ + u32 reg; + + if (state->internal->dev_ver >= 0x20) { + switch (state->config->ts1_mode) { + case STV090x_TSMODE_PARALLEL_PUNCTURED: + case STV090x_TSMODE_DVBCI: + switch (state->config->ts2_mode) { + case STV090x_TSMODE_SERIAL_PUNCTURED: + case STV090x_TSMODE_SERIAL_CONTINUOUS: + default: + stv090x_write_reg(state, STV090x_TSGENERAL, 0x00); + break; + + case STV090x_TSMODE_PARALLEL_PUNCTURED: + case STV090x_TSMODE_DVBCI: + if (stv090x_write_reg(state, STV090x_TSGENERAL, 0x06) < 0) /* Mux'd stream mode */ + goto err; + reg = stv090x_read_reg(state, STV090x_P1_TSCFGM); + STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); + if (stv090x_write_reg(state, STV090x_P1_TSCFGM, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_P2_TSCFGM); + STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); + if (stv090x_write_reg(state, STV090x_P2_TSCFGM, reg) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_P1_TSSPEED, 0x14) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_P2_TSSPEED, 0x28) < 0) + goto err; + break; + } + break; + + case STV090x_TSMODE_SERIAL_PUNCTURED: + case STV090x_TSMODE_SERIAL_CONTINUOUS: + default: + switch (state->config->ts2_mode) { + case STV090x_TSMODE_SERIAL_PUNCTURED: + case STV090x_TSMODE_SERIAL_CONTINUOUS: + default: + if (stv090x_write_reg(state, STV090x_TSGENERAL, 0x0c) < 0) + goto err; + break; + + case STV090x_TSMODE_PARALLEL_PUNCTURED: + case STV090x_TSMODE_DVBCI: + if (stv090x_write_reg(state, STV090x_TSGENERAL, 0x0a) < 0) + goto err; + break; + } + break; + } + } else { + switch (state->config->ts1_mode) { + case STV090x_TSMODE_PARALLEL_PUNCTURED: + case STV090x_TSMODE_DVBCI: + switch (state->config->ts2_mode) { + case STV090x_TSMODE_SERIAL_PUNCTURED: + case STV090x_TSMODE_SERIAL_CONTINUOUS: + default: + stv090x_write_reg(state, STV090x_TSGENERAL1X, 0x10); + break; + + case STV090x_TSMODE_PARALLEL_PUNCTURED: + case STV090x_TSMODE_DVBCI: + stv090x_write_reg(state, STV090x_TSGENERAL1X, 0x16); + reg = stv090x_read_reg(state, STV090x_P1_TSCFGM); + STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); + if (stv090x_write_reg(state, STV090x_P1_TSCFGM, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_P1_TSCFGM); + STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 0); + if (stv090x_write_reg(state, STV090x_P1_TSCFGM, reg) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_P1_TSSPEED, 0x14) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_P2_TSSPEED, 0x28) < 0) + goto err; + break; + } + break; + + case STV090x_TSMODE_SERIAL_PUNCTURED: + case STV090x_TSMODE_SERIAL_CONTINUOUS: + default: + switch (state->config->ts2_mode) { + case STV090x_TSMODE_SERIAL_PUNCTURED: + case STV090x_TSMODE_SERIAL_CONTINUOUS: + default: + stv090x_write_reg(state, STV090x_TSGENERAL1X, 0x14); + break; + + case STV090x_TSMODE_PARALLEL_PUNCTURED: + case STV090x_TSMODE_DVBCI: + stv090x_write_reg(state, STV090x_TSGENERAL1X, 0x12); + break; + } + break; + } + } + + switch (state->config->ts1_mode) { + case STV090x_TSMODE_PARALLEL_PUNCTURED: + reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); + STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); + STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); + if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) + goto err; + break; + + case STV090x_TSMODE_DVBCI: + reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); + STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); + STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); + if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) + goto err; + break; + + case STV090x_TSMODE_SERIAL_PUNCTURED: + reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); + STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); + STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); + if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) + goto err; + break; + + case STV090x_TSMODE_SERIAL_CONTINUOUS: + reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); + STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); + STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); + if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) + goto err; + break; + + default: + break; + } + + switch (state->config->ts2_mode) { + case STV090x_TSMODE_PARALLEL_PUNCTURED: + reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); + STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); + STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); + if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) + goto err; + break; + + case STV090x_TSMODE_DVBCI: + reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); + STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); + STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); + if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) + goto err; + break; + + case STV090x_TSMODE_SERIAL_PUNCTURED: + reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); + STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); + STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); + if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) + goto err; + break; + + case STV090x_TSMODE_SERIAL_CONTINUOUS: + reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); + STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); + STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); + if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) + goto err; + break; + + default: + break; + } + + if (state->config->ts1_clk > 0) { + u32 speed; + + switch (state->config->ts1_mode) { + case STV090x_TSMODE_PARALLEL_PUNCTURED: + case STV090x_TSMODE_DVBCI: + default: + speed = state->internal->mclk / + (state->config->ts1_clk / 4); + if (speed < 0x08) + speed = 0x08; + if (speed > 0xFF) + speed = 0xFF; + break; + case STV090x_TSMODE_SERIAL_PUNCTURED: + case STV090x_TSMODE_SERIAL_CONTINUOUS: + speed = state->internal->mclk / + (state->config->ts1_clk / 32); + if (speed < 0x20) + speed = 0x20; + if (speed > 0xFF) + speed = 0xFF; + break; + } + reg = stv090x_read_reg(state, STV090x_P1_TSCFGM); + STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); + if (stv090x_write_reg(state, STV090x_P1_TSCFGM, reg) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_P1_TSSPEED, speed) < 0) + goto err; + } + + if (state->config->ts2_clk > 0) { + u32 speed; + + switch (state->config->ts2_mode) { + case STV090x_TSMODE_PARALLEL_PUNCTURED: + case STV090x_TSMODE_DVBCI: + default: + speed = state->internal->mclk / + (state->config->ts2_clk / 4); + if (speed < 0x08) + speed = 0x08; + if (speed > 0xFF) + speed = 0xFF; + break; + case STV090x_TSMODE_SERIAL_PUNCTURED: + case STV090x_TSMODE_SERIAL_CONTINUOUS: + speed = state->internal->mclk / + (state->config->ts2_clk / 32); + if (speed < 0x20) + speed = 0x20; + if (speed > 0xFF) + speed = 0xFF; + break; + } + reg = stv090x_read_reg(state, STV090x_P2_TSCFGM); + STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); + if (stv090x_write_reg(state, STV090x_P2_TSCFGM, reg) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_P2_TSSPEED, speed) < 0) + goto err; + } + + reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); + STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0x01); + if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) + goto err; + STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0x00); + if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) + goto err; + + reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); + STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0x01); + if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) + goto err; + STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0x00); + if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) + goto err; + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_init(struct dvb_frontend *fe) +{ + struct stv090x_state *state = fe->demodulator_priv; + const struct stv090x_config *config = state->config; + u32 reg; + + if (state->internal->mclk == 0) { + /* call tuner init to configure the tuner's clock output + divider directly before setting up the master clock of + the stv090x. */ + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (config->tuner_init) { + if (config->tuner_init(fe) < 0) + goto err_gateoff; + } + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + stv090x_set_mclk(state, 135000000, config->xtal); /* 135 Mhz */ + msleep(5); + if (stv090x_write_reg(state, STV090x_SYNTCTRL, + 0x20 | config->clk_mode) < 0) + goto err; + stv090x_get_mclk(state); + } + + if (stv090x_wakeup(fe) < 0) { + dprintk(FE_ERROR, 1, "Error waking device"); + goto err; + } + + if (stv090x_ldpc_mode(state, state->demod_mode) < 0) + goto err; + + reg = STV090x_READ_DEMOD(state, TNRCFG2); + STV090x_SETFIELD_Px(reg, TUN_IQSWAP_FIELD, state->inversion); + if (STV090x_WRITE_DEMOD(state, TNRCFG2, reg) < 0) + goto err; + reg = STV090x_READ_DEMOD(state, DEMOD); + STV090x_SETFIELD_Px(reg, ROLLOFF_CONTROL_FIELD, state->rolloff); + if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) + goto err; + + if (stv090x_i2c_gate_ctrl(state, 1) < 0) + goto err; + + if (config->tuner_set_mode) { + if (config->tuner_set_mode(fe, TUNER_WAKE) < 0) + goto err_gateoff; + } + + if (config->tuner_init) { + if (config->tuner_init(fe) < 0) + goto err_gateoff; + } + + if (stv090x_i2c_gate_ctrl(state, 0) < 0) + goto err; + + if (stv090x_set_tspath(state) < 0) + goto err; + + return 0; + +err_gateoff: + stv090x_i2c_gate_ctrl(state, 0); +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +static int stv090x_setup(struct dvb_frontend *fe) +{ + struct stv090x_state *state = fe->demodulator_priv; + const struct stv090x_config *config = state->config; + const struct stv090x_reg *stv090x_initval = NULL; + const struct stv090x_reg *stv090x_cut20_val = NULL; + unsigned long t1_size = 0, t2_size = 0; + u32 reg = 0; + + int i; + + if (state->device == STV0900) { + dprintk(FE_DEBUG, 1, "Initializing STV0900"); + stv090x_initval = stv0900_initval; + t1_size = ARRAY_SIZE(stv0900_initval); + stv090x_cut20_val = stv0900_cut20_val; + t2_size = ARRAY_SIZE(stv0900_cut20_val); + } else if (state->device == STV0903) { + dprintk(FE_DEBUG, 1, "Initializing STV0903"); + stv090x_initval = stv0903_initval; + t1_size = ARRAY_SIZE(stv0903_initval); + stv090x_cut20_val = stv0903_cut20_val; + t2_size = ARRAY_SIZE(stv0903_cut20_val); + } + + /* STV090x init */ + + /* Stop Demod */ + if (stv090x_write_reg(state, STV090x_P1_DMDISTATE, 0x5c) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_P2_DMDISTATE, 0x5c) < 0) + goto err; + + msleep(5); + + /* Set No Tuner Mode */ + if (stv090x_write_reg(state, STV090x_P1_TNRCFG, 0x6c) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_P2_TNRCFG, 0x6c) < 0) + goto err; + + /* I2C repeater OFF */ + STV090x_SETFIELD_Px(reg, ENARPT_LEVEL_FIELD, config->repeater_level); + if (stv090x_write_reg(state, STV090x_P1_I2CRPT, reg) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_P2_I2CRPT, reg) < 0) + goto err; + + if (stv090x_write_reg(state, STV090x_NCOARSE, 0x13) < 0) /* set PLL divider */ + goto err; + msleep(5); + if (stv090x_write_reg(state, STV090x_I2CCFG, 0x08) < 0) /* 1/41 oversampling */ + goto err; + if (stv090x_write_reg(state, STV090x_SYNTCTRL, 0x20 | config->clk_mode) < 0) /* enable PLL */ + goto err; + msleep(5); + + /* write initval */ + dprintk(FE_DEBUG, 1, "Setting up initial values"); + for (i = 0; i < t1_size; i++) { + if (stv090x_write_reg(state, stv090x_initval[i].addr, stv090x_initval[i].data) < 0) + goto err; + } + + state->internal->dev_ver = stv090x_read_reg(state, STV090x_MID); + if (state->internal->dev_ver >= 0x20) { + if (stv090x_write_reg(state, STV090x_TSGENERAL, 0x0c) < 0) + goto err; + + /* write cut20_val*/ + dprintk(FE_DEBUG, 1, "Setting up Cut 2.0 initial values"); + for (i = 0; i < t2_size; i++) { + if (stv090x_write_reg(state, stv090x_cut20_val[i].addr, stv090x_cut20_val[i].data) < 0) + goto err; + } + + } else if (state->internal->dev_ver < 0x20) { + dprintk(FE_ERROR, 1, "ERROR: Unsupported Cut: 0x%02x!", + state->internal->dev_ver); + + goto err; + } else if (state->internal->dev_ver > 0x30) { + /* we shouldn't bail out from here */ + dprintk(FE_ERROR, 1, "INFO: Cut: 0x%02x probably incomplete support!", + state->internal->dev_ver); + } + + /* ADC1 range */ + reg = stv090x_read_reg(state, STV090x_TSTTNR1); + STV090x_SETFIELD(reg, ADC1_INMODE_FIELD, + (config->adc1_range == STV090x_ADC_1Vpp) ? 0 : 1); + if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) + goto err; + + /* ADC2 range */ + reg = stv090x_read_reg(state, STV090x_TSTTNR3); + STV090x_SETFIELD(reg, ADC2_INMODE_FIELD, + (config->adc2_range == STV090x_ADC_1Vpp) ? 0 : 1); + if (stv090x_write_reg(state, STV090x_TSTTNR3, reg) < 0) + goto err; + + if (stv090x_write_reg(state, STV090x_TSTRES0, 0x80) < 0) + goto err; + if (stv090x_write_reg(state, STV090x_TSTRES0, 0x00) < 0) + goto err; + + return 0; +err: + dprintk(FE_ERROR, 1, "I/O error"); + return -1; +} + +int stv090x_set_gpio(struct dvb_frontend *fe, u8 gpio, u8 dir, u8 value, + u8 xor_value) +{ + struct stv090x_state *state = fe->demodulator_priv; + u8 reg = 0; + + STV090x_SETFIELD(reg, GPIOx_OPD_FIELD, dir); + STV090x_SETFIELD(reg, GPIOx_CONFIG_FIELD, value); + STV090x_SETFIELD(reg, GPIOx_XOR_FIELD, xor_value); + + return stv090x_write_reg(state, STV090x_GPIOxCFG(gpio), reg); +} +EXPORT_SYMBOL(stv090x_set_gpio); + +static struct dvb_frontend_ops stv090x_ops = { + .delsys = { SYS_DVBS, SYS_DVBS2, SYS_DSS }, + .info = { + .name = "STV090x Multistandard", + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 0, + .frequency_tolerance = 0, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_2G_MODULATION + }, + + .release = stv090x_release, + .init = stv090x_init, + + .sleep = stv090x_sleep, + .get_frontend_algo = stv090x_frontend_algo, + + .diseqc_send_master_cmd = stv090x_send_diseqc_msg, + .diseqc_send_burst = stv090x_send_diseqc_burst, + .diseqc_recv_slave_reply = stv090x_recv_slave_reply, + .set_tone = stv090x_set_tone, + + .search = stv090x_search, + .read_status = stv090x_read_status, + .read_ber = stv090x_read_per, + .read_signal_strength = stv090x_read_signal_strength, + .read_snr = stv090x_read_cnr, +}; + + +struct dvb_frontend *stv090x_attach(const struct stv090x_config *config, + struct i2c_adapter *i2c, + enum stv090x_demodulator demod) +{ + struct stv090x_state *state = NULL; + struct stv090x_dev *temp_int; + + state = kzalloc(sizeof (struct stv090x_state), GFP_KERNEL); + if (state == NULL) + goto error; + + state->verbose = &verbose; + state->config = config; + state->i2c = i2c; + state->frontend.ops = stv090x_ops; + state->frontend.demodulator_priv = state; + state->demod = demod; + state->demod_mode = config->demod_mode; /* Single or Dual mode */ + state->device = config->device; + state->rolloff = STV090x_RO_35; /* default */ + + temp_int = find_dev(state->i2c, + state->config->address); + + if ((temp_int != NULL) && (state->demod_mode == STV090x_DUAL)) { + state->internal = temp_int->internal; + state->internal->num_used++; + dprintk(FE_INFO, 1, "Found Internal Structure!"); + } else { + state->internal = kmalloc(sizeof(struct stv090x_internal), + GFP_KERNEL); + if (!state->internal) + goto error; + temp_int = append_internal(state->internal); + if (!temp_int) { + kfree(state->internal); + goto error; + } + state->internal->num_used = 1; + state->internal->mclk = 0; + state->internal->dev_ver = 0; + state->internal->i2c_adap = state->i2c; + state->internal->i2c_addr = state->config->address; + dprintk(FE_INFO, 1, "Create New Internal Structure!"); + + mutex_init(&state->internal->demod_lock); + mutex_init(&state->internal->tuner_lock); + + if (stv090x_setup(&state->frontend) < 0) { + dprintk(FE_ERROR, 1, "Error setting up device"); + goto err_remove; + } + } + + if (state->internal->dev_ver >= 0x30) + state->frontend.ops.info.caps |= FE_CAN_MULTISTREAM; + + /* workaround for stuck DiSEqC output */ + if (config->diseqc_envelope_mode) + stv090x_send_diseqc_burst(&state->frontend, SEC_MINI_A); + + dprintk(FE_ERROR, 1, "Attaching %s demodulator(%d) Cut=0x%02x", + state->device == STV0900 ? "STV0900" : "STV0903", + demod, + state->internal->dev_ver); + + return &state->frontend; + +err_remove: + remove_dev(state->internal); + kfree(state->internal); +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(stv090x_attach); +MODULE_PARM_DESC(verbose, "Set Verbosity level"); +MODULE_AUTHOR("Manu Abraham"); +MODULE_DESCRIPTION("STV090x Multi-Std Broadcast frontend"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/stv090x.h b/drivers/media/dvb-frontends/stv090x.h similarity index 100% rename from drivers/media/dvb/frontends/stv090x.h rename to drivers/media/dvb-frontends/stv090x.h diff --git a/drivers/media/dvb/frontends/stv090x_priv.h b/drivers/media/dvb-frontends/stv090x_priv.h similarity index 100% rename from drivers/media/dvb/frontends/stv090x_priv.h rename to drivers/media/dvb-frontends/stv090x_priv.h diff --git a/drivers/media/dvb/frontends/stv090x_reg.h b/drivers/media/dvb-frontends/stv090x_reg.h similarity index 100% rename from drivers/media/dvb/frontends/stv090x_reg.h rename to drivers/media/dvb-frontends/stv090x_reg.h diff --git a/drivers/media/dvb/frontends/stv6110.c b/drivers/media/dvb-frontends/stv6110.c similarity index 100% rename from drivers/media/dvb/frontends/stv6110.c rename to drivers/media/dvb-frontends/stv6110.c diff --git a/drivers/media/dvb/frontends/stv6110.h b/drivers/media/dvb-frontends/stv6110.h similarity index 100% rename from drivers/media/dvb/frontends/stv6110.h rename to drivers/media/dvb-frontends/stv6110.h diff --git a/drivers/media/dvb/frontends/stv6110x.c b/drivers/media/dvb-frontends/stv6110x.c similarity index 100% rename from drivers/media/dvb/frontends/stv6110x.c rename to drivers/media/dvb-frontends/stv6110x.c diff --git a/drivers/media/dvb/frontends/stv6110x.h b/drivers/media/dvb-frontends/stv6110x.h similarity index 100% rename from drivers/media/dvb/frontends/stv6110x.h rename to drivers/media/dvb-frontends/stv6110x.h diff --git a/drivers/media/dvb/frontends/stv6110x_priv.h b/drivers/media/dvb-frontends/stv6110x_priv.h similarity index 100% rename from drivers/media/dvb/frontends/stv6110x_priv.h rename to drivers/media/dvb-frontends/stv6110x_priv.h diff --git a/drivers/media/dvb/frontends/stv6110x_reg.h b/drivers/media/dvb-frontends/stv6110x_reg.h similarity index 100% rename from drivers/media/dvb/frontends/stv6110x_reg.h rename to drivers/media/dvb-frontends/stv6110x_reg.h diff --git a/drivers/media/dvb/frontends/tda10021.c b/drivers/media/dvb-frontends/tda10021.c similarity index 100% rename from drivers/media/dvb/frontends/tda10021.c rename to drivers/media/dvb-frontends/tda10021.c diff --git a/drivers/media/dvb/frontends/tda10023.c b/drivers/media/dvb-frontends/tda10023.c similarity index 100% rename from drivers/media/dvb/frontends/tda10023.c rename to drivers/media/dvb-frontends/tda10023.c diff --git a/drivers/media/dvb/frontends/tda1002x.h b/drivers/media/dvb-frontends/tda1002x.h similarity index 100% rename from drivers/media/dvb/frontends/tda1002x.h rename to drivers/media/dvb-frontends/tda1002x.h diff --git a/drivers/media/dvb/frontends/tda10048.c b/drivers/media/dvb-frontends/tda10048.c similarity index 100% rename from drivers/media/dvb/frontends/tda10048.c rename to drivers/media/dvb-frontends/tda10048.c diff --git a/drivers/media/dvb/frontends/tda10048.h b/drivers/media/dvb-frontends/tda10048.h similarity index 100% rename from drivers/media/dvb/frontends/tda10048.h rename to drivers/media/dvb-frontends/tda10048.h diff --git a/drivers/media/dvb-frontends/tda1004x.c b/drivers/media/dvb-frontends/tda1004x.c new file mode 100644 index 000000000000..a2631be7ffac --- /dev/null +++ b/drivers/media/dvb-frontends/tda1004x.c @@ -0,0 +1,1385 @@ + /* + Driver for Philips tda1004xh OFDM Demodulator + + (c) 2003, 2004 Andrew de Quincey & Robert Schlabbach + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* + * This driver needs external firmware. Please use the commands + * "/Documentation/dvb/get_dvb_firmware tda10045", + * "/Documentation/dvb/get_dvb_firmware tda10046" to + * download/extract them, and then copy them to /usr/lib/hotplug/firmware + * or /lib/firmware (depending on configuration of firmware hotplug). + */ +#define TDA10045_DEFAULT_FIRMWARE "dvb-fe-tda10045.fw" +#define TDA10046_DEFAULT_FIRMWARE "dvb-fe-tda10046.fw" + +#include +#include +#include +#include +#include +#include + +#include "dvb_frontend.h" +#include "tda1004x.h" + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "tda1004x: " args); \ + } while (0) + +#define TDA1004X_CHIPID 0x00 +#define TDA1004X_AUTO 0x01 +#define TDA1004X_IN_CONF1 0x02 +#define TDA1004X_IN_CONF2 0x03 +#define TDA1004X_OUT_CONF1 0x04 +#define TDA1004X_OUT_CONF2 0x05 +#define TDA1004X_STATUS_CD 0x06 +#define TDA1004X_CONFC4 0x07 +#define TDA1004X_DSSPARE2 0x0C +#define TDA10045H_CODE_IN 0x0D +#define TDA10045H_FWPAGE 0x0E +#define TDA1004X_SCAN_CPT 0x10 +#define TDA1004X_DSP_CMD 0x11 +#define TDA1004X_DSP_ARG 0x12 +#define TDA1004X_DSP_DATA1 0x13 +#define TDA1004X_DSP_DATA2 0x14 +#define TDA1004X_CONFADC1 0x15 +#define TDA1004X_CONFC1 0x16 +#define TDA10045H_S_AGC 0x1a +#define TDA10046H_AGC_TUN_LEVEL 0x1a +#define TDA1004X_SNR 0x1c +#define TDA1004X_CONF_TS1 0x1e +#define TDA1004X_CONF_TS2 0x1f +#define TDA1004X_CBER_RESET 0x20 +#define TDA1004X_CBER_MSB 0x21 +#define TDA1004X_CBER_LSB 0x22 +#define TDA1004X_CVBER_LUT 0x23 +#define TDA1004X_VBER_MSB 0x24 +#define TDA1004X_VBER_MID 0x25 +#define TDA1004X_VBER_LSB 0x26 +#define TDA1004X_UNCOR 0x27 + +#define TDA10045H_CONFPLL_P 0x2D +#define TDA10045H_CONFPLL_M_MSB 0x2E +#define TDA10045H_CONFPLL_M_LSB 0x2F +#define TDA10045H_CONFPLL_N 0x30 + +#define TDA10046H_CONFPLL1 0x2D +#define TDA10046H_CONFPLL2 0x2F +#define TDA10046H_CONFPLL3 0x30 +#define TDA10046H_TIME_WREF1 0x31 +#define TDA10046H_TIME_WREF2 0x32 +#define TDA10046H_TIME_WREF3 0x33 +#define TDA10046H_TIME_WREF4 0x34 +#define TDA10046H_TIME_WREF5 0x35 + +#define TDA10045H_UNSURW_MSB 0x31 +#define TDA10045H_UNSURW_LSB 0x32 +#define TDA10045H_WREF_MSB 0x33 +#define TDA10045H_WREF_MID 0x34 +#define TDA10045H_WREF_LSB 0x35 +#define TDA10045H_MUXOUT 0x36 +#define TDA1004X_CONFADC2 0x37 + +#define TDA10045H_IOFFSET 0x38 + +#define TDA10046H_CONF_TRISTATE1 0x3B +#define TDA10046H_CONF_TRISTATE2 0x3C +#define TDA10046H_CONF_POLARITY 0x3D +#define TDA10046H_FREQ_OFFSET 0x3E +#define TDA10046H_GPIO_OUT_SEL 0x41 +#define TDA10046H_GPIO_SELECT 0x42 +#define TDA10046H_AGC_CONF 0x43 +#define TDA10046H_AGC_THR 0x44 +#define TDA10046H_AGC_RENORM 0x45 +#define TDA10046H_AGC_GAINS 0x46 +#define TDA10046H_AGC_TUN_MIN 0x47 +#define TDA10046H_AGC_TUN_MAX 0x48 +#define TDA10046H_AGC_IF_MIN 0x49 +#define TDA10046H_AGC_IF_MAX 0x4A + +#define TDA10046H_FREQ_PHY2_MSB 0x4D +#define TDA10046H_FREQ_PHY2_LSB 0x4E + +#define TDA10046H_CVBER_CTRL 0x4F +#define TDA10046H_AGC_IF_LEVEL 0x52 +#define TDA10046H_CODE_CPT 0x57 +#define TDA10046H_CODE_IN 0x58 + + +static int tda1004x_write_byteI(struct tda1004x_state *state, int reg, int data) +{ + int ret; + u8 buf[] = { reg, data }; + struct i2c_msg msg = { .flags = 0, .buf = buf, .len = 2 }; + + dprintk("%s: reg=0x%x, data=0x%x\n", __func__, reg, data); + + msg.addr = state->config->demod_address; + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: error reg=0x%x, data=0x%x, ret=%i\n", + __func__, reg, data, ret); + + dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __func__, + reg, data, ret); + return (ret != 1) ? -1 : 0; +} + +static int tda1004x_read_byte(struct tda1004x_state *state, int reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = {{ .flags = 0, .buf = b0, .len = 1 }, + { .flags = I2C_M_RD, .buf = b1, .len = 1 }}; + + dprintk("%s: reg=0x%x\n", __func__, reg); + + msg[0].addr = state->config->demod_address; + msg[1].addr = state->config->demod_address; + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) { + dprintk("%s: error reg=0x%x, ret=%i\n", __func__, reg, + ret); + return -EINVAL; + } + + dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __func__, + reg, b1[0], ret); + return b1[0]; +} + +static int tda1004x_write_mask(struct tda1004x_state *state, int reg, int mask, int data) +{ + int val; + dprintk("%s: reg=0x%x, mask=0x%x, data=0x%x\n", __func__, reg, + mask, data); + + // read a byte and check + val = tda1004x_read_byte(state, reg); + if (val < 0) + return val; + + // mask if off + val = val & ~mask; + val |= data & 0xff; + + // write it out again + return tda1004x_write_byteI(state, reg, val); +} + +static int tda1004x_write_buf(struct tda1004x_state *state, int reg, unsigned char *buf, int len) +{ + int i; + int result; + + dprintk("%s: reg=0x%x, len=0x%x\n", __func__, reg, len); + + result = 0; + for (i = 0; i < len; i++) { + result = tda1004x_write_byteI(state, reg + i, buf[i]); + if (result != 0) + break; + } + + return result; +} + +static int tda1004x_enable_tuner_i2c(struct tda1004x_state *state) +{ + int result; + dprintk("%s\n", __func__); + + result = tda1004x_write_mask(state, TDA1004X_CONFC4, 2, 2); + msleep(20); + return result; +} + +static int tda1004x_disable_tuner_i2c(struct tda1004x_state *state) +{ + dprintk("%s\n", __func__); + + return tda1004x_write_mask(state, TDA1004X_CONFC4, 2, 0); +} + +static int tda10045h_set_bandwidth(struct tda1004x_state *state, + u32 bandwidth) +{ + static u8 bandwidth_6mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x60, 0x1e, 0xa7, 0x45, 0x4f }; + static u8 bandwidth_7mhz[] = { 0x02, 0x00, 0x37, 0x00, 0x4a, 0x2f, 0x6d, 0x76, 0xdb }; + static u8 bandwidth_8mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x48, 0x17, 0x89, 0xc7, 0x14 }; + + switch (bandwidth) { + case 6000000: + tda1004x_write_buf(state, TDA10045H_CONFPLL_P, bandwidth_6mhz, sizeof(bandwidth_6mhz)); + break; + + case 7000000: + tda1004x_write_buf(state, TDA10045H_CONFPLL_P, bandwidth_7mhz, sizeof(bandwidth_7mhz)); + break; + + case 8000000: + tda1004x_write_buf(state, TDA10045H_CONFPLL_P, bandwidth_8mhz, sizeof(bandwidth_8mhz)); + break; + + default: + return -EINVAL; + } + + tda1004x_write_byteI(state, TDA10045H_IOFFSET, 0); + + return 0; +} + +static int tda10046h_set_bandwidth(struct tda1004x_state *state, + u32 bandwidth) +{ + static u8 bandwidth_6mhz_53M[] = { 0x7b, 0x2e, 0x11, 0xf0, 0xd2 }; + static u8 bandwidth_7mhz_53M[] = { 0x6a, 0x02, 0x6a, 0x43, 0x9f }; + static u8 bandwidth_8mhz_53M[] = { 0x5c, 0x32, 0xc2, 0x96, 0x6d }; + + static u8 bandwidth_6mhz_48M[] = { 0x70, 0x02, 0x49, 0x24, 0x92 }; + static u8 bandwidth_7mhz_48M[] = { 0x60, 0x02, 0xaa, 0xaa, 0xab }; + static u8 bandwidth_8mhz_48M[] = { 0x54, 0x03, 0x0c, 0x30, 0xc3 }; + int tda10046_clk53m; + + if ((state->config->if_freq == TDA10046_FREQ_045) || + (state->config->if_freq == TDA10046_FREQ_052)) + tda10046_clk53m = 0; + else + tda10046_clk53m = 1; + switch (bandwidth) { + case 6000000: + if (tda10046_clk53m) + tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_6mhz_53M, + sizeof(bandwidth_6mhz_53M)); + else + tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_6mhz_48M, + sizeof(bandwidth_6mhz_48M)); + if (state->config->if_freq == TDA10046_FREQ_045) { + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0a); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0xab); + } + break; + + case 7000000: + if (tda10046_clk53m) + tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_7mhz_53M, + sizeof(bandwidth_7mhz_53M)); + else + tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_7mhz_48M, + sizeof(bandwidth_7mhz_48M)); + if (state->config->if_freq == TDA10046_FREQ_045) { + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0c); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x00); + } + break; + + case 8000000: + if (tda10046_clk53m) + tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_8mhz_53M, + sizeof(bandwidth_8mhz_53M)); + else + tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_8mhz_48M, + sizeof(bandwidth_8mhz_48M)); + if (state->config->if_freq == TDA10046_FREQ_045) { + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0d); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x55); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int tda1004x_do_upload(struct tda1004x_state *state, + const unsigned char *mem, unsigned int len, + u8 dspCodeCounterReg, u8 dspCodeInReg) +{ + u8 buf[65]; + struct i2c_msg fw_msg = { .flags = 0, .buf = buf, .len = 0 }; + int tx_size; + int pos = 0; + + /* clear code counter */ + tda1004x_write_byteI(state, dspCodeCounterReg, 0); + fw_msg.addr = state->config->demod_address; + + i2c_lock_adapter(state->i2c); + buf[0] = dspCodeInReg; + while (pos != len) { + // work out how much to send this time + tx_size = len - pos; + if (tx_size > 0x10) + tx_size = 0x10; + + // send the chunk + memcpy(buf + 1, mem + pos, tx_size); + fw_msg.len = tx_size + 1; + if (__i2c_transfer(state->i2c, &fw_msg, 1) != 1) { + printk(KERN_ERR "tda1004x: Error during firmware upload\n"); + i2c_unlock_adapter(state->i2c); + return -EIO; + } + pos += tx_size; + + dprintk("%s: fw_pos=0x%x\n", __func__, pos); + } + i2c_unlock_adapter(state->i2c); + + /* give the DSP a chance to settle 03/10/05 Hac */ + msleep(100); + + return 0; +} + +static int tda1004x_check_upload_ok(struct tda1004x_state *state) +{ + u8 data1, data2; + unsigned long timeout; + + if (state->demod_type == TDA1004X_DEMOD_TDA10046) { + timeout = jiffies + 2 * HZ; + while(!(tda1004x_read_byte(state, TDA1004X_STATUS_CD) & 0x20)) { + if (time_after(jiffies, timeout)) { + printk(KERN_ERR "tda1004x: timeout waiting for DSP ready\n"); + break; + } + msleep(1); + } + } else + msleep(100); + + // check upload was OK + tda1004x_write_mask(state, TDA1004X_CONFC4, 0x10, 0); // we want to read from the DSP + tda1004x_write_byteI(state, TDA1004X_DSP_CMD, 0x67); + + data1 = tda1004x_read_byte(state, TDA1004X_DSP_DATA1); + data2 = tda1004x_read_byte(state, TDA1004X_DSP_DATA2); + if (data1 != 0x67 || data2 < 0x20 || data2 > 0x2e) { + printk(KERN_INFO "tda1004x: found firmware revision %x -- invalid\n", data2); + return -EIO; + } + printk(KERN_INFO "tda1004x: found firmware revision %x -- ok\n", data2); + return 0; +} + +static int tda10045_fwupload(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int ret; + const struct firmware *fw; + + /* don't re-upload unless necessary */ + if (tda1004x_check_upload_ok(state) == 0) + return 0; + + /* request the firmware, this will block until someone uploads it */ + printk(KERN_INFO "tda1004x: waiting for firmware upload (%s)...\n", TDA10045_DEFAULT_FIRMWARE); + ret = state->config->request_firmware(fe, &fw, TDA10045_DEFAULT_FIRMWARE); + if (ret) { + printk(KERN_ERR "tda1004x: no firmware upload (timeout or file not found?)\n"); + return ret; + } + + /* reset chip */ + tda1004x_write_mask(state, TDA1004X_CONFC4, 0x10, 0); + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 8); + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 0); + msleep(10); + + /* set parameters */ + tda10045h_set_bandwidth(state, 8000000); + + ret = tda1004x_do_upload(state, fw->data, fw->size, TDA10045H_FWPAGE, TDA10045H_CODE_IN); + release_firmware(fw); + if (ret) + return ret; + printk(KERN_INFO "tda1004x: firmware upload complete\n"); + + /* wait for DSP to initialise */ + /* DSPREADY doesn't seem to work on the TDA10045H */ + msleep(100); + + return tda1004x_check_upload_ok(state); +} + +static void tda10046_init_plls(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tda10046_clk53m; + + if ((state->config->if_freq == TDA10046_FREQ_045) || + (state->config->if_freq == TDA10046_FREQ_052)) + tda10046_clk53m = 0; + else + tda10046_clk53m = 1; + + tda1004x_write_byteI(state, TDA10046H_CONFPLL1, 0xf0); + if(tda10046_clk53m) { + printk(KERN_INFO "tda1004x: setting up plls for 53MHz sampling clock\n"); + tda1004x_write_byteI(state, TDA10046H_CONFPLL2, 0x08); // PLL M = 8 + } else { + printk(KERN_INFO "tda1004x: setting up plls for 48MHz sampling clock\n"); + tda1004x_write_byteI(state, TDA10046H_CONFPLL2, 0x03); // PLL M = 3 + } + if (state->config->xtal_freq == TDA10046_XTAL_4M ) { + dprintk("%s: setting up PLLs for a 4 MHz Xtal\n", __func__); + tda1004x_write_byteI(state, TDA10046H_CONFPLL3, 0); // PLL P = N = 0 + } else { + dprintk("%s: setting up PLLs for a 16 MHz Xtal\n", __func__); + tda1004x_write_byteI(state, TDA10046H_CONFPLL3, 3); // PLL P = 0, N = 3 + } + if(tda10046_clk53m) + tda1004x_write_byteI(state, TDA10046H_FREQ_OFFSET, 0x67); + else + tda1004x_write_byteI(state, TDA10046H_FREQ_OFFSET, 0x72); + /* Note clock frequency is handled implicitly */ + switch (state->config->if_freq) { + case TDA10046_FREQ_045: + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0c); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x00); + break; + case TDA10046_FREQ_052: + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0d); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0xc7); + break; + case TDA10046_FREQ_3617: + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0xd7); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x59); + break; + case TDA10046_FREQ_3613: + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0xd7); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x3f); + break; + } + tda10046h_set_bandwidth(state, 8000000); /* default bandwidth 8 MHz */ + /* let the PLLs settle */ + msleep(120); +} + +static int tda10046_fwupload(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int ret, confc4; + const struct firmware *fw; + + /* reset + wake up chip */ + if (state->config->xtal_freq == TDA10046_XTAL_4M) { + confc4 = 0; + } else { + dprintk("%s: 16MHz Xtal, reducing I2C speed\n", __func__); + confc4 = 0x80; + } + tda1004x_write_byteI(state, TDA1004X_CONFC4, confc4); + + tda1004x_write_mask(state, TDA10046H_CONF_TRISTATE1, 1, 0); + /* set GPIO 1 and 3 */ + if (state->config->gpio_config != TDA10046_GPTRI) { + tda1004x_write_byteI(state, TDA10046H_CONF_TRISTATE2, 0x33); + tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0x0f, state->config->gpio_config &0x0f); + } + /* let the clocks recover from sleep */ + msleep(10); + + /* The PLLs need to be reprogrammed after sleep */ + tda10046_init_plls(fe); + tda1004x_write_mask(state, TDA1004X_CONFADC2, 0xc0, 0); + + /* don't re-upload unless necessary */ + if (tda1004x_check_upload_ok(state) == 0) + return 0; + + /* + For i2c normal work, we need to slow down the bus speed. + However, the slow down breaks the eeprom firmware load. + So, use normal speed for eeprom booting and then restore the + i2c speed after that. Tested with MSI TV @nyware A/D board, + that comes with firmware version 29 inside their eeprom. + + It should also be noticed that no other I2C transfer should + be in course while booting from eeprom, otherwise, tda10046 + goes into an instable state. So, proper locking are needed + at the i2c bus master. + */ + printk(KERN_INFO "tda1004x: trying to boot from eeprom\n"); + tda1004x_write_byteI(state, TDA1004X_CONFC4, 4); + msleep(300); + tda1004x_write_byteI(state, TDA1004X_CONFC4, confc4); + + /* Checks if eeprom firmware went without troubles */ + if (tda1004x_check_upload_ok(state) == 0) + return 0; + + /* eeprom firmware didn't work. Load one manually. */ + + if (state->config->request_firmware != NULL) { + /* request the firmware, this will block until someone uploads it */ + printk(KERN_INFO "tda1004x: waiting for firmware upload...\n"); + ret = state->config->request_firmware(fe, &fw, TDA10046_DEFAULT_FIRMWARE); + if (ret) { + /* remain compatible to old bug: try to load with tda10045 image name */ + ret = state->config->request_firmware(fe, &fw, TDA10045_DEFAULT_FIRMWARE); + if (ret) { + printk(KERN_ERR "tda1004x: no firmware upload (timeout or file not found?)\n"); + return ret; + } else { + printk(KERN_INFO "tda1004x: please rename the firmware file to %s\n", + TDA10046_DEFAULT_FIRMWARE); + } + } + } else { + printk(KERN_ERR "tda1004x: no request function defined, can't upload from file\n"); + return -EIO; + } + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 8); // going to boot from HOST + ret = tda1004x_do_upload(state, fw->data, fw->size, TDA10046H_CODE_CPT, TDA10046H_CODE_IN); + release_firmware(fw); + return tda1004x_check_upload_ok(state); +} + +static int tda1004x_encode_fec(int fec) +{ + // convert known FEC values + switch (fec) { + case FEC_1_2: + return 0; + case FEC_2_3: + return 1; + case FEC_3_4: + return 2; + case FEC_5_6: + return 3; + case FEC_7_8: + return 4; + } + + // unsupported + return -EINVAL; +} + +static int tda1004x_decode_fec(int tdafec) +{ + // convert known FEC values + switch (tdafec) { + case 0: + return FEC_1_2; + case 1: + return FEC_2_3; + case 2: + return FEC_3_4; + case 3: + return FEC_5_6; + case 4: + return FEC_7_8; + } + + // unsupported + return -1; +} + +static int tda1004x_write(struct dvb_frontend* fe, const u8 buf[], int len) +{ + struct tda1004x_state* state = fe->demodulator_priv; + + if (len != 2) + return -EINVAL; + + return tda1004x_write_byteI(state, buf[0], buf[1]); +} + +static int tda10045_init(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + if (tda10045_fwupload(fe)) { + printk("tda1004x: firmware upload failed\n"); + return -EIO; + } + + tda1004x_write_mask(state, TDA1004X_CONFADC1, 0x10, 0); // wake up the ADC + + // tda setup + tda1004x_write_mask(state, TDA1004X_CONFC4, 0x20, 0); // disable DSP watchdog timer + tda1004x_write_mask(state, TDA1004X_AUTO, 8, 0); // select HP stream + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x40, 0); // set polarity of VAGC signal + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x80, 0x80); // enable pulse killer + tda1004x_write_mask(state, TDA1004X_AUTO, 0x10, 0x10); // enable auto offset + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0xC0, 0x0); // no frequency offset + tda1004x_write_byteI(state, TDA1004X_CONF_TS1, 0); // setup MPEG2 TS interface + tda1004x_write_byteI(state, TDA1004X_CONF_TS2, 0); // setup MPEG2 TS interface + tda1004x_write_mask(state, TDA1004X_VBER_MSB, 0xe0, 0xa0); // 10^6 VBER measurement bits + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x10, 0); // VAGC polarity + tda1004x_write_byteI(state, TDA1004X_CONFADC1, 0x2e); + + tda1004x_write_mask(state, 0x1f, 0x01, state->config->invert_oclk); + + return 0; +} + +static int tda10046_init(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + dprintk("%s\n", __func__); + + if (tda10046_fwupload(fe)) { + printk("tda1004x: firmware upload failed\n"); + return -EIO; + } + + // tda setup + tda1004x_write_mask(state, TDA1004X_CONFC4, 0x20, 0); // disable DSP watchdog timer + tda1004x_write_byteI(state, TDA1004X_AUTO, 0x87); // 100 ppm crystal, select HP stream + tda1004x_write_byteI(state, TDA1004X_CONFC1, 0x88); // enable pulse killer + + switch (state->config->agc_config) { + case TDA10046_AGC_DEFAULT: + tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x00); // AGC setup + tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0xf0, 0x60); // set AGC polarities + break; + case TDA10046_AGC_IFO_AUTO_NEG: + tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x0a); // AGC setup + tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0xf0, 0x60); // set AGC polarities + break; + case TDA10046_AGC_IFO_AUTO_POS: + tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x0a); // AGC setup + tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0xf0, 0x00); // set AGC polarities + break; + case TDA10046_AGC_TDA827X: + tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x02); // AGC setup + tda1004x_write_byteI(state, TDA10046H_AGC_THR, 0x70); // AGC Threshold + tda1004x_write_byteI(state, TDA10046H_AGC_RENORM, 0x08); // Gain Renormalize + tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0xf0, 0x60); // set AGC polarities + break; + } + if (state->config->ts_mode == 0) { + tda1004x_write_mask(state, TDA10046H_CONF_TRISTATE1, 0xc0, 0x40); + tda1004x_write_mask(state, 0x3a, 0x80, state->config->invert_oclk << 7); + } else { + tda1004x_write_mask(state, TDA10046H_CONF_TRISTATE1, 0xc0, 0x80); + tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0x10, + state->config->invert_oclk << 4); + } + tda1004x_write_byteI(state, TDA1004X_CONFADC2, 0x38); + tda1004x_write_mask (state, TDA10046H_CONF_TRISTATE1, 0x3e, 0x38); // Turn IF AGC output on + tda1004x_write_byteI(state, TDA10046H_AGC_TUN_MIN, 0); // } + tda1004x_write_byteI(state, TDA10046H_AGC_TUN_MAX, 0xff); // } AGC min/max values + tda1004x_write_byteI(state, TDA10046H_AGC_IF_MIN, 0); // } + tda1004x_write_byteI(state, TDA10046H_AGC_IF_MAX, 0xff); // } + tda1004x_write_byteI(state, TDA10046H_AGC_GAINS, 0x12); // IF gain 2, TUN gain 1 + tda1004x_write_byteI(state, TDA10046H_CVBER_CTRL, 0x1a); // 10^6 VBER measurement bits + tda1004x_write_byteI(state, TDA1004X_CONF_TS1, 7); // MPEG2 interface config + tda1004x_write_byteI(state, TDA1004X_CONF_TS2, 0xc0); // MPEG2 interface config + // tda1004x_write_mask(state, 0x50, 0x80, 0x80); // handle out of guard echoes + + return 0; +} + +static int tda1004x_set_fe(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *fe_params = &fe->dtv_property_cache; + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + int inversion; + + dprintk("%s\n", __func__); + + if (state->demod_type == TDA1004X_DEMOD_TDA10046) { + // setup auto offset + tda1004x_write_mask(state, TDA1004X_AUTO, 0x10, 0x10); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x80, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0xC0, 0); + + // disable agc_conf[2] + tda1004x_write_mask(state, TDA10046H_AGC_CONF, 4, 0); + } + + // set frequency + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + // Hardcoded to use auto as much as possible on the TDA10045 as it + // is very unreliable if AUTO mode is _not_ used. + if (state->demod_type == TDA1004X_DEMOD_TDA10045) { + fe_params->code_rate_HP = FEC_AUTO; + fe_params->guard_interval = GUARD_INTERVAL_AUTO; + fe_params->transmission_mode = TRANSMISSION_MODE_AUTO; + } + + // Set standard params.. or put them to auto + if ((fe_params->code_rate_HP == FEC_AUTO) || + (fe_params->code_rate_LP == FEC_AUTO) || + (fe_params->modulation == QAM_AUTO) || + (fe_params->hierarchy == HIERARCHY_AUTO)) { + tda1004x_write_mask(state, TDA1004X_AUTO, 1, 1); // enable auto + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x03, 0); /* turn off modulation bits */ + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 0); // turn off hierarchy bits + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0x3f, 0); // turn off FEC bits + } else { + tda1004x_write_mask(state, TDA1004X_AUTO, 1, 0); // disable auto + + // set HP FEC + tmp = tda1004x_encode_fec(fe_params->code_rate_HP); + if (tmp < 0) + return tmp; + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 7, tmp); + + // set LP FEC + tmp = tda1004x_encode_fec(fe_params->code_rate_LP); + if (tmp < 0) + return tmp; + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0x38, tmp << 3); + + /* set modulation */ + switch (fe_params->modulation) { + case QPSK: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 3, 0); + break; + + case QAM_16: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 3, 1); + break; + + case QAM_64: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 3, 2); + break; + + default: + return -EINVAL; + } + + // set hierarchy + switch (fe_params->hierarchy) { + case HIERARCHY_NONE: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 0 << 5); + break; + + case HIERARCHY_1: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 1 << 5); + break; + + case HIERARCHY_2: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 2 << 5); + break; + + case HIERARCHY_4: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 3 << 5); + break; + + default: + return -EINVAL; + } + } + + // set bandwidth + switch (state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + tda10045h_set_bandwidth(state, fe_params->bandwidth_hz); + break; + + case TDA1004X_DEMOD_TDA10046: + tda10046h_set_bandwidth(state, fe_params->bandwidth_hz); + break; + } + + // set inversion + inversion = fe_params->inversion; + if (state->config->invert) + inversion = inversion ? INVERSION_OFF : INVERSION_ON; + switch (inversion) { + case INVERSION_OFF: + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x20, 0); + break; + + case INVERSION_ON: + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x20, 0x20); + break; + + default: + return -EINVAL; + } + + // set guard interval + switch (fe_params->guard_interval) { + case GUARD_INTERVAL_1_32: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 0 << 2); + break; + + case GUARD_INTERVAL_1_16: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 1 << 2); + break; + + case GUARD_INTERVAL_1_8: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 2 << 2); + break; + + case GUARD_INTERVAL_1_4: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 3 << 2); + break; + + case GUARD_INTERVAL_AUTO: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 2); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 0 << 2); + break; + + default: + return -EINVAL; + } + + // set transmission mode + switch (fe_params->transmission_mode) { + case TRANSMISSION_MODE_2K: + tda1004x_write_mask(state, TDA1004X_AUTO, 4, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x10, 0 << 4); + break; + + case TRANSMISSION_MODE_8K: + tda1004x_write_mask(state, TDA1004X_AUTO, 4, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x10, 1 << 4); + break; + + case TRANSMISSION_MODE_AUTO: + tda1004x_write_mask(state, TDA1004X_AUTO, 4, 4); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x10, 0); + break; + + default: + return -EINVAL; + } + + // start the lock + switch (state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 8); + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 0); + break; + + case TDA1004X_DEMOD_TDA10046: + tda1004x_write_mask(state, TDA1004X_AUTO, 0x40, 0x40); + msleep(1); + tda1004x_write_mask(state, TDA10046H_AGC_CONF, 4, 1); + break; + } + + msleep(10); + + return 0; +} + +static int tda1004x_get_fe(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *fe_params = &fe->dtv_property_cache; + struct tda1004x_state* state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + // inversion status + fe_params->inversion = INVERSION_OFF; + if (tda1004x_read_byte(state, TDA1004X_CONFC1) & 0x20) + fe_params->inversion = INVERSION_ON; + if (state->config->invert) + fe_params->inversion = fe_params->inversion ? INVERSION_OFF : INVERSION_ON; + + // bandwidth + switch (state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + switch (tda1004x_read_byte(state, TDA10045H_WREF_LSB)) { + case 0x14: + fe_params->bandwidth_hz = 8000000; + break; + case 0xdb: + fe_params->bandwidth_hz = 7000000; + break; + case 0x4f: + fe_params->bandwidth_hz = 6000000; + break; + } + break; + case TDA1004X_DEMOD_TDA10046: + switch (tda1004x_read_byte(state, TDA10046H_TIME_WREF1)) { + case 0x5c: + case 0x54: + fe_params->bandwidth_hz = 8000000; + break; + case 0x6a: + case 0x60: + fe_params->bandwidth_hz = 7000000; + break; + case 0x7b: + case 0x70: + fe_params->bandwidth_hz = 6000000; + break; + } + break; + } + + // FEC + fe_params->code_rate_HP = + tda1004x_decode_fec(tda1004x_read_byte(state, TDA1004X_OUT_CONF2) & 7); + fe_params->code_rate_LP = + tda1004x_decode_fec((tda1004x_read_byte(state, TDA1004X_OUT_CONF2) >> 3) & 7); + + /* modulation */ + switch (tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 3) { + case 0: + fe_params->modulation = QPSK; + break; + case 1: + fe_params->modulation = QAM_16; + break; + case 2: + fe_params->modulation = QAM_64; + break; + } + + // transmission mode + fe_params->transmission_mode = TRANSMISSION_MODE_2K; + if (tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 0x10) + fe_params->transmission_mode = TRANSMISSION_MODE_8K; + + // guard interval + switch ((tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 0x0c) >> 2) { + case 0: + fe_params->guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + fe_params->guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + fe_params->guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + fe_params->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + // hierarchy + switch ((tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 0x60) >> 5) { + case 0: + fe_params->hierarchy = HIERARCHY_NONE; + break; + case 1: + fe_params->hierarchy = HIERARCHY_1; + break; + case 2: + fe_params->hierarchy = HIERARCHY_2; + break; + case 3: + fe_params->hierarchy = HIERARCHY_4; + break; + } + + return 0; +} + +static int tda1004x_read_status(struct dvb_frontend* fe, fe_status_t * fe_status) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int status; + int cber; + int vber; + + dprintk("%s\n", __func__); + + // read status + status = tda1004x_read_byte(state, TDA1004X_STATUS_CD); + if (status == -1) + return -EIO; + + // decode + *fe_status = 0; + if (status & 4) + *fe_status |= FE_HAS_SIGNAL; + if (status & 2) + *fe_status |= FE_HAS_CARRIER; + if (status & 8) + *fe_status |= FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + + // if we don't already have VITERBI (i.e. not LOCKED), see if the viterbi + // is getting anything valid + if (!(*fe_status & FE_HAS_VITERBI)) { + // read the CBER + cber = tda1004x_read_byte(state, TDA1004X_CBER_LSB); + if (cber == -1) + return -EIO; + status = tda1004x_read_byte(state, TDA1004X_CBER_MSB); + if (status == -1) + return -EIO; + cber |= (status << 8); + // The address 0x20 should be read to cope with a TDA10046 bug + tda1004x_read_byte(state, TDA1004X_CBER_RESET); + + if (cber != 65535) + *fe_status |= FE_HAS_VITERBI; + } + + // if we DO have some valid VITERBI output, but don't already have SYNC + // bytes (i.e. not LOCKED), see if the RS decoder is getting anything valid. + if ((*fe_status & FE_HAS_VITERBI) && (!(*fe_status & FE_HAS_SYNC))) { + // read the VBER + vber = tda1004x_read_byte(state, TDA1004X_VBER_LSB); + if (vber == -1) + return -EIO; + status = tda1004x_read_byte(state, TDA1004X_VBER_MID); + if (status == -1) + return -EIO; + vber |= (status << 8); + status = tda1004x_read_byte(state, TDA1004X_VBER_MSB); + if (status == -1) + return -EIO; + vber |= (status & 0x0f) << 16; + // The CVBER_LUT should be read to cope with TDA10046 hardware bug + tda1004x_read_byte(state, TDA1004X_CVBER_LUT); + + // if RS has passed some valid TS packets, then we must be + // getting some SYNC bytes + if (vber < 16632) + *fe_status |= FE_HAS_SYNC; + } + + // success + dprintk("%s: fe_status=0x%x\n", __func__, *fe_status); + return 0; +} + +static int tda1004x_read_signal_strength(struct dvb_frontend* fe, u16 * signal) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + int reg = 0; + + dprintk("%s\n", __func__); + + // determine the register to use + switch (state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + reg = TDA10045H_S_AGC; + break; + + case TDA1004X_DEMOD_TDA10046: + reg = TDA10046H_AGC_IF_LEVEL; + break; + } + + // read it + tmp = tda1004x_read_byte(state, reg); + if (tmp < 0) + return -EIO; + + *signal = (tmp << 8) | tmp; + dprintk("%s: signal=0x%x\n", __func__, *signal); + return 0; +} + +static int tda1004x_read_snr(struct dvb_frontend* fe, u16 * snr) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + + dprintk("%s\n", __func__); + + // read it + tmp = tda1004x_read_byte(state, TDA1004X_SNR); + if (tmp < 0) + return -EIO; + tmp = 255 - tmp; + + *snr = ((tmp << 8) | tmp); + dprintk("%s: snr=0x%x\n", __func__, *snr); + return 0; +} + +static int tda1004x_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + int tmp2; + int counter; + + dprintk("%s\n", __func__); + + // read the UCBLOCKS and reset + counter = 0; + tmp = tda1004x_read_byte(state, TDA1004X_UNCOR); + if (tmp < 0) + return -EIO; + tmp &= 0x7f; + while (counter++ < 5) { + tda1004x_write_mask(state, TDA1004X_UNCOR, 0x80, 0); + tda1004x_write_mask(state, TDA1004X_UNCOR, 0x80, 0); + tda1004x_write_mask(state, TDA1004X_UNCOR, 0x80, 0); + + tmp2 = tda1004x_read_byte(state, TDA1004X_UNCOR); + if (tmp2 < 0) + return -EIO; + tmp2 &= 0x7f; + if ((tmp2 < tmp) || (tmp2 == 0)) + break; + } + + if (tmp != 0x7f) + *ucblocks = tmp; + else + *ucblocks = 0xffffffff; + + dprintk("%s: ucblocks=0x%x\n", __func__, *ucblocks); + return 0; +} + +static int tda1004x_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + + dprintk("%s\n", __func__); + + // read it in + tmp = tda1004x_read_byte(state, TDA1004X_CBER_LSB); + if (tmp < 0) + return -EIO; + *ber = tmp << 1; + tmp = tda1004x_read_byte(state, TDA1004X_CBER_MSB); + if (tmp < 0) + return -EIO; + *ber |= (tmp << 9); + // The address 0x20 should be read to cope with a TDA10046 bug + tda1004x_read_byte(state, TDA1004X_CBER_RESET); + + dprintk("%s: ber=0x%x\n", __func__, *ber); + return 0; +} + +static int tda1004x_sleep(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int gpio_conf; + + switch (state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + tda1004x_write_mask(state, TDA1004X_CONFADC1, 0x10, 0x10); + break; + + case TDA1004X_DEMOD_TDA10046: + /* set outputs to tristate */ + tda1004x_write_byteI(state, TDA10046H_CONF_TRISTATE1, 0xff); + /* invert GPIO 1 and 3 if desired*/ + gpio_conf = state->config->gpio_config; + if (gpio_conf >= TDA10046_GP00_I) + tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0x0f, + (gpio_conf & 0x0f) ^ 0x0a); + + tda1004x_write_mask(state, TDA1004X_CONFADC2, 0xc0, 0xc0); + tda1004x_write_mask(state, TDA1004X_CONFC4, 1, 1); + break; + } + + return 0; +} + +static int tda1004x_i2c_gate_ctrl(struct dvb_frontend* fe, int enable) +{ + struct tda1004x_state* state = fe->demodulator_priv; + + if (enable) { + return tda1004x_enable_tuner_i2c(state); + } else { + return tda1004x_disable_tuner_i2c(state); + } +} + +static int tda1004x_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 800; + /* Drift compensation makes no sense for DVB-T */ + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static void tda1004x_release(struct dvb_frontend* fe) +{ + struct tda1004x_state *state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops tda10045_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "Philips TDA10045H DVB-T", + .frequency_min = 51000000, + .frequency_max = 858000000, + .frequency_stepsize = 166667, + .caps = + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO + }, + + .release = tda1004x_release, + + .init = tda10045_init, + .sleep = tda1004x_sleep, + .write = tda1004x_write, + .i2c_gate_ctrl = tda1004x_i2c_gate_ctrl, + + .set_frontend = tda1004x_set_fe, + .get_frontend = tda1004x_get_fe, + .get_tune_settings = tda1004x_get_tune_settings, + + .read_status = tda1004x_read_status, + .read_ber = tda1004x_read_ber, + .read_signal_strength = tda1004x_read_signal_strength, + .read_snr = tda1004x_read_snr, + .read_ucblocks = tda1004x_read_ucblocks, +}; + +struct dvb_frontend* tda10045_attach(const struct tda1004x_config* config, + struct i2c_adapter* i2c) +{ + struct tda1004x_state *state; + int id; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct tda1004x_state), GFP_KERNEL); + if (!state) { + printk(KERN_ERR "Can't allocate memory for tda10045 state\n"); + return NULL; + } + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->demod_type = TDA1004X_DEMOD_TDA10045; + + /* check if the demod is there */ + id = tda1004x_read_byte(state, TDA1004X_CHIPID); + if (id < 0) { + printk(KERN_ERR "tda10045: chip is not answering. Giving up.\n"); + kfree(state); + return NULL; + } + + if (id != 0x25) { + printk(KERN_ERR "Invalid tda1004x ID = 0x%02x. Can't proceed\n", id); + kfree(state); + return NULL; + } + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &tda10045_ops, sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; +} + +static struct dvb_frontend_ops tda10046_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "Philips TDA10046H DVB-T", + .frequency_min = 51000000, + .frequency_max = 858000000, + .frequency_stepsize = 166667, + .caps = + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO + }, + + .release = tda1004x_release, + + .init = tda10046_init, + .sleep = tda1004x_sleep, + .write = tda1004x_write, + .i2c_gate_ctrl = tda1004x_i2c_gate_ctrl, + + .set_frontend = tda1004x_set_fe, + .get_frontend = tda1004x_get_fe, + .get_tune_settings = tda1004x_get_tune_settings, + + .read_status = tda1004x_read_status, + .read_ber = tda1004x_read_ber, + .read_signal_strength = tda1004x_read_signal_strength, + .read_snr = tda1004x_read_snr, + .read_ucblocks = tda1004x_read_ucblocks, +}; + +struct dvb_frontend* tda10046_attach(const struct tda1004x_config* config, + struct i2c_adapter* i2c) +{ + struct tda1004x_state *state; + int id; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct tda1004x_state), GFP_KERNEL); + if (!state) { + printk(KERN_ERR "Can't allocate memory for tda10046 state\n"); + return NULL; + } + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->demod_type = TDA1004X_DEMOD_TDA10046; + + /* check if the demod is there */ + id = tda1004x_read_byte(state, TDA1004X_CHIPID); + if (id < 0) { + printk(KERN_ERR "tda10046: chip is not answering. Giving up.\n"); + kfree(state); + return NULL; + } + if (id != 0x46) { + printk(KERN_ERR "Invalid tda1004x ID = 0x%02x. Can't proceed\n", id); + kfree(state); + return NULL; + } + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &tda10046_ops, sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; +} + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Philips TDA10045H & TDA10046H DVB-T Demodulator"); +MODULE_AUTHOR("Andrew de Quincey & Robert Schlabbach"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(tda10045_attach); +EXPORT_SYMBOL(tda10046_attach); diff --git a/drivers/media/dvb/frontends/tda1004x.h b/drivers/media/dvb-frontends/tda1004x.h similarity index 100% rename from drivers/media/dvb/frontends/tda1004x.h rename to drivers/media/dvb-frontends/tda1004x.h diff --git a/drivers/media/dvb-frontends/tda10071.c b/drivers/media/dvb-frontends/tda10071.c new file mode 100644 index 000000000000..a83bf6802345 --- /dev/null +++ b/drivers/media/dvb-frontends/tda10071.c @@ -0,0 +1,1285 @@ +/* + * NXP TDA10071 + Conexant CX24118A DVB-S/S2 demodulator + tuner driver + * + * Copyright (C) 2011 Antti Palosaari + * + * 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. + */ + +#include "tda10071_priv.h" + +static struct dvb_frontend_ops tda10071_ops; + +/* write multiple registers */ +static int tda10071_wr_regs(struct tda10071_priv *priv, u8 reg, u8 *val, + int len) +{ + int ret; + u8 buf[len+1]; + struct i2c_msg msg[1] = { + { + .addr = priv->cfg.i2c_address, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + } + }; + + buf[0] = reg; + memcpy(&buf[1], val, len); + + ret = i2c_transfer(priv->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c wr failed=%d reg=%02x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* read multiple registers */ +static int tda10071_rd_regs(struct tda10071_priv *priv, u8 reg, u8 *val, + int len) +{ + int ret; + u8 buf[len]; + struct i2c_msg msg[2] = { + { + .addr = priv->cfg.i2c_address, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = priv->cfg.i2c_address, + .flags = I2C_M_RD, + .len = sizeof(buf), + .buf = buf, + } + }; + + ret = i2c_transfer(priv->i2c, msg, 2); + if (ret == 2) { + memcpy(val, buf, len); + ret = 0; + } else { + dev_warn(&priv->i2c->dev, "%s: i2c rd failed=%d reg=%02x " \ + "len=%d\n", KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* write single register */ +static int tda10071_wr_reg(struct tda10071_priv *priv, u8 reg, u8 val) +{ + return tda10071_wr_regs(priv, reg, &val, 1); +} + +/* read single register */ +static int tda10071_rd_reg(struct tda10071_priv *priv, u8 reg, u8 *val) +{ + return tda10071_rd_regs(priv, reg, val, 1); +} + +/* write single register with mask */ +int tda10071_wr_reg_mask(struct tda10071_priv *priv, u8 reg, u8 val, u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = tda10071_rd_regs(priv, reg, &tmp, 1); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return tda10071_wr_regs(priv, reg, &val, 1); +} + +/* read single register with mask */ +int tda10071_rd_reg_mask(struct tda10071_priv *priv, u8 reg, u8 *val, u8 mask) +{ + int ret, i; + u8 tmp; + + ret = tda10071_rd_regs(priv, reg, &tmp, 1); + if (ret) + return ret; + + tmp &= mask; + + /* find position of the first bit */ + for (i = 0; i < 8; i++) { + if ((mask >> i) & 0x01) + break; + } + *val = tmp >> i; + + return 0; +} + +/* execute firmware command */ +static int tda10071_cmd_execute(struct tda10071_priv *priv, + struct tda10071_cmd *cmd) +{ + int ret, i; + u8 tmp; + + if (!priv->warm) { + ret = -EFAULT; + goto error; + } + + /* write cmd and args for firmware */ + ret = tda10071_wr_regs(priv, 0x00, cmd->args, cmd->len); + if (ret) + goto error; + + /* start cmd execution */ + ret = tda10071_wr_reg(priv, 0x1f, 1); + if (ret) + goto error; + + /* wait cmd execution terminate */ + for (i = 1000, tmp = 1; i && tmp; i--) { + ret = tda10071_rd_reg(priv, 0x1f, &tmp); + if (ret) + goto error; + + usleep_range(200, 5000); + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + if (i == 0) { + ret = -ETIMEDOUT; + goto error; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_set_tone(struct dvb_frontend *fe, + fe_sec_tone_mode_t fe_sec_tone_mode) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + int ret; + u8 tone; + + if (!priv->warm) { + ret = -EFAULT; + goto error; + } + + dev_dbg(&priv->i2c->dev, "%s: tone_mode=%d\n", __func__, + fe_sec_tone_mode); + + switch (fe_sec_tone_mode) { + case SEC_TONE_ON: + tone = 1; + break; + case SEC_TONE_OFF: + tone = 0; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_tone_mode\n", + __func__); + ret = -EINVAL; + goto error; + } + + cmd.args[0] = CMD_LNB_PCB_CONFIG; + cmd.args[1] = 0; + cmd.args[2] = 0x00; + cmd.args[3] = 0x00; + cmd.args[4] = tone; + cmd.len = 5; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t fe_sec_voltage) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + int ret; + u8 voltage; + + if (!priv->warm) { + ret = -EFAULT; + goto error; + } + + dev_dbg(&priv->i2c->dev, "%s: voltage=%d\n", __func__, fe_sec_voltage); + + switch (fe_sec_voltage) { + case SEC_VOLTAGE_13: + voltage = 0; + break; + case SEC_VOLTAGE_18: + voltage = 1; + break; + case SEC_VOLTAGE_OFF: + voltage = 0; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_voltage\n", + __func__); + ret = -EINVAL; + goto error; + } + + cmd.args[0] = CMD_LNB_SET_DC_LEVEL; + cmd.args[1] = 0; + cmd.args[2] = voltage; + cmd.len = 3; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_diseqc_send_master_cmd(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *diseqc_cmd) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + int ret, i; + u8 tmp; + + if (!priv->warm) { + ret = -EFAULT; + goto error; + } + + dev_dbg(&priv->i2c->dev, "%s: msg_len=%d\n", __func__, + diseqc_cmd->msg_len); + + if (diseqc_cmd->msg_len < 3 || diseqc_cmd->msg_len > 6) { + ret = -EINVAL; + goto error; + } + + /* wait LNB TX */ + for (i = 500, tmp = 0; i && !tmp; i--) { + ret = tda10071_rd_reg_mask(priv, 0x47, &tmp, 0x01); + if (ret) + goto error; + + usleep_range(10000, 20000); + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + if (i == 0) { + ret = -ETIMEDOUT; + goto error; + } + + ret = tda10071_wr_reg_mask(priv, 0x47, 0x00, 0x01); + if (ret) + goto error; + + cmd.args[0] = CMD_LNB_SEND_DISEQC; + cmd.args[1] = 0; + cmd.args[2] = 0; + cmd.args[3] = 0; + cmd.args[4] = 2; + cmd.args[5] = 0; + cmd.args[6] = diseqc_cmd->msg_len; + memcpy(&cmd.args[7], diseqc_cmd->msg, diseqc_cmd->msg_len); + cmd.len = 7 + diseqc_cmd->msg_len; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_diseqc_recv_slave_reply(struct dvb_frontend *fe, + struct dvb_diseqc_slave_reply *reply) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + int ret, i; + u8 tmp; + + if (!priv->warm) { + ret = -EFAULT; + goto error; + } + + dev_dbg(&priv->i2c->dev, "%s:\n", __func__); + + /* wait LNB RX */ + for (i = 500, tmp = 0; i && !tmp; i--) { + ret = tda10071_rd_reg_mask(priv, 0x47, &tmp, 0x02); + if (ret) + goto error; + + usleep_range(10000, 20000); + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + if (i == 0) { + ret = -ETIMEDOUT; + goto error; + } + + /* reply len */ + ret = tda10071_rd_reg(priv, 0x46, &tmp); + if (ret) + goto error; + + reply->msg_len = tmp & 0x1f; /* [4:0] */ + if (reply->msg_len > sizeof(reply->msg)) + reply->msg_len = sizeof(reply->msg); /* truncate API max */ + + /* read reply */ + cmd.args[0] = CMD_LNB_UPDATE_REPLY; + cmd.args[1] = 0; + cmd.len = 2; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + ret = tda10071_rd_regs(priv, cmd.len, reply->msg, reply->msg_len); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_diseqc_send_burst(struct dvb_frontend *fe, + fe_sec_mini_cmd_t fe_sec_mini_cmd) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + int ret, i; + u8 tmp, burst; + + if (!priv->warm) { + ret = -EFAULT; + goto error; + } + + dev_dbg(&priv->i2c->dev, "%s: fe_sec_mini_cmd=%d\n", __func__, + fe_sec_mini_cmd); + + switch (fe_sec_mini_cmd) { + case SEC_MINI_A: + burst = 0; + break; + case SEC_MINI_B: + burst = 1; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_mini_cmd\n", + __func__); + ret = -EINVAL; + goto error; + } + + /* wait LNB TX */ + for (i = 500, tmp = 0; i && !tmp; i--) { + ret = tda10071_rd_reg_mask(priv, 0x47, &tmp, 0x01); + if (ret) + goto error; + + usleep_range(10000, 20000); + } + + dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); + + if (i == 0) { + ret = -ETIMEDOUT; + goto error; + } + + ret = tda10071_wr_reg_mask(priv, 0x47, 0x00, 0x01); + if (ret) + goto error; + + cmd.args[0] = CMD_LNB_SEND_TONEBURST; + cmd.args[1] = 0; + cmd.args[2] = burst; + cmd.len = 3; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + int ret; + u8 tmp; + + *status = 0; + + if (!priv->warm) { + ret = 0; + goto error; + } + + ret = tda10071_rd_reg(priv, 0x39, &tmp); + if (ret) + goto error; + + if (tmp & 0x01) /* tuner PLL */ + *status |= FE_HAS_SIGNAL; + if (tmp & 0x02) /* demod PLL */ + *status |= FE_HAS_CARRIER; + if (tmp & 0x04) /* viterbi or LDPC*/ + *status |= FE_HAS_VITERBI; + if (tmp & 0x08) /* RS or BCH */ + *status |= FE_HAS_SYNC | FE_HAS_LOCK; + + priv->fe_status = *status; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + int ret; + u8 buf[2]; + + if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { + *snr = 0; + ret = 0; + goto error; + } + + ret = tda10071_rd_regs(priv, 0x3a, buf, 2); + if (ret) + goto error; + + /* Es/No dBx10 */ + *snr = buf[0] << 8 | buf[1]; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + int ret; + u8 tmp; + + if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { + *strength = 0; + ret = 0; + goto error; + } + + cmd.args[0] = CMD_GET_AGCACC; + cmd.args[1] = 0; + cmd.len = 2; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + /* input power estimate dBm */ + ret = tda10071_rd_reg(priv, 0x50, &tmp); + if (ret) + goto error; + + if (tmp < 181) + tmp = 181; /* -75 dBm */ + else if (tmp > 236) + tmp = 236; /* -20 dBm */ + + /* scale value to 0x0000-0xffff */ + *strength = (tmp-181) * 0xffff / (236-181); + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + int ret, i, len; + u8 tmp, reg, buf[8]; + + if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { + *ber = priv->ber = 0; + ret = 0; + goto error; + } + + switch (priv->delivery_system) { + case SYS_DVBS: + reg = 0x4c; + len = 8; + i = 1; + break; + case SYS_DVBS2: + reg = 0x4d; + len = 4; + i = 0; + break; + default: + *ber = priv->ber = 0; + return 0; + } + + ret = tda10071_rd_reg(priv, reg, &tmp); + if (ret) + goto error; + + if (priv->meas_count[i] == tmp) { + dev_dbg(&priv->i2c->dev, "%s: meas not ready=%02x\n", __func__, + tmp); + *ber = priv->ber; + return 0; + } else { + priv->meas_count[i] = tmp; + } + + cmd.args[0] = CMD_BER_UPDATE_COUNTERS; + cmd.args[1] = 0; + cmd.args[2] = i; + cmd.len = 3; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + ret = tda10071_rd_regs(priv, cmd.len, buf, len); + if (ret) + goto error; + + if (priv->delivery_system == SYS_DVBS) { + *ber = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + priv->ucb += (buf[4] << 8) | buf[5]; + } else { + *ber = (buf[0] << 8) | buf[1]; + } + priv->ber = *ber; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + int ret = 0; + + if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { + *ucblocks = 0; + goto error; + } + + /* UCB is updated when BER is read. Assume BER is read anyway. */ + + *ucblocks = priv->ucb; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_set_frontend(struct dvb_frontend *fe) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i; + u8 mode, rolloff, pilot, inversion, div; + + dev_dbg(&priv->i2c->dev, "%s: delivery_system=%d modulation=%d " \ + "frequency=%d symbol_rate=%d inversion=%d pilot=%d " \ + "rolloff=%d\n", __func__, c->delivery_system, c->modulation, + c->frequency, c->symbol_rate, c->inversion, c->pilot, + c->rolloff); + + priv->delivery_system = SYS_UNDEFINED; + + if (!priv->warm) { + ret = -EFAULT; + goto error; + } + + switch (c->inversion) { + case INVERSION_OFF: + inversion = 1; + break; + case INVERSION_ON: + inversion = 0; + break; + case INVERSION_AUTO: + /* 2 = auto; try first on then off + * 3 = auto; try first off then on */ + inversion = 3; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n", __func__); + ret = -EINVAL; + goto error; + } + + switch (c->delivery_system) { + case SYS_DVBS: + rolloff = 0; + pilot = 2; + break; + case SYS_DVBS2: + switch (c->rolloff) { + case ROLLOFF_20: + rolloff = 2; + break; + case ROLLOFF_25: + rolloff = 1; + break; + case ROLLOFF_35: + rolloff = 0; + break; + case ROLLOFF_AUTO: + default: + dev_dbg(&priv->i2c->dev, "%s: invalid rolloff\n", + __func__); + ret = -EINVAL; + goto error; + } + + switch (c->pilot) { + case PILOT_OFF: + pilot = 0; + break; + case PILOT_ON: + pilot = 1; + break; + case PILOT_AUTO: + pilot = 2; + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid pilot\n", + __func__); + ret = -EINVAL; + goto error; + } + break; + default: + dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", + __func__); + ret = -EINVAL; + goto error; + } + + for (i = 0, mode = 0xff; i < ARRAY_SIZE(TDA10071_MODCOD); i++) { + if (c->delivery_system == TDA10071_MODCOD[i].delivery_system && + c->modulation == TDA10071_MODCOD[i].modulation && + c->fec_inner == TDA10071_MODCOD[i].fec) { + mode = TDA10071_MODCOD[i].val; + dev_dbg(&priv->i2c->dev, "%s: mode found=%02x\n", + __func__, mode); + break; + } + } + + if (mode == 0xff) { + dev_dbg(&priv->i2c->dev, "%s: invalid parameter combination\n", + __func__); + ret = -EINVAL; + goto error; + } + + if (c->symbol_rate <= 5000000) + div = 14; + else + div = 4; + + ret = tda10071_wr_reg(priv, 0x81, div); + if (ret) + goto error; + + ret = tda10071_wr_reg(priv, 0xe3, div); + if (ret) + goto error; + + cmd.args[0] = CMD_CHANGE_CHANNEL; + cmd.args[1] = 0; + cmd.args[2] = mode; + cmd.args[3] = (c->frequency >> 16) & 0xff; + cmd.args[4] = (c->frequency >> 8) & 0xff; + cmd.args[5] = (c->frequency >> 0) & 0xff; + cmd.args[6] = ((c->symbol_rate / 1000) >> 8) & 0xff; + cmd.args[7] = ((c->symbol_rate / 1000) >> 0) & 0xff; + cmd.args[8] = (tda10071_ops.info.frequency_tolerance >> 8) & 0xff; + cmd.args[9] = (tda10071_ops.info.frequency_tolerance >> 0) & 0xff; + cmd.args[10] = rolloff; + cmd.args[11] = inversion; + cmd.args[12] = pilot; + cmd.args[13] = 0x00; + cmd.args[14] = 0x00; + cmd.len = 15; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + priv->delivery_system = c->delivery_system; + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_get_frontend(struct dvb_frontend *fe) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i; + u8 buf[5], tmp; + + if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { + ret = -EFAULT; + goto error; + } + + ret = tda10071_rd_regs(priv, 0x30, buf, 5); + if (ret) + goto error; + + tmp = buf[0] & 0x3f; + for (i = 0; i < ARRAY_SIZE(TDA10071_MODCOD); i++) { + if (tmp == TDA10071_MODCOD[i].val) { + c->modulation = TDA10071_MODCOD[i].modulation; + c->fec_inner = TDA10071_MODCOD[i].fec; + c->delivery_system = TDA10071_MODCOD[i].delivery_system; + } + } + + switch ((buf[1] >> 0) & 0x01) { + case 0: + c->inversion = INVERSION_OFF; + break; + case 1: + c->inversion = INVERSION_ON; + break; + } + + switch ((buf[1] >> 7) & 0x01) { + case 0: + c->pilot = PILOT_OFF; + break; + case 1: + c->pilot = PILOT_ON; + break; + } + + c->frequency = (buf[2] << 16) | (buf[3] << 8) | (buf[4] << 0); + + ret = tda10071_rd_regs(priv, 0x52, buf, 3); + if (ret) + goto error; + + c->symbol_rate = (buf[0] << 16) | (buf[1] << 8) | (buf[2] << 0); + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_init(struct dvb_frontend *fe) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + int ret, i, len, remaining, fw_size; + const struct firmware *fw; + u8 *fw_file = TDA10071_FIRMWARE; + u8 tmp, buf[4]; + struct tda10071_reg_val_mask tab[] = { + { 0xcd, 0x00, 0x07 }, + { 0x80, 0x00, 0x02 }, + { 0xcd, 0x00, 0xc0 }, + { 0xce, 0x00, 0x1b }, + { 0x9d, 0x00, 0x01 }, + { 0x9d, 0x00, 0x02 }, + { 0x9e, 0x00, 0x01 }, + { 0x87, 0x00, 0x80 }, + { 0xce, 0x00, 0x08 }, + { 0xce, 0x00, 0x10 }, + }; + struct tda10071_reg_val_mask tab2[] = { + { 0xf1, 0x70, 0xff }, + { 0x88, priv->cfg.pll_multiplier, 0x3f }, + { 0x89, 0x00, 0x10 }, + { 0x89, 0x10, 0x10 }, + { 0xc0, 0x01, 0x01 }, + { 0xc0, 0x00, 0x01 }, + { 0xe0, 0xff, 0xff }, + { 0xe0, 0x00, 0xff }, + { 0x96, 0x1e, 0x7e }, + { 0x8b, 0x08, 0x08 }, + { 0x8b, 0x00, 0x08 }, + { 0x8f, 0x1a, 0x7e }, + { 0x8c, 0x68, 0xff }, + { 0x8d, 0x08, 0xff }, + { 0x8e, 0x4c, 0xff }, + { 0x8f, 0x01, 0x01 }, + { 0x8b, 0x04, 0x04 }, + { 0x8b, 0x00, 0x04 }, + { 0x87, 0x05, 0x07 }, + { 0x80, 0x00, 0x20 }, + { 0xc8, 0x01, 0xff }, + { 0xb4, 0x47, 0xff }, + { 0xb5, 0x9c, 0xff }, + { 0xb6, 0x7d, 0xff }, + { 0xba, 0x00, 0x03 }, + { 0xb7, 0x47, 0xff }, + { 0xb8, 0x9c, 0xff }, + { 0xb9, 0x7d, 0xff }, + { 0xba, 0x00, 0x0c }, + { 0xc8, 0x00, 0xff }, + { 0xcd, 0x00, 0x04 }, + { 0xcd, 0x00, 0x20 }, + { 0xe8, 0x02, 0xff }, + { 0xcf, 0x20, 0xff }, + { 0x9b, 0xd7, 0xff }, + { 0x9a, 0x01, 0x03 }, + { 0xa8, 0x05, 0x0f }, + { 0xa8, 0x65, 0xf0 }, + { 0xa6, 0xa0, 0xf0 }, + { 0x9d, 0x50, 0xfc }, + { 0x9e, 0x20, 0xe0 }, + { 0xa3, 0x1c, 0x7c }, + { 0xd5, 0x03, 0x03 }, + }; + + /* firmware status */ + ret = tda10071_rd_reg(priv, 0x51, &tmp); + if (ret) + goto error; + + if (!tmp) { + /* warm state - wake up device from sleep */ + priv->warm = 1; + + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = tda10071_wr_reg_mask(priv, tab[i].reg, + tab[i].val, tab[i].mask); + if (ret) + goto error; + } + + cmd.args[0] = CMD_SET_SLEEP_MODE; + cmd.args[1] = 0; + cmd.args[2] = 0; + cmd.len = 3; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + } else { + /* cold state - try to download firmware */ + priv->warm = 0; + + /* request the firmware, this will block and timeout */ + ret = request_firmware(&fw, fw_file, priv->i2c->dev.parent); + if (ret) { + dev_err(&priv->i2c->dev, "%s: did not find the " \ + "firmware file. (%s) Please see " \ + "linux/Documentation/dvb/ for more " \ + "details on firmware-problems. (%d)\n", + KBUILD_MODNAME, fw_file, ret); + goto error; + } + + /* init */ + for (i = 0; i < ARRAY_SIZE(tab2); i++) { + ret = tda10071_wr_reg_mask(priv, tab2[i].reg, + tab2[i].val, tab2[i].mask); + if (ret) + goto error_release_firmware; + } + + /* download firmware */ + ret = tda10071_wr_reg(priv, 0xe0, 0x7f); + if (ret) + goto error_release_firmware; + + ret = tda10071_wr_reg(priv, 0xf7, 0x81); + if (ret) + goto error_release_firmware; + + ret = tda10071_wr_reg(priv, 0xf8, 0x00); + if (ret) + goto error_release_firmware; + + ret = tda10071_wr_reg(priv, 0xf9, 0x00); + if (ret) + goto error_release_firmware; + + dev_info(&priv->i2c->dev, "%s: found a '%s' in cold state, " \ + "will try to load a firmware\n", KBUILD_MODNAME, + tda10071_ops.info.name); + dev_info(&priv->i2c->dev, "%s: downloading firmware from " \ + "file '%s'\n", KBUILD_MODNAME, fw_file); + + /* do not download last byte */ + fw_size = fw->size - 1; + + for (remaining = fw_size; remaining > 0; + remaining -= (priv->cfg.i2c_wr_max - 1)) { + len = remaining; + if (len > (priv->cfg.i2c_wr_max - 1)) + len = (priv->cfg.i2c_wr_max - 1); + + ret = tda10071_wr_regs(priv, 0xfa, + (u8 *) &fw->data[fw_size - remaining], len); + if (ret) { + dev_err(&priv->i2c->dev, "%s: firmware " \ + "download failed=%d\n", + KBUILD_MODNAME, ret); + if (ret) + goto error_release_firmware; + } + } + release_firmware(fw); + + ret = tda10071_wr_reg(priv, 0xf7, 0x0c); + if (ret) + goto error; + + ret = tda10071_wr_reg(priv, 0xe0, 0x00); + if (ret) + goto error; + + /* wait firmware start */ + msleep(250); + + /* firmware status */ + ret = tda10071_rd_reg(priv, 0x51, &tmp); + if (ret) + goto error; + + if (tmp) { + dev_info(&priv->i2c->dev, "%s: firmware did not run\n", + KBUILD_MODNAME); + ret = -EFAULT; + goto error; + } else { + priv->warm = 1; + } + + cmd.args[0] = CMD_GET_FW_VERSION; + cmd.len = 1; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + ret = tda10071_rd_regs(priv, cmd.len, buf, 4); + if (ret) + goto error; + + dev_info(&priv->i2c->dev, "%s: firmware version %d.%d.%d.%d\n", + KBUILD_MODNAME, buf[0], buf[1], buf[2], buf[3]); + dev_info(&priv->i2c->dev, "%s: found a '%s' in warm state\n", + KBUILD_MODNAME, tda10071_ops.info.name); + + ret = tda10071_rd_regs(priv, 0x81, buf, 2); + if (ret) + goto error; + + cmd.args[0] = CMD_DEMOD_INIT; + cmd.args[1] = ((priv->cfg.xtal / 1000) >> 8) & 0xff; + cmd.args[2] = ((priv->cfg.xtal / 1000) >> 0) & 0xff; + cmd.args[3] = buf[0]; + cmd.args[4] = buf[1]; + cmd.args[5] = priv->cfg.pll_multiplier; + cmd.args[6] = priv->cfg.spec_inv; + cmd.args[7] = 0x00; + cmd.len = 8; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + cmd.args[0] = CMD_TUNER_INIT; + cmd.args[1] = 0x00; + cmd.args[2] = 0x00; + cmd.args[3] = 0x00; + cmd.args[4] = 0x00; + cmd.args[5] = 0x14; + cmd.args[6] = 0x00; + cmd.args[7] = 0x03; + cmd.args[8] = 0x02; + cmd.args[9] = 0x02; + cmd.args[10] = 0x00; + cmd.args[11] = 0x00; + cmd.args[12] = 0x00; + cmd.args[13] = 0x00; + cmd.args[14] = 0x00; + cmd.len = 15; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + cmd.args[0] = CMD_MPEG_CONFIG; + cmd.args[1] = 0; + cmd.args[2] = priv->cfg.ts_mode; + cmd.args[3] = 0x00; + cmd.args[4] = 0x04; + cmd.args[5] = 0x00; + cmd.len = 6; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + ret = tda10071_wr_reg_mask(priv, 0xf0, 0x01, 0x01); + if (ret) + goto error; + + cmd.args[0] = CMD_LNB_CONFIG; + cmd.args[1] = 0; + cmd.args[2] = 150; + cmd.args[3] = 3; + cmd.args[4] = 22; + cmd.args[5] = 1; + cmd.args[6] = 1; + cmd.args[7] = 30; + cmd.args[8] = 30; + cmd.args[9] = 30; + cmd.args[10] = 30; + cmd.len = 11; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + cmd.args[0] = CMD_BER_CONTROL; + cmd.args[1] = 0; + cmd.args[2] = 14; + cmd.args[3] = 14; + cmd.len = 4; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + } + + return ret; +error_release_firmware: + release_firmware(fw); +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_sleep(struct dvb_frontend *fe) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + struct tda10071_cmd cmd; + int ret, i; + struct tda10071_reg_val_mask tab[] = { + { 0xcd, 0x07, 0x07 }, + { 0x80, 0x02, 0x02 }, + { 0xcd, 0xc0, 0xc0 }, + { 0xce, 0x1b, 0x1b }, + { 0x9d, 0x01, 0x01 }, + { 0x9d, 0x02, 0x02 }, + { 0x9e, 0x01, 0x01 }, + { 0x87, 0x80, 0x80 }, + { 0xce, 0x08, 0x08 }, + { 0xce, 0x10, 0x10 }, + }; + + if (!priv->warm) { + ret = -EFAULT; + goto error; + } + + cmd.args[0] = CMD_SET_SLEEP_MODE; + cmd.args[1] = 0; + cmd.args[2] = 1; + cmd.len = 3; + ret = tda10071_cmd_execute(priv, &cmd); + if (ret) + goto error; + + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = tda10071_wr_reg_mask(priv, tab[i].reg, tab[i].val, + tab[i].mask); + if (ret) + goto error; + } + + return ret; +error: + dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); + return ret; +} + +static int tda10071_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 8000; + s->step_size = 0; + s->max_drift = 0; + + return 0; +} + +static void tda10071_release(struct dvb_frontend *fe) +{ + struct tda10071_priv *priv = fe->demodulator_priv; + kfree(priv); +} + +struct dvb_frontend *tda10071_attach(const struct tda10071_config *config, + struct i2c_adapter *i2c) +{ + int ret; + struct tda10071_priv *priv = NULL; + u8 tmp; + + /* allocate memory for the internal priv */ + priv = kzalloc(sizeof(struct tda10071_priv), GFP_KERNEL); + if (priv == NULL) { + ret = -ENOMEM; + goto error; + } + + /* setup the priv */ + priv->i2c = i2c; + memcpy(&priv->cfg, config, sizeof(struct tda10071_config)); + + /* chip ID */ + ret = tda10071_rd_reg(priv, 0xff, &tmp); + if (ret || tmp != 0x0f) + goto error; + + /* chip type */ + ret = tda10071_rd_reg(priv, 0xdd, &tmp); + if (ret || tmp != 0x00) + goto error; + + /* chip version */ + ret = tda10071_rd_reg(priv, 0xfe, &tmp); + if (ret || tmp != 0x01) + goto error; + + /* create dvb_frontend */ + memcpy(&priv->fe.ops, &tda10071_ops, sizeof(struct dvb_frontend_ops)); + priv->fe.demodulator_priv = priv; + + return &priv->fe; +error: + dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret); + kfree(priv); + return NULL; +} +EXPORT_SYMBOL(tda10071_attach); + +static struct dvb_frontend_ops tda10071_ops = { + .delsys = { SYS_DVBS, SYS_DVBS2 }, + .info = { + .name = "NXP TDA10071", + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_tolerance = 5000, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_6_7 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_8_9 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_RECOVER | + FE_CAN_2G_MODULATION + }, + + .release = tda10071_release, + + .get_tune_settings = tda10071_get_tune_settings, + + .init = tda10071_init, + .sleep = tda10071_sleep, + + .set_frontend = tda10071_set_frontend, + .get_frontend = tda10071_get_frontend, + + .read_status = tda10071_read_status, + .read_snr = tda10071_read_snr, + .read_signal_strength = tda10071_read_signal_strength, + .read_ber = tda10071_read_ber, + .read_ucblocks = tda10071_read_ucblocks, + + .diseqc_send_master_cmd = tda10071_diseqc_send_master_cmd, + .diseqc_recv_slave_reply = tda10071_diseqc_recv_slave_reply, + .diseqc_send_burst = tda10071_diseqc_send_burst, + + .set_tone = tda10071_set_tone, + .set_voltage = tda10071_set_voltage, +}; + +MODULE_AUTHOR("Antti Palosaari "); +MODULE_DESCRIPTION("NXP TDA10071 DVB-S/S2 demodulator driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(TDA10071_FIRMWARE); diff --git a/drivers/media/dvb/frontends/tda10071.h b/drivers/media/dvb-frontends/tda10071.h similarity index 100% rename from drivers/media/dvb/frontends/tda10071.h rename to drivers/media/dvb-frontends/tda10071.h diff --git a/drivers/media/dvb-frontends/tda10071_priv.h b/drivers/media/dvb-frontends/tda10071_priv.h new file mode 100644 index 000000000000..4baf14bfb65a --- /dev/null +++ b/drivers/media/dvb-frontends/tda10071_priv.h @@ -0,0 +1,109 @@ +/* + * NXP TDA10071 + Conexant CX24118A DVB-S/S2 demodulator + tuner driver + * + * Copyright (C) 2011 Antti Palosaari + * + * 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. + */ + +#ifndef TDA10071_PRIV +#define TDA10071_PRIV + +#include "dvb_frontend.h" +#include "tda10071.h" +#include + +struct tda10071_priv { + struct i2c_adapter *i2c; + struct dvb_frontend fe; + struct tda10071_config cfg; + + u8 meas_count[2]; + u32 ber; + u32 ucb; + fe_status_t fe_status; + fe_delivery_system_t delivery_system; + bool warm; /* FW running */ +}; + +static struct tda10071_modcod { + fe_delivery_system_t delivery_system; + fe_modulation_t modulation; + fe_code_rate_t fec; + u8 val; +} TDA10071_MODCOD[] = { + /* NBC-QPSK */ + { SYS_DVBS2, QPSK, FEC_AUTO, 0x00 }, + { SYS_DVBS2, QPSK, FEC_1_2, 0x04 }, + { SYS_DVBS2, QPSK, FEC_3_5, 0x05 }, + { SYS_DVBS2, QPSK, FEC_2_3, 0x06 }, + { SYS_DVBS2, QPSK, FEC_3_4, 0x07 }, + { SYS_DVBS2, QPSK, FEC_4_5, 0x08 }, + { SYS_DVBS2, QPSK, FEC_5_6, 0x09 }, + { SYS_DVBS2, QPSK, FEC_8_9, 0x0a }, + { SYS_DVBS2, QPSK, FEC_9_10, 0x0b }, + /* 8PSK */ + { SYS_DVBS2, PSK_8, FEC_3_5, 0x0c }, + { SYS_DVBS2, PSK_8, FEC_2_3, 0x0d }, + { SYS_DVBS2, PSK_8, FEC_3_4, 0x0e }, + { SYS_DVBS2, PSK_8, FEC_5_6, 0x0f }, + { SYS_DVBS2, PSK_8, FEC_8_9, 0x10 }, + { SYS_DVBS2, PSK_8, FEC_9_10, 0x11 }, + /* QPSK */ + { SYS_DVBS, QPSK, FEC_AUTO, 0x2d }, + { SYS_DVBS, QPSK, FEC_1_2, 0x2e }, + { SYS_DVBS, QPSK, FEC_2_3, 0x2f }, + { SYS_DVBS, QPSK, FEC_3_4, 0x30 }, + { SYS_DVBS, QPSK, FEC_5_6, 0x31 }, + { SYS_DVBS, QPSK, FEC_7_8, 0x32 }, +}; + +struct tda10071_reg_val_mask { + u8 reg; + u8 val; + u8 mask; +}; + +/* firmware filename */ +#define TDA10071_FIRMWARE "dvb-fe-tda10071.fw" + +/* firmware commands */ +#define CMD_DEMOD_INIT 0x10 +#define CMD_CHANGE_CHANNEL 0x11 +#define CMD_MPEG_CONFIG 0x13 +#define CMD_TUNER_INIT 0x15 +#define CMD_GET_AGCACC 0x1a + +#define CMD_LNB_CONFIG 0x20 +#define CMD_LNB_SEND_DISEQC 0x21 +#define CMD_LNB_SET_DC_LEVEL 0x22 +#define CMD_LNB_PCB_CONFIG 0x23 +#define CMD_LNB_SEND_TONEBURST 0x24 +#define CMD_LNB_UPDATE_REPLY 0x25 + +#define CMD_GET_FW_VERSION 0x35 +#define CMD_SET_SLEEP_MODE 0x36 +#define CMD_BER_CONTROL 0x3e +#define CMD_BER_UPDATE_COUNTERS 0x3f + +/* firmare command struct */ +#define TDA10071_ARGLEN 30 +struct tda10071_cmd { + u8 args[TDA10071_ARGLEN]; + u8 len; +}; + + +#endif /* TDA10071_PRIV */ diff --git a/drivers/media/dvb/frontends/tda10086.c b/drivers/media/dvb-frontends/tda10086.c similarity index 100% rename from drivers/media/dvb/frontends/tda10086.c rename to drivers/media/dvb-frontends/tda10086.c diff --git a/drivers/media/dvb/frontends/tda10086.h b/drivers/media/dvb-frontends/tda10086.h similarity index 100% rename from drivers/media/dvb/frontends/tda10086.h rename to drivers/media/dvb-frontends/tda10086.h diff --git a/drivers/media/dvb/frontends/tda18271c2dd.c b/drivers/media/dvb-frontends/tda18271c2dd.c similarity index 100% rename from drivers/media/dvb/frontends/tda18271c2dd.c rename to drivers/media/dvb-frontends/tda18271c2dd.c diff --git a/drivers/media/dvb/frontends/tda18271c2dd.h b/drivers/media/dvb-frontends/tda18271c2dd.h similarity index 100% rename from drivers/media/dvb/frontends/tda18271c2dd.h rename to drivers/media/dvb-frontends/tda18271c2dd.h diff --git a/drivers/media/dvb/frontends/tda18271c2dd_maps.h b/drivers/media/dvb-frontends/tda18271c2dd_maps.h similarity index 100% rename from drivers/media/dvb/frontends/tda18271c2dd_maps.h rename to drivers/media/dvb-frontends/tda18271c2dd_maps.h diff --git a/drivers/media/dvb/frontends/tda665x.c b/drivers/media/dvb-frontends/tda665x.c similarity index 100% rename from drivers/media/dvb/frontends/tda665x.c rename to drivers/media/dvb-frontends/tda665x.c diff --git a/drivers/media/dvb/frontends/tda665x.h b/drivers/media/dvb-frontends/tda665x.h similarity index 100% rename from drivers/media/dvb/frontends/tda665x.h rename to drivers/media/dvb-frontends/tda665x.h diff --git a/drivers/media/dvb/frontends/tda8083.c b/drivers/media/dvb-frontends/tda8083.c similarity index 100% rename from drivers/media/dvb/frontends/tda8083.c rename to drivers/media/dvb-frontends/tda8083.c diff --git a/drivers/media/dvb/frontends/tda8083.h b/drivers/media/dvb-frontends/tda8083.h similarity index 100% rename from drivers/media/dvb/frontends/tda8083.h rename to drivers/media/dvb-frontends/tda8083.h diff --git a/drivers/media/dvb-frontends/tda8261.c b/drivers/media/dvb-frontends/tda8261.c new file mode 100644 index 000000000000..19c488814e5c --- /dev/null +++ b/drivers/media/dvb-frontends/tda8261.c @@ -0,0 +1,230 @@ +/* + TDA8261 8PSK/QPSK tuner driver + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +#include +#include +#include +#include + +#include "dvb_frontend.h" +#include "tda8261.h" + +struct tda8261_state { + struct dvb_frontend *fe; + struct i2c_adapter *i2c; + const struct tda8261_config *config; + + /* state cache */ + u32 frequency; + u32 bandwidth; +}; + +static int tda8261_read(struct tda8261_state *state, u8 *buf) +{ + const struct tda8261_config *config = state->config; + int err = 0; + struct i2c_msg msg = { .addr = config->addr, .flags = I2C_M_RD,.buf = buf, .len = 1 }; + + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) + pr_err("%s: read error, err=%d\n", __func__, err); + + return err; +} + +static int tda8261_write(struct tda8261_state *state, u8 *buf) +{ + const struct tda8261_config *config = state->config; + int err = 0; + struct i2c_msg msg = { .addr = config->addr, .flags = 0, .buf = buf, .len = 4 }; + + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) + pr_err("%s: write error, err=%d\n", __func__, err); + + return err; +} + +static int tda8261_get_status(struct dvb_frontend *fe, u32 *status) +{ + struct tda8261_state *state = fe->tuner_priv; + u8 result = 0; + int err = 0; + + *status = 0; + + if ((err = tda8261_read(state, &result)) < 0) { + pr_err("%s: I/O Error\n", __func__); + return err; + } + if ((result >> 6) & 0x01) { + pr_debug("%s: Tuner Phase Locked\n", __func__); + *status = 1; + } + + return err; +} + +static const u32 div_tab[] = { 2000, 1000, 500, 250, 125 }; /* kHz */ +static const u8 ref_div[] = { 0x00, 0x01, 0x02, 0x05, 0x07 }; + +static int tda8261_get_state(struct dvb_frontend *fe, + enum tuner_param param, + struct tuner_state *tstate) +{ + struct tda8261_state *state = fe->tuner_priv; + int err = 0; + + switch (param) { + case DVBFE_TUNER_FREQUENCY: + tstate->frequency = state->frequency; + break; + case DVBFE_TUNER_BANDWIDTH: + tstate->bandwidth = 40000000; /* FIXME! need to calculate Bandwidth */ + break; + default: + pr_err("%s: Unknown parameter (param=%d)\n", __func__, param); + err = -EINVAL; + break; + } + + return err; +} + +static int tda8261_set_state(struct dvb_frontend *fe, + enum tuner_param param, + struct tuner_state *tstate) +{ + struct tda8261_state *state = fe->tuner_priv; + const struct tda8261_config *config = state->config; + u32 frequency, N, status = 0; + u8 buf[4]; + int err = 0; + + if (param & DVBFE_TUNER_FREQUENCY) { + /** + * N = Max VCO Frequency / Channel Spacing + * Max VCO Frequency = VCO frequency + (channel spacing - 1) + * (to account for half channel spacing on either side) + */ + frequency = tstate->frequency; + if ((frequency < 950000) || (frequency > 2150000)) { + pr_warn("%s: Frequency beyond limits, frequency=%d\n", __func__, frequency); + return -EINVAL; + } + N = (frequency + (div_tab[config->step_size] - 1)) / div_tab[config->step_size]; + pr_debug("%s: Step size=%d, Divider=%d, PG=0x%02x (%d)\n", + __func__, config->step_size, div_tab[config->step_size], N, N); + + buf[0] = (N >> 8) & 0xff; + buf[1] = N & 0xff; + buf[2] = (0x01 << 7) | ((ref_div[config->step_size] & 0x07) << 1); + + if (frequency < 1450000) + buf[3] = 0x00; + else if (frequency < 2000000) + buf[3] = 0x40; + else if (frequency < 2150000) + buf[3] = 0x80; + + /* Set params */ + if ((err = tda8261_write(state, buf)) < 0) { + pr_err("%s: I/O Error\n", __func__); + return err; + } + /* sleep for some time */ + pr_debug("%s: Waiting to Phase LOCK\n", __func__); + msleep(20); + /* check status */ + if ((err = tda8261_get_status(fe, &status)) < 0) { + pr_err("%s: I/O Error\n", __func__); + return err; + } + if (status == 1) { + pr_debug("%s: Tuner Phase locked: status=%d\n", __func__, status); + state->frequency = frequency; /* cache successful state */ + } else { + pr_debug("%s: No Phase lock: status=%d\n", __func__, status); + } + } else { + pr_err("%s: Unknown parameter (param=%d)\n", __func__, param); + return -EINVAL; + } + + return 0; +} + +static int tda8261_release(struct dvb_frontend *fe) +{ + struct tda8261_state *state = fe->tuner_priv; + + fe->tuner_priv = NULL; + kfree(state); + return 0; +} + +static struct dvb_tuner_ops tda8261_ops = { + + .info = { + .name = "TDA8261", +// .tuner_name = NULL, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_step = 0 + }, + + .set_state = tda8261_set_state, + .get_state = tda8261_get_state, + .get_status = tda8261_get_status, + .release = tda8261_release +}; + +struct dvb_frontend *tda8261_attach(struct dvb_frontend *fe, + const struct tda8261_config *config, + struct i2c_adapter *i2c) +{ + struct tda8261_state *state = NULL; + + if ((state = kzalloc(sizeof (struct tda8261_state), GFP_KERNEL)) == NULL) + goto exit; + + state->config = config; + state->i2c = i2c; + state->fe = fe; + fe->tuner_priv = state; + fe->ops.tuner_ops = tda8261_ops; + + fe->ops.tuner_ops.info.frequency_step = div_tab[config->step_size]; +// fe->ops.tuner_ops.tuner_name = &config->buf; + +// printk("%s: Attaching %s TDA8261 8PSK/QPSK tuner\n", +// __func__, fe->ops.tuner_ops.tuner_name); + pr_info("%s: Attaching TDA8261 8PSK/QPSK tuner\n", __func__); + + return fe; + +exit: + kfree(state); + return NULL; +} + +EXPORT_SYMBOL(tda8261_attach); + +MODULE_AUTHOR("Manu Abraham"); +MODULE_DESCRIPTION("TDA8261 8PSK/QPSK Tuner"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/tda8261.h b/drivers/media/dvb-frontends/tda8261.h similarity index 100% rename from drivers/media/dvb/frontends/tda8261.h rename to drivers/media/dvb-frontends/tda8261.h diff --git a/drivers/media/dvb/frontends/tda8261_cfg.h b/drivers/media/dvb-frontends/tda8261_cfg.h similarity index 100% rename from drivers/media/dvb/frontends/tda8261_cfg.h rename to drivers/media/dvb-frontends/tda8261_cfg.h diff --git a/drivers/media/dvb/frontends/tda826x.c b/drivers/media/dvb-frontends/tda826x.c similarity index 100% rename from drivers/media/dvb/frontends/tda826x.c rename to drivers/media/dvb-frontends/tda826x.c diff --git a/drivers/media/dvb/frontends/tda826x.h b/drivers/media/dvb-frontends/tda826x.h similarity index 100% rename from drivers/media/dvb/frontends/tda826x.h rename to drivers/media/dvb-frontends/tda826x.h diff --git a/drivers/media/dvb/frontends/tdhd1.h b/drivers/media/dvb-frontends/tdhd1.h similarity index 100% rename from drivers/media/dvb/frontends/tdhd1.h rename to drivers/media/dvb-frontends/tdhd1.h diff --git a/drivers/media/dvb/frontends/tua6100.c b/drivers/media/dvb-frontends/tua6100.c similarity index 100% rename from drivers/media/dvb/frontends/tua6100.c rename to drivers/media/dvb-frontends/tua6100.c diff --git a/drivers/media/dvb/frontends/tua6100.h b/drivers/media/dvb-frontends/tua6100.h similarity index 100% rename from drivers/media/dvb/frontends/tua6100.h rename to drivers/media/dvb-frontends/tua6100.h diff --git a/drivers/media/dvb/frontends/ves1820.c b/drivers/media/dvb-frontends/ves1820.c similarity index 100% rename from drivers/media/dvb/frontends/ves1820.c rename to drivers/media/dvb-frontends/ves1820.c diff --git a/drivers/media/dvb/frontends/ves1820.h b/drivers/media/dvb-frontends/ves1820.h similarity index 100% rename from drivers/media/dvb/frontends/ves1820.h rename to drivers/media/dvb-frontends/ves1820.h diff --git a/drivers/media/dvb/frontends/ves1x93.c b/drivers/media/dvb-frontends/ves1x93.c similarity index 100% rename from drivers/media/dvb/frontends/ves1x93.c rename to drivers/media/dvb-frontends/ves1x93.c diff --git a/drivers/media/dvb/frontends/ves1x93.h b/drivers/media/dvb-frontends/ves1x93.h similarity index 100% rename from drivers/media/dvb/frontends/ves1x93.h rename to drivers/media/dvb-frontends/ves1x93.h diff --git a/drivers/media/dvb/frontends/z0194a.h b/drivers/media/dvb-frontends/z0194a.h similarity index 100% rename from drivers/media/dvb/frontends/z0194a.h rename to drivers/media/dvb-frontends/z0194a.h diff --git a/drivers/media/dvb/frontends/zl10036.c b/drivers/media/dvb-frontends/zl10036.c similarity index 100% rename from drivers/media/dvb/frontends/zl10036.c rename to drivers/media/dvb-frontends/zl10036.c diff --git a/drivers/media/dvb/frontends/zl10036.h b/drivers/media/dvb-frontends/zl10036.h similarity index 100% rename from drivers/media/dvb/frontends/zl10036.h rename to drivers/media/dvb-frontends/zl10036.h diff --git a/drivers/media/dvb/frontends/zl10039.c b/drivers/media/dvb-frontends/zl10039.c similarity index 100% rename from drivers/media/dvb/frontends/zl10039.c rename to drivers/media/dvb-frontends/zl10039.c diff --git a/drivers/media/dvb/frontends/zl10039.h b/drivers/media/dvb-frontends/zl10039.h similarity index 100% rename from drivers/media/dvb/frontends/zl10039.h rename to drivers/media/dvb-frontends/zl10039.h diff --git a/drivers/media/dvb/frontends/zl10353.c b/drivers/media/dvb-frontends/zl10353.c similarity index 100% rename from drivers/media/dvb/frontends/zl10353.c rename to drivers/media/dvb-frontends/zl10353.c diff --git a/drivers/media/dvb/frontends/zl10353.h b/drivers/media/dvb-frontends/zl10353.h similarity index 100% rename from drivers/media/dvb/frontends/zl10353.h rename to drivers/media/dvb-frontends/zl10353.h diff --git a/drivers/media/dvb/frontends/zl10353_priv.h b/drivers/media/dvb-frontends/zl10353_priv.h similarity index 100% rename from drivers/media/dvb/frontends/zl10353_priv.h rename to drivers/media/dvb-frontends/zl10353_priv.h diff --git a/drivers/media/dvb/Kconfig b/drivers/media/dvb/Kconfig deleted file mode 100644 index f6e40b3a44cc..000000000000 --- a/drivers/media/dvb/Kconfig +++ /dev/null @@ -1,91 +0,0 @@ -# -# DVB device configuration -# - -config DVB_MAX_ADAPTERS - int "maximum number of DVB/ATSC adapters" - depends on DVB_CORE - default 8 - range 1 255 - help - Maximum number of DVB/ATSC adapters. Increasing this number - increases the memory consumption of the DVB subsystem even - if a much lower number of DVB/ATSC adapters is present. - Only values in the range 4-32 are tested. - - If you are unsure about this, use the default value 8 - -config DVB_DYNAMIC_MINORS - bool "Dynamic DVB minor allocation" - depends on DVB_CORE - default n - help - If you say Y here, the DVB subsystem will use dynamic minor - allocation for any device that uses the DVB major number. - This means that you can have more than 4 of a single type - of device (like demuxes and frontends) per adapter, but udev - will be required to manage the device nodes. - - If you are unsure about this, say N here. - -menuconfig DVB_CAPTURE_DRIVERS - bool "DVB/ATSC adapters" - depends on DVB_CORE - default y - ---help--- - Say Y to select Digital TV adapters - -if DVB_CAPTURE_DRIVERS && DVB_CORE - -comment "Supported SAA7146 based PCI Adapters" - depends on DVB_CORE && PCI && I2C -source "drivers/media/dvb/ttpci/Kconfig" - -comment "Supported USB Adapters" - depends on DVB_CORE && USB && I2C -source "drivers/media/dvb/dvb-usb/Kconfig" -source "drivers/media/dvb/ttusb-budget/Kconfig" -source "drivers/media/dvb/ttusb-dec/Kconfig" -source "drivers/media/dvb/siano/Kconfig" - -comment "Supported FlexCopII (B2C2) Adapters" - depends on DVB_CORE && (PCI || USB) && I2C -source "drivers/media/dvb/b2c2/Kconfig" - -comment "Supported BT878 Adapters" - depends on DVB_CORE && PCI && I2C -source "drivers/media/dvb/bt8xx/Kconfig" - -comment "Supported Pluto2 Adapters" - depends on DVB_CORE && PCI && I2C -source "drivers/media/dvb/pluto2/Kconfig" - -comment "Supported SDMC DM1105 Adapters" - depends on DVB_CORE && PCI && I2C -source "drivers/media/dvb/dm1105/Kconfig" - -comment "Supported FireWire (IEEE 1394) Adapters" - depends on DVB_CORE && FIREWIRE -source "drivers/media/dvb/firewire/Kconfig" - -comment "Supported Earthsoft PT1 Adapters" - depends on DVB_CORE && PCI && I2C -source "drivers/media/dvb/pt1/Kconfig" - -comment "Supported Mantis Adapters" - depends on DVB_CORE && PCI && I2C - source "drivers/media/dvb/mantis/Kconfig" - -comment "Supported nGene Adapters" - depends on DVB_CORE && PCI && I2C - source "drivers/media/dvb/ngene/Kconfig" - -comment "Supported ddbridge ('Octopus') Adapters" - depends on DVB_CORE && PCI && I2C - source "drivers/media/dvb/ddbridge/Kconfig" - -comment "Supported DVB Frontends" - depends on DVB_CORE -source "drivers/media/dvb/frontends/Kconfig" - -endif # DVB_CAPTURE_DRIVERS diff --git a/drivers/media/dvb/Makefile b/drivers/media/dvb/Makefile deleted file mode 100644 index b2cefe637a64..000000000000 --- a/drivers/media/dvb/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# -# Makefile for the kernel multimedia device drivers. -# - -obj-y := dvb-core/ \ - frontends/ \ - ttpci/ \ - ttusb-dec/ \ - ttusb-budget/ \ - b2c2/ \ - bt8xx/ \ - dvb-usb/ \ - pluto2/ \ - siano/ \ - dm1105/ \ - pt1/ \ - mantis/ \ - ngene/ \ - ddbridge/ - -obj-$(CONFIG_DVB_FIREDTV) += firewire/ diff --git a/drivers/media/dvb/b2c2/Kconfig b/drivers/media/dvb/b2c2/Kconfig deleted file mode 100644 index 9e5781400744..000000000000 --- a/drivers/media/dvb/b2c2/Kconfig +++ /dev/null @@ -1,45 +0,0 @@ -config DVB_B2C2_FLEXCOP - tristate "Technisat/B2C2 FlexCopII(b) and FlexCopIII adapters" - depends on DVB_CORE && I2C - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_MT352 if !DVB_FE_CUSTOMISE - select DVB_MT312 if !DVB_FE_CUSTOMISE - select DVB_NXT200X if !DVB_FE_CUSTOMISE - select DVB_STV0297 if !DVB_FE_CUSTOMISE - select DVB_BCM3510 if !DVB_FE_CUSTOMISE - select DVB_LGDT330X if !DVB_FE_CUSTOMISE - select DVB_S5H1420 if !DVB_FE_CUSTOMISE - select DVB_TUNER_ITD1000 if !DVB_FE_CUSTOMISE - select DVB_ISL6421 if !DVB_FE_CUSTOMISE - select DVB_CX24123 if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_SIMPLE if !MEDIA_TUNER_CUSTOMISE - select DVB_TUNER_CX24113 if !DVB_FE_CUSTOMISE - help - Support for the digital TV receiver chip made by B2C2 Inc. included in - Technisats PCI cards and USB boxes. - - Say Y if you own such a device and want to use it. - -config DVB_B2C2_FLEXCOP_PCI - tristate "Technisat/B2C2 Air/Sky/Cable2PC PCI" - depends on DVB_B2C2_FLEXCOP && PCI && I2C - help - Support for the Air/Sky/CableStar2 PCI card (DVB/ATSC) by Technisat/B2C2. - - Say Y if you own such a device and want to use it. - -config DVB_B2C2_FLEXCOP_USB - tristate "Technisat/B2C2 Air/Sky/Cable2PC USB" - depends on DVB_B2C2_FLEXCOP && USB && I2C - help - Support for the Air/Sky/Cable2PC USB1.1 box (DVB/ATSC) by Technisat/B2C2, - - Say Y if you own such a device and want to use it. - -config DVB_B2C2_FLEXCOP_DEBUG - bool "Enable debug for the B2C2 FlexCop drivers" - depends on DVB_B2C2_FLEXCOP - help - Say Y if you want to enable the module option to control debug messages - of all B2C2 FlexCop drivers. diff --git a/drivers/media/dvb/b2c2/Makefile b/drivers/media/dvb/b2c2/Makefile deleted file mode 100644 index 3d04a8dba99e..000000000000 --- a/drivers/media/dvb/b2c2/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -b2c2-flexcop-objs = flexcop.o flexcop-fe-tuner.o flexcop-i2c.o \ - flexcop-sram.o flexcop-eeprom.o flexcop-misc.o flexcop-hw-filter.o -obj-$(CONFIG_DVB_B2C2_FLEXCOP) += b2c2-flexcop.o - -ifneq ($(CONFIG_DVB_B2C2_FLEXCOP_PCI),) -b2c2-flexcop-objs += flexcop-dma.o -endif - -b2c2-flexcop-pci-objs = flexcop-pci.o -obj-$(CONFIG_DVB_B2C2_FLEXCOP_PCI) += b2c2-flexcop-pci.o - -b2c2-flexcop-usb-objs = flexcop-usb.o -obj-$(CONFIG_DVB_B2C2_FLEXCOP_USB) += b2c2-flexcop-usb.o - -ccflags-y += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/ -ccflags-y += -Idrivers/media/common/tuners/ diff --git a/drivers/media/dvb/b2c2/flexcop-usb.c b/drivers/media/dvb/b2c2/flexcop-usb.c deleted file mode 100644 index 26c666dd3514..000000000000 --- a/drivers/media/dvb/b2c2/flexcop-usb.c +++ /dev/null @@ -1,590 +0,0 @@ -/* - * Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III - * flexcop-usb.c - covers the USB part - * see flexcop.c for copyright information - */ -#define FC_LOG_PREFIX "flexcop_usb" -#include "flexcop-usb.h" -#include "flexcop-common.h" - -/* Version information */ -#define DRIVER_VERSION "0.1" -#define DRIVER_NAME "Technisat/B2C2 FlexCop II/IIb/III Digital TV USB Driver" -#define DRIVER_AUTHOR "Patrick Boettcher " - -/* debug */ -#ifdef CONFIG_DVB_B2C2_FLEXCOP_DEBUG -#define dprintk(level,args...) \ - do { if ((debug & level)) printk(args); } while (0) - -#define debug_dump(b, l, method) do {\ - int i; \ - for (i = 0; i < l; i++) \ - method("%02x ", b[i]); \ - method("\n"); \ -} while (0) - -#define DEBSTATUS "" -#else -#define dprintk(level, args...) -#define debug_dump(b, l, method) -#define DEBSTATUS " (debugging is not enabled)" -#endif - -static int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=info,ts=2," - "ctrl=4,i2c=8,v8mem=16 (or-able))." DEBSTATUS); -#undef DEBSTATUS - -#define deb_info(args...) dprintk(0x01, args) -#define deb_ts(args...) dprintk(0x02, args) -#define deb_ctrl(args...) dprintk(0x04, args) -#define deb_i2c(args...) dprintk(0x08, args) -#define deb_v8(args...) dprintk(0x10, args) - -/* JLP 111700: we will include the 1 bit gap between the upper and lower 3 bits - * in the IBI address, to make the V8 code simpler. - * PCI ADDRESS FORMAT: 0x71C -> 0000 0111 0001 1100 (the six bits used) - * in general: 0000 0HHH 000L LL00 - * IBI ADDRESS FORMAT: RHHH BLLL - * - * where R is the read(1)/write(0) bit, B is the busy bit - * and HHH and LLL are the two sets of three bits from the PCI address. - */ -#define B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(usPCI) (u8) \ - (((usPCI >> 2) & 0x07) + ((usPCI >> 4) & 0x70)) -#define B2C2_FLEX_INTERNALADDR_TO_PCIOFFSET(ucAddr) (u16) \ - (((ucAddr & 0x07) << 2) + ((ucAddr & 0x70) << 4)) - -/* - * DKT 020228 - * - forget about this VENDOR_BUFFER_SIZE, read and write register - * deal with DWORD or 4 bytes, that should be should from now on - * - from now on, we don't support anything older than firm 1.00 - * I eliminated the write register as a 2 trip of writing hi word and lo word - * and force this to write only 4 bytes at a time. - * NOTE: this should work with all the firmware from 1.00 and newer - */ -static int flexcop_usb_readwrite_dw(struct flexcop_device *fc, u16 wRegOffsPCI, u32 *val, u8 read) -{ - struct flexcop_usb *fc_usb = fc->bus_specific; - u8 request = read ? B2C2_USB_READ_REG : B2C2_USB_WRITE_REG; - u8 request_type = (read ? USB_DIR_IN : USB_DIR_OUT) | USB_TYPE_VENDOR; - u8 wAddress = B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(wRegOffsPCI) | - (read ? 0x80 : 0); - - int len = usb_control_msg(fc_usb->udev, - read ? B2C2_USB_CTRL_PIPE_IN : B2C2_USB_CTRL_PIPE_OUT, - request, - request_type, /* 0xc0 read or 0x40 write */ - wAddress, - 0, - val, - sizeof(u32), - B2C2_WAIT_FOR_OPERATION_RDW * HZ); - - if (len != sizeof(u32)) { - err("error while %s dword from %d (%d).", read ? "reading" : - "writing", wAddress, wRegOffsPCI); - return -EIO; - } - return 0; -} -/* - * DKT 010817 - add support for V8 memory read/write and flash update - */ -static int flexcop_usb_v8_memory_req(struct flexcop_usb *fc_usb, - flexcop_usb_request_t req, u8 page, u16 wAddress, - u8 *pbBuffer, u32 buflen) -{ - u8 request_type = USB_TYPE_VENDOR; - u16 wIndex; - int nWaitTime, pipe, len; - wIndex = page << 8; - - switch (req) { - case B2C2_USB_READ_V8_MEM: - nWaitTime = B2C2_WAIT_FOR_OPERATION_V8READ; - request_type |= USB_DIR_IN; - pipe = B2C2_USB_CTRL_PIPE_IN; - break; - case B2C2_USB_WRITE_V8_MEM: - wIndex |= pbBuffer[0]; - request_type |= USB_DIR_OUT; - nWaitTime = B2C2_WAIT_FOR_OPERATION_V8WRITE; - pipe = B2C2_USB_CTRL_PIPE_OUT; - break; - case B2C2_USB_FLASH_BLOCK: - request_type |= USB_DIR_OUT; - nWaitTime = B2C2_WAIT_FOR_OPERATION_V8FLASH; - pipe = B2C2_USB_CTRL_PIPE_OUT; - break; - default: - deb_info("unsupported request for v8_mem_req %x.\n", req); - return -EINVAL; - } - deb_v8("v8mem: %02x %02x %04x %04x, len: %d\n", request_type, req, - wAddress, wIndex, buflen); - - len = usb_control_msg(fc_usb->udev, pipe, - req, - request_type, - wAddress, - wIndex, - pbBuffer, - buflen, - nWaitTime * HZ); - - debug_dump(pbBuffer, len, deb_v8); - return len == buflen ? 0 : -EIO; -} - -#define bytes_left_to_read_on_page(paddr,buflen) \ - ((V8_MEMORY_PAGE_SIZE - (paddr & V8_MEMORY_PAGE_MASK)) > buflen \ - ? buflen : (V8_MEMORY_PAGE_SIZE - (paddr & V8_MEMORY_PAGE_MASK))) - -static int flexcop_usb_memory_req(struct flexcop_usb *fc_usb, - flexcop_usb_request_t req, flexcop_usb_mem_page_t page_start, - u32 addr, int extended, u8 *buf, u32 len) -{ - int i,ret = 0; - u16 wMax; - u32 pagechunk = 0; - - switch(req) { - case B2C2_USB_READ_V8_MEM: - wMax = USB_MEM_READ_MAX; - break; - case B2C2_USB_WRITE_V8_MEM: - wMax = USB_MEM_WRITE_MAX; - break; - case B2C2_USB_FLASH_BLOCK: - wMax = USB_FLASH_MAX; - break; - default: - return -EINVAL; - break; - } - for (i = 0; i < len;) { - pagechunk = - wMax < bytes_left_to_read_on_page(addr, len) ? - wMax : - bytes_left_to_read_on_page(addr, len); - deb_info("%x\n", - (addr & V8_MEMORY_PAGE_MASK) | - (V8_MEMORY_EXTENDED*extended)); - - ret = flexcop_usb_v8_memory_req(fc_usb, req, - page_start + (addr / V8_MEMORY_PAGE_SIZE), - (addr & V8_MEMORY_PAGE_MASK) | - (V8_MEMORY_EXTENDED*extended), - &buf[i], pagechunk); - - if (ret < 0) - return ret; - addr += pagechunk; - len -= pagechunk; - } - return 0; -} - -static int flexcop_usb_get_mac_addr(struct flexcop_device *fc, int extended) -{ - return flexcop_usb_memory_req(fc->bus_specific, B2C2_USB_READ_V8_MEM, - V8_MEMORY_PAGE_FLASH, 0x1f010, 1, - fc->dvb_adapter.proposed_mac, 6); -} - -#if 0 -static int flexcop_usb_utility_req(struct flexcop_usb *fc_usb, int set, - flexcop_usb_utility_function_t func, u8 extra, u16 wIndex, - u16 buflen, u8 *pvBuffer) -{ - u16 wValue; - u8 request_type = (set ? USB_DIR_OUT : USB_DIR_IN) | USB_TYPE_VENDOR; - int nWaitTime = 2, - pipe = set ? B2C2_USB_CTRL_PIPE_OUT : B2C2_USB_CTRL_PIPE_IN, len; - wValue = (func << 8) | extra; - - len = usb_control_msg(fc_usb->udev,pipe, - B2C2_USB_UTILITY, - request_type, - wValue, - wIndex, - pvBuffer, - buflen, - nWaitTime * HZ); - return len == buflen ? 0 : -EIO; -} -#endif - -/* usb i2c stuff */ -static int flexcop_usb_i2c_req(struct flexcop_i2c_adapter *i2c, - flexcop_usb_request_t req, flexcop_usb_i2c_function_t func, - u8 chipaddr, u8 addr, u8 *buf, u8 buflen) -{ - struct flexcop_usb *fc_usb = i2c->fc->bus_specific; - u16 wValue, wIndex; - int nWaitTime,pipe,len; - u8 request_type = USB_TYPE_VENDOR; - - switch (func) { - case USB_FUNC_I2C_WRITE: - case USB_FUNC_I2C_MULTIWRITE: - case USB_FUNC_I2C_REPEATWRITE: - /* DKT 020208 - add this to support special case of DiSEqC */ - case USB_FUNC_I2C_CHECKWRITE: - pipe = B2C2_USB_CTRL_PIPE_OUT; - nWaitTime = 2; - request_type |= USB_DIR_OUT; - break; - case USB_FUNC_I2C_READ: - case USB_FUNC_I2C_REPEATREAD: - pipe = B2C2_USB_CTRL_PIPE_IN; - nWaitTime = 2; - request_type |= USB_DIR_IN; - break; - default: - deb_info("unsupported function for i2c_req %x\n", func); - return -EINVAL; - } - wValue = (func << 8) | (i2c->port << 4); - wIndex = (chipaddr << 8 ) | addr; - - deb_i2c("i2c %2d: %02x %02x %02x %02x %02x %02x\n", - func, request_type, req, - wValue & 0xff, wValue >> 8, - wIndex & 0xff, wIndex >> 8); - - len = usb_control_msg(fc_usb->udev,pipe, - req, - request_type, - wValue, - wIndex, - buf, - buflen, - nWaitTime * HZ); - return len == buflen ? 0 : -EREMOTEIO; -} - -/* actual bus specific access functions, - make sure prototype are/will be equal to pci */ -static flexcop_ibi_value flexcop_usb_read_ibi_reg(struct flexcop_device *fc, - flexcop_ibi_register reg) -{ - flexcop_ibi_value val; - val.raw = 0; - flexcop_usb_readwrite_dw(fc, reg, &val.raw, 1); - return val; -} - -static int flexcop_usb_write_ibi_reg(struct flexcop_device *fc, - flexcop_ibi_register reg, flexcop_ibi_value val) -{ - return flexcop_usb_readwrite_dw(fc, reg, &val.raw, 0); -} - -static int flexcop_usb_i2c_request(struct flexcop_i2c_adapter *i2c, - flexcop_access_op_t op, u8 chipaddr, u8 addr, u8 *buf, u16 len) -{ - if (op == FC_READ) - return flexcop_usb_i2c_req(i2c, B2C2_USB_I2C_REQUEST, - USB_FUNC_I2C_READ, chipaddr, addr, buf, len); - else - return flexcop_usb_i2c_req(i2c, B2C2_USB_I2C_REQUEST, - USB_FUNC_I2C_WRITE, chipaddr, addr, buf, len); -} - -static void flexcop_usb_process_frame(struct flexcop_usb *fc_usb, - u8 *buffer, int buffer_length) -{ - u8 *b; - int l; - - deb_ts("tmp_buffer_length=%d, buffer_length=%d\n", - fc_usb->tmp_buffer_length, buffer_length); - - if (fc_usb->tmp_buffer_length > 0) { - memcpy(fc_usb->tmp_buffer+fc_usb->tmp_buffer_length, buffer, - buffer_length); - fc_usb->tmp_buffer_length += buffer_length; - b = fc_usb->tmp_buffer; - l = fc_usb->tmp_buffer_length; - } else { - b=buffer; - l=buffer_length; - } - - while (l >= 190) { - if (*b == 0xff) { - switch (*(b+1) & 0x03) { - case 0x01: /* media packet */ - if (*(b+2) == 0x47) - flexcop_pass_dmx_packets( - fc_usb->fc_dev, b+2, 1); - else - deb_ts( - "not ts packet %02x %02x %02x %02x \n", - *(b+2), *(b+3), - *(b+4), *(b+5)); - b += 190; - l -= 190; - break; - default: - deb_ts("wrong packet type\n"); - l = 0; - break; - } - } else { - deb_ts("wrong header\n"); - l = 0; - } - } - - if (l>0) - memcpy(fc_usb->tmp_buffer, b, l); - fc_usb->tmp_buffer_length = l; -} - -static void flexcop_usb_urb_complete(struct urb *urb) -{ - struct flexcop_usb *fc_usb = urb->context; - int i; - - if (urb->actual_length > 0) - deb_ts("urb completed, bufsize: %d actlen; %d\n", - urb->transfer_buffer_length, urb->actual_length); - - for (i = 0; i < urb->number_of_packets; i++) { - if (urb->iso_frame_desc[i].status < 0) { - err("iso frame descriptor %d has an error: %d\n", i, - urb->iso_frame_desc[i].status); - } else - if (urb->iso_frame_desc[i].actual_length > 0) { - deb_ts("passed %d bytes to the demux\n", - urb->iso_frame_desc[i].actual_length); - - flexcop_usb_process_frame(fc_usb, - urb->transfer_buffer + - urb->iso_frame_desc[i].offset, - urb->iso_frame_desc[i].actual_length); - } - urb->iso_frame_desc[i].status = 0; - urb->iso_frame_desc[i].actual_length = 0; - } - usb_submit_urb(urb,GFP_ATOMIC); -} - -static int flexcop_usb_stream_control(struct flexcop_device *fc, int onoff) -{ - /* submit/kill iso packets */ - return 0; -} - -static void flexcop_usb_transfer_exit(struct flexcop_usb *fc_usb) -{ - int i; - for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) - if (fc_usb->iso_urb[i] != NULL) { - deb_ts("unlinking/killing urb no. %d\n",i); - usb_kill_urb(fc_usb->iso_urb[i]); - usb_free_urb(fc_usb->iso_urb[i]); - } - - if (fc_usb->iso_buffer != NULL) - pci_free_consistent(NULL, - fc_usb->buffer_size, fc_usb->iso_buffer, - fc_usb->dma_addr); -} - -static int flexcop_usb_transfer_init(struct flexcop_usb *fc_usb) -{ - u16 frame_size = le16_to_cpu( - fc_usb->uintf->cur_altsetting->endpoint[0].desc.wMaxPacketSize); - int bufsize = B2C2_USB_NUM_ISO_URB * B2C2_USB_FRAMES_PER_ISO * - frame_size, i, j, ret; - int buffer_offset = 0; - - deb_ts("creating %d iso-urbs with %d frames " - "each of %d bytes size = %d.\n", B2C2_USB_NUM_ISO_URB, - B2C2_USB_FRAMES_PER_ISO, frame_size, bufsize); - - fc_usb->iso_buffer = pci_alloc_consistent(NULL, - bufsize, &fc_usb->dma_addr); - if (fc_usb->iso_buffer == NULL) - return -ENOMEM; - - memset(fc_usb->iso_buffer, 0, bufsize); - fc_usb->buffer_size = bufsize; - - /* creating iso urbs */ - for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) { - fc_usb->iso_urb[i] = usb_alloc_urb(B2C2_USB_FRAMES_PER_ISO, - GFP_ATOMIC); - if (fc_usb->iso_urb[i] == NULL) { - ret = -ENOMEM; - goto urb_error; - } - } - - /* initialising and submitting iso urbs */ - for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) { - int frame_offset = 0; - struct urb *urb = fc_usb->iso_urb[i]; - deb_ts("initializing and submitting urb no. %d " - "(buf_offset: %d).\n", i, buffer_offset); - - urb->dev = fc_usb->udev; - urb->context = fc_usb; - urb->complete = flexcop_usb_urb_complete; - urb->pipe = B2C2_USB_DATA_PIPE; - urb->transfer_flags = URB_ISO_ASAP; - urb->interval = 1; - urb->number_of_packets = B2C2_USB_FRAMES_PER_ISO; - urb->transfer_buffer_length = frame_size * B2C2_USB_FRAMES_PER_ISO; - urb->transfer_buffer = fc_usb->iso_buffer + buffer_offset; - - buffer_offset += frame_size * B2C2_USB_FRAMES_PER_ISO; - for (j = 0; j < B2C2_USB_FRAMES_PER_ISO; j++) { - deb_ts("urb no: %d, frame: %d, frame_offset: %d\n", - i, j, frame_offset); - urb->iso_frame_desc[j].offset = frame_offset; - urb->iso_frame_desc[j].length = frame_size; - frame_offset += frame_size; - } - - if ((ret = usb_submit_urb(fc_usb->iso_urb[i],GFP_ATOMIC))) { - err("submitting urb %d failed with %d.", i, ret); - goto urb_error; - } - deb_ts("submitted urb no. %d.\n",i); - } - - /* SRAM */ - flexcop_sram_set_dest(fc_usb->fc_dev, FC_SRAM_DEST_MEDIA | - FC_SRAM_DEST_NET | FC_SRAM_DEST_CAO | FC_SRAM_DEST_CAI, - FC_SRAM_DEST_TARGET_WAN_USB); - flexcop_wan_set_speed(fc_usb->fc_dev, FC_WAN_SPEED_8MBITS); - flexcop_sram_ctrl(fc_usb->fc_dev, 1, 1, 1); - return 0; - -urb_error: - flexcop_usb_transfer_exit(fc_usb); - return ret; -} - -static int flexcop_usb_init(struct flexcop_usb *fc_usb) -{ - /* use the alternate setting with the larges buffer */ - usb_set_interface(fc_usb->udev,0,1); - switch (fc_usb->udev->speed) { - case USB_SPEED_LOW: - err("cannot handle USB speed because it is too slow."); - return -ENODEV; - break; - case USB_SPEED_FULL: - info("running at FULL speed."); - break; - case USB_SPEED_HIGH: - info("running at HIGH speed."); - break; - case USB_SPEED_UNKNOWN: /* fall through */ - default: - err("cannot handle USB speed because it is unknown."); - return -ENODEV; - } - usb_set_intfdata(fc_usb->uintf, fc_usb); - return 0; -} - -static void flexcop_usb_exit(struct flexcop_usb *fc_usb) -{ - usb_set_intfdata(fc_usb->uintf, NULL); -} - -static int flexcop_usb_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - struct usb_device *udev = interface_to_usbdev(intf); - struct flexcop_usb *fc_usb = NULL; - struct flexcop_device *fc = NULL; - int ret; - - if ((fc = flexcop_device_kmalloc(sizeof(struct flexcop_usb))) == NULL) { - err("out of memory\n"); - return -ENOMEM; - } - - /* general flexcop init */ - fc_usb = fc->bus_specific; - fc_usb->fc_dev = fc; - - fc->read_ibi_reg = flexcop_usb_read_ibi_reg; - fc->write_ibi_reg = flexcop_usb_write_ibi_reg; - fc->i2c_request = flexcop_usb_i2c_request; - fc->get_mac_addr = flexcop_usb_get_mac_addr; - - fc->stream_control = flexcop_usb_stream_control; - - fc->pid_filtering = 1; - fc->bus_type = FC_USB; - - fc->dev = &udev->dev; - fc->owner = THIS_MODULE; - - /* bus specific part */ - fc_usb->udev = udev; - fc_usb->uintf = intf; - if ((ret = flexcop_usb_init(fc_usb)) != 0) - goto err_kfree; - - /* init flexcop */ - if ((ret = flexcop_device_initialize(fc)) != 0) - goto err_usb_exit; - - /* xfer init */ - if ((ret = flexcop_usb_transfer_init(fc_usb)) != 0) - goto err_fc_exit; - - info("%s successfully initialized and connected.", DRIVER_NAME); - return 0; - -err_fc_exit: - flexcop_device_exit(fc); -err_usb_exit: - flexcop_usb_exit(fc_usb); -err_kfree: - flexcop_device_kfree(fc); - return ret; -} - -static void flexcop_usb_disconnect(struct usb_interface *intf) -{ - struct flexcop_usb *fc_usb = usb_get_intfdata(intf); - flexcop_usb_transfer_exit(fc_usb); - flexcop_device_exit(fc_usb->fc_dev); - flexcop_usb_exit(fc_usb); - flexcop_device_kfree(fc_usb->fc_dev); - info("%s successfully deinitialized and disconnected.", DRIVER_NAME); -} - -static struct usb_device_id flexcop_usb_table [] = { - { USB_DEVICE(0x0af7, 0x0101) }, - { } -}; -MODULE_DEVICE_TABLE (usb, flexcop_usb_table); - -/* usb specific object needed to register this driver with the usb subsystem */ -static struct usb_driver flexcop_usb_driver = { - .name = "b2c2_flexcop_usb", - .probe = flexcop_usb_probe, - .disconnect = flexcop_usb_disconnect, - .id_table = flexcop_usb_table, -}; - -module_usb_driver(flexcop_usb_driver); - -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_NAME); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/b2c2/flexcop.c b/drivers/media/dvb/b2c2/flexcop.c deleted file mode 100644 index b1e8c99f469b..000000000000 --- a/drivers/media/dvb/b2c2/flexcop.c +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III - * flexcop.c - main module part - * Copyright (C) 2004-9 Patrick Boettcher - * based on skystar2-driver Copyright (C) 2003 Vadim Catana, skystar@moldova.cc - * - * Acknowledgements: - * John Jurrius from BBTI, Inc. for extensive support - * with code examples and data books - * Bjarne Steinsbo, bjarne at steinsbo.com (some ideas for rewriting) - * - * Contributions to the skystar2-driver have been done by - * Vincenzo Di Massa, hawk.it at tiscalinet.it (several DiSEqC fixes) - * Roberto Ragusa, r.ragusa at libero.it (polishing, restyling the code) - * Uwe Bugla, uwe.bugla at gmx.de (doing tests, restyling code, writing docu) - * Niklas Peinecke, peinecke at gdv.uni-hannover.de (hardware pid/mac - * filtering) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * 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 Lesser 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 "flexcop.h" - -#define DRIVER_NAME "B2C2 FlexcopII/II(b)/III digital TV receiver chip" -#define DRIVER_AUTHOR "Patrick Boettcher demux->priv; - return flexcop_pid_feed_control(fc, dvbdmxfeed, 1); -} - -static int flexcop_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) -{ - struct flexcop_device *fc = dvbdmxfeed->demux->priv; - return flexcop_pid_feed_control(fc, dvbdmxfeed, 0); -} - -static int flexcop_dvb_init(struct flexcop_device *fc) -{ - int ret = dvb_register_adapter(&fc->dvb_adapter, - "FlexCop Digital TV device", fc->owner, - fc->dev, adapter_nr); - if (ret < 0) { - err("error registering DVB adapter"); - return ret; - } - fc->dvb_adapter.priv = fc; - - fc->demux.dmx.capabilities = (DMX_TS_FILTERING | DMX_SECTION_FILTERING - | DMX_MEMORY_BASED_FILTERING); - fc->demux.priv = fc; - fc->demux.filternum = fc->demux.feednum = FC_MAX_FEED; - fc->demux.start_feed = flexcop_dvb_start_feed; - fc->demux.stop_feed = flexcop_dvb_stop_feed; - fc->demux.write_to_decoder = NULL; - - ret = dvb_dmx_init(&fc->demux); - if (ret < 0) { - err("dvb_dmx failed: error %d", ret); - goto err_dmx; - } - - fc->hw_frontend.source = DMX_FRONTEND_0; - - fc->dmxdev.filternum = fc->demux.feednum; - fc->dmxdev.demux = &fc->demux.dmx; - fc->dmxdev.capabilities = 0; - ret = dvb_dmxdev_init(&fc->dmxdev, &fc->dvb_adapter); - if (ret < 0) { - err("dvb_dmxdev_init failed: error %d", ret); - goto err_dmx_dev; - } - - ret = fc->demux.dmx.add_frontend(&fc->demux.dmx, &fc->hw_frontend); - if (ret < 0) { - err("adding hw_frontend to dmx failed: error %d", ret); - goto err_dmx_add_hw_frontend; - } - - fc->mem_frontend.source = DMX_MEMORY_FE; - ret = fc->demux.dmx.add_frontend(&fc->demux.dmx, &fc->mem_frontend); - if (ret < 0) { - err("adding mem_frontend to dmx failed: error %d", ret); - goto err_dmx_add_mem_frontend; - } - - ret = fc->demux.dmx.connect_frontend(&fc->demux.dmx, &fc->hw_frontend); - if (ret < 0) { - err("connect frontend failed: error %d", ret); - goto err_connect_frontend; - } - - ret = dvb_net_init(&fc->dvb_adapter, &fc->dvbnet, &fc->demux.dmx); - if (ret < 0) { - err("dvb_net_init failed: error %d", ret); - goto err_net; - } - - fc->init_state |= FC_STATE_DVB_INIT; - return 0; - -err_net: - fc->demux.dmx.disconnect_frontend(&fc->demux.dmx); -err_connect_frontend: - fc->demux.dmx.remove_frontend(&fc->demux.dmx, &fc->mem_frontend); -err_dmx_add_mem_frontend: - fc->demux.dmx.remove_frontend(&fc->demux.dmx, &fc->hw_frontend); -err_dmx_add_hw_frontend: - dvb_dmxdev_release(&fc->dmxdev); -err_dmx_dev: - dvb_dmx_release(&fc->demux); -err_dmx: - dvb_unregister_adapter(&fc->dvb_adapter); - return ret; -} - -static void flexcop_dvb_exit(struct flexcop_device *fc) -{ - if (fc->init_state & FC_STATE_DVB_INIT) { - dvb_net_release(&fc->dvbnet); - - fc->demux.dmx.close(&fc->demux.dmx); - fc->demux.dmx.remove_frontend(&fc->demux.dmx, - &fc->mem_frontend); - fc->demux.dmx.remove_frontend(&fc->demux.dmx, - &fc->hw_frontend); - dvb_dmxdev_release(&fc->dmxdev); - dvb_dmx_release(&fc->demux); - dvb_unregister_adapter(&fc->dvb_adapter); - deb_info("deinitialized dvb stuff\n"); - } - fc->init_state &= ~FC_STATE_DVB_INIT; -} - -/* these methods are necessary to achieve the long-term-goal of hiding the - * struct flexcop_device from the bus-parts */ -void flexcop_pass_dmx_data(struct flexcop_device *fc, u8 *buf, u32 len) -{ - dvb_dmx_swfilter(&fc->demux, buf, len); -} -EXPORT_SYMBOL(flexcop_pass_dmx_data); - -void flexcop_pass_dmx_packets(struct flexcop_device *fc, u8 *buf, u32 no) -{ - dvb_dmx_swfilter_packets(&fc->demux, buf, no); -} -EXPORT_SYMBOL(flexcop_pass_dmx_packets); - -static void flexcop_reset(struct flexcop_device *fc) -{ - flexcop_ibi_value v210, v204; - - /* reset the flexcop itself */ - fc->write_ibi_reg(fc,ctrl_208,ibi_zero); - - v210.raw = 0; - v210.sw_reset_210.reset_block_000 = 1; - v210.sw_reset_210.reset_block_100 = 1; - v210.sw_reset_210.reset_block_200 = 1; - v210.sw_reset_210.reset_block_300 = 1; - v210.sw_reset_210.reset_block_400 = 1; - v210.sw_reset_210.reset_block_500 = 1; - v210.sw_reset_210.reset_block_600 = 1; - v210.sw_reset_210.reset_block_700 = 1; - v210.sw_reset_210.Block_reset_enable = 0xb2; - v210.sw_reset_210.Special_controls = 0xc259; - fc->write_ibi_reg(fc,sw_reset_210,v210); - msleep(1); - - /* reset the periphical devices */ - - v204 = fc->read_ibi_reg(fc,misc_204); - v204.misc_204.Per_reset_sig = 0; - fc->write_ibi_reg(fc,misc_204,v204); - msleep(1); - v204.misc_204.Per_reset_sig = 1; - fc->write_ibi_reg(fc,misc_204,v204); -} - -void flexcop_reset_block_300(struct flexcop_device *fc) -{ - flexcop_ibi_value v208_save = fc->read_ibi_reg(fc, ctrl_208), - v210 = fc->read_ibi_reg(fc, sw_reset_210); - - deb_rdump("208: %08x, 210: %08x\n", v208_save.raw, v210.raw); - fc->write_ibi_reg(fc,ctrl_208,ibi_zero); - - v210.sw_reset_210.reset_block_300 = 1; - v210.sw_reset_210.Block_reset_enable = 0xb2; - - fc->write_ibi_reg(fc,sw_reset_210,v210); - fc->write_ibi_reg(fc,ctrl_208,v208_save); -} - -struct flexcop_device *flexcop_device_kmalloc(size_t bus_specific_len) -{ - void *bus; - struct flexcop_device *fc = kzalloc(sizeof(struct flexcop_device), - GFP_KERNEL); - if (!fc) { - err("no memory"); - return NULL; - } - - bus = kzalloc(bus_specific_len, GFP_KERNEL); - if (!bus) { - err("no memory"); - kfree(fc); - return NULL; - } - - fc->bus_specific = bus; - - return fc; -} -EXPORT_SYMBOL(flexcop_device_kmalloc); - -void flexcop_device_kfree(struct flexcop_device *fc) -{ - kfree(fc->bus_specific); - kfree(fc); -} -EXPORT_SYMBOL(flexcop_device_kfree); - -int flexcop_device_initialize(struct flexcop_device *fc) -{ - int ret; - ibi_zero.raw = 0; - - flexcop_reset(fc); - flexcop_determine_revision(fc); - flexcop_sram_init(fc); - flexcop_hw_filter_init(fc); - flexcop_smc_ctrl(fc, 0); - - ret = flexcop_dvb_init(fc); - if (ret) - goto error; - - /* i2c has to be done before doing EEProm stuff - - * because the EEProm is accessed via i2c */ - ret = flexcop_i2c_init(fc); - if (ret) - goto error; - - /* do the MAC address reading after initializing the dvb_adapter */ - if (fc->get_mac_addr(fc, 0) == 0) { - u8 *b = fc->dvb_adapter.proposed_mac; - info("MAC address = %pM", b); - flexcop_set_mac_filter(fc,b); - flexcop_mac_filter_ctrl(fc,1); - } else - warn("reading of MAC address failed.\n"); - - ret = flexcop_frontend_init(fc); - if (ret) - goto error; - - flexcop_device_name(fc,"initialization of","complete"); - return 0; - -error: - flexcop_device_exit(fc); - return ret; -} -EXPORT_SYMBOL(flexcop_device_initialize); - -void flexcop_device_exit(struct flexcop_device *fc) -{ - flexcop_frontend_exit(fc); - flexcop_i2c_exit(fc); - flexcop_dvb_exit(fc); -} -EXPORT_SYMBOL(flexcop_device_exit); - -static int flexcop_module_init(void) -{ - info(DRIVER_NAME " loaded successfully"); - return 0; -} - -static void flexcop_module_cleanup(void) -{ - info(DRIVER_NAME " unloaded successfully"); -} - -module_init(flexcop_module_init); -module_exit(flexcop_module_cleanup); - -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_NAME); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/bt8xx/Kconfig b/drivers/media/dvb/bt8xx/Kconfig deleted file mode 100644 index 8668e634c7ec..000000000000 --- a/drivers/media/dvb/bt8xx/Kconfig +++ /dev/null @@ -1,22 +0,0 @@ -config DVB_BT8XX - tristate "BT8xx based PCI cards" - depends on DVB_CORE && PCI && I2C && VIDEO_BT848 - select DVB_MT352 if !DVB_FE_CUSTOMISE - select DVB_SP887X if !DVB_FE_CUSTOMISE - select DVB_NXT6000 if !DVB_FE_CUSTOMISE - select DVB_CX24110 if !DVB_FE_CUSTOMISE - select DVB_OR51211 if !DVB_FE_CUSTOMISE - select DVB_LGDT330X if !DVB_FE_CUSTOMISE - select DVB_ZL10353 if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_SIMPLE if !MEDIA_TUNER_CUSTOMISE - help - Support for PCI cards based on the Bt8xx PCI bridge. Examples are - the Nebula cards, the Pinnacle PCTV cards, the Twinhan DST cards, - the pcHDTV HD2000 cards, the DViCO FusionHDTV Lite cards, and - some AVerMedia cards. - - Since these cards have no MPEG decoder onboard, they transmit - only compressed MPEG data over the PCI bus, so you need - an external software decoder to watch TV on your computer. - - Say Y if you own such a device and want to use it. diff --git a/drivers/media/dvb/bt8xx/Makefile b/drivers/media/dvb/bt8xx/Makefile deleted file mode 100644 index 0713b3af2050..000000000000 --- a/drivers/media/dvb/bt8xx/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -obj-$(CONFIG_DVB_BT8XX) += bt878.o dvb-bt8xx.o dst.o dst_ca.o - -ccflags-y += -Idrivers/media/dvb/dvb-core -ccflags-y += -Idrivers/media/dvb/frontends -ccflags-y += -Idrivers/media/video/bt8xx -ccflags-y += -Idrivers/media/common/tuners diff --git a/drivers/media/dvb/bt8xx/dst_ca.c b/drivers/media/dvb/bt8xx/dst_ca.c deleted file mode 100644 index 66f52f116b60..000000000000 --- a/drivers/media/dvb/bt8xx/dst_ca.c +++ /dev/null @@ -1,725 +0,0 @@ -/* - CA-driver for TwinHan DST Frontend/Card - - Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include "dvbdev.h" -#include "dvb_frontend.h" -#include "dst_ca.h" -#include "dst_common.h" - -#define DST_CA_ERROR 0 -#define DST_CA_NOTICE 1 -#define DST_CA_INFO 2 -#define DST_CA_DEBUG 3 - -#define dprintk(x, y, z, format, arg...) do { \ - if (z) { \ - if ((x > DST_CA_ERROR) && (x > y)) \ - printk(KERN_ERR "%s: " format "\n", __func__ , ##arg); \ - else if ((x > DST_CA_NOTICE) && (x > y)) \ - printk(KERN_NOTICE "%s: " format "\n", __func__ , ##arg); \ - else if ((x > DST_CA_INFO) && (x > y)) \ - printk(KERN_INFO "%s: " format "\n", __func__ , ##arg); \ - else if ((x > DST_CA_DEBUG) && (x > y)) \ - printk(KERN_DEBUG "%s: " format "\n", __func__ , ##arg); \ - } else { \ - if (x > y) \ - printk(format, ## arg); \ - } \ -} while(0) - - -static DEFINE_MUTEX(dst_ca_mutex); -static unsigned int verbose = 5; -module_param(verbose, int, 0644); -MODULE_PARM_DESC(verbose, "verbose startup messages, default is 1 (yes)"); - -/* Need some more work */ -static int ca_set_slot_descr(void) -{ - /* We could make this more graceful ? */ - return -EOPNOTSUPP; -} - -/* Need some more work */ -static int ca_set_pid(void) -{ - /* We could make this more graceful ? */ - return -EOPNOTSUPP; -} - -static void put_command_and_length(u8 *data, int command, int length) -{ - data[0] = (command >> 16) & 0xff; - data[1] = (command >> 8) & 0xff; - data[2] = command & 0xff; - data[3] = length; -} - -static void put_checksum(u8 *check_string, int length) -{ - dprintk(verbose, DST_CA_DEBUG, 1, " Computing string checksum."); - dprintk(verbose, DST_CA_DEBUG, 1, " -> string length : 0x%02x", length); - check_string[length] = dst_check_sum (check_string, length); - dprintk(verbose, DST_CA_DEBUG, 1, " -> checksum : 0x%02x", check_string[length]); -} - -static int dst_ci_command(struct dst_state* state, u8 * data, u8 *ca_string, u8 len, int read) -{ - u8 reply; - - mutex_lock(&state->dst_mutex); - dst_comm_init(state); - msleep(65); - - if (write_dst(state, data, len)) { - dprintk(verbose, DST_CA_INFO, 1, " Write not successful, trying to recover"); - dst_error_recovery(state); - goto error; - } - if ((dst_pio_disable(state)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " DST PIO disable failed."); - goto error; - } - if (read_dst(state, &reply, GET_ACK) < 0) { - dprintk(verbose, DST_CA_INFO, 1, " Read not successful, trying to recover"); - dst_error_recovery(state); - goto error; - } - if (read) { - if (! dst_wait_dst_ready(state, LONG_DELAY)) { - dprintk(verbose, DST_CA_NOTICE, 1, " 8820 not ready"); - goto error; - } - if (read_dst(state, ca_string, 128) < 0) { /* Try to make this dynamic */ - dprintk(verbose, DST_CA_INFO, 1, " Read not successful, trying to recover"); - dst_error_recovery(state); - goto error; - } - } - mutex_unlock(&state->dst_mutex); - return 0; - -error: - mutex_unlock(&state->dst_mutex); - return -EIO; -} - - -static int dst_put_ci(struct dst_state *state, u8 *data, int len, u8 *ca_string, int read) -{ - u8 dst_ca_comm_err = 0; - - while (dst_ca_comm_err < RETRIES) { - dprintk(verbose, DST_CA_NOTICE, 1, " Put Command"); - if (dst_ci_command(state, data, ca_string, len, read)) { // If error - dst_error_recovery(state); - dst_ca_comm_err++; // work required here. - } else { - break; - } - } - - if(dst_ca_comm_err == RETRIES) - return -1; - - return 0; -} - - - -static int ca_get_app_info(struct dst_state *state) -{ - int length, str_length; - static u8 command[8] = {0x07, 0x40, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff}; - - put_checksum(&command[0], command[0]); - if ((dst_put_ci(state, command, sizeof(command), state->messages, GET_REPLY)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); - return -1; - } - dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !"); - dprintk(verbose, DST_CA_INFO, 1, " ================================ CI Module Application Info ======================================"); - dprintk(verbose, DST_CA_INFO, 1, " Application Type=[%d], Application Vendor=[%d], Vendor Code=[%d]\n%s: Application info=[%s]", - state->messages[7], (state->messages[8] << 8) | state->messages[9], - (state->messages[10] << 8) | state->messages[11], __func__, (char *)(&state->messages[12])); - dprintk(verbose, DST_CA_INFO, 1, " =================================================================================================="); - - // Transform dst message to correct application_info message - length = state->messages[5]; - str_length = length - 6; - if (str_length < 0) { - str_length = 0; - dprintk(verbose, DST_CA_ERROR, 1, "Invalid string length returned in ca_get_app_info(). Recovering."); - } - - // First, the command and length fields - put_command_and_length(&state->messages[0], CA_APP_INFO, length); - - // Copy application_type, application_manufacturer and manufacturer_code - memcpy(&state->messages[4], &state->messages[7], 5); - - // Set string length and copy string - state->messages[9] = str_length; - memcpy(&state->messages[10], &state->messages[12], str_length); - - return 0; -} - -static int ca_get_ca_info(struct dst_state *state) -{ - int srcPtr, dstPtr, i, num_ids; - static u8 slot_command[8] = {0x07, 0x40, 0x00, 0x00, 0x02, 0x00, 0x00, 0xff}; - const int in_system_id_pos = 8, out_system_id_pos = 4, in_num_ids_pos = 7; - - put_checksum(&slot_command[0], slot_command[0]); - if ((dst_put_ci(state, slot_command, sizeof (slot_command), state->messages, GET_REPLY)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); - return -1; - } - dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !"); - - // Print raw data - dprintk(verbose, DST_CA_INFO, 0, " DST data = ["); - for (i = 0; i < state->messages[0] + 1; i++) { - dprintk(verbose, DST_CA_INFO, 0, " 0x%02x", state->messages[i]); - } - dprintk(verbose, DST_CA_INFO, 0, "]\n"); - - // Set the command and length of the output - num_ids = state->messages[in_num_ids_pos]; - if (num_ids >= 100) { - num_ids = 100; - dprintk(verbose, DST_CA_ERROR, 1, "Invalid number of ids (>100). Recovering."); - } - put_command_and_length(&state->messages[0], CA_INFO, num_ids * 2); - - dprintk(verbose, DST_CA_INFO, 0, " CA_INFO = ["); - srcPtr = in_system_id_pos; - dstPtr = out_system_id_pos; - for(i = 0; i < num_ids; i++) { - dprintk(verbose, DST_CA_INFO, 0, " 0x%02x%02x", state->messages[srcPtr + 0], state->messages[srcPtr + 1]); - // Append to output - state->messages[dstPtr + 0] = state->messages[srcPtr + 0]; - state->messages[dstPtr + 1] = state->messages[srcPtr + 1]; - srcPtr += 2; - dstPtr += 2; - } - dprintk(verbose, DST_CA_INFO, 0, "]\n"); - - return 0; -} - -static int ca_get_slot_caps(struct dst_state *state, struct ca_caps *p_ca_caps, void __user *arg) -{ - int i; - u8 slot_cap[256]; - static u8 slot_command[8] = {0x07, 0x40, 0x02, 0x00, 0x02, 0x00, 0x00, 0xff}; - - put_checksum(&slot_command[0], slot_command[0]); - if ((dst_put_ci(state, slot_command, sizeof (slot_command), slot_cap, GET_REPLY)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); - return -1; - } - dprintk(verbose, DST_CA_NOTICE, 1, " -->dst_put_ci SUCCESS !"); - - /* Will implement the rest soon */ - - dprintk(verbose, DST_CA_INFO, 1, " Slot cap = [%d]", slot_cap[7]); - dprintk(verbose, DST_CA_INFO, 0, "===================================\n"); - for (i = 0; i < slot_cap[0] + 1; i++) - dprintk(verbose, DST_CA_INFO, 0, " %d", slot_cap[i]); - dprintk(verbose, DST_CA_INFO, 0, "\n"); - - p_ca_caps->slot_num = 1; - p_ca_caps->slot_type = 1; - p_ca_caps->descr_num = slot_cap[7]; - p_ca_caps->descr_type = 1; - - if (copy_to_user(arg, p_ca_caps, sizeof (struct ca_caps))) - return -EFAULT; - - return 0; -} - -/* Need some more work */ -static int ca_get_slot_descr(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg) -{ - return -EOPNOTSUPP; -} - - -static int ca_get_slot_info(struct dst_state *state, struct ca_slot_info *p_ca_slot_info, void __user *arg) -{ - int i; - static u8 slot_command[8] = {0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}; - - u8 *slot_info = state->messages; - - put_checksum(&slot_command[0], 7); - if ((dst_put_ci(state, slot_command, sizeof (slot_command), slot_info, GET_REPLY)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); - return -1; - } - dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !"); - - /* Will implement the rest soon */ - - dprintk(verbose, DST_CA_INFO, 1, " Slot info = [%d]", slot_info[3]); - dprintk(verbose, DST_CA_INFO, 0, "===================================\n"); - for (i = 0; i < 8; i++) - dprintk(verbose, DST_CA_INFO, 0, " %d", slot_info[i]); - dprintk(verbose, DST_CA_INFO, 0, "\n"); - - if (slot_info[4] & 0x80) { - p_ca_slot_info->flags = CA_CI_MODULE_PRESENT; - p_ca_slot_info->num = 1; - p_ca_slot_info->type = CA_CI; - } else if (slot_info[4] & 0x40) { - p_ca_slot_info->flags = CA_CI_MODULE_READY; - p_ca_slot_info->num = 1; - p_ca_slot_info->type = CA_CI; - } else - p_ca_slot_info->flags = 0; - - if (copy_to_user(arg, p_ca_slot_info, sizeof (struct ca_slot_info))) - return -EFAULT; - - return 0; -} - - -static int ca_get_message(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg) -{ - u8 i = 0; - u32 command = 0; - - if (copy_from_user(p_ca_message, arg, sizeof (struct ca_msg))) - return -EFAULT; - - if (p_ca_message->msg) { - dprintk(verbose, DST_CA_NOTICE, 1, " Message = [%02x %02x %02x]", p_ca_message->msg[0], p_ca_message->msg[1], p_ca_message->msg[2]); - - for (i = 0; i < 3; i++) { - command = command | p_ca_message->msg[i]; - if (i < 2) - command = command << 8; - } - dprintk(verbose, DST_CA_NOTICE, 1, " Command=[0x%x]", command); - - switch (command) { - case CA_APP_INFO: - memcpy(p_ca_message->msg, state->messages, 128); - if (copy_to_user(arg, p_ca_message, sizeof (struct ca_msg)) ) - return -EFAULT; - break; - case CA_INFO: - memcpy(p_ca_message->msg, state->messages, 128); - if (copy_to_user(arg, p_ca_message, sizeof (struct ca_msg)) ) - return -EFAULT; - break; - } - } - - return 0; -} - -static int handle_dst_tag(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer, u32 length) -{ - if (state->dst_hw_cap & DST_TYPE_HAS_SESSION) { - hw_buffer->msg[2] = p_ca_message->msg[1]; /* MSB */ - hw_buffer->msg[3] = p_ca_message->msg[2]; /* LSB */ - } else { - if (length > 247) { - dprintk(verbose, DST_CA_ERROR, 1, " Message too long ! *** Bailing Out *** !"); - return -1; - } - hw_buffer->msg[0] = (length & 0xff) + 7; - hw_buffer->msg[1] = 0x40; - hw_buffer->msg[2] = 0x03; - hw_buffer->msg[3] = 0x00; - hw_buffer->msg[4] = 0x03; - hw_buffer->msg[5] = length & 0xff; - hw_buffer->msg[6] = 0x00; - - /* - * Need to compute length for EN50221 section 8.3.2, for the time being - * assuming 8.3.2 is not applicable - */ - memcpy(&hw_buffer->msg[7], &p_ca_message->msg[4], length); - } - - return 0; -} - -static int write_to_8820(struct dst_state *state, struct ca_msg *hw_buffer, u8 length, u8 reply) -{ - if ((dst_put_ci(state, hw_buffer->msg, length, hw_buffer->msg, reply)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " DST-CI Command failed."); - dprintk(verbose, DST_CA_NOTICE, 1, " Resetting DST."); - rdc_reset_state(state); - return -1; - } - dprintk(verbose, DST_CA_NOTICE, 1, " DST-CI Command success."); - - return 0; -} - -static u32 asn_1_decode(u8 *asn_1_array) -{ - u8 length_field = 0, word_count = 0, count = 0; - u32 length = 0; - - length_field = asn_1_array[0]; - dprintk(verbose, DST_CA_DEBUG, 1, " Length field=[%02x]", length_field); - if (length_field < 0x80) { - length = length_field & 0x7f; - dprintk(verbose, DST_CA_DEBUG, 1, " Length=[%02x]\n", length); - } else { - word_count = length_field & 0x7f; - for (count = 0; count < word_count; count++) { - length = length << 8; - length += asn_1_array[count + 1]; - dprintk(verbose, DST_CA_DEBUG, 1, " Length=[%04x]", length); - } - } - return length; -} - -static int debug_string(u8 *msg, u32 length, u32 offset) -{ - u32 i; - - dprintk(verbose, DST_CA_DEBUG, 0, " String=[ "); - for (i = offset; i < length; i++) - dprintk(verbose, DST_CA_DEBUG, 0, "%02x ", msg[i]); - dprintk(verbose, DST_CA_DEBUG, 0, "]\n"); - - return 0; -} - - -static int ca_set_pmt(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer, u8 reply, u8 query) -{ - u32 length = 0; - u8 tag_length = 8; - - length = asn_1_decode(&p_ca_message->msg[3]); - dprintk(verbose, DST_CA_DEBUG, 1, " CA Message length=[%d]", length); - debug_string(&p_ca_message->msg[4], length, 0); /* length is excluding tag & length */ - - memset(hw_buffer->msg, '\0', length); - handle_dst_tag(state, p_ca_message, hw_buffer, length); - put_checksum(hw_buffer->msg, hw_buffer->msg[0]); - - debug_string(hw_buffer->msg, (length + tag_length), 0); /* tags too */ - write_to_8820(state, hw_buffer, (length + tag_length), reply); - - return 0; -} - - -/* Board supports CA PMT reply ? */ -static int dst_check_ca_pmt(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer) -{ - int ca_pmt_reply_test = 0; - - /* Do test board */ - /* Not there yet but soon */ - - /* CA PMT Reply capable */ - if (ca_pmt_reply_test) { - if ((ca_set_pmt(state, p_ca_message, hw_buffer, 1, GET_REPLY)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " ca_set_pmt.. failed !"); - return -1; - } - - /* Process CA PMT Reply */ - /* will implement soon */ - dprintk(verbose, DST_CA_ERROR, 1, " Not there yet"); - } - /* CA PMT Reply not capable */ - if (!ca_pmt_reply_test) { - if ((ca_set_pmt(state, p_ca_message, hw_buffer, 0, NO_REPLY)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " ca_set_pmt.. failed !"); - return -1; - } - dprintk(verbose, DST_CA_NOTICE, 1, " ca_set_pmt.. success !"); - /* put a dummy message */ - - } - return 0; -} - -static int ca_send_message(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg) -{ - int i = 0; - - u32 command = 0; - struct ca_msg *hw_buffer; - int result = 0; - - if ((hw_buffer = kmalloc(sizeof (struct ca_msg), GFP_KERNEL)) == NULL) { - dprintk(verbose, DST_CA_ERROR, 1, " Memory allocation failure"); - return -ENOMEM; - } - dprintk(verbose, DST_CA_DEBUG, 1, " "); - - if (copy_from_user(p_ca_message, arg, sizeof (struct ca_msg))) { - result = -EFAULT; - goto free_mem_and_exit; - } - - - if (p_ca_message->msg) { - /* EN50221 tag */ - command = 0; - - for (i = 0; i < 3; i++) { - command = command | p_ca_message->msg[i]; - if (i < 2) - command = command << 8; - } - dprintk(verbose, DST_CA_DEBUG, 1, " Command=[0x%x]\n", command); - - switch (command) { - case CA_PMT: - dprintk(verbose, DST_CA_DEBUG, 1, "Command = SEND_CA_PMT"); - if ((ca_set_pmt(state, p_ca_message, hw_buffer, 0, 0)) < 0) { // code simplification started - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_PMT Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_PMT Success !"); - break; - case CA_PMT_REPLY: - dprintk(verbose, DST_CA_INFO, 1, "Command = CA_PMT_REPLY"); - /* Have to handle the 2 basic types of cards here */ - if ((dst_check_ca_pmt(state, p_ca_message, hw_buffer)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_PMT_REPLY Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_PMT_REPLY Success !"); - break; - case CA_APP_INFO_ENQUIRY: // only for debugging - dprintk(verbose, DST_CA_INFO, 1, " Getting Cam Application information"); - - if ((ca_get_app_info(state)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_APP_INFO_ENQUIRY Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_APP_INFO_ENQUIRY Success !"); - break; - case CA_INFO_ENQUIRY: - dprintk(verbose, DST_CA_INFO, 1, " Getting CA Information"); - - if ((ca_get_ca_info(state)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_INFO_ENQUIRY Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_INFO_ENQUIRY Success !"); - break; - } - } -free_mem_and_exit: - kfree (hw_buffer); - - return result; -} - -static long dst_ca_ioctl(struct file *file, unsigned int cmd, unsigned long ioctl_arg) -{ - struct dvb_device *dvbdev; - struct dst_state *state; - struct ca_slot_info *p_ca_slot_info; - struct ca_caps *p_ca_caps; - struct ca_msg *p_ca_message; - void __user *arg = (void __user *)ioctl_arg; - int result = 0; - - mutex_lock(&dst_ca_mutex); - dvbdev = file->private_data; - state = (struct dst_state *)dvbdev->priv; - p_ca_message = kmalloc(sizeof (struct ca_msg), GFP_KERNEL); - p_ca_slot_info = kmalloc(sizeof (struct ca_slot_info), GFP_KERNEL); - p_ca_caps = kmalloc(sizeof (struct ca_caps), GFP_KERNEL); - if (!p_ca_message || !p_ca_slot_info || !p_ca_caps) { - dprintk(verbose, DST_CA_ERROR, 1, " Memory allocation failure"); - result = -ENOMEM; - goto free_mem_and_exit; - } - - /* We have now only the standard ioctl's, the driver is upposed to handle internals. */ - switch (cmd) { - case CA_SEND_MSG: - dprintk(verbose, DST_CA_INFO, 1, " Sending message"); - if ((ca_send_message(state, p_ca_message, arg)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SEND_MSG Failed !"); - result = -1; - goto free_mem_and_exit; - } - break; - case CA_GET_MSG: - dprintk(verbose, DST_CA_INFO, 1, " Getting message"); - if ((ca_get_message(state, p_ca_message, arg)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_MSG Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_MSG Success !"); - break; - case CA_RESET: - dprintk(verbose, DST_CA_ERROR, 1, " Resetting DST"); - dst_error_bailout(state); - msleep(4000); - break; - case CA_GET_SLOT_INFO: - dprintk(verbose, DST_CA_INFO, 1, " Getting Slot info"); - if ((ca_get_slot_info(state, p_ca_slot_info, arg)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_SLOT_INFO Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_SLOT_INFO Success !"); - break; - case CA_GET_CAP: - dprintk(verbose, DST_CA_INFO, 1, " Getting Slot capabilities"); - if ((ca_get_slot_caps(state, p_ca_caps, arg)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_CAP Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_CAP Success !"); - break; - case CA_GET_DESCR_INFO: - dprintk(verbose, DST_CA_INFO, 1, " Getting descrambler description"); - if ((ca_get_slot_descr(state, p_ca_message, arg)) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_DESCR_INFO Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_DESCR_INFO Success !"); - break; - case CA_SET_DESCR: - dprintk(verbose, DST_CA_INFO, 1, " Setting descrambler"); - if ((ca_set_slot_descr()) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SET_DESCR Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_SET_DESCR Success !"); - break; - case CA_SET_PID: - dprintk(verbose, DST_CA_INFO, 1, " Setting PID"); - if ((ca_set_pid()) < 0) { - dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SET_PID Failed !"); - result = -1; - goto free_mem_and_exit; - } - dprintk(verbose, DST_CA_INFO, 1, " -->CA_SET_PID Success !"); - default: - result = -EOPNOTSUPP; - }; - free_mem_and_exit: - kfree (p_ca_message); - kfree (p_ca_slot_info); - kfree (p_ca_caps); - - mutex_unlock(&dst_ca_mutex); - return result; -} - -static int dst_ca_open(struct inode *inode, struct file *file) -{ - dprintk(verbose, DST_CA_DEBUG, 1, " Device opened [%p] ", file); - try_module_get(THIS_MODULE); - - return 0; -} - -static int dst_ca_release(struct inode *inode, struct file *file) -{ - dprintk(verbose, DST_CA_DEBUG, 1, " Device closed."); - module_put(THIS_MODULE); - - return 0; -} - -static ssize_t dst_ca_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) -{ - ssize_t bytes_read = 0; - - dprintk(verbose, DST_CA_DEBUG, 1, " Device read."); - - return bytes_read; -} - -static ssize_t dst_ca_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) -{ - dprintk(verbose, DST_CA_DEBUG, 1, " Device write."); - - return 0; -} - -static const struct file_operations dst_ca_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = dst_ca_ioctl, - .open = dst_ca_open, - .release = dst_ca_release, - .read = dst_ca_read, - .write = dst_ca_write, - .llseek = noop_llseek, -}; - -static struct dvb_device dvbdev_ca = { - .priv = NULL, - .users = 1, - .readers = 1, - .writers = 1, - .fops = &dst_ca_fops -}; - -struct dvb_device *dst_ca_attach(struct dst_state *dst, struct dvb_adapter *dvb_adapter) -{ - struct dvb_device *dvbdev; - - dprintk(verbose, DST_CA_ERROR, 1, "registering DST-CA device"); - if (dvb_register_device(dvb_adapter, &dvbdev, &dvbdev_ca, dst, DVB_DEVICE_CA) == 0) { - dst->dst_ca = dvbdev; - return dst->dst_ca; - } - - return NULL; -} - -EXPORT_SYMBOL(dst_ca_attach); - -MODULE_DESCRIPTION("DST DVB-S/T/C Combo CA driver"); -MODULE_AUTHOR("Manu Abraham"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/ddbridge/Kconfig b/drivers/media/dvb/ddbridge/Kconfig deleted file mode 100644 index d099e1a12c85..000000000000 --- a/drivers/media/dvb/ddbridge/Kconfig +++ /dev/null @@ -1,18 +0,0 @@ -config DVB_DDBRIDGE - tristate "Digital Devices bridge support" - depends on DVB_CORE && PCI && I2C - select DVB_LNBP21 if !DVB_FE_CUSTOMISE - select DVB_STV6110x if !DVB_FE_CUSTOMISE - select DVB_STV090x if !DVB_FE_CUSTOMISE - select DVB_DRXK if !DVB_FE_CUSTOMISE - select DVB_TDA18271C2DD if !DVB_FE_CUSTOMISE - ---help--- - Support for cards with the Digital Devices PCI express bridge: - - Octopus PCIe Bridge - - Octopus mini PCIe Bridge - - Octopus LE - - DuoFlex S2 Octopus - - DuoFlex CT Octopus - - cineS2(v6) - - Say Y if you own such a card and want to use it. diff --git a/drivers/media/dvb/ddbridge/Makefile b/drivers/media/dvb/ddbridge/Makefile deleted file mode 100644 index 38019bafb862..000000000000 --- a/drivers/media/dvb/ddbridge/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# Makefile for the ddbridge device driver -# - -ddbridge-objs := ddbridge-core.o - -obj-$(CONFIG_DVB_DDBRIDGE) += ddbridge.o - -ccflags-y += -Idrivers/media/dvb/dvb-core/ -ccflags-y += -Idrivers/media/dvb/frontends/ -ccflags-y += -Idrivers/media/common/tuners/ - -# For the staging CI driver cxd2099 -ccflags-y += -Idrivers/staging/media/cxd2099/ diff --git a/drivers/media/dvb/ddbridge/ddbridge-core.c b/drivers/media/dvb/ddbridge/ddbridge-core.c deleted file mode 100644 index ebf3f05839d2..000000000000 --- a/drivers/media/dvb/ddbridge/ddbridge-core.c +++ /dev/null @@ -1,1723 +0,0 @@ -/* - * ddbridge.c: Digital Devices PCIe bridge driver - * - * Copyright (C) 2010-2011 Digital Devices GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 only, as published by the Free Software Foundation. - * - * - * 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 - * Or, point your browser to http://www.gnu.org/copyleft/gpl.html - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ddbridge.h" - -#include "ddbridge-regs.h" - -#include "tda18271c2dd.h" -#include "stv6110x.h" -#include "stv090x.h" -#include "lnbh24.h" -#include "drxk.h" - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -/* MSI had problems with lost interrupts, fixed but needs testing */ -#undef CONFIG_PCI_MSI - -/******************************************************************************/ - -static int i2c_read(struct i2c_adapter *adapter, u8 adr, u8 *val) -{ - struct i2c_msg msgs[1] = {{.addr = adr, .flags = I2C_M_RD, - .buf = val, .len = 1 } }; - return (i2c_transfer(adapter, msgs, 1) == 1) ? 0 : -1; -} - -static int i2c_read_reg(struct i2c_adapter *adapter, u8 adr, u8 reg, u8 *val) -{ - struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, - .buf = ®, .len = 1 }, - {.addr = adr, .flags = I2C_M_RD, - .buf = val, .len = 1 } }; - return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; -} - -static int i2c_read_reg16(struct i2c_adapter *adapter, u8 adr, - u16 reg, u8 *val) -{ - u8 msg[2] = {reg>>8, reg&0xff}; - struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, - .buf = msg, .len = 2}, - {.addr = adr, .flags = I2C_M_RD, - .buf = val, .len = 1} }; - return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; -} - -static int ddb_i2c_cmd(struct ddb_i2c *i2c, u32 adr, u32 cmd) -{ - struct ddb *dev = i2c->dev; - int stat; - u32 val; - - i2c->done = 0; - ddbwritel((adr << 9) | cmd, i2c->regs + I2C_COMMAND); - stat = wait_event_timeout(i2c->wq, i2c->done == 1, HZ); - if (stat <= 0) { - printk(KERN_ERR "I2C timeout\n"); - { /* MSI debugging*/ - u32 istat = ddbreadl(INTERRUPT_STATUS); - printk(KERN_ERR "IRS %08x\n", istat); - ddbwritel(istat, INTERRUPT_ACK); - } - return -EIO; - } - val = ddbreadl(i2c->regs+I2C_COMMAND); - if (val & 0x70000) - return -EIO; - return 0; -} - -static int ddb_i2c_master_xfer(struct i2c_adapter *adapter, - struct i2c_msg msg[], int num) -{ - struct ddb_i2c *i2c = (struct ddb_i2c *)i2c_get_adapdata(adapter); - struct ddb *dev = i2c->dev; - u8 addr = 0; - - if (num) - addr = msg[0].addr; - - if (num == 2 && msg[1].flags & I2C_M_RD && - !(msg[0].flags & I2C_M_RD)) { - memcpy_toio(dev->regs + I2C_TASKMEM_BASE + i2c->wbuf, - msg[0].buf, msg[0].len); - ddbwritel(msg[0].len|(msg[1].len << 16), - i2c->regs+I2C_TASKLENGTH); - if (!ddb_i2c_cmd(i2c, addr, 1)) { - memcpy_fromio(msg[1].buf, - dev->regs + I2C_TASKMEM_BASE + i2c->rbuf, - msg[1].len); - return num; - } - } - - if (num == 1 && !(msg[0].flags & I2C_M_RD)) { - ddbcpyto(I2C_TASKMEM_BASE + i2c->wbuf, msg[0].buf, msg[0].len); - ddbwritel(msg[0].len, i2c->regs + I2C_TASKLENGTH); - if (!ddb_i2c_cmd(i2c, addr, 2)) - return num; - } - if (num == 1 && (msg[0].flags & I2C_M_RD)) { - ddbwritel(msg[0].len << 16, i2c->regs + I2C_TASKLENGTH); - if (!ddb_i2c_cmd(i2c, addr, 3)) { - ddbcpyfrom(msg[0].buf, - I2C_TASKMEM_BASE + i2c->rbuf, msg[0].len); - return num; - } - } - return -EIO; -} - - -static u32 ddb_i2c_functionality(struct i2c_adapter *adap) -{ - return I2C_FUNC_SMBUS_EMUL; -} - -struct i2c_algorithm ddb_i2c_algo = { - .master_xfer = ddb_i2c_master_xfer, - .functionality = ddb_i2c_functionality, -}; - -static void ddb_i2c_release(struct ddb *dev) -{ - int i; - struct ddb_i2c *i2c; - struct i2c_adapter *adap; - - for (i = 0; i < dev->info->port_num; i++) { - i2c = &dev->i2c[i]; - adap = &i2c->adap; - i2c_del_adapter(adap); - } -} - -static int ddb_i2c_init(struct ddb *dev) -{ - int i, j, stat = 0; - struct ddb_i2c *i2c; - struct i2c_adapter *adap; - - for (i = 0; i < dev->info->port_num; i++) { - i2c = &dev->i2c[i]; - i2c->dev = dev; - i2c->nr = i; - i2c->wbuf = i * (I2C_TASKMEM_SIZE / 4); - i2c->rbuf = i2c->wbuf + (I2C_TASKMEM_SIZE / 8); - i2c->regs = 0x80 + i * 0x20; - ddbwritel(I2C_SPEED_100, i2c->regs + I2C_TIMING); - ddbwritel((i2c->rbuf << 16) | i2c->wbuf, - i2c->regs + I2C_TASKADDRESS); - init_waitqueue_head(&i2c->wq); - - adap = &i2c->adap; - i2c_set_adapdata(adap, i2c); -#ifdef I2C_ADAP_CLASS_TV_DIGITAL - adap->class = I2C_ADAP_CLASS_TV_DIGITAL|I2C_CLASS_TV_ANALOG; -#else -#ifdef I2C_CLASS_TV_ANALOG - adap->class = I2C_CLASS_TV_ANALOG; -#endif -#endif - strcpy(adap->name, "ddbridge"); - adap->algo = &ddb_i2c_algo; - adap->algo_data = (void *)i2c; - adap->dev.parent = &dev->pdev->dev; - stat = i2c_add_adapter(adap); - if (stat) - break; - } - if (stat) - for (j = 0; j < i; j++) { - i2c = &dev->i2c[j]; - adap = &i2c->adap; - i2c_del_adapter(adap); - } - return stat; -} - - -/******************************************************************************/ -/******************************************************************************/ -/******************************************************************************/ - -#if 0 -static void set_table(struct ddb *dev, u32 off, - dma_addr_t *pbuf, u32 num) -{ - u32 i, base; - u64 mem; - - base = DMA_BASE_ADDRESS_TABLE + off; - for (i = 0; i < num; i++) { - mem = pbuf[i]; - ddbwritel(mem & 0xffffffff, base + i * 8); - ddbwritel(mem >> 32, base + i * 8 + 4); - } -} -#endif - -static void ddb_address_table(struct ddb *dev) -{ - u32 i, j, base; - u64 mem; - dma_addr_t *pbuf; - - for (i = 0; i < dev->info->port_num * 2; i++) { - base = DMA_BASE_ADDRESS_TABLE + i * 0x100; - pbuf = dev->input[i].pbuf; - for (j = 0; j < dev->input[i].dma_buf_num; j++) { - mem = pbuf[j]; - ddbwritel(mem & 0xffffffff, base + j * 8); - ddbwritel(mem >> 32, base + j * 8 + 4); - } - } - for (i = 0; i < dev->info->port_num; i++) { - base = DMA_BASE_ADDRESS_TABLE + 0x800 + i * 0x100; - pbuf = dev->output[i].pbuf; - for (j = 0; j < dev->output[i].dma_buf_num; j++) { - mem = pbuf[j]; - ddbwritel(mem & 0xffffffff, base + j * 8); - ddbwritel(mem >> 32, base + j * 8 + 4); - } - } -} - -static void io_free(struct pci_dev *pdev, u8 **vbuf, - dma_addr_t *pbuf, u32 size, int num) -{ - int i; - - for (i = 0; i < num; i++) { - if (vbuf[i]) { - pci_free_consistent(pdev, size, vbuf[i], pbuf[i]); - vbuf[i] = 0; - } - } -} - -static int io_alloc(struct pci_dev *pdev, u8 **vbuf, - dma_addr_t *pbuf, u32 size, int num) -{ - int i; - - for (i = 0; i < num; i++) { - vbuf[i] = pci_alloc_consistent(pdev, size, &pbuf[i]); - if (!vbuf[i]) - return -ENOMEM; - } - return 0; -} - -static int ddb_buffers_alloc(struct ddb *dev) -{ - int i; - struct ddb_port *port; - - for (i = 0; i < dev->info->port_num; i++) { - port = &dev->port[i]; - switch (port->class) { - case DDB_PORT_TUNER: - if (io_alloc(dev->pdev, port->input[0]->vbuf, - port->input[0]->pbuf, - port->input[0]->dma_buf_size, - port->input[0]->dma_buf_num) < 0) - return -1; - if (io_alloc(dev->pdev, port->input[1]->vbuf, - port->input[1]->pbuf, - port->input[1]->dma_buf_size, - port->input[1]->dma_buf_num) < 0) - return -1; - break; - case DDB_PORT_CI: - if (io_alloc(dev->pdev, port->input[0]->vbuf, - port->input[0]->pbuf, - port->input[0]->dma_buf_size, - port->input[0]->dma_buf_num) < 0) - return -1; - if (io_alloc(dev->pdev, port->output->vbuf, - port->output->pbuf, - port->output->dma_buf_size, - port->output->dma_buf_num) < 0) - return -1; - break; - default: - break; - } - } - ddb_address_table(dev); - return 0; -} - -static void ddb_buffers_free(struct ddb *dev) -{ - int i; - struct ddb_port *port; - - for (i = 0; i < dev->info->port_num; i++) { - port = &dev->port[i]; - io_free(dev->pdev, port->input[0]->vbuf, - port->input[0]->pbuf, - port->input[0]->dma_buf_size, - port->input[0]->dma_buf_num); - io_free(dev->pdev, port->input[1]->vbuf, - port->input[1]->pbuf, - port->input[1]->dma_buf_size, - port->input[1]->dma_buf_num); - io_free(dev->pdev, port->output->vbuf, - port->output->pbuf, - port->output->dma_buf_size, - port->output->dma_buf_num); - } -} - -static void ddb_input_start(struct ddb_input *input) -{ - struct ddb *dev = input->port->dev; - - spin_lock_irq(&input->lock); - input->cbuf = 0; - input->coff = 0; - - /* reset */ - ddbwritel(0, TS_INPUT_CONTROL(input->nr)); - ddbwritel(2, TS_INPUT_CONTROL(input->nr)); - ddbwritel(0, TS_INPUT_CONTROL(input->nr)); - - ddbwritel((1 << 16) | - (input->dma_buf_num << 11) | - (input->dma_buf_size >> 7), - DMA_BUFFER_SIZE(input->nr)); - ddbwritel(0, DMA_BUFFER_ACK(input->nr)); - - ddbwritel(1, DMA_BASE_WRITE); - ddbwritel(3, DMA_BUFFER_CONTROL(input->nr)); - ddbwritel(9, TS_INPUT_CONTROL(input->nr)); - input->running = 1; - spin_unlock_irq(&input->lock); -} - -static void ddb_input_stop(struct ddb_input *input) -{ - struct ddb *dev = input->port->dev; - - spin_lock_irq(&input->lock); - ddbwritel(0, TS_INPUT_CONTROL(input->nr)); - ddbwritel(0, DMA_BUFFER_CONTROL(input->nr)); - input->running = 0; - spin_unlock_irq(&input->lock); -} - -static void ddb_output_start(struct ddb_output *output) -{ - struct ddb *dev = output->port->dev; - - spin_lock_irq(&output->lock); - output->cbuf = 0; - output->coff = 0; - ddbwritel(0, TS_OUTPUT_CONTROL(output->nr)); - ddbwritel(2, TS_OUTPUT_CONTROL(output->nr)); - ddbwritel(0, TS_OUTPUT_CONTROL(output->nr)); - ddbwritel(0x3c, TS_OUTPUT_CONTROL(output->nr)); - ddbwritel((1 << 16) | - (output->dma_buf_num << 11) | - (output->dma_buf_size >> 7), - DMA_BUFFER_SIZE(output->nr + 8)); - ddbwritel(0, DMA_BUFFER_ACK(output->nr + 8)); - - ddbwritel(1, DMA_BASE_READ); - ddbwritel(3, DMA_BUFFER_CONTROL(output->nr + 8)); - /* ddbwritel(0xbd, TS_OUTPUT_CONTROL(output->nr)); */ - ddbwritel(0x1d, TS_OUTPUT_CONTROL(output->nr)); - output->running = 1; - spin_unlock_irq(&output->lock); -} - -static void ddb_output_stop(struct ddb_output *output) -{ - struct ddb *dev = output->port->dev; - - spin_lock_irq(&output->lock); - ddbwritel(0, TS_OUTPUT_CONTROL(output->nr)); - ddbwritel(0, DMA_BUFFER_CONTROL(output->nr + 8)); - output->running = 0; - spin_unlock_irq(&output->lock); -} - -static u32 ddb_output_free(struct ddb_output *output) -{ - u32 idx, off, stat = output->stat; - s32 diff; - - idx = (stat >> 11) & 0x1f; - off = (stat & 0x7ff) << 7; - - if (output->cbuf != idx) { - if ((((output->cbuf + 1) % output->dma_buf_num) == idx) && - (output->dma_buf_size - output->coff <= 188)) - return 0; - return 188; - } - diff = off - output->coff; - if (diff <= 0 || diff > 188) - return 188; - return 0; -} - -static ssize_t ddb_output_write(struct ddb_output *output, - const u8 *buf, size_t count) -{ - struct ddb *dev = output->port->dev; - u32 idx, off, stat = output->stat; - u32 left = count, len; - - idx = (stat >> 11) & 0x1f; - off = (stat & 0x7ff) << 7; - - while (left) { - len = output->dma_buf_size - output->coff; - if ((((output->cbuf + 1) % output->dma_buf_num) == idx) && - (off == 0)) { - if (len <= 188) - break; - len -= 188; - } - if (output->cbuf == idx) { - if (off > output->coff) { -#if 1 - len = off - output->coff; - len -= (len % 188); - if (len <= 188) - -#endif - break; - len -= 188; - } - } - if (len > left) - len = left; - if (copy_from_user(output->vbuf[output->cbuf] + output->coff, - buf, len)) - return -EIO; - left -= len; - buf += len; - output->coff += len; - if (output->coff == output->dma_buf_size) { - output->coff = 0; - output->cbuf = ((output->cbuf + 1) % output->dma_buf_num); - } - ddbwritel((output->cbuf << 11) | (output->coff >> 7), - DMA_BUFFER_ACK(output->nr + 8)); - } - return count - left; -} - -static u32 ddb_input_avail(struct ddb_input *input) -{ - struct ddb *dev = input->port->dev; - u32 idx, off, stat = input->stat; - u32 ctrl = ddbreadl(DMA_BUFFER_CONTROL(input->nr)); - - idx = (stat >> 11) & 0x1f; - off = (stat & 0x7ff) << 7; - - if (ctrl & 4) { - printk(KERN_ERR "IA %d %d %08x\n", idx, off, ctrl); - ddbwritel(input->stat, DMA_BUFFER_ACK(input->nr)); - return 0; - } - if (input->cbuf != idx) - return 188; - return 0; -} - -static ssize_t ddb_input_read(struct ddb_input *input, u8 *buf, size_t count) -{ - struct ddb *dev = input->port->dev; - u32 left = count; - u32 idx, free, stat = input->stat; - int ret; - - idx = (stat >> 11) & 0x1f; - - while (left) { - if (input->cbuf == idx) - return count - left; - free = input->dma_buf_size - input->coff; - if (free > left) - free = left; - ret = copy_to_user(buf, input->vbuf[input->cbuf] + - input->coff, free); - if (ret) - return -EFAULT; - input->coff += free; - if (input->coff == input->dma_buf_size) { - input->coff = 0; - input->cbuf = (input->cbuf+1) % input->dma_buf_num; - } - left -= free; - ddbwritel((input->cbuf << 11) | (input->coff >> 7), - DMA_BUFFER_ACK(input->nr)); - } - return count; -} - -/******************************************************************************/ -/******************************************************************************/ -/******************************************************************************/ - -#if 0 -static struct ddb_input *fe2input(struct ddb *dev, struct dvb_frontend *fe) -{ - int i; - - for (i = 0; i < dev->info->port_num * 2; i++) { - if (dev->input[i].fe == fe) - return &dev->input[i]; - } - return NULL; -} -#endif - -static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct ddb_input *input = fe->sec_priv; - struct ddb_port *port = input->port; - int status; - - if (enable) { - mutex_lock(&port->i2c_gate_lock); - status = input->gate_ctrl(fe, 1); - } else { - status = input->gate_ctrl(fe, 0); - mutex_unlock(&port->i2c_gate_lock); - } - return status; -} - -static int demod_attach_drxk(struct ddb_input *input) -{ - struct i2c_adapter *i2c = &input->port->i2c->adap; - struct dvb_frontend *fe; - struct drxk_config config; - - memset(&config, 0, sizeof(config)); - config.microcode_name = "drxk_a3.mc"; - config.qam_demod_parameter_count = 4; - config.adr = 0x29 + (input->nr & 1); - - fe = input->fe = dvb_attach(drxk_attach, &config, i2c); - if (!input->fe) { - printk(KERN_ERR "No DRXK found!\n"); - return -ENODEV; - } - fe->sec_priv = input; - input->gate_ctrl = fe->ops.i2c_gate_ctrl; - fe->ops.i2c_gate_ctrl = drxk_gate_ctrl; - return 0; -} - -static int tuner_attach_tda18271(struct ddb_input *input) -{ - struct i2c_adapter *i2c = &input->port->i2c->adap; - struct dvb_frontend *fe; - - if (input->fe->ops.i2c_gate_ctrl) - input->fe->ops.i2c_gate_ctrl(input->fe, 1); - fe = dvb_attach(tda18271c2dd_attach, input->fe, i2c, 0x60); - if (!fe) { - printk(KERN_ERR "No TDA18271 found!\n"); - return -ENODEV; - } - if (input->fe->ops.i2c_gate_ctrl) - input->fe->ops.i2c_gate_ctrl(input->fe, 0); - return 0; -} - -/******************************************************************************/ -/******************************************************************************/ -/******************************************************************************/ - -static struct stv090x_config stv0900 = { - .device = STV0900, - .demod_mode = STV090x_DUAL, - .clk_mode = STV090x_CLK_EXT, - - .xtal = 27000000, - .address = 0x69, - - .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, - .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, - - .repeater_level = STV090x_RPTLEVEL_16, - - .adc1_range = STV090x_ADC_1Vpp, - .adc2_range = STV090x_ADC_1Vpp, - - .diseqc_envelope_mode = true, -}; - -static struct stv090x_config stv0900_aa = { - .device = STV0900, - .demod_mode = STV090x_DUAL, - .clk_mode = STV090x_CLK_EXT, - - .xtal = 27000000, - .address = 0x68, - - .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, - .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, - - .repeater_level = STV090x_RPTLEVEL_16, - - .adc1_range = STV090x_ADC_1Vpp, - .adc2_range = STV090x_ADC_1Vpp, - - .diseqc_envelope_mode = true, -}; - -static struct stv6110x_config stv6110a = { - .addr = 0x60, - .refclk = 27000000, - .clk_div = 1, -}; - -static struct stv6110x_config stv6110b = { - .addr = 0x63, - .refclk = 27000000, - .clk_div = 1, -}; - -static int demod_attach_stv0900(struct ddb_input *input, int type) -{ - struct i2c_adapter *i2c = &input->port->i2c->adap; - struct stv090x_config *feconf = type ? &stv0900_aa : &stv0900; - - input->fe = dvb_attach(stv090x_attach, feconf, i2c, - (input->nr & 1) ? STV090x_DEMODULATOR_1 - : STV090x_DEMODULATOR_0); - if (!input->fe) { - printk(KERN_ERR "No STV0900 found!\n"); - return -ENODEV; - } - if (!dvb_attach(lnbh24_attach, input->fe, i2c, 0, - 0, (input->nr & 1) ? - (0x09 - type) : (0x0b - type))) { - printk(KERN_ERR "No LNBH24 found!\n"); - return -ENODEV; - } - return 0; -} - -static int tuner_attach_stv6110(struct ddb_input *input, int type) -{ - struct i2c_adapter *i2c = &input->port->i2c->adap; - struct stv090x_config *feconf = type ? &stv0900_aa : &stv0900; - struct stv6110x_config *tunerconf = (input->nr & 1) ? - &stv6110b : &stv6110a; - struct stv6110x_devctl *ctl; - - ctl = dvb_attach(stv6110x_attach, input->fe, tunerconf, i2c); - if (!ctl) { - printk(KERN_ERR "No STV6110X found!\n"); - return -ENODEV; - } - printk(KERN_INFO "attach tuner input %d adr %02x\n", - input->nr, tunerconf->addr); - - feconf->tuner_init = ctl->tuner_init; - feconf->tuner_sleep = ctl->tuner_sleep; - feconf->tuner_set_mode = ctl->tuner_set_mode; - feconf->tuner_set_frequency = ctl->tuner_set_frequency; - feconf->tuner_get_frequency = ctl->tuner_get_frequency; - feconf->tuner_set_bandwidth = ctl->tuner_set_bandwidth; - feconf->tuner_get_bandwidth = ctl->tuner_get_bandwidth; - feconf->tuner_set_bbgain = ctl->tuner_set_bbgain; - feconf->tuner_get_bbgain = ctl->tuner_get_bbgain; - feconf->tuner_set_refclk = ctl->tuner_set_refclk; - feconf->tuner_get_status = ctl->tuner_get_status; - - return 0; -} - -static int my_dvb_dmx_ts_card_init(struct dvb_demux *dvbdemux, char *id, - int (*start_feed)(struct dvb_demux_feed *), - int (*stop_feed)(struct dvb_demux_feed *), - void *priv) -{ - dvbdemux->priv = priv; - - dvbdemux->filternum = 256; - dvbdemux->feednum = 256; - dvbdemux->start_feed = start_feed; - dvbdemux->stop_feed = stop_feed; - dvbdemux->write_to_decoder = NULL; - dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | - DMX_SECTION_FILTERING | - DMX_MEMORY_BASED_FILTERING); - return dvb_dmx_init(dvbdemux); -} - -static int my_dvb_dmxdev_ts_card_init(struct dmxdev *dmxdev, - struct dvb_demux *dvbdemux, - struct dmx_frontend *hw_frontend, - struct dmx_frontend *mem_frontend, - struct dvb_adapter *dvb_adapter) -{ - int ret; - - dmxdev->filternum = 256; - dmxdev->demux = &dvbdemux->dmx; - dmxdev->capabilities = 0; - ret = dvb_dmxdev_init(dmxdev, dvb_adapter); - if (ret < 0) - return ret; - - hw_frontend->source = DMX_FRONTEND_0; - dvbdemux->dmx.add_frontend(&dvbdemux->dmx, hw_frontend); - mem_frontend->source = DMX_MEMORY_FE; - dvbdemux->dmx.add_frontend(&dvbdemux->dmx, mem_frontend); - return dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, hw_frontend); -} - -static int start_feed(struct dvb_demux_feed *dvbdmxfeed) -{ - struct dvb_demux *dvbdmx = dvbdmxfeed->demux; - struct ddb_input *input = dvbdmx->priv; - - if (!input->users) - ddb_input_start(input); - - return ++input->users; -} - -static int stop_feed(struct dvb_demux_feed *dvbdmxfeed) -{ - struct dvb_demux *dvbdmx = dvbdmxfeed->demux; - struct ddb_input *input = dvbdmx->priv; - - if (--input->users) - return input->users; - - ddb_input_stop(input); - return 0; -} - - -static void dvb_input_detach(struct ddb_input *input) -{ - struct dvb_adapter *adap = &input->adap; - struct dvb_demux *dvbdemux = &input->demux; - - switch (input->attached) { - case 5: - if (input->fe2) - dvb_unregister_frontend(input->fe2); - if (input->fe) { - dvb_unregister_frontend(input->fe); - dvb_frontend_detach(input->fe); - input->fe = NULL; - } - case 4: - dvb_net_release(&input->dvbnet); - - case 3: - dvbdemux->dmx.close(&dvbdemux->dmx); - dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, - &input->hw_frontend); - dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, - &input->mem_frontend); - dvb_dmxdev_release(&input->dmxdev); - - case 2: - dvb_dmx_release(&input->demux); - - case 1: - dvb_unregister_adapter(adap); - } - input->attached = 0; -} - -static int dvb_input_attach(struct ddb_input *input) -{ - int ret; - struct ddb_port *port = input->port; - struct dvb_adapter *adap = &input->adap; - struct dvb_demux *dvbdemux = &input->demux; - - ret = dvb_register_adapter(adap, "DDBridge", THIS_MODULE, - &input->port->dev->pdev->dev, - adapter_nr); - if (ret < 0) { - printk(KERN_ERR "ddbridge: Could not register adapter." - "Check if you enabled enough adapters in dvb-core!\n"); - return ret; - } - input->attached = 1; - - ret = my_dvb_dmx_ts_card_init(dvbdemux, "SW demux", - start_feed, - stop_feed, input); - if (ret < 0) - return ret; - input->attached = 2; - - ret = my_dvb_dmxdev_ts_card_init(&input->dmxdev, &input->demux, - &input->hw_frontend, - &input->mem_frontend, adap); - if (ret < 0) - return ret; - input->attached = 3; - - ret = dvb_net_init(adap, &input->dvbnet, input->dmxdev.demux); - if (ret < 0) - return ret; - input->attached = 4; - - input->fe = 0; - switch (port->type) { - case DDB_TUNER_DVBS_ST: - if (demod_attach_stv0900(input, 0) < 0) - return -ENODEV; - if (tuner_attach_stv6110(input, 0) < 0) - return -ENODEV; - if (input->fe) { - if (dvb_register_frontend(adap, input->fe) < 0) - return -ENODEV; - } - break; - case DDB_TUNER_DVBS_ST_AA: - if (demod_attach_stv0900(input, 1) < 0) - return -ENODEV; - if (tuner_attach_stv6110(input, 1) < 0) - return -ENODEV; - if (input->fe) { - if (dvb_register_frontend(adap, input->fe) < 0) - return -ENODEV; - } - break; - case DDB_TUNER_DVBCT_TR: - if (demod_attach_drxk(input) < 0) - return -ENODEV; - if (tuner_attach_tda18271(input) < 0) - return -ENODEV; - if (input->fe) { - if (dvb_register_frontend(adap, input->fe) < 0) - return -ENODEV; - } - if (input->fe2) { - if (dvb_register_frontend(adap, input->fe2) < 0) - return -ENODEV; - input->fe2->tuner_priv = input->fe->tuner_priv; - memcpy(&input->fe2->ops.tuner_ops, - &input->fe->ops.tuner_ops, - sizeof(struct dvb_tuner_ops)); - } - break; - } - input->attached = 5; - return 0; -} - -/****************************************************************************/ -/****************************************************************************/ - -static ssize_t ts_write(struct file *file, const char *buf, - size_t count, loff_t *ppos) -{ - struct dvb_device *dvbdev = file->private_data; - struct ddb_output *output = dvbdev->priv; - size_t left = count; - int stat; - - while (left) { - if (ddb_output_free(output) < 188) { - if (file->f_flags & O_NONBLOCK) - break; - if (wait_event_interruptible( - output->wq, ddb_output_free(output) >= 188) < 0) - break; - } - stat = ddb_output_write(output, buf, left); - if (stat < 0) - break; - buf += stat; - left -= stat; - } - return (left == count) ? -EAGAIN : (count - left); -} - -static ssize_t ts_read(struct file *file, char *buf, - size_t count, loff_t *ppos) -{ - struct dvb_device *dvbdev = file->private_data; - struct ddb_output *output = dvbdev->priv; - struct ddb_input *input = output->port->input[0]; - int left, read; - - count -= count % 188; - left = count; - while (left) { - if (ddb_input_avail(input) < 188) { - if (file->f_flags & O_NONBLOCK) - break; - if (wait_event_interruptible( - input->wq, ddb_input_avail(input) >= 188) < 0) - break; - } - read = ddb_input_read(input, buf, left); - if (read < 0) - return read; - left -= read; - buf += read; - } - return (left == count) ? -EAGAIN : (count - left); -} - -static unsigned int ts_poll(struct file *file, poll_table *wait) -{ - /* - struct dvb_device *dvbdev = file->private_data; - struct ddb_output *output = dvbdev->priv; - struct ddb_input *input = output->port->input[0]; - */ - unsigned int mask = 0; - -#if 0 - if (data_avail_to_read) - mask |= POLLIN | POLLRDNORM; - if (data_avail_to_write) - mask |= POLLOUT | POLLWRNORM; - - poll_wait(file, &read_queue, wait); - poll_wait(file, &write_queue, wait); -#endif - return mask; -} - -static const struct file_operations ci_fops = { - .owner = THIS_MODULE, - .read = ts_read, - .write = ts_write, - .open = dvb_generic_open, - .release = dvb_generic_release, - .poll = ts_poll, - .mmap = 0, -}; - -static struct dvb_device dvbdev_ci = { - .priv = 0, - .readers = -1, - .writers = -1, - .users = -1, - .fops = &ci_fops, -}; - -/****************************************************************************/ -/****************************************************************************/ -/****************************************************************************/ - -static void input_tasklet(unsigned long data) -{ - struct ddb_input *input = (struct ddb_input *) data; - struct ddb *dev = input->port->dev; - - spin_lock(&input->lock); - if (!input->running) { - spin_unlock(&input->lock); - return; - } - input->stat = ddbreadl(DMA_BUFFER_CURRENT(input->nr)); - - if (input->port->class == DDB_PORT_TUNER) { - if (4&ddbreadl(DMA_BUFFER_CONTROL(input->nr))) - printk(KERN_ERR "Overflow input %d\n", input->nr); - while (input->cbuf != ((input->stat >> 11) & 0x1f) - || (4&ddbreadl(DMA_BUFFER_CONTROL(input->nr)))) { - dvb_dmx_swfilter_packets(&input->demux, - input->vbuf[input->cbuf], - input->dma_buf_size / 188); - - input->cbuf = (input->cbuf + 1) % input->dma_buf_num; - ddbwritel((input->cbuf << 11), - DMA_BUFFER_ACK(input->nr)); - input->stat = ddbreadl(DMA_BUFFER_CURRENT(input->nr)); - } - } - if (input->port->class == DDB_PORT_CI) - wake_up(&input->wq); - spin_unlock(&input->lock); -} - -static void output_tasklet(unsigned long data) -{ - struct ddb_output *output = (struct ddb_output *) data; - struct ddb *dev = output->port->dev; - - spin_lock(&output->lock); - if (!output->running) { - spin_unlock(&output->lock); - return; - } - output->stat = ddbreadl(DMA_BUFFER_CURRENT(output->nr + 8)); - wake_up(&output->wq); - spin_unlock(&output->lock); -} - - -struct cxd2099_cfg cxd_cfg = { - .bitrate = 62000, - .adr = 0x40, - .polarity = 1, - .clock_mode = 1, -}; - -static int ddb_ci_attach(struct ddb_port *port) -{ - int ret; - - ret = dvb_register_adapter(&port->output->adap, - "DDBridge", - THIS_MODULE, - &port->dev->pdev->dev, - adapter_nr); - if (ret < 0) - return ret; - port->en = cxd2099_attach(&cxd_cfg, port, &port->i2c->adap); - if (!port->en) { - dvb_unregister_adapter(&port->output->adap); - return -ENODEV; - } - ddb_input_start(port->input[0]); - ddb_output_start(port->output); - dvb_ca_en50221_init(&port->output->adap, - port->en, 0, 1); - ret = dvb_register_device(&port->output->adap, &port->output->dev, - &dvbdev_ci, (void *) port->output, - DVB_DEVICE_SEC); - return ret; -} - -static int ddb_port_attach(struct ddb_port *port) -{ - int ret = 0; - - switch (port->class) { - case DDB_PORT_TUNER: - ret = dvb_input_attach(port->input[0]); - if (ret < 0) - break; - ret = dvb_input_attach(port->input[1]); - break; - case DDB_PORT_CI: - ret = ddb_ci_attach(port); - break; - default: - break; - } - if (ret < 0) - printk(KERN_ERR "port_attach on port %d failed\n", port->nr); - return ret; -} - -static int ddb_ports_attach(struct ddb *dev) -{ - int i, ret = 0; - struct ddb_port *port; - - for (i = 0; i < dev->info->port_num; i++) { - port = &dev->port[i]; - ret = ddb_port_attach(port); - if (ret < 0) - break; - } - return ret; -} - -static void ddb_ports_detach(struct ddb *dev) -{ - int i; - struct ddb_port *port; - - for (i = 0; i < dev->info->port_num; i++) { - port = &dev->port[i]; - switch (port->class) { - case DDB_PORT_TUNER: - dvb_input_detach(port->input[0]); - dvb_input_detach(port->input[1]); - break; - case DDB_PORT_CI: - if (port->output->dev) - dvb_unregister_device(port->output->dev); - if (port->en) { - ddb_input_stop(port->input[0]); - ddb_output_stop(port->output); - dvb_ca_en50221_release(port->en); - kfree(port->en); - port->en = 0; - dvb_unregister_adapter(&port->output->adap); - } - break; - } - } -} - -/****************************************************************************/ -/****************************************************************************/ - -static int port_has_ci(struct ddb_port *port) -{ - u8 val; - return i2c_read_reg(&port->i2c->adap, 0x40, 0, &val) ? 0 : 1; -} - -static int port_has_stv0900(struct ddb_port *port) -{ - u8 val; - if (i2c_read_reg16(&port->i2c->adap, 0x69, 0xf100, &val) < 0) - return 0; - return 1; -} - -static int port_has_stv0900_aa(struct ddb_port *port) -{ - u8 val; - if (i2c_read_reg16(&port->i2c->adap, 0x68, 0xf100, &val) < 0) - return 0; - return 1; -} - -static int port_has_drxks(struct ddb_port *port) -{ - u8 val; - if (i2c_read(&port->i2c->adap, 0x29, &val) < 0) - return 0; - if (i2c_read(&port->i2c->adap, 0x2a, &val) < 0) - return 0; - return 1; -} - -static void ddb_port_probe(struct ddb_port *port) -{ - struct ddb *dev = port->dev; - char *modname = "NO MODULE"; - - port->class = DDB_PORT_NONE; - - if (port_has_ci(port)) { - modname = "CI"; - port->class = DDB_PORT_CI; - ddbwritel(I2C_SPEED_400, port->i2c->regs + I2C_TIMING); - } else if (port_has_stv0900(port)) { - modname = "DUAL DVB-S2"; - port->class = DDB_PORT_TUNER; - port->type = DDB_TUNER_DVBS_ST; - ddbwritel(I2C_SPEED_100, port->i2c->regs + I2C_TIMING); - } else if (port_has_stv0900_aa(port)) { - modname = "DUAL DVB-S2"; - port->class = DDB_PORT_TUNER; - port->type = DDB_TUNER_DVBS_ST_AA; - ddbwritel(I2C_SPEED_100, port->i2c->regs + I2C_TIMING); - } else if (port_has_drxks(port)) { - modname = "DUAL DVB-C/T"; - port->class = DDB_PORT_TUNER; - port->type = DDB_TUNER_DVBCT_TR; - ddbwritel(I2C_SPEED_400, port->i2c->regs + I2C_TIMING); - } - printk(KERN_INFO "Port %d (TAB %d): %s\n", - port->nr, port->nr+1, modname); -} - -static void ddb_input_init(struct ddb_port *port, int nr) -{ - struct ddb *dev = port->dev; - struct ddb_input *input = &dev->input[nr]; - - input->nr = nr; - input->port = port; - input->dma_buf_num = INPUT_DMA_BUFS; - input->dma_buf_size = INPUT_DMA_SIZE; - ddbwritel(0, TS_INPUT_CONTROL(nr)); - ddbwritel(2, TS_INPUT_CONTROL(nr)); - ddbwritel(0, TS_INPUT_CONTROL(nr)); - ddbwritel(0, DMA_BUFFER_ACK(nr)); - tasklet_init(&input->tasklet, input_tasklet, (unsigned long) input); - spin_lock_init(&input->lock); - init_waitqueue_head(&input->wq); -} - -static void ddb_output_init(struct ddb_port *port, int nr) -{ - struct ddb *dev = port->dev; - struct ddb_output *output = &dev->output[nr]; - output->nr = nr; - output->port = port; - output->dma_buf_num = OUTPUT_DMA_BUFS; - output->dma_buf_size = OUTPUT_DMA_SIZE; - - ddbwritel(0, TS_OUTPUT_CONTROL(nr)); - ddbwritel(2, TS_OUTPUT_CONTROL(nr)); - ddbwritel(0, TS_OUTPUT_CONTROL(nr)); - tasklet_init(&output->tasklet, output_tasklet, (unsigned long) output); - init_waitqueue_head(&output->wq); -} - -static void ddb_ports_init(struct ddb *dev) -{ - int i; - struct ddb_port *port; - - for (i = 0; i < dev->info->port_num; i++) { - port = &dev->port[i]; - port->dev = dev; - port->nr = i; - port->i2c = &dev->i2c[i]; - port->input[0] = &dev->input[2 * i]; - port->input[1] = &dev->input[2 * i + 1]; - port->output = &dev->output[i]; - - mutex_init(&port->i2c_gate_lock); - ddb_port_probe(port); - ddb_input_init(port, 2 * i); - ddb_input_init(port, 2 * i + 1); - ddb_output_init(port, i); - } -} - -static void ddb_ports_release(struct ddb *dev) -{ - int i; - struct ddb_port *port; - - for (i = 0; i < dev->info->port_num; i++) { - port = &dev->port[i]; - port->dev = dev; - tasklet_kill(&port->input[0]->tasklet); - tasklet_kill(&port->input[1]->tasklet); - tasklet_kill(&port->output->tasklet); - } -} - -/****************************************************************************/ -/****************************************************************************/ -/****************************************************************************/ - -static void irq_handle_i2c(struct ddb *dev, int n) -{ - struct ddb_i2c *i2c = &dev->i2c[n]; - - i2c->done = 1; - wake_up(&i2c->wq); -} - -static irqreturn_t irq_handler(int irq, void *dev_id) -{ - struct ddb *dev = (struct ddb *) dev_id; - u32 s = ddbreadl(INTERRUPT_STATUS); - - if (!s) - return IRQ_NONE; - - do { - ddbwritel(s, INTERRUPT_ACK); - - if (s & 0x00000001) - irq_handle_i2c(dev, 0); - if (s & 0x00000002) - irq_handle_i2c(dev, 1); - if (s & 0x00000004) - irq_handle_i2c(dev, 2); - if (s & 0x00000008) - irq_handle_i2c(dev, 3); - - if (s & 0x00000100) - tasklet_schedule(&dev->input[0].tasklet); - if (s & 0x00000200) - tasklet_schedule(&dev->input[1].tasklet); - if (s & 0x00000400) - tasklet_schedule(&dev->input[2].tasklet); - if (s & 0x00000800) - tasklet_schedule(&dev->input[3].tasklet); - if (s & 0x00001000) - tasklet_schedule(&dev->input[4].tasklet); - if (s & 0x00002000) - tasklet_schedule(&dev->input[5].tasklet); - if (s & 0x00004000) - tasklet_schedule(&dev->input[6].tasklet); - if (s & 0x00008000) - tasklet_schedule(&dev->input[7].tasklet); - - if (s & 0x00010000) - tasklet_schedule(&dev->output[0].tasklet); - if (s & 0x00020000) - tasklet_schedule(&dev->output[1].tasklet); - if (s & 0x00040000) - tasklet_schedule(&dev->output[2].tasklet); - if (s & 0x00080000) - tasklet_schedule(&dev->output[3].tasklet); - - /* if (s & 0x000f0000) printk(KERN_DEBUG "%08x\n", istat); */ - } while ((s = ddbreadl(INTERRUPT_STATUS))); - - return IRQ_HANDLED; -} - -/******************************************************************************/ -/******************************************************************************/ -/******************************************************************************/ - -static int flashio(struct ddb *dev, u8 *wbuf, u32 wlen, u8 *rbuf, u32 rlen) -{ - u32 data, shift; - - if (wlen > 4) - ddbwritel(1, SPI_CONTROL); - while (wlen > 4) { - /* FIXME: check for big-endian */ - data = swab32(*(u32 *)wbuf); - wbuf += 4; - wlen -= 4; - ddbwritel(data, SPI_DATA); - while (ddbreadl(SPI_CONTROL) & 0x0004) - ; - } - - if (rlen) - ddbwritel(0x0001 | ((wlen << (8 + 3)) & 0x1f00), SPI_CONTROL); - else - ddbwritel(0x0003 | ((wlen << (8 + 3)) & 0x1f00), SPI_CONTROL); - - data = 0; - shift = ((4 - wlen) * 8); - while (wlen) { - data <<= 8; - data |= *wbuf; - wlen--; - wbuf++; - } - if (shift) - data <<= shift; - ddbwritel(data, SPI_DATA); - while (ddbreadl(SPI_CONTROL) & 0x0004) - ; - - if (!rlen) { - ddbwritel(0, SPI_CONTROL); - return 0; - } - if (rlen > 4) - ddbwritel(1, SPI_CONTROL); - - while (rlen > 4) { - ddbwritel(0xffffffff, SPI_DATA); - while (ddbreadl(SPI_CONTROL) & 0x0004) - ; - data = ddbreadl(SPI_DATA); - *(u32 *) rbuf = swab32(data); - rbuf += 4; - rlen -= 4; - } - ddbwritel(0x0003 | ((rlen << (8 + 3)) & 0x1F00), SPI_CONTROL); - ddbwritel(0xffffffff, SPI_DATA); - while (ddbreadl(SPI_CONTROL) & 0x0004) - ; - - data = ddbreadl(SPI_DATA); - ddbwritel(0, SPI_CONTROL); - - if (rlen < 4) - data <<= ((4 - rlen) * 8); - - while (rlen > 0) { - *rbuf = ((data >> 24) & 0xff); - data <<= 8; - rbuf++; - rlen--; - } - return 0; -} - -#define DDB_MAGIC 'd' - -struct ddb_flashio { - __u8 *write_buf; - __u32 write_len; - __u8 *read_buf; - __u32 read_len; -}; - -#define IOCTL_DDB_FLASHIO _IOWR(DDB_MAGIC, 0x00, struct ddb_flashio) - -#define DDB_NAME "ddbridge" - -static u32 ddb_num; -static struct ddb *ddbs[32]; -static struct class *ddb_class; -static int ddb_major; - -static int ddb_open(struct inode *inode, struct file *file) -{ - struct ddb *dev = ddbs[iminor(inode)]; - - file->private_data = dev; - return 0; -} - -static long ddb_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - struct ddb *dev = file->private_data; - void *parg = (void *)arg; - int res; - - switch (cmd) { - case IOCTL_DDB_FLASHIO: - { - struct ddb_flashio fio; - u8 *rbuf, *wbuf; - - if (copy_from_user(&fio, parg, sizeof(fio))) - return -EFAULT; - - if (fio.write_len > 1028 || fio.read_len > 1028) - return -EINVAL; - if (fio.write_len + fio.read_len > 1028) - return -EINVAL; - - wbuf = &dev->iobuf[0]; - rbuf = wbuf + fio.write_len; - - if (copy_from_user(wbuf, fio.write_buf, fio.write_len)) - return -EFAULT; - res = flashio(dev, wbuf, fio.write_len, rbuf, fio.read_len); - if (res) - return res; - if (copy_to_user(fio.read_buf, rbuf, fio.read_len)) - return -EFAULT; - break; - } - default: - return -ENOTTY; - } - return 0; -} - -static const struct file_operations ddb_fops = { - .unlocked_ioctl = ddb_ioctl, - .open = ddb_open, -}; - -static char *ddb_devnode(struct device *device, umode_t *mode) -{ - struct ddb *dev = dev_get_drvdata(device); - - return kasprintf(GFP_KERNEL, "ddbridge/card%d", dev->nr); -} - -static int ddb_class_create(void) -{ - ddb_major = register_chrdev(0, DDB_NAME, &ddb_fops); - if (ddb_major < 0) - return ddb_major; - - ddb_class = class_create(THIS_MODULE, DDB_NAME); - if (IS_ERR(ddb_class)) { - unregister_chrdev(ddb_major, DDB_NAME); - return -1; - } - ddb_class->devnode = ddb_devnode; - return 0; -} - -static void ddb_class_destroy(void) -{ - class_destroy(ddb_class); - unregister_chrdev(ddb_major, DDB_NAME); -} - -static int ddb_device_create(struct ddb *dev) -{ - dev->nr = ddb_num++; - dev->ddb_dev = device_create(ddb_class, NULL, - MKDEV(ddb_major, dev->nr), - dev, "ddbridge%d", dev->nr); - ddbs[dev->nr] = dev; - if (IS_ERR(dev->ddb_dev)) - return -1; - return 0; -} - -static void ddb_device_destroy(struct ddb *dev) -{ - ddb_num--; - if (IS_ERR(dev->ddb_dev)) - return; - device_destroy(ddb_class, MKDEV(ddb_major, 0)); -} - - -/****************************************************************************/ -/****************************************************************************/ -/****************************************************************************/ - -static void ddb_unmap(struct ddb *dev) -{ - if (dev->regs) - iounmap(dev->regs); - vfree(dev); -} - - -static void __devexit ddb_remove(struct pci_dev *pdev) -{ - struct ddb *dev = (struct ddb *) pci_get_drvdata(pdev); - - ddb_ports_detach(dev); - ddb_i2c_release(dev); - - ddbwritel(0, INTERRUPT_ENABLE); - free_irq(dev->pdev->irq, dev); -#ifdef CONFIG_PCI_MSI - if (dev->msi) - pci_disable_msi(dev->pdev); -#endif - ddb_ports_release(dev); - ddb_buffers_free(dev); - ddb_device_destroy(dev); - - ddb_unmap(dev); - pci_set_drvdata(pdev, 0); - pci_disable_device(pdev); -} - - -static int __devinit ddb_probe(struct pci_dev *pdev, - const struct pci_device_id *id) -{ - struct ddb *dev; - int stat = 0; - int irq_flag = IRQF_SHARED; - - if (pci_enable_device(pdev) < 0) - return -ENODEV; - - dev = vmalloc(sizeof(struct ddb)); - if (dev == NULL) - return -ENOMEM; - memset(dev, 0, sizeof(struct ddb)); - - dev->pdev = pdev; - pci_set_drvdata(pdev, dev); - dev->info = (struct ddb_info *) id->driver_data; - printk(KERN_INFO "DDBridge driver detected: %s\n", dev->info->name); - - dev->regs = ioremap(pci_resource_start(dev->pdev, 0), - pci_resource_len(dev->pdev, 0)); - if (!dev->regs) { - stat = -ENOMEM; - goto fail; - } - printk(KERN_INFO "HW %08x FW %08x\n", ddbreadl(0), ddbreadl(4)); - -#ifdef CONFIG_PCI_MSI - if (pci_msi_enabled()) - stat = pci_enable_msi(dev->pdev); - if (stat) { - printk(KERN_INFO ": MSI not available.\n"); - } else { - irq_flag = 0; - dev->msi = 1; - } -#endif - stat = request_irq(dev->pdev->irq, irq_handler, - irq_flag, "DDBridge", (void *) dev); - if (stat < 0) - goto fail1; - ddbwritel(0, DMA_BASE_WRITE); - ddbwritel(0, DMA_BASE_READ); - ddbwritel(0xffffffff, INTERRUPT_ACK); - ddbwritel(0xfff0f, INTERRUPT_ENABLE); - ddbwritel(0, MSI1_ENABLE); - - if (ddb_i2c_init(dev) < 0) - goto fail1; - ddb_ports_init(dev); - if (ddb_buffers_alloc(dev) < 0) { - printk(KERN_INFO ": Could not allocate buffer memory\n"); - goto fail2; - } - if (ddb_ports_attach(dev) < 0) - goto fail3; - ddb_device_create(dev); - return 0; - -fail3: - ddb_ports_detach(dev); - printk(KERN_ERR "fail3\n"); - ddb_ports_release(dev); -fail2: - printk(KERN_ERR "fail2\n"); - ddb_buffers_free(dev); -fail1: - printk(KERN_ERR "fail1\n"); - if (dev->msi) - pci_disable_msi(dev->pdev); - free_irq(dev->pdev->irq, dev); -fail: - printk(KERN_ERR "fail\n"); - ddb_unmap(dev); - pci_set_drvdata(pdev, 0); - pci_disable_device(pdev); - return -1; -} - -/******************************************************************************/ -/******************************************************************************/ -/******************************************************************************/ - -static struct ddb_info ddb_none = { - .type = DDB_NONE, - .name = "Digital Devices PCIe bridge", -}; - -static struct ddb_info ddb_octopus = { - .type = DDB_OCTOPUS, - .name = "Digital Devices Octopus DVB adapter", - .port_num = 4, -}; - -static struct ddb_info ddb_octopus_le = { - .type = DDB_OCTOPUS, - .name = "Digital Devices Octopus LE DVB adapter", - .port_num = 2, -}; - -static struct ddb_info ddb_v6 = { - .type = DDB_OCTOPUS, - .name = "Digital Devices Cine S2 V6 DVB adapter", - .port_num = 3, -}; - -#define DDVID 0xdd01 /* Digital Devices Vendor ID */ - -#define DDB_ID(_vend, _dev, _subvend, _subdev, _driverdata) { \ - .vendor = _vend, .device = _dev, \ - .subvendor = _subvend, .subdevice = _subdev, \ - .driver_data = (unsigned long)&_driverdata } - -static const struct pci_device_id ddb_id_tbl[] __devinitdata = { - DDB_ID(DDVID, 0x0002, DDVID, 0x0001, ddb_octopus), - DDB_ID(DDVID, 0x0003, DDVID, 0x0001, ddb_octopus), - DDB_ID(DDVID, 0x0003, DDVID, 0x0002, ddb_octopus_le), - DDB_ID(DDVID, 0x0003, DDVID, 0x0010, ddb_octopus), - DDB_ID(DDVID, 0x0003, DDVID, 0x0020, ddb_v6), - /* in case sub-ids got deleted in flash */ - DDB_ID(DDVID, 0x0003, PCI_ANY_ID, PCI_ANY_ID, ddb_none), - {0} -}; -MODULE_DEVICE_TABLE(pci, ddb_id_tbl); - - -static struct pci_driver ddb_pci_driver = { - .name = "DDBridge", - .id_table = ddb_id_tbl, - .probe = ddb_probe, - .remove = __devexit_p(ddb_remove), -}; - -static __init int module_init_ddbridge(void) -{ - printk(KERN_INFO "Digital Devices PCIE bridge driver, " - "Copyright (C) 2010-11 Digital Devices GmbH\n"); - if (ddb_class_create()) - return -1; - return pci_register_driver(&ddb_pci_driver); -} - -static __exit void module_exit_ddbridge(void) -{ - pci_unregister_driver(&ddb_pci_driver); - ddb_class_destroy(); -} - -module_init(module_init_ddbridge); -module_exit(module_exit_ddbridge); - -MODULE_DESCRIPTION("Digital Devices PCIe Bridge"); -MODULE_AUTHOR("Ralph Metzler"); -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.5"); diff --git a/drivers/media/dvb/dm1105/Kconfig b/drivers/media/dvb/dm1105/Kconfig deleted file mode 100644 index f3de0a4d63f2..000000000000 --- a/drivers/media/dvb/dm1105/Kconfig +++ /dev/null @@ -1,20 +0,0 @@ -config DVB_DM1105 - tristate "SDMC DM1105 based PCI cards" - depends on DVB_CORE && PCI && I2C - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_STV0288 if !DVB_FE_CUSTOMISE - select DVB_STB6000 if !DVB_FE_CUSTOMISE - select DVB_CX24116 if !DVB_FE_CUSTOMISE - select DVB_SI21XX if !DVB_FE_CUSTOMISE - select DVB_DS3000 if !DVB_FE_CUSTOMISE - depends on RC_CORE - help - Support for cards based on the SDMC DM1105 PCI chip like - DvbWorld 2002 - - Since these cards have no MPEG decoder onboard, they transmit - only compressed MPEG data over the PCI bus, so you need - an external software decoder to watch TV on your computer. - - Say Y or M if you own such a device and want to use it. diff --git a/drivers/media/dvb/dm1105/Makefile b/drivers/media/dvb/dm1105/Makefile deleted file mode 100644 index 95a008b71fe5..000000000000 --- a/drivers/media/dvb/dm1105/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -obj-$(CONFIG_DVB_DM1105) += dm1105.o - -ccflags-y += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends diff --git a/drivers/media/dvb/dvb-core/dmxdev.c b/drivers/media/dvb/dvb-core/dmxdev.c deleted file mode 100644 index 73970cd97af1..000000000000 --- a/drivers/media/dvb/dvb-core/dmxdev.c +++ /dev/null @@ -1,1277 +0,0 @@ -/* - * dmxdev.c - DVB demultiplexer device - * - * Copyright (C) 2000 Ralph Metzler & Marcus Metzler - * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * 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 Lesser 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include "dmxdev.h" - -static int debug; - -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); - -#define dprintk if (debug) printk - -static int dvb_dmxdev_buffer_write(struct dvb_ringbuffer *buf, - const u8 *src, size_t len) -{ - ssize_t free; - - if (!len) - return 0; - if (!buf->data) - return 0; - - free = dvb_ringbuffer_free(buf); - if (len > free) { - dprintk("dmxdev: buffer overflow\n"); - return -EOVERFLOW; - } - - return dvb_ringbuffer_write(buf, src, len); -} - -static ssize_t dvb_dmxdev_buffer_read(struct dvb_ringbuffer *src, - int non_blocking, char __user *buf, - size_t count, loff_t *ppos) -{ - size_t todo; - ssize_t avail; - ssize_t ret = 0; - - if (!src->data) - return 0; - - if (src->error) { - ret = src->error; - dvb_ringbuffer_flush(src); - return ret; - } - - for (todo = count; todo > 0; todo -= ret) { - if (non_blocking && dvb_ringbuffer_empty(src)) { - ret = -EWOULDBLOCK; - break; - } - - ret = wait_event_interruptible(src->queue, - !dvb_ringbuffer_empty(src) || - (src->error != 0)); - if (ret < 0) - break; - - if (src->error) { - ret = src->error; - dvb_ringbuffer_flush(src); - break; - } - - avail = dvb_ringbuffer_avail(src); - if (avail > todo) - avail = todo; - - ret = dvb_ringbuffer_read_user(src, buf, avail); - if (ret < 0) - break; - - buf += ret; - } - - return (count - todo) ? (count - todo) : ret; -} - -static struct dmx_frontend *get_fe(struct dmx_demux *demux, int type) -{ - struct list_head *head, *pos; - - head = demux->get_frontends(demux); - if (!head) - return NULL; - list_for_each(pos, head) - if (DMX_FE_ENTRY(pos)->source == type) - return DMX_FE_ENTRY(pos); - - return NULL; -} - -static int dvb_dvr_open(struct inode *inode, struct file *file) -{ - struct dvb_device *dvbdev = file->private_data; - struct dmxdev *dmxdev = dvbdev->priv; - struct dmx_frontend *front; - - dprintk("function : %s\n", __func__); - - if (mutex_lock_interruptible(&dmxdev->mutex)) - return -ERESTARTSYS; - - if (dmxdev->exit) { - mutex_unlock(&dmxdev->mutex); - return -ENODEV; - } - - if ((file->f_flags & O_ACCMODE) == O_RDWR) { - if (!(dmxdev->capabilities & DMXDEV_CAP_DUPLEX)) { - mutex_unlock(&dmxdev->mutex); - return -EOPNOTSUPP; - } - } - - if ((file->f_flags & O_ACCMODE) == O_RDONLY) { - void *mem; - if (!dvbdev->readers) { - mutex_unlock(&dmxdev->mutex); - return -EBUSY; - } - mem = vmalloc(DVR_BUFFER_SIZE); - if (!mem) { - mutex_unlock(&dmxdev->mutex); - return -ENOMEM; - } - dvb_ringbuffer_init(&dmxdev->dvr_buffer, mem, DVR_BUFFER_SIZE); - dvbdev->readers--; - } - - if ((file->f_flags & O_ACCMODE) == O_WRONLY) { - dmxdev->dvr_orig_fe = dmxdev->demux->frontend; - - if (!dmxdev->demux->write) { - mutex_unlock(&dmxdev->mutex); - return -EOPNOTSUPP; - } - - front = get_fe(dmxdev->demux, DMX_MEMORY_FE); - - if (!front) { - mutex_unlock(&dmxdev->mutex); - return -EINVAL; - } - dmxdev->demux->disconnect_frontend(dmxdev->demux); - dmxdev->demux->connect_frontend(dmxdev->demux, front); - } - dvbdev->users++; - mutex_unlock(&dmxdev->mutex); - return 0; -} - -static int dvb_dvr_release(struct inode *inode, struct file *file) -{ - struct dvb_device *dvbdev = file->private_data; - struct dmxdev *dmxdev = dvbdev->priv; - - mutex_lock(&dmxdev->mutex); - - if ((file->f_flags & O_ACCMODE) == O_WRONLY) { - dmxdev->demux->disconnect_frontend(dmxdev->demux); - dmxdev->demux->connect_frontend(dmxdev->demux, - dmxdev->dvr_orig_fe); - } - if ((file->f_flags & O_ACCMODE) == O_RDONLY) { - dvbdev->readers++; - if (dmxdev->dvr_buffer.data) { - void *mem = dmxdev->dvr_buffer.data; - mb(); - spin_lock_irq(&dmxdev->lock); - dmxdev->dvr_buffer.data = NULL; - spin_unlock_irq(&dmxdev->lock); - vfree(mem); - } - } - /* TODO */ - dvbdev->users--; - if (dvbdev->users == 1 && dmxdev->exit == 1) { - fops_put(file->f_op); - file->f_op = NULL; - mutex_unlock(&dmxdev->mutex); - wake_up(&dvbdev->wait_queue); - } else - mutex_unlock(&dmxdev->mutex); - - return 0; -} - -static ssize_t dvb_dvr_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) -{ - struct dvb_device *dvbdev = file->private_data; - struct dmxdev *dmxdev = dvbdev->priv; - int ret; - - if (!dmxdev->demux->write) - return -EOPNOTSUPP; - if ((file->f_flags & O_ACCMODE) != O_WRONLY) - return -EINVAL; - if (mutex_lock_interruptible(&dmxdev->mutex)) - return -ERESTARTSYS; - - if (dmxdev->exit) { - mutex_unlock(&dmxdev->mutex); - return -ENODEV; - } - ret = dmxdev->demux->write(dmxdev->demux, buf, count); - mutex_unlock(&dmxdev->mutex); - return ret; -} - -static ssize_t dvb_dvr_read(struct file *file, char __user *buf, size_t count, - loff_t *ppos) -{ - struct dvb_device *dvbdev = file->private_data; - struct dmxdev *dmxdev = dvbdev->priv; - - if (dmxdev->exit) - return -ENODEV; - - return dvb_dmxdev_buffer_read(&dmxdev->dvr_buffer, - file->f_flags & O_NONBLOCK, - buf, count, ppos); -} - -static int dvb_dvr_set_buffer_size(struct dmxdev *dmxdev, - unsigned long size) -{ - struct dvb_ringbuffer *buf = &dmxdev->dvr_buffer; - void *newmem; - void *oldmem; - - dprintk("function : %s\n", __func__); - - if (buf->size == size) - return 0; - if (!size) - return -EINVAL; - - newmem = vmalloc(size); - if (!newmem) - return -ENOMEM; - - oldmem = buf->data; - - spin_lock_irq(&dmxdev->lock); - buf->data = newmem; - buf->size = size; - - /* reset and not flush in case the buffer shrinks */ - dvb_ringbuffer_reset(buf); - spin_unlock_irq(&dmxdev->lock); - - vfree(oldmem); - - return 0; -} - -static inline void dvb_dmxdev_filter_state_set(struct dmxdev_filter - *dmxdevfilter, int state) -{ - spin_lock_irq(&dmxdevfilter->dev->lock); - dmxdevfilter->state = state; - spin_unlock_irq(&dmxdevfilter->dev->lock); -} - -static int dvb_dmxdev_set_buffer_size(struct dmxdev_filter *dmxdevfilter, - unsigned long size) -{ - struct dvb_ringbuffer *buf = &dmxdevfilter->buffer; - void *newmem; - void *oldmem; - - if (buf->size == size) - return 0; - if (!size) - return -EINVAL; - if (dmxdevfilter->state >= DMXDEV_STATE_GO) - return -EBUSY; - - newmem = vmalloc(size); - if (!newmem) - return -ENOMEM; - - oldmem = buf->data; - - spin_lock_irq(&dmxdevfilter->dev->lock); - buf->data = newmem; - buf->size = size; - - /* reset and not flush in case the buffer shrinks */ - dvb_ringbuffer_reset(buf); - spin_unlock_irq(&dmxdevfilter->dev->lock); - - vfree(oldmem); - - return 0; -} - -static void dvb_dmxdev_filter_timeout(unsigned long data) -{ - struct dmxdev_filter *dmxdevfilter = (struct dmxdev_filter *)data; - - dmxdevfilter->buffer.error = -ETIMEDOUT; - spin_lock_irq(&dmxdevfilter->dev->lock); - dmxdevfilter->state = DMXDEV_STATE_TIMEDOUT; - spin_unlock_irq(&dmxdevfilter->dev->lock); - wake_up(&dmxdevfilter->buffer.queue); -} - -static void dvb_dmxdev_filter_timer(struct dmxdev_filter *dmxdevfilter) -{ - struct dmx_sct_filter_params *para = &dmxdevfilter->params.sec; - - del_timer(&dmxdevfilter->timer); - if (para->timeout) { - dmxdevfilter->timer.function = dvb_dmxdev_filter_timeout; - dmxdevfilter->timer.data = (unsigned long)dmxdevfilter; - dmxdevfilter->timer.expires = - jiffies + 1 + (HZ / 2 + HZ * para->timeout) / 1000; - add_timer(&dmxdevfilter->timer); - } -} - -static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len, - const u8 *buffer2, size_t buffer2_len, - struct dmx_section_filter *filter, - enum dmx_success success) -{ - struct dmxdev_filter *dmxdevfilter = filter->priv; - int ret; - - if (dmxdevfilter->buffer.error) { - wake_up(&dmxdevfilter->buffer.queue); - return 0; - } - spin_lock(&dmxdevfilter->dev->lock); - if (dmxdevfilter->state != DMXDEV_STATE_GO) { - spin_unlock(&dmxdevfilter->dev->lock); - return 0; - } - del_timer(&dmxdevfilter->timer); - dprintk("dmxdev: section callback %02x %02x %02x %02x %02x %02x\n", - buffer1[0], buffer1[1], - buffer1[2], buffer1[3], buffer1[4], buffer1[5]); - ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, buffer1, - buffer1_len); - if (ret == buffer1_len) { - ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, buffer2, - buffer2_len); - } - if (ret < 0) { - dvb_ringbuffer_flush(&dmxdevfilter->buffer); - dmxdevfilter->buffer.error = ret; - } - if (dmxdevfilter->params.sec.flags & DMX_ONESHOT) - dmxdevfilter->state = DMXDEV_STATE_DONE; - spin_unlock(&dmxdevfilter->dev->lock); - wake_up(&dmxdevfilter->buffer.queue); - return 0; -} - -static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len, - const u8 *buffer2, size_t buffer2_len, - struct dmx_ts_feed *feed, - enum dmx_success success) -{ - struct dmxdev_filter *dmxdevfilter = feed->priv; - struct dvb_ringbuffer *buffer; - int ret; - - spin_lock(&dmxdevfilter->dev->lock); - if (dmxdevfilter->params.pes.output == DMX_OUT_DECODER) { - spin_unlock(&dmxdevfilter->dev->lock); - return 0; - } - - if (dmxdevfilter->params.pes.output == DMX_OUT_TAP - || dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP) - buffer = &dmxdevfilter->buffer; - else - buffer = &dmxdevfilter->dev->dvr_buffer; - if (buffer->error) { - spin_unlock(&dmxdevfilter->dev->lock); - wake_up(&buffer->queue); - return 0; - } - ret = dvb_dmxdev_buffer_write(buffer, buffer1, buffer1_len); - if (ret == buffer1_len) - ret = dvb_dmxdev_buffer_write(buffer, buffer2, buffer2_len); - if (ret < 0) { - dvb_ringbuffer_flush(buffer); - buffer->error = ret; - } - spin_unlock(&dmxdevfilter->dev->lock); - wake_up(&buffer->queue); - return 0; -} - -/* stop feed but only mark the specified filter as stopped (state set) */ -static int dvb_dmxdev_feed_stop(struct dmxdev_filter *dmxdevfilter) -{ - struct dmxdev_feed *feed; - - dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); - - switch (dmxdevfilter->type) { - case DMXDEV_TYPE_SEC: - del_timer(&dmxdevfilter->timer); - dmxdevfilter->feed.sec->stop_filtering(dmxdevfilter->feed.sec); - break; - case DMXDEV_TYPE_PES: - list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) - feed->ts->stop_filtering(feed->ts); - break; - default: - return -EINVAL; - } - return 0; -} - -/* start feed associated with the specified filter */ -static int dvb_dmxdev_feed_start(struct dmxdev_filter *filter) -{ - struct dmxdev_feed *feed; - int ret; - - dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); - - switch (filter->type) { - case DMXDEV_TYPE_SEC: - return filter->feed.sec->start_filtering(filter->feed.sec); - case DMXDEV_TYPE_PES: - list_for_each_entry(feed, &filter->feed.ts, next) { - ret = feed->ts->start_filtering(feed->ts); - if (ret < 0) { - dvb_dmxdev_feed_stop(filter); - return ret; - } - } - break; - default: - return -EINVAL; - } - - return 0; -} - -/* restart section feed if it has filters left associated with it, - otherwise release the feed */ -static int dvb_dmxdev_feed_restart(struct dmxdev_filter *filter) -{ - int i; - struct dmxdev *dmxdev = filter->dev; - u16 pid = filter->params.sec.pid; - - for (i = 0; i < dmxdev->filternum; i++) - if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && - dmxdev->filter[i].type == DMXDEV_TYPE_SEC && - dmxdev->filter[i].params.sec.pid == pid) { - dvb_dmxdev_feed_start(&dmxdev->filter[i]); - return 0; - } - - filter->dev->demux->release_section_feed(dmxdev->demux, - filter->feed.sec); - - return 0; -} - -static int dvb_dmxdev_filter_stop(struct dmxdev_filter *dmxdevfilter) -{ - struct dmxdev_feed *feed; - struct dmx_demux *demux; - - if (dmxdevfilter->state < DMXDEV_STATE_GO) - return 0; - - switch (dmxdevfilter->type) { - case DMXDEV_TYPE_SEC: - if (!dmxdevfilter->feed.sec) - break; - dvb_dmxdev_feed_stop(dmxdevfilter); - if (dmxdevfilter->filter.sec) - dmxdevfilter->feed.sec-> - release_filter(dmxdevfilter->feed.sec, - dmxdevfilter->filter.sec); - dvb_dmxdev_feed_restart(dmxdevfilter); - dmxdevfilter->feed.sec = NULL; - break; - case DMXDEV_TYPE_PES: - dvb_dmxdev_feed_stop(dmxdevfilter); - demux = dmxdevfilter->dev->demux; - list_for_each_entry(feed, &dmxdevfilter->feed.ts, next) { - demux->release_ts_feed(demux, feed->ts); - feed->ts = NULL; - } - break; - default: - if (dmxdevfilter->state == DMXDEV_STATE_ALLOCATED) - return 0; - return -EINVAL; - } - - dvb_ringbuffer_flush(&dmxdevfilter->buffer); - return 0; -} - -static void dvb_dmxdev_delete_pids(struct dmxdev_filter *dmxdevfilter) -{ - struct dmxdev_feed *feed, *tmp; - - /* delete all PIDs */ - list_for_each_entry_safe(feed, tmp, &dmxdevfilter->feed.ts, next) { - list_del(&feed->next); - kfree(feed); - } - - BUG_ON(!list_empty(&dmxdevfilter->feed.ts)); -} - -static inline int dvb_dmxdev_filter_reset(struct dmxdev_filter *dmxdevfilter) -{ - if (dmxdevfilter->state < DMXDEV_STATE_SET) - return 0; - - if (dmxdevfilter->type == DMXDEV_TYPE_PES) - dvb_dmxdev_delete_pids(dmxdevfilter); - - dmxdevfilter->type = DMXDEV_TYPE_NONE; - dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); - return 0; -} - -static int dvb_dmxdev_start_feed(struct dmxdev *dmxdev, - struct dmxdev_filter *filter, - struct dmxdev_feed *feed) -{ - struct timespec timeout = { 0 }; - struct dmx_pes_filter_params *para = &filter->params.pes; - dmx_output_t otype; - int ret; - int ts_type; - dmx_pes_type_t ts_pes; - struct dmx_ts_feed *tsfeed; - - feed->ts = NULL; - otype = para->output; - - ts_pes = para->pes_type; - - if (ts_pes < DMX_PES_OTHER) - ts_type = TS_DECODER; - else - ts_type = 0; - - if (otype == DMX_OUT_TS_TAP) - ts_type |= TS_PACKET; - else if (otype == DMX_OUT_TSDEMUX_TAP) - ts_type |= TS_PACKET | TS_DEMUX; - else if (otype == DMX_OUT_TAP) - ts_type |= TS_PACKET | TS_DEMUX | TS_PAYLOAD_ONLY; - - ret = dmxdev->demux->allocate_ts_feed(dmxdev->demux, &feed->ts, - dvb_dmxdev_ts_callback); - if (ret < 0) - return ret; - - tsfeed = feed->ts; - tsfeed->priv = filter; - - ret = tsfeed->set(tsfeed, feed->pid, ts_type, ts_pes, 32768, timeout); - if (ret < 0) { - dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); - return ret; - } - - ret = tsfeed->start_filtering(tsfeed); - if (ret < 0) { - dmxdev->demux->release_ts_feed(dmxdev->demux, tsfeed); - return ret; - } - - return 0; -} - -static int dvb_dmxdev_filter_start(struct dmxdev_filter *filter) -{ - struct dmxdev *dmxdev = filter->dev; - struct dmxdev_feed *feed; - void *mem; - int ret, i; - - if (filter->state < DMXDEV_STATE_SET) - return -EINVAL; - - if (filter->state >= DMXDEV_STATE_GO) - dvb_dmxdev_filter_stop(filter); - - if (!filter->buffer.data) { - mem = vmalloc(filter->buffer.size); - if (!mem) - return -ENOMEM; - spin_lock_irq(&filter->dev->lock); - filter->buffer.data = mem; - spin_unlock_irq(&filter->dev->lock); - } - - dvb_ringbuffer_flush(&filter->buffer); - - switch (filter->type) { - case DMXDEV_TYPE_SEC: - { - struct dmx_sct_filter_params *para = &filter->params.sec; - struct dmx_section_filter **secfilter = &filter->filter.sec; - struct dmx_section_feed **secfeed = &filter->feed.sec; - - *secfilter = NULL; - *secfeed = NULL; - - - /* find active filter/feed with same PID */ - for (i = 0; i < dmxdev->filternum; i++) { - if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && - dmxdev->filter[i].type == DMXDEV_TYPE_SEC && - dmxdev->filter[i].params.sec.pid == para->pid) { - *secfeed = dmxdev->filter[i].feed.sec; - break; - } - } - - /* if no feed found, try to allocate new one */ - if (!*secfeed) { - ret = dmxdev->demux->allocate_section_feed(dmxdev->demux, - secfeed, - dvb_dmxdev_section_callback); - if (ret < 0) { - printk("DVB (%s): could not alloc feed\n", - __func__); - return ret; - } - - ret = (*secfeed)->set(*secfeed, para->pid, 32768, - (para->flags & DMX_CHECK_CRC) ? 1 : 0); - if (ret < 0) { - printk("DVB (%s): could not set feed\n", - __func__); - dvb_dmxdev_feed_restart(filter); - return ret; - } - } else { - dvb_dmxdev_feed_stop(filter); - } - - ret = (*secfeed)->allocate_filter(*secfeed, secfilter); - if (ret < 0) { - dvb_dmxdev_feed_restart(filter); - filter->feed.sec->start_filtering(*secfeed); - dprintk("could not get filter\n"); - return ret; - } - - (*secfilter)->priv = filter; - - memcpy(&((*secfilter)->filter_value[3]), - &(para->filter.filter[1]), DMX_FILTER_SIZE - 1); - memcpy(&(*secfilter)->filter_mask[3], - ¶->filter.mask[1], DMX_FILTER_SIZE - 1); - memcpy(&(*secfilter)->filter_mode[3], - ¶->filter.mode[1], DMX_FILTER_SIZE - 1); - - (*secfilter)->filter_value[0] = para->filter.filter[0]; - (*secfilter)->filter_mask[0] = para->filter.mask[0]; - (*secfilter)->filter_mode[0] = para->filter.mode[0]; - (*secfilter)->filter_mask[1] = 0; - (*secfilter)->filter_mask[2] = 0; - - filter->todo = 0; - - ret = filter->feed.sec->start_filtering(filter->feed.sec); - if (ret < 0) - return ret; - - dvb_dmxdev_filter_timer(filter); - break; - } - case DMXDEV_TYPE_PES: - list_for_each_entry(feed, &filter->feed.ts, next) { - ret = dvb_dmxdev_start_feed(dmxdev, filter, feed); - if (ret < 0) { - dvb_dmxdev_filter_stop(filter); - return ret; - } - } - break; - default: - return -EINVAL; - } - - dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); - return 0; -} - -static int dvb_demux_open(struct inode *inode, struct file *file) -{ - struct dvb_device *dvbdev = file->private_data; - struct dmxdev *dmxdev = dvbdev->priv; - int i; - struct dmxdev_filter *dmxdevfilter; - - if (!dmxdev->filter) - return -EINVAL; - - if (mutex_lock_interruptible(&dmxdev->mutex)) - return -ERESTARTSYS; - - for (i = 0; i < dmxdev->filternum; i++) - if (dmxdev->filter[i].state == DMXDEV_STATE_FREE) - break; - - if (i == dmxdev->filternum) { - mutex_unlock(&dmxdev->mutex); - return -EMFILE; - } - - dmxdevfilter = &dmxdev->filter[i]; - mutex_init(&dmxdevfilter->mutex); - file->private_data = dmxdevfilter; - - dvb_ringbuffer_init(&dmxdevfilter->buffer, NULL, 8192); - dmxdevfilter->type = DMXDEV_TYPE_NONE; - dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); - init_timer(&dmxdevfilter->timer); - - dvbdev->users++; - - mutex_unlock(&dmxdev->mutex); - return 0; -} - -static int dvb_dmxdev_filter_free(struct dmxdev *dmxdev, - struct dmxdev_filter *dmxdevfilter) -{ - mutex_lock(&dmxdev->mutex); - mutex_lock(&dmxdevfilter->mutex); - - dvb_dmxdev_filter_stop(dmxdevfilter); - dvb_dmxdev_filter_reset(dmxdevfilter); - - if (dmxdevfilter->buffer.data) { - void *mem = dmxdevfilter->buffer.data; - - spin_lock_irq(&dmxdev->lock); - dmxdevfilter->buffer.data = NULL; - spin_unlock_irq(&dmxdev->lock); - vfree(mem); - } - - dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_FREE); - wake_up(&dmxdevfilter->buffer.queue); - mutex_unlock(&dmxdevfilter->mutex); - mutex_unlock(&dmxdev->mutex); - return 0; -} - -static inline void invert_mode(dmx_filter_t *filter) -{ - int i; - - for (i = 0; i < DMX_FILTER_SIZE; i++) - filter->mode[i] ^= 0xff; -} - -static int dvb_dmxdev_add_pid(struct dmxdev *dmxdev, - struct dmxdev_filter *filter, u16 pid) -{ - struct dmxdev_feed *feed; - - if ((filter->type != DMXDEV_TYPE_PES) || - (filter->state < DMXDEV_STATE_SET)) - return -EINVAL; - - /* only TS packet filters may have multiple PIDs */ - if ((filter->params.pes.output != DMX_OUT_TSDEMUX_TAP) && - (!list_empty(&filter->feed.ts))) - return -EINVAL; - - feed = kzalloc(sizeof(struct dmxdev_feed), GFP_KERNEL); - if (feed == NULL) - return -ENOMEM; - - feed->pid = pid; - list_add(&feed->next, &filter->feed.ts); - - if (filter->state >= DMXDEV_STATE_GO) - return dvb_dmxdev_start_feed(dmxdev, filter, feed); - - return 0; -} - -static int dvb_dmxdev_remove_pid(struct dmxdev *dmxdev, - struct dmxdev_filter *filter, u16 pid) -{ - struct dmxdev_feed *feed, *tmp; - - if ((filter->type != DMXDEV_TYPE_PES) || - (filter->state < DMXDEV_STATE_SET)) - return -EINVAL; - - list_for_each_entry_safe(feed, tmp, &filter->feed.ts, next) { - if ((feed->pid == pid) && (feed->ts != NULL)) { - feed->ts->stop_filtering(feed->ts); - filter->dev->demux->release_ts_feed(filter->dev->demux, - feed->ts); - list_del(&feed->next); - kfree(feed); - } - } - - return 0; -} - -static int dvb_dmxdev_filter_set(struct dmxdev *dmxdev, - struct dmxdev_filter *dmxdevfilter, - struct dmx_sct_filter_params *params) -{ - dprintk("function : %s\n", __func__); - - dvb_dmxdev_filter_stop(dmxdevfilter); - - dmxdevfilter->type = DMXDEV_TYPE_SEC; - memcpy(&dmxdevfilter->params.sec, - params, sizeof(struct dmx_sct_filter_params)); - invert_mode(&dmxdevfilter->params.sec.filter); - dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); - - if (params->flags & DMX_IMMEDIATE_START) - return dvb_dmxdev_filter_start(dmxdevfilter); - - return 0; -} - -static int dvb_dmxdev_pes_filter_set(struct dmxdev *dmxdev, - struct dmxdev_filter *dmxdevfilter, - struct dmx_pes_filter_params *params) -{ - int ret; - - dvb_dmxdev_filter_stop(dmxdevfilter); - dvb_dmxdev_filter_reset(dmxdevfilter); - - if (params->pes_type > DMX_PES_OTHER || params->pes_type < 0) - return -EINVAL; - - dmxdevfilter->type = DMXDEV_TYPE_PES; - memcpy(&dmxdevfilter->params, params, - sizeof(struct dmx_pes_filter_params)); - INIT_LIST_HEAD(&dmxdevfilter->feed.ts); - - dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); - - ret = dvb_dmxdev_add_pid(dmxdev, dmxdevfilter, - dmxdevfilter->params.pes.pid); - if (ret < 0) - return ret; - - if (params->flags & DMX_IMMEDIATE_START) - return dvb_dmxdev_filter_start(dmxdevfilter); - - return 0; -} - -static ssize_t dvb_dmxdev_read_sec(struct dmxdev_filter *dfil, - struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ - int result, hcount; - int done = 0; - - if (dfil->todo <= 0) { - hcount = 3 + dfil->todo; - if (hcount > count) - hcount = count; - result = dvb_dmxdev_buffer_read(&dfil->buffer, - file->f_flags & O_NONBLOCK, - buf, hcount, ppos); - if (result < 0) { - dfil->todo = 0; - return result; - } - if (copy_from_user(dfil->secheader - dfil->todo, buf, result)) - return -EFAULT; - buf += result; - done = result; - count -= result; - dfil->todo -= result; - if (dfil->todo > -3) - return done; - dfil->todo = ((dfil->secheader[1] << 8) | dfil->secheader[2]) & 0xfff; - if (!count) - return done; - } - if (count > dfil->todo) - count = dfil->todo; - result = dvb_dmxdev_buffer_read(&dfil->buffer, - file->f_flags & O_NONBLOCK, - buf, count, ppos); - if (result < 0) - return result; - dfil->todo -= result; - return (result + done); -} - -static ssize_t -dvb_demux_read(struct file *file, char __user *buf, size_t count, - loff_t *ppos) -{ - struct dmxdev_filter *dmxdevfilter = file->private_data; - int ret; - - if (mutex_lock_interruptible(&dmxdevfilter->mutex)) - return -ERESTARTSYS; - - if (dmxdevfilter->type == DMXDEV_TYPE_SEC) - ret = dvb_dmxdev_read_sec(dmxdevfilter, file, buf, count, ppos); - else - ret = dvb_dmxdev_buffer_read(&dmxdevfilter->buffer, - file->f_flags & O_NONBLOCK, - buf, count, ppos); - - mutex_unlock(&dmxdevfilter->mutex); - return ret; -} - -static int dvb_demux_do_ioctl(struct file *file, - unsigned int cmd, void *parg) -{ - struct dmxdev_filter *dmxdevfilter = file->private_data; - struct dmxdev *dmxdev = dmxdevfilter->dev; - unsigned long arg = (unsigned long)parg; - int ret = 0; - - if (mutex_lock_interruptible(&dmxdev->mutex)) - return -ERESTARTSYS; - - switch (cmd) { - case DMX_START: - if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { - mutex_unlock(&dmxdev->mutex); - return -ERESTARTSYS; - } - if (dmxdevfilter->state < DMXDEV_STATE_SET) - ret = -EINVAL; - else - ret = dvb_dmxdev_filter_start(dmxdevfilter); - mutex_unlock(&dmxdevfilter->mutex); - break; - - case DMX_STOP: - if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { - mutex_unlock(&dmxdev->mutex); - return -ERESTARTSYS; - } - ret = dvb_dmxdev_filter_stop(dmxdevfilter); - mutex_unlock(&dmxdevfilter->mutex); - break; - - case DMX_SET_FILTER: - if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { - mutex_unlock(&dmxdev->mutex); - return -ERESTARTSYS; - } - ret = dvb_dmxdev_filter_set(dmxdev, dmxdevfilter, parg); - mutex_unlock(&dmxdevfilter->mutex); - break; - - case DMX_SET_PES_FILTER: - if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { - mutex_unlock(&dmxdev->mutex); - return -ERESTARTSYS; - } - ret = dvb_dmxdev_pes_filter_set(dmxdev, dmxdevfilter, parg); - mutex_unlock(&dmxdevfilter->mutex); - break; - - case DMX_SET_BUFFER_SIZE: - if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { - mutex_unlock(&dmxdev->mutex); - return -ERESTARTSYS; - } - ret = dvb_dmxdev_set_buffer_size(dmxdevfilter, arg); - mutex_unlock(&dmxdevfilter->mutex); - break; - - case DMX_GET_PES_PIDS: - if (!dmxdev->demux->get_pes_pids) { - ret = -EINVAL; - break; - } - dmxdev->demux->get_pes_pids(dmxdev->demux, parg); - break; - - case DMX_GET_CAPS: - if (!dmxdev->demux->get_caps) { - ret = -EINVAL; - break; - } - ret = dmxdev->demux->get_caps(dmxdev->demux, parg); - break; - - case DMX_SET_SOURCE: - if (!dmxdev->demux->set_source) { - ret = -EINVAL; - break; - } - ret = dmxdev->demux->set_source(dmxdev->demux, parg); - break; - - case DMX_GET_STC: - if (!dmxdev->demux->get_stc) { - ret = -EINVAL; - break; - } - ret = dmxdev->demux->get_stc(dmxdev->demux, - ((struct dmx_stc *)parg)->num, - &((struct dmx_stc *)parg)->stc, - &((struct dmx_stc *)parg)->base); - break; - - case DMX_ADD_PID: - if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { - ret = -ERESTARTSYS; - break; - } - ret = dvb_dmxdev_add_pid(dmxdev, dmxdevfilter, *(u16 *)parg); - mutex_unlock(&dmxdevfilter->mutex); - break; - - case DMX_REMOVE_PID: - if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { - ret = -ERESTARTSYS; - break; - } - ret = dvb_dmxdev_remove_pid(dmxdev, dmxdevfilter, *(u16 *)parg); - mutex_unlock(&dmxdevfilter->mutex); - break; - - default: - ret = -EINVAL; - break; - } - mutex_unlock(&dmxdev->mutex); - return ret; -} - -static long dvb_demux_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - return dvb_usercopy(file, cmd, arg, dvb_demux_do_ioctl); -} - -static unsigned int dvb_demux_poll(struct file *file, poll_table *wait) -{ - struct dmxdev_filter *dmxdevfilter = file->private_data; - unsigned int mask = 0; - - if (!dmxdevfilter) - return -EINVAL; - - poll_wait(file, &dmxdevfilter->buffer.queue, wait); - - if (dmxdevfilter->state != DMXDEV_STATE_GO && - dmxdevfilter->state != DMXDEV_STATE_DONE && - dmxdevfilter->state != DMXDEV_STATE_TIMEDOUT) - return 0; - - if (dmxdevfilter->buffer.error) - mask |= (POLLIN | POLLRDNORM | POLLPRI | POLLERR); - - if (!dvb_ringbuffer_empty(&dmxdevfilter->buffer)) - mask |= (POLLIN | POLLRDNORM | POLLPRI); - - return mask; -} - -static int dvb_demux_release(struct inode *inode, struct file *file) -{ - struct dmxdev_filter *dmxdevfilter = file->private_data; - struct dmxdev *dmxdev = dmxdevfilter->dev; - - int ret; - - ret = dvb_dmxdev_filter_free(dmxdev, dmxdevfilter); - - mutex_lock(&dmxdev->mutex); - dmxdev->dvbdev->users--; - if(dmxdev->dvbdev->users==1 && dmxdev->exit==1) { - fops_put(file->f_op); - file->f_op = NULL; - mutex_unlock(&dmxdev->mutex); - wake_up(&dmxdev->dvbdev->wait_queue); - } else - mutex_unlock(&dmxdev->mutex); - - return ret; -} - -static const struct file_operations dvb_demux_fops = { - .owner = THIS_MODULE, - .read = dvb_demux_read, - .unlocked_ioctl = dvb_demux_ioctl, - .open = dvb_demux_open, - .release = dvb_demux_release, - .poll = dvb_demux_poll, - .llseek = default_llseek, -}; - -static struct dvb_device dvbdev_demux = { - .priv = NULL, - .users = 1, - .writers = 1, - .fops = &dvb_demux_fops -}; - -static int dvb_dvr_do_ioctl(struct file *file, - unsigned int cmd, void *parg) -{ - struct dvb_device *dvbdev = file->private_data; - struct dmxdev *dmxdev = dvbdev->priv; - unsigned long arg = (unsigned long)parg; - int ret; - - if (mutex_lock_interruptible(&dmxdev->mutex)) - return -ERESTARTSYS; - - switch (cmd) { - case DMX_SET_BUFFER_SIZE: - ret = dvb_dvr_set_buffer_size(dmxdev, arg); - break; - - default: - ret = -EINVAL; - break; - } - mutex_unlock(&dmxdev->mutex); - return ret; -} - -static long dvb_dvr_ioctl(struct file *file, - unsigned int cmd, unsigned long arg) -{ - return dvb_usercopy(file, cmd, arg, dvb_dvr_do_ioctl); -} - -static unsigned int dvb_dvr_poll(struct file *file, poll_table *wait) -{ - struct dvb_device *dvbdev = file->private_data; - struct dmxdev *dmxdev = dvbdev->priv; - unsigned int mask = 0; - - dprintk("function : %s\n", __func__); - - poll_wait(file, &dmxdev->dvr_buffer.queue, wait); - - if ((file->f_flags & O_ACCMODE) == O_RDONLY) { - if (dmxdev->dvr_buffer.error) - mask |= (POLLIN | POLLRDNORM | POLLPRI | POLLERR); - - if (!dvb_ringbuffer_empty(&dmxdev->dvr_buffer)) - mask |= (POLLIN | POLLRDNORM | POLLPRI); - } else - mask |= (POLLOUT | POLLWRNORM | POLLPRI); - - return mask; -} - -static const struct file_operations dvb_dvr_fops = { - .owner = THIS_MODULE, - .read = dvb_dvr_read, - .write = dvb_dvr_write, - .unlocked_ioctl = dvb_dvr_ioctl, - .open = dvb_dvr_open, - .release = dvb_dvr_release, - .poll = dvb_dvr_poll, - .llseek = default_llseek, -}; - -static struct dvb_device dvbdev_dvr = { - .priv = NULL, - .readers = 1, - .users = 1, - .fops = &dvb_dvr_fops -}; - -int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) -{ - int i; - - if (dmxdev->demux->open(dmxdev->demux) < 0) - return -EUSERS; - - dmxdev->filter = vmalloc(dmxdev->filternum * sizeof(struct dmxdev_filter)); - if (!dmxdev->filter) - return -ENOMEM; - - mutex_init(&dmxdev->mutex); - spin_lock_init(&dmxdev->lock); - for (i = 0; i < dmxdev->filternum; i++) { - dmxdev->filter[i].dev = dmxdev; - dmxdev->filter[i].buffer.data = NULL; - dvb_dmxdev_filter_state_set(&dmxdev->filter[i], - DMXDEV_STATE_FREE); - } - - dvb_register_device(dvb_adapter, &dmxdev->dvbdev, &dvbdev_demux, dmxdev, - DVB_DEVICE_DEMUX); - dvb_register_device(dvb_adapter, &dmxdev->dvr_dvbdev, &dvbdev_dvr, - dmxdev, DVB_DEVICE_DVR); - - dvb_ringbuffer_init(&dmxdev->dvr_buffer, NULL, 8192); - - return 0; -} - -EXPORT_SYMBOL(dvb_dmxdev_init); - -void dvb_dmxdev_release(struct dmxdev *dmxdev) -{ - dmxdev->exit=1; - if (dmxdev->dvbdev->users > 1) { - wait_event(dmxdev->dvbdev->wait_queue, - dmxdev->dvbdev->users==1); - } - if (dmxdev->dvr_dvbdev->users > 1) { - wait_event(dmxdev->dvr_dvbdev->wait_queue, - dmxdev->dvr_dvbdev->users==1); - } - - dvb_unregister_device(dmxdev->dvbdev); - dvb_unregister_device(dmxdev->dvr_dvbdev); - - vfree(dmxdev->filter); - dmxdev->filter = NULL; - dmxdev->demux->close(dmxdev->demux); -} - -EXPORT_SYMBOL(dvb_dmxdev_release); diff --git a/drivers/media/dvb/dvb-core/dvb_demux.c b/drivers/media/dvb/dvb-core/dvb_demux.c deleted file mode 100644 index d82469f842e2..000000000000 --- a/drivers/media/dvb/dvb-core/dvb_demux.c +++ /dev/null @@ -1,1309 +0,0 @@ -/* - * dvb_demux.c - DVB kernel demux API - * - * Copyright (C) 2000-2001 Ralph Metzler - * & Marcus Metzler - * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * 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 Lesser 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dvb_demux.h" - -#define NOBUFS -/* -** #define DVB_DEMUX_SECTION_LOSS_LOG to monitor payload loss in the syslog -*/ -// #define DVB_DEMUX_SECTION_LOSS_LOG - -static int dvb_demux_tscheck; -module_param(dvb_demux_tscheck, int, 0644); -MODULE_PARM_DESC(dvb_demux_tscheck, - "enable transport stream continuity and TEI check"); - -static int dvb_demux_speedcheck; -module_param(dvb_demux_speedcheck, int, 0644); -MODULE_PARM_DESC(dvb_demux_speedcheck, - "enable transport stream speed check"); - -#define dprintk_tscheck(x...) do { \ - if (dvb_demux_tscheck && printk_ratelimit()) \ - printk(x); \ - } while (0) - -/****************************************************************************** - * static inlined helper functions - ******************************************************************************/ - -static inline u16 section_length(const u8 *buf) -{ - return 3 + ((buf[1] & 0x0f) << 8) + buf[2]; -} - -static inline u16 ts_pid(const u8 *buf) -{ - return ((buf[1] & 0x1f) << 8) + buf[2]; -} - -static inline u8 payload(const u8 *tsp) -{ - if (!(tsp[3] & 0x10)) // no payload? - return 0; - - if (tsp[3] & 0x20) { // adaptation field? - if (tsp[4] > 183) // corrupted data? - return 0; - else - return 184 - 1 - tsp[4]; - } - - return 184; -} - -static u32 dvb_dmx_crc32(struct dvb_demux_feed *f, const u8 *src, size_t len) -{ - return (f->feed.sec.crc_val = crc32_be(f->feed.sec.crc_val, src, len)); -} - -static void dvb_dmx_memcopy(struct dvb_demux_feed *f, u8 *d, const u8 *s, - size_t len) -{ - memcpy(d, s, len); -} - -/****************************************************************************** - * Software filter functions - ******************************************************************************/ - -static inline int dvb_dmx_swfilter_payload(struct dvb_demux_feed *feed, - const u8 *buf) -{ - int count = payload(buf); - int p; - //int ccok; - //u8 cc; - - if (count == 0) - return -1; - - p = 188 - count; - - /* - cc = buf[3] & 0x0f; - ccok = ((feed->cc + 1) & 0x0f) == cc; - feed->cc = cc; - if (!ccok) - printk("missed packet!\n"); - */ - - if (buf[1] & 0x40) // PUSI ? - feed->peslen = 0xfffa; - - feed->peslen += count; - - return feed->cb.ts(&buf[p], count, NULL, 0, &feed->feed.ts, DMX_OK); -} - -static int dvb_dmx_swfilter_sectionfilter(struct dvb_demux_feed *feed, - struct dvb_demux_filter *f) -{ - u8 neq = 0; - int i; - - for (i = 0; i < DVB_DEMUX_MASK_MAX; i++) { - u8 xor = f->filter.filter_value[i] ^ feed->feed.sec.secbuf[i]; - - if (f->maskandmode[i] & xor) - return 0; - - neq |= f->maskandnotmode[i] & xor; - } - - if (f->doneq && !neq) - return 0; - - return feed->cb.sec(feed->feed.sec.secbuf, feed->feed.sec.seclen, - NULL, 0, &f->filter, DMX_OK); -} - -static inline int dvb_dmx_swfilter_section_feed(struct dvb_demux_feed *feed) -{ - struct dvb_demux *demux = feed->demux; - struct dvb_demux_filter *f = feed->filter; - struct dmx_section_feed *sec = &feed->feed.sec; - int section_syntax_indicator; - - if (!sec->is_filtering) - return 0; - - if (!f) - return 0; - - if (sec->check_crc) { - section_syntax_indicator = ((sec->secbuf[1] & 0x80) != 0); - if (section_syntax_indicator && - demux->check_crc32(feed, sec->secbuf, sec->seclen)) - return -1; - } - - do { - if (dvb_dmx_swfilter_sectionfilter(feed, f) < 0) - return -1; - } while ((f = f->next) && sec->is_filtering); - - sec->seclen = 0; - - return 0; -} - -static void dvb_dmx_swfilter_section_new(struct dvb_demux_feed *feed) -{ - struct dmx_section_feed *sec = &feed->feed.sec; - -#ifdef DVB_DEMUX_SECTION_LOSS_LOG - if (sec->secbufp < sec->tsfeedp) { - int i, n = sec->tsfeedp - sec->secbufp; - - /* - * Section padding is done with 0xff bytes entirely. - * Due to speed reasons, we won't check all of them - * but just first and last. - */ - if (sec->secbuf[0] != 0xff || sec->secbuf[n - 1] != 0xff) { - printk("dvb_demux.c section ts padding loss: %d/%d\n", - n, sec->tsfeedp); - printk("dvb_demux.c pad data:"); - for (i = 0; i < n; i++) - printk(" %02x", sec->secbuf[i]); - printk("\n"); - } - } -#endif - - sec->tsfeedp = sec->secbufp = sec->seclen = 0; - sec->secbuf = sec->secbuf_base; -} - -/* - * Losless Section Demux 1.4.1 by Emard - * Valsecchi Patrick: - * - middle of section A (no PUSI) - * - end of section A and start of section B - * (with PUSI pointing to the start of the second section) - * - * In this case, without feed->pusi_seen you'll receive a garbage section - * consisting of the end of section A. Basically because tsfeedp - * is incemented and the use=0 condition is not raised - * when the second packet arrives. - * - * Fix: - * when demux is started, let feed->pusi_seen = 0 to - * prevent initial feeding of garbage from the end of - * previous section. When you for the first time see PUSI=1 - * then set feed->pusi_seen = 1 - */ -static int dvb_dmx_swfilter_section_copy_dump(struct dvb_demux_feed *feed, - const u8 *buf, u8 len) -{ - struct dvb_demux *demux = feed->demux; - struct dmx_section_feed *sec = &feed->feed.sec; - u16 limit, seclen, n; - - if (sec->tsfeedp >= DMX_MAX_SECFEED_SIZE) - return 0; - - if (sec->tsfeedp + len > DMX_MAX_SECFEED_SIZE) { -#ifdef DVB_DEMUX_SECTION_LOSS_LOG - printk("dvb_demux.c section buffer full loss: %d/%d\n", - sec->tsfeedp + len - DMX_MAX_SECFEED_SIZE, - DMX_MAX_SECFEED_SIZE); -#endif - len = DMX_MAX_SECFEED_SIZE - sec->tsfeedp; - } - - if (len <= 0) - return 0; - - demux->memcopy(feed, sec->secbuf_base + sec->tsfeedp, buf, len); - sec->tsfeedp += len; - - /* - * Dump all the sections we can find in the data (Emard) - */ - limit = sec->tsfeedp; - if (limit > DMX_MAX_SECFEED_SIZE) - return -1; /* internal error should never happen */ - - /* to be sure always set secbuf */ - sec->secbuf = sec->secbuf_base + sec->secbufp; - - for (n = 0; sec->secbufp + 2 < limit; n++) { - seclen = section_length(sec->secbuf); - if (seclen <= 0 || seclen > DMX_MAX_SECTION_SIZE - || seclen + sec->secbufp > limit) - return 0; - sec->seclen = seclen; - sec->crc_val = ~0; - /* dump [secbuf .. secbuf+seclen) */ - if (feed->pusi_seen) - dvb_dmx_swfilter_section_feed(feed); -#ifdef DVB_DEMUX_SECTION_LOSS_LOG - else - printk("dvb_demux.c pusi not seen, discarding section data\n"); -#endif - sec->secbufp += seclen; /* secbufp and secbuf moving together is */ - sec->secbuf += seclen; /* redundant but saves pointer arithmetic */ - } - - return 0; -} - -static int dvb_dmx_swfilter_section_packet(struct dvb_demux_feed *feed, - const u8 *buf) -{ - u8 p, count; - int ccok, dc_i = 0; - u8 cc; - - count = payload(buf); - - if (count == 0) /* count == 0 if no payload or out of range */ - return -1; - - p = 188 - count; /* payload start */ - - cc = buf[3] & 0x0f; - ccok = ((feed->cc + 1) & 0x0f) == cc; - feed->cc = cc; - - if (buf[3] & 0x20) { - /* adaption field present, check for discontinuity_indicator */ - if ((buf[4] > 0) && (buf[5] & 0x80)) - dc_i = 1; - } - - if (!ccok || dc_i) { -#ifdef DVB_DEMUX_SECTION_LOSS_LOG - printk("dvb_demux.c discontinuity detected %d bytes lost\n", - count); - /* - * those bytes under sume circumstances will again be reported - * in the following dvb_dmx_swfilter_section_new - */ -#endif - /* - * Discontinuity detected. Reset pusi_seen = 0 to - * stop feeding of suspicious data until next PUSI=1 arrives - */ - feed->pusi_seen = 0; - dvb_dmx_swfilter_section_new(feed); - } - - if (buf[1] & 0x40) { - /* PUSI=1 (is set), section boundary is here */ - if (count > 1 && buf[p] < count) { - const u8 *before = &buf[p + 1]; - u8 before_len = buf[p]; - const u8 *after = &before[before_len]; - u8 after_len = count - 1 - before_len; - - dvb_dmx_swfilter_section_copy_dump(feed, before, - before_len); - /* before start of new section, set pusi_seen = 1 */ - feed->pusi_seen = 1; - dvb_dmx_swfilter_section_new(feed); - dvb_dmx_swfilter_section_copy_dump(feed, after, - after_len); - } -#ifdef DVB_DEMUX_SECTION_LOSS_LOG - else if (count > 0) - printk("dvb_demux.c PUSI=1 but %d bytes lost\n", count); -#endif - } else { - /* PUSI=0 (is not set), no section boundary */ - dvb_dmx_swfilter_section_copy_dump(feed, &buf[p], count); - } - - return 0; -} - -static inline void dvb_dmx_swfilter_packet_type(struct dvb_demux_feed *feed, - const u8 *buf) -{ - switch (feed->type) { - case DMX_TYPE_TS: - if (!feed->feed.ts.is_filtering) - break; - if (feed->ts_type & TS_PACKET) { - if (feed->ts_type & TS_PAYLOAD_ONLY) - dvb_dmx_swfilter_payload(feed, buf); - else - feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, - DMX_OK); - } - if (feed->ts_type & TS_DECODER) - if (feed->demux->write_to_decoder) - feed->demux->write_to_decoder(feed, buf, 188); - break; - - case DMX_TYPE_SEC: - if (!feed->feed.sec.is_filtering) - break; - if (dvb_dmx_swfilter_section_packet(feed, buf) < 0) - feed->feed.sec.seclen = feed->feed.sec.secbufp = 0; - break; - - default: - break; - } -} - -#define DVR_FEED(f) \ - (((f)->type == DMX_TYPE_TS) && \ - ((f)->feed.ts.is_filtering) && \ - (((f)->ts_type & (TS_PACKET | TS_DEMUX)) == TS_PACKET)) - -static void dvb_dmx_swfilter_packet(struct dvb_demux *demux, const u8 *buf) -{ - struct dvb_demux_feed *feed; - u16 pid = ts_pid(buf); - int dvr_done = 0; - - if (dvb_demux_speedcheck) { - struct timespec cur_time, delta_time; - u64 speed_bytes, speed_timedelta; - - demux->speed_pkts_cnt++; - - /* show speed every SPEED_PKTS_INTERVAL packets */ - if (!(demux->speed_pkts_cnt % SPEED_PKTS_INTERVAL)) { - cur_time = current_kernel_time(); - - if (demux->speed_last_time.tv_sec != 0 && - demux->speed_last_time.tv_nsec != 0) { - delta_time = timespec_sub(cur_time, - demux->speed_last_time); - speed_bytes = (u64)demux->speed_pkts_cnt - * 188 * 8; - /* convert to 1024 basis */ - speed_bytes = 1000 * div64_u64(speed_bytes, - 1024); - speed_timedelta = - (u64)timespec_to_ns(&delta_time); - speed_timedelta = div64_u64(speed_timedelta, - 1000000); /* nsec -> usec */ - printk(KERN_INFO "TS speed %llu Kbits/sec \n", - div64_u64(speed_bytes, - speed_timedelta)); - }; - - demux->speed_last_time = cur_time; - demux->speed_pkts_cnt = 0; - }; - }; - - if (demux->cnt_storage && dvb_demux_tscheck) { - /* check pkt counter */ - if (pid < MAX_PID) { - if (buf[1] & 0x80) - dprintk_tscheck("TEI detected. " - "PID=0x%x data1=0x%x\n", - pid, buf[1]); - - if ((buf[3] & 0xf) != demux->cnt_storage[pid]) - dprintk_tscheck("TS packet counter mismatch. " - "PID=0x%x expected 0x%x " - "got 0x%x\n", - pid, demux->cnt_storage[pid], - buf[3] & 0xf); - - demux->cnt_storage[pid] = ((buf[3] & 0xf) + 1)&0xf; - }; - /* end check */ - }; - - list_for_each_entry(feed, &demux->feed_list, list_head) { - if ((feed->pid != pid) && (feed->pid != 0x2000)) - continue; - - /* copy each packet only once to the dvr device, even - * if a PID is in multiple filters (e.g. video + PCR) */ - if ((DVR_FEED(feed)) && (dvr_done++)) - continue; - - if (feed->pid == pid) - dvb_dmx_swfilter_packet_type(feed, buf); - else if (feed->pid == 0x2000) - feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, DMX_OK); - } -} - -void dvb_dmx_swfilter_packets(struct dvb_demux *demux, const u8 *buf, - size_t count) -{ - spin_lock(&demux->lock); - - while (count--) { - if (buf[0] == 0x47) - dvb_dmx_swfilter_packet(demux, buf); - buf += 188; - } - - spin_unlock(&demux->lock); -} - -EXPORT_SYMBOL(dvb_dmx_swfilter_packets); - -static inline int find_next_packet(const u8 *buf, int pos, size_t count, - const int pktsize) -{ - int start = pos, lost; - - while (pos < count) { - if (buf[pos] == 0x47 || - (pktsize == 204 && buf[pos] == 0xB8)) - break; - pos++; - } - - lost = pos - start; - if (lost) { - /* This garbage is part of a valid packet? */ - int backtrack = pos - pktsize; - if (backtrack >= 0 && (buf[backtrack] == 0x47 || - (pktsize == 204 && buf[backtrack] == 0xB8))) - return backtrack; - } - - return pos; -} - -/* Filter all pktsize= 188 or 204 sized packets and skip garbage. */ -static inline void _dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, - size_t count, const int pktsize) -{ - int p = 0, i, j; - const u8 *q; - - spin_lock(&demux->lock); - - if (demux->tsbufp) { /* tsbuf[0] is now 0x47. */ - i = demux->tsbufp; - j = pktsize - i; - if (count < j) { - memcpy(&demux->tsbuf[i], buf, count); - demux->tsbufp += count; - goto bailout; - } - memcpy(&demux->tsbuf[i], buf, j); - if (demux->tsbuf[0] == 0x47) /* double check */ - dvb_dmx_swfilter_packet(demux, demux->tsbuf); - demux->tsbufp = 0; - p += j; - } - - while (1) { - p = find_next_packet(buf, p, count, pktsize); - if (p >= count) - break; - if (count - p < pktsize) - break; - - q = &buf[p]; - - if (pktsize == 204 && (*q == 0xB8)) { - memcpy(demux->tsbuf, q, 188); - demux->tsbuf[0] = 0x47; - q = demux->tsbuf; - } - dvb_dmx_swfilter_packet(demux, q); - p += pktsize; - } - - i = count - p; - if (i) { - memcpy(demux->tsbuf, &buf[p], i); - demux->tsbufp = i; - if (pktsize == 204 && demux->tsbuf[0] == 0xB8) - demux->tsbuf[0] = 0x47; - } - -bailout: - spin_unlock(&demux->lock); -} - -void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count) -{ - _dvb_dmx_swfilter(demux, buf, count, 188); -} -EXPORT_SYMBOL(dvb_dmx_swfilter); - -void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, size_t count) -{ - _dvb_dmx_swfilter(demux, buf, count, 204); -} -EXPORT_SYMBOL(dvb_dmx_swfilter_204); - -void dvb_dmx_swfilter_raw(struct dvb_demux *demux, const u8 *buf, size_t count) -{ - spin_lock(&demux->lock); - - demux->feed->cb.ts(buf, count, NULL, 0, &demux->feed->feed.ts, DMX_OK); - - spin_unlock(&demux->lock); -} -EXPORT_SYMBOL(dvb_dmx_swfilter_raw); - -static struct dvb_demux_filter *dvb_dmx_filter_alloc(struct dvb_demux *demux) -{ - int i; - - for (i = 0; i < demux->filternum; i++) - if (demux->filter[i].state == DMX_STATE_FREE) - break; - - if (i == demux->filternum) - return NULL; - - demux->filter[i].state = DMX_STATE_ALLOCATED; - - return &demux->filter[i]; -} - -static struct dvb_demux_feed *dvb_dmx_feed_alloc(struct dvb_demux *demux) -{ - int i; - - for (i = 0; i < demux->feednum; i++) - if (demux->feed[i].state == DMX_STATE_FREE) - break; - - if (i == demux->feednum) - return NULL; - - demux->feed[i].state = DMX_STATE_ALLOCATED; - - return &demux->feed[i]; -} - -static int dvb_demux_feed_find(struct dvb_demux_feed *feed) -{ - struct dvb_demux_feed *entry; - - list_for_each_entry(entry, &feed->demux->feed_list, list_head) - if (entry == feed) - return 1; - - return 0; -} - -static void dvb_demux_feed_add(struct dvb_demux_feed *feed) -{ - spin_lock_irq(&feed->demux->lock); - if (dvb_demux_feed_find(feed)) { - printk(KERN_ERR "%s: feed already in list (type=%x state=%x pid=%x)\n", - __func__, feed->type, feed->state, feed->pid); - goto out; - } - - list_add(&feed->list_head, &feed->demux->feed_list); -out: - spin_unlock_irq(&feed->demux->lock); -} - -static void dvb_demux_feed_del(struct dvb_demux_feed *feed) -{ - spin_lock_irq(&feed->demux->lock); - if (!(dvb_demux_feed_find(feed))) { - printk(KERN_ERR "%s: feed not in list (type=%x state=%x pid=%x)\n", - __func__, feed->type, feed->state, feed->pid); - goto out; - } - - list_del(&feed->list_head); -out: - spin_unlock_irq(&feed->demux->lock); -} - -static int dmx_ts_feed_set(struct dmx_ts_feed *ts_feed, u16 pid, int ts_type, - enum dmx_ts_pes pes_type, - size_t circular_buffer_size, struct timespec timeout) -{ - struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; - struct dvb_demux *demux = feed->demux; - - if (pid > DMX_MAX_PID) - return -EINVAL; - - if (mutex_lock_interruptible(&demux->mutex)) - return -ERESTARTSYS; - - if (ts_type & TS_DECODER) { - if (pes_type >= DMX_TS_PES_OTHER) { - mutex_unlock(&demux->mutex); - return -EINVAL; - } - - if (demux->pesfilter[pes_type] && - demux->pesfilter[pes_type] != feed) { - mutex_unlock(&demux->mutex); - return -EINVAL; - } - - demux->pesfilter[pes_type] = feed; - demux->pids[pes_type] = pid; - } - - dvb_demux_feed_add(feed); - - feed->pid = pid; - feed->buffer_size = circular_buffer_size; - feed->timeout = timeout; - feed->ts_type = ts_type; - feed->pes_type = pes_type; - - if (feed->buffer_size) { -#ifdef NOBUFS - feed->buffer = NULL; -#else - feed->buffer = vmalloc(feed->buffer_size); - if (!feed->buffer) { - mutex_unlock(&demux->mutex); - return -ENOMEM; - } -#endif - } - - feed->state = DMX_STATE_READY; - mutex_unlock(&demux->mutex); - - return 0; -} - -static int dmx_ts_feed_start_filtering(struct dmx_ts_feed *ts_feed) -{ - struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; - struct dvb_demux *demux = feed->demux; - int ret; - - if (mutex_lock_interruptible(&demux->mutex)) - return -ERESTARTSYS; - - if (feed->state != DMX_STATE_READY || feed->type != DMX_TYPE_TS) { - mutex_unlock(&demux->mutex); - return -EINVAL; - } - - if (!demux->start_feed) { - mutex_unlock(&demux->mutex); - return -ENODEV; - } - - if ((ret = demux->start_feed(feed)) < 0) { - mutex_unlock(&demux->mutex); - return ret; - } - - spin_lock_irq(&demux->lock); - ts_feed->is_filtering = 1; - feed->state = DMX_STATE_GO; - spin_unlock_irq(&demux->lock); - mutex_unlock(&demux->mutex); - - return 0; -} - -static int dmx_ts_feed_stop_filtering(struct dmx_ts_feed *ts_feed) -{ - struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; - struct dvb_demux *demux = feed->demux; - int ret; - - mutex_lock(&demux->mutex); - - if (feed->state < DMX_STATE_GO) { - mutex_unlock(&demux->mutex); - return -EINVAL; - } - - if (!demux->stop_feed) { - mutex_unlock(&demux->mutex); - return -ENODEV; - } - - ret = demux->stop_feed(feed); - - spin_lock_irq(&demux->lock); - ts_feed->is_filtering = 0; - feed->state = DMX_STATE_ALLOCATED; - spin_unlock_irq(&demux->lock); - mutex_unlock(&demux->mutex); - - return ret; -} - -static int dvbdmx_allocate_ts_feed(struct dmx_demux *dmx, - struct dmx_ts_feed **ts_feed, - dmx_ts_cb callback) -{ - struct dvb_demux *demux = (struct dvb_demux *)dmx; - struct dvb_demux_feed *feed; - - if (mutex_lock_interruptible(&demux->mutex)) - return -ERESTARTSYS; - - if (!(feed = dvb_dmx_feed_alloc(demux))) { - mutex_unlock(&demux->mutex); - return -EBUSY; - } - - feed->type = DMX_TYPE_TS; - feed->cb.ts = callback; - feed->demux = demux; - feed->pid = 0xffff; - feed->peslen = 0xfffa; - feed->buffer = NULL; - - (*ts_feed) = &feed->feed.ts; - (*ts_feed)->parent = dmx; - (*ts_feed)->priv = NULL; - (*ts_feed)->is_filtering = 0; - (*ts_feed)->start_filtering = dmx_ts_feed_start_filtering; - (*ts_feed)->stop_filtering = dmx_ts_feed_stop_filtering; - (*ts_feed)->set = dmx_ts_feed_set; - - if (!(feed->filter = dvb_dmx_filter_alloc(demux))) { - feed->state = DMX_STATE_FREE; - mutex_unlock(&demux->mutex); - return -EBUSY; - } - - feed->filter->type = DMX_TYPE_TS; - feed->filter->feed = feed; - feed->filter->state = DMX_STATE_READY; - - mutex_unlock(&demux->mutex); - - return 0; -} - -static int dvbdmx_release_ts_feed(struct dmx_demux *dmx, - struct dmx_ts_feed *ts_feed) -{ - struct dvb_demux *demux = (struct dvb_demux *)dmx; - struct dvb_demux_feed *feed = (struct dvb_demux_feed *)ts_feed; - - mutex_lock(&demux->mutex); - - if (feed->state == DMX_STATE_FREE) { - mutex_unlock(&demux->mutex); - return -EINVAL; - } -#ifndef NOBUFS - vfree(feed->buffer); - feed->buffer = NULL; -#endif - - feed->state = DMX_STATE_FREE; - feed->filter->state = DMX_STATE_FREE; - - dvb_demux_feed_del(feed); - - feed->pid = 0xffff; - - if (feed->ts_type & TS_DECODER && feed->pes_type < DMX_TS_PES_OTHER) - demux->pesfilter[feed->pes_type] = NULL; - - mutex_unlock(&demux->mutex); - return 0; -} - -/****************************************************************************** - * dmx_section_feed API calls - ******************************************************************************/ - -static int dmx_section_feed_allocate_filter(struct dmx_section_feed *feed, - struct dmx_section_filter **filter) -{ - struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; - struct dvb_demux *dvbdemux = dvbdmxfeed->demux; - struct dvb_demux_filter *dvbdmxfilter; - - if (mutex_lock_interruptible(&dvbdemux->mutex)) - return -ERESTARTSYS; - - dvbdmxfilter = dvb_dmx_filter_alloc(dvbdemux); - if (!dvbdmxfilter) { - mutex_unlock(&dvbdemux->mutex); - return -EBUSY; - } - - spin_lock_irq(&dvbdemux->lock); - *filter = &dvbdmxfilter->filter; - (*filter)->parent = feed; - (*filter)->priv = NULL; - dvbdmxfilter->feed = dvbdmxfeed; - dvbdmxfilter->type = DMX_TYPE_SEC; - dvbdmxfilter->state = DMX_STATE_READY; - dvbdmxfilter->next = dvbdmxfeed->filter; - dvbdmxfeed->filter = dvbdmxfilter; - spin_unlock_irq(&dvbdemux->lock); - - mutex_unlock(&dvbdemux->mutex); - return 0; -} - -static int dmx_section_feed_set(struct dmx_section_feed *feed, - u16 pid, size_t circular_buffer_size, - int check_crc) -{ - struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; - struct dvb_demux *dvbdmx = dvbdmxfeed->demux; - - if (pid > 0x1fff) - return -EINVAL; - - if (mutex_lock_interruptible(&dvbdmx->mutex)) - return -ERESTARTSYS; - - dvb_demux_feed_add(dvbdmxfeed); - - dvbdmxfeed->pid = pid; - dvbdmxfeed->buffer_size = circular_buffer_size; - dvbdmxfeed->feed.sec.check_crc = check_crc; - -#ifdef NOBUFS - dvbdmxfeed->buffer = NULL; -#else - dvbdmxfeed->buffer = vmalloc(dvbdmxfeed->buffer_size); - if (!dvbdmxfeed->buffer) { - mutex_unlock(&dvbdmx->mutex); - return -ENOMEM; - } -#endif - - dvbdmxfeed->state = DMX_STATE_READY; - mutex_unlock(&dvbdmx->mutex); - return 0; -} - -static void prepare_secfilters(struct dvb_demux_feed *dvbdmxfeed) -{ - int i; - struct dvb_demux_filter *f; - struct dmx_section_filter *sf; - u8 mask, mode, doneq; - - if (!(f = dvbdmxfeed->filter)) - return; - do { - sf = &f->filter; - doneq = 0; - for (i = 0; i < DVB_DEMUX_MASK_MAX; i++) { - mode = sf->filter_mode[i]; - mask = sf->filter_mask[i]; - f->maskandmode[i] = mask & mode; - doneq |= f->maskandnotmode[i] = mask & ~mode; - } - f->doneq = doneq ? 1 : 0; - } while ((f = f->next)); -} - -static int dmx_section_feed_start_filtering(struct dmx_section_feed *feed) -{ - struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; - struct dvb_demux *dvbdmx = dvbdmxfeed->demux; - int ret; - - if (mutex_lock_interruptible(&dvbdmx->mutex)) - return -ERESTARTSYS; - - if (feed->is_filtering) { - mutex_unlock(&dvbdmx->mutex); - return -EBUSY; - } - - if (!dvbdmxfeed->filter) { - mutex_unlock(&dvbdmx->mutex); - return -EINVAL; - } - - dvbdmxfeed->feed.sec.tsfeedp = 0; - dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base; - dvbdmxfeed->feed.sec.secbufp = 0; - dvbdmxfeed->feed.sec.seclen = 0; - - if (!dvbdmx->start_feed) { - mutex_unlock(&dvbdmx->mutex); - return -ENODEV; - } - - prepare_secfilters(dvbdmxfeed); - - if ((ret = dvbdmx->start_feed(dvbdmxfeed)) < 0) { - mutex_unlock(&dvbdmx->mutex); - return ret; - } - - spin_lock_irq(&dvbdmx->lock); - feed->is_filtering = 1; - dvbdmxfeed->state = DMX_STATE_GO; - spin_unlock_irq(&dvbdmx->lock); - - mutex_unlock(&dvbdmx->mutex); - return 0; -} - -static int dmx_section_feed_stop_filtering(struct dmx_section_feed *feed) -{ - struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; - struct dvb_demux *dvbdmx = dvbdmxfeed->demux; - int ret; - - mutex_lock(&dvbdmx->mutex); - - if (!dvbdmx->stop_feed) { - mutex_unlock(&dvbdmx->mutex); - return -ENODEV; - } - - ret = dvbdmx->stop_feed(dvbdmxfeed); - - spin_lock_irq(&dvbdmx->lock); - dvbdmxfeed->state = DMX_STATE_READY; - feed->is_filtering = 0; - spin_unlock_irq(&dvbdmx->lock); - - mutex_unlock(&dvbdmx->mutex); - return ret; -} - -static int dmx_section_feed_release_filter(struct dmx_section_feed *feed, - struct dmx_section_filter *filter) -{ - struct dvb_demux_filter *dvbdmxfilter = (struct dvb_demux_filter *)filter, *f; - struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; - struct dvb_demux *dvbdmx = dvbdmxfeed->demux; - - mutex_lock(&dvbdmx->mutex); - - if (dvbdmxfilter->feed != dvbdmxfeed) { - mutex_unlock(&dvbdmx->mutex); - return -EINVAL; - } - - if (feed->is_filtering) - feed->stop_filtering(feed); - - spin_lock_irq(&dvbdmx->lock); - f = dvbdmxfeed->filter; - - if (f == dvbdmxfilter) { - dvbdmxfeed->filter = dvbdmxfilter->next; - } else { - while (f->next != dvbdmxfilter) - f = f->next; - f->next = f->next->next; - } - - dvbdmxfilter->state = DMX_STATE_FREE; - spin_unlock_irq(&dvbdmx->lock); - mutex_unlock(&dvbdmx->mutex); - return 0; -} - -static int dvbdmx_allocate_section_feed(struct dmx_demux *demux, - struct dmx_section_feed **feed, - dmx_section_cb callback) -{ - struct dvb_demux *dvbdmx = (struct dvb_demux *)demux; - struct dvb_demux_feed *dvbdmxfeed; - - if (mutex_lock_interruptible(&dvbdmx->mutex)) - return -ERESTARTSYS; - - if (!(dvbdmxfeed = dvb_dmx_feed_alloc(dvbdmx))) { - mutex_unlock(&dvbdmx->mutex); - return -EBUSY; - } - - dvbdmxfeed->type = DMX_TYPE_SEC; - dvbdmxfeed->cb.sec = callback; - dvbdmxfeed->demux = dvbdmx; - dvbdmxfeed->pid = 0xffff; - dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base; - dvbdmxfeed->feed.sec.secbufp = dvbdmxfeed->feed.sec.seclen = 0; - dvbdmxfeed->feed.sec.tsfeedp = 0; - dvbdmxfeed->filter = NULL; - dvbdmxfeed->buffer = NULL; - - (*feed) = &dvbdmxfeed->feed.sec; - (*feed)->is_filtering = 0; - (*feed)->parent = demux; - (*feed)->priv = NULL; - - (*feed)->set = dmx_section_feed_set; - (*feed)->allocate_filter = dmx_section_feed_allocate_filter; - (*feed)->start_filtering = dmx_section_feed_start_filtering; - (*feed)->stop_filtering = dmx_section_feed_stop_filtering; - (*feed)->release_filter = dmx_section_feed_release_filter; - - mutex_unlock(&dvbdmx->mutex); - return 0; -} - -static int dvbdmx_release_section_feed(struct dmx_demux *demux, - struct dmx_section_feed *feed) -{ - struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *)feed; - struct dvb_demux *dvbdmx = (struct dvb_demux *)demux; - - mutex_lock(&dvbdmx->mutex); - - if (dvbdmxfeed->state == DMX_STATE_FREE) { - mutex_unlock(&dvbdmx->mutex); - return -EINVAL; - } -#ifndef NOBUFS - vfree(dvbdmxfeed->buffer); - dvbdmxfeed->buffer = NULL; -#endif - dvbdmxfeed->state = DMX_STATE_FREE; - - dvb_demux_feed_del(dvbdmxfeed); - - dvbdmxfeed->pid = 0xffff; - - mutex_unlock(&dvbdmx->mutex); - return 0; -} - -/****************************************************************************** - * dvb_demux kernel data API calls - ******************************************************************************/ - -static int dvbdmx_open(struct dmx_demux *demux) -{ - struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - - if (dvbdemux->users >= MAX_DVB_DEMUX_USERS) - return -EUSERS; - - dvbdemux->users++; - return 0; -} - -static int dvbdmx_close(struct dmx_demux *demux) -{ - struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - - if (dvbdemux->users == 0) - return -ENODEV; - - dvbdemux->users--; - //FIXME: release any unneeded resources if users==0 - return 0; -} - -static int dvbdmx_write(struct dmx_demux *demux, const char __user *buf, size_t count) -{ - struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - void *p; - - if ((!demux->frontend) || (demux->frontend->source != DMX_MEMORY_FE)) - return -EINVAL; - - p = memdup_user(buf, count); - if (IS_ERR(p)) - return PTR_ERR(p); - if (mutex_lock_interruptible(&dvbdemux->mutex)) { - kfree(p); - return -ERESTARTSYS; - } - dvb_dmx_swfilter(dvbdemux, p, count); - kfree(p); - mutex_unlock(&dvbdemux->mutex); - - if (signal_pending(current)) - return -EINTR; - return count; -} - -static int dvbdmx_add_frontend(struct dmx_demux *demux, - struct dmx_frontend *frontend) -{ - struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - struct list_head *head = &dvbdemux->frontend_list; - - list_add(&(frontend->connectivity_list), head); - - return 0; -} - -static int dvbdmx_remove_frontend(struct dmx_demux *demux, - struct dmx_frontend *frontend) -{ - struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - struct list_head *pos, *n, *head = &dvbdemux->frontend_list; - - list_for_each_safe(pos, n, head) { - if (DMX_FE_ENTRY(pos) == frontend) { - list_del(pos); - return 0; - } - } - - return -ENODEV; -} - -static struct list_head *dvbdmx_get_frontends(struct dmx_demux *demux) -{ - struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - - if (list_empty(&dvbdemux->frontend_list)) - return NULL; - - return &dvbdemux->frontend_list; -} - -static int dvbdmx_connect_frontend(struct dmx_demux *demux, - struct dmx_frontend *frontend) -{ - struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - - if (demux->frontend) - return -EINVAL; - - mutex_lock(&dvbdemux->mutex); - - demux->frontend = frontend; - mutex_unlock(&dvbdemux->mutex); - return 0; -} - -static int dvbdmx_disconnect_frontend(struct dmx_demux *demux) -{ - struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - - mutex_lock(&dvbdemux->mutex); - - demux->frontend = NULL; - mutex_unlock(&dvbdemux->mutex); - return 0; -} - -static int dvbdmx_get_pes_pids(struct dmx_demux *demux, u16 * pids) -{ - struct dvb_demux *dvbdemux = (struct dvb_demux *)demux; - - memcpy(pids, dvbdemux->pids, 5 * sizeof(u16)); - return 0; -} - -int dvb_dmx_init(struct dvb_demux *dvbdemux) -{ - int i; - struct dmx_demux *dmx = &dvbdemux->dmx; - - dvbdemux->cnt_storage = NULL; - dvbdemux->users = 0; - dvbdemux->filter = vmalloc(dvbdemux->filternum * sizeof(struct dvb_demux_filter)); - - if (!dvbdemux->filter) - return -ENOMEM; - - dvbdemux->feed = vmalloc(dvbdemux->feednum * sizeof(struct dvb_demux_feed)); - if (!dvbdemux->feed) { - vfree(dvbdemux->filter); - dvbdemux->filter = NULL; - return -ENOMEM; - } - for (i = 0; i < dvbdemux->filternum; i++) { - dvbdemux->filter[i].state = DMX_STATE_FREE; - dvbdemux->filter[i].index = i; - } - for (i = 0; i < dvbdemux->feednum; i++) { - dvbdemux->feed[i].state = DMX_STATE_FREE; - dvbdemux->feed[i].index = i; - } - - dvbdemux->cnt_storage = vmalloc(MAX_PID + 1); - if (!dvbdemux->cnt_storage) - printk(KERN_WARNING "Couldn't allocate memory for TS/TEI check. Disabling it\n"); - - INIT_LIST_HEAD(&dvbdemux->frontend_list); - - for (i = 0; i < DMX_TS_PES_OTHER; i++) { - dvbdemux->pesfilter[i] = NULL; - dvbdemux->pids[i] = 0xffff; - } - - INIT_LIST_HEAD(&dvbdemux->feed_list); - - dvbdemux->playing = 0; - dvbdemux->recording = 0; - dvbdemux->tsbufp = 0; - - if (!dvbdemux->check_crc32) - dvbdemux->check_crc32 = dvb_dmx_crc32; - - if (!dvbdemux->memcopy) - dvbdemux->memcopy = dvb_dmx_memcopy; - - dmx->frontend = NULL; - dmx->priv = dvbdemux; - dmx->open = dvbdmx_open; - dmx->close = dvbdmx_close; - dmx->write = dvbdmx_write; - dmx->allocate_ts_feed = dvbdmx_allocate_ts_feed; - dmx->release_ts_feed = dvbdmx_release_ts_feed; - dmx->allocate_section_feed = dvbdmx_allocate_section_feed; - dmx->release_section_feed = dvbdmx_release_section_feed; - - dmx->add_frontend = dvbdmx_add_frontend; - dmx->remove_frontend = dvbdmx_remove_frontend; - dmx->get_frontends = dvbdmx_get_frontends; - dmx->connect_frontend = dvbdmx_connect_frontend; - dmx->disconnect_frontend = dvbdmx_disconnect_frontend; - dmx->get_pes_pids = dvbdmx_get_pes_pids; - - mutex_init(&dvbdemux->mutex); - spin_lock_init(&dvbdemux->lock); - - return 0; -} - -EXPORT_SYMBOL(dvb_dmx_init); - -void dvb_dmx_release(struct dvb_demux *dvbdemux) -{ - vfree(dvbdemux->cnt_storage); - vfree(dvbdemux->filter); - vfree(dvbdemux->feed); -} - -EXPORT_SYMBOL(dvb_dmx_release); diff --git a/drivers/media/dvb/dvb-core/dvb_frontend.c b/drivers/media/dvb/dvb-core/dvb_frontend.c deleted file mode 100644 index aebcdf221dda..000000000000 --- a/drivers/media/dvb/dvb-core/dvb_frontend.c +++ /dev/null @@ -1,2563 +0,0 @@ -/* - * dvb_frontend.c: DVB frontend tuning interface/thread - * - * - * Copyright (C) 1999-2001 Ralph Metzler - * Marcus Metzler - * Holger Waechtler - * for convergence integrated media GmbH - * - * Copyright (C) 2004 Andrew de Quincey (tuning thread cleanup) - * - * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * Or, point your browser to http://www.gnu.org/copyleft/gpl.html - */ - -/* Enables DVBv3 compatibility bits at the headers */ -#define __DVB_CORE__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dvb_frontend.h" -#include "dvbdev.h" -#include - -static int dvb_frontend_debug; -static int dvb_shutdown_timeout; -static int dvb_force_auto_inversion; -static int dvb_override_tune_delay; -static int dvb_powerdown_on_sleep = 1; -static int dvb_mfe_wait_time = 5; - -module_param_named(frontend_debug, dvb_frontend_debug, int, 0644); -MODULE_PARM_DESC(frontend_debug, "Turn on/off frontend core debugging (default:off)."); -module_param(dvb_shutdown_timeout, int, 0644); -MODULE_PARM_DESC(dvb_shutdown_timeout, "wait seconds after close() before suspending hardware"); -module_param(dvb_force_auto_inversion, int, 0644); -MODULE_PARM_DESC(dvb_force_auto_inversion, "0: normal (default), 1: INVERSION_AUTO forced always"); -module_param(dvb_override_tune_delay, int, 0644); -MODULE_PARM_DESC(dvb_override_tune_delay, "0: normal (default), >0 => delay in milliseconds to wait for lock after a tune attempt"); -module_param(dvb_powerdown_on_sleep, int, 0644); -MODULE_PARM_DESC(dvb_powerdown_on_sleep, "0: do not power down, 1: turn LNB voltage off on sleep (default)"); -module_param(dvb_mfe_wait_time, int, 0644); -MODULE_PARM_DESC(dvb_mfe_wait_time, "Wait up to seconds on open() for multi-frontend to become available (default:5 seconds)"); - -#define dprintk if (dvb_frontend_debug) printk - -#define FESTATE_IDLE 1 -#define FESTATE_RETUNE 2 -#define FESTATE_TUNING_FAST 4 -#define FESTATE_TUNING_SLOW 8 -#define FESTATE_TUNED 16 -#define FESTATE_ZIGZAG_FAST 32 -#define FESTATE_ZIGZAG_SLOW 64 -#define FESTATE_DISEQC 128 -#define FESTATE_ERROR 256 -#define FESTATE_WAITFORLOCK (FESTATE_TUNING_FAST | FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW | FESTATE_DISEQC) -#define FESTATE_SEARCHING_FAST (FESTATE_TUNING_FAST | FESTATE_ZIGZAG_FAST) -#define FESTATE_SEARCHING_SLOW (FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_SLOW) -#define FESTATE_LOSTLOCK (FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW) - -#define FE_ALGO_HW 1 -/* - * FESTATE_IDLE. No tuning parameters have been supplied and the loop is idling. - * FESTATE_RETUNE. Parameters have been supplied, but we have not yet performed the first tune. - * FESTATE_TUNING_FAST. Tuning parameters have been supplied and fast zigzag scan is in progress. - * FESTATE_TUNING_SLOW. Tuning parameters have been supplied. Fast zigzag failed, so we're trying again, but slower. - * FESTATE_TUNED. The frontend has successfully locked on. - * FESTATE_ZIGZAG_FAST. The lock has been lost, and a fast zigzag has been initiated to try and regain it. - * FESTATE_ZIGZAG_SLOW. The lock has been lost. Fast zigzag has been failed, so we're trying again, but slower. - * FESTATE_DISEQC. A DISEQC command has just been issued. - * FESTATE_WAITFORLOCK. When we're waiting for a lock. - * FESTATE_SEARCHING_FAST. When we're searching for a signal using a fast zigzag scan. - * FESTATE_SEARCHING_SLOW. When we're searching for a signal using a slow zigzag scan. - * FESTATE_LOSTLOCK. When the lock has been lost, and we're searching it again. - */ - -#define DVB_FE_NO_EXIT 0 -#define DVB_FE_NORMAL_EXIT 1 -#define DVB_FE_DEVICE_REMOVED 2 - -static DEFINE_MUTEX(frontend_mutex); - -struct dvb_frontend_private { - - /* thread/frontend values */ - struct dvb_device *dvbdev; - struct dvb_frontend_parameters parameters_out; - struct dvb_fe_events events; - struct semaphore sem; - struct list_head list_head; - wait_queue_head_t wait_queue; - struct task_struct *thread; - unsigned long release_jiffies; - unsigned int exit; - unsigned int wakeup; - fe_status_t status; - unsigned long tune_mode_flags; - unsigned int delay; - unsigned int reinitialise; - int tone; - int voltage; - - /* swzigzag values */ - unsigned int state; - unsigned int bending; - int lnb_drift; - unsigned int inversion; - unsigned int auto_step; - unsigned int auto_sub_step; - unsigned int started_auto_step; - unsigned int min_delay; - unsigned int max_drift; - unsigned int step_size; - int quality; - unsigned int check_wrapped; - enum dvbfe_search algo_status; -}; - -static void dvb_frontend_wakeup(struct dvb_frontend *fe); -static int dtv_get_frontend(struct dvb_frontend *fe, - struct dvb_frontend_parameters *p_out); -static int dtv_property_legacy_params_sync(struct dvb_frontend *fe, - struct dvb_frontend_parameters *p); - -static bool has_get_frontend(struct dvb_frontend *fe) -{ - return fe->ops.get_frontend != NULL; -} - -/* - * Due to DVBv3 API calls, a delivery system should be mapped into one of - * the 4 DVBv3 delivery systems (FE_QPSK, FE_QAM, FE_OFDM or FE_ATSC), - * otherwise, a DVBv3 call will fail. - */ -enum dvbv3_emulation_type { - DVBV3_UNKNOWN, - DVBV3_QPSK, - DVBV3_QAM, - DVBV3_OFDM, - DVBV3_ATSC, -}; - -static enum dvbv3_emulation_type dvbv3_type(u32 delivery_system) -{ - switch (delivery_system) { - case SYS_DVBC_ANNEX_A: - case SYS_DVBC_ANNEX_C: - return DVBV3_QAM; - case SYS_DVBS: - case SYS_DVBS2: - case SYS_TURBO: - case SYS_ISDBS: - case SYS_DSS: - return DVBV3_QPSK; - case SYS_DVBT: - case SYS_DVBT2: - case SYS_ISDBT: - case SYS_DMBTH: - return DVBV3_OFDM; - case SYS_ATSC: - case SYS_ATSCMH: - case SYS_DVBC_ANNEX_B: - return DVBV3_ATSC; - case SYS_UNDEFINED: - case SYS_ISDBC: - case SYS_DVBH: - case SYS_DAB: - default: - /* - * Doesn't know how to emulate those types and/or - * there's no frontend driver from this type yet - * with some emulation code, so, we're not sure yet how - * to handle them, or they're not compatible with a DVBv3 call. - */ - return DVBV3_UNKNOWN; - } -} - -static void dvb_frontend_add_event(struct dvb_frontend *fe, fe_status_t status) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dvb_fe_events *events = &fepriv->events; - struct dvb_frontend_event *e; - int wp; - - dprintk ("%s\n", __func__); - - if ((status & FE_HAS_LOCK) && has_get_frontend(fe)) - dtv_get_frontend(fe, &fepriv->parameters_out); - - mutex_lock(&events->mtx); - - wp = (events->eventw + 1) % MAX_EVENT; - if (wp == events->eventr) { - events->overflow = 1; - events->eventr = (events->eventr + 1) % MAX_EVENT; - } - - e = &events->events[events->eventw]; - e->status = status; - e->parameters = fepriv->parameters_out; - - events->eventw = wp; - - mutex_unlock(&events->mtx); - - wake_up_interruptible (&events->wait_queue); -} - -static int dvb_frontend_get_event(struct dvb_frontend *fe, - struct dvb_frontend_event *event, int flags) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dvb_fe_events *events = &fepriv->events; - - dprintk ("%s\n", __func__); - - if (events->overflow) { - events->overflow = 0; - return -EOVERFLOW; - } - - if (events->eventw == events->eventr) { - int ret; - - if (flags & O_NONBLOCK) - return -EWOULDBLOCK; - - up(&fepriv->sem); - - ret = wait_event_interruptible (events->wait_queue, - events->eventw != events->eventr); - - if (down_interruptible (&fepriv->sem)) - return -ERESTARTSYS; - - if (ret < 0) - return ret; - } - - mutex_lock(&events->mtx); - *event = events->events[events->eventr]; - events->eventr = (events->eventr + 1) % MAX_EVENT; - mutex_unlock(&events->mtx); - - return 0; -} - -static void dvb_frontend_clear_events(struct dvb_frontend *fe) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dvb_fe_events *events = &fepriv->events; - - mutex_lock(&events->mtx); - events->eventr = events->eventw; - mutex_unlock(&events->mtx); -} - -static void dvb_frontend_init(struct dvb_frontend *fe) -{ - dprintk ("DVB: initialising adapter %i frontend %i (%s)...\n", - fe->dvb->num, - fe->id, - fe->ops.info.name); - - if (fe->ops.init) - fe->ops.init(fe); - if (fe->ops.tuner_ops.init) { - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - fe->ops.tuner_ops.init(fe); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } -} - -void dvb_frontend_reinitialise(struct dvb_frontend *fe) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - - fepriv->reinitialise = 1; - dvb_frontend_wakeup(fe); -} -EXPORT_SYMBOL(dvb_frontend_reinitialise); - -static void dvb_frontend_swzigzag_update_delay(struct dvb_frontend_private *fepriv, int locked) -{ - int q2; - - dprintk ("%s\n", __func__); - - if (locked) - (fepriv->quality) = (fepriv->quality * 220 + 36*256) / 256; - else - (fepriv->quality) = (fepriv->quality * 220 + 0) / 256; - - q2 = fepriv->quality - 128; - q2 *= q2; - - fepriv->delay = fepriv->min_delay + q2 * HZ / (128*128); -} - -/** - * Performs automatic twiddling of frontend parameters. - * - * @param fe The frontend concerned. - * @param check_wrapped Checks if an iteration has completed. DO NOT SET ON THE FIRST ATTEMPT - * @returns Number of complete iterations that have been performed. - */ -static int dvb_frontend_swzigzag_autotune(struct dvb_frontend *fe, int check_wrapped) -{ - int autoinversion; - int ready = 0; - int fe_set_err = 0; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache, tmp; - int original_inversion = c->inversion; - u32 original_frequency = c->frequency; - - /* are we using autoinversion? */ - autoinversion = ((!(fe->ops.info.caps & FE_CAN_INVERSION_AUTO)) && - (c->inversion == INVERSION_AUTO)); - - /* setup parameters correctly */ - while(!ready) { - /* calculate the lnb_drift */ - fepriv->lnb_drift = fepriv->auto_step * fepriv->step_size; - - /* wrap the auto_step if we've exceeded the maximum drift */ - if (fepriv->lnb_drift > fepriv->max_drift) { - fepriv->auto_step = 0; - fepriv->auto_sub_step = 0; - fepriv->lnb_drift = 0; - } - - /* perform inversion and +/- zigzag */ - switch(fepriv->auto_sub_step) { - case 0: - /* try with the current inversion and current drift setting */ - ready = 1; - break; - - case 1: - if (!autoinversion) break; - - fepriv->inversion = (fepriv->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF; - ready = 1; - break; - - case 2: - if (fepriv->lnb_drift == 0) break; - - fepriv->lnb_drift = -fepriv->lnb_drift; - ready = 1; - break; - - case 3: - if (fepriv->lnb_drift == 0) break; - if (!autoinversion) break; - - fepriv->inversion = (fepriv->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF; - fepriv->lnb_drift = -fepriv->lnb_drift; - ready = 1; - break; - - default: - fepriv->auto_step++; - fepriv->auto_sub_step = -1; /* it'll be incremented to 0 in a moment */ - break; - } - - if (!ready) fepriv->auto_sub_step++; - } - - /* if this attempt would hit where we started, indicate a complete - * iteration has occurred */ - if ((fepriv->auto_step == fepriv->started_auto_step) && - (fepriv->auto_sub_step == 0) && check_wrapped) { - return 1; - } - - dprintk("%s: drift:%i inversion:%i auto_step:%i " - "auto_sub_step:%i started_auto_step:%i\n", - __func__, fepriv->lnb_drift, fepriv->inversion, - fepriv->auto_step, fepriv->auto_sub_step, fepriv->started_auto_step); - - /* set the frontend itself */ - c->frequency += fepriv->lnb_drift; - if (autoinversion) - c->inversion = fepriv->inversion; - tmp = *c; - if (fe->ops.set_frontend) - fe_set_err = fe->ops.set_frontend(fe); - *c = tmp; - if (fe_set_err < 0) { - fepriv->state = FESTATE_ERROR; - return fe_set_err; - } - - c->frequency = original_frequency; - c->inversion = original_inversion; - - fepriv->auto_sub_step++; - return 0; -} - -static void dvb_frontend_swzigzag(struct dvb_frontend *fe) -{ - fe_status_t s = 0; - int retval = 0; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache, tmp; - - /* if we've got no parameters, just keep idling */ - if (fepriv->state & FESTATE_IDLE) { - fepriv->delay = 3*HZ; - fepriv->quality = 0; - return; - } - - /* in SCAN mode, we just set the frontend when asked and leave it alone */ - if (fepriv->tune_mode_flags & FE_TUNE_MODE_ONESHOT) { - if (fepriv->state & FESTATE_RETUNE) { - tmp = *c; - if (fe->ops.set_frontend) - retval = fe->ops.set_frontend(fe); - *c = tmp; - if (retval < 0) - fepriv->state = FESTATE_ERROR; - else - fepriv->state = FESTATE_TUNED; - } - fepriv->delay = 3*HZ; - fepriv->quality = 0; - return; - } - - /* get the frontend status */ - if (fepriv->state & FESTATE_RETUNE) { - s = 0; - } else { - if (fe->ops.read_status) - fe->ops.read_status(fe, &s); - if (s != fepriv->status) { - dvb_frontend_add_event(fe, s); - fepriv->status = s; - } - } - - /* if we're not tuned, and we have a lock, move to the TUNED state */ - if ((fepriv->state & FESTATE_WAITFORLOCK) && (s & FE_HAS_LOCK)) { - dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); - fepriv->state = FESTATE_TUNED; - - /* if we're tuned, then we have determined the correct inversion */ - if ((!(fe->ops.info.caps & FE_CAN_INVERSION_AUTO)) && - (c->inversion == INVERSION_AUTO)) { - c->inversion = fepriv->inversion; - } - return; - } - - /* if we are tuned already, check we're still locked */ - if (fepriv->state & FESTATE_TUNED) { - dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); - - /* we're tuned, and the lock is still good... */ - if (s & FE_HAS_LOCK) { - return; - } else { /* if we _WERE_ tuned, but now don't have a lock */ - fepriv->state = FESTATE_ZIGZAG_FAST; - fepriv->started_auto_step = fepriv->auto_step; - fepriv->check_wrapped = 0; - } - } - - /* don't actually do anything if we're in the LOSTLOCK state, - * the frontend is set to FE_CAN_RECOVER, and the max_drift is 0 */ - if ((fepriv->state & FESTATE_LOSTLOCK) && - (fe->ops.info.caps & FE_CAN_RECOVER) && (fepriv->max_drift == 0)) { - dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); - return; - } - - /* don't do anything if we're in the DISEQC state, since this - * might be someone with a motorized dish controlled by DISEQC. - * If its actually a re-tune, there will be a SET_FRONTEND soon enough. */ - if (fepriv->state & FESTATE_DISEQC) { - dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); - return; - } - - /* if we're in the RETUNE state, set everything up for a brand - * new scan, keeping the current inversion setting, as the next - * tune is _very_ likely to require the same */ - if (fepriv->state & FESTATE_RETUNE) { - fepriv->lnb_drift = 0; - fepriv->auto_step = 0; - fepriv->auto_sub_step = 0; - fepriv->started_auto_step = 0; - fepriv->check_wrapped = 0; - } - - /* fast zigzag. */ - if ((fepriv->state & FESTATE_SEARCHING_FAST) || (fepriv->state & FESTATE_RETUNE)) { - fepriv->delay = fepriv->min_delay; - - /* perform a tune */ - retval = dvb_frontend_swzigzag_autotune(fe, - fepriv->check_wrapped); - if (retval < 0) { - return; - } else if (retval) { - /* OK, if we've run out of trials at the fast speed. - * Drop back to slow for the _next_ attempt */ - fepriv->state = FESTATE_SEARCHING_SLOW; - fepriv->started_auto_step = fepriv->auto_step; - return; - } - fepriv->check_wrapped = 1; - - /* if we've just retuned, enter the ZIGZAG_FAST state. - * This ensures we cannot return from an - * FE_SET_FRONTEND ioctl before the first frontend tune - * occurs */ - if (fepriv->state & FESTATE_RETUNE) { - fepriv->state = FESTATE_TUNING_FAST; - } - } - - /* slow zigzag */ - if (fepriv->state & FESTATE_SEARCHING_SLOW) { - dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK); - - /* Note: don't bother checking for wrapping; we stay in this - * state until we get a lock */ - dvb_frontend_swzigzag_autotune(fe, 0); - } -} - -static int dvb_frontend_is_exiting(struct dvb_frontend *fe) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - - if (fepriv->exit != DVB_FE_NO_EXIT) - return 1; - - if (fepriv->dvbdev->writers == 1) - if (time_after_eq(jiffies, fepriv->release_jiffies + - dvb_shutdown_timeout * HZ)) - return 1; - - return 0; -} - -static int dvb_frontend_should_wakeup(struct dvb_frontend *fe) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - - if (fepriv->wakeup) { - fepriv->wakeup = 0; - return 1; - } - return dvb_frontend_is_exiting(fe); -} - -static void dvb_frontend_wakeup(struct dvb_frontend *fe) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - - fepriv->wakeup = 1; - wake_up_interruptible(&fepriv->wait_queue); -} - -static int dvb_frontend_thread(void *data) -{ - struct dvb_frontend *fe = data; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - fe_status_t s; - enum dvbfe_algo algo; - - bool re_tune = false; - - dprintk("%s\n", __func__); - - fepriv->check_wrapped = 0; - fepriv->quality = 0; - fepriv->delay = 3*HZ; - fepriv->status = 0; - fepriv->wakeup = 0; - fepriv->reinitialise = 0; - - dvb_frontend_init(fe); - - set_freezable(); - while (1) { - up(&fepriv->sem); /* is locked when we enter the thread... */ -restart: - wait_event_interruptible_timeout(fepriv->wait_queue, - dvb_frontend_should_wakeup(fe) || kthread_should_stop() - || freezing(current), - fepriv->delay); - - if (kthread_should_stop() || dvb_frontend_is_exiting(fe)) { - /* got signal or quitting */ - fepriv->exit = DVB_FE_NORMAL_EXIT; - break; - } - - if (try_to_freeze()) - goto restart; - - if (down_interruptible(&fepriv->sem)) - break; - - if (fepriv->reinitialise) { - dvb_frontend_init(fe); - if (fe->ops.set_tone && fepriv->tone != -1) - fe->ops.set_tone(fe, fepriv->tone); - if (fe->ops.set_voltage && fepriv->voltage != -1) - fe->ops.set_voltage(fe, fepriv->voltage); - fepriv->reinitialise = 0; - } - - /* do an iteration of the tuning loop */ - if (fe->ops.get_frontend_algo) { - algo = fe->ops.get_frontend_algo(fe); - switch (algo) { - case DVBFE_ALGO_HW: - dprintk("%s: Frontend ALGO = DVBFE_ALGO_HW\n", __func__); - - if (fepriv->state & FESTATE_RETUNE) { - dprintk("%s: Retune requested, FESTATE_RETUNE\n", __func__); - re_tune = true; - fepriv->state = FESTATE_TUNED; - } else { - re_tune = false; - } - - if (fe->ops.tune) - fe->ops.tune(fe, re_tune, fepriv->tune_mode_flags, &fepriv->delay, &s); - - if (s != fepriv->status && !(fepriv->tune_mode_flags & FE_TUNE_MODE_ONESHOT)) { - dprintk("%s: state changed, adding current state\n", __func__); - dvb_frontend_add_event(fe, s); - fepriv->status = s; - } - break; - case DVBFE_ALGO_SW: - dprintk("%s: Frontend ALGO = DVBFE_ALGO_SW\n", __func__); - dvb_frontend_swzigzag(fe); - break; - case DVBFE_ALGO_CUSTOM: - dprintk("%s: Frontend ALGO = DVBFE_ALGO_CUSTOM, state=%d\n", __func__, fepriv->state); - if (fepriv->state & FESTATE_RETUNE) { - dprintk("%s: Retune requested, FESTAT_RETUNE\n", __func__); - fepriv->state = FESTATE_TUNED; - } - /* Case where we are going to search for a carrier - * User asked us to retune again for some reason, possibly - * requesting a search with a new set of parameters - */ - if (fepriv->algo_status & DVBFE_ALGO_SEARCH_AGAIN) { - if (fe->ops.search) { - fepriv->algo_status = fe->ops.search(fe); - /* We did do a search as was requested, the flags are - * now unset as well and has the flags wrt to search. - */ - } else { - fepriv->algo_status &= ~DVBFE_ALGO_SEARCH_AGAIN; - } - } - /* Track the carrier if the search was successful */ - if (fepriv->algo_status != DVBFE_ALGO_SEARCH_SUCCESS) { - fepriv->algo_status |= DVBFE_ALGO_SEARCH_AGAIN; - fepriv->delay = HZ / 2; - } - dtv_property_legacy_params_sync(fe, &fepriv->parameters_out); - fe->ops.read_status(fe, &s); - if (s != fepriv->status) { - dvb_frontend_add_event(fe, s); /* update event list */ - fepriv->status = s; - if (!(s & FE_HAS_LOCK)) { - fepriv->delay = HZ / 10; - fepriv->algo_status |= DVBFE_ALGO_SEARCH_AGAIN; - } else { - fepriv->delay = 60 * HZ; - } - } - break; - default: - dprintk("%s: UNDEFINED ALGO !\n", __func__); - break; - } - } else { - dvb_frontend_swzigzag(fe); - } - } - - if (dvb_powerdown_on_sleep) { - if (fe->ops.set_voltage) - fe->ops.set_voltage(fe, SEC_VOLTAGE_OFF); - if (fe->ops.tuner_ops.sleep) { - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - fe->ops.tuner_ops.sleep(fe); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } - if (fe->ops.sleep) - fe->ops.sleep(fe); - } - - fepriv->thread = NULL; - if (kthread_should_stop()) - fepriv->exit = DVB_FE_DEVICE_REMOVED; - else - fepriv->exit = DVB_FE_NO_EXIT; - mb(); - - dvb_frontend_wakeup(fe); - return 0; -} - -static void dvb_frontend_stop(struct dvb_frontend *fe) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - - dprintk ("%s\n", __func__); - - fepriv->exit = DVB_FE_NORMAL_EXIT; - mb(); - - if (!fepriv->thread) - return; - - kthread_stop(fepriv->thread); - - sema_init(&fepriv->sem, 1); - fepriv->state = FESTATE_IDLE; - - /* paranoia check in case a signal arrived */ - if (fepriv->thread) - printk("dvb_frontend_stop: warning: thread %p won't exit\n", - fepriv->thread); -} - -s32 timeval_usec_diff(struct timeval lasttime, struct timeval curtime) -{ - return ((curtime.tv_usec < lasttime.tv_usec) ? - 1000000 - lasttime.tv_usec + curtime.tv_usec : - curtime.tv_usec - lasttime.tv_usec); -} -EXPORT_SYMBOL(timeval_usec_diff); - -static inline void timeval_usec_add(struct timeval *curtime, u32 add_usec) -{ - curtime->tv_usec += add_usec; - if (curtime->tv_usec >= 1000000) { - curtime->tv_usec -= 1000000; - curtime->tv_sec++; - } -} - -/* - * Sleep until gettimeofday() > waketime + add_usec - * This needs to be as precise as possible, but as the delay is - * usually between 2ms and 32ms, it is done using a scheduled msleep - * followed by usleep (normally a busy-wait loop) for the remainder - */ -void dvb_frontend_sleep_until(struct timeval *waketime, u32 add_usec) -{ - struct timeval lasttime; - s32 delta, newdelta; - - timeval_usec_add(waketime, add_usec); - - do_gettimeofday(&lasttime); - delta = timeval_usec_diff(lasttime, *waketime); - if (delta > 2500) { - msleep((delta - 1500) / 1000); - do_gettimeofday(&lasttime); - newdelta = timeval_usec_diff(lasttime, *waketime); - delta = (newdelta > delta) ? 0 : newdelta; - } - if (delta > 0) - udelay(delta); -} -EXPORT_SYMBOL(dvb_frontend_sleep_until); - -static int dvb_frontend_start(struct dvb_frontend *fe) -{ - int ret; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct task_struct *fe_thread; - - dprintk ("%s\n", __func__); - - if (fepriv->thread) { - if (fepriv->exit == DVB_FE_NO_EXIT) - return 0; - else - dvb_frontend_stop (fe); - } - - if (signal_pending(current)) - return -EINTR; - if (down_interruptible (&fepriv->sem)) - return -EINTR; - - fepriv->state = FESTATE_IDLE; - fepriv->exit = DVB_FE_NO_EXIT; - fepriv->thread = NULL; - mb(); - - fe_thread = kthread_run(dvb_frontend_thread, fe, - "kdvb-ad-%i-fe-%i", fe->dvb->num,fe->id); - if (IS_ERR(fe_thread)) { - ret = PTR_ERR(fe_thread); - printk("dvb_frontend_start: failed to start kthread (%d)\n", ret); - up(&fepriv->sem); - return ret; - } - fepriv->thread = fe_thread; - return 0; -} - -static void dvb_frontend_get_frequency_limits(struct dvb_frontend *fe, - u32 *freq_min, u32 *freq_max) -{ - *freq_min = max(fe->ops.info.frequency_min, fe->ops.tuner_ops.info.frequency_min); - - if (fe->ops.info.frequency_max == 0) - *freq_max = fe->ops.tuner_ops.info.frequency_max; - else if (fe->ops.tuner_ops.info.frequency_max == 0) - *freq_max = fe->ops.info.frequency_max; - else - *freq_max = min(fe->ops.info.frequency_max, fe->ops.tuner_ops.info.frequency_max); - - if (*freq_min == 0 || *freq_max == 0) - printk(KERN_WARNING "DVB: adapter %i frontend %u frequency limits undefined - fix the driver\n", - fe->dvb->num,fe->id); -} - -static int dvb_frontend_check_parameters(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u32 freq_min; - u32 freq_max; - - /* range check: frequency */ - dvb_frontend_get_frequency_limits(fe, &freq_min, &freq_max); - if ((freq_min && c->frequency < freq_min) || - (freq_max && c->frequency > freq_max)) { - printk(KERN_WARNING "DVB: adapter %i frontend %i frequency %u out of range (%u..%u)\n", - fe->dvb->num, fe->id, c->frequency, freq_min, freq_max); - return -EINVAL; - } - - /* range check: symbol rate */ - switch (c->delivery_system) { - case SYS_DVBS: - case SYS_DVBS2: - case SYS_TURBO: - case SYS_DVBC_ANNEX_A: - case SYS_DVBC_ANNEX_C: - if ((fe->ops.info.symbol_rate_min && - c->symbol_rate < fe->ops.info.symbol_rate_min) || - (fe->ops.info.symbol_rate_max && - c->symbol_rate > fe->ops.info.symbol_rate_max)) { - printk(KERN_WARNING "DVB: adapter %i frontend %i symbol rate %u out of range (%u..%u)\n", - fe->dvb->num, fe->id, c->symbol_rate, - fe->ops.info.symbol_rate_min, - fe->ops.info.symbol_rate_max); - return -EINVAL; - } - default: - break; - } - - return 0; -} - -static int dvb_frontend_clear_cache(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int i; - u32 delsys; - - delsys = c->delivery_system; - memset(c, 0, sizeof(struct dtv_frontend_properties)); - c->delivery_system = delsys; - - c->state = DTV_CLEAR; - - dprintk("%s() Clearing cache for delivery system %d\n", __func__, - c->delivery_system); - - c->transmission_mode = TRANSMISSION_MODE_AUTO; - c->bandwidth_hz = 0; /* AUTO */ - c->guard_interval = GUARD_INTERVAL_AUTO; - c->hierarchy = HIERARCHY_AUTO; - c->symbol_rate = 0; - c->code_rate_HP = FEC_AUTO; - c->code_rate_LP = FEC_AUTO; - c->fec_inner = FEC_AUTO; - c->rolloff = ROLLOFF_AUTO; - c->voltage = SEC_VOLTAGE_OFF; - c->sectone = SEC_TONE_OFF; - c->pilot = PILOT_AUTO; - - c->isdbt_partial_reception = 0; - c->isdbt_sb_mode = 0; - c->isdbt_sb_subchannel = 0; - c->isdbt_sb_segment_idx = 0; - c->isdbt_sb_segment_count = 0; - c->isdbt_layer_enabled = 0; - for (i = 0; i < 3; i++) { - c->layer[i].fec = FEC_AUTO; - c->layer[i].modulation = QAM_AUTO; - c->layer[i].interleaving = 0; - c->layer[i].segment_count = 0; - } - - c->isdbs_ts_id = 0; - c->dvbt2_plp_id = 0; - - switch (c->delivery_system) { - case SYS_DVBS: - case SYS_DVBS2: - case SYS_TURBO: - c->modulation = QPSK; /* implied for DVB-S in legacy API */ - c->rolloff = ROLLOFF_35;/* implied for DVB-S */ - break; - case SYS_ATSC: - c->modulation = VSB_8; - break; - default: - c->modulation = QAM_AUTO; - break; - } - - return 0; -} - -#define _DTV_CMD(n, s, b) \ -[n] = { \ - .name = #n, \ - .cmd = n, \ - .set = s,\ - .buffer = b \ -} - -static struct dtv_cmds_h dtv_cmds[DTV_MAX_COMMAND + 1] = { - _DTV_CMD(DTV_TUNE, 1, 0), - _DTV_CMD(DTV_CLEAR, 1, 0), - - /* Set */ - _DTV_CMD(DTV_FREQUENCY, 1, 0), - _DTV_CMD(DTV_BANDWIDTH_HZ, 1, 0), - _DTV_CMD(DTV_MODULATION, 1, 0), - _DTV_CMD(DTV_INVERSION, 1, 0), - _DTV_CMD(DTV_DISEQC_MASTER, 1, 1), - _DTV_CMD(DTV_SYMBOL_RATE, 1, 0), - _DTV_CMD(DTV_INNER_FEC, 1, 0), - _DTV_CMD(DTV_VOLTAGE, 1, 0), - _DTV_CMD(DTV_TONE, 1, 0), - _DTV_CMD(DTV_PILOT, 1, 0), - _DTV_CMD(DTV_ROLLOFF, 1, 0), - _DTV_CMD(DTV_DELIVERY_SYSTEM, 1, 0), - _DTV_CMD(DTV_HIERARCHY, 1, 0), - _DTV_CMD(DTV_CODE_RATE_HP, 1, 0), - _DTV_CMD(DTV_CODE_RATE_LP, 1, 0), - _DTV_CMD(DTV_GUARD_INTERVAL, 1, 0), - _DTV_CMD(DTV_TRANSMISSION_MODE, 1, 0), - - _DTV_CMD(DTV_ISDBT_PARTIAL_RECEPTION, 1, 0), - _DTV_CMD(DTV_ISDBT_SOUND_BROADCASTING, 1, 0), - _DTV_CMD(DTV_ISDBT_SB_SUBCHANNEL_ID, 1, 0), - _DTV_CMD(DTV_ISDBT_SB_SEGMENT_IDX, 1, 0), - _DTV_CMD(DTV_ISDBT_SB_SEGMENT_COUNT, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYER_ENABLED, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERA_FEC, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERA_MODULATION, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERA_SEGMENT_COUNT, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERA_TIME_INTERLEAVING, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERB_FEC, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERB_MODULATION, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERB_SEGMENT_COUNT, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERB_TIME_INTERLEAVING, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERC_FEC, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERC_MODULATION, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERC_SEGMENT_COUNT, 1, 0), - _DTV_CMD(DTV_ISDBT_LAYERC_TIME_INTERLEAVING, 1, 0), - - _DTV_CMD(DTV_ISDBS_TS_ID, 1, 0), - _DTV_CMD(DTV_DVBT2_PLP_ID, 1, 0), - - /* Get */ - _DTV_CMD(DTV_DISEQC_SLAVE_REPLY, 0, 1), - _DTV_CMD(DTV_API_VERSION, 0, 0), - _DTV_CMD(DTV_CODE_RATE_HP, 0, 0), - _DTV_CMD(DTV_CODE_RATE_LP, 0, 0), - _DTV_CMD(DTV_GUARD_INTERVAL, 0, 0), - _DTV_CMD(DTV_TRANSMISSION_MODE, 0, 0), - _DTV_CMD(DTV_HIERARCHY, 0, 0), - - _DTV_CMD(DTV_ENUM_DELSYS, 0, 0), - - _DTV_CMD(DTV_ATSCMH_PARADE_ID, 1, 0), - _DTV_CMD(DTV_ATSCMH_RS_FRAME_ENSEMBLE, 1, 0), - - _DTV_CMD(DTV_ATSCMH_FIC_VER, 0, 0), - _DTV_CMD(DTV_ATSCMH_PARADE_ID, 0, 0), - _DTV_CMD(DTV_ATSCMH_NOG, 0, 0), - _DTV_CMD(DTV_ATSCMH_TNOG, 0, 0), - _DTV_CMD(DTV_ATSCMH_SGN, 0, 0), - _DTV_CMD(DTV_ATSCMH_PRC, 0, 0), - _DTV_CMD(DTV_ATSCMH_RS_FRAME_MODE, 0, 0), - _DTV_CMD(DTV_ATSCMH_RS_FRAME_ENSEMBLE, 0, 0), - _DTV_CMD(DTV_ATSCMH_RS_CODE_MODE_PRI, 0, 0), - _DTV_CMD(DTV_ATSCMH_RS_CODE_MODE_SEC, 0, 0), - _DTV_CMD(DTV_ATSCMH_SCCC_BLOCK_MODE, 0, 0), - _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_A, 0, 0), - _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_B, 0, 0), - _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_C, 0, 0), - _DTV_CMD(DTV_ATSCMH_SCCC_CODE_MODE_D, 0, 0), -}; - -static void dtv_property_dump(struct dtv_property *tvp) -{ - int i; - - if (tvp->cmd <= 0 || tvp->cmd > DTV_MAX_COMMAND) { - printk(KERN_WARNING "%s: tvp.cmd = 0x%08x undefined\n", - __func__, tvp->cmd); - return; - } - - dprintk("%s() tvp.cmd = 0x%08x (%s)\n" - ,__func__ - ,tvp->cmd - ,dtv_cmds[ tvp->cmd ].name); - - if(dtv_cmds[ tvp->cmd ].buffer) { - - dprintk("%s() tvp.u.buffer.len = 0x%02x\n" - ,__func__ - ,tvp->u.buffer.len); - - for(i = 0; i < tvp->u.buffer.len; i++) - dprintk("%s() tvp.u.buffer.data[0x%02x] = 0x%02x\n" - ,__func__ - ,i - ,tvp->u.buffer.data[i]); - - } else - dprintk("%s() tvp.u.data = 0x%08x\n", __func__, tvp->u.data); -} - -/* Synchronise the legacy tuning parameters into the cache, so that demodulator - * drivers can use a single set_frontend tuning function, regardless of whether - * it's being used for the legacy or new API, reducing code and complexity. - */ -static int dtv_property_cache_sync(struct dvb_frontend *fe, - struct dtv_frontend_properties *c, - const struct dvb_frontend_parameters *p) -{ - c->frequency = p->frequency; - c->inversion = p->inversion; - - switch (dvbv3_type(c->delivery_system)) { - case DVBV3_QPSK: - dprintk("%s() Preparing QPSK req\n", __func__); - c->symbol_rate = p->u.qpsk.symbol_rate; - c->fec_inner = p->u.qpsk.fec_inner; - break; - case DVBV3_QAM: - dprintk("%s() Preparing QAM req\n", __func__); - c->symbol_rate = p->u.qam.symbol_rate; - c->fec_inner = p->u.qam.fec_inner; - c->modulation = p->u.qam.modulation; - break; - case DVBV3_OFDM: - dprintk("%s() Preparing OFDM req\n", __func__); - switch (p->u.ofdm.bandwidth) { - case BANDWIDTH_10_MHZ: - c->bandwidth_hz = 10000000; - break; - case BANDWIDTH_8_MHZ: - c->bandwidth_hz = 8000000; - break; - case BANDWIDTH_7_MHZ: - c->bandwidth_hz = 7000000; - break; - case BANDWIDTH_6_MHZ: - c->bandwidth_hz = 6000000; - break; - case BANDWIDTH_5_MHZ: - c->bandwidth_hz = 5000000; - break; - case BANDWIDTH_1_712_MHZ: - c->bandwidth_hz = 1712000; - break; - case BANDWIDTH_AUTO: - c->bandwidth_hz = 0; - } - - c->code_rate_HP = p->u.ofdm.code_rate_HP; - c->code_rate_LP = p->u.ofdm.code_rate_LP; - c->modulation = p->u.ofdm.constellation; - c->transmission_mode = p->u.ofdm.transmission_mode; - c->guard_interval = p->u.ofdm.guard_interval; - c->hierarchy = p->u.ofdm.hierarchy_information; - break; - case DVBV3_ATSC: - dprintk("%s() Preparing ATSC req\n", __func__); - c->modulation = p->u.vsb.modulation; - if (c->delivery_system == SYS_ATSCMH) - break; - if ((c->modulation == VSB_8) || (c->modulation == VSB_16)) - c->delivery_system = SYS_ATSC; - else - c->delivery_system = SYS_DVBC_ANNEX_B; - break; - case DVBV3_UNKNOWN: - printk(KERN_ERR - "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", - __func__, c->delivery_system); - return -EINVAL; - } - - return 0; -} - -/* Ensure the cached values are set correctly in the frontend - * legacy tuning structures, for the advanced tuning API. - */ -static int dtv_property_legacy_params_sync(struct dvb_frontend *fe, - struct dvb_frontend_parameters *p) -{ - const struct dtv_frontend_properties *c = &fe->dtv_property_cache; - - p->frequency = c->frequency; - p->inversion = c->inversion; - - switch (dvbv3_type(c->delivery_system)) { - case DVBV3_UNKNOWN: - printk(KERN_ERR - "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", - __func__, c->delivery_system); - return -EINVAL; - case DVBV3_QPSK: - dprintk("%s() Preparing QPSK req\n", __func__); - p->u.qpsk.symbol_rate = c->symbol_rate; - p->u.qpsk.fec_inner = c->fec_inner; - break; - case DVBV3_QAM: - dprintk("%s() Preparing QAM req\n", __func__); - p->u.qam.symbol_rate = c->symbol_rate; - p->u.qam.fec_inner = c->fec_inner; - p->u.qam.modulation = c->modulation; - break; - case DVBV3_OFDM: - dprintk("%s() Preparing OFDM req\n", __func__); - - switch (c->bandwidth_hz) { - case 10000000: - p->u.ofdm.bandwidth = BANDWIDTH_10_MHZ; - break; - case 8000000: - p->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; - break; - case 7000000: - p->u.ofdm.bandwidth = BANDWIDTH_7_MHZ; - break; - case 6000000: - p->u.ofdm.bandwidth = BANDWIDTH_6_MHZ; - break; - case 5000000: - p->u.ofdm.bandwidth = BANDWIDTH_5_MHZ; - break; - case 1712000: - p->u.ofdm.bandwidth = BANDWIDTH_1_712_MHZ; - break; - case 0: - default: - p->u.ofdm.bandwidth = BANDWIDTH_AUTO; - } - p->u.ofdm.code_rate_HP = c->code_rate_HP; - p->u.ofdm.code_rate_LP = c->code_rate_LP; - p->u.ofdm.constellation = c->modulation; - p->u.ofdm.transmission_mode = c->transmission_mode; - p->u.ofdm.guard_interval = c->guard_interval; - p->u.ofdm.hierarchy_information = c->hierarchy; - break; - case DVBV3_ATSC: - dprintk("%s() Preparing VSB req\n", __func__); - p->u.vsb.modulation = c->modulation; - break; - } - return 0; -} - -/** - * dtv_get_frontend - calls a callback for retrieving DTV parameters - * @fe: struct dvb_frontend pointer - * @c: struct dtv_frontend_properties pointer (DVBv5 cache) - * @p_out struct dvb_frontend_parameters pointer (DVBv3 FE struct) - * - * This routine calls either the DVBv3 or DVBv5 get_frontend call. - * If c is not null, it will update the DVBv5 cache struct pointed by it. - * If p_out is not null, it will update the DVBv3 params pointed by it. - */ -static int dtv_get_frontend(struct dvb_frontend *fe, - struct dvb_frontend_parameters *p_out) -{ - int r; - - if (fe->ops.get_frontend) { - r = fe->ops.get_frontend(fe); - if (unlikely(r < 0)) - return r; - if (p_out) - dtv_property_legacy_params_sync(fe, p_out); - return 0; - } - - /* As everything is in cache, get_frontend fops are always supported */ - return 0; -} - -static int dvb_frontend_ioctl_legacy(struct file *file, - unsigned int cmd, void *parg); -static int dvb_frontend_ioctl_properties(struct file *file, - unsigned int cmd, void *parg); - -static int dtv_property_process_get(struct dvb_frontend *fe, - const struct dtv_frontend_properties *c, - struct dtv_property *tvp, - struct file *file) -{ - int r, ncaps; - - switch(tvp->cmd) { - case DTV_ENUM_DELSYS: - ncaps = 0; - while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { - tvp->u.buffer.data[ncaps] = fe->ops.delsys[ncaps]; - ncaps++; - } - tvp->u.buffer.len = ncaps; - break; - case DTV_FREQUENCY: - tvp->u.data = c->frequency; - break; - case DTV_MODULATION: - tvp->u.data = c->modulation; - break; - case DTV_BANDWIDTH_HZ: - tvp->u.data = c->bandwidth_hz; - break; - case DTV_INVERSION: - tvp->u.data = c->inversion; - break; - case DTV_SYMBOL_RATE: - tvp->u.data = c->symbol_rate; - break; - case DTV_INNER_FEC: - tvp->u.data = c->fec_inner; - break; - case DTV_PILOT: - tvp->u.data = c->pilot; - break; - case DTV_ROLLOFF: - tvp->u.data = c->rolloff; - break; - case DTV_DELIVERY_SYSTEM: - tvp->u.data = c->delivery_system; - break; - case DTV_VOLTAGE: - tvp->u.data = c->voltage; - break; - case DTV_TONE: - tvp->u.data = c->sectone; - break; - case DTV_API_VERSION: - tvp->u.data = (DVB_API_VERSION << 8) | DVB_API_VERSION_MINOR; - break; - case DTV_CODE_RATE_HP: - tvp->u.data = c->code_rate_HP; - break; - case DTV_CODE_RATE_LP: - tvp->u.data = c->code_rate_LP; - break; - case DTV_GUARD_INTERVAL: - tvp->u.data = c->guard_interval; - break; - case DTV_TRANSMISSION_MODE: - tvp->u.data = c->transmission_mode; - break; - case DTV_HIERARCHY: - tvp->u.data = c->hierarchy; - break; - - /* ISDB-T Support here */ - case DTV_ISDBT_PARTIAL_RECEPTION: - tvp->u.data = c->isdbt_partial_reception; - break; - case DTV_ISDBT_SOUND_BROADCASTING: - tvp->u.data = c->isdbt_sb_mode; - break; - case DTV_ISDBT_SB_SUBCHANNEL_ID: - tvp->u.data = c->isdbt_sb_subchannel; - break; - case DTV_ISDBT_SB_SEGMENT_IDX: - tvp->u.data = c->isdbt_sb_segment_idx; - break; - case DTV_ISDBT_SB_SEGMENT_COUNT: - tvp->u.data = c->isdbt_sb_segment_count; - break; - case DTV_ISDBT_LAYER_ENABLED: - tvp->u.data = c->isdbt_layer_enabled; - break; - case DTV_ISDBT_LAYERA_FEC: - tvp->u.data = c->layer[0].fec; - break; - case DTV_ISDBT_LAYERA_MODULATION: - tvp->u.data = c->layer[0].modulation; - break; - case DTV_ISDBT_LAYERA_SEGMENT_COUNT: - tvp->u.data = c->layer[0].segment_count; - break; - case DTV_ISDBT_LAYERA_TIME_INTERLEAVING: - tvp->u.data = c->layer[0].interleaving; - break; - case DTV_ISDBT_LAYERB_FEC: - tvp->u.data = c->layer[1].fec; - break; - case DTV_ISDBT_LAYERB_MODULATION: - tvp->u.data = c->layer[1].modulation; - break; - case DTV_ISDBT_LAYERB_SEGMENT_COUNT: - tvp->u.data = c->layer[1].segment_count; - break; - case DTV_ISDBT_LAYERB_TIME_INTERLEAVING: - tvp->u.data = c->layer[1].interleaving; - break; - case DTV_ISDBT_LAYERC_FEC: - tvp->u.data = c->layer[2].fec; - break; - case DTV_ISDBT_LAYERC_MODULATION: - tvp->u.data = c->layer[2].modulation; - break; - case DTV_ISDBT_LAYERC_SEGMENT_COUNT: - tvp->u.data = c->layer[2].segment_count; - break; - case DTV_ISDBT_LAYERC_TIME_INTERLEAVING: - tvp->u.data = c->layer[2].interleaving; - break; - case DTV_ISDBS_TS_ID: - tvp->u.data = c->isdbs_ts_id; - break; - case DTV_DVBT2_PLP_ID: - tvp->u.data = c->dvbt2_plp_id; - break; - - /* ATSC-MH */ - case DTV_ATSCMH_FIC_VER: - tvp->u.data = fe->dtv_property_cache.atscmh_fic_ver; - break; - case DTV_ATSCMH_PARADE_ID: - tvp->u.data = fe->dtv_property_cache.atscmh_parade_id; - break; - case DTV_ATSCMH_NOG: - tvp->u.data = fe->dtv_property_cache.atscmh_nog; - break; - case DTV_ATSCMH_TNOG: - tvp->u.data = fe->dtv_property_cache.atscmh_tnog; - break; - case DTV_ATSCMH_SGN: - tvp->u.data = fe->dtv_property_cache.atscmh_sgn; - break; - case DTV_ATSCMH_PRC: - tvp->u.data = fe->dtv_property_cache.atscmh_prc; - break; - case DTV_ATSCMH_RS_FRAME_MODE: - tvp->u.data = fe->dtv_property_cache.atscmh_rs_frame_mode; - break; - case DTV_ATSCMH_RS_FRAME_ENSEMBLE: - tvp->u.data = fe->dtv_property_cache.atscmh_rs_frame_ensemble; - break; - case DTV_ATSCMH_RS_CODE_MODE_PRI: - tvp->u.data = fe->dtv_property_cache.atscmh_rs_code_mode_pri; - break; - case DTV_ATSCMH_RS_CODE_MODE_SEC: - tvp->u.data = fe->dtv_property_cache.atscmh_rs_code_mode_sec; - break; - case DTV_ATSCMH_SCCC_BLOCK_MODE: - tvp->u.data = fe->dtv_property_cache.atscmh_sccc_block_mode; - break; - case DTV_ATSCMH_SCCC_CODE_MODE_A: - tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_a; - break; - case DTV_ATSCMH_SCCC_CODE_MODE_B: - tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_b; - break; - case DTV_ATSCMH_SCCC_CODE_MODE_C: - tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_c; - break; - case DTV_ATSCMH_SCCC_CODE_MODE_D: - tvp->u.data = fe->dtv_property_cache.atscmh_sccc_code_mode_d; - break; - - default: - return -EINVAL; - } - - /* Allow the frontend to override outgoing properties */ - if (fe->ops.get_property) { - r = fe->ops.get_property(fe, tvp); - if (r < 0) - return r; - } - - dtv_property_dump(tvp); - - return 0; -} - -static int dtv_set_frontend(struct dvb_frontend *fe); - -static bool is_dvbv3_delsys(u32 delsys) -{ - bool status; - - status = (delsys == SYS_DVBT) || (delsys == SYS_DVBC_ANNEX_A) || - (delsys == SYS_DVBS) || (delsys == SYS_ATSC); - - return status; -} - -static int set_delivery_system(struct dvb_frontend *fe, u32 desired_system) -{ - int ncaps, i; - u32 delsys = SYS_UNDEFINED; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - enum dvbv3_emulation_type type; - - /* - * It was reported that some old DVBv5 applications were - * filling delivery_system with SYS_UNDEFINED. If this happens, - * assume that the application wants to use the first supported - * delivery system. - */ - if (c->delivery_system == SYS_UNDEFINED) - c->delivery_system = fe->ops.delsys[0]; - - if (desired_system == SYS_UNDEFINED) { - /* - * A DVBv3 call doesn't know what's the desired system. - * Also, DVBv3 applications don't know that ops.info->type - * could be changed, and they simply dies when it doesn't - * match. - * So, don't change the current delivery system, as it - * may be trying to do the wrong thing, like setting an - * ISDB-T frontend as DVB-T. Instead, find the closest - * DVBv3 system that matches the delivery system. - */ - if (is_dvbv3_delsys(c->delivery_system)) { - dprintk("%s() Using delivery system to %d\n", - __func__, c->delivery_system); - return 0; - } - type = dvbv3_type(c->delivery_system); - switch (type) { - case DVBV3_QPSK: - desired_system = SYS_DVBS; - break; - case DVBV3_QAM: - desired_system = SYS_DVBC_ANNEX_A; - break; - case DVBV3_ATSC: - desired_system = SYS_ATSC; - break; - case DVBV3_OFDM: - desired_system = SYS_DVBT; - break; - default: - dprintk("%s(): This frontend doesn't support DVBv3 calls\n", - __func__); - return -EINVAL; - } - /* - * Get a delivery system that is compatible with DVBv3 - * NOTE: in order for this to work with softwares like Kaffeine that - * uses a DVBv5 call for DVB-S2 and a DVBv3 call to go back to - * DVB-S, drivers that support both should put the SYS_DVBS entry - * before the SYS_DVBS2, otherwise it won't switch back to DVB-S. - * The real fix is that userspace applications should not use DVBv3 - * and not trust on calling FE_SET_FRONTEND to switch the delivery - * system. - */ - ncaps = 0; - while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { - if (fe->ops.delsys[ncaps] == desired_system) { - delsys = desired_system; - break; - } - ncaps++; - } - if (delsys == SYS_UNDEFINED) { - dprintk("%s() Couldn't find a delivery system that matches %d\n", - __func__, desired_system); - } - } else { - /* - * This is a DVBv5 call. So, it likely knows the supported - * delivery systems. - */ - - /* Check if the desired delivery system is supported */ - ncaps = 0; - while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { - if (fe->ops.delsys[ncaps] == desired_system) { - c->delivery_system = desired_system; - dprintk("%s() Changing delivery system to %d\n", - __func__, desired_system); - return 0; - } - ncaps++; - } - type = dvbv3_type(desired_system); - - /* - * The delivery system is not supported. See if it can be - * emulated. - * The emulation only works if the desired system is one of the - * DVBv3 delivery systems - */ - if (!is_dvbv3_delsys(desired_system)) { - dprintk("%s() can't use a DVBv3 FE_SET_FRONTEND call on this frontend\n", - __func__); - return -EINVAL; - } - - /* - * Get the last non-DVBv3 delivery system that has the same type - * of the desired system - */ - ncaps = 0; - while (fe->ops.delsys[ncaps] && ncaps < MAX_DELSYS) { - if ((dvbv3_type(fe->ops.delsys[ncaps]) == type) && - !is_dvbv3_delsys(fe->ops.delsys[ncaps])) - delsys = fe->ops.delsys[ncaps]; - ncaps++; - } - /* There's nothing compatible with the desired delivery system */ - if (delsys == SYS_UNDEFINED) { - dprintk("%s() Incompatible DVBv3 FE_SET_FRONTEND call for this frontend\n", - __func__); - return -EINVAL; - } - } - - c->delivery_system = delsys; - - /* - * The DVBv3 or DVBv5 call is requesting a different system. So, - * emulation is needed. - * - * Emulate newer delivery systems like ISDBT, DVBT and DMBTH - * for older DVBv5 applications. The emulation will try to use - * the auto mode for most things, and will assume that the desired - * delivery system is the last one at the ops.delsys[] array - */ - dprintk("%s() Using delivery system %d emulated as if it were a %d\n", - __func__, delsys, desired_system); - - /* - * For now, handles ISDB-T calls. More code may be needed here for the - * other emulated stuff - */ - if (type == DVBV3_OFDM) { - if (c->delivery_system == SYS_ISDBT) { - dprintk("%s() Using defaults for SYS_ISDBT\n", - __func__); - if (!c->bandwidth_hz) - c->bandwidth_hz = 6000000; - - c->isdbt_partial_reception = 0; - c->isdbt_sb_mode = 0; - c->isdbt_sb_subchannel = 0; - c->isdbt_sb_segment_idx = 0; - c->isdbt_sb_segment_count = 0; - c->isdbt_layer_enabled = 0; - for (i = 0; i < 3; i++) { - c->layer[i].fec = FEC_AUTO; - c->layer[i].modulation = QAM_AUTO; - c->layer[i].interleaving = 0; - c->layer[i].segment_count = 0; - } - } - } - dprintk("change delivery system on cache to %d\n", c->delivery_system); - - return 0; -} - -static int dtv_property_process_set(struct dvb_frontend *fe, - struct dtv_property *tvp, - struct file *file) -{ - int r = 0; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - - /* Allow the frontend to validate incoming properties */ - if (fe->ops.set_property) { - r = fe->ops.set_property(fe, tvp); - if (r < 0) - return r; - } - - switch(tvp->cmd) { - case DTV_CLEAR: - /* - * Reset a cache of data specific to the frontend here. This does - * not effect hardware. - */ - dvb_frontend_clear_cache(fe); - break; - case DTV_TUNE: - /* interpret the cache of data, build either a traditional frontend - * tunerequest so we can pass validation in the FE_SET_FRONTEND - * ioctl. - */ - c->state = tvp->cmd; - dprintk("%s() Finalised property cache\n", __func__); - - r = dtv_set_frontend(fe); - break; - case DTV_FREQUENCY: - c->frequency = tvp->u.data; - break; - case DTV_MODULATION: - c->modulation = tvp->u.data; - break; - case DTV_BANDWIDTH_HZ: - c->bandwidth_hz = tvp->u.data; - break; - case DTV_INVERSION: - c->inversion = tvp->u.data; - break; - case DTV_SYMBOL_RATE: - c->symbol_rate = tvp->u.data; - break; - case DTV_INNER_FEC: - c->fec_inner = tvp->u.data; - break; - case DTV_PILOT: - c->pilot = tvp->u.data; - break; - case DTV_ROLLOFF: - c->rolloff = tvp->u.data; - break; - case DTV_DELIVERY_SYSTEM: - r = set_delivery_system(fe, tvp->u.data); - break; - case DTV_VOLTAGE: - c->voltage = tvp->u.data; - r = dvb_frontend_ioctl_legacy(file, FE_SET_VOLTAGE, - (void *)c->voltage); - break; - case DTV_TONE: - c->sectone = tvp->u.data; - r = dvb_frontend_ioctl_legacy(file, FE_SET_TONE, - (void *)c->sectone); - break; - case DTV_CODE_RATE_HP: - c->code_rate_HP = tvp->u.data; - break; - case DTV_CODE_RATE_LP: - c->code_rate_LP = tvp->u.data; - break; - case DTV_GUARD_INTERVAL: - c->guard_interval = tvp->u.data; - break; - case DTV_TRANSMISSION_MODE: - c->transmission_mode = tvp->u.data; - break; - case DTV_HIERARCHY: - c->hierarchy = tvp->u.data; - break; - - /* ISDB-T Support here */ - case DTV_ISDBT_PARTIAL_RECEPTION: - c->isdbt_partial_reception = tvp->u.data; - break; - case DTV_ISDBT_SOUND_BROADCASTING: - c->isdbt_sb_mode = tvp->u.data; - break; - case DTV_ISDBT_SB_SUBCHANNEL_ID: - c->isdbt_sb_subchannel = tvp->u.data; - break; - case DTV_ISDBT_SB_SEGMENT_IDX: - c->isdbt_sb_segment_idx = tvp->u.data; - break; - case DTV_ISDBT_SB_SEGMENT_COUNT: - c->isdbt_sb_segment_count = tvp->u.data; - break; - case DTV_ISDBT_LAYER_ENABLED: - c->isdbt_layer_enabled = tvp->u.data; - break; - case DTV_ISDBT_LAYERA_FEC: - c->layer[0].fec = tvp->u.data; - break; - case DTV_ISDBT_LAYERA_MODULATION: - c->layer[0].modulation = tvp->u.data; - break; - case DTV_ISDBT_LAYERA_SEGMENT_COUNT: - c->layer[0].segment_count = tvp->u.data; - break; - case DTV_ISDBT_LAYERA_TIME_INTERLEAVING: - c->layer[0].interleaving = tvp->u.data; - break; - case DTV_ISDBT_LAYERB_FEC: - c->layer[1].fec = tvp->u.data; - break; - case DTV_ISDBT_LAYERB_MODULATION: - c->layer[1].modulation = tvp->u.data; - break; - case DTV_ISDBT_LAYERB_SEGMENT_COUNT: - c->layer[1].segment_count = tvp->u.data; - break; - case DTV_ISDBT_LAYERB_TIME_INTERLEAVING: - c->layer[1].interleaving = tvp->u.data; - break; - case DTV_ISDBT_LAYERC_FEC: - c->layer[2].fec = tvp->u.data; - break; - case DTV_ISDBT_LAYERC_MODULATION: - c->layer[2].modulation = tvp->u.data; - break; - case DTV_ISDBT_LAYERC_SEGMENT_COUNT: - c->layer[2].segment_count = tvp->u.data; - break; - case DTV_ISDBT_LAYERC_TIME_INTERLEAVING: - c->layer[2].interleaving = tvp->u.data; - break; - case DTV_ISDBS_TS_ID: - c->isdbs_ts_id = tvp->u.data; - break; - case DTV_DVBT2_PLP_ID: - c->dvbt2_plp_id = tvp->u.data; - break; - - /* ATSC-MH */ - case DTV_ATSCMH_PARADE_ID: - fe->dtv_property_cache.atscmh_parade_id = tvp->u.data; - break; - case DTV_ATSCMH_RS_FRAME_ENSEMBLE: - fe->dtv_property_cache.atscmh_rs_frame_ensemble = tvp->u.data; - break; - - default: - return -EINVAL; - } - - return r; -} - -static int dvb_frontend_ioctl(struct file *file, - unsigned int cmd, void *parg) -{ - struct dvb_device *dvbdev = file->private_data; - struct dvb_frontend *fe = dvbdev->priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - int err = -EOPNOTSUPP; - - dprintk("%s (%d)\n", __func__, _IOC_NR(cmd)); - - if (fepriv->exit != DVB_FE_NO_EXIT) - return -ENODEV; - - if ((file->f_flags & O_ACCMODE) == O_RDONLY && - (_IOC_DIR(cmd) != _IOC_READ || cmd == FE_GET_EVENT || - cmd == FE_DISEQC_RECV_SLAVE_REPLY)) - return -EPERM; - - if (down_interruptible (&fepriv->sem)) - return -ERESTARTSYS; - - if ((cmd == FE_SET_PROPERTY) || (cmd == FE_GET_PROPERTY)) - err = dvb_frontend_ioctl_properties(file, cmd, parg); - else { - c->state = DTV_UNDEFINED; - err = dvb_frontend_ioctl_legacy(file, cmd, parg); - } - - up(&fepriv->sem); - return err; -} - -static int dvb_frontend_ioctl_properties(struct file *file, - unsigned int cmd, void *parg) -{ - struct dvb_device *dvbdev = file->private_data; - struct dvb_frontend *fe = dvbdev->priv; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int err = 0; - - struct dtv_properties *tvps = NULL; - struct dtv_property *tvp = NULL; - int i; - - dprintk("%s\n", __func__); - - if(cmd == FE_SET_PROPERTY) { - tvps = (struct dtv_properties __user *)parg; - - dprintk("%s() properties.num = %d\n", __func__, tvps->num); - dprintk("%s() properties.props = %p\n", __func__, tvps->props); - - /* Put an arbitrary limit on the number of messages that can - * be sent at once */ - if ((tvps->num == 0) || (tvps->num > DTV_IOCTL_MAX_MSGS)) - return -EINVAL; - - tvp = kmalloc(tvps->num * sizeof(struct dtv_property), GFP_KERNEL); - if (!tvp) { - err = -ENOMEM; - goto out; - } - - if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { - err = -EFAULT; - goto out; - } - - for (i = 0; i < tvps->num; i++) { - err = dtv_property_process_set(fe, tvp + i, file); - if (err < 0) - goto out; - (tvp + i)->result = err; - } - - if (c->state == DTV_TUNE) - dprintk("%s() Property cache is full, tuning\n", __func__); - - } else - if(cmd == FE_GET_PROPERTY) { - tvps = (struct dtv_properties __user *)parg; - - dprintk("%s() properties.num = %d\n", __func__, tvps->num); - dprintk("%s() properties.props = %p\n", __func__, tvps->props); - - /* Put an arbitrary limit on the number of messages that can - * be sent at once */ - if ((tvps->num == 0) || (tvps->num > DTV_IOCTL_MAX_MSGS)) - return -EINVAL; - - tvp = kmalloc(tvps->num * sizeof(struct dtv_property), GFP_KERNEL); - if (!tvp) { - err = -ENOMEM; - goto out; - } - - if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { - err = -EFAULT; - goto out; - } - - /* - * Fills the cache out struct with the cache contents, plus - * the data retrieved from get_frontend, if the frontend - * is not idle. Otherwise, returns the cached content - */ - if (fepriv->state != FESTATE_IDLE) { - err = dtv_get_frontend(fe, NULL); - if (err < 0) - goto out; - } - for (i = 0; i < tvps->num; i++) { - err = dtv_property_process_get(fe, c, tvp + i, file); - if (err < 0) - goto out; - (tvp + i)->result = err; - } - - if (copy_to_user(tvps->props, tvp, tvps->num * sizeof(struct dtv_property))) { - err = -EFAULT; - goto out; - } - - } else - err = -EOPNOTSUPP; - -out: - kfree(tvp); - return err; -} - -static int dtv_set_frontend(struct dvb_frontend *fe) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct dvb_frontend_tune_settings fetunesettings; - u32 rolloff = 0; - - if (dvb_frontend_check_parameters(fe) < 0) - return -EINVAL; - - /* - * Initialize output parameters to match the values given by - * the user. FE_SET_FRONTEND triggers an initial frontend event - * with status = 0, which copies output parameters to userspace. - */ - dtv_property_legacy_params_sync(fe, &fepriv->parameters_out); - - /* - * Be sure that the bandwidth will be filled for all - * non-satellite systems, as tuners need to know what - * low pass/Nyquist half filter should be applied, in - * order to avoid inter-channel noise. - * - * ISDB-T and DVB-T/T2 already sets bandwidth. - * ATSC and DVB-C don't set, so, the core should fill it. - * - * On DVB-C Annex A and C, the bandwidth is a function of - * the roll-off and symbol rate. Annex B defines different - * roll-off factors depending on the modulation. Fortunately, - * Annex B is only used with 6MHz, so there's no need to - * calculate it. - * - * While not officially supported, a side effect of handling it at - * the cache level is that a program could retrieve the bandwidth - * via DTV_BANDWIDTH_HZ, which may be useful for test programs. - */ - switch (c->delivery_system) { - case SYS_ATSC: - case SYS_DVBC_ANNEX_B: - c->bandwidth_hz = 6000000; - break; - case SYS_DVBC_ANNEX_A: - rolloff = 115; - break; - case SYS_DVBC_ANNEX_C: - rolloff = 113; - break; - default: - break; - } - if (rolloff) - c->bandwidth_hz = (c->symbol_rate * rolloff) / 100; - - /* force auto frequency inversion if requested */ - if (dvb_force_auto_inversion) - c->inversion = INVERSION_AUTO; - - /* - * without hierarchical coding code_rate_LP is irrelevant, - * so we tolerate the otherwise invalid FEC_NONE setting - */ - if (c->hierarchy == HIERARCHY_NONE && c->code_rate_LP == FEC_NONE) - c->code_rate_LP = FEC_AUTO; - - /* get frontend-specific tuning settings */ - memset(&fetunesettings, 0, sizeof(struct dvb_frontend_tune_settings)); - if (fe->ops.get_tune_settings && (fe->ops.get_tune_settings(fe, &fetunesettings) == 0)) { - fepriv->min_delay = (fetunesettings.min_delay_ms * HZ) / 1000; - fepriv->max_drift = fetunesettings.max_drift; - fepriv->step_size = fetunesettings.step_size; - } else { - /* default values */ - switch (c->delivery_system) { - case SYS_DVBS: - case SYS_DVBS2: - case SYS_ISDBS: - case SYS_TURBO: - case SYS_DVBC_ANNEX_A: - case SYS_DVBC_ANNEX_C: - fepriv->min_delay = HZ / 20; - fepriv->step_size = c->symbol_rate / 16000; - fepriv->max_drift = c->symbol_rate / 2000; - break; - case SYS_DVBT: - case SYS_DVBT2: - case SYS_ISDBT: - case SYS_DMBTH: - fepriv->min_delay = HZ / 20; - fepriv->step_size = fe->ops.info.frequency_stepsize * 2; - fepriv->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; - break; - default: - /* - * FIXME: This sounds wrong! if freqency_stepsize is - * defined by the frontend, why not use it??? - */ - fepriv->min_delay = HZ / 20; - fepriv->step_size = 0; /* no zigzag */ - fepriv->max_drift = 0; - break; - } - } - if (dvb_override_tune_delay > 0) - fepriv->min_delay = (dvb_override_tune_delay * HZ) / 1000; - - fepriv->state = FESTATE_RETUNE; - - /* Request the search algorithm to search */ - fepriv->algo_status |= DVBFE_ALGO_SEARCH_AGAIN; - - dvb_frontend_clear_events(fe); - dvb_frontend_add_event(fe, 0); - dvb_frontend_wakeup(fe); - fepriv->status = 0; - - return 0; -} - - -static int dvb_frontend_ioctl_legacy(struct file *file, - unsigned int cmd, void *parg) -{ - struct dvb_device *dvbdev = file->private_data; - struct dvb_frontend *fe = dvbdev->priv; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int cb_err, err = -EOPNOTSUPP; - - if (fe->dvb->fe_ioctl_override) { - cb_err = fe->dvb->fe_ioctl_override(fe, cmd, parg, - DVB_FE_IOCTL_PRE); - if (cb_err < 0) - return cb_err; - if (cb_err > 0) - return 0; - /* fe_ioctl_override returning 0 allows - * dvb-core to continue handling the ioctl */ - } - - switch (cmd) { - case FE_GET_INFO: { - struct dvb_frontend_info* info = parg; - - memcpy(info, &fe->ops.info, sizeof(struct dvb_frontend_info)); - dvb_frontend_get_frequency_limits(fe, &info->frequency_min, &info->frequency_max); - - /* - * Associate the 4 delivery systems supported by DVBv3 - * API with their DVBv5 counterpart. For the other standards, - * use the closest type, assuming that it would hopefully - * work with a DVBv3 application. - * It should be noticed that, on multi-frontend devices with - * different types (terrestrial and cable, for example), - * a pure DVBv3 application won't be able to use all delivery - * systems. Yet, changing the DVBv5 cache to the other delivery - * system should be enough for making it work. - */ - switch (dvbv3_type(c->delivery_system)) { - case DVBV3_QPSK: - info->type = FE_QPSK; - break; - case DVBV3_ATSC: - info->type = FE_ATSC; - break; - case DVBV3_QAM: - info->type = FE_QAM; - break; - case DVBV3_OFDM: - info->type = FE_OFDM; - break; - default: - printk(KERN_ERR - "%s: doesn't know how to handle a DVBv3 call to delivery system %i\n", - __func__, c->delivery_system); - fe->ops.info.type = FE_OFDM; - } - dprintk("current delivery system on cache: %d, V3 type: %d\n", - c->delivery_system, fe->ops.info.type); - - /* Force the CAN_INVERSION_AUTO bit on. If the frontend doesn't - * do it, it is done for it. */ - info->caps |= FE_CAN_INVERSION_AUTO; - err = 0; - break; - } - - case FE_READ_STATUS: { - fe_status_t* status = parg; - - /* if retune was requested but hasn't occurred yet, prevent - * that user get signal state from previous tuning */ - if (fepriv->state == FESTATE_RETUNE || - fepriv->state == FESTATE_ERROR) { - err=0; - *status = 0; - break; - } - - if (fe->ops.read_status) - err = fe->ops.read_status(fe, status); - break; - } - case FE_READ_BER: - if (fe->ops.read_ber) - err = fe->ops.read_ber(fe, (__u32*) parg); - break; - - case FE_READ_SIGNAL_STRENGTH: - if (fe->ops.read_signal_strength) - err = fe->ops.read_signal_strength(fe, (__u16*) parg); - break; - - case FE_READ_SNR: - if (fe->ops.read_snr) - err = fe->ops.read_snr(fe, (__u16*) parg); - break; - - case FE_READ_UNCORRECTED_BLOCKS: - if (fe->ops.read_ucblocks) - err = fe->ops.read_ucblocks(fe, (__u32*) parg); - break; - - - case FE_DISEQC_RESET_OVERLOAD: - if (fe->ops.diseqc_reset_overload) { - err = fe->ops.diseqc_reset_overload(fe); - fepriv->state = FESTATE_DISEQC; - fepriv->status = 0; - } - break; - - case FE_DISEQC_SEND_MASTER_CMD: - if (fe->ops.diseqc_send_master_cmd) { - err = fe->ops.diseqc_send_master_cmd(fe, (struct dvb_diseqc_master_cmd*) parg); - fepriv->state = FESTATE_DISEQC; - fepriv->status = 0; - } - break; - - case FE_DISEQC_SEND_BURST: - if (fe->ops.diseqc_send_burst) { - err = fe->ops.diseqc_send_burst(fe, (fe_sec_mini_cmd_t) parg); - fepriv->state = FESTATE_DISEQC; - fepriv->status = 0; - } - break; - - case FE_SET_TONE: - if (fe->ops.set_tone) { - err = fe->ops.set_tone(fe, (fe_sec_tone_mode_t) parg); - fepriv->tone = (fe_sec_tone_mode_t) parg; - fepriv->state = FESTATE_DISEQC; - fepriv->status = 0; - } - break; - - case FE_SET_VOLTAGE: - if (fe->ops.set_voltage) { - err = fe->ops.set_voltage(fe, (fe_sec_voltage_t) parg); - fepriv->voltage = (fe_sec_voltage_t) parg; - fepriv->state = FESTATE_DISEQC; - fepriv->status = 0; - } - break; - - case FE_DISHNETWORK_SEND_LEGACY_CMD: - if (fe->ops.dishnetwork_send_legacy_command) { - err = fe->ops.dishnetwork_send_legacy_command(fe, (unsigned long) parg); - fepriv->state = FESTATE_DISEQC; - fepriv->status = 0; - } else if (fe->ops.set_voltage) { - /* - * NOTE: This is a fallback condition. Some frontends - * (stv0299 for instance) take longer than 8msec to - * respond to a set_voltage command. Those switches - * need custom routines to switch properly. For all - * other frontends, the following should work ok. - * Dish network legacy switches (as used by Dish500) - * are controlled by sending 9-bit command words - * spaced 8msec apart. - * the actual command word is switch/port dependent - * so it is up to the userspace application to send - * the right command. - * The command must always start with a '0' after - * initialization, so parg is 8 bits and does not - * include the initialization or start bit - */ - unsigned long swcmd = ((unsigned long) parg) << 1; - struct timeval nexttime; - struct timeval tv[10]; - int i; - u8 last = 1; - if (dvb_frontend_debug) - printk("%s switch command: 0x%04lx\n", __func__, swcmd); - do_gettimeofday(&nexttime); - if (dvb_frontend_debug) - memcpy(&tv[0], &nexttime, sizeof(struct timeval)); - /* before sending a command, initialize by sending - * a 32ms 18V to the switch - */ - fe->ops.set_voltage(fe, SEC_VOLTAGE_18); - dvb_frontend_sleep_until(&nexttime, 32000); - - for (i = 0; i < 9; i++) { - if (dvb_frontend_debug) - do_gettimeofday(&tv[i + 1]); - if ((swcmd & 0x01) != last) { - /* set voltage to (last ? 13V : 18V) */ - fe->ops.set_voltage(fe, (last) ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18); - last = (last) ? 0 : 1; - } - swcmd = swcmd >> 1; - if (i != 8) - dvb_frontend_sleep_until(&nexttime, 8000); - } - if (dvb_frontend_debug) { - printk("%s(%d): switch delay (should be 32k followed by all 8k\n", - __func__, fe->dvb->num); - for (i = 1; i < 10; i++) - printk("%d: %d\n", i, timeval_usec_diff(tv[i-1] , tv[i])); - } - err = 0; - fepriv->state = FESTATE_DISEQC; - fepriv->status = 0; - } - break; - - case FE_DISEQC_RECV_SLAVE_REPLY: - if (fe->ops.diseqc_recv_slave_reply) - err = fe->ops.diseqc_recv_slave_reply(fe, (struct dvb_diseqc_slave_reply*) parg); - break; - - case FE_ENABLE_HIGH_LNB_VOLTAGE: - if (fe->ops.enable_high_lnb_voltage) - err = fe->ops.enable_high_lnb_voltage(fe, (long) parg); - break; - - case FE_SET_FRONTEND: - err = set_delivery_system(fe, SYS_UNDEFINED); - if (err) - break; - - err = dtv_property_cache_sync(fe, c, parg); - if (err) - break; - err = dtv_set_frontend(fe); - break; - case FE_GET_EVENT: - err = dvb_frontend_get_event (fe, parg, file->f_flags); - break; - - case FE_GET_FRONTEND: - err = dtv_get_frontend(fe, parg); - break; - - case FE_SET_FRONTEND_TUNE_MODE: - fepriv->tune_mode_flags = (unsigned long) parg; - err = 0; - break; - }; - - if (fe->dvb->fe_ioctl_override) { - cb_err = fe->dvb->fe_ioctl_override(fe, cmd, parg, - DVB_FE_IOCTL_POST); - if (cb_err < 0) - return cb_err; - } - - return err; -} - - -static unsigned int dvb_frontend_poll(struct file *file, struct poll_table_struct *wait) -{ - struct dvb_device *dvbdev = file->private_data; - struct dvb_frontend *fe = dvbdev->priv; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - - dprintk ("%s\n", __func__); - - poll_wait (file, &fepriv->events.wait_queue, wait); - - if (fepriv->events.eventw != fepriv->events.eventr) - return (POLLIN | POLLRDNORM | POLLPRI); - - return 0; -} - -static int dvb_frontend_open(struct inode *inode, struct file *file) -{ - struct dvb_device *dvbdev = file->private_data; - struct dvb_frontend *fe = dvbdev->priv; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - struct dvb_adapter *adapter = fe->dvb; - int ret; - - dprintk ("%s\n", __func__); - if (fepriv->exit == DVB_FE_DEVICE_REMOVED) - return -ENODEV; - - if (adapter->mfe_shared) { - mutex_lock (&adapter->mfe_lock); - - if (adapter->mfe_dvbdev == NULL) - adapter->mfe_dvbdev = dvbdev; - - else if (adapter->mfe_dvbdev != dvbdev) { - struct dvb_device - *mfedev = adapter->mfe_dvbdev; - struct dvb_frontend - *mfe = mfedev->priv; - struct dvb_frontend_private - *mfepriv = mfe->frontend_priv; - int mferetry = (dvb_mfe_wait_time << 1); - - mutex_unlock (&adapter->mfe_lock); - while (mferetry-- && (mfedev->users != -1 || - mfepriv->thread != NULL)) { - if(msleep_interruptible(500)) { - if(signal_pending(current)) - return -EINTR; - } - } - - mutex_lock (&adapter->mfe_lock); - if(adapter->mfe_dvbdev != dvbdev) { - mfedev = adapter->mfe_dvbdev; - mfe = mfedev->priv; - mfepriv = mfe->frontend_priv; - if (mfedev->users != -1 || - mfepriv->thread != NULL) { - mutex_unlock (&adapter->mfe_lock); - return -EBUSY; - } - adapter->mfe_dvbdev = dvbdev; - } - } - } - - if (dvbdev->users == -1 && fe->ops.ts_bus_ctrl) { - if ((ret = fe->ops.ts_bus_ctrl(fe, 1)) < 0) - goto err0; - - /* If we took control of the bus, we need to force - reinitialization. This is because many ts_bus_ctrl() - functions strobe the RESET pin on the demod, and if the - frontend thread already exists then the dvb_init() routine - won't get called (which is what usually does initial - register configuration). */ - fepriv->reinitialise = 1; - } - - if ((ret = dvb_generic_open (inode, file)) < 0) - goto err1; - - if ((file->f_flags & O_ACCMODE) != O_RDONLY) { - /* normal tune mode when opened R/W */ - fepriv->tune_mode_flags &= ~FE_TUNE_MODE_ONESHOT; - fepriv->tone = -1; - fepriv->voltage = -1; - - ret = dvb_frontend_start (fe); - if (ret) - goto err2; - - /* empty event queue */ - fepriv->events.eventr = fepriv->events.eventw = 0; - } - - if (adapter->mfe_shared) - mutex_unlock (&adapter->mfe_lock); - return ret; - -err2: - dvb_generic_release(inode, file); -err1: - if (dvbdev->users == -1 && fe->ops.ts_bus_ctrl) - fe->ops.ts_bus_ctrl(fe, 0); -err0: - if (adapter->mfe_shared) - mutex_unlock (&adapter->mfe_lock); - return ret; -} - -static int dvb_frontend_release(struct inode *inode, struct file *file) -{ - struct dvb_device *dvbdev = file->private_data; - struct dvb_frontend *fe = dvbdev->priv; - struct dvb_frontend_private *fepriv = fe->frontend_priv; - int ret; - - dprintk ("%s\n", __func__); - - if ((file->f_flags & O_ACCMODE) != O_RDONLY) { - fepriv->release_jiffies = jiffies; - mb(); - } - - ret = dvb_generic_release (inode, file); - - if (dvbdev->users == -1) { - wake_up(&fepriv->wait_queue); - if (fepriv->exit != DVB_FE_NO_EXIT) { - fops_put(file->f_op); - file->f_op = NULL; - wake_up(&dvbdev->wait_queue); - } - if (fe->ops.ts_bus_ctrl) - fe->ops.ts_bus_ctrl(fe, 0); - } - - return ret; -} - -static const struct file_operations dvb_frontend_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = dvb_generic_ioctl, - .poll = dvb_frontend_poll, - .open = dvb_frontend_open, - .release = dvb_frontend_release, - .llseek = noop_llseek, -}; - -int dvb_register_frontend(struct dvb_adapter* dvb, - struct dvb_frontend* fe) -{ - struct dvb_frontend_private *fepriv; - static const struct dvb_device dvbdev_template = { - .users = ~0, - .writers = 1, - .readers = (~0)-1, - .fops = &dvb_frontend_fops, - .kernel_ioctl = dvb_frontend_ioctl - }; - - dprintk ("%s\n", __func__); - - if (mutex_lock_interruptible(&frontend_mutex)) - return -ERESTARTSYS; - - fe->frontend_priv = kzalloc(sizeof(struct dvb_frontend_private), GFP_KERNEL); - if (fe->frontend_priv == NULL) { - mutex_unlock(&frontend_mutex); - return -ENOMEM; - } - fepriv = fe->frontend_priv; - - sema_init(&fepriv->sem, 1); - init_waitqueue_head (&fepriv->wait_queue); - init_waitqueue_head (&fepriv->events.wait_queue); - mutex_init(&fepriv->events.mtx); - fe->dvb = dvb; - fepriv->inversion = INVERSION_OFF; - - printk ("DVB: registering adapter %i frontend %i (%s)...\n", - fe->dvb->num, - fe->id, - fe->ops.info.name); - - dvb_register_device (fe->dvb, &fepriv->dvbdev, &dvbdev_template, - fe, DVB_DEVICE_FRONTEND); - - /* - * Initialize the cache to the proper values according with the - * first supported delivery system (ops->delsys[0]) - */ - - fe->dtv_property_cache.delivery_system = fe->ops.delsys[0]; - dvb_frontend_clear_cache(fe); - - mutex_unlock(&frontend_mutex); - return 0; -} -EXPORT_SYMBOL(dvb_register_frontend); - -int dvb_unregister_frontend(struct dvb_frontend* fe) -{ - struct dvb_frontend_private *fepriv = fe->frontend_priv; - dprintk ("%s\n", __func__); - - mutex_lock(&frontend_mutex); - dvb_frontend_stop (fe); - mutex_unlock(&frontend_mutex); - - if (fepriv->dvbdev->users < -1) - wait_event(fepriv->dvbdev->wait_queue, - fepriv->dvbdev->users==-1); - - mutex_lock(&frontend_mutex); - dvb_unregister_device (fepriv->dvbdev); - - /* fe is invalid now */ - kfree(fepriv); - mutex_unlock(&frontend_mutex); - return 0; -} -EXPORT_SYMBOL(dvb_unregister_frontend); - -#ifdef CONFIG_MEDIA_ATTACH -void dvb_frontend_detach(struct dvb_frontend* fe) -{ - void *ptr; - - if (fe->ops.release_sec) { - fe->ops.release_sec(fe); - symbol_put_addr(fe->ops.release_sec); - } - if (fe->ops.tuner_ops.release) { - fe->ops.tuner_ops.release(fe); - symbol_put_addr(fe->ops.tuner_ops.release); - } - if (fe->ops.analog_ops.release) { - fe->ops.analog_ops.release(fe); - symbol_put_addr(fe->ops.analog_ops.release); - } - ptr = (void*)fe->ops.release; - if (ptr) { - fe->ops.release(fe); - symbol_put_addr(ptr); - } -} -#else -void dvb_frontend_detach(struct dvb_frontend* fe) -{ - if (fe->ops.release_sec) - fe->ops.release_sec(fe); - if (fe->ops.tuner_ops.release) - fe->ops.tuner_ops.release(fe); - if (fe->ops.analog_ops.release) - fe->ops.analog_ops.release(fe); - if (fe->ops.release) - fe->ops.release(fe); -} -#endif -EXPORT_SYMBOL(dvb_frontend_detach); diff --git a/drivers/media/dvb/dvb-core/dvb_frontend.h b/drivers/media/dvb/dvb-core/dvb_frontend.h deleted file mode 100644 index 7c64c09103a9..000000000000 --- a/drivers/media/dvb/dvb-core/dvb_frontend.h +++ /dev/null @@ -1,423 +0,0 @@ -/* - * dvb_frontend.h - * - * Copyright (C) 2001 convergence integrated media GmbH - * Copyright (C) 2004 convergence GmbH - * - * Written by Ralph Metzler - * Overhauled by Holger Waechtler - * Kernel I2C stuff by Michael Hunold - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * 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 Lesser 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. - * - */ - -#ifndef _DVB_FRONTEND_H_ -#define _DVB_FRONTEND_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "dvbdev.h" - -/* - * Maximum number of Delivery systems per frontend. It - * should be smaller or equal to 32 - */ -#define MAX_DELSYS 8 - -struct dvb_frontend_tune_settings { - int min_delay_ms; - int step_size; - int max_drift; -}; - -struct dvb_frontend; - -struct dvb_tuner_info { - char name[128]; - - u32 frequency_min; - u32 frequency_max; - u32 frequency_step; - - u32 bandwidth_min; - u32 bandwidth_max; - u32 bandwidth_step; -}; - -struct analog_parameters { - unsigned int frequency; - unsigned int mode; - unsigned int audmode; - u64 std; -}; - -enum dvbfe_modcod { - DVBFE_MODCOD_DUMMY_PLFRAME = 0, - DVBFE_MODCOD_QPSK_1_4, - DVBFE_MODCOD_QPSK_1_3, - DVBFE_MODCOD_QPSK_2_5, - DVBFE_MODCOD_QPSK_1_2, - DVBFE_MODCOD_QPSK_3_5, - DVBFE_MODCOD_QPSK_2_3, - DVBFE_MODCOD_QPSK_3_4, - DVBFE_MODCOD_QPSK_4_5, - DVBFE_MODCOD_QPSK_5_6, - DVBFE_MODCOD_QPSK_8_9, - DVBFE_MODCOD_QPSK_9_10, - DVBFE_MODCOD_8PSK_3_5, - DVBFE_MODCOD_8PSK_2_3, - DVBFE_MODCOD_8PSK_3_4, - DVBFE_MODCOD_8PSK_5_6, - DVBFE_MODCOD_8PSK_8_9, - DVBFE_MODCOD_8PSK_9_10, - DVBFE_MODCOD_16APSK_2_3, - DVBFE_MODCOD_16APSK_3_4, - DVBFE_MODCOD_16APSK_4_5, - DVBFE_MODCOD_16APSK_5_6, - DVBFE_MODCOD_16APSK_8_9, - DVBFE_MODCOD_16APSK_9_10, - DVBFE_MODCOD_32APSK_3_4, - DVBFE_MODCOD_32APSK_4_5, - DVBFE_MODCOD_32APSK_5_6, - DVBFE_MODCOD_32APSK_8_9, - DVBFE_MODCOD_32APSK_9_10, - DVBFE_MODCOD_RESERVED_1, - DVBFE_MODCOD_BPSK_1_3, - DVBFE_MODCOD_BPSK_1_4, - DVBFE_MODCOD_RESERVED_2 -}; - -enum tuner_param { - DVBFE_TUNER_FREQUENCY = (1 << 0), - DVBFE_TUNER_TUNERSTEP = (1 << 1), - DVBFE_TUNER_IFFREQ = (1 << 2), - DVBFE_TUNER_BANDWIDTH = (1 << 3), - DVBFE_TUNER_REFCLOCK = (1 << 4), - DVBFE_TUNER_IQSENSE = (1 << 5), - DVBFE_TUNER_DUMMY = (1 << 31) -}; - -/* - * ALGO_HW: (Hardware Algorithm) - * ---------------------------------------------------------------- - * Devices that support this algorithm do everything in hardware - * and no software support is needed to handle them. - * Requesting these devices to LOCK is the only thing required, - * device is supposed to do everything in the hardware. - * - * ALGO_SW: (Software Algorithm) - * ---------------------------------------------------------------- - * These are dumb devices, that require software to do everything - * - * ALGO_CUSTOM: (Customizable Agorithm) - * ---------------------------------------------------------------- - * Devices having this algorithm can be customized to have specific - * algorithms in the frontend driver, rather than simply doing a - * software zig-zag. In this case the zigzag maybe hardware assisted - * or it maybe completely done in hardware. In all cases, usage of - * this algorithm, in conjunction with the search and track - * callbacks, utilizes the driver specific algorithm. - * - * ALGO_RECOVERY: (Recovery Algorithm) - * ---------------------------------------------------------------- - * These devices have AUTO recovery capabilities from LOCK failure - */ -enum dvbfe_algo { - DVBFE_ALGO_HW = (1 << 0), - DVBFE_ALGO_SW = (1 << 1), - DVBFE_ALGO_CUSTOM = (1 << 2), - DVBFE_ALGO_RECOVERY = (1 << 31) -}; - -struct tuner_state { - u32 frequency; - u32 tunerstep; - u32 ifreq; - u32 bandwidth; - u32 iqsense; - u32 refclock; -}; - -/* - * search callback possible return status - * - * DVBFE_ALGO_SEARCH_SUCCESS - * The frontend search algorithm completed and returned successfully - * - * DVBFE_ALGO_SEARCH_ASLEEP - * The frontend search algorithm is sleeping - * - * DVBFE_ALGO_SEARCH_FAILED - * The frontend search for a signal failed - * - * DVBFE_ALGO_SEARCH_INVALID - * The frontend search algorith was probably supplied with invalid - * parameters and the search is an invalid one - * - * DVBFE_ALGO_SEARCH_ERROR - * The frontend search algorithm failed due to some error - * - * DVBFE_ALGO_SEARCH_AGAIN - * The frontend search algorithm was requested to search again - */ -enum dvbfe_search { - DVBFE_ALGO_SEARCH_SUCCESS = (1 << 0), - DVBFE_ALGO_SEARCH_ASLEEP = (1 << 1), - DVBFE_ALGO_SEARCH_FAILED = (1 << 2), - DVBFE_ALGO_SEARCH_INVALID = (1 << 3), - DVBFE_ALGO_SEARCH_AGAIN = (1 << 4), - DVBFE_ALGO_SEARCH_ERROR = (1 << 31), -}; - - -struct dvb_tuner_ops { - - struct dvb_tuner_info info; - - int (*release)(struct dvb_frontend *fe); - int (*init)(struct dvb_frontend *fe); - int (*sleep)(struct dvb_frontend *fe); - - /** This is for simple PLLs - set all parameters in one go. */ - int (*set_params)(struct dvb_frontend *fe); - int (*set_analog_params)(struct dvb_frontend *fe, struct analog_parameters *p); - - /** This is support for demods like the mt352 - fills out the supplied buffer with what to write. */ - int (*calc_regs)(struct dvb_frontend *fe, u8 *buf, int buf_len); - - /** This is to allow setting tuner-specific configs */ - int (*set_config)(struct dvb_frontend *fe, void *priv_cfg); - - int (*get_frequency)(struct dvb_frontend *fe, u32 *frequency); - int (*get_bandwidth)(struct dvb_frontend *fe, u32 *bandwidth); - int (*get_if_frequency)(struct dvb_frontend *fe, u32 *frequency); - -#define TUNER_STATUS_LOCKED 1 -#define TUNER_STATUS_STEREO 2 - int (*get_status)(struct dvb_frontend *fe, u32 *status); - int (*get_rf_strength)(struct dvb_frontend *fe, u16 *strength); - int (*get_afc)(struct dvb_frontend *fe, s32 *afc); - - /** These are provided separately from set_params in order to facilitate silicon - * tuners which require sophisticated tuning loops, controlling each parameter separately. */ - int (*set_frequency)(struct dvb_frontend *fe, u32 frequency); - int (*set_bandwidth)(struct dvb_frontend *fe, u32 bandwidth); - - /* - * These are provided separately from set_params in order to facilitate silicon - * tuners which require sophisticated tuning loops, controlling each parameter separately. - */ - int (*set_state)(struct dvb_frontend *fe, enum tuner_param param, struct tuner_state *state); - int (*get_state)(struct dvb_frontend *fe, enum tuner_param param, struct tuner_state *state); -}; - -struct analog_demod_info { - char *name; -}; - -struct analog_demod_ops { - - struct analog_demod_info info; - - void (*set_params)(struct dvb_frontend *fe, - struct analog_parameters *params); - int (*has_signal)(struct dvb_frontend *fe); - int (*get_afc)(struct dvb_frontend *fe); - void (*tuner_status)(struct dvb_frontend *fe); - void (*standby)(struct dvb_frontend *fe); - void (*release)(struct dvb_frontend *fe); - int (*i2c_gate_ctrl)(struct dvb_frontend *fe, int enable); - - /** This is to allow setting tuner-specific configuration */ - int (*set_config)(struct dvb_frontend *fe, void *priv_cfg); -}; - -struct dtv_frontend_properties; - -struct dvb_frontend_ops { - - struct dvb_frontend_info info; - - u8 delsys[MAX_DELSYS]; - - void (*release)(struct dvb_frontend* fe); - void (*release_sec)(struct dvb_frontend* fe); - - int (*init)(struct dvb_frontend* fe); - int (*sleep)(struct dvb_frontend* fe); - - int (*write)(struct dvb_frontend* fe, const u8 buf[], int len); - - /* if this is set, it overrides the default swzigzag */ - int (*tune)(struct dvb_frontend* fe, - bool re_tune, - unsigned int mode_flags, - unsigned int *delay, - fe_status_t *status); - /* get frontend tuning algorithm from the module */ - enum dvbfe_algo (*get_frontend_algo)(struct dvb_frontend *fe); - - /* these two are only used for the swzigzag code */ - int (*set_frontend)(struct dvb_frontend *fe); - int (*get_tune_settings)(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* settings); - - int (*get_frontend)(struct dvb_frontend *fe); - - int (*read_status)(struct dvb_frontend* fe, fe_status_t* status); - int (*read_ber)(struct dvb_frontend* fe, u32* ber); - int (*read_signal_strength)(struct dvb_frontend* fe, u16* strength); - int (*read_snr)(struct dvb_frontend* fe, u16* snr); - int (*read_ucblocks)(struct dvb_frontend* fe, u32* ucblocks); - - int (*diseqc_reset_overload)(struct dvb_frontend* fe); - int (*diseqc_send_master_cmd)(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd); - int (*diseqc_recv_slave_reply)(struct dvb_frontend* fe, struct dvb_diseqc_slave_reply* reply); - int (*diseqc_send_burst)(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd); - int (*set_tone)(struct dvb_frontend* fe, fe_sec_tone_mode_t tone); - int (*set_voltage)(struct dvb_frontend* fe, fe_sec_voltage_t voltage); - int (*enable_high_lnb_voltage)(struct dvb_frontend* fe, long arg); - int (*dishnetwork_send_legacy_command)(struct dvb_frontend* fe, unsigned long cmd); - int (*i2c_gate_ctrl)(struct dvb_frontend* fe, int enable); - int (*ts_bus_ctrl)(struct dvb_frontend* fe, int acquire); - - /* These callbacks are for devices that implement their own - * tuning algorithms, rather than a simple swzigzag - */ - enum dvbfe_search (*search)(struct dvb_frontend *fe); - - struct dvb_tuner_ops tuner_ops; - struct analog_demod_ops analog_ops; - - int (*set_property)(struct dvb_frontend* fe, struct dtv_property* tvp); - int (*get_property)(struct dvb_frontend* fe, struct dtv_property* tvp); -}; - -#ifdef __DVB_CORE__ -#define MAX_EVENT 8 - -struct dvb_fe_events { - struct dvb_frontend_event events[MAX_EVENT]; - int eventw; - int eventr; - int overflow; - wait_queue_head_t wait_queue; - struct mutex mtx; -}; -#endif - -struct dtv_frontend_properties { - - /* Cache State */ - u32 state; - - u32 frequency; - fe_modulation_t modulation; - - fe_sec_voltage_t voltage; - fe_sec_tone_mode_t sectone; - fe_spectral_inversion_t inversion; - fe_code_rate_t fec_inner; - fe_transmit_mode_t transmission_mode; - u32 bandwidth_hz; /* 0 = AUTO */ - fe_guard_interval_t guard_interval; - fe_hierarchy_t hierarchy; - u32 symbol_rate; - fe_code_rate_t code_rate_HP; - fe_code_rate_t code_rate_LP; - - fe_pilot_t pilot; - fe_rolloff_t rolloff; - - fe_delivery_system_t delivery_system; - - /* ISDB-T specifics */ - u8 isdbt_partial_reception; - u8 isdbt_sb_mode; - u8 isdbt_sb_subchannel; - u32 isdbt_sb_segment_idx; - u32 isdbt_sb_segment_count; - u8 isdbt_layer_enabled; - struct { - u8 segment_count; - fe_code_rate_t fec; - fe_modulation_t modulation; - u8 interleaving; - } layer[3]; - - /* ISDB-T specifics */ - u32 isdbs_ts_id; - - /* DVB-T2 specifics */ - u32 dvbt2_plp_id; - - /* ATSC-MH specifics */ - u8 atscmh_fic_ver; - u8 atscmh_parade_id; - u8 atscmh_nog; - u8 atscmh_tnog; - u8 atscmh_sgn; - u8 atscmh_prc; - - u8 atscmh_rs_frame_mode; - u8 atscmh_rs_frame_ensemble; - u8 atscmh_rs_code_mode_pri; - u8 atscmh_rs_code_mode_sec; - u8 atscmh_sccc_block_mode; - u8 atscmh_sccc_code_mode_a; - u8 atscmh_sccc_code_mode_b; - u8 atscmh_sccc_code_mode_c; - u8 atscmh_sccc_code_mode_d; -}; - -struct dvb_frontend { - struct dvb_frontend_ops ops; - struct dvb_adapter *dvb; - void *demodulator_priv; - void *tuner_priv; - void *frontend_priv; - void *sec_priv; - void *analog_demod_priv; - struct dtv_frontend_properties dtv_property_cache; -#define DVB_FRONTEND_COMPONENT_TUNER 0 -#define DVB_FRONTEND_COMPONENT_DEMOD 1 - int (*callback)(void *adapter_priv, int component, int cmd, int arg); - int id; -}; - -extern int dvb_register_frontend(struct dvb_adapter *dvb, - struct dvb_frontend *fe); - -extern int dvb_unregister_frontend(struct dvb_frontend *fe); - -extern void dvb_frontend_detach(struct dvb_frontend *fe); - -extern void dvb_frontend_reinitialise(struct dvb_frontend *fe); - -extern void dvb_frontend_sleep_until(struct timeval *waketime, u32 add_usec); -extern s32 timeval_usec_diff(struct timeval lasttime, struct timeval curtime); - -#endif diff --git a/drivers/media/dvb/dvb-core/dvbdev.c b/drivers/media/dvb/dvb-core/dvbdev.c deleted file mode 100644 index 39eab73b01ae..000000000000 --- a/drivers/media/dvb/dvb-core/dvbdev.c +++ /dev/null @@ -1,507 +0,0 @@ -/* - * dvbdev.c - * - * Copyright (C) 2000 Ralph Metzler - * & Marcus Metzler - * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * 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 Lesser 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dvbdev.h" - -static DEFINE_MUTEX(dvbdev_mutex); -static int dvbdev_debug; - -module_param(dvbdev_debug, int, 0644); -MODULE_PARM_DESC(dvbdev_debug, "Turn on/off device debugging (default:off)."); - -#define dprintk if (dvbdev_debug) printk - -static LIST_HEAD(dvb_adapter_list); -static DEFINE_MUTEX(dvbdev_register_lock); - -static const char * const dnames[] = { - "video", "audio", "sec", "frontend", "demux", "dvr", "ca", - "net", "osd" -}; - -#ifdef CONFIG_DVB_DYNAMIC_MINORS -#define MAX_DVB_MINORS 256 -#define DVB_MAX_IDS MAX_DVB_MINORS -#else -#define DVB_MAX_IDS 4 -#define nums2minor(num,type,id) ((num << 6) | (id << 4) | type) -#define MAX_DVB_MINORS (DVB_MAX_ADAPTERS*64) -#endif - -static struct class *dvb_class; - -static struct dvb_device *dvb_minors[MAX_DVB_MINORS]; -static DECLARE_RWSEM(minor_rwsem); - -static int dvb_device_open(struct inode *inode, struct file *file) -{ - struct dvb_device *dvbdev; - - mutex_lock(&dvbdev_mutex); - down_read(&minor_rwsem); - dvbdev = dvb_minors[iminor(inode)]; - - if (dvbdev && dvbdev->fops) { - int err = 0; - const struct file_operations *old_fops; - - file->private_data = dvbdev; - old_fops = file->f_op; - file->f_op = fops_get(dvbdev->fops); - if (file->f_op == NULL) { - file->f_op = old_fops; - goto fail; - } - if(file->f_op->open) - err = file->f_op->open(inode,file); - if (err) { - fops_put(file->f_op); - file->f_op = fops_get(old_fops); - } - fops_put(old_fops); - up_read(&minor_rwsem); - mutex_unlock(&dvbdev_mutex); - return err; - } -fail: - up_read(&minor_rwsem); - mutex_unlock(&dvbdev_mutex); - return -ENODEV; -} - - -static const struct file_operations dvb_device_fops = -{ - .owner = THIS_MODULE, - .open = dvb_device_open, - .llseek = noop_llseek, -}; - -static struct cdev dvb_device_cdev; - -int dvb_generic_open(struct inode *inode, struct file *file) -{ - struct dvb_device *dvbdev = file->private_data; - - if (!dvbdev) - return -ENODEV; - - if (!dvbdev->users) - return -EBUSY; - - if ((file->f_flags & O_ACCMODE) == O_RDONLY) { - if (!dvbdev->readers) - return -EBUSY; - dvbdev->readers--; - } else { - if (!dvbdev->writers) - return -EBUSY; - dvbdev->writers--; - } - - dvbdev->users--; - return 0; -} -EXPORT_SYMBOL(dvb_generic_open); - - -int dvb_generic_release(struct inode *inode, struct file *file) -{ - struct dvb_device *dvbdev = file->private_data; - - if (!dvbdev) - return -ENODEV; - - if ((file->f_flags & O_ACCMODE) == O_RDONLY) { - dvbdev->readers++; - } else { - dvbdev->writers++; - } - - dvbdev->users++; - return 0; -} -EXPORT_SYMBOL(dvb_generic_release); - - -long dvb_generic_ioctl(struct file *file, - unsigned int cmd, unsigned long arg) -{ - struct dvb_device *dvbdev = file->private_data; - - if (!dvbdev) - return -ENODEV; - - if (!dvbdev->kernel_ioctl) - return -EINVAL; - - return dvb_usercopy(file, cmd, arg, dvbdev->kernel_ioctl); -} -EXPORT_SYMBOL(dvb_generic_ioctl); - - -static int dvbdev_get_free_id (struct dvb_adapter *adap, int type) -{ - u32 id = 0; - - while (id < DVB_MAX_IDS) { - struct dvb_device *dev; - list_for_each_entry(dev, &adap->device_list, list_head) - if (dev->type == type && dev->id == id) - goto skip; - return id; -skip: - id++; - } - return -ENFILE; -} - - -int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, - const struct dvb_device *template, void *priv, int type) -{ - struct dvb_device *dvbdev; - struct file_operations *dvbdevfops; - struct device *clsdev; - int minor; - int id; - - mutex_lock(&dvbdev_register_lock); - - if ((id = dvbdev_get_free_id (adap, type)) < 0){ - mutex_unlock(&dvbdev_register_lock); - *pdvbdev = NULL; - printk(KERN_ERR "%s: couldn't find free device id\n", __func__); - return -ENFILE; - } - - *pdvbdev = dvbdev = kmalloc(sizeof(struct dvb_device), GFP_KERNEL); - - if (!dvbdev){ - mutex_unlock(&dvbdev_register_lock); - return -ENOMEM; - } - - dvbdevfops = kzalloc(sizeof(struct file_operations), GFP_KERNEL); - - if (!dvbdevfops){ - kfree (dvbdev); - mutex_unlock(&dvbdev_register_lock); - return -ENOMEM; - } - - memcpy(dvbdev, template, sizeof(struct dvb_device)); - dvbdev->type = type; - dvbdev->id = id; - dvbdev->adapter = adap; - dvbdev->priv = priv; - dvbdev->fops = dvbdevfops; - init_waitqueue_head (&dvbdev->wait_queue); - - memcpy(dvbdevfops, template->fops, sizeof(struct file_operations)); - dvbdevfops->owner = adap->module; - - list_add_tail (&dvbdev->list_head, &adap->device_list); - - down_write(&minor_rwsem); -#ifdef CONFIG_DVB_DYNAMIC_MINORS - for (minor = 0; minor < MAX_DVB_MINORS; minor++) - if (dvb_minors[minor] == NULL) - break; - - if (minor == MAX_DVB_MINORS) { - kfree(dvbdevfops); - kfree(dvbdev); - up_write(&minor_rwsem); - mutex_unlock(&dvbdev_register_lock); - return -EINVAL; - } -#else - minor = nums2minor(adap->num, type, id); -#endif - - dvbdev->minor = minor; - dvb_minors[minor] = dvbdev; - up_write(&minor_rwsem); - - mutex_unlock(&dvbdev_register_lock); - - clsdev = device_create(dvb_class, adap->device, - MKDEV(DVB_MAJOR, minor), - dvbdev, "dvb%d.%s%d", adap->num, dnames[type], id); - if (IS_ERR(clsdev)) { - printk(KERN_ERR "%s: failed to create device dvb%d.%s%d (%ld)\n", - __func__, adap->num, dnames[type], id, PTR_ERR(clsdev)); - return PTR_ERR(clsdev); - } - - dprintk(KERN_DEBUG "DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n", - adap->num, dnames[type], id, minor, minor); - - return 0; -} -EXPORT_SYMBOL(dvb_register_device); - - -void dvb_unregister_device(struct dvb_device *dvbdev) -{ - if (!dvbdev) - return; - - down_write(&minor_rwsem); - dvb_minors[dvbdev->minor] = NULL; - up_write(&minor_rwsem); - - device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor)); - - list_del (&dvbdev->list_head); - kfree (dvbdev->fops); - kfree (dvbdev); -} -EXPORT_SYMBOL(dvb_unregister_device); - -static int dvbdev_check_free_adapter_num(int num) -{ - struct list_head *entry; - list_for_each(entry, &dvb_adapter_list) { - struct dvb_adapter *adap; - adap = list_entry(entry, struct dvb_adapter, list_head); - if (adap->num == num) - return 0; - } - return 1; -} - -static int dvbdev_get_free_adapter_num (void) -{ - int num = 0; - - while (num < DVB_MAX_ADAPTERS) { - if (dvbdev_check_free_adapter_num(num)) - return num; - num++; - } - - return -ENFILE; -} - - -int dvb_register_adapter(struct dvb_adapter *adap, const char *name, - struct module *module, struct device *device, - short *adapter_nums) -{ - int i, num; - - mutex_lock(&dvbdev_register_lock); - - for (i = 0; i < DVB_MAX_ADAPTERS; ++i) { - num = adapter_nums[i]; - if (num >= 0 && num < DVB_MAX_ADAPTERS) { - /* use the one the driver asked for */ - if (dvbdev_check_free_adapter_num(num)) - break; - } else { - num = dvbdev_get_free_adapter_num(); - break; - } - num = -1; - } - - if (num < 0) { - mutex_unlock(&dvbdev_register_lock); - return -ENFILE; - } - - memset (adap, 0, sizeof(struct dvb_adapter)); - INIT_LIST_HEAD (&adap->device_list); - - printk(KERN_INFO "DVB: registering new adapter (%s)\n", name); - - adap->num = num; - adap->name = name; - adap->module = module; - adap->device = device; - adap->mfe_shared = 0; - adap->mfe_dvbdev = NULL; - mutex_init (&adap->mfe_lock); - - list_add_tail (&adap->list_head, &dvb_adapter_list); - - mutex_unlock(&dvbdev_register_lock); - - return num; -} -EXPORT_SYMBOL(dvb_register_adapter); - - -int dvb_unregister_adapter(struct dvb_adapter *adap) -{ - mutex_lock(&dvbdev_register_lock); - list_del (&adap->list_head); - mutex_unlock(&dvbdev_register_lock); - return 0; -} -EXPORT_SYMBOL(dvb_unregister_adapter); - -/* if the miracle happens and "generic_usercopy()" is included into - the kernel, then this can vanish. please don't make the mistake and - define this as video_usercopy(). this will introduce a dependecy - to the v4l "videodev.o" module, which is unnecessary for some - cards (ie. the budget dvb-cards don't need the v4l module...) */ -int dvb_usercopy(struct file *file, - unsigned int cmd, unsigned long arg, - int (*func)(struct file *file, - unsigned int cmd, void *arg)) -{ - char sbuf[128]; - void *mbuf = NULL; - void *parg = NULL; - int err = -EINVAL; - - /* Copy arguments into temp kernel buffer */ - switch (_IOC_DIR(cmd)) { - case _IOC_NONE: - /* - * For this command, the pointer is actually an integer - * argument. - */ - parg = (void *) arg; - break; - case _IOC_READ: /* some v4l ioctls are marked wrong ... */ - case _IOC_WRITE: - case (_IOC_WRITE | _IOC_READ): - if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { - parg = sbuf; - } else { - /* too big to allocate from stack */ - mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL); - if (NULL == mbuf) - return -ENOMEM; - parg = mbuf; - } - - err = -EFAULT; - if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) - goto out; - break; - } - - /* call driver */ - mutex_lock(&dvbdev_mutex); - if ((err = func(file, cmd, parg)) == -ENOIOCTLCMD) - err = -EINVAL; - mutex_unlock(&dvbdev_mutex); - - if (err < 0) - goto out; - - /* Copy results into user buffer */ - switch (_IOC_DIR(cmd)) - { - case _IOC_READ: - case (_IOC_WRITE | _IOC_READ): - if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) - err = -EFAULT; - break; - } - -out: - kfree(mbuf); - return err; -} - -static int dvb_uevent(struct device *dev, struct kobj_uevent_env *env) -{ - struct dvb_device *dvbdev = dev_get_drvdata(dev); - - add_uevent_var(env, "DVB_ADAPTER_NUM=%d", dvbdev->adapter->num); - add_uevent_var(env, "DVB_DEVICE_TYPE=%s", dnames[dvbdev->type]); - add_uevent_var(env, "DVB_DEVICE_NUM=%d", dvbdev->id); - return 0; -} - -static char *dvb_devnode(struct device *dev, umode_t *mode) -{ - struct dvb_device *dvbdev = dev_get_drvdata(dev); - - return kasprintf(GFP_KERNEL, "dvb/adapter%d/%s%d", - dvbdev->adapter->num, dnames[dvbdev->type], dvbdev->id); -} - - -static int __init init_dvbdev(void) -{ - int retval; - dev_t dev = MKDEV(DVB_MAJOR, 0); - - if ((retval = register_chrdev_region(dev, MAX_DVB_MINORS, "DVB")) != 0) { - printk(KERN_ERR "dvb-core: unable to get major %d\n", DVB_MAJOR); - return retval; - } - - cdev_init(&dvb_device_cdev, &dvb_device_fops); - if ((retval = cdev_add(&dvb_device_cdev, dev, MAX_DVB_MINORS)) != 0) { - printk(KERN_ERR "dvb-core: unable register character device\n"); - goto error; - } - - dvb_class = class_create(THIS_MODULE, "dvb"); - if (IS_ERR(dvb_class)) { - retval = PTR_ERR(dvb_class); - goto error; - } - dvb_class->dev_uevent = dvb_uevent; - dvb_class->devnode = dvb_devnode; - return 0; - -error: - cdev_del(&dvb_device_cdev); - unregister_chrdev_region(dev, MAX_DVB_MINORS); - return retval; -} - - -static void __exit exit_dvbdev(void) -{ - class_destroy(dvb_class); - cdev_del(&dvb_device_cdev); - unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS); -} - -subsys_initcall(init_dvbdev); -module_exit(exit_dvbdev); - -MODULE_DESCRIPTION("DVB Core Driver"); -MODULE_AUTHOR("Marcus Metzler, Ralph Metzler, Holger Waechtler"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-core/dvbdev.h b/drivers/media/dvb/dvb-core/dvbdev.h deleted file mode 100644 index fcc6ae98745e..000000000000 --- a/drivers/media/dvb/dvb-core/dvbdev.h +++ /dev/null @@ -1,172 +0,0 @@ -/* - * dvbdev.h - * - * Copyright (C) 2000 Ralph Metzler & Marcus Metzler - * for convergence integrated media GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Lesser Public License - * as published by the Free Software Foundation; either version 2.1 - * 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 Lesser 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. - * - */ - -#ifndef _DVBDEV_H_ -#define _DVBDEV_H_ - -#include -#include -#include -#include - -#define DVB_MAJOR 212 - -#if defined(CONFIG_DVB_MAX_ADAPTERS) && CONFIG_DVB_MAX_ADAPTERS > 0 - #define DVB_MAX_ADAPTERS CONFIG_DVB_MAX_ADAPTERS -#else - #define DVB_MAX_ADAPTERS 8 -#endif - -#define DVB_UNSET (-1) - -#define DVB_DEVICE_VIDEO 0 -#define DVB_DEVICE_AUDIO 1 -#define DVB_DEVICE_SEC 2 -#define DVB_DEVICE_FRONTEND 3 -#define DVB_DEVICE_DEMUX 4 -#define DVB_DEVICE_DVR 5 -#define DVB_DEVICE_CA 6 -#define DVB_DEVICE_NET 7 -#define DVB_DEVICE_OSD 8 - -#define DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr) \ - static short adapter_nr[] = \ - {[0 ... (DVB_MAX_ADAPTERS - 1)] = DVB_UNSET }; \ - module_param_array(adapter_nr, short, NULL, 0444); \ - MODULE_PARM_DESC(adapter_nr, "DVB adapter numbers") - -struct dvb_frontend; - -struct dvb_adapter { - int num; - struct list_head list_head; - struct list_head device_list; - const char *name; - u8 proposed_mac [6]; - void* priv; - - struct device *device; - - struct module *module; - - int mfe_shared; /* indicates mutually exclusive frontends */ - struct dvb_device *mfe_dvbdev; /* frontend device in use */ - struct mutex mfe_lock; /* access lock for thread creation */ - - /* Allow the adapter/bridge driver to perform an action before and/or - * after the core handles an ioctl: - * - * DVB_FE_IOCTL_PRE indicates that the ioctl has not yet been handled. - * DVB_FE_IOCTL_POST indicates that the ioctl has been handled. - * - * When DVB_FE_IOCTL_PRE is passed to the callback as the stage arg: - * - * return 0 to allow dvb-core to handle the ioctl. - * return a positive int to prevent dvb-core from handling the ioctl, - * and exit without error. - * return a negative int to prevent dvb-core from handling the ioctl, - * and return that value as an error. - * - * When DVB_FE_IOCTL_POST is passed to the callback as the stage arg: - * - * return 0 to allow the dvb_frontend ioctl handler to exit normally. - * return a negative int to cause the dvb_frontend ioctl handler to - * return that value as an error. - */ -#define DVB_FE_IOCTL_PRE 0 -#define DVB_FE_IOCTL_POST 1 - int (*fe_ioctl_override)(struct dvb_frontend *fe, - unsigned int cmd, void *parg, - unsigned int stage); -}; - - -struct dvb_device { - struct list_head list_head; - const struct file_operations *fops; - struct dvb_adapter *adapter; - int type; - int minor; - u32 id; - - /* in theory, 'users' can vanish now, - but I don't want to change too much now... */ - int readers; - int writers; - int users; - - wait_queue_head_t wait_queue; - /* don't really need those !? -- FIXME: use video_usercopy */ - int (*kernel_ioctl)(struct file *file, unsigned int cmd, void *arg); - - void *priv; -}; - - -extern int dvb_register_adapter(struct dvb_adapter *adap, const char *name, - struct module *module, struct device *device, - short *adapter_nums); -extern int dvb_unregister_adapter (struct dvb_adapter *adap); - -extern int dvb_register_device (struct dvb_adapter *adap, - struct dvb_device **pdvbdev, - const struct dvb_device *template, - void *priv, - int type); - -extern void dvb_unregister_device (struct dvb_device *dvbdev); - -extern int dvb_generic_open (struct inode *inode, struct file *file); -extern int dvb_generic_release (struct inode *inode, struct file *file); -extern long dvb_generic_ioctl (struct file *file, - unsigned int cmd, unsigned long arg); - -/* we don't mess with video_usercopy() any more, -we simply define out own dvb_usercopy(), which will hopefully become -generic_usercopy() someday... */ - -extern int dvb_usercopy(struct file *file, unsigned int cmd, unsigned long arg, - int (*func)(struct file *file, unsigned int cmd, void *arg)); - -/** generic DVB attach function. */ -#ifdef CONFIG_MEDIA_ATTACH -#define dvb_attach(FUNCTION, ARGS...) ({ \ - void *__r = NULL; \ - typeof(&FUNCTION) __a = symbol_request(FUNCTION); \ - if (__a) { \ - __r = (void *) __a(ARGS); \ - if (__r == NULL) \ - symbol_put(FUNCTION); \ - } else { \ - printk(KERN_ERR "DVB: Unable to find symbol "#FUNCTION"()\n"); \ - } \ - __r; \ -}) - -#else -#define dvb_attach(FUNCTION, ARGS...) ({ \ - FUNCTION(ARGS); \ -}) - -#endif - -#endif /* #ifndef _DVBDEV_H_ */ diff --git a/drivers/media/dvb/dvb-usb/Kconfig b/drivers/media/dvb/dvb-usb/Kconfig deleted file mode 100644 index c2161565023a..000000000000 --- a/drivers/media/dvb/dvb-usb/Kconfig +++ /dev/null @@ -1,440 +0,0 @@ -config DVB_USB - tristate "Support for various USB DVB devices" - depends on DVB_CORE && USB && I2C && RC_CORE - help - By enabling this you will be able to choose the various supported - USB1.1 and USB2.0 DVB devices. - - Almost every USB device needs a firmware, please look into - . - - For a complete list of supported USB devices see the LinuxTV DVB Wiki: - - - Say Y if you own a USB DVB device. - -config DVB_USB_DEBUG - bool "Enable extended debug support for all DVB-USB devices" - depends on DVB_USB - help - Say Y if you want to enable debugging. See modinfo dvb-usb (and the - appropriate drivers) for debug levels. - -config DVB_USB_A800 - tristate "AVerMedia AverTV DVB-T USB 2.0 (A800)" - depends on DVB_USB - select DVB_DIB3000MC - select DVB_PLL if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the AVerMedia AverTV DVB-T USB 2.0 (A800) receiver. - -config DVB_USB_DIBUSB_MB - tristate "DiBcom USB DVB-T devices (based on the DiB3000M-B) (see help for device list)" - depends on DVB_USB - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_DIB3000MB - select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE - help - Support for USB 1.1 and 2.0 DVB-T receivers based on reference designs made by - DiBcom () equipped with a DiB3000M-B demodulator. - - For an up-to-date list of devices supported by this driver, have a look - on the Linux-DVB Wiki at www.linuxtv.org. - - Say Y if you own such a device and want to use it. You should build it as - a module. - -config DVB_USB_DIBUSB_MB_FAULTY - bool "Support faulty USB IDs" - depends on DVB_USB_DIBUSB_MB - help - Support for faulty USB IDs due to an invalid EEPROM on some Artec devices. - -config DVB_USB_DIBUSB_MC - tristate "DiBcom USB DVB-T devices (based on the DiB3000M-C/P) (see help for device list)" - depends on DVB_USB - select DVB_DIB3000MC - select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE - help - Support for USB2.0 DVB-T receivers based on reference designs made by - DiBcom () equipped with a DiB3000M-C/P demodulator. - - For an up-to-date list of devices supported by this driver, have a look - on the Linux-DVB Wiki at www.linuxtv.org. - - Say Y if you own such a device and want to use it. You should build it as - a module. - -config DVB_USB_DIB0700 - tristate "DiBcom DiB0700 USB DVB devices (see help for supported devices)" - depends on DVB_USB - select DVB_DIB7000P if !DVB_FE_CUSTOMISE - select DVB_DIB7000M if !DVB_FE_CUSTOMISE - select DVB_DIB8000 if !DVB_FE_CUSTOMISE - select DVB_DIB3000MC if !DVB_FE_CUSTOMISE - select DVB_S5H1411 if !DVB_FE_CUSTOMISE - select DVB_LGDT3305 if !DVB_FE_CUSTOMISE - select DVB_TUNER_DIB0070 if !DVB_FE_CUSTOMISE - select DVB_TUNER_DIB0090 if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MT2266 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_XC2028 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_XC5000 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_XC4000 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MXL5007T if !MEDIA_TUNER_CUSTOMISE - help - Support for USB2.0/1.1 DVB receivers based on the DiB0700 USB bridge. The - USB bridge is also present in devices having the DiB7700 DVB-T-USB - silicon. This chip can be found in devices offered by Hauppauge, - Avermedia and other big and small companies. - - For an up-to-date list of devices supported by this driver, have a look - on the LinuxTV Wiki at www.linuxtv.org. - - Say Y if you own such a device and want to use it. You should build it as - a module. - -config DVB_USB_UMT_010 - tristate "HanfTek UMT-010 DVB-T USB2.0 support" - depends on DVB_USB - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_DIB3000MC - select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE - select DVB_MT352 if !DVB_FE_CUSTOMISE - help - Say Y here to support the HanfTek UMT-010 USB2.0 stick-sized DVB-T receiver. - -config DVB_USB_CXUSB - tristate "Conexant USB2.0 hybrid reference design support" - depends on DVB_USB - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_CX22702 if !DVB_FE_CUSTOMISE - select DVB_LGDT330X if !DVB_FE_CUSTOMISE - select DVB_MT352 if !DVB_FE_CUSTOMISE - select DVB_ZL10353 if !DVB_FE_CUSTOMISE - select DVB_DIB7000P if !DVB_FE_CUSTOMISE - select DVB_TUNER_DIB0070 if !DVB_FE_CUSTOMISE - select DVB_ATBM8830 if !DVB_FE_CUSTOMISE - select DVB_LGS8GXX if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_SIMPLE if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_XC2028 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MXL5005S if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MAX2165 if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the Conexant USB2.0 hybrid reference design. - Currently, only DVB and ATSC modes are supported, analog mode - shall be added in the future. Devices that require this module: - - Medion MD95700 hybrid USB2.0 device. - DViCO FusionHDTV (Bluebird) USB2.0 devices - -config DVB_USB_M920X - tristate "Uli m920x DVB-T USB2.0 support" - depends on DVB_USB - select DVB_MT352 if !DVB_FE_CUSTOMISE - select DVB_TDA1004X if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_QT1010 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_TDA827X if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_SIMPLE if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the MSI Mega Sky 580 USB2.0 DVB-T receiver. - Currently, only devices with a product id of - "DTV USB MINI" (in cold state) are supported. - Firmware required. - -config DVB_USB_GL861 - tristate "Genesys Logic GL861 USB2.0 support" - depends on DVB_USB - select DVB_ZL10353 if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_QT1010 if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the MSI Megasky 580 (55801) DVB-T USB2.0 - receiver with USB ID 0db0:5581. - -config DVB_USB_AU6610 - tristate "Alcor Micro AU6610 USB2.0 support" - depends on DVB_USB - select DVB_ZL10353 if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_QT1010 if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the Sigmatek DVB-110 DVB-T USB2.0 receiver. - -config DVB_USB_DIGITV - tristate "Nebula Electronics uDigiTV DVB-T USB2.0 support" - depends on DVB_USB - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_NXT6000 if !DVB_FE_CUSTOMISE - select DVB_MT352 if !DVB_FE_CUSTOMISE - help - Say Y here to support the Nebula Electronics uDigitV USB2.0 DVB-T receiver. - -config DVB_USB_VP7045 - tristate "TwinhanDTV Alpha/MagicBoxII, DNTV tinyUSB2, Beetle USB2.0 support" - depends on DVB_USB - help - Say Y here to support the - - TwinhanDTV Alpha (stick) (VP-7045), - TwinhanDTV MagicBox II (VP-7046), - DigitalNow TinyUSB 2 DVB-t, - DigitalRise USB 2.0 Ter (Beetle) and - TYPHOON DVB-T USB DRIVE - - DVB-T USB2.0 receivers. - -config DVB_USB_VP702X - tristate "TwinhanDTV StarBox and clones DVB-S USB2.0 support" - depends on DVB_USB - help - Say Y here to support the - - TwinhanDTV StarBox, - DigitalRise USB Starbox and - TYPHOON DVB-S USB 2.0 BOX - - DVB-S USB2.0 receivers. - -config DVB_USB_GP8PSK - tristate "GENPIX 8PSK->USB module support" - depends on DVB_USB - help - Say Y here to support the - GENPIX 8psk module - - DVB-S USB2.0 receivers. - -config DVB_USB_NOVA_T_USB2 - tristate "Hauppauge WinTV-NOVA-T usb2 DVB-T USB2.0 support" - depends on DVB_USB - select DVB_DIB3000MC - select DVB_PLL if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the Hauppauge WinTV-NOVA-T usb2 DVB-T USB2.0 receiver. - -config DVB_USB_TTUSB2 - tristate "Pinnacle 400e DVB-S USB2.0 support" - depends on DVB_USB - select DVB_TDA10086 if !DVB_FE_CUSTOMISE - select DVB_LNBP21 if !DVB_FE_CUSTOMISE - select DVB_TDA826X if !DVB_FE_CUSTOMISE - help - Say Y here to support the Pinnacle 400e DVB-S USB2.0 receiver. The - firmware protocol used by this module is similar to the one used by the - old ttusb-driver - that's why the module is called dvb-usb-ttusb2. - -config DVB_USB_DTT200U - tristate "WideView WT-200U and WT-220U (pen) DVB-T USB2.0 support (Yakumo/Hama/Typhoon/Yuan)" - depends on DVB_USB - help - Say Y here to support the WideView/Yakumo/Hama/Typhoon/Yuan DVB-T USB2.0 receiver. - - The receivers are also known as DTT200U (Yakumo) and UB300 (Yuan). - - The WT-220U and its clones are pen-sized. - -config DVB_USB_OPERA1 - tristate "Opera1 DVB-S USB2.0 receiver" - depends on DVB_USB - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_PLL if !DVB_FE_CUSTOMISE - help - Say Y here to support the Opera DVB-S USB2.0 receiver. - -config DVB_USB_AF9005 - tristate "Afatech AF9005 DVB-T USB1.1 support" - depends on DVB_USB && EXPERIMENTAL - select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_QT1010 if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the Afatech AF9005 based DVB-T USB1.1 receiver - and the TerraTec Cinergy T USB XE (Rev.1) - -config DVB_USB_AF9005_REMOTE - tristate "Afatech AF9005 default remote control support" - depends on DVB_USB_AF9005 - help - Say Y here to support the default remote control decoding for the - Afatech AF9005 based receiver. - -config DVB_USB_PCTV452E - tristate "Pinnacle PCTV HDTV Pro USB device/TT Connect S2-3600" - depends on DVB_USB - select TTPCI_EEPROM - select DVB_LNBP22 if !DVB_FE_CUSTOMISE - select DVB_STB0899 if !DVB_FE_CUSTOMISE - select DVB_STB6100 if !DVB_FE_CUSTOMISE - help - Support for external USB adapter designed by Pinnacle, - shipped under the brand name 'PCTV HDTV Pro USB'. - Also supports TT Connect S2-3600/3650 cards. - Say Y if you own such a device and want to use it. - -config DVB_USB_DW2102 - tristate "DvbWorld & TeVii DVB-S/S2 USB2.0 support" - depends on DVB_USB - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_STV0288 if !DVB_FE_CUSTOMISE - select DVB_STB6000 if !DVB_FE_CUSTOMISE - select DVB_CX24116 if !DVB_FE_CUSTOMISE - select DVB_SI21XX if !DVB_FE_CUSTOMISE - select DVB_TDA10023 if !DVB_FE_CUSTOMISE - select DVB_MT312 if !DVB_FE_CUSTOMISE - select DVB_ZL10039 if !DVB_FE_CUSTOMISE - select DVB_DS3000 if !DVB_FE_CUSTOMISE - select DVB_STB6100 if !DVB_FE_CUSTOMISE - select DVB_STV6110 if !DVB_FE_CUSTOMISE - select DVB_STV0900 if !DVB_FE_CUSTOMISE - help - Say Y here to support the DvbWorld, TeVii, Prof DVB-S/S2 USB2.0 - receivers. - -config DVB_USB_CINERGY_T2 - tristate "Terratec CinergyT2/qanu USB 2.0 DVB-T receiver" - depends on DVB_USB - help - Support for "TerraTec CinergyT2" USB2.0 Highspeed DVB Receivers - - Say Y if you own such a device and want to use it. - -config DVB_USB_ANYSEE - tristate "Anysee DVB-T/C USB2.0 support" - depends on DVB_USB - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_MT352 if !DVB_FE_CUSTOMISE - select DVB_ZL10353 if !DVB_FE_CUSTOMISE - select DVB_TDA10023 if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_TDA18212 if !MEDIA_TUNER_CUSTOMISE - select DVB_CX24116 if !DVB_FE_CUSTOMISE - select DVB_STV0900 if !DVB_FE_CUSTOMISE - select DVB_STV6110 if !DVB_FE_CUSTOMISE - select DVB_ISL6423 if !DVB_FE_CUSTOMISE - select DVB_CXD2820R if !DVB_FE_CUSTOMISE - help - Say Y here to support the Anysee E30, Anysee E30 Plus or - Anysee E30 C Plus DVB USB2.0 receiver. - -config DVB_USB_DTV5100 - tristate "AME DTV-5100 USB2.0 DVB-T support" - depends on DVB_USB - select DVB_ZL10353 if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_QT1010 if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the AME DTV-5100 USB2.0 DVB-T receiver. - -config DVB_USB_AF9015 - tristate "Afatech AF9015 DVB-T USB2.0 support" - depends on DVB_USB - select DVB_AF9013 - select DVB_PLL if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_QT1010 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_TDA18271 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MXL5005S if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MC44S803 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_TDA18218 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MXL5007T if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the Afatech AF9015 based DVB-T USB2.0 receiver - -config DVB_USB_CE6230 - tristate "Intel CE6230 DVB-T USB2.0 support" - depends on DVB_USB - select DVB_ZL10353 - select MEDIA_TUNER_MXL5005S if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the Intel CE6230 DVB-T USB2.0 receiver - -config DVB_USB_FRIIO - tristate "Friio ISDB-T USB2.0 Receiver support" - depends on DVB_USB - help - Say Y here to support the Japanese DTV receiver Friio. - -config DVB_USB_EC168 - tristate "E3C EC168 DVB-T USB2.0 support" - depends on DVB_USB - select DVB_EC100 - select MEDIA_TUNER_MXL5005S if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the E3C EC168 DVB-T USB2.0 receiver. - -config DVB_USB_AZ6007 - tristate "AzureWave 6007 and clones DVB-T/C USB2.0 support" - depends on DVB_USB - select DVB_DRXK if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_MT2063 if !DVB_FE_CUSTOMISE - help - Say Y here to support theAfatech AF9005 based DVB-T/DVB-C receivers. - -config DVB_USB_AZ6027 - tristate "Azurewave DVB-S/S2 USB2.0 AZ6027 support" - depends on DVB_USB - select DVB_STB0899 if !DVB_FE_CUSTOMISE - select DVB_STB6100 if !DVB_FE_CUSTOMISE - help - Say Y here to support the AZ6027 device - -config DVB_USB_LME2510 - tristate "LME DM04/QQBOX DVB-S USB2.0 support" - depends on DVB_USB - select DVB_TDA10086 if !DVB_FE_CUSTOMISE - select DVB_TDA826X if !DVB_FE_CUSTOMISE - select DVB_STV0288 if !DVB_FE_CUSTOMISE - select DVB_IX2505V if !DVB_FE_CUSTOMISE - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_M88RS2000 if !DVB_FE_CUSTOMISE - help - Say Y here to support the LME DM04/QQBOX DVB-S USB2.0 . - -config DVB_USB_TECHNISAT_USB2 - tristate "Technisat DVB-S/S2 USB2.0 support" - depends on DVB_USB - select DVB_STV090x if !DVB_FE_CUSTOMISE - select DVB_STV6110x if !DVB_FE_CUSTOMISE - help - Say Y here to support the Technisat USB2 DVB-S/S2 device - -config DVB_USB_IT913X - tristate "it913x driver" - depends on DVB_USB - select DVB_IT913X_FE - help - Say Y here to support the it913x device - -config DVB_USB_MXL111SF - tristate "MxL111SF DTV USB2.0 support" - depends on DVB_USB - select DVB_LGDT3305 if !DVB_FE_CUSTOMISE - select DVB_LG2160 if !DVB_FE_CUSTOMISE - select VIDEO_TVEEPROM - help - Say Y here to support the MxL111SF USB2.0 DTV receiver. - -config DVB_USB_RTL28XXU - tristate "Realtek RTL28xxU DVB USB support" - depends on DVB_USB && EXPERIMENTAL - select DVB_RTL2830 - select DVB_RTL2832 - select MEDIA_TUNER_QT1010 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MXL5005S if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_FC0012 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_FC0013 if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the Realtek RTL28xxU DVB USB receiver. - -config DVB_USB_AF9035 - tristate "Afatech AF9035 DVB-T USB2.0 support" - depends on DVB_USB - select DVB_AF9033 - select MEDIA_TUNER_TUA9001 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_FC0011 if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_MXL5007T if !MEDIA_TUNER_CUSTOMISE - select MEDIA_TUNER_TDA18218 if !MEDIA_TUNER_CUSTOMISE - help - Say Y here to support the Afatech AF9035 based DVB USB receiver. - diff --git a/drivers/media/dvb/dvb-usb/Makefile b/drivers/media/dvb/dvb-usb/Makefile deleted file mode 100644 index b667ac39a4e3..000000000000 --- a/drivers/media/dvb/dvb-usb/Makefile +++ /dev/null @@ -1,121 +0,0 @@ -dvb-usb-objs = dvb-usb-firmware.o dvb-usb-init.o dvb-usb-urb.o dvb-usb-i2c.o dvb-usb-dvb.o dvb-usb-remote.o usb-urb.o -obj-$(CONFIG_DVB_USB) += dvb-usb.o - -dvb-usb-vp7045-objs = vp7045.o vp7045-fe.o -obj-$(CONFIG_DVB_USB_VP7045) += dvb-usb-vp7045.o - -dvb-usb-vp702x-objs = vp702x.o vp702x-fe.o -obj-$(CONFIG_DVB_USB_VP702X) += dvb-usb-vp702x.o - -dvb-usb-gp8psk-objs = gp8psk.o gp8psk-fe.o -obj-$(CONFIG_DVB_USB_GP8PSK) += dvb-usb-gp8psk.o - -dvb-usb-dtt200u-objs = dtt200u.o dtt200u-fe.o -obj-$(CONFIG_DVB_USB_DTT200U) += dvb-usb-dtt200u.o - -dvb-usb-dibusb-common-objs = dibusb-common.o - -dvb-usb-a800-objs = a800.o -obj-$(CONFIG_DVB_USB_A800) += dvb-usb-dibusb-common.o dvb-usb-a800.o - -dvb-usb-dibusb-mb-objs = dibusb-mb.o -obj-$(CONFIG_DVB_USB_DIBUSB_MB) += dvb-usb-dibusb-common.o dvb-usb-dibusb-mb.o - -dvb-usb-dibusb-mc-objs = dibusb-mc.o -obj-$(CONFIG_DVB_USB_DIBUSB_MC) += dvb-usb-dibusb-common.o dvb-usb-dibusb-mc.o - -dvb-usb-nova-t-usb2-objs = nova-t-usb2.o -obj-$(CONFIG_DVB_USB_NOVA_T_USB2) += dvb-usb-dibusb-common.o dvb-usb-nova-t-usb2.o - -dvb-usb-umt-010-objs = umt-010.o -obj-$(CONFIG_DVB_USB_UMT_010) += dvb-usb-dibusb-common.o dvb-usb-umt-010.o - -dvb-usb-m920x-objs = m920x.o -obj-$(CONFIG_DVB_USB_M920X) += dvb-usb-m920x.o - -dvb-usb-gl861-objs = gl861.o -obj-$(CONFIG_DVB_USB_GL861) += dvb-usb-gl861.o - -dvb-usb-au6610-objs = au6610.o -obj-$(CONFIG_DVB_USB_AU6610) += dvb-usb-au6610.o - -dvb-usb-digitv-objs = digitv.o -obj-$(CONFIG_DVB_USB_DIGITV) += dvb-usb-digitv.o - -dvb-usb-cxusb-objs = cxusb.o -obj-$(CONFIG_DVB_USB_CXUSB) += dvb-usb-cxusb.o - -dvb-usb-ttusb2-objs = ttusb2.o -obj-$(CONFIG_DVB_USB_TTUSB2) += dvb-usb-ttusb2.o - -dvb-usb-dib0700-objs = dib0700_core.o dib0700_devices.o -obj-$(CONFIG_DVB_USB_DIB0700) += dvb-usb-dib0700.o - -dvb-usb-opera-objs = opera1.o -obj-$(CONFIG_DVB_USB_OPERA1) += dvb-usb-opera.o - -dvb-usb-af9005-objs = af9005.o af9005-fe.o -obj-$(CONFIG_DVB_USB_AF9005) += dvb-usb-af9005.o - -dvb-usb-af9005-remote-objs = af9005-remote.o -obj-$(CONFIG_DVB_USB_AF9005_REMOTE) += dvb-usb-af9005-remote.o - -dvb-usb-anysee-objs = anysee.o -obj-$(CONFIG_DVB_USB_ANYSEE) += dvb-usb-anysee.o - -dvb-usb-pctv452e-objs = pctv452e.o -obj-$(CONFIG_DVB_USB_PCTV452E) += dvb-usb-pctv452e.o - -dvb-usb-dw2102-objs = dw2102.o -obj-$(CONFIG_DVB_USB_DW2102) += dvb-usb-dw2102.o - -dvb-usb-dtv5100-objs = dtv5100.o -obj-$(CONFIG_DVB_USB_DTV5100) += dvb-usb-dtv5100.o - -dvb-usb-af9015-objs = af9015.o -obj-$(CONFIG_DVB_USB_AF9015) += dvb-usb-af9015.o - -dvb-usb-cinergyT2-objs = cinergyT2-core.o cinergyT2-fe.o -obj-$(CONFIG_DVB_USB_CINERGY_T2) += dvb-usb-cinergyT2.o - -dvb-usb-ce6230-objs = ce6230.o -obj-$(CONFIG_DVB_USB_CE6230) += dvb-usb-ce6230.o - -dvb-usb-friio-objs = friio.o friio-fe.o -obj-$(CONFIG_DVB_USB_FRIIO) += dvb-usb-friio.o - -dvb-usb-ec168-objs = ec168.o -obj-$(CONFIG_DVB_USB_EC168) += dvb-usb-ec168.o - -dvb-usb-az6007-objs = az6007.o -obj-$(CONFIG_DVB_USB_AZ6007) += dvb-usb-az6007.o - -dvb-usb-az6027-objs = az6027.o -obj-$(CONFIG_DVB_USB_AZ6027) += dvb-usb-az6027.o - -dvb-usb-lmedm04-objs = lmedm04.o -obj-$(CONFIG_DVB_USB_LME2510) += dvb-usb-lmedm04.o - -dvb-usb-technisat-usb2-objs = technisat-usb2.o -obj-$(CONFIG_DVB_USB_TECHNISAT_USB2) += dvb-usb-technisat-usb2.o - -dvb-usb-it913x-objs := it913x.o -obj-$(CONFIG_DVB_USB_IT913X) += dvb-usb-it913x.o - -dvb-usb-mxl111sf-objs = mxl111sf.o mxl111sf-phy.o mxl111sf-i2c.o mxl111sf-gpio.o -obj-$(CONFIG_DVB_USB_MXL111SF) += dvb-usb-mxl111sf.o -obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-demod.o -obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o - -dvb-usb-rtl28xxu-objs = rtl28xxu.o -obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o - -dvb-usb-af9035-objs = af9035.o -obj-$(CONFIG_DVB_USB_AF9035) += dvb-usb-af9035.o - -ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core -ccflags-y += -I$(srctree)/drivers/media/dvb/frontends/ -# due to tuner-xc3028 -ccflags-y += -I$(srctree)/drivers/media/common/tuners -ccflags-y += -I$(srctree)/drivers/media/dvb/ttpci - diff --git a/drivers/media/dvb/dvb-usb/af9015.c b/drivers/media/dvb/dvb-usb/af9015.c deleted file mode 100644 index 677fed79b01e..000000000000 --- a/drivers/media/dvb/dvb-usb/af9015.c +++ /dev/null @@ -1,1952 +0,0 @@ -/* - * DVB USB Linux driver for Afatech AF9015 DVB-T USB2.0 receiver - * - * Copyright (C) 2007 Antti Palosaari - * - * Thanks to Afatech who kindly provided information. - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include -#include - -#include "af9015.h" -#include "af9013.h" -#include "mt2060.h" -#include "qt1010.h" -#include "tda18271.h" -#include "mxl5005s.h" -#include "mc44s803.h" -#include "tda18218.h" -#include "mxl5007t.h" - -static int dvb_usb_af9015_debug; -module_param_named(debug, dvb_usb_af9015_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level" DVB_USB_DEBUG_STATUS); -static int dvb_usb_af9015_remote; -module_param_named(remote, dvb_usb_af9015_remote, int, 0644); -MODULE_PARM_DESC(remote, "select remote"); -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -static DEFINE_MUTEX(af9015_usb_mutex); - -static struct af9015_config af9015_config; -static struct dvb_usb_device_properties af9015_properties[3]; -static int af9015_properties_count = ARRAY_SIZE(af9015_properties); - -static struct af9013_config af9015_af9013_config[] = { - { - .i2c_addr = AF9015_I2C_DEMOD, - .ts_mode = AF9013_TS_USB, - .api_version = { 0, 1, 9, 0 }, - .gpio[0] = AF9013_GPIO_HI, - .gpio[3] = AF9013_GPIO_TUNER_ON, - - }, { - .ts_mode = AF9013_TS_SERIAL, - .api_version = { 0, 1, 9, 0 }, - .gpio[0] = AF9013_GPIO_TUNER_ON, - .gpio[1] = AF9013_GPIO_LO, - } -}; - -static int af9015_rw_udev(struct usb_device *udev, struct req_t *req) -{ -#define BUF_LEN 63 -#define REQ_HDR_LEN 8 /* send header size */ -#define ACK_HDR_LEN 2 /* rece header size */ - int act_len, ret; - u8 buf[BUF_LEN]; - u8 write = 1; - u8 msg_len = REQ_HDR_LEN; - static u8 seq; /* packet sequence number */ - - if (mutex_lock_interruptible(&af9015_usb_mutex) < 0) - return -EAGAIN; - - buf[0] = req->cmd; - buf[1] = seq++; - buf[2] = req->i2c_addr; - buf[3] = req->addr >> 8; - buf[4] = req->addr & 0xff; - buf[5] = req->mbox; - buf[6] = req->addr_len; - buf[7] = req->data_len; - - switch (req->cmd) { - case GET_CONFIG: - case READ_MEMORY: - case RECONNECT_USB: - write = 0; - break; - case READ_I2C: - write = 0; - buf[2] |= 0x01; /* set I2C direction */ - case WRITE_I2C: - buf[0] = READ_WRITE_I2C; - break; - case WRITE_MEMORY: - if (((req->addr & 0xff00) == 0xff00) || - ((req->addr & 0xff00) == 0xae00)) - buf[0] = WRITE_VIRTUAL_MEMORY; - case WRITE_VIRTUAL_MEMORY: - case COPY_FIRMWARE: - case DOWNLOAD_FIRMWARE: - case BOOT: - break; - default: - err("unknown command:%d", req->cmd); - ret = -1; - goto error_unlock; - } - - /* buffer overflow check */ - if ((write && (req->data_len > BUF_LEN - REQ_HDR_LEN)) || - (!write && (req->data_len > BUF_LEN - ACK_HDR_LEN))) { - err("too much data; cmd:%d len:%d", req->cmd, req->data_len); - ret = -EINVAL; - goto error_unlock; - } - - /* write requested */ - if (write) { - memcpy(&buf[REQ_HDR_LEN], req->data, req->data_len); - msg_len += req->data_len; - } - - deb_xfer(">>> "); - debug_dump(buf, msg_len, deb_xfer); - - /* send req */ - ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, 0x02), buf, msg_len, - &act_len, AF9015_USB_TIMEOUT); - if (ret) - err("bulk message failed:%d (%d/%d)", ret, msg_len, act_len); - else - if (act_len != msg_len) - ret = -1; /* all data is not send */ - if (ret) - goto error_unlock; - - /* no ack for those packets */ - if (req->cmd == DOWNLOAD_FIRMWARE || req->cmd == RECONNECT_USB) - goto exit_unlock; - - /* write receives seq + status = 2 bytes - read receives seq + status + data = 2 + N bytes */ - msg_len = ACK_HDR_LEN; - if (!write) - msg_len += req->data_len; - - ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, 0x81), buf, msg_len, - &act_len, AF9015_USB_TIMEOUT); - if (ret) { - err("recv bulk message failed:%d", ret); - ret = -1; - goto error_unlock; - } - - deb_xfer("<<< "); - debug_dump(buf, act_len, deb_xfer); - - /* check status */ - if (buf[1]) { - err("command failed:%d", buf[1]); - ret = -1; - goto error_unlock; - } - - /* read request, copy returned data to return buf */ - if (!write) - memcpy(req->data, &buf[ACK_HDR_LEN], req->data_len); - -error_unlock: -exit_unlock: - mutex_unlock(&af9015_usb_mutex); - - return ret; -} - -static int af9015_ctrl_msg(struct dvb_usb_device *d, struct req_t *req) -{ - return af9015_rw_udev(d->udev, req); -} - -static int af9015_write_regs(struct dvb_usb_device *d, u16 addr, u8 *val, - u8 len) -{ - struct req_t req = {WRITE_MEMORY, AF9015_I2C_DEMOD, addr, 0, 0, len, - val}; - return af9015_ctrl_msg(d, &req); -} - -static int af9015_write_reg(struct dvb_usb_device *d, u16 addr, u8 val) -{ - return af9015_write_regs(d, addr, &val, 1); -} - -static int af9015_read_regs(struct dvb_usb_device *d, u16 addr, u8 *val, u8 len) -{ - struct req_t req = {READ_MEMORY, AF9015_I2C_DEMOD, addr, 0, 0, len, - val}; - return af9015_ctrl_msg(d, &req); -} - -static int af9015_read_reg(struct dvb_usb_device *d, u16 addr, u8 *val) -{ - return af9015_read_regs(d, addr, val, 1); -} - -static int af9015_write_reg_i2c(struct dvb_usb_device *d, u8 addr, u16 reg, - u8 val) -{ - struct req_t req = {WRITE_I2C, addr, reg, 1, 1, 1, &val}; - - if (addr == af9015_af9013_config[0].i2c_addr || - addr == af9015_af9013_config[1].i2c_addr) - req.addr_len = 3; - - return af9015_ctrl_msg(d, &req); -} - -static int af9015_read_reg_i2c(struct dvb_usb_device *d, u8 addr, u16 reg, - u8 *val) -{ - struct req_t req = {READ_I2C, addr, reg, 0, 1, 1, val}; - - if (addr == af9015_af9013_config[0].i2c_addr || - addr == af9015_af9013_config[1].i2c_addr) - req.addr_len = 3; - - return af9015_ctrl_msg(d, &req); -} - -static int af9015_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - int ret = 0, i = 0; - u16 addr; - u8 uninitialized_var(mbox), addr_len; - struct req_t req; - -/* -The bus lock is needed because there is two tuners both using same I2C-address. -Due to that the only way to select correct tuner is use demodulator I2C-gate. - -................................................ -. AF9015 includes integrated AF9013 demodulator. -. ____________ ____________ . ____________ -.| uC | | demod | . | tuner | -.|------------| |------------| . |------------| -.| AF9015 | | AF9013/5 | . | MXL5003 | -.| |--+----I2C-------|-----/ -----|-.-----I2C-------| | -.| | | | addr 0x38 | . | addr 0xc6 | -.|____________| | |____________| . |____________| -.................|.............................. - | ____________ ____________ - | | demod | | tuner | - | |------------| |------------| - | | AF9013 | | MXL5003 | - +----I2C-------|-----/ -----|-------I2C-------| | - | addr 0x3a | | addr 0xc6 | - |____________| |____________| -*/ - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - while (i < num) { - if (msg[i].addr == af9015_af9013_config[0].i2c_addr || - msg[i].addr == af9015_af9013_config[1].i2c_addr) { - addr = msg[i].buf[0] << 8; - addr += msg[i].buf[1]; - mbox = msg[i].buf[2]; - addr_len = 3; - } else { - addr = msg[i].buf[0]; - addr_len = 1; - /* mbox is don't care in that case */ - } - - if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { - if (msg[i].len > 3 || msg[i+1].len > 61) { - ret = -EOPNOTSUPP; - goto error; - } - if (msg[i].addr == af9015_af9013_config[0].i2c_addr) - req.cmd = READ_MEMORY; - else - req.cmd = READ_I2C; - req.i2c_addr = msg[i].addr; - req.addr = addr; - req.mbox = mbox; - req.addr_len = addr_len; - req.data_len = msg[i+1].len; - req.data = &msg[i+1].buf[0]; - ret = af9015_ctrl_msg(d, &req); - i += 2; - } else if (msg[i].flags & I2C_M_RD) { - if (msg[i].len > 61) { - ret = -EOPNOTSUPP; - goto error; - } - if (msg[i].addr == - af9015_af9013_config[0].i2c_addr) { - ret = -EINVAL; - goto error; - } - req.cmd = READ_I2C; - req.i2c_addr = msg[i].addr; - req.addr = addr; - req.mbox = mbox; - req.addr_len = addr_len; - req.data_len = msg[i].len; - req.data = &msg[i].buf[0]; - ret = af9015_ctrl_msg(d, &req); - i += 1; - } else { - if (msg[i].len > 21) { - ret = -EOPNOTSUPP; - goto error; - } - if (msg[i].addr == af9015_af9013_config[0].i2c_addr) - req.cmd = WRITE_MEMORY; - else - req.cmd = WRITE_I2C; - req.i2c_addr = msg[i].addr; - req.addr = addr; - req.mbox = mbox; - req.addr_len = addr_len; - req.data_len = msg[i].len-addr_len; - req.data = &msg[i].buf[addr_len]; - ret = af9015_ctrl_msg(d, &req); - i += 1; - } - if (ret) - goto error; - - } - ret = i; - -error: - mutex_unlock(&d->i2c_mutex); - - return ret; -} - -static u32 af9015_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm af9015_i2c_algo = { - .master_xfer = af9015_i2c_xfer, - .functionality = af9015_i2c_func, -}; - -static int af9015_do_reg_bit(struct dvb_usb_device *d, u16 addr, u8 bit, u8 op) -{ - int ret; - u8 val, mask = 0x01; - - ret = af9015_read_reg(d, addr, &val); - if (ret) - return ret; - - mask <<= bit; - if (op) { - /* set bit */ - val |= mask; - } else { - /* clear bit */ - mask ^= 0xff; - val &= mask; - } - - return af9015_write_reg(d, addr, val); -} - -static int af9015_set_reg_bit(struct dvb_usb_device *d, u16 addr, u8 bit) -{ - return af9015_do_reg_bit(d, addr, bit, 1); -} - -static int af9015_clear_reg_bit(struct dvb_usb_device *d, u16 addr, u8 bit) -{ - return af9015_do_reg_bit(d, addr, bit, 0); -} - -static int af9015_init_endpoint(struct dvb_usb_device *d) -{ - int ret; - u16 frame_size; - u8 packet_size; - deb_info("%s: USB speed:%d\n", __func__, d->udev->speed); - - /* Windows driver uses packet count 21 for USB1.1 and 348 for USB2.0. - We use smaller - about 1/4 from the original, 5 and 87. */ -#define TS_PACKET_SIZE 188 - -#define TS_USB20_PACKET_COUNT 87 -#define TS_USB20_FRAME_SIZE (TS_PACKET_SIZE*TS_USB20_PACKET_COUNT) - -#define TS_USB11_PACKET_COUNT 5 -#define TS_USB11_FRAME_SIZE (TS_PACKET_SIZE*TS_USB11_PACKET_COUNT) - -#define TS_USB20_MAX_PACKET_SIZE 512 -#define TS_USB11_MAX_PACKET_SIZE 64 - - if (d->udev->speed == USB_SPEED_FULL) { - frame_size = TS_USB11_FRAME_SIZE/4; - packet_size = TS_USB11_MAX_PACKET_SIZE/4; - } else { - frame_size = TS_USB20_FRAME_SIZE/4; - packet_size = TS_USB20_MAX_PACKET_SIZE/4; - } - - ret = af9015_set_reg_bit(d, 0xd507, 2); /* assert EP4 reset */ - if (ret) - goto error; - ret = af9015_set_reg_bit(d, 0xd50b, 1); /* assert EP5 reset */ - if (ret) - goto error; - ret = af9015_clear_reg_bit(d, 0xdd11, 5); /* disable EP4 */ - if (ret) - goto error; - ret = af9015_clear_reg_bit(d, 0xdd11, 6); /* disable EP5 */ - if (ret) - goto error; - ret = af9015_set_reg_bit(d, 0xdd11, 5); /* enable EP4 */ - if (ret) - goto error; - if (af9015_config.dual_mode) { - ret = af9015_set_reg_bit(d, 0xdd11, 6); /* enable EP5 */ - if (ret) - goto error; - } - ret = af9015_clear_reg_bit(d, 0xdd13, 5); /* disable EP4 NAK */ - if (ret) - goto error; - if (af9015_config.dual_mode) { - ret = af9015_clear_reg_bit(d, 0xdd13, 6); /* disable EP5 NAK */ - if (ret) - goto error; - } - /* EP4 xfer length */ - ret = af9015_write_reg(d, 0xdd88, frame_size & 0xff); - if (ret) - goto error; - ret = af9015_write_reg(d, 0xdd89, frame_size >> 8); - if (ret) - goto error; - /* EP5 xfer length */ - ret = af9015_write_reg(d, 0xdd8a, frame_size & 0xff); - if (ret) - goto error; - ret = af9015_write_reg(d, 0xdd8b, frame_size >> 8); - if (ret) - goto error; - ret = af9015_write_reg(d, 0xdd0c, packet_size); /* EP4 packet size */ - if (ret) - goto error; - ret = af9015_write_reg(d, 0xdd0d, packet_size); /* EP5 packet size */ - if (ret) - goto error; - ret = af9015_clear_reg_bit(d, 0xd507, 2); /* negate EP4 reset */ - if (ret) - goto error; - if (af9015_config.dual_mode) { - ret = af9015_clear_reg_bit(d, 0xd50b, 1); /* negate EP5 reset */ - if (ret) - goto error; - } - - /* enable / disable mp2if2 */ - if (af9015_config.dual_mode) - ret = af9015_set_reg_bit(d, 0xd50b, 0); - else - ret = af9015_clear_reg_bit(d, 0xd50b, 0); - -error: - if (ret) - err("endpoint init failed:%d", ret); - return ret; -} - -static int af9015_copy_firmware(struct dvb_usb_device *d) -{ - int ret; - u8 fw_params[4]; - u8 val, i; - struct req_t req = {COPY_FIRMWARE, 0, 0x5100, 0, 0, sizeof(fw_params), - fw_params }; - deb_info("%s:\n", __func__); - - fw_params[0] = af9015_config.firmware_size >> 8; - fw_params[1] = af9015_config.firmware_size & 0xff; - fw_params[2] = af9015_config.firmware_checksum >> 8; - fw_params[3] = af9015_config.firmware_checksum & 0xff; - - /* wait 2nd demodulator ready */ - msleep(100); - - ret = af9015_read_reg_i2c(d, - af9015_af9013_config[1].i2c_addr, 0x98be, &val); - if (ret) - goto error; - else - deb_info("%s: firmware status:%02x\n", __func__, val); - - if (val == 0x0c) /* fw is running, no need for download */ - goto exit; - - /* set I2C master clock to fast (to speed up firmware copy) */ - ret = af9015_write_reg(d, 0xd416, 0x04); /* 0x04 * 400ns */ - if (ret) - goto error; - - msleep(50); - - /* copy firmware */ - ret = af9015_ctrl_msg(d, &req); - if (ret) - err("firmware copy cmd failed:%d", ret); - deb_info("%s: firmware copy done\n", __func__); - - /* set I2C master clock back to normal */ - ret = af9015_write_reg(d, 0xd416, 0x14); /* 0x14 * 400ns */ - if (ret) - goto error; - - /* request boot firmware */ - ret = af9015_write_reg_i2c(d, af9015_af9013_config[1].i2c_addr, - 0xe205, 1); - deb_info("%s: firmware boot cmd status:%d\n", __func__, ret); - if (ret) - goto error; - - for (i = 0; i < 15; i++) { - msleep(100); - - /* check firmware status */ - ret = af9015_read_reg_i2c(d, - af9015_af9013_config[1].i2c_addr, 0x98be, &val); - deb_info("%s: firmware status cmd status:%d fw status:%02x\n", - __func__, ret, val); - if (ret) - goto error; - - if (val == 0x0c || val == 0x04) /* success or fail */ - break; - } - - if (val == 0x04) { - err("firmware did not run"); - ret = -1; - } else if (val != 0x0c) { - err("firmware boot timeout"); - ret = -1; - } - -error: -exit: - return ret; -} - -/* hash (and dump) eeprom */ -static int af9015_eeprom_hash(struct usb_device *udev) -{ - static const unsigned int eeprom_size = 256; - unsigned int reg; - int ret; - u8 val, *eeprom; - struct req_t req = {READ_I2C, AF9015_I2C_EEPROM, 0, 0, 1, 1, &val}; - - eeprom = kmalloc(eeprom_size, GFP_KERNEL); - if (eeprom == NULL) - return -ENOMEM; - - for (reg = 0; reg < eeprom_size; reg++) { - req.addr = reg; - ret = af9015_rw_udev(udev, &req); - if (ret) - goto free; - eeprom[reg] = val; - } - - if (dvb_usb_af9015_debug & 0x01) - print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, eeprom, - eeprom_size); - - BUG_ON(eeprom_size % 4); - - af9015_config.eeprom_sum = 0; - for (reg = 0; reg < eeprom_size / sizeof(u32); reg++) { - af9015_config.eeprom_sum *= GOLDEN_RATIO_PRIME_32; - af9015_config.eeprom_sum += le32_to_cpu(((u32 *)eeprom)[reg]); - } - - deb_info("%s: eeprom sum=%.8x\n", __func__, af9015_config.eeprom_sum); - - ret = 0; -free: - kfree(eeprom); - return ret; -} - -static int af9015_init(struct dvb_usb_device *d) -{ - int ret; - deb_info("%s:\n", __func__); - - /* init RC canary */ - ret = af9015_write_reg(d, 0x98e9, 0xff); - if (ret) - goto error; - - ret = af9015_init_endpoint(d); - if (ret) - goto error; - -error: - return ret; -} - -static int af9015_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - int ret; - deb_info("%s: onoff:%d\n", __func__, onoff); - - if (onoff) - ret = af9015_set_reg_bit(adap->dev, 0xd503, 0); - else - ret = af9015_clear_reg_bit(adap->dev, 0xd503, 0); - - return ret; -} - -static int af9015_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid, - int onoff) -{ - int ret; - u8 idx; - - deb_info("%s: set pid filter, index %d, pid %x, onoff %d\n", - __func__, index, pid, onoff); - - ret = af9015_write_reg(adap->dev, 0xd505, (pid & 0xff)); - if (ret) - goto error; - - ret = af9015_write_reg(adap->dev, 0xd506, (pid >> 8)); - if (ret) - goto error; - - idx = ((index & 0x1f) | (1 << 5)); - ret = af9015_write_reg(adap->dev, 0xd504, idx); - -error: - return ret; -} - -static int af9015_download_firmware(struct usb_device *udev, - const struct firmware *fw) -{ - int i, len, remaining, ret; - struct req_t req = {DOWNLOAD_FIRMWARE, 0, 0, 0, 0, 0, NULL}; - u16 checksum = 0; - - deb_info("%s:\n", __func__); - - /* calc checksum */ - for (i = 0; i < fw->size; i++) - checksum += fw->data[i]; - - af9015_config.firmware_size = fw->size; - af9015_config.firmware_checksum = checksum; - - #define FW_ADDR 0x5100 /* firmware start address */ - #define LEN_MAX 55 /* max packet size */ - for (remaining = fw->size; remaining > 0; remaining -= LEN_MAX) { - len = remaining; - if (len > LEN_MAX) - len = LEN_MAX; - - req.data_len = len; - req.data = (u8 *) &fw->data[fw->size - remaining]; - req.addr = FW_ADDR + fw->size - remaining; - - ret = af9015_rw_udev(udev, &req); - if (ret) { - err("firmware download failed:%d", ret); - goto error; - } - } - - /* firmware loaded, request boot */ - req.cmd = BOOT; - ret = af9015_rw_udev(udev, &req); - if (ret) { - err("firmware boot failed:%d", ret); - goto error; - } - -error: - return ret; -} - -struct af9015_rc_setup { - unsigned int id; - char *rc_codes; -}; - -static char *af9015_rc_setup_match(unsigned int id, - const struct af9015_rc_setup *table) -{ - for (; table->rc_codes; table++) - if (table->id == id) - return table->rc_codes; - return NULL; -} - -static const struct af9015_rc_setup af9015_rc_setup_modparam[] = { - { AF9015_REMOTE_A_LINK_DTU_M, RC_MAP_ALINK_DTU_M }, - { AF9015_REMOTE_MSI_DIGIVOX_MINI_II_V3, RC_MAP_MSI_DIGIVOX_II }, - { AF9015_REMOTE_MYGICTV_U718, RC_MAP_TOTAL_MEDIA_IN_HAND }, - { AF9015_REMOTE_DIGITTRADE_DVB_T, RC_MAP_DIGITTRADE }, - { AF9015_REMOTE_AVERMEDIA_KS, RC_MAP_AVERMEDIA_RM_KS }, - { } -}; - -static const struct af9015_rc_setup af9015_rc_setup_hashes[] = { - { 0xb8feb708, RC_MAP_MSI_DIGIVOX_II }, - { 0xa3703d00, RC_MAP_ALINK_DTU_M }, - { 0x9b7dc64e, RC_MAP_TOTAL_MEDIA_IN_HAND }, /* MYGICTV U718 */ - { 0x5d49e3db, RC_MAP_DIGITTRADE }, /* LC-Power LC-USB-DVBT */ - { } -}; - -static const struct af9015_rc_setup af9015_rc_setup_usbids[] = { - { (USB_VID_TERRATEC << 16) | USB_PID_TERRATEC_CINERGY_T_STICK_RC, - RC_MAP_TERRATEC_SLIM_2 }, - { (USB_VID_TERRATEC << 16) | USB_PID_TERRATEC_CINERGY_T_STICK_DUAL_RC, - RC_MAP_TERRATEC_SLIM }, - { (USB_VID_VISIONPLUS << 16) | USB_PID_AZUREWAVE_AD_TU700, - RC_MAP_AZUREWAVE_AD_TU700 }, - { (USB_VID_VISIONPLUS << 16) | USB_PID_TINYTWIN, - RC_MAP_AZUREWAVE_AD_TU700 }, - { (USB_VID_MSI_2 << 16) | USB_PID_MSI_DIGI_VOX_MINI_III, - RC_MAP_MSI_DIGIVOX_III }, - { (USB_VID_MSI_2 << 16) | USB_PID_MSI_DIGIVOX_DUO, - RC_MAP_MSI_DIGIVOX_III }, - { (USB_VID_LEADTEK << 16) | USB_PID_WINFAST_DTV_DONGLE_GOLD, - RC_MAP_LEADTEK_Y04G0051 }, - { (USB_VID_LEADTEK << 16) | USB_PID_WINFAST_DTV2000DS, - RC_MAP_LEADTEK_Y04G0051 }, - { (USB_VID_AVERMEDIA << 16) | USB_PID_AVERMEDIA_VOLAR_X, - RC_MAP_AVERMEDIA_M135A }, - { (USB_VID_AFATECH << 16) | USB_PID_TREKSTOR_DVBT, - RC_MAP_TREKSTOR }, - { (USB_VID_KWORLD_2 << 16) | USB_PID_TINYTWIN_2, - RC_MAP_DIGITALNOW_TINYTWIN }, - { (USB_VID_GTEK << 16) | USB_PID_TINYTWIN_3, - RC_MAP_DIGITALNOW_TINYTWIN }, - { (USB_VID_KWORLD_2 << 16) | USB_PID_SVEON_STV22, - RC_MAP_MSI_DIGIVOX_III }, - { } -}; - -static void af9015_set_remote_config(struct usb_device *udev, - struct dvb_usb_device_properties *props) -{ - u16 vid = le16_to_cpu(udev->descriptor.idVendor); - u16 pid = le16_to_cpu(udev->descriptor.idProduct); - - /* try to load remote based module param */ - props->rc.core.rc_codes = af9015_rc_setup_match( - dvb_usb_af9015_remote, af9015_rc_setup_modparam); - - /* try to load remote based eeprom hash */ - if (!props->rc.core.rc_codes) - props->rc.core.rc_codes = af9015_rc_setup_match( - af9015_config.eeprom_sum, af9015_rc_setup_hashes); - - /* try to load remote based USB ID */ - if (!props->rc.core.rc_codes) - props->rc.core.rc_codes = af9015_rc_setup_match( - (vid << 16) | pid, af9015_rc_setup_usbids); - - /* try to load remote based USB iManufacturer string */ - if (!props->rc.core.rc_codes && vid == USB_VID_AFATECH) { - /* Check USB manufacturer and product strings and try - to determine correct remote in case of chip vendor - reference IDs are used. - DO NOT ADD ANYTHING NEW HERE. Use hashes instead. */ - char manufacturer[10]; - memset(manufacturer, 0, sizeof(manufacturer)); - usb_string(udev, udev->descriptor.iManufacturer, - manufacturer, sizeof(manufacturer)); - if (!strcmp("MSI", manufacturer)) { - /* iManufacturer 1 MSI - iProduct 2 MSI K-VOX */ - props->rc.core.rc_codes = af9015_rc_setup_match( - AF9015_REMOTE_MSI_DIGIVOX_MINI_II_V3, - af9015_rc_setup_modparam); - } - } - - /* finally load "empty" just for leaving IR receiver enabled */ - if (!props->rc.core.rc_codes) - props->rc.core.rc_codes = RC_MAP_EMPTY; - - return; -} - -static int af9015_read_config(struct usb_device *udev) -{ - int ret; - u8 val, i, offset = 0; - struct req_t req = {READ_I2C, AF9015_I2C_EEPROM, 0, 0, 1, 1, &val}; - - /* IR remote controller */ - req.addr = AF9015_EEPROM_IR_MODE; - /* first message will timeout often due to possible hw bug */ - for (i = 0; i < 4; i++) { - ret = af9015_rw_udev(udev, &req); - if (!ret) - break; - } - if (ret) - goto error; - - ret = af9015_eeprom_hash(udev); - if (ret) - goto error; - - deb_info("%s: IR mode=%d\n", __func__, val); - for (i = 0; i < af9015_properties_count; i++) { - if (val == AF9015_IR_MODE_DISABLED) - af9015_properties[i].rc.core.rc_codes = NULL; - else - af9015_set_remote_config(udev, &af9015_properties[i]); - } - - /* TS mode - one or two receivers */ - req.addr = AF9015_EEPROM_TS_MODE; - ret = af9015_rw_udev(udev, &req); - if (ret) - goto error; - af9015_config.dual_mode = val; - deb_info("%s: TS mode=%d\n", __func__, af9015_config.dual_mode); - - /* Set adapter0 buffer size according to USB port speed, adapter1 buffer - size can be static because it is enabled only USB2.0 */ - for (i = 0; i < af9015_properties_count; i++) { - /* USB1.1 set smaller buffersize and disable 2nd adapter */ - if (udev->speed == USB_SPEED_FULL) { - af9015_properties[i].adapter[0].fe[0].stream.u.bulk.buffersize - = TS_USB11_FRAME_SIZE; - /* disable 2nd adapter because we don't have - PID-filters */ - af9015_config.dual_mode = 0; - } else { - af9015_properties[i].adapter[0].fe[0].stream.u.bulk.buffersize - = TS_USB20_FRAME_SIZE; - } - } - - if (af9015_config.dual_mode) { - /* read 2nd demodulator I2C address */ - req.addr = AF9015_EEPROM_DEMOD2_I2C; - ret = af9015_rw_udev(udev, &req); - if (ret) - goto error; - af9015_af9013_config[1].i2c_addr = val; - - /* enable 2nd adapter */ - for (i = 0; i < af9015_properties_count; i++) - af9015_properties[i].num_adapters = 2; - - } else { - /* disable 2nd adapter */ - for (i = 0; i < af9015_properties_count; i++) - af9015_properties[i].num_adapters = 1; - } - - for (i = 0; i < af9015_properties[0].num_adapters; i++) { - if (i == 1) - offset = AF9015_EEPROM_OFFSET; - /* xtal */ - req.addr = AF9015_EEPROM_XTAL_TYPE1 + offset; - ret = af9015_rw_udev(udev, &req); - if (ret) - goto error; - switch (val) { - case 0: - af9015_af9013_config[i].clock = 28800000; - break; - case 1: - af9015_af9013_config[i].clock = 20480000; - break; - case 2: - af9015_af9013_config[i].clock = 28000000; - break; - case 3: - af9015_af9013_config[i].clock = 25000000; - break; - }; - deb_info("%s: [%d] xtal=%d set clock=%d\n", __func__, i, - val, af9015_af9013_config[i].clock); - - /* IF frequency */ - req.addr = AF9015_EEPROM_IF1H + offset; - ret = af9015_rw_udev(udev, &req); - if (ret) - goto error; - - af9015_af9013_config[i].if_frequency = val << 8; - - req.addr = AF9015_EEPROM_IF1L + offset; - ret = af9015_rw_udev(udev, &req); - if (ret) - goto error; - - af9015_af9013_config[i].if_frequency += val; - af9015_af9013_config[i].if_frequency *= 1000; - deb_info("%s: [%d] IF frequency=%d\n", __func__, i, - af9015_af9013_config[0].if_frequency); - - /* MT2060 IF1 */ - req.addr = AF9015_EEPROM_MT2060_IF1H + offset; - ret = af9015_rw_udev(udev, &req); - if (ret) - goto error; - af9015_config.mt2060_if1[i] = val << 8; - req.addr = AF9015_EEPROM_MT2060_IF1L + offset; - ret = af9015_rw_udev(udev, &req); - if (ret) - goto error; - af9015_config.mt2060_if1[i] += val; - deb_info("%s: [%d] MT2060 IF1=%d\n", __func__, i, - af9015_config.mt2060_if1[i]); - - /* tuner */ - req.addr = AF9015_EEPROM_TUNER_ID1 + offset; - ret = af9015_rw_udev(udev, &req); - if (ret) - goto error; - switch (val) { - case AF9013_TUNER_ENV77H11D5: - case AF9013_TUNER_MT2060: - case AF9013_TUNER_QT1010: - case AF9013_TUNER_UNKNOWN: - case AF9013_TUNER_MT2060_2: - case AF9013_TUNER_TDA18271: - case AF9013_TUNER_QT1010A: - case AF9013_TUNER_TDA18218: - af9015_af9013_config[i].spec_inv = 1; - break; - case AF9013_TUNER_MXL5003D: - case AF9013_TUNER_MXL5005D: - case AF9013_TUNER_MXL5005R: - case AF9013_TUNER_MXL5007T: - af9015_af9013_config[i].spec_inv = 0; - break; - case AF9013_TUNER_MC44S803: - af9015_af9013_config[i].gpio[1] = AF9013_GPIO_LO; - af9015_af9013_config[i].spec_inv = 1; - break; - default: - warn("tuner id=%d not supported, please report!", val); - return -ENODEV; - }; - - af9015_af9013_config[i].tuner = val; - deb_info("%s: [%d] tuner id=%d\n", __func__, i, val); - } - -error: - if (ret) - err("eeprom read failed=%d", ret); - - /* AverMedia AVerTV Volar Black HD (A850) device have bad EEPROM - content :-( Override some wrong values here. Ditto for the - AVerTV Red HD+ (A850T) device. */ - if (le16_to_cpu(udev->descriptor.idVendor) == USB_VID_AVERMEDIA && - ((le16_to_cpu(udev->descriptor.idProduct) == - USB_PID_AVERMEDIA_A850) || - (le16_to_cpu(udev->descriptor.idProduct) == - USB_PID_AVERMEDIA_A850T))) { - deb_info("%s: AverMedia A850: overriding config\n", __func__); - /* disable dual mode */ - af9015_config.dual_mode = 0; - /* disable 2nd adapter */ - for (i = 0; i < af9015_properties_count; i++) - af9015_properties[i].num_adapters = 1; - - /* set correct IF */ - af9015_af9013_config[0].if_frequency = 4570000; - } - - return ret; -} - -static int af9015_identify_state(struct usb_device *udev, - struct dvb_usb_device_properties *props, - struct dvb_usb_device_description **desc, - int *cold) -{ - int ret; - u8 reply; - struct req_t req = {GET_CONFIG, 0, 0, 0, 0, 1, &reply}; - - ret = af9015_rw_udev(udev, &req); - if (ret) - return ret; - - deb_info("%s: reply:%02x\n", __func__, reply); - if (reply == 0x02) - *cold = 0; - else - *cold = 1; - - return ret; -} - -static int af9015_rc_query(struct dvb_usb_device *d) -{ - struct af9015_state *priv = d->priv; - int ret; - u8 buf[17]; - - /* read registers needed to detect remote controller code */ - ret = af9015_read_regs(d, 0x98d9, buf, sizeof(buf)); - if (ret) - goto error; - - /* If any of these are non-zero, assume invalid data */ - if (buf[1] || buf[2] || buf[3]) - return ret; - - /* Check for repeat of previous code */ - if ((priv->rc_repeat != buf[6] || buf[0]) && - !memcmp(&buf[12], priv->rc_last, 4)) { - deb_rc("%s: key repeated\n", __func__); - rc_keydown(d->rc_dev, priv->rc_keycode, 0); - priv->rc_repeat = buf[6]; - return ret; - } - - /* Only process key if canary killed */ - if (buf[16] != 0xff && buf[0] != 0x01) { - deb_rc("%s: key pressed %02x %02x %02x %02x\n", __func__, - buf[12], buf[13], buf[14], buf[15]); - - /* Reset the canary */ - ret = af9015_write_reg(d, 0x98e9, 0xff); - if (ret) - goto error; - - /* Remember this key */ - memcpy(priv->rc_last, &buf[12], 4); - if (buf[14] == (u8) ~buf[15]) { - if (buf[12] == (u8) ~buf[13]) { - /* NEC */ - priv->rc_keycode = buf[12] << 8 | buf[14]; - } else { - /* NEC extended*/ - priv->rc_keycode = buf[12] << 16 | - buf[13] << 8 | buf[14]; - } - } else { - /* 32 bit NEC */ - priv->rc_keycode = buf[12] << 24 | buf[13] << 16 | - buf[14] << 8 | buf[15]; - } - rc_keydown(d->rc_dev, priv->rc_keycode, 0); - } else { - deb_rc("%s: no key press\n", __func__); - /* Invalidate last keypress */ - /* Not really needed, but helps with debug */ - priv->rc_last[2] = priv->rc_last[3]; - } - - priv->rc_repeat = buf[6]; - -error: - if (ret) - err("%s: failed:%d", __func__, ret); - - return ret; -} - -/* override demod callbacks for resource locking */ -static int af9015_af9013_set_frontend(struct dvb_frontend *fe) -{ - int ret; - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct af9015_state *priv = adap->dev->priv; - - if (mutex_lock_interruptible(&adap->dev->usb_mutex)) - return -EAGAIN; - - ret = priv->set_frontend[adap->id](fe); - - mutex_unlock(&adap->dev->usb_mutex); - - return ret; -} - -/* override demod callbacks for resource locking */ -static int af9015_af9013_read_status(struct dvb_frontend *fe, - fe_status_t *status) -{ - int ret; - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct af9015_state *priv = adap->dev->priv; - - if (mutex_lock_interruptible(&adap->dev->usb_mutex)) - return -EAGAIN; - - ret = priv->read_status[adap->id](fe, status); - - mutex_unlock(&adap->dev->usb_mutex); - - return ret; -} - -/* override demod callbacks for resource locking */ -static int af9015_af9013_init(struct dvb_frontend *fe) -{ - int ret; - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct af9015_state *priv = adap->dev->priv; - - if (mutex_lock_interruptible(&adap->dev->usb_mutex)) - return -EAGAIN; - - ret = priv->init[adap->id](fe); - - mutex_unlock(&adap->dev->usb_mutex); - - return ret; -} - -/* override demod callbacks for resource locking */ -static int af9015_af9013_sleep(struct dvb_frontend *fe) -{ - int ret; - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct af9015_state *priv = adap->dev->priv; - - if (mutex_lock_interruptible(&adap->dev->usb_mutex)) - return -EAGAIN; - - ret = priv->sleep[adap->id](fe); - - mutex_unlock(&adap->dev->usb_mutex); - - return ret; -} - -/* override tuner callbacks for resource locking */ -static int af9015_tuner_init(struct dvb_frontend *fe) -{ - int ret; - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct af9015_state *priv = adap->dev->priv; - - if (mutex_lock_interruptible(&adap->dev->usb_mutex)) - return -EAGAIN; - - ret = priv->tuner_init[adap->id](fe); - - mutex_unlock(&adap->dev->usb_mutex); - - return ret; -} - -/* override tuner callbacks for resource locking */ -static int af9015_tuner_sleep(struct dvb_frontend *fe) -{ - int ret; - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct af9015_state *priv = adap->dev->priv; - - if (mutex_lock_interruptible(&adap->dev->usb_mutex)) - return -EAGAIN; - - ret = priv->tuner_sleep[adap->id](fe); - - mutex_unlock(&adap->dev->usb_mutex); - - return ret; -} - - -static int af9015_af9013_frontend_attach(struct dvb_usb_adapter *adap) -{ - int ret; - struct af9015_state *state = adap->dev->priv; - - if (adap->id == 1) { - /* copy firmware to 2nd demodulator */ - if (af9015_config.dual_mode) { - ret = af9015_copy_firmware(adap->dev); - if (ret) { - err("firmware copy to 2nd frontend " \ - "failed, will disable it"); - af9015_config.dual_mode = 0; - return -ENODEV; - } - } else { - return -ENODEV; - } - } - - /* attach demodulator */ - adap->fe_adap[0].fe = dvb_attach(af9013_attach, - &af9015_af9013_config[adap->id], &adap->dev->i2c_adap); - - /* - * AF9015 firmware does not like if it gets interrupted by I2C adapter - * request on some critical phases. During normal operation I2C adapter - * is used only 2nd demodulator and tuner on dual tuner devices. - * Override demodulator callbacks and use mutex for limit access to - * those "critical" paths to keep AF9015 happy. - * Note: we abuse unused usb_mutex here. - */ - if (adap->fe_adap[0].fe) { - state->set_frontend[adap->id] = - adap->fe_adap[0].fe->ops.set_frontend; - adap->fe_adap[0].fe->ops.set_frontend = - af9015_af9013_set_frontend; - - state->read_status[adap->id] = - adap->fe_adap[0].fe->ops.read_status; - adap->fe_adap[0].fe->ops.read_status = - af9015_af9013_read_status; - - state->init[adap->id] = adap->fe_adap[0].fe->ops.init; - adap->fe_adap[0].fe->ops.init = af9015_af9013_init; - - state->sleep[adap->id] = adap->fe_adap[0].fe->ops.sleep; - adap->fe_adap[0].fe->ops.sleep = af9015_af9013_sleep; - } - - return adap->fe_adap[0].fe == NULL ? -ENODEV : 0; -} - -static struct mt2060_config af9015_mt2060_config = { - .i2c_address = 0xc0, - .clock_out = 0, -}; - -static struct qt1010_config af9015_qt1010_config = { - .i2c_address = 0xc4, -}; - -static struct tda18271_config af9015_tda18271_config = { - .gate = TDA18271_GATE_DIGITAL, - .small_i2c = TDA18271_16_BYTE_CHUNK_INIT, -}; - -static struct mxl5005s_config af9015_mxl5003_config = { - .i2c_address = 0xc6, - .if_freq = IF_FREQ_4570000HZ, - .xtal_freq = CRYSTAL_FREQ_16000000HZ, - .agc_mode = MXL_SINGLE_AGC, - .tracking_filter = MXL_TF_DEFAULT, - .rssi_enable = MXL_RSSI_ENABLE, - .cap_select = MXL_CAP_SEL_ENABLE, - .div_out = MXL_DIV_OUT_4, - .clock_out = MXL_CLOCK_OUT_DISABLE, - .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, - .top = MXL5005S_TOP_25P2, - .mod_mode = MXL_DIGITAL_MODE, - .if_mode = MXL_ZERO_IF, - .AgcMasterByte = 0x00, -}; - -static struct mxl5005s_config af9015_mxl5005_config = { - .i2c_address = 0xc6, - .if_freq = IF_FREQ_4570000HZ, - .xtal_freq = CRYSTAL_FREQ_16000000HZ, - .agc_mode = MXL_SINGLE_AGC, - .tracking_filter = MXL_TF_OFF, - .rssi_enable = MXL_RSSI_ENABLE, - .cap_select = MXL_CAP_SEL_ENABLE, - .div_out = MXL_DIV_OUT_4, - .clock_out = MXL_CLOCK_OUT_DISABLE, - .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, - .top = MXL5005S_TOP_25P2, - .mod_mode = MXL_DIGITAL_MODE, - .if_mode = MXL_ZERO_IF, - .AgcMasterByte = 0x00, -}; - -static struct mc44s803_config af9015_mc44s803_config = { - .i2c_address = 0xc0, - .dig_out = 1, -}; - -static struct tda18218_config af9015_tda18218_config = { - .i2c_address = 0xc0, - .i2c_wr_max = 21, /* max wr bytes AF9015 I2C adap can handle at once */ -}; - -static struct mxl5007t_config af9015_mxl5007t_config = { - .xtal_freq_hz = MxL_XTAL_24_MHZ, - .if_freq_hz = MxL_IF_4_57_MHZ, -}; - -static int af9015_tuner_attach(struct dvb_usb_adapter *adap) -{ - int ret; - struct af9015_state *state = adap->dev->priv; - deb_info("%s:\n", __func__); - - switch (af9015_af9013_config[adap->id].tuner) { - case AF9013_TUNER_MT2060: - case AF9013_TUNER_MT2060_2: - ret = dvb_attach(mt2060_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, &af9015_mt2060_config, - af9015_config.mt2060_if1[adap->id]) - == NULL ? -ENODEV : 0; - break; - case AF9013_TUNER_QT1010: - case AF9013_TUNER_QT1010A: - ret = dvb_attach(qt1010_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, - &af9015_qt1010_config) == NULL ? -ENODEV : 0; - break; - case AF9013_TUNER_TDA18271: - ret = dvb_attach(tda18271_attach, adap->fe_adap[0].fe, 0xc0, - &adap->dev->i2c_adap, - &af9015_tda18271_config) == NULL ? -ENODEV : 0; - break; - case AF9013_TUNER_TDA18218: - ret = dvb_attach(tda18218_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, - &af9015_tda18218_config) == NULL ? -ENODEV : 0; - break; - case AF9013_TUNER_MXL5003D: - ret = dvb_attach(mxl5005s_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, - &af9015_mxl5003_config) == NULL ? -ENODEV : 0; - break; - case AF9013_TUNER_MXL5005D: - case AF9013_TUNER_MXL5005R: - ret = dvb_attach(mxl5005s_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, - &af9015_mxl5005_config) == NULL ? -ENODEV : 0; - break; - case AF9013_TUNER_ENV77H11D5: - ret = dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0xc0, - &adap->dev->i2c_adap, - DVB_PLL_TDA665X) == NULL ? -ENODEV : 0; - break; - case AF9013_TUNER_MC44S803: - ret = dvb_attach(mc44s803_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, - &af9015_mc44s803_config) == NULL ? -ENODEV : 0; - break; - case AF9013_TUNER_MXL5007T: - ret = dvb_attach(mxl5007t_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, - 0xc0, &af9015_mxl5007t_config) == NULL ? -ENODEV : 0; - break; - case AF9013_TUNER_UNKNOWN: - default: - ret = -ENODEV; - err("Unknown tuner id:%d", - af9015_af9013_config[adap->id].tuner); - } - - if (adap->fe_adap[0].fe->ops.tuner_ops.init) { - state->tuner_init[adap->id] = - adap->fe_adap[0].fe->ops.tuner_ops.init; - adap->fe_adap[0].fe->ops.tuner_ops.init = af9015_tuner_init; - } - - if (adap->fe_adap[0].fe->ops.tuner_ops.sleep) { - state->tuner_sleep[adap->id] = - adap->fe_adap[0].fe->ops.tuner_ops.sleep; - adap->fe_adap[0].fe->ops.tuner_ops.sleep = af9015_tuner_sleep; - } - - return ret; -} - -enum af9015_usb_table_entry { - AFATECH_9015, - AFATECH_9016, - WINFAST_DTV_GOLD, - PINNACLE_PCTV_71E, - KWORLD_PLUSTV_399U, - TINYTWIN, - AZUREWAVE_TU700, - TERRATEC_AF9015, - KWORLD_PLUSTV_PC160, - AVERTV_VOLAR_X, - XTENSIONS_380U, - MSI_DIGIVOX_DUO, - AVERTV_VOLAR_X_REV2, - TELESTAR_STARSTICK_2, - AVERMEDIA_A309_USB, - MSI_DIGIVOX_MINI_III, - KWORLD_E396, - KWORLD_E39B, - KWORLD_E395, - TREKSTOR_DVBT, - AVERTV_A850, - AVERTV_A805, - CONCEPTRONIC_CTVDIGRCU, - KWORLD_MC810, - GENIUS_TVGO_DVB_T03, - KWORLD_399U_2, - KWORLD_PC160_T, - SVEON_STV20, - TINYTWIN_2, - WINFAST_DTV2000DS, - KWORLD_UB383_T, - KWORLD_E39A, - AVERMEDIA_A815M, - CINERGY_T_STICK_RC, - CINERGY_T_DUAL_RC, - AVERTV_A850T, - TINYTWIN_3, - SVEON_STV22, -}; - -static struct usb_device_id af9015_usb_table[] = { - [AFATECH_9015] = { - USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9015_9015)}, - [AFATECH_9016] = { - USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9015_9016)}, - [WINFAST_DTV_GOLD] = { - USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV_DONGLE_GOLD)}, - [PINNACLE_PCTV_71E] = { - USB_DEVICE(USB_VID_PINNACLE, USB_PID_PINNACLE_PCTV71E)}, - [KWORLD_PLUSTV_399U] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_399U)}, - [TINYTWIN] = { - USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_TINYTWIN)}, - [AZUREWAVE_TU700] = { - USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_AZUREWAVE_AD_TU700)}, - [TERRATEC_AF9015] = { - USB_DEVICE(USB_VID_TERRATEC, - USB_PID_TERRATEC_CINERGY_T_USB_XE_REV2)}, - [KWORLD_PLUSTV_PC160] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_PC160_2T)}, - [AVERTV_VOLAR_X] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_X)}, - [XTENSIONS_380U] = { - USB_DEVICE(USB_VID_XTENSIONS, USB_PID_XTENSIONS_XD_380)}, - [MSI_DIGIVOX_DUO] = { - USB_DEVICE(USB_VID_MSI_2, USB_PID_MSI_DIGIVOX_DUO)}, - [AVERTV_VOLAR_X_REV2] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_X_2)}, - [TELESTAR_STARSTICK_2] = { - USB_DEVICE(USB_VID_TELESTAR, USB_PID_TELESTAR_STARSTICK_2)}, - [AVERMEDIA_A309_USB] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A309)}, - [MSI_DIGIVOX_MINI_III] = { - USB_DEVICE(USB_VID_MSI_2, USB_PID_MSI_DIGI_VOX_MINI_III)}, - [KWORLD_E396] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U)}, - [KWORLD_E39B] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_2)}, - [KWORLD_E395] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_3)}, - [TREKSTOR_DVBT] = { - USB_DEVICE(USB_VID_AFATECH, USB_PID_TREKSTOR_DVBT)}, - [AVERTV_A850] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A850)}, - [AVERTV_A805] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A805)}, - [CONCEPTRONIC_CTVDIGRCU] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_CONCEPTRONIC_CTVDIGRCU)}, - [KWORLD_MC810] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_MC810)}, - [GENIUS_TVGO_DVB_T03] = { - USB_DEVICE(USB_VID_KYE, USB_PID_GENIUS_TVGO_DVB_T03)}, - [KWORLD_399U_2] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_399U_2)}, - [KWORLD_PC160_T] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_PC160_T)}, - [SVEON_STV20] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV20)}, - [TINYTWIN_2] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_TINYTWIN_2)}, - [WINFAST_DTV2000DS] = { - USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV2000DS)}, - [KWORLD_UB383_T] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_UB383_T)}, - [KWORLD_E39A] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_4)}, - [AVERMEDIA_A815M] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A815M)}, - [CINERGY_T_STICK_RC] = { - USB_DEVICE(USB_VID_TERRATEC, - USB_PID_TERRATEC_CINERGY_T_STICK_RC)}, - [CINERGY_T_DUAL_RC] = { - USB_DEVICE(USB_VID_TERRATEC, - USB_PID_TERRATEC_CINERGY_T_STICK_DUAL_RC)}, - [AVERTV_A850T] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A850T)}, - [TINYTWIN_3] = { - USB_DEVICE(USB_VID_GTEK, USB_PID_TINYTWIN_3)}, - [SVEON_STV22] = { - USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV22)}, - { } -}; -MODULE_DEVICE_TABLE(usb, af9015_usb_table); - -#define AF9015_RC_INTERVAL 500 -static struct dvb_usb_device_properties af9015_properties[] = { - { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - .download_firmware = af9015_download_firmware, - .firmware = "dvb-usb-af9015.fw", - .no_reconnect = 1, - - .size_of_priv = sizeof(struct af9015_state), - - .num_adapters = 2, - .adapter = { - { - .num_frontends = 1, - .fe = { - { - .caps = DVB_USB_ADAP_HAS_PID_FILTER | - DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, - - .pid_filter_count = 32, - .pid_filter = af9015_pid_filter, - .pid_filter_ctrl = af9015_pid_filter_ctrl, - - .frontend_attach = af9015_af9013_frontend_attach, - .tuner_attach = af9015_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x84, - }, - } - }, - }, - { - .num_frontends = 1, - .fe = { - { - .frontend_attach = af9015_af9013_frontend_attach, - .tuner_attach = af9015_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x85, - .u = { - .bulk = { - .buffersize = TS_USB20_FRAME_SIZE, - } - } - }, - } - }, - } - }, - - .identify_state = af9015_identify_state, - - .rc.core = { - .protocol = RC_TYPE_NEC, - .module_name = "af9015", - .rc_query = af9015_rc_query, - .rc_interval = AF9015_RC_INTERVAL, - .allowed_protos = RC_TYPE_NEC, - }, - - .i2c_algo = &af9015_i2c_algo, - - .num_device_descs = 12, /* check max from dvb-usb.h */ - .devices = { - { - .name = "Afatech AF9015 DVB-T USB2.0 stick", - .cold_ids = { - &af9015_usb_table[AFATECH_9015], - &af9015_usb_table[AFATECH_9016], - }, - }, { - .name = "Leadtek WinFast DTV Dongle Gold", - .cold_ids = { - &af9015_usb_table[WINFAST_DTV_GOLD], - }, - }, { - .name = "Pinnacle PCTV 71e", - .cold_ids = { - &af9015_usb_table[PINNACLE_PCTV_71E], - }, - }, { - .name = "KWorld PlusTV Dual DVB-T Stick " \ - "(DVB-T 399U)", - .cold_ids = { - &af9015_usb_table[KWORLD_PLUSTV_399U], - &af9015_usb_table[KWORLD_399U_2], - }, - }, { - .name = "DigitalNow TinyTwin DVB-T Receiver", - .cold_ids = { - &af9015_usb_table[TINYTWIN], - &af9015_usb_table[TINYTWIN_2], - &af9015_usb_table[TINYTWIN_3], - }, - }, { - .name = "TwinHan AzureWave AD-TU700(704J)", - .cold_ids = { - &af9015_usb_table[AZUREWAVE_TU700], - }, - }, { - .name = "TerraTec Cinergy T USB XE", - .cold_ids = { - &af9015_usb_table[TERRATEC_AF9015], - }, - }, { - .name = "KWorld PlusTV Dual DVB-T PCI " \ - "(DVB-T PC160-2T)", - .cold_ids = { - &af9015_usb_table[KWORLD_PLUSTV_PC160], - }, - }, { - .name = "AVerMedia AVerTV DVB-T Volar X", - .cold_ids = { - &af9015_usb_table[AVERTV_VOLAR_X], - }, - }, { - .name = "TerraTec Cinergy T Stick RC", - .cold_ids = { - &af9015_usb_table[CINERGY_T_STICK_RC], - }, - }, { - .name = "TerraTec Cinergy T Stick Dual RC", - .cold_ids = { - &af9015_usb_table[CINERGY_T_DUAL_RC], - }, - }, { - .name = "AverMedia AVerTV Red HD+ (A850T)", - .cold_ids = { - &af9015_usb_table[AVERTV_A850T], - }, - }, - } - }, { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - .download_firmware = af9015_download_firmware, - .firmware = "dvb-usb-af9015.fw", - .no_reconnect = 1, - - .size_of_priv = sizeof(struct af9015_state), - - .num_adapters = 2, - .adapter = { - { - .num_frontends = 1, - .fe = { - { - .caps = DVB_USB_ADAP_HAS_PID_FILTER | - DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, - - .pid_filter_count = 32, - .pid_filter = af9015_pid_filter, - .pid_filter_ctrl = af9015_pid_filter_ctrl, - - .frontend_attach = af9015_af9013_frontend_attach, - .tuner_attach = af9015_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x84, - }, - } - }, - }, - { - .num_frontends = 1, - .fe = { - { - .frontend_attach = af9015_af9013_frontend_attach, - .tuner_attach = af9015_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x85, - .u = { - .bulk = { - .buffersize = TS_USB20_FRAME_SIZE, - } - } - }, - } - }, - } - }, - - .identify_state = af9015_identify_state, - - .rc.core = { - .protocol = RC_TYPE_NEC, - .module_name = "af9015", - .rc_query = af9015_rc_query, - .rc_interval = AF9015_RC_INTERVAL, - .allowed_protos = RC_TYPE_NEC, - }, - - .i2c_algo = &af9015_i2c_algo, - - .num_device_descs = 10, /* check max from dvb-usb.h */ - .devices = { - { - .name = "Xtensions XD-380", - .cold_ids = { - &af9015_usb_table[XTENSIONS_380U], - }, - }, { - .name = "MSI DIGIVOX Duo", - .cold_ids = { - &af9015_usb_table[MSI_DIGIVOX_DUO], - }, - }, { - .name = "Fujitsu-Siemens Slim Mobile USB DVB-T", - .cold_ids = { - &af9015_usb_table[AVERTV_VOLAR_X_REV2], - }, - }, { - .name = "Telestar Starstick 2", - .cold_ids = { - &af9015_usb_table[TELESTAR_STARSTICK_2], - }, - }, { - .name = "AVerMedia A309", - .cold_ids = { - &af9015_usb_table[AVERMEDIA_A309_USB], - }, - }, { - .name = "MSI Digi VOX mini III", - .cold_ids = { - &af9015_usb_table[MSI_DIGIVOX_MINI_III], - }, - }, { - .name = "KWorld USB DVB-T TV Stick II " \ - "(VS-DVB-T 395U)", - .cold_ids = { - &af9015_usb_table[KWORLD_E396], - &af9015_usb_table[KWORLD_E39B], - &af9015_usb_table[KWORLD_E395], - &af9015_usb_table[KWORLD_E39A], - }, - }, { - .name = "TrekStor DVB-T USB Stick", - .cold_ids = { - &af9015_usb_table[TREKSTOR_DVBT], - }, - }, { - .name = "AverMedia AVerTV Volar Black HD " \ - "(A850)", - .cold_ids = { - &af9015_usb_table[AVERTV_A850], - }, - }, { - .name = "Sveon STV22 Dual USB DVB-T Tuner HDTV", - .cold_ids = { - &af9015_usb_table[SVEON_STV22], - }, - }, - } - }, { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - .download_firmware = af9015_download_firmware, - .firmware = "dvb-usb-af9015.fw", - .no_reconnect = 1, - - .size_of_priv = sizeof(struct af9015_state), - - .num_adapters = 2, - .adapter = { - { - .num_frontends = 1, - .fe = { - { - .caps = DVB_USB_ADAP_HAS_PID_FILTER | - DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, - - .pid_filter_count = 32, - .pid_filter = af9015_pid_filter, - .pid_filter_ctrl = af9015_pid_filter_ctrl, - - .frontend_attach = af9015_af9013_frontend_attach, - .tuner_attach = af9015_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x84, - }, - } - }, - }, - { - .num_frontends = 1, - .fe = { - { - .frontend_attach = af9015_af9013_frontend_attach, - .tuner_attach = af9015_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x85, - .u = { - .bulk = { - .buffersize = TS_USB20_FRAME_SIZE, - } - } - }, - } - }, - } - }, - - .identify_state = af9015_identify_state, - - .rc.core = { - .protocol = RC_TYPE_NEC, - .module_name = "af9015", - .rc_query = af9015_rc_query, - .rc_interval = AF9015_RC_INTERVAL, - .allowed_protos = RC_TYPE_NEC, - }, - - .i2c_algo = &af9015_i2c_algo, - - .num_device_descs = 9, /* check max from dvb-usb.h */ - .devices = { - { - .name = "AverMedia AVerTV Volar GPS 805 (A805)", - .cold_ids = { - &af9015_usb_table[AVERTV_A805], - }, - }, { - .name = "Conceptronic USB2.0 DVB-T CTVDIGRCU " \ - "V3.0", - .cold_ids = { - &af9015_usb_table[CONCEPTRONIC_CTVDIGRCU], - }, - }, { - .name = "KWorld Digial MC-810", - .cold_ids = { - &af9015_usb_table[KWORLD_MC810], - }, - }, { - .name = "Genius TVGo DVB-T03", - .cold_ids = { - &af9015_usb_table[GENIUS_TVGO_DVB_T03], - }, - }, { - .name = "KWorld PlusTV DVB-T PCI Pro Card " \ - "(DVB-T PC160-T)", - .cold_ids = { - &af9015_usb_table[KWORLD_PC160_T], - }, - }, { - .name = "Sveon STV20 Tuner USB DVB-T HDTV", - .cold_ids = { - &af9015_usb_table[SVEON_STV20], - }, - }, { - .name = "Leadtek WinFast DTV2000DS", - .cold_ids = { - &af9015_usb_table[WINFAST_DTV2000DS], - }, - }, { - .name = "KWorld USB DVB-T Stick Mobile " \ - "(UB383-T)", - .cold_ids = { - &af9015_usb_table[KWORLD_UB383_T], - }, - }, { - .name = "AverMedia AVerTV Volar M (A815Mac)", - .cold_ids = { - &af9015_usb_table[AVERMEDIA_A815M], - }, - }, - } - }, -}; - -static int af9015_usb_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - int ret = 0; - struct dvb_usb_device *d = NULL; - struct usb_device *udev = interface_to_usbdev(intf); - u8 i; - - deb_info("%s: interface:%d\n", __func__, - intf->cur_altsetting->desc.bInterfaceNumber); - - /* interface 0 is used by DVB-T receiver and - interface 1 is for remote controller (HID) */ - if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { - ret = af9015_read_config(udev); - if (ret) - return ret; - - for (i = 0; i < af9015_properties_count; i++) { - ret = dvb_usb_device_init(intf, &af9015_properties[i], - THIS_MODULE, &d, adapter_nr); - if (!ret) - break; - if (ret != -ENODEV) - return ret; - } - if (ret) - return ret; - - if (d) - ret = af9015_init(d); - } - - return ret; -} - -/* usb specific object needed to register this driver with the usb subsystem */ -static struct usb_driver af9015_usb_driver = { - .name = "dvb_usb_af9015", - .probe = af9015_usb_probe, - .disconnect = dvb_usb_device_exit, - .id_table = af9015_usb_table, -}; - -module_usb_driver(af9015_usb_driver); - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Afatech AF9015 driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/af9015.h b/drivers/media/dvb/dvb-usb/af9015.h deleted file mode 100644 index 2f68419e899b..000000000000 --- a/drivers/media/dvb/dvb-usb/af9015.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * DVB USB Linux driver for Afatech AF9015 DVB-T USB2.0 receiver - * - * Copyright (C) 2007 Antti Palosaari - * - * Thanks to Afatech who kindly provided information. - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _DVB_USB_AF9015_H_ -#define _DVB_USB_AF9015_H_ - -#define DVB_USB_LOG_PREFIX "af9015" -#include "dvb-usb.h" - -#define deb_info(args...) dprintk(dvb_usb_af9015_debug, 0x01, args) -#define deb_rc(args...) dprintk(dvb_usb_af9015_debug, 0x02, args) -#define deb_xfer(args...) dprintk(dvb_usb_af9015_debug, 0x04, args) -#define deb_reg(args...) dprintk(dvb_usb_af9015_debug, 0x08, args) -#define deb_i2c(args...) dprintk(dvb_usb_af9015_debug, 0x10, args) -#define deb_fw(args...) dprintk(dvb_usb_af9015_debug, 0x20, args) - -#define AF9015_I2C_EEPROM 0xa0 -#define AF9015_I2C_DEMOD 0x38 -#define AF9015_USB_TIMEOUT 2000 - -/* EEPROM locations */ -#define AF9015_EEPROM_IR_MODE 0x18 -#define AF9015_EEPROM_IR_REMOTE_TYPE 0x34 -#define AF9015_EEPROM_TS_MODE 0x31 -#define AF9015_EEPROM_DEMOD2_I2C 0x32 - -#define AF9015_EEPROM_SAW_BW1 0x35 -#define AF9015_EEPROM_XTAL_TYPE1 0x36 -#define AF9015_EEPROM_SPEC_INV1 0x37 -#define AF9015_EEPROM_IF1L 0x38 -#define AF9015_EEPROM_IF1H 0x39 -#define AF9015_EEPROM_MT2060_IF1L 0x3a -#define AF9015_EEPROM_MT2060_IF1H 0x3b -#define AF9015_EEPROM_TUNER_ID1 0x3c - -#define AF9015_EEPROM_SAW_BW2 0x45 -#define AF9015_EEPROM_XTAL_TYPE2 0x46 -#define AF9015_EEPROM_SPEC_INV2 0x47 -#define AF9015_EEPROM_IF2L 0x48 -#define AF9015_EEPROM_IF2H 0x49 -#define AF9015_EEPROM_MT2060_IF2L 0x4a -#define AF9015_EEPROM_MT2060_IF2H 0x4b -#define AF9015_EEPROM_TUNER_ID2 0x4c - -#define AF9015_EEPROM_OFFSET (AF9015_EEPROM_SAW_BW2 - AF9015_EEPROM_SAW_BW1) - -struct req_t { - u8 cmd; /* [0] */ - /* seq */ /* [1] */ - u8 i2c_addr; /* [2] */ - u16 addr; /* [3|4] */ - u8 mbox; /* [5] */ - u8 addr_len; /* [6] */ - u8 data_len; /* [7] */ - u8 *data; -}; - -enum af9015_cmd { - GET_CONFIG = 0x10, - DOWNLOAD_FIRMWARE = 0x11, - BOOT = 0x13, - READ_MEMORY = 0x20, - WRITE_MEMORY = 0x21, - READ_WRITE_I2C = 0x22, - COPY_FIRMWARE = 0x23, - RECONNECT_USB = 0x5a, - WRITE_VIRTUAL_MEMORY = 0x26, - GET_IR_CODE = 0x27, - READ_I2C, - WRITE_I2C, -}; - -enum af9015_ir_mode { - AF9015_IR_MODE_DISABLED = 0, - AF9015_IR_MODE_HID, - AF9015_IR_MODE_RLC, - AF9015_IR_MODE_RC6, - AF9015_IR_MODE_POLLING, /* just guess */ -}; - -struct af9015_state { - u8 rc_repeat; - u32 rc_keycode; - u8 rc_last[4]; - - /* for demod callback override */ - int (*set_frontend[2]) (struct dvb_frontend *fe); - int (*read_status[2]) (struct dvb_frontend *fe, fe_status_t *status); - int (*init[2]) (struct dvb_frontend *fe); - int (*sleep[2]) (struct dvb_frontend *fe); - int (*tuner_init[2]) (struct dvb_frontend *fe); - int (*tuner_sleep[2]) (struct dvb_frontend *fe); -}; - -struct af9015_config { - u8 dual_mode:1; - u16 mt2060_if1[2]; - u16 firmware_size; - u16 firmware_checksum; - u32 eeprom_sum; -}; - -enum af9015_remote { - AF9015_REMOTE_NONE = 0, -/* 1 */ AF9015_REMOTE_A_LINK_DTU_M, - AF9015_REMOTE_MSI_DIGIVOX_MINI_II_V3, - AF9015_REMOTE_MYGICTV_U718, - AF9015_REMOTE_DIGITTRADE_DVB_T, -/* 5 */ AF9015_REMOTE_AVERMEDIA_KS, -}; - -#endif diff --git a/drivers/media/dvb/dvb-usb/af9035.c b/drivers/media/dvb/dvb-usb/af9035.c deleted file mode 100644 index e83b39d3993c..000000000000 --- a/drivers/media/dvb/dvb-usb/af9035.c +++ /dev/null @@ -1,1242 +0,0 @@ -/* - * Afatech AF9035 DVB USB driver - * - * Copyright (C) 2009 Antti Palosaari - * Copyright (C) 2012 Antti Palosaari - * - * 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. - */ - -#include "af9035.h" - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); -static DEFINE_MUTEX(af9035_usb_mutex); -static struct dvb_usb_device_properties af9035_properties[2]; -static int af9035_properties_count = ARRAY_SIZE(af9035_properties); - -static u16 af9035_checksum(const u8 *buf, size_t len) -{ - size_t i; - u16 checksum = 0; - - for (i = 1; i < len; i++) { - if (i % 2) - checksum += buf[i] << 8; - else - checksum += buf[i]; - } - checksum = ~checksum; - - return checksum; -} - -static int af9035_ctrl_msg(struct usb_device *udev, struct usb_req *req) -{ -#define BUF_LEN 64 -#define REQ_HDR_LEN 4 /* send header size */ -#define ACK_HDR_LEN 3 /* rece header size */ -#define CHECKSUM_LEN 2 -#define USB_TIMEOUT 2000 - - int ret, msg_len, act_len; - u8 buf[BUF_LEN]; - static u8 seq; /* packet sequence number */ - u16 checksum, tmp_checksum; - - /* buffer overflow check */ - if (req->wlen > (BUF_LEN - REQ_HDR_LEN - CHECKSUM_LEN) || - req->rlen > (BUF_LEN - ACK_HDR_LEN - CHECKSUM_LEN)) { - pr_debug("%s: too much data wlen=%d rlen=%d\n", __func__, - req->wlen, req->rlen); - return -EINVAL; - } - - if (mutex_lock_interruptible(&af9035_usb_mutex) < 0) - return -EAGAIN; - - buf[0] = REQ_HDR_LEN + req->wlen + CHECKSUM_LEN - 1; - buf[1] = req->mbox; - buf[2] = req->cmd; - buf[3] = seq++; - if (req->wlen) - memcpy(&buf[4], req->wbuf, req->wlen); - - /* calc and add checksum */ - checksum = af9035_checksum(buf, buf[0] - 1); - buf[buf[0] - 1] = (checksum >> 8); - buf[buf[0] - 0] = (checksum & 0xff); - - msg_len = REQ_HDR_LEN + req->wlen + CHECKSUM_LEN ; - - /* send req */ - ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, 0x02), buf, msg_len, - &act_len, USB_TIMEOUT); - if (ret < 0) - err("bulk message failed=%d (%d/%d)", ret, msg_len, act_len); - else - if (act_len != msg_len) - ret = -EIO; /* all data is not send */ - if (ret < 0) - goto err_mutex_unlock; - - /* no ack for those packets */ - if (req->cmd == CMD_FW_DL) - goto exit_mutex_unlock; - - /* receive ack and data if read req */ - msg_len = ACK_HDR_LEN + req->rlen + CHECKSUM_LEN; - ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, 0x81), buf, msg_len, - &act_len, USB_TIMEOUT); - if (ret < 0) { - err("recv bulk message failed=%d", ret); - ret = -EIO; - goto err_mutex_unlock; - } - - if (act_len != msg_len) { - err("recv bulk message truncated (%d != %d)", act_len, msg_len); - ret = -EIO; - goto err_mutex_unlock; - } - - /* verify checksum */ - checksum = af9035_checksum(buf, act_len - 2); - tmp_checksum = (buf[act_len - 2] << 8) | buf[act_len - 1]; - if (tmp_checksum != checksum) { - err("%s: command=%02x checksum mismatch (%04x != %04x)", - __func__, req->cmd, tmp_checksum, checksum); - ret = -EIO; - goto err_mutex_unlock; - } - - /* check status */ - if (buf[2]) { - pr_debug("%s: command=%02x failed fw error=%d\n", __func__, - req->cmd, buf[2]); - ret = -EIO; - goto err_mutex_unlock; - } - - /* read request, copy returned data to return buf */ - if (req->rlen) - memcpy(req->rbuf, &buf[ACK_HDR_LEN], req->rlen); - -err_mutex_unlock: -exit_mutex_unlock: - mutex_unlock(&af9035_usb_mutex); - - return ret; -} - -/* write multiple registers */ -static int af9035_wr_regs(struct dvb_usb_device *d, u32 reg, u8 *val, int len) -{ - u8 wbuf[6 + len]; - u8 mbox = (reg >> 16) & 0xff; - struct usb_req req = { CMD_MEM_WR, mbox, sizeof(wbuf), wbuf, 0, NULL }; - - wbuf[0] = len; - wbuf[1] = 2; - wbuf[2] = 0; - wbuf[3] = 0; - wbuf[4] = (reg >> 8) & 0xff; - wbuf[5] = (reg >> 0) & 0xff; - memcpy(&wbuf[6], val, len); - - return af9035_ctrl_msg(d->udev, &req); -} - -/* read multiple registers */ -static int af9035_rd_regs(struct dvb_usb_device *d, u32 reg, u8 *val, int len) -{ - u8 wbuf[] = { len, 2, 0, 0, (reg >> 8) & 0xff, reg & 0xff }; - u8 mbox = (reg >> 16) & 0xff; - struct usb_req req = { CMD_MEM_RD, mbox, sizeof(wbuf), wbuf, len, val }; - - return af9035_ctrl_msg(d->udev, &req); -} - -/* write single register */ -static int af9035_wr_reg(struct dvb_usb_device *d, u32 reg, u8 val) -{ - return af9035_wr_regs(d, reg, &val, 1); -} - -/* read single register */ -static int af9035_rd_reg(struct dvb_usb_device *d, u32 reg, u8 *val) -{ - return af9035_rd_regs(d, reg, val, 1); -} - -/* write single register with mask */ -static int af9035_wr_reg_mask(struct dvb_usb_device *d, u32 reg, u8 val, - u8 mask) -{ - int ret; - u8 tmp; - - /* no need for read if whole reg is written */ - if (mask != 0xff) { - ret = af9035_rd_regs(d, reg, &tmp, 1); - if (ret) - return ret; - - val &= mask; - tmp &= ~mask; - val |= tmp; - } - - return af9035_wr_regs(d, reg, &val, 1); -} - -static int af9035_i2c_master_xfer(struct i2c_adapter *adap, - struct i2c_msg msg[], int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - struct state *state = d->priv; - int ret; - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - /* - * I2C sub header is 5 bytes long. Meaning of those bytes are: - * 0: data len - * 1: I2C addr << 1 - * 2: reg addr len - * byte 3 and 4 can be used as reg addr - * 3: reg addr MSB - * used when reg addr len is set to 2 - * 4: reg addr LSB - * used when reg addr len is set to 1 or 2 - * - * For the simplify we do not use register addr at all. - * NOTE: As a firmware knows tuner type there is very small possibility - * there could be some tuner I2C hacks done by firmware and this may - * lead problems if firmware expects those bytes are used. - */ - if (num == 2 && !(msg[0].flags & I2C_M_RD) && - (msg[1].flags & I2C_M_RD)) { - if (msg[0].len > 40 || msg[1].len > 40) { - /* TODO: correct limits > 40 */ - ret = -EOPNOTSUPP; - } else if (msg[0].addr == state->af9033_config[0].i2c_addr) { - /* integrated demod */ - u32 reg = msg[0].buf[0] << 16 | msg[0].buf[1] << 8 | - msg[0].buf[2]; - ret = af9035_rd_regs(d, reg, &msg[1].buf[0], - msg[1].len); - } else { - /* I2C */ - u8 buf[5 + msg[0].len]; - struct usb_req req = { CMD_I2C_RD, 0, sizeof(buf), - buf, msg[1].len, msg[1].buf }; - buf[0] = msg[1].len; - buf[1] = msg[0].addr << 1; - buf[2] = 0x00; /* reg addr len */ - buf[3] = 0x00; /* reg addr MSB */ - buf[4] = 0x00; /* reg addr LSB */ - memcpy(&buf[5], msg[0].buf, msg[0].len); - ret = af9035_ctrl_msg(d->udev, &req); - } - } else if (num == 1 && !(msg[0].flags & I2C_M_RD)) { - if (msg[0].len > 40) { - /* TODO: correct limits > 40 */ - ret = -EOPNOTSUPP; - } else if (msg[0].addr == state->af9033_config[0].i2c_addr) { - /* integrated demod */ - u32 reg = msg[0].buf[0] << 16 | msg[0].buf[1] << 8 | - msg[0].buf[2]; - ret = af9035_wr_regs(d, reg, &msg[0].buf[3], - msg[0].len - 3); - } else { - /* I2C */ - u8 buf[5 + msg[0].len]; - struct usb_req req = { CMD_I2C_WR, 0, sizeof(buf), buf, - 0, NULL }; - buf[0] = msg[0].len; - buf[1] = msg[0].addr << 1; - buf[2] = 0x00; /* reg addr len */ - buf[3] = 0x00; /* reg addr MSB */ - buf[4] = 0x00; /* reg addr LSB */ - memcpy(&buf[5], msg[0].buf, msg[0].len); - ret = af9035_ctrl_msg(d->udev, &req); - } - } else { - /* - * We support only two kind of I2C transactions: - * 1) 1 x read + 1 x write - * 2) 1 x write - */ - ret = -EOPNOTSUPP; - } - - mutex_unlock(&d->i2c_mutex); - - if (ret < 0) - return ret; - else - return num; -} - -static u32 af9035_i2c_functionality(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm af9035_i2c_algo = { - .master_xfer = af9035_i2c_master_xfer, - .functionality = af9035_i2c_functionality, -}; - -#define AF9035_POLL 250 -static int af9035_rc_query(struct dvb_usb_device *d) -{ - unsigned int key; - unsigned char b[4]; - int ret; - struct usb_req req = { CMD_IR_GET, 0, 0, NULL, 4, b }; - - ret = af9035_ctrl_msg(d->udev, &req); - if (ret < 0) - goto err; - - if ((b[2] + b[3]) == 0xff) { - if ((b[0] + b[1]) == 0xff) { - /* NEC */ - key = b[0] << 8 | b[2]; - } else { - /* ext. NEC */ - key = b[0] << 16 | b[1] << 8 | b[2]; - } - } else { - key = b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; - } - - rc_keydown(d->rc_dev, key, 0); - -err: - /* ignore errors */ - return 0; -} - -static int af9035_init(struct dvb_usb_device *d) -{ - struct state *state = d->priv; - int ret, i; - u16 frame_size = 87 * 188 / 4; - u8 packet_size = 512 / 4; - struct reg_val_mask tab[] = { - { 0x80f99d, 0x01, 0x01 }, - { 0x80f9a4, 0x01, 0x01 }, - { 0x00dd11, 0x00, 0x20 }, - { 0x00dd11, 0x00, 0x40 }, - { 0x00dd13, 0x00, 0x20 }, - { 0x00dd13, 0x00, 0x40 }, - { 0x00dd11, 0x20, 0x20 }, - { 0x00dd88, (frame_size >> 0) & 0xff, 0xff}, - { 0x00dd89, (frame_size >> 8) & 0xff, 0xff}, - { 0x00dd0c, packet_size, 0xff}, - { 0x00dd11, state->dual_mode << 6, 0x40 }, - { 0x00dd8a, (frame_size >> 0) & 0xff, 0xff}, - { 0x00dd8b, (frame_size >> 8) & 0xff, 0xff}, - { 0x00dd0d, packet_size, 0xff }, - { 0x80f9a3, 0x00, 0x01 }, - { 0x80f9cd, 0x00, 0x01 }, - { 0x80f99d, 0x00, 0x01 }, - { 0x80f9a4, 0x00, 0x01 }, - }; - - pr_debug("%s: USB speed=%d frame_size=%04x packet_size=%02x\n", - __func__, d->udev->speed, frame_size, packet_size); - - /* init endpoints */ - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = af9035_wr_reg_mask(d, tab[i].reg, tab[i].val, - tab[i].mask); - if (ret < 0) - goto err; - } - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9035_identify_state(struct usb_device *udev, - struct dvb_usb_device_properties *props, - struct dvb_usb_device_description **desc, - int *cold) -{ - int ret; - u8 wbuf[1] = { 1 }; - u8 rbuf[4]; - struct usb_req req = { CMD_FW_QUERYINFO, 0, sizeof(wbuf), wbuf, - sizeof(rbuf), rbuf }; - - ret = af9035_ctrl_msg(udev, &req); - if (ret < 0) - goto err; - - pr_debug("%s: reply=%02x %02x %02x %02x\n", __func__, - rbuf[0], rbuf[1], rbuf[2], rbuf[3]); - if (rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3]) - *cold = 0; - else - *cold = 1; - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9035_download_firmware(struct usb_device *udev, - const struct firmware *fw) -{ - int ret, i, j, len; - u8 wbuf[1]; - u8 rbuf[4]; - struct usb_req req = { 0, 0, 0, NULL, 0, NULL }; - struct usb_req req_fw_dl = { CMD_FW_DL, 0, 0, wbuf, 0, NULL }; - struct usb_req req_fw_ver = { CMD_FW_QUERYINFO, 0, 1, wbuf, 4, rbuf } ; - u8 hdr_core; - u16 hdr_addr, hdr_data_len, hdr_checksum; - #define MAX_DATA 58 - #define HDR_SIZE 7 - - /* - * Thanks to Daniel Glöckner about that info! - * - * byte 0: MCS 51 core - * There are two inside the AF9035 (1=Link and 2=OFDM) with separate - * address spaces - * byte 1-2: Big endian destination address - * byte 3-4: Big endian number of data bytes following the header - * byte 5-6: Big endian header checksum, apparently ignored by the chip - * Calculated as ~(h[0]*256+h[1]+h[2]*256+h[3]+h[4]*256) - */ - - for (i = fw->size; i > HDR_SIZE;) { - hdr_core = fw->data[fw->size - i + 0]; - hdr_addr = fw->data[fw->size - i + 1] << 8; - hdr_addr |= fw->data[fw->size - i + 2] << 0; - hdr_data_len = fw->data[fw->size - i + 3] << 8; - hdr_data_len |= fw->data[fw->size - i + 4] << 0; - hdr_checksum = fw->data[fw->size - i + 5] << 8; - hdr_checksum |= fw->data[fw->size - i + 6] << 0; - - pr_debug("%s: core=%d addr=%04x data_len=%d checksum=%04x\n", - __func__, hdr_core, hdr_addr, hdr_data_len, - hdr_checksum); - - if (((hdr_core != 1) && (hdr_core != 2)) || - (hdr_data_len > i)) { - pr_debug("%s: bad firmware\n", __func__); - break; - } - - /* download begin packet */ - req.cmd = CMD_FW_DL_BEGIN; - ret = af9035_ctrl_msg(udev, &req); - if (ret < 0) - goto err; - - /* download firmware packet(s) */ - for (j = HDR_SIZE + hdr_data_len; j > 0; j -= MAX_DATA) { - len = j; - if (len > MAX_DATA) - len = MAX_DATA; - req_fw_dl.wlen = len; - req_fw_dl.wbuf = (u8 *) &fw->data[fw->size - i + - HDR_SIZE + hdr_data_len - j]; - ret = af9035_ctrl_msg(udev, &req_fw_dl); - if (ret < 0) - goto err; - } - - /* download end packet */ - req.cmd = CMD_FW_DL_END; - ret = af9035_ctrl_msg(udev, &req); - if (ret < 0) - goto err; - - i -= hdr_data_len + HDR_SIZE; - - pr_debug("%s: data uploaded=%zu\n", __func__, fw->size - i); - } - - /* firmware loaded, request boot */ - req.cmd = CMD_FW_BOOT; - ret = af9035_ctrl_msg(udev, &req); - if (ret < 0) - goto err; - - /* ensure firmware starts */ - wbuf[0] = 1; - ret = af9035_ctrl_msg(udev, &req_fw_ver); - if (ret < 0) - goto err; - - if (!(rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3])) { - info("firmware did not run"); - ret = -ENODEV; - goto err; - } - - info("firmware version=%d.%d.%d.%d", rbuf[0], rbuf[1], rbuf[2], - rbuf[3]); - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9035_download_firmware_it9135(struct usb_device *udev, - const struct firmware *fw) -{ - int ret, i, i_prev; - u8 wbuf[1]; - u8 rbuf[4]; - struct usb_req req = { 0, 0, 0, NULL, 0, NULL }; - struct usb_req req_fw_dl = { CMD_FW_SCATTER_WR, 0, 0, NULL, 0, NULL }; - struct usb_req req_fw_ver = { CMD_FW_QUERYINFO, 0, 1, wbuf, 4, rbuf } ; - #define HDR_SIZE 7 - - /* - * There seems to be following firmware header. Meaning of bytes 0-3 - * is unknown. - * - * 0: 3 - * 1: 0, 1 - * 2: 0 - * 3: 1, 2, 3 - * 4: addr MSB - * 5: addr LSB - * 6: count of data bytes ? - */ - - for (i = HDR_SIZE, i_prev = 0; i <= fw->size; i++) { - if (i == fw->size || - (fw->data[i + 0] == 0x03 && - (fw->data[i + 1] == 0x00 || - fw->data[i + 1] == 0x01) && - fw->data[i + 2] == 0x00)) { - req_fw_dl.wlen = i - i_prev; - req_fw_dl.wbuf = (u8 *) &fw->data[i_prev]; - i_prev = i; - ret = af9035_ctrl_msg(udev, &req_fw_dl); - if (ret < 0) - goto err; - - pr_debug("%s: data uploaded=%d\n", __func__, i); - } - } - - /* firmware loaded, request boot */ - req.cmd = CMD_FW_BOOT; - ret = af9035_ctrl_msg(udev, &req); - if (ret < 0) - goto err; - - /* ensure firmware starts */ - wbuf[0] = 1; - ret = af9035_ctrl_msg(udev, &req_fw_ver); - if (ret < 0) - goto err; - - if (!(rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3])) { - info("firmware did not run"); - ret = -ENODEV; - goto err; - } - - info("firmware version=%d.%d.%d.%d", rbuf[0], rbuf[1], rbuf[2], - rbuf[3]); - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -/* abuse that callback as there is no better one for reading eeprom */ -static int af9035_read_mac_address(struct dvb_usb_device *d, u8 mac[6]) -{ - struct state *state = d->priv; - int ret, i, eeprom_shift = 0; - u8 tmp; - u16 tmp16; - - /* check if there is dual tuners */ - ret = af9035_rd_reg(d, EEPROM_DUAL_MODE, &tmp); - if (ret < 0) - goto err; - - state->dual_mode = tmp; - pr_debug("%s: dual mode=%d\n", __func__, state->dual_mode); - - for (i = 0; i < af9035_properties[0].num_adapters; i++) { - /* tuner */ - ret = af9035_rd_reg(d, EEPROM_1_TUNER_ID + eeprom_shift, &tmp); - if (ret < 0) - goto err; - - state->af9033_config[i].tuner = tmp; - pr_debug("%s: [%d]tuner=%02x\n", __func__, i, tmp); - - switch (tmp) { - case AF9033_TUNER_TUA9001: - case AF9033_TUNER_FC0011: - case AF9033_TUNER_MXL5007T: - case AF9033_TUNER_TDA18218: - state->af9033_config[i].spec_inv = 1; - break; - default: - warn("tuner ID=%02x not supported, please report!", - tmp); - }; - - /* tuner IF frequency */ - ret = af9035_rd_reg(d, EEPROM_1_IFFREQ_L + eeprom_shift, &tmp); - if (ret < 0) - goto err; - - tmp16 = tmp; - - ret = af9035_rd_reg(d, EEPROM_1_IFFREQ_H + eeprom_shift, &tmp); - if (ret < 0) - goto err; - - tmp16 |= tmp << 8; - - pr_debug("%s: [%d]IF=%d\n", __func__, i, tmp16); - - eeprom_shift = 0x10; /* shift for the 2nd tuner params */ - } - - /* get demod clock */ - ret = af9035_rd_reg(d, 0x00d800, &tmp); - if (ret < 0) - goto err; - - tmp = (tmp >> 0) & 0x0f; - - for (i = 0; i < af9035_properties[0].num_adapters; i++) - state->af9033_config[i].clock = clock_lut[tmp]; - - ret = af9035_rd_reg(d, EEPROM_IR_MODE, &tmp); - if (ret < 0) - goto err; - pr_debug("%s: ir_mode=%02x\n", __func__, tmp); - - /* don't activate rc if in HID mode or if not available */ - if (tmp == 5) { - ret = af9035_rd_reg(d, EEPROM_IR_TYPE, &tmp); - if (ret < 0) - goto err; - pr_debug("%s: ir_type=%02x\n", __func__, tmp); - - switch (tmp) { - case 0: /* NEC */ - default: - d->props.rc.core.protocol = RC_TYPE_NEC; - d->props.rc.core.allowed_protos = RC_TYPE_NEC; - break; - case 1: /* RC6 */ - d->props.rc.core.protocol = RC_TYPE_RC6; - d->props.rc.core.allowed_protos = RC_TYPE_RC6; - break; - } - d->props.rc.core.rc_query = af9035_rc_query; - } - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -/* abuse that callback as there is no better one for reading eeprom */ -static int af9035_read_mac_address_it9135(struct dvb_usb_device *d, u8 mac[6]) -{ - struct state *state = d->priv; - int ret, i; - u8 tmp; - - state->dual_mode = false; - - /* get demod clock */ - ret = af9035_rd_reg(d, 0x00d800, &tmp); - if (ret < 0) - goto err; - - tmp = (tmp >> 0) & 0x0f; - - for (i = 0; i < af9035_properties[0].num_adapters; i++) - state->af9033_config[i].clock = clock_lut_it9135[tmp]; - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9035_fc0011_tuner_callback(struct dvb_usb_device *d, - int cmd, int arg) -{ - int ret; - - switch (cmd) { - case FC0011_FE_CALLBACK_POWER: - /* Tuner enable */ - ret = af9035_wr_reg_mask(d, 0xd8eb, 1, 1); - if (ret < 0) - goto err; - - ret = af9035_wr_reg_mask(d, 0xd8ec, 1, 1); - if (ret < 0) - goto err; - - ret = af9035_wr_reg_mask(d, 0xd8ed, 1, 1); - if (ret < 0) - goto err; - - /* LED */ - ret = af9035_wr_reg_mask(d, 0xd8d0, 1, 1); - if (ret < 0) - goto err; - - ret = af9035_wr_reg_mask(d, 0xd8d1, 1, 1); - if (ret < 0) - goto err; - - usleep_range(10000, 50000); - break; - case FC0011_FE_CALLBACK_RESET: - ret = af9035_wr_reg(d, 0xd8e9, 1); - if (ret < 0) - goto err; - - ret = af9035_wr_reg(d, 0xd8e8, 1); - if (ret < 0) - goto err; - - ret = af9035_wr_reg(d, 0xd8e7, 1); - if (ret < 0) - goto err; - - usleep_range(10000, 20000); - - ret = af9035_wr_reg(d, 0xd8e7, 0); - if (ret < 0) - goto err; - - usleep_range(10000, 20000); - break; - default: - ret = -EINVAL; - goto err; - } - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9035_tuner_callback(struct dvb_usb_device *d, int cmd, int arg) -{ - struct state *state = d->priv; - - switch (state->af9033_config[0].tuner) { - case AF9033_TUNER_FC0011: - return af9035_fc0011_tuner_callback(d, cmd, arg); - default: - break; - } - - return -ENODEV; -} - -static int af9035_frontend_callback(void *adapter_priv, int component, - int cmd, int arg) -{ - struct i2c_adapter *adap = adapter_priv; - struct dvb_usb_device *d = i2c_get_adapdata(adap); - - switch (component) { - case DVB_FRONTEND_COMPONENT_TUNER: - return af9035_tuner_callback(d, cmd, arg); - default: - break; - } - - return -EINVAL; -} - -static int af9035_frontend_attach(struct dvb_usb_adapter *adap) -{ - struct state *state = adap->dev->priv; - int ret; - - if (!state->af9033_config[adap->id].tuner) { - /* unsupported tuner */ - ret = -ENODEV; - goto err; - } - - if (adap->id == 0) { - state->af9033_config[0].ts_mode = AF9033_TS_MODE_USB; - state->af9033_config[1].ts_mode = AF9033_TS_MODE_SERIAL; - - ret = af9035_wr_reg(adap->dev, 0x00417f, - state->af9033_config[1].i2c_addr); - if (ret < 0) - goto err; - - ret = af9035_wr_reg(adap->dev, 0x00d81a, - state->dual_mode); - if (ret < 0) - goto err; - } - - /* attach demodulator */ - adap->fe_adap[0].fe = dvb_attach(af9033_attach, - &state->af9033_config[adap->id], &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe == NULL) { - ret = -ENODEV; - goto err; - } - - /* disable I2C-gate */ - adap->fe_adap[0].fe->ops.i2c_gate_ctrl = NULL; - adap->fe_adap[0].fe->callback = af9035_frontend_callback; - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static struct tua9001_config af9035_tua9001_config = { - .i2c_addr = 0x60, -}; - -static const struct fc0011_config af9035_fc0011_config = { - .i2c_address = 0x60, -}; - -static struct mxl5007t_config af9035_mxl5007t_config = { - .xtal_freq_hz = MxL_XTAL_24_MHZ, - .if_freq_hz = MxL_IF_4_57_MHZ, - .invert_if = 0, - .loop_thru_enable = 0, - .clk_out_enable = 0, - .clk_out_amp = MxL_CLKOUT_AMP_0_94V, -}; - -static struct tda18218_config af9035_tda18218_config = { - .i2c_address = 0x60, - .i2c_wr_max = 21, -}; - -static int af9035_tuner_attach(struct dvb_usb_adapter *adap) -{ - struct state *state = adap->dev->priv; - int ret; - struct dvb_frontend *fe; - - switch (state->af9033_config[adap->id].tuner) { - case AF9033_TUNER_TUA9001: - /* AF9035 gpiot3 = TUA9001 RESETN - AF9035 gpiot2 = TUA9001 RXEN */ - - /* configure gpiot2 and gpiot2 as output */ - ret = af9035_wr_reg_mask(adap->dev, 0x00d8ec, 0x01, 0x01); - if (ret < 0) - goto err; - - ret = af9035_wr_reg_mask(adap->dev, 0x00d8ed, 0x01, 0x01); - if (ret < 0) - goto err; - - ret = af9035_wr_reg_mask(adap->dev, 0x00d8e8, 0x01, 0x01); - if (ret < 0) - goto err; - - ret = af9035_wr_reg_mask(adap->dev, 0x00d8e9, 0x01, 0x01); - if (ret < 0) - goto err; - - /* reset tuner */ - ret = af9035_wr_reg_mask(adap->dev, 0x00d8e7, 0x00, 0x01); - if (ret < 0) - goto err; - - usleep_range(2000, 20000); - - ret = af9035_wr_reg_mask(adap->dev, 0x00d8e7, 0x01, 0x01); - if (ret < 0) - goto err; - - /* activate tuner RX */ - /* TODO: use callback for TUA9001 RXEN */ - ret = af9035_wr_reg_mask(adap->dev, 0x00d8eb, 0x01, 0x01); - if (ret < 0) - goto err; - - /* attach tuner */ - fe = dvb_attach(tua9001_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, &af9035_tua9001_config); - break; - case AF9033_TUNER_FC0011: - fe = dvb_attach(fc0011_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, &af9035_fc0011_config); - break; - case AF9033_TUNER_MXL5007T: - ret = af9035_wr_reg(adap->dev, 0x00d8e0, 1); - if (ret < 0) - goto err; - ret = af9035_wr_reg(adap->dev, 0x00d8e1, 1); - if (ret < 0) - goto err; - ret = af9035_wr_reg(adap->dev, 0x00d8df, 0); - if (ret < 0) - goto err; - - msleep(30); - - ret = af9035_wr_reg(adap->dev, 0x00d8df, 1); - if (ret < 0) - goto err; - - msleep(300); - - ret = af9035_wr_reg(adap->dev, 0x00d8c0, 1); - if (ret < 0) - goto err; - ret = af9035_wr_reg(adap->dev, 0x00d8c1, 1); - if (ret < 0) - goto err; - ret = af9035_wr_reg(adap->dev, 0x00d8bf, 0); - if (ret < 0) - goto err; - ret = af9035_wr_reg(adap->dev, 0x00d8b4, 1); - if (ret < 0) - goto err; - ret = af9035_wr_reg(adap->dev, 0x00d8b5, 1); - if (ret < 0) - goto err; - ret = af9035_wr_reg(adap->dev, 0x00d8b3, 1); - if (ret < 0) - goto err; - - /* attach tuner */ - fe = dvb_attach(mxl5007t_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, 0x60, &af9035_mxl5007t_config); - break; - case AF9033_TUNER_TDA18218: - /* attach tuner */ - fe = dvb_attach(tda18218_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, &af9035_tda18218_config); - break; - default: - fe = NULL; - } - - if (fe == NULL) { - ret = -ENODEV; - goto err; - } - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -enum af9035_id_entry { - AF9035_15A4_9035, - AF9035_15A4_1000, - AF9035_15A4_1001, - AF9035_15A4_1002, - AF9035_15A4_1003, - AF9035_0CCD_0093, - AF9035_07CA_A835, - AF9035_07CA_B835, - AF9035_07CA_1867, - AF9035_07CA_A867, - AF9035_07CA_0825, -}; - -static struct usb_device_id af9035_id[] = { - [AF9035_15A4_9035] = { - USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_9035)}, - [AF9035_15A4_1000] = { - USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1000)}, - [AF9035_15A4_1001] = { - USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1001)}, - [AF9035_15A4_1002] = { - USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1002)}, - [AF9035_15A4_1003] = { - USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1003)}, - [AF9035_0CCD_0093] = { - USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK)}, - [AF9035_07CA_A835] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835)}, - [AF9035_07CA_B835] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_B835)}, - [AF9035_07CA_1867] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_1867)}, - [AF9035_07CA_A867] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A867)}, - [AF9035_07CA_0825] = { - USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_TWINSTAR)}, - {}, -}; - -MODULE_DEVICE_TABLE(usb, af9035_id); - -static struct dvb_usb_device_properties af9035_properties[] = { - { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - .download_firmware = af9035_download_firmware, - .firmware = "dvb-usb-af9035-02.fw", - .no_reconnect = 1, - - .size_of_priv = sizeof(struct state), - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = { - { - .frontend_attach = af9035_frontend_attach, - .tuner_attach = af9035_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x84, - .u = { - .bulk = { - .buffersize = (87 * 188), - } - } - } - } - } - } - }, - - .identify_state = af9035_identify_state, - .read_mac_address = af9035_read_mac_address, - - .i2c_algo = &af9035_i2c_algo, - - .rc.core = { - .protocol = RC_TYPE_UNKNOWN, - .module_name = "af9035", - .rc_query = NULL, - .rc_interval = AF9035_POLL, - .allowed_protos = RC_TYPE_UNKNOWN, - .rc_codes = RC_MAP_EMPTY, - }, - .num_device_descs = 5, - .devices = { - { - .name = "Afatech AF9035 reference design", - .cold_ids = { - &af9035_id[AF9035_15A4_9035], - &af9035_id[AF9035_15A4_1000], - &af9035_id[AF9035_15A4_1001], - &af9035_id[AF9035_15A4_1002], - &af9035_id[AF9035_15A4_1003], - }, - }, { - .name = "TerraTec Cinergy T Stick", - .cold_ids = { - &af9035_id[AF9035_0CCD_0093], - }, - }, { - .name = "AVerMedia AVerTV Volar HD/PRO (A835)", - .cold_ids = { - &af9035_id[AF9035_07CA_A835], - &af9035_id[AF9035_07CA_B835], - }, - }, { - .name = "AVerMedia HD Volar (A867)", - .cold_ids = { - &af9035_id[AF9035_07CA_1867], - &af9035_id[AF9035_07CA_A867], - }, - }, { - .name = "AVerMedia Twinstar (A825)", - .cold_ids = { - &af9035_id[AF9035_07CA_0825], - }, - }, - } - }, - { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - .download_firmware = af9035_download_firmware_it9135, - .firmware = "dvb-usb-it9135-01.fw", - .no_reconnect = 1, - - .size_of_priv = sizeof(struct state), - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = { - { - .frontend_attach = af9035_frontend_attach, - .tuner_attach = af9035_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x84, - .u = { - .bulk = { - .buffersize = (87 * 188), - } - } - } - } - } - } - }, - - .identify_state = af9035_identify_state, - .read_mac_address = af9035_read_mac_address_it9135, - - .i2c_algo = &af9035_i2c_algo, - - .num_device_descs = 0, /* disabled as no support for IT9135 */ - .devices = { - { - .name = "ITE Tech. IT9135 reference design", - }, - } - }, -}; - -static int af9035_usb_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - int ret, i; - struct dvb_usb_device *d = NULL; - struct usb_device *udev; - bool found; - - pr_debug("%s: interface=%d\n", __func__, - intf->cur_altsetting->desc.bInterfaceNumber); - - /* interface 0 is used by DVB-T receiver and - interface 1 is for remote controller (HID) */ - if (intf->cur_altsetting->desc.bInterfaceNumber != 0) - return 0; - - /* Dynamic USB ID support. Replaces first device ID with current one. */ - udev = interface_to_usbdev(intf); - - for (i = 0, found = false; i < ARRAY_SIZE(af9035_id) - 1; i++) { - if (af9035_id[i].idVendor == - le16_to_cpu(udev->descriptor.idVendor) && - af9035_id[i].idProduct == - le16_to_cpu(udev->descriptor.idProduct)) { - found = true; - break; - } - } - - if (!found) { - pr_debug("%s: using dynamic ID %04x:%04x\n", __func__, - le16_to_cpu(udev->descriptor.idVendor), - le16_to_cpu(udev->descriptor.idProduct)); - af9035_properties[0].devices[0].cold_ids[0]->idVendor = - le16_to_cpu(udev->descriptor.idVendor); - af9035_properties[0].devices[0].cold_ids[0]->idProduct = - le16_to_cpu(udev->descriptor.idProduct); - } - - - for (i = 0; i < af9035_properties_count; i++) { - ret = dvb_usb_device_init(intf, &af9035_properties[i], - THIS_MODULE, &d, adapter_nr); - - if (ret == -ENODEV) - continue; - else - break; - } - - if (ret < 0) - goto err; - - if (d) { - ret = af9035_init(d); - if (ret < 0) - goto err; - } - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -/* usb specific object needed to register this driver with the usb subsystem */ -static struct usb_driver af9035_usb_driver = { - .name = "dvb_usb_af9035", - .probe = af9035_usb_probe, - .disconnect = dvb_usb_device_exit, - .id_table = af9035_id, -}; - -module_usb_driver(af9035_usb_driver); - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Afatech AF9035 driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/af9035.h b/drivers/media/dvb/dvb-usb/af9035.h deleted file mode 100644 index 481a1a43dd2a..000000000000 --- a/drivers/media/dvb/dvb-usb/af9035.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Afatech AF9035 DVB USB driver - * - * Copyright (C) 2009 Antti Palosaari - * Copyright (C) 2012 Antti Palosaari - * - * 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. - */ - -#ifndef AF9035_H -#define AF9035_H - -/* prefix for dvb-usb log writings */ -#define DVB_USB_LOG_PREFIX "af9035" - -#include "dvb-usb.h" -#include "af9033.h" -#include "tua9001.h" -#include "fc0011.h" -#include "mxl5007t.h" -#include "tda18218.h" - -struct reg_val { - u32 reg; - u8 val; -}; - -struct reg_val_mask { - u32 reg; - u8 val; - u8 mask; -}; - -struct usb_req { - u8 cmd; - u8 mbox; - u8 wlen; - u8 *wbuf; - u8 rlen; - u8 *rbuf; -}; - -struct state { - bool dual_mode; - - struct af9033_config af9033_config[2]; -}; - -u32 clock_lut[] = { - 20480000, /* FPGA */ - 16384000, /* 16.38 MHz */ - 20480000, /* 20.48 MHz */ - 36000000, /* 36.00 MHz */ - 30000000, /* 30.00 MHz */ - 26000000, /* 26.00 MHz */ - 28000000, /* 28.00 MHz */ - 32000000, /* 32.00 MHz */ - 34000000, /* 34.00 MHz */ - 24000000, /* 24.00 MHz */ - 22000000, /* 22.00 MHz */ - 12000000, /* 12.00 MHz */ -}; - -u32 clock_lut_it9135[] = { - 12000000, /* 12.00 MHz */ - 20480000, /* 20.48 MHz */ - 36000000, /* 36.00 MHz */ - 30000000, /* 30.00 MHz */ - 26000000, /* 26.00 MHz */ - 28000000, /* 28.00 MHz */ - 32000000, /* 32.00 MHz */ - 34000000, /* 34.00 MHz */ - 24000000, /* 24.00 MHz */ - 22000000, /* 22.00 MHz */ -}; - -/* EEPROM locations */ -#define EEPROM_IR_MODE 0x430d -#define EEPROM_DUAL_MODE 0x4326 -#define EEPROM_IR_TYPE 0x4329 -#define EEPROM_1_IFFREQ_L 0x432d -#define EEPROM_1_IFFREQ_H 0x432e -#define EEPROM_1_TUNER_ID 0x4331 -#define EEPROM_2_IFFREQ_L 0x433d -#define EEPROM_2_IFFREQ_H 0x433e -#define EEPROM_2_TUNER_ID 0x4341 - -/* USB commands */ -#define CMD_MEM_RD 0x00 -#define CMD_MEM_WR 0x01 -#define CMD_I2C_RD 0x02 -#define CMD_I2C_WR 0x03 -#define CMD_IR_GET 0x18 -#define CMD_FW_DL 0x21 -#define CMD_FW_QUERYINFO 0x22 -#define CMD_FW_BOOT 0x23 -#define CMD_FW_DL_BEGIN 0x24 -#define CMD_FW_DL_END 0x25 -#define CMD_FW_SCATTER_WR 0x29 - -#endif diff --git a/drivers/media/dvb/dvb-usb/anysee.c b/drivers/media/dvb/dvb-usb/anysee.c deleted file mode 100644 index 03c28655af1b..000000000000 --- a/drivers/media/dvb/dvb-usb/anysee.c +++ /dev/null @@ -1,1406 +0,0 @@ -/* - * DVB USB Linux driver for Anysee E30 DVB-C & DVB-T USB2.0 receiver - * - * Copyright (C) 2007 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * TODO: - * - add smart card reader support for Conditional Access (CA) - * - * Card reader in Anysee is nothing more than ISO 7816 card reader. - * There is no hardware CAM in any Anysee device sold. - * In my understanding it should be implemented by making own module - * for ISO 7816 card reader, like dvb_ca_en50221 is implemented. This - * module registers serial interface that can be used to communicate - * with any ISO 7816 smart card. - * - * Any help according to implement serial smart card reader support - * is highly welcome! - */ - -#include "anysee.h" -#include "tda1002x.h" -#include "mt352.h" -#include "mt352_priv.h" -#include "zl10353.h" -#include "tda18212.h" -#include "cx24116.h" -#include "stv0900.h" -#include "stv6110.h" -#include "isl6423.h" -#include "cxd2820r.h" - -/* debug */ -static int dvb_usb_anysee_debug; -module_param_named(debug, dvb_usb_anysee_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level" DVB_USB_DEBUG_STATUS); -static int dvb_usb_anysee_delsys; -module_param_named(delsys, dvb_usb_anysee_delsys, int, 0644); -MODULE_PARM_DESC(delsys, "select delivery mode (0=DVB-C, 1=DVB-T)"); -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -static DEFINE_MUTEX(anysee_usb_mutex); - -static int anysee_ctrl_msg(struct dvb_usb_device *d, u8 *sbuf, u8 slen, - u8 *rbuf, u8 rlen) -{ - struct anysee_state *state = d->priv; - int act_len, ret, i; - u8 buf[64]; - - memcpy(&buf[0], sbuf, slen); - buf[60] = state->seq++; - - if (mutex_lock_interruptible(&anysee_usb_mutex) < 0) - return -EAGAIN; - - deb_xfer(">>> "); - debug_dump(buf, slen, deb_xfer); - - /* We need receive one message more after dvb_usb_generic_rw due - to weird transaction flow, which is 1 x send + 2 x receive. */ - ret = dvb_usb_generic_rw(d, buf, sizeof(buf), buf, sizeof(buf), 0); - if (ret) - goto error_unlock; - - /* TODO FIXME: dvb_usb_generic_rw() fails rarely with error code -32 - * (EPIPE, Broken pipe). Function supports currently msleep() as a - * parameter but I would not like to use it, since according to - * Documentation/timers/timers-howto.txt it should not be used such - * short, under < 20ms, sleeps. Repeating failed message would be - * better choice as not to add unwanted delays... - * Fixing that correctly is one of those or both; - * 1) use repeat if possible - * 2) add suitable delay - */ - - /* get answer, retry few times if error returned */ - for (i = 0; i < 3; i++) { - /* receive 2nd answer */ - ret = usb_bulk_msg(d->udev, usb_rcvbulkpipe(d->udev, - d->props.generic_bulk_ctrl_endpoint), buf, sizeof(buf), - &act_len, 2000); - - if (ret) { - deb_info("%s: recv bulk message failed: %d", - __func__, ret); - } else { - deb_xfer("<<< "); - debug_dump(buf, rlen, deb_xfer); - - if (buf[63] != 0x4f) - deb_info("%s: cmd failed\n", __func__); - - break; - } - } - - if (ret) { - /* all retries failed, it is fatal */ - err("%s: recv bulk message failed: %d", __func__, ret); - goto error_unlock; - } - - /* read request, copy returned data to return buf */ - if (rbuf && rlen) - memcpy(rbuf, buf, rlen); - -error_unlock: - mutex_unlock(&anysee_usb_mutex); - - return ret; -} - -static int anysee_read_reg(struct dvb_usb_device *d, u16 reg, u8 *val) -{ - u8 buf[] = {CMD_REG_READ, reg >> 8, reg & 0xff, 0x01}; - int ret; - ret = anysee_ctrl_msg(d, buf, sizeof(buf), val, 1); - deb_info("%s: reg:%04x val:%02x\n", __func__, reg, *val); - return ret; -} - -static int anysee_write_reg(struct dvb_usb_device *d, u16 reg, u8 val) -{ - u8 buf[] = {CMD_REG_WRITE, reg >> 8, reg & 0xff, 0x01, val}; - deb_info("%s: reg:%04x val:%02x\n", __func__, reg, val); - return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); -} - -/* write single register with mask */ -static int anysee_wr_reg_mask(struct dvb_usb_device *d, u16 reg, u8 val, - u8 mask) -{ - int ret; - u8 tmp; - - /* no need for read if whole reg is written */ - if (mask != 0xff) { - ret = anysee_read_reg(d, reg, &tmp); - if (ret) - return ret; - - val &= mask; - tmp &= ~mask; - val |= tmp; - } - - return anysee_write_reg(d, reg, val); -} - -/* read single register with mask */ -static int anysee_rd_reg_mask(struct dvb_usb_device *d, u16 reg, u8 *val, - u8 mask) -{ - int ret, i; - u8 tmp; - - ret = anysee_read_reg(d, reg, &tmp); - if (ret) - return ret; - - tmp &= mask; - - /* find position of the first bit */ - for (i = 0; i < 8; i++) { - if ((mask >> i) & 0x01) - break; - } - *val = tmp >> i; - - return 0; -} - -static int anysee_get_hw_info(struct dvb_usb_device *d, u8 *id) -{ - u8 buf[] = {CMD_GET_HW_INFO}; - return anysee_ctrl_msg(d, buf, sizeof(buf), id, 3); -} - -static int anysee_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - u8 buf[] = {CMD_STREAMING_CTRL, (u8)onoff, 0x00}; - deb_info("%s: onoff:%02x\n", __func__, onoff); - return anysee_ctrl_msg(adap->dev, buf, sizeof(buf), NULL, 0); -} - -static int anysee_led_ctrl(struct dvb_usb_device *d, u8 mode, u8 interval) -{ - u8 buf[] = {CMD_LED_AND_IR_CTRL, 0x01, mode, interval}; - deb_info("%s: state:%02x interval:%02x\n", __func__, mode, interval); - return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); -} - -static int anysee_ir_ctrl(struct dvb_usb_device *d, u8 onoff) -{ - u8 buf[] = {CMD_LED_AND_IR_CTRL, 0x02, onoff}; - deb_info("%s: onoff:%02x\n", __func__, onoff); - return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); -} - -/* I2C */ -static int anysee_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - int ret = 0, inc, i = 0; - u8 buf[52]; /* 4 + 48 (I2C WR USB command header + I2C WR max) */ - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - while (i < num) { - if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { - if (msg[i].len > 2 || msg[i+1].len > 60) { - ret = -EOPNOTSUPP; - break; - } - buf[0] = CMD_I2C_READ; - buf[1] = (msg[i].addr << 1) | 0x01; - buf[2] = msg[i].buf[0]; - buf[3] = msg[i].buf[1]; - buf[4] = msg[i].len-1; - buf[5] = msg[i+1].len; - ret = anysee_ctrl_msg(d, buf, 6, msg[i+1].buf, - msg[i+1].len); - inc = 2; - } else { - if (msg[i].len > 48) { - ret = -EOPNOTSUPP; - break; - } - buf[0] = CMD_I2C_WRITE; - buf[1] = (msg[i].addr << 1); - buf[2] = msg[i].len; - buf[3] = 0x01; - memcpy(&buf[4], msg[i].buf, msg[i].len); - ret = anysee_ctrl_msg(d, buf, 4 + msg[i].len, NULL, 0); - inc = 1; - } - if (ret) - break; - - i += inc; - } - - mutex_unlock(&d->i2c_mutex); - - return ret ? ret : i; -} - -static u32 anysee_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm anysee_i2c_algo = { - .master_xfer = anysee_master_xfer, - .functionality = anysee_i2c_func, -}; - -static int anysee_mt352_demod_init(struct dvb_frontend *fe) -{ - static u8 clock_config[] = { CLOCK_CTL, 0x38, 0x28 }; - static u8 reset[] = { RESET, 0x80 }; - static u8 adc_ctl_1_cfg[] = { ADC_CTL_1, 0x40 }; - static u8 agc_cfg[] = { AGC_TARGET, 0x28, 0x20 }; - static u8 gpp_ctl_cfg[] = { GPP_CTL, 0x33 }; - static u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 }; - - mt352_write(fe, clock_config, sizeof(clock_config)); - udelay(200); - mt352_write(fe, reset, sizeof(reset)); - mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); - - mt352_write(fe, agc_cfg, sizeof(agc_cfg)); - mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); - mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); - - return 0; -} - -/* Callbacks for DVB USB */ -static struct tda10023_config anysee_tda10023_config = { - .demod_address = (0x1a >> 1), - .invert = 0, - .xtal = 16000000, - .pll_m = 11, - .pll_p = 3, - .pll_n = 1, - .output_mode = TDA10023_OUTPUT_MODE_PARALLEL_C, - .deltaf = 0xfeeb, -}; - -static struct mt352_config anysee_mt352_config = { - .demod_address = (0x1e >> 1), - .demod_init = anysee_mt352_demod_init, -}; - -static struct zl10353_config anysee_zl10353_config = { - .demod_address = (0x1e >> 1), - .parallel_ts = 1, -}; - -static struct zl10353_config anysee_zl10353_tda18212_config2 = { - .demod_address = (0x1e >> 1), - .parallel_ts = 1, - .disable_i2c_gate_ctrl = 1, - .no_tuner = 1, - .if2 = 41500, -}; - -static struct zl10353_config anysee_zl10353_tda18212_config = { - .demod_address = (0x18 >> 1), - .parallel_ts = 1, - .disable_i2c_gate_ctrl = 1, - .no_tuner = 1, - .if2 = 41500, -}; - -static struct tda10023_config anysee_tda10023_tda18212_config = { - .demod_address = (0x1a >> 1), - .xtal = 16000000, - .pll_m = 12, - .pll_p = 3, - .pll_n = 1, - .output_mode = TDA10023_OUTPUT_MODE_PARALLEL_B, - .deltaf = 0xba02, -}; - -static struct tda18212_config anysee_tda18212_config = { - .i2c_address = (0xc0 >> 1), - .if_dvbt_6 = 4150, - .if_dvbt_7 = 4150, - .if_dvbt_8 = 4150, - .if_dvbc = 5000, -}; - -static struct tda18212_config anysee_tda18212_config2 = { - .i2c_address = 0x60 /* (0xc0 >> 1) */, - .if_dvbt_6 = 3550, - .if_dvbt_7 = 3700, - .if_dvbt_8 = 4150, - .if_dvbt2_6 = 3250, - .if_dvbt2_7 = 4000, - .if_dvbt2_8 = 4000, - .if_dvbc = 5000, -}; - -static struct cx24116_config anysee_cx24116_config = { - .demod_address = (0xaa >> 1), - .mpg_clk_pos_pol = 0x00, - .i2c_wr_max = 48, -}; - -static struct stv0900_config anysee_stv0900_config = { - .demod_address = (0xd0 >> 1), - .demod_mode = 0, - .xtal = 8000000, - .clkmode = 3, - .diseqc_mode = 2, - .tun1_maddress = 0, - .tun1_adc = 1, /* 1 Vpp */ - .path1_mode = 3, -}; - -static struct stv6110_config anysee_stv6110_config = { - .i2c_address = (0xc0 >> 1), - .mclk = 16000000, - .clk_div = 1, -}; - -static struct isl6423_config anysee_isl6423_config = { - .current_max = SEC_CURRENT_800m, - .curlim = SEC_CURRENT_LIM_OFF, - .mod_extern = 1, - .addr = (0x10 >> 1), -}; - -static struct cxd2820r_config anysee_cxd2820r_config = { - .i2c_address = 0x6d, /* (0xda >> 1) */ - .ts_mode = 0x38, -}; - -/* - * New USB device strings: Mfr=1, Product=2, SerialNumber=0 - * Manufacturer: AMT.CO.KR - * - * E30 VID=04b4 PID=861f HW=2 FW=2.1 Product=???????? - * PCB: ? - * parts: DNOS404ZH102A(MT352, DTT7579(?)) - * - * E30 VID=04b4 PID=861f HW=2 FW=2.1 "anysee-T(LP)" - * PCB: PCB 507T (rev1.61) - * parts: DNOS404ZH103A(ZL10353, DTT7579(?)) - * OEA=0a OEB=00 OEC=00 OED=ff OEE=00 - * IOA=45 IOB=ff IOC=00 IOD=ff IOE=00 - * - * E30 Plus VID=04b4 PID=861f HW=6 FW=1.0 "anysee" - * PCB: 507CD (rev1.1) - * parts: DNOS404ZH103A(ZL10353, DTT7579(?)), CST56I01 - * OEA=80 OEB=00 OEC=00 OED=ff OEE=fe - * IOA=4f IOB=ff IOC=00 IOD=06 IOE=01 - * IOD[0] ZL10353 1=enabled - * IOA[7] TS 0=enabled - * tuner is not behind ZL10353 I2C-gate (no care if gate disabled or not) - * - * E30 C Plus VID=04b4 PID=861f HW=10 FW=1.0 "anysee-DC(LP)" - * PCB: 507DC (rev0.2) - * parts: TDA10023, DTOS403IH102B TM, CST56I01 - * OEA=80 OEB=00 OEC=00 OED=ff OEE=fe - * IOA=4f IOB=ff IOC=00 IOD=26 IOE=01 - * IOD[0] TDA10023 1=enabled - * - * E30 S2 Plus VID=04b4 PID=861f HW=11 FW=0.1 "anysee-S2(LP)" - * PCB: 507SI (rev2.1) - * parts: BS2N10WCC01(CX24116, CX24118), ISL6423, TDA8024 - * OEA=80 OEB=00 OEC=ff OED=ff OEE=fe - * IOA=4d IOB=ff IOC=00 IOD=26 IOE=01 - * IOD[0] CX24116 1=enabled - * - * E30 C Plus VID=1c73 PID=861f HW=15 FW=1.2 "anysee-FA(LP)" - * PCB: 507FA (rev0.4) - * parts: TDA10023, DTOS403IH102B TM, TDA8024 - * OEA=80 OEB=00 OEC=ff OED=ff OEE=ff - * IOA=4d IOB=ff IOC=00 IOD=00 IOE=c0 - * IOD[5] TDA10023 1=enabled - * IOE[0] tuner 1=enabled - * - * E30 Combo Plus VID=1c73 PID=861f HW=15 FW=1.2 "anysee-FA(LP)" - * PCB: 507FA (rev1.1) - * parts: ZL10353, TDA10023, DTOS403IH102B TM, TDA8024 - * OEA=80 OEB=00 OEC=ff OED=ff OEE=ff - * IOA=4d IOB=ff IOC=00 IOD=00 IOE=c0 - * DVB-C: - * IOD[5] TDA10023 1=enabled - * IOE[0] tuner 1=enabled - * DVB-T: - * IOD[0] ZL10353 1=enabled - * IOE[0] tuner 0=enabled - * tuner is behind ZL10353 I2C-gate - * - * E7 TC VID=1c73 PID=861f HW=18 FW=0.7 AMTCI=0.5 "anysee-E7TC(LP)" - * PCB: 508TC (rev0.6) - * parts: ZL10353, TDA10023, DNOD44CDH086A(TDA18212) - * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff - * IOA=4d IOB=00 IOC=cc IOD=48 IOE=e4 - * IOA[7] TS 1=enabled - * IOE[4] TDA18212 1=enabled - * DVB-C: - * IOD[6] ZL10353 0=disabled - * IOD[5] TDA10023 1=enabled - * IOE[0] IF 1=enabled - * DVB-T: - * IOD[5] TDA10023 0=disabled - * IOD[6] ZL10353 1=enabled - * IOE[0] IF 0=enabled - * - * E7 S2 VID=1c73 PID=861f HW=19 FW=0.4 AMTCI=0.5 "anysee-E7S2(LP)" - * PCB: 508S2 (rev0.7) - * parts: DNBU10512IST(STV0903, STV6110), ISL6423 - * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff - * IOA=4d IOB=00 IOC=c4 IOD=08 IOE=e4 - * IOA[7] TS 1=enabled - * IOE[5] STV0903 1=enabled - * - * E7 T2C VID=1c73 PID=861f HW=20 FW=0.1 AMTCI=0.5 "anysee-E7T2C(LP)" - * PCB: 508T2C (rev0.3) - * parts: DNOQ44QCH106A(CXD2820R, TDA18212), TDA8024 - * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff - * IOA=4d IOB=00 IOC=cc IOD=48 IOE=e4 - * IOA[7] TS 1=enabled - * IOE[5] CXD2820R 1=enabled - * - * E7 PTC VID=1c73 PID=861f HW=21 FW=0.1 AMTCI=?? "anysee-E7PTC(LP)" - * PCB: 508PTC (rev0.5) - * parts: ZL10353, TDA10023, DNOD44CDH086A(TDA18212) - * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff - * IOA=4d IOB=00 IOC=cc IOD=48 IOE=e4 - * IOA[7] TS 1=enabled - * IOE[4] TDA18212 1=enabled - * DVB-C: - * IOD[6] ZL10353 0=disabled - * IOD[5] TDA10023 1=enabled - * IOE[0] IF 1=enabled - * DVB-T: - * IOD[5] TDA10023 0=disabled - * IOD[6] ZL10353 1=enabled - * IOE[0] IF 0=enabled - * - * E7 PS2 VID=1c73 PID=861f HW=22 FW=0.1 AMTCI=?? "anysee-E7PS2(LP)" - * PCB: 508PS2 (rev0.4) - * parts: DNBU10512IST(STV0903, STV6110), ISL6423 - * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff - * IOA=4d IOB=00 IOC=c4 IOD=08 IOE=e4 - * IOA[7] TS 1=enabled - * IOE[5] STV0903 1=enabled - */ - - -/* external I2C gate used for DNOD44CDH086A(TDA18212) tuner module */ -static int anysee_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - - /* enable / disable tuner access on IOE[4] */ - return anysee_wr_reg_mask(adap->dev, REG_IOE, (enable << 4), 0x10); -} - -static int anysee_frontend_ctrl(struct dvb_frontend *fe, int onoff) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct anysee_state *state = adap->dev->priv; - int ret; - - deb_info("%s: fe=%d onoff=%d\n", __func__, fe->id, onoff); - - /* no frontend sleep control */ - if (onoff == 0) - return 0; - - switch (state->hw) { - case ANYSEE_HW_507FA: /* 15 */ - /* E30 Combo Plus */ - /* E30 C Plus */ - - if ((fe->id ^ dvb_usb_anysee_delsys) == 0) { - /* disable DVB-T demod on IOD[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (0 << 0), - 0x01); - if (ret) - goto error; - - /* enable DVB-C demod on IOD[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 5), - 0x20); - if (ret) - goto error; - - /* enable DVB-C tuner on IOE[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOE, (1 << 0), - 0x01); - if (ret) - goto error; - } else { - /* disable DVB-C demod on IOD[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (0 << 5), - 0x20); - if (ret) - goto error; - - /* enable DVB-T demod on IOD[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 0), - 0x01); - if (ret) - goto error; - - /* enable DVB-T tuner on IOE[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOE, (0 << 0), - 0x01); - if (ret) - goto error; - } - - break; - case ANYSEE_HW_508TC: /* 18 */ - case ANYSEE_HW_508PTC: /* 21 */ - /* E7 TC */ - /* E7 PTC */ - - if ((fe->id ^ dvb_usb_anysee_delsys) == 0) { - /* disable DVB-T demod on IOD[6] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (0 << 6), - 0x40); - if (ret) - goto error; - - /* enable DVB-C demod on IOD[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 5), - 0x20); - if (ret) - goto error; - - /* enable IF route on IOE[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOE, (1 << 0), - 0x01); - if (ret) - goto error; - } else { - /* disable DVB-C demod on IOD[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (0 << 5), - 0x20); - if (ret) - goto error; - - /* enable DVB-T demod on IOD[6] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 6), - 0x40); - if (ret) - goto error; - - /* enable IF route on IOE[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOE, (0 << 0), - 0x01); - if (ret) - goto error; - } - - break; - default: - ret = 0; - } - -error: - return ret; -} - -static int anysee_frontend_attach(struct dvb_usb_adapter *adap) -{ - int ret; - struct anysee_state *state = adap->dev->priv; - u8 hw_info[3]; - u8 tmp; - struct i2c_msg msg[2] = { - { - .addr = anysee_tda18212_config.i2c_address, - .flags = 0, - .len = 1, - .buf = "\x00", - }, { - .addr = anysee_tda18212_config.i2c_address, - .flags = I2C_M_RD, - .len = 1, - .buf = &tmp, - } - }; - - /* detect hardware only once */ - if (adap->fe_adap[0].fe == NULL) { - /* Check which hardware we have. - * We must do this call two times to get reliable values - * (hw/fw bug). - */ - ret = anysee_get_hw_info(adap->dev, hw_info); - if (ret) - goto error; - - ret = anysee_get_hw_info(adap->dev, hw_info); - if (ret) - goto error; - - /* Meaning of these info bytes are guessed. */ - info("firmware version:%d.%d hardware id:%d", - hw_info[1], hw_info[2], hw_info[0]); - - state->hw = hw_info[0]; - } - - /* set current frondend ID for devices having two frondends */ - if (adap->fe_adap[0].fe) - state->fe_id++; - - switch (state->hw) { - case ANYSEE_HW_507T: /* 2 */ - /* E30 */ - - if (state->fe_id) - break; - - /* attach demod */ - adap->fe_adap[0].fe = dvb_attach(mt352_attach, - &anysee_mt352_config, &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe) - break; - - /* attach demod */ - adap->fe_adap[0].fe = dvb_attach(zl10353_attach, - &anysee_zl10353_config, &adap->dev->i2c_adap); - - break; - case ANYSEE_HW_507CD: /* 6 */ - /* E30 Plus */ - - if (state->fe_id) - break; - - /* enable DVB-T demod on IOD[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 0), 0x01); - if (ret) - goto error; - - /* enable transport stream on IOA[7] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOA, (0 << 7), 0x80); - if (ret) - goto error; - - /* attach demod */ - adap->fe_adap[0].fe = dvb_attach(zl10353_attach, - &anysee_zl10353_config, &adap->dev->i2c_adap); - - break; - case ANYSEE_HW_507DC: /* 10 */ - /* E30 C Plus */ - - if (state->fe_id) - break; - - /* enable DVB-C demod on IOD[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 0), 0x01); - if (ret) - goto error; - - /* attach demod */ - adap->fe_adap[0].fe = dvb_attach(tda10023_attach, - &anysee_tda10023_config, &adap->dev->i2c_adap, 0x48); - - break; - case ANYSEE_HW_507SI: /* 11 */ - /* E30 S2 Plus */ - - if (state->fe_id) - break; - - /* enable DVB-S/S2 demod on IOD[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 0), 0x01); - if (ret) - goto error; - - /* attach demod */ - adap->fe_adap[0].fe = dvb_attach(cx24116_attach, - &anysee_cx24116_config, &adap->dev->i2c_adap); - - break; - case ANYSEE_HW_507FA: /* 15 */ - /* E30 Combo Plus */ - /* E30 C Plus */ - - /* enable tuner on IOE[4] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOE, (1 << 4), 0x10); - if (ret) - goto error; - - /* probe TDA18212 */ - tmp = 0; - ret = i2c_transfer(&adap->dev->i2c_adap, msg, 2); - if (ret == 2 && tmp == 0xc7) - deb_info("%s: TDA18212 found\n", __func__); - else - tmp = 0; - - /* disable tuner on IOE[4] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOE, (0 << 4), 0x10); - if (ret) - goto error; - - if ((state->fe_id ^ dvb_usb_anysee_delsys) == 0) { - /* disable DVB-T demod on IOD[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (0 << 0), - 0x01); - if (ret) - goto error; - - /* enable DVB-C demod on IOD[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 5), - 0x20); - if (ret) - goto error; - - /* attach demod */ - if (tmp == 0xc7) { - /* TDA18212 config */ - adap->fe_adap[state->fe_id].fe = dvb_attach( - tda10023_attach, - &anysee_tda10023_tda18212_config, - &adap->dev->i2c_adap, 0x48); - } else { - /* PLL config */ - adap->fe_adap[state->fe_id].fe = dvb_attach( - tda10023_attach, - &anysee_tda10023_config, - &adap->dev->i2c_adap, 0x48); - } - } else { - /* disable DVB-C demod on IOD[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (0 << 5), - 0x20); - if (ret) - goto error; - - /* enable DVB-T demod on IOD[0] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 0), - 0x01); - if (ret) - goto error; - - /* attach demod */ - if (tmp == 0xc7) { - /* TDA18212 config */ - adap->fe_adap[state->fe_id].fe = dvb_attach( - zl10353_attach, - &anysee_zl10353_tda18212_config2, - &adap->dev->i2c_adap); - } else { - /* PLL config */ - adap->fe_adap[state->fe_id].fe = dvb_attach( - zl10353_attach, - &anysee_zl10353_config, - &adap->dev->i2c_adap); - } - } - - /* I2C gate for DNOD44CDH086A(TDA18212) tuner module */ - if (tmp == 0xc7) { - if (adap->fe_adap[state->fe_id].fe) - adap->fe_adap[state->fe_id].fe->ops.i2c_gate_ctrl = - anysee_i2c_gate_ctrl; - } - - break; - case ANYSEE_HW_508TC: /* 18 */ - case ANYSEE_HW_508PTC: /* 21 */ - /* E7 TC */ - /* E7 PTC */ - - if ((state->fe_id ^ dvb_usb_anysee_delsys) == 0) { - /* disable DVB-T demod on IOD[6] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (0 << 6), - 0x40); - if (ret) - goto error; - - /* enable DVB-C demod on IOD[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 5), - 0x20); - if (ret) - goto error; - - /* attach demod */ - adap->fe_adap[state->fe_id].fe = - dvb_attach(tda10023_attach, - &anysee_tda10023_tda18212_config, - &adap->dev->i2c_adap, 0x48); - } else { - /* disable DVB-C demod on IOD[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (0 << 5), - 0x20); - if (ret) - goto error; - - /* enable DVB-T demod on IOD[6] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOD, (1 << 6), - 0x40); - if (ret) - goto error; - - /* attach demod */ - adap->fe_adap[state->fe_id].fe = - dvb_attach(zl10353_attach, - &anysee_zl10353_tda18212_config, - &adap->dev->i2c_adap); - } - - /* I2C gate for DNOD44CDH086A(TDA18212) tuner module */ - if (adap->fe_adap[state->fe_id].fe) - adap->fe_adap[state->fe_id].fe->ops.i2c_gate_ctrl = - anysee_i2c_gate_ctrl; - - state->has_ci = true; - - break; - case ANYSEE_HW_508S2: /* 19 */ - case ANYSEE_HW_508PS2: /* 22 */ - /* E7 S2 */ - /* E7 PS2 */ - - if (state->fe_id) - break; - - /* enable DVB-S/S2 demod on IOE[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOE, (1 << 5), 0x20); - if (ret) - goto error; - - /* attach demod */ - adap->fe_adap[0].fe = dvb_attach(stv0900_attach, - &anysee_stv0900_config, &adap->dev->i2c_adap, 0); - - state->has_ci = true; - - break; - case ANYSEE_HW_508T2C: /* 20 */ - /* E7 T2C */ - - if (state->fe_id) - break; - - /* enable DVB-T/T2/C demod on IOE[5] */ - ret = anysee_wr_reg_mask(adap->dev, REG_IOE, (1 << 5), 0x20); - if (ret) - goto error; - - /* attach demod */ - adap->fe_adap[state->fe_id].fe = dvb_attach(cxd2820r_attach, - &anysee_cxd2820r_config, &adap->dev->i2c_adap); - - state->has_ci = true; - - break; - } - - if (!adap->fe_adap[0].fe) { - /* we have no frontend :-( */ - ret = -ENODEV; - err("Unsupported Anysee version. " \ - "Please report the ."); - } -error: - return ret; -} - -static int anysee_tuner_attach(struct dvb_usb_adapter *adap) -{ - struct anysee_state *state = adap->dev->priv; - struct dvb_frontend *fe; - int ret; - deb_info("%s: fe=%d\n", __func__, state->fe_id); - - switch (state->hw) { - case ANYSEE_HW_507T: /* 2 */ - /* E30 */ - - /* attach tuner */ - fe = dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, - (0xc2 >> 1), NULL, DVB_PLL_THOMSON_DTT7579); - - break; - case ANYSEE_HW_507CD: /* 6 */ - /* E30 Plus */ - - /* attach tuner */ - fe = dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, - (0xc2 >> 1), &adap->dev->i2c_adap, - DVB_PLL_THOMSON_DTT7579); - - break; - case ANYSEE_HW_507DC: /* 10 */ - /* E30 C Plus */ - - /* attach tuner */ - fe = dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, - (0xc0 >> 1), &adap->dev->i2c_adap, - DVB_PLL_SAMSUNG_DTOS403IH102A); - - break; - case ANYSEE_HW_507SI: /* 11 */ - /* E30 S2 Plus */ - - /* attach LNB controller */ - fe = dvb_attach(isl6423_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, &anysee_isl6423_config); - - break; - case ANYSEE_HW_507FA: /* 15 */ - /* E30 Combo Plus */ - /* E30 C Plus */ - - /* Try first attach TDA18212 silicon tuner on IOE[4], if that - * fails attach old simple PLL. */ - - /* attach tuner */ - fe = dvb_attach(tda18212_attach, adap->fe_adap[state->fe_id].fe, - &adap->dev->i2c_adap, &anysee_tda18212_config); - if (fe) - break; - - /* attach tuner */ - fe = dvb_attach(dvb_pll_attach, adap->fe_adap[state->fe_id].fe, - (0xc0 >> 1), &adap->dev->i2c_adap, - DVB_PLL_SAMSUNG_DTOS403IH102A); - - break; - case ANYSEE_HW_508TC: /* 18 */ - case ANYSEE_HW_508PTC: /* 21 */ - /* E7 TC */ - /* E7 PTC */ - - /* attach tuner */ - fe = dvb_attach(tda18212_attach, adap->fe_adap[state->fe_id].fe, - &adap->dev->i2c_adap, &anysee_tda18212_config); - - break; - case ANYSEE_HW_508S2: /* 19 */ - case ANYSEE_HW_508PS2: /* 22 */ - /* E7 S2 */ - /* E7 PS2 */ - - /* attach tuner */ - fe = dvb_attach(stv6110_attach, adap->fe_adap[0].fe, - &anysee_stv6110_config, &adap->dev->i2c_adap); - - if (fe) { - /* attach LNB controller */ - fe = dvb_attach(isl6423_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, &anysee_isl6423_config); - } - - break; - - case ANYSEE_HW_508T2C: /* 20 */ - /* E7 T2C */ - - /* attach tuner */ - fe = dvb_attach(tda18212_attach, adap->fe_adap[state->fe_id].fe, - &adap->dev->i2c_adap, &anysee_tda18212_config2); - - break; - default: - fe = NULL; - } - - if (fe) - ret = 0; - else - ret = -ENODEV; - - return ret; -} - -static int anysee_rc_query(struct dvb_usb_device *d) -{ - u8 buf[] = {CMD_GET_IR_CODE}; - u8 ircode[2]; - int ret; - - /* Remote controller is basic NEC using address byte 0x08. - Anysee device RC query returns only two bytes, status and code, - address byte is dropped. Also it does not return any value for - NEC RCs having address byte other than 0x08. Due to that, we - cannot use that device as standard NEC receiver. - It could be possible make hack which reads whole code directly - from device memory... */ - - ret = anysee_ctrl_msg(d, buf, sizeof(buf), ircode, sizeof(ircode)); - if (ret) - return ret; - - if (ircode[0]) { - deb_rc("%s: key pressed %02x\n", __func__, ircode[1]); - rc_keydown(d->rc_dev, 0x08 << 8 | ircode[1], 0); - } - - return 0; -} - -static int anysee_ci_read_attribute_mem(struct dvb_ca_en50221 *ci, int slot, - int addr) -{ - struct dvb_usb_device *d = ci->data; - int ret; - u8 buf[] = {CMD_CI, 0x02, 0x40 | addr >> 8, addr & 0xff, 0x00, 1}; - u8 val; - - ret = anysee_ctrl_msg(d, buf, sizeof(buf), &val, 1); - if (ret) - return ret; - - return val; -} - -static int anysee_ci_write_attribute_mem(struct dvb_ca_en50221 *ci, int slot, - int addr, u8 val) -{ - struct dvb_usb_device *d = ci->data; - int ret; - u8 buf[] = {CMD_CI, 0x03, 0x40 | addr >> 8, addr & 0xff, 0x00, 1, val}; - - ret = anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); - if (ret) - return ret; - - return 0; -} - -static int anysee_ci_read_cam_control(struct dvb_ca_en50221 *ci, int slot, - u8 addr) -{ - struct dvb_usb_device *d = ci->data; - int ret; - u8 buf[] = {CMD_CI, 0x04, 0x40, addr, 0x00, 1}; - u8 val; - - ret = anysee_ctrl_msg(d, buf, sizeof(buf), &val, 1); - if (ret) - return ret; - - return val; -} - -static int anysee_ci_write_cam_control(struct dvb_ca_en50221 *ci, int slot, - u8 addr, u8 val) -{ - struct dvb_usb_device *d = ci->data; - int ret; - u8 buf[] = {CMD_CI, 0x05, 0x40, addr, 0x00, 1, val}; - - ret = anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0); - if (ret) - return ret; - - return 0; -} - -static int anysee_ci_slot_reset(struct dvb_ca_en50221 *ci, int slot) -{ - struct dvb_usb_device *d = ci->data; - int ret; - struct anysee_state *state = d->priv; - - state->ci_cam_ready = jiffies + msecs_to_jiffies(1000); - - ret = anysee_wr_reg_mask(d, REG_IOA, (0 << 7), 0x80); - if (ret) - return ret; - - msleep(300); - - ret = anysee_wr_reg_mask(d, REG_IOA, (1 << 7), 0x80); - if (ret) - return ret; - - return 0; -} - -static int anysee_ci_slot_shutdown(struct dvb_ca_en50221 *ci, int slot) -{ - struct dvb_usb_device *d = ci->data; - int ret; - - ret = anysee_wr_reg_mask(d, REG_IOA, (0 << 7), 0x80); - if (ret) - return ret; - - msleep(30); - - ret = anysee_wr_reg_mask(d, REG_IOA, (1 << 7), 0x80); - if (ret) - return ret; - - return 0; -} - -static int anysee_ci_slot_ts_enable(struct dvb_ca_en50221 *ci, int slot) -{ - struct dvb_usb_device *d = ci->data; - int ret; - - ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 1), 0x02); - if (ret) - return ret; - - return 0; -} - -static int anysee_ci_poll_slot_status(struct dvb_ca_en50221 *ci, int slot, - int open) -{ - struct dvb_usb_device *d = ci->data; - struct anysee_state *state = d->priv; - int ret; - u8 tmp; - - ret = anysee_rd_reg_mask(d, REG_IOC, &tmp, 0x40); - if (ret) - return ret; - - if (tmp == 0) { - ret = DVB_CA_EN50221_POLL_CAM_PRESENT; - if (time_after(jiffies, state->ci_cam_ready)) - ret |= DVB_CA_EN50221_POLL_CAM_READY; - } - - return ret; -} - -static int anysee_ci_init(struct dvb_usb_device *d) -{ - struct anysee_state *state = d->priv; - int ret; - - state->ci.owner = THIS_MODULE; - state->ci.read_attribute_mem = anysee_ci_read_attribute_mem; - state->ci.write_attribute_mem = anysee_ci_write_attribute_mem; - state->ci.read_cam_control = anysee_ci_read_cam_control; - state->ci.write_cam_control = anysee_ci_write_cam_control; - state->ci.slot_reset = anysee_ci_slot_reset; - state->ci.slot_shutdown = anysee_ci_slot_shutdown; - state->ci.slot_ts_enable = anysee_ci_slot_ts_enable; - state->ci.poll_slot_status = anysee_ci_poll_slot_status; - state->ci.data = d; - - ret = anysee_wr_reg_mask(d, REG_IOA, (1 << 7), 0x80); - if (ret) - return ret; - - ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 2)|(0 << 1)|(0 << 0), 0x07); - if (ret) - return ret; - - ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 2)|(1 << 1)|(1 << 0), 0x07); - if (ret) - return ret; - - ret = dvb_ca_en50221_init(&d->adapter[0].dvb_adap, &state->ci, 0, 1); - if (ret) - return ret; - - return 0; -} - -static void anysee_ci_release(struct dvb_usb_device *d) -{ - struct anysee_state *state = d->priv; - - /* detach CI */ - if (state->has_ci) - dvb_ca_en50221_release(&state->ci); - - return; -} - -static int anysee_init(struct dvb_usb_device *d) -{ - struct anysee_state *state = d->priv; - int ret; - - /* LED light */ - ret = anysee_led_ctrl(d, 0x01, 0x03); - if (ret) - return ret; - - /* enable IR */ - ret = anysee_ir_ctrl(d, 1); - if (ret) - return ret; - - /* attach CI */ - if (state->has_ci) { - ret = anysee_ci_init(d); - if (ret) { - state->has_ci = false; - return ret; - } - } - - return 0; -} - -/* DVB USB Driver stuff */ -static struct dvb_usb_device_properties anysee_properties; - -static int anysee_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - struct dvb_usb_device *d; - struct usb_host_interface *alt; - int ret; - - /* There is one interface with two alternate settings. - Alternate setting 0 is for bulk transfer. - Alternate setting 1 is for isochronous transfer. - We use bulk transfer (alternate setting 0). */ - if (intf->num_altsetting < 1) - return -ENODEV; - - /* - * Anysee is always warm (its USB-bridge, Cypress FX2, uploads - * firmware from eeprom). If dvb_usb_device_init() succeeds that - * means d is a valid pointer. - */ - ret = dvb_usb_device_init(intf, &anysee_properties, THIS_MODULE, &d, - adapter_nr); - if (ret) - return ret; - - alt = usb_altnum_to_altsetting(intf, 0); - if (alt == NULL) { - deb_info("%s: no alt found!\n", __func__); - return -ENODEV; - } - - ret = usb_set_interface(d->udev, alt->desc.bInterfaceNumber, - alt->desc.bAlternateSetting); - if (ret) - return ret; - - return anysee_init(d); -} - -static void anysee_disconnect(struct usb_interface *intf) -{ - struct dvb_usb_device *d = usb_get_intfdata(intf); - - anysee_ci_release(d); - dvb_usb_device_exit(intf); - - return; -} - -static struct usb_device_id anysee_table[] = { - { USB_DEVICE(USB_VID_CYPRESS, USB_PID_ANYSEE) }, - { USB_DEVICE(USB_VID_AMT, USB_PID_ANYSEE) }, - { } /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(usb, anysee_table); - -static struct dvb_usb_device_properties anysee_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - - .size_of_priv = sizeof(struct anysee_state), - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 2, - .frontend_ctrl = anysee_frontend_ctrl, - .fe = { { - .streaming_ctrl = anysee_streaming_ctrl, - .frontend_attach = anysee_frontend_attach, - .tuner_attach = anysee_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 8, - .endpoint = 0x82, - .u = { - .bulk = { - .buffersize = (16*512), - } - } - }, - }, { - .streaming_ctrl = anysee_streaming_ctrl, - .frontend_attach = anysee_frontend_attach, - .tuner_attach = anysee_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 8, - .endpoint = 0x82, - .u = { - .bulk = { - .buffersize = (16*512), - } - } - }, - } }, - } - }, - - .rc.core = { - .rc_codes = RC_MAP_ANYSEE, - .protocol = RC_TYPE_OTHER, - .module_name = "anysee", - .rc_query = anysee_rc_query, - .rc_interval = 250, /* windows driver uses 500ms */ - }, - - .i2c_algo = &anysee_i2c_algo, - - .generic_bulk_ctrl_endpoint = 1, - - .num_device_descs = 1, - .devices = { - { - .name = "Anysee DVB USB2.0", - .cold_ids = {NULL}, - .warm_ids = {&anysee_table[0], - &anysee_table[1], NULL}, - }, - } -}; - -static struct usb_driver anysee_driver = { - .name = "dvb_usb_anysee", - .probe = anysee_probe, - .disconnect = anysee_disconnect, - .id_table = anysee_table, -}; - -module_usb_driver(anysee_driver); - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Driver Anysee E30 DVB-C & DVB-T USB2.0"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/anysee.h b/drivers/media/dvb/dvb-usb/anysee.h deleted file mode 100644 index 8ac879431540..000000000000 --- a/drivers/media/dvb/dvb-usb/anysee.h +++ /dev/null @@ -1,332 +0,0 @@ -/* - * DVB USB Linux driver for Anysee E30 DVB-C & DVB-T USB2.0 receiver - * - * Copyright (C) 2007 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * TODO: - * - add smart card reader support for Conditional Access (CA) - * - * Card reader in Anysee is nothing more than ISO 7816 card reader. - * There is no hardware CAM in any Anysee device sold. - * In my understanding it should be implemented by making own module - * for ISO 7816 card reader, like dvb_ca_en50221 is implemented. This - * module registers serial interface that can be used to communicate - * with any ISO 7816 smart card. - * - * Any help according to implement serial smart card reader support - * is highly welcome! - */ - -#ifndef _DVB_USB_ANYSEE_H_ -#define _DVB_USB_ANYSEE_H_ - -#define DVB_USB_LOG_PREFIX "anysee" -#include "dvb-usb.h" -#include "dvb_ca_en50221.h" - -#define deb_info(args...) dprintk(dvb_usb_anysee_debug, 0x01, args) -#define deb_xfer(args...) dprintk(dvb_usb_anysee_debug, 0x02, args) -#define deb_rc(args...) dprintk(dvb_usb_anysee_debug, 0x04, args) -#define deb_reg(args...) dprintk(dvb_usb_anysee_debug, 0x08, args) -#define deb_i2c(args...) dprintk(dvb_usb_anysee_debug, 0x10, args) -#define deb_fw(args...) dprintk(dvb_usb_anysee_debug, 0x20, args) - -enum cmd { - CMD_I2C_READ = 0x33, - CMD_I2C_WRITE = 0x31, - CMD_REG_READ = 0xb0, - CMD_REG_WRITE = 0xb1, - CMD_STREAMING_CTRL = 0x12, - CMD_LED_AND_IR_CTRL = 0x16, - CMD_GET_IR_CODE = 0x41, - CMD_GET_HW_INFO = 0x19, - CMD_SMARTCARD = 0x34, - CMD_CI = 0x37, -}; - -struct anysee_state { - u8 hw; /* PCB ID */ - u8 seq; - u8 fe_id:1; /* frondend ID */ - u8 has_ci:1; - struct dvb_ca_en50221 ci; - unsigned long ci_cam_ready; /* jiffies */ -}; - -#define ANYSEE_HW_507T 2 /* E30 */ -#define ANYSEE_HW_507CD 6 /* E30 Plus */ -#define ANYSEE_HW_507DC 10 /* E30 C Plus */ -#define ANYSEE_HW_507SI 11 /* E30 S2 Plus */ -#define ANYSEE_HW_507FA 15 /* E30 Combo Plus / E30 C Plus */ -#define ANYSEE_HW_508TC 18 /* E7 TC */ -#define ANYSEE_HW_508S2 19 /* E7 S2 */ -#define ANYSEE_HW_508T2C 20 /* E7 T2C */ -#define ANYSEE_HW_508PTC 21 /* E7 PTC Plus */ -#define ANYSEE_HW_508PS2 22 /* E7 PS2 Plus */ - -#define REG_IOA 0x80 /* Port A (bit addressable) */ -#define REG_IOB 0x90 /* Port B (bit addressable) */ -#define REG_IOC 0xa0 /* Port C (bit addressable) */ -#define REG_IOD 0xb0 /* Port D (bit addressable) */ -#define REG_IOE 0xb1 /* Port E (NOT bit addressable) */ -#define REG_OEA 0xb2 /* Port A Output Enable */ -#define REG_OEB 0xb3 /* Port B Output Enable */ -#define REG_OEC 0xb4 /* Port C Output Enable */ -#define REG_OED 0xb5 /* Port D Output Enable */ -#define REG_OEE 0xb6 /* Port E Output Enable */ - -#endif - -/*************************************************************************** - * USB API description (reverse engineered) - *************************************************************************** - -Transaction flow: -================= -BULK[00001] >>> REQUEST PACKET 64 bytes -BULK[00081] <<< REPLY PACKET #1 64 bytes (PREVIOUS TRANSACTION REPLY) -BULK[00081] <<< REPLY PACKET #2 64 bytes (CURRENT TRANSACTION REPLY) - -General reply packet(s) are always used if not own reply defined. - -============================================================================ -| 00-63 | GENERAL REPLY PACKET #1 (PREVIOUS REPLY) -============================================================================ -| 00 | reply data (if any) from previous transaction -| | Just same reply packet as returned during previous transaction. -| | Needed only if reply is missed in previous transaction. -| | Just skip normally. ----------------------------------------------------------------------------- -| 01-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | GENERAL REPLY PACKET #2 (CURRENT REPLY) -============================================================================ -| 00 | reply data (if any) ----------------------------------------------------------------------------- -| 01-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | I2C WRITE REQUEST PACKET -============================================================================ -| 00 | 0x31 I2C write command ----------------------------------------------------------------------------- -| 01 | i2c address ----------------------------------------------------------------------------- -| 02 | data length -| | 0x02 (for typical I2C reg / val pair) ----------------------------------------------------------------------------- -| 03 | 0x01 ----------------------------------------------------------------------------- -| 04- | data ----------------------------------------------------------------------------- -| -59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | I2C READ REQUEST PACKET -============================================================================ -| 00 | 0x33 I2C read command ----------------------------------------------------------------------------- -| 01 | i2c address + 1 ----------------------------------------------------------------------------- -| 02 | register ----------------------------------------------------------------------------- -| 03 | 0x00 ----------------------------------------------------------------------------- -| 04 | 0x00 ----------------------------------------------------------------------------- -| 05 | data length ----------------------------------------------------------------------------- -| 06-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | USB CONTROLLER REGISTER WRITE REQUEST PACKET -============================================================================ -| 00 | 0xb1 register write command ----------------------------------------------------------------------------- -| 01-02 | register ----------------------------------------------------------------------------- -| 03 | 0x01 ----------------------------------------------------------------------------- -| 04 | value ----------------------------------------------------------------------------- -| 05-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | USB CONTROLLER REGISTER READ REQUEST PACKET -============================================================================ -| 00 | 0xb0 register read command ----------------------------------------------------------------------------- -| 01-02 | register ----------------------------------------------------------------------------- -| 03 | 0x01 ----------------------------------------------------------------------------- -| 04-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | LED CONTROL REQUEST PACKET -============================================================================ -| 00 | 0x16 LED and IR control command ----------------------------------------------------------------------------- -| 01 | 0x01 (LED) ----------------------------------------------------------------------------- -| 03 | 0x00 blink -| | 0x01 lights continuously ----------------------------------------------------------------------------- -| 04 | blink interval -| | 0x00 fastest (looks like LED lights continuously) -| | 0xff slowest ----------------------------------------------------------------------------- -| 05-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | IR CONTROL REQUEST PACKET -============================================================================ -| 00 | 0x16 LED and IR control command ----------------------------------------------------------------------------- -| 01 | 0x02 (IR) ----------------------------------------------------------------------------- -| 03 | 0x00 IR disabled -| | 0x01 IR enabled ----------------------------------------------------------------------------- -| 04-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | STREAMING CONTROL REQUEST PACKET -============================================================================ -| 00 | 0x12 streaming control command ----------------------------------------------------------------------------- -| 01 | 0x00 streaming disabled -| | 0x01 streaming enabled ----------------------------------------------------------------------------- -| 02 | 0x00 ----------------------------------------------------------------------------- -| 03-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | REMOTE CONTROL REQUEST PACKET -============================================================================ -| 00 | 0x41 remote control command ----------------------------------------------------------------------------- -| 01-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | REMOTE CONTROL REPLY PACKET -============================================================================ -| 00 | 0x00 code not received -| | 0x01 code received ----------------------------------------------------------------------------- -| 01 | remote control code ----------------------------------------------------------------------------- -| 02-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | GET HARDWARE INFO REQUEST PACKET -============================================================================ -| 00 | 0x19 get hardware info command ----------------------------------------------------------------------------- -| 01-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | GET HARDWARE INFO REPLY PACKET -============================================================================ -| 00 | hardware id ----------------------------------------------------------------------------- -| 01-02 | firmware version ----------------------------------------------------------------------------- -| 03-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -============================================================================ -| 00-63 | SMART CARD READER PACKET -============================================================================ -| 00 | 0x34 smart card reader command ----------------------------------------------------------------------------- -| xx | ----------------------------------------------------------------------------- -| xx-59 | don't care ----------------------------------------------------------------------------- -| 60 | packet sequence number ----------------------------------------------------------------------------- -| 61-63 | don't care ----------------------------------------------------------------------------- - -*/ diff --git a/drivers/media/dvb/dvb-usb/au6610.c b/drivers/media/dvb/dvb-usb/au6610.c deleted file mode 100644 index 16210c060302..000000000000 --- a/drivers/media/dvb/dvb-usb/au6610.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * DVB USB Linux driver for Alcor Micro AU6610 DVB-T USB2.0. - * - * Copyright (C) 2006 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include "au6610.h" -#include "zl10353.h" -#include "qt1010.h" - -/* debug */ -static int dvb_usb_au6610_debug; -module_param_named(debug, dvb_usb_au6610_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level" DVB_USB_DEBUG_STATUS); -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -static int au6610_usb_msg(struct dvb_usb_device *d, u8 operation, u8 addr, - u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) -{ - int ret; - u16 index; - u8 *usb_buf; - - /* - * allocate enough for all known requests, - * read returns 5 and write 6 bytes - */ - usb_buf = kmalloc(6, GFP_KERNEL); - if (!usb_buf) - return -ENOMEM; - - switch (wlen) { - case 1: - index = wbuf[0] << 8; - break; - case 2: - index = wbuf[0] << 8; - index += wbuf[1]; - break; - default: - warn("wlen = %x, aborting.", wlen); - ret = -EINVAL; - goto error; - } - - ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), operation, - USB_TYPE_VENDOR|USB_DIR_IN, addr << 1, index, - usb_buf, 6, AU6610_USB_TIMEOUT); - if (ret < 0) - goto error; - - switch (operation) { - case AU6610_REQ_I2C_READ: - case AU6610_REQ_USB_READ: - /* requested value is always 5th byte in buffer */ - rbuf[0] = usb_buf[4]; - } -error: - kfree(usb_buf); - return ret; -} - -static int au6610_i2c_msg(struct dvb_usb_device *d, u8 addr, - u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) -{ - u8 request; - u8 wo = (rbuf == NULL || rlen == 0); /* write-only */ - - if (wo) { - request = AU6610_REQ_I2C_WRITE; - } else { /* rw */ - request = AU6610_REQ_I2C_READ; - } - - return au6610_usb_msg(d, request, addr, wbuf, wlen, rbuf, rlen); -} - - -/* I2C */ -static int au6610_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - int i; - - if (num > 2) - return -EINVAL; - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - for (i = 0; i < num; i++) { - /* write/read request */ - if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) { - if (au6610_i2c_msg(d, msg[i].addr, msg[i].buf, - msg[i].len, msg[i+1].buf, - msg[i+1].len) < 0) - break; - i++; - } else if (au6610_i2c_msg(d, msg[i].addr, msg[i].buf, - msg[i].len, NULL, 0) < 0) - break; - } - - mutex_unlock(&d->i2c_mutex); - return i; -} - - -static u32 au6610_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm au6610_i2c_algo = { - .master_xfer = au6610_i2c_xfer, - .functionality = au6610_i2c_func, -}; - -/* Callbacks for DVB USB */ -static struct zl10353_config au6610_zl10353_config = { - .demod_address = 0x0f, - .no_tuner = 1, - .parallel_ts = 1, -}; - -static int au6610_zl10353_frontend_attach(struct dvb_usb_adapter *adap) -{ - adap->fe_adap[0].fe = dvb_attach(zl10353_attach, &au6610_zl10353_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe == NULL) - return -ENODEV; - - return 0; -} - -static struct qt1010_config au6610_qt1010_config = { - .i2c_address = 0x62 -}; - -static int au6610_qt1010_tuner_attach(struct dvb_usb_adapter *adap) -{ - return dvb_attach(qt1010_attach, - adap->fe_adap[0].fe, &adap->dev->i2c_adap, - &au6610_qt1010_config) == NULL ? -ENODEV : 0; -} - -/* DVB USB Driver stuff */ -static struct dvb_usb_device_properties au6610_properties; - -static int au6610_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - struct dvb_usb_device *d; - struct usb_host_interface *alt; - int ret; - - if (intf->num_altsetting < AU6610_ALTSETTING_COUNT) - return -ENODEV; - - ret = dvb_usb_device_init(intf, &au6610_properties, THIS_MODULE, &d, - adapter_nr); - if (ret == 0) { - alt = usb_altnum_to_altsetting(intf, AU6610_ALTSETTING); - - if (alt == NULL) { - deb_info("%s: no alt found!\n", __func__); - return -ENODEV; - } - ret = usb_set_interface(d->udev, alt->desc.bInterfaceNumber, - alt->desc.bAlternateSetting); - } - - return ret; -} - -static struct usb_device_id au6610_table [] = { - { USB_DEVICE(USB_VID_ALCOR_MICRO, USB_PID_SIGMATEK_DVB_110) }, - { } /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(usb, au6610_table); - -static struct dvb_usb_device_properties au6610_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - - .size_of_priv = 0, - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = {{ - .frontend_attach = au6610_zl10353_frontend_attach, - .tuner_attach = au6610_qt1010_tuner_attach, - - .stream = { - .type = USB_ISOC, - .count = 5, - .endpoint = 0x82, - .u = { - .isoc = { - .framesperurb = 40, - .framesize = 942, - .interval = 1, - } - } - }, - }}, - } - }, - - .i2c_algo = &au6610_i2c_algo, - - .num_device_descs = 1, - .devices = { - { - .name = "Sigmatek DVB-110 DVB-T USB2.0", - .cold_ids = {NULL}, - .warm_ids = {&au6610_table[0], NULL}, - }, - } -}; - -static struct usb_driver au6610_driver = { - .name = "dvb_usb_au6610", - .probe = au6610_probe, - .disconnect = dvb_usb_device_exit, - .id_table = au6610_table, -}; - -module_usb_driver(au6610_driver); - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Driver for Alcor Micro AU6610 DVB-T USB2.0"); -MODULE_VERSION("0.1"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/au6610.h b/drivers/media/dvb/dvb-usb/au6610.h deleted file mode 100644 index 7849abe2c614..000000000000 --- a/drivers/media/dvb/dvb-usb/au6610.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * DVB USB Linux driver for Alcor Micro AU6610 DVB-T USB2.0. - * - * Copyright (C) 2006 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#ifndef _DVB_USB_AU6610_H_ -#define _DVB_USB_AU6610_H_ - -#define DVB_USB_LOG_PREFIX "au6610" -#include "dvb-usb.h" - -#define deb_info(args...) dprintk(dvb_usb_au6610_debug, 0x01, args) - -#define AU6610_REQ_I2C_WRITE 0x14 -#define AU6610_REQ_I2C_READ 0x13 -#define AU6610_REQ_USB_WRITE 0x16 -#define AU6610_REQ_USB_READ 0x15 - -#define AU6610_USB_TIMEOUT 1000 - -#define AU6610_ALTSETTING_COUNT 6 -#define AU6610_ALTSETTING 5 - -#endif diff --git a/drivers/media/dvb/dvb-usb/az6007.c b/drivers/media/dvb/dvb-usb/az6007.c deleted file mode 100644 index 86861e6f86d2..000000000000 --- a/drivers/media/dvb/dvb-usb/az6007.c +++ /dev/null @@ -1,955 +0,0 @@ -/* - * Driver for AzureWave 6007 DVB-C/T USB2.0 and clones - * - * Copyright (c) Henry Wang - * - * This driver was made publicly available by Terratec, at: - * http://linux.terratec.de/files/TERRATEC_H7/20110323_TERRATEC_H7_Linux.tar.gz - * The original driver's license is GPL, as declared with MODULE_LICENSE() - * - * Copyright (c) 2010-2011 Mauro Carvalho Chehab - * Driver modified by in order to work with upstream drxk driver, and - * tons of bugs got fixed. - * - * 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 under 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. - */ - -#include "drxk.h" -#include "mt2063.h" -#include "dvb_ca_en50221.h" - -#define DVB_USB_LOG_PREFIX "az6007" -#include "dvb-usb.h" - -/* debug */ -int dvb_usb_az6007_debug; -module_param_named(debug, dvb_usb_az6007_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2,rc=4 (or-able))." - DVB_USB_DEBUG_STATUS); - -#define deb_info(args...) dprintk(dvb_usb_az6007_debug, 0x01, args) -#define deb_xfer(args...) dprintk(dvb_usb_az6007_debug, 0x02, args) -#define deb_rc(args...) dprintk(dvb_usb_az6007_debug, 0x04, args) -#define deb_fe(args...) dprintk(dvb_usb_az6007_debug, 0x08, args) - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -/* Known requests (Cypress FX2 firmware + az6007 "private" ones*/ - -#define FX2_OED 0xb5 -#define AZ6007_READ_DATA 0xb7 -#define AZ6007_I2C_RD 0xb9 -#define AZ6007_POWER 0xbc -#define AZ6007_I2C_WR 0xbd -#define FX2_SCON1 0xc0 -#define AZ6007_TS_THROUGH 0xc7 -#define AZ6007_READ_IR 0xb4 - -struct az6007_device_state { - struct mutex mutex; - struct mutex ca_mutex; - struct dvb_ca_en50221 ca; - unsigned warm:1; - int (*gate_ctrl) (struct dvb_frontend *, int); - unsigned char data[4096]; -}; - -static struct drxk_config terratec_h7_drxk = { - .adr = 0x29, - .parallel_ts = true, - .dynamic_clk = true, - .single_master = true, - .enable_merr_cfg = true, - .no_i2c_bridge = false, - .chunk_size = 64, - .mpeg_out_clk_strength = 0x02, - .microcode_name = "dvb-usb-terratec-h7-drxk.fw", -}; - -static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct dvb_usb_adapter *adap = fe->sec_priv; - struct az6007_device_state *st; - int status = 0; - - deb_info("%s: %s\n", __func__, enable ? "enable" : "disable"); - - if (!adap) - return -EINVAL; - - st = adap->dev->priv; - - if (!st) - return -EINVAL; - - if (enable) - status = st->gate_ctrl(fe, 1); - else - status = st->gate_ctrl(fe, 0); - - return status; -} - -static struct mt2063_config az6007_mt2063_config = { - .tuner_address = 0x60, - .refclock = 36125000, -}; - -static int __az6007_read(struct usb_device *udev, u8 req, u16 value, - u16 index, u8 *b, int blen) -{ - int ret; - - ret = usb_control_msg(udev, - usb_rcvctrlpipe(udev, 0), - req, - USB_TYPE_VENDOR | USB_DIR_IN, - value, index, b, blen, 5000); - if (ret < 0) { - warn("usb read operation failed. (%d)", ret); - return -EIO; - } - - deb_xfer("in: req. %02x, val: %04x, ind: %04x, buffer: ", req, value, - index); - debug_dump(b, blen, deb_xfer); - - return ret; -} - -static int az6007_read(struct dvb_usb_device *d, u8 req, u16 value, - u16 index, u8 *b, int blen) -{ - struct az6007_device_state *st = d->priv; - int ret; - - if (mutex_lock_interruptible(&st->mutex) < 0) - return -EAGAIN; - - ret = __az6007_read(d->udev, req, value, index, b, blen); - - mutex_unlock(&st->mutex); - - return ret; -} - -static int __az6007_write(struct usb_device *udev, u8 req, u16 value, - u16 index, u8 *b, int blen) -{ - int ret; - - deb_xfer("out: req. %02x, val: %04x, ind: %04x, buffer: ", req, value, - index); - debug_dump(b, blen, deb_xfer); - - if (blen > 64) { - err("az6007: tried to write %d bytes, but I2C max size is 64 bytes\n", - blen); - return -EOPNOTSUPP; - } - - ret = usb_control_msg(udev, - usb_sndctrlpipe(udev, 0), - req, - USB_TYPE_VENDOR | USB_DIR_OUT, - value, index, b, blen, 5000); - if (ret != blen) { - err("usb write operation failed. (%d)", ret); - return -EIO; - } - - return 0; -} - -static int az6007_write(struct dvb_usb_device *d, u8 req, u16 value, - u16 index, u8 *b, int blen) -{ - struct az6007_device_state *st = d->priv; - int ret; - - if (mutex_lock_interruptible(&st->mutex) < 0) - return -EAGAIN; - - ret = __az6007_write(d->udev, req, value, index, b, blen); - - mutex_unlock(&st->mutex); - - return ret; -} - -static int az6007_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct dvb_usb_device *d = adap->dev; - - deb_info("%s: %s", __func__, onoff ? "enable" : "disable"); - - return az6007_write(d, 0xbc, onoff, 0, NULL, 0); -} - -/* remote control stuff (does not work with my box) */ -static int az6007_rc_query(struct dvb_usb_device *d) -{ - struct az6007_device_state *st = d->priv; - unsigned code = 0; - - az6007_read(d, AZ6007_READ_IR, 0, 0, st->data, 10); - - if (st->data[1] == 0x44) - return 0; - - if ((st->data[1] ^ st->data[2]) == 0xff) - code = st->data[1]; - else - code = st->data[1] << 8 | st->data[2]; - - if ((st->data[3] ^ st->data[4]) == 0xff) - code = code << 8 | st->data[3]; - else - code = code << 16 | st->data[3] << 8 | st->data[4]; - - rc_keydown(d->rc_dev, code, st->data[5]); - - return 0; -} - -static int az6007_ci_read_attribute_mem(struct dvb_ca_en50221 *ca, - int slot, - int address) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - struct az6007_device_state *state = (struct az6007_device_state *)d->priv; - - int ret; - u8 req; - u16 value; - u16 index; - int blen; - u8 *b; - - if (slot != 0) - return -EINVAL; - - b = kmalloc(12, GFP_KERNEL); - if (!b) - return -ENOMEM; - - mutex_lock(&state->ca_mutex); - - req = 0xC1; - value = address; - index = 0; - blen = 1; - - ret = az6007_read(d, req, value, index, b, blen); - if (ret < 0) { - warn("usb in operation failed. (%d)", ret); - ret = -EINVAL; - } else { - ret = b[0]; - } - - mutex_unlock(&state->ca_mutex); - kfree(b); - return ret; -} - -static int az6007_ci_write_attribute_mem(struct dvb_ca_en50221 *ca, - int slot, - int address, - u8 value) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - struct az6007_device_state *state = (struct az6007_device_state *)d->priv; - - int ret; - u8 req; - u16 value1; - u16 index; - int blen; - - deb_info("%s %d", __func__, slot); - if (slot != 0) - return -EINVAL; - - mutex_lock(&state->ca_mutex); - req = 0xC2; - value1 = address; - index = value; - blen = 0; - - ret = az6007_write(d, req, value1, index, NULL, blen); - if (ret != 0) - warn("usb out operation failed. (%d)", ret); - - mutex_unlock(&state->ca_mutex); - return ret; -} - -static int az6007_ci_read_cam_control(struct dvb_ca_en50221 *ca, - int slot, - u8 address) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - struct az6007_device_state *state = (struct az6007_device_state *)d->priv; - - int ret; - u8 req; - u16 value; - u16 index; - int blen; - u8 *b; - - if (slot != 0) - return -EINVAL; - - b = kmalloc(12, GFP_KERNEL); - if (!b) - return -ENOMEM; - - mutex_lock(&state->ca_mutex); - - req = 0xC3; - value = address; - index = 0; - blen = 2; - - ret = az6007_read(d, req, value, index, b, blen); - if (ret < 0) { - warn("usb in operation failed. (%d)", ret); - ret = -EINVAL; - } else { - if (b[0] == 0) - warn("Read CI IO error"); - - ret = b[1]; - deb_info("read cam data = %x from 0x%x", b[1], value); - } - - mutex_unlock(&state->ca_mutex); - kfree(b); - return ret; -} - -static int az6007_ci_write_cam_control(struct dvb_ca_en50221 *ca, - int slot, - u8 address, - u8 value) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - struct az6007_device_state *state = (struct az6007_device_state *)d->priv; - - int ret; - u8 req; - u16 value1; - u16 index; - int blen; - - if (slot != 0) - return -EINVAL; - - mutex_lock(&state->ca_mutex); - req = 0xC4; - value1 = address; - index = value; - blen = 0; - - ret = az6007_write(d, req, value1, index, NULL, blen); - if (ret != 0) { - warn("usb out operation failed. (%d)", ret); - goto failed; - } - -failed: - mutex_unlock(&state->ca_mutex); - return ret; -} - -static int CI_CamReady(struct dvb_ca_en50221 *ca, int slot) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - - int ret; - u8 req; - u16 value; - u16 index; - int blen; - u8 *b; - - b = kmalloc(12, GFP_KERNEL); - if (!b) - return -ENOMEM; - - req = 0xC8; - value = 0; - index = 0; - blen = 1; - - ret = az6007_read(d, req, value, index, b, blen); - if (ret < 0) { - warn("usb in operation failed. (%d)", ret); - ret = -EIO; - } else{ - ret = b[0]; - } - kfree(b); - return ret; -} - -static int az6007_ci_slot_reset(struct dvb_ca_en50221 *ca, int slot) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - struct az6007_device_state *state = (struct az6007_device_state *)d->priv; - - int ret, i; - u8 req; - u16 value; - u16 index; - int blen; - - mutex_lock(&state->ca_mutex); - - req = 0xC6; - value = 1; - index = 0; - blen = 0; - - ret = az6007_write(d, req, value, index, NULL, blen); - if (ret != 0) { - warn("usb out operation failed. (%d)", ret); - goto failed; - } - - msleep(500); - req = 0xC6; - value = 0; - index = 0; - blen = 0; - - ret = az6007_write(d, req, value, index, NULL, blen); - if (ret != 0) { - warn("usb out operation failed. (%d)", ret); - goto failed; - } - - for (i = 0; i < 15; i++) { - msleep(100); - - if (CI_CamReady(ca, slot)) { - deb_info("CAM Ready"); - break; - } - } - msleep(5000); - -failed: - mutex_unlock(&state->ca_mutex); - return ret; -} - -static int az6007_ci_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) -{ - return 0; -} - -static int az6007_ci_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - struct az6007_device_state *state = (struct az6007_device_state *)d->priv; - - int ret; - u8 req; - u16 value; - u16 index; - int blen; - - deb_info("%s", __func__); - mutex_lock(&state->ca_mutex); - req = 0xC7; - value = 1; - index = 0; - blen = 0; - - ret = az6007_write(d, req, value, index, NULL, blen); - if (ret != 0) { - warn("usb out operation failed. (%d)", ret); - goto failed; - } - -failed: - mutex_unlock(&state->ca_mutex); - return ret; -} - -static int az6007_ci_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - struct az6007_device_state *state = (struct az6007_device_state *)d->priv; - int ret; - u8 req; - u16 value; - u16 index; - int blen; - u8 *b; - - b = kmalloc(12, GFP_KERNEL); - if (!b) - return -ENOMEM; - mutex_lock(&state->ca_mutex); - - req = 0xC5; - value = 0; - index = 0; - blen = 1; - - ret = az6007_read(d, req, value, index, b, blen); - if (ret < 0) { - warn("usb in operation failed. (%d)", ret); - ret = -EIO; - } else - ret = 0; - - if (!ret && b[0] == 1) { - ret = DVB_CA_EN50221_POLL_CAM_PRESENT | - DVB_CA_EN50221_POLL_CAM_READY; - } - - mutex_unlock(&state->ca_mutex); - kfree(b); - return ret; -} - - -static void az6007_ci_uninit(struct dvb_usb_device *d) -{ - struct az6007_device_state *state; - - deb_info("%s", __func__); - - if (NULL == d) - return; - - state = (struct az6007_device_state *)d->priv; - if (NULL == state) - return; - - if (NULL == state->ca.data) - return; - - dvb_ca_en50221_release(&state->ca); - - memset(&state->ca, 0, sizeof(state->ca)); -} - - -static int az6007_ci_init(struct dvb_usb_adapter *a) -{ - struct dvb_usb_device *d = a->dev; - struct az6007_device_state *state = (struct az6007_device_state *)d->priv; - int ret; - - deb_info("%s", __func__); - - mutex_init(&state->ca_mutex); - - state->ca.owner = THIS_MODULE; - state->ca.read_attribute_mem = az6007_ci_read_attribute_mem; - state->ca.write_attribute_mem = az6007_ci_write_attribute_mem; - state->ca.read_cam_control = az6007_ci_read_cam_control; - state->ca.write_cam_control = az6007_ci_write_cam_control; - state->ca.slot_reset = az6007_ci_slot_reset; - state->ca.slot_shutdown = az6007_ci_slot_shutdown; - state->ca.slot_ts_enable = az6007_ci_slot_ts_enable; - state->ca.poll_slot_status = az6007_ci_poll_slot_status; - state->ca.data = d; - - ret = dvb_ca_en50221_init(&a->dvb_adap, - &state->ca, - 0, /* flags */ - 1);/* n_slots */ - if (ret != 0) { - err("Cannot initialize CI: Error %d.", ret); - memset(&state->ca, 0, sizeof(state->ca)); - return ret; - } - - deb_info("CI initialized."); - - return 0; -} - -static int az6007_read_mac_addr(struct dvb_usb_device *d, u8 mac[6]) -{ - struct az6007_device_state *st = d->priv; - int ret; - - ret = az6007_read(d, AZ6007_READ_DATA, 6, 0, st->data, 6); - memcpy(mac, st->data, 6); - - if (ret > 0) - deb_info("%s: mac is %pM\n", __func__, mac); - - return ret; -} - -static int az6007_frontend_attach(struct dvb_usb_adapter *adap) -{ - struct az6007_device_state *st = adap->dev->priv; - - deb_info("attaching demod drxk"); - - adap->fe_adap[0].fe = dvb_attach(drxk_attach, &terratec_h7_drxk, - &adap->dev->i2c_adap); - if (!adap->fe_adap[0].fe) - return -EINVAL; - - adap->fe_adap[0].fe->sec_priv = adap; - st->gate_ctrl = adap->fe_adap[0].fe->ops.i2c_gate_ctrl; - adap->fe_adap[0].fe->ops.i2c_gate_ctrl = drxk_gate_ctrl; - - az6007_ci_init(adap); - - return 0; -} - -static int az6007_tuner_attach(struct dvb_usb_adapter *adap) -{ - deb_info("attaching tuner mt2063"); - - /* Attach mt2063 to DVB-C frontend */ - if (adap->fe_adap[0].fe->ops.i2c_gate_ctrl) - adap->fe_adap[0].fe->ops.i2c_gate_ctrl(adap->fe_adap[0].fe, 1); - if (!dvb_attach(mt2063_attach, adap->fe_adap[0].fe, - &az6007_mt2063_config, - &adap->dev->i2c_adap)) - return -EINVAL; - - if (adap->fe_adap[0].fe->ops.i2c_gate_ctrl) - adap->fe_adap[0].fe->ops.i2c_gate_ctrl(adap->fe_adap[0].fe, 0); - - return 0; -} - -int az6007_power_ctrl(struct dvb_usb_device *d, int onoff) -{ - struct az6007_device_state *st = d->priv; - int ret; - - deb_info("%s()\n", __func__); - - if (!st->warm) { - mutex_init(&st->mutex); - - ret = az6007_write(d, AZ6007_POWER, 0, 2, NULL, 0); - if (ret < 0) - return ret; - msleep(60); - ret = az6007_write(d, AZ6007_POWER, 1, 4, NULL, 0); - if (ret < 0) - return ret; - msleep(100); - ret = az6007_write(d, AZ6007_POWER, 1, 3, NULL, 0); - if (ret < 0) - return ret; - msleep(20); - ret = az6007_write(d, AZ6007_POWER, 1, 4, NULL, 0); - if (ret < 0) - return ret; - - msleep(400); - ret = az6007_write(d, FX2_SCON1, 0, 3, NULL, 0); - if (ret < 0) - return ret; - msleep(150); - ret = az6007_write(d, FX2_SCON1, 1, 3, NULL, 0); - if (ret < 0) - return ret; - msleep(430); - ret = az6007_write(d, AZ6007_POWER, 0, 0, NULL, 0); - if (ret < 0) - return ret; - - st->warm = true; - - return 0; - } - - if (!onoff) - return 0; - - az6007_write(d, AZ6007_POWER, 0, 0, NULL, 0); - az6007_write(d, AZ6007_TS_THROUGH, 0, 0, NULL, 0); - - return 0; -} - -/* I2C */ -static int az6007_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - struct az6007_device_state *st = d->priv; - int i, j, len; - int ret = 0; - u16 index; - u16 value; - int length; - u8 req, addr; - - if (mutex_lock_interruptible(&st->mutex) < 0) - return -EAGAIN; - - for (i = 0; i < num; i++) { - addr = msgs[i].addr << 1; - if (((i + 1) < num) - && (msgs[i].len == 1) - && (!msgs[i].flags & I2C_M_RD) - && (msgs[i + 1].flags & I2C_M_RD) - && (msgs[i].addr == msgs[i + 1].addr)) { - /* - * A write + read xfer for the same address, where - * the first xfer has just 1 byte length. - * Need to join both into one operation - */ - if (dvb_usb_az6007_debug & 2) - printk(KERN_DEBUG - "az6007 I2C xfer write+read addr=0x%x len=%d/%d: ", - addr, msgs[i].len, msgs[i + 1].len); - req = AZ6007_I2C_RD; - index = msgs[i].buf[0]; - value = addr | (1 << 8); - length = 6 + msgs[i + 1].len; - len = msgs[i + 1].len; - ret = __az6007_read(d->udev, req, value, index, - st->data, length); - if (ret >= len) { - for (j = 0; j < len; j++) { - msgs[i + 1].buf[j] = st->data[j + 5]; - if (dvb_usb_az6007_debug & 2) - printk(KERN_CONT - "0x%02x ", - msgs[i + 1].buf[j]); - } - } else - ret = -EIO; - i++; - } else if (!(msgs[i].flags & I2C_M_RD)) { - /* write bytes */ - if (dvb_usb_az6007_debug & 2) - printk(KERN_DEBUG - "az6007 I2C xfer write addr=0x%x len=%d: ", - addr, msgs[i].len); - req = AZ6007_I2C_WR; - index = msgs[i].buf[0]; - value = addr | (1 << 8); - length = msgs[i].len - 1; - len = msgs[i].len - 1; - if (dvb_usb_az6007_debug & 2) - printk(KERN_CONT "(0x%02x) ", msgs[i].buf[0]); - for (j = 0; j < len; j++) { - st->data[j] = msgs[i].buf[j + 1]; - if (dvb_usb_az6007_debug & 2) - printk(KERN_CONT "0x%02x ", - st->data[j]); - } - ret = __az6007_write(d->udev, req, value, index, - st->data, length); - } else { - /* read bytes */ - if (dvb_usb_az6007_debug & 2) - printk(KERN_DEBUG - "az6007 I2C xfer read addr=0x%x len=%d: ", - addr, msgs[i].len); - req = AZ6007_I2C_RD; - index = msgs[i].buf[0]; - value = addr; - length = msgs[i].len + 6; - len = msgs[i].len; - ret = __az6007_read(d->udev, req, value, index, - st->data, length); - for (j = 0; j < len; j++) { - msgs[i].buf[j] = st->data[j + 5]; - if (dvb_usb_az6007_debug & 2) - printk(KERN_CONT - "0x%02x ", st->data[j + 5]); - } - } - if (dvb_usb_az6007_debug & 2) - printk(KERN_CONT "\n"); - if (ret < 0) - goto err; - } -err: - mutex_unlock(&st->mutex); - - if (ret < 0) { - info("%s ERROR: %i", __func__, ret); - return ret; - } - return num; -} - -static u32 az6007_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm az6007_i2c_algo = { - .master_xfer = az6007_i2c_xfer, - .functionality = az6007_i2c_func, -}; - -int az6007_identify_state(struct usb_device *udev, - struct dvb_usb_device_properties *props, - struct dvb_usb_device_description **desc, int *cold) -{ - int ret; - u8 *mac; - - mac = kmalloc(6, GFP_ATOMIC); - if (!mac) - return -ENOMEM; - - /* Try to read the mac address */ - ret = __az6007_read(udev, AZ6007_READ_DATA, 6, 0, mac, 6); - if (ret == 6) - *cold = 0; - else - *cold = 1; - - kfree(mac); - - if (*cold) { - __az6007_write(udev, 0x09, 1, 0, NULL, 0); - __az6007_write(udev, 0x00, 0, 0, NULL, 0); - __az6007_write(udev, 0x00, 0, 0, NULL, 0); - } - - deb_info("Device is on %s state\n", *cold ? "warm" : "cold"); - return 0; -} - -static struct dvb_usb_device_properties az6007_properties; - -static void az6007_usb_disconnect(struct usb_interface *intf) -{ - struct dvb_usb_device *d = usb_get_intfdata(intf); - az6007_ci_uninit(d); - dvb_usb_device_exit(intf); -} - -static int az6007_usb_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - return dvb_usb_device_init(intf, &az6007_properties, - THIS_MODULE, NULL, adapter_nr); -} - -static struct usb_device_id az6007_usb_table[] = { - {USB_DEVICE(USB_VID_AZUREWAVE, USB_PID_AZUREWAVE_6007)}, - {USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7)}, - {USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7_2)}, - {0}, -}; - -MODULE_DEVICE_TABLE(usb, az6007_usb_table); - -static struct dvb_usb_device_properties az6007_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - .usb_ctrl = CYPRESS_FX2, - .firmware = "dvb-usb-terratec-h7-az6007.fw", - .no_reconnect = 1, - .size_of_priv = sizeof(struct az6007_device_state), - .identify_state = az6007_identify_state, - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = {{ - .streaming_ctrl = az6007_streaming_ctrl, - .tuner_attach = az6007_tuner_attach, - .frontend_attach = az6007_frontend_attach, - - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_BULK, - .count = 10, - .endpoint = 0x02, - .u = { - .bulk = { - .buffersize = 4096, - } - } - }, - } } - } }, - .power_ctrl = az6007_power_ctrl, - .read_mac_address = az6007_read_mac_addr, - - .rc.core = { - .rc_interval = 400, - .rc_codes = RC_MAP_NEC_TERRATEC_CINERGY_XS, - .module_name = "az6007", - .rc_query = az6007_rc_query, - .allowed_protos = RC_TYPE_NEC, - }, - .i2c_algo = &az6007_i2c_algo, - - .num_device_descs = 2, - .devices = { - { .name = "AzureWave DTV StarBox DVB-T/C USB2.0 (az6007)", - .cold_ids = { &az6007_usb_table[0], NULL }, - .warm_ids = { NULL }, - }, - { .name = "TerraTec DTV StarBox DVB-T/C USB2.0 (az6007)", - .cold_ids = { &az6007_usb_table[1], &az6007_usb_table[2], NULL }, - .warm_ids = { NULL }, - }, - { NULL }, - } -}; - -/* usb specific object needed to register this driver with the usb subsystem */ -static struct usb_driver az6007_usb_driver = { - .name = "dvb_usb_az6007", - .probe = az6007_usb_probe, - .disconnect = az6007_usb_disconnect, - .id_table = az6007_usb_table, -}; - -/* module stuff */ -static int __init az6007_usb_module_init(void) -{ - int result; - deb_info("az6007 usb module init\n"); - - result = usb_register(&az6007_usb_driver); - if (result) { - err("usb_register failed. (%d)", result); - return result; - } - - return 0; -} - -static void __exit az6007_usb_module_exit(void) -{ - /* deregister this driver from the USB subsystem */ - deb_info("az6007 usb module exit\n"); - usb_deregister(&az6007_usb_driver); -} - -module_init(az6007_usb_module_init); -module_exit(az6007_usb_module_exit); - -MODULE_AUTHOR("Henry Wang "); -MODULE_AUTHOR("Mauro Carvalho Chehab "); -MODULE_DESCRIPTION("Driver for AzureWave 6007 DVB-C/T USB2.0 and clones"); -MODULE_VERSION("1.1"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/ce6230.c b/drivers/media/dvb/dvb-usb/ce6230.c deleted file mode 100644 index fa637255729c..000000000000 --- a/drivers/media/dvb/dvb-usb/ce6230.c +++ /dev/null @@ -1,324 +0,0 @@ -/* - * DVB USB Linux driver for Intel CE6230 DVB-T USB2.0 receiver - * - * Copyright (C) 2009 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "ce6230.h" -#include "zl10353.h" -#include "mxl5005s.h" - -/* debug */ -static int dvb_usb_ce6230_debug; -module_param_named(debug, dvb_usb_ce6230_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level" DVB_USB_DEBUG_STATUS); -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -static struct zl10353_config ce6230_zl10353_config; - -static int ce6230_rw_udev(struct usb_device *udev, struct req_t *req) -{ - int ret; - unsigned int pipe; - u8 request; - u8 requesttype; - u16 value; - u16 index; - u8 *buf; - - request = req->cmd; - value = req->value; - index = req->index; - - switch (req->cmd) { - case I2C_READ: - case DEMOD_READ: - case REG_READ: - requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); - break; - case I2C_WRITE: - case DEMOD_WRITE: - case REG_WRITE: - requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); - break; - default: - err("unknown command:%02x", req->cmd); - ret = -EPERM; - goto error; - } - - buf = kmalloc(req->data_len, GFP_KERNEL); - if (!buf) { - ret = -ENOMEM; - goto error; - } - - if (requesttype == (USB_TYPE_VENDOR | USB_DIR_OUT)) { - /* write */ - memcpy(buf, req->data, req->data_len); - pipe = usb_sndctrlpipe(udev, 0); - } else { - /* read */ - pipe = usb_rcvctrlpipe(udev, 0); - } - - msleep(1); /* avoid I2C errors */ - - ret = usb_control_msg(udev, pipe, request, requesttype, value, index, - buf, req->data_len, CE6230_USB_TIMEOUT); - - ce6230_debug_dump(request, requesttype, value, index, buf, - req->data_len, deb_xfer); - - if (ret < 0) - deb_info("%s: usb_control_msg failed:%d\n", __func__, ret); - else - ret = 0; - - /* read request, copy returned data to return buf */ - if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN)) - memcpy(req->data, buf, req->data_len); - - kfree(buf); -error: - return ret; -} - -static int ce6230_ctrl_msg(struct dvb_usb_device *d, struct req_t *req) -{ - return ce6230_rw_udev(d->udev, req); -} - -/* I2C */ -static int ce6230_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - int i = 0; - struct req_t req; - int ret = 0; - memset(&req, 0, sizeof(req)); - - if (num > 2) - return -EINVAL; - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - while (i < num) { - if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { - if (msg[i].addr == - ce6230_zl10353_config.demod_address) { - req.cmd = DEMOD_READ; - req.value = msg[i].addr >> 1; - req.index = msg[i].buf[0]; - req.data_len = msg[i+1].len; - req.data = &msg[i+1].buf[0]; - ret = ce6230_ctrl_msg(d, &req); - } else { - err("i2c read not implemented"); - ret = -EPERM; - } - i += 2; - } else { - if (msg[i].addr == - ce6230_zl10353_config.demod_address) { - req.cmd = DEMOD_WRITE; - req.value = msg[i].addr >> 1; - req.index = msg[i].buf[0]; - req.data_len = msg[i].len-1; - req.data = &msg[i].buf[1]; - ret = ce6230_ctrl_msg(d, &req); - } else { - req.cmd = I2C_WRITE; - req.value = 0x2000 + (msg[i].addr >> 1); - req.index = 0x0000; - req.data_len = msg[i].len; - req.data = &msg[i].buf[0]; - ret = ce6230_ctrl_msg(d, &req); - } - i += 1; - } - if (ret) - break; - } - - mutex_unlock(&d->i2c_mutex); - return ret ? ret : i; -} - -static u32 ce6230_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm ce6230_i2c_algo = { - .master_xfer = ce6230_i2c_xfer, - .functionality = ce6230_i2c_func, -}; - -/* Callbacks for DVB USB */ -static struct zl10353_config ce6230_zl10353_config = { - .demod_address = 0x1e, - .adc_clock = 450000, - .if2 = 45700, - .no_tuner = 1, - .parallel_ts = 1, - .clock_ctl_1 = 0x34, - .pll_0 = 0x0e, -}; - -static int ce6230_zl10353_frontend_attach(struct dvb_usb_adapter *adap) -{ - deb_info("%s:\n", __func__); - adap->fe_adap[0].fe = dvb_attach(zl10353_attach, &ce6230_zl10353_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe == NULL) - return -ENODEV; - return 0; -} - -static struct mxl5005s_config ce6230_mxl5003s_config = { - .i2c_address = 0xc6, - .if_freq = IF_FREQ_4570000HZ, - .xtal_freq = CRYSTAL_FREQ_16000000HZ, - .agc_mode = MXL_SINGLE_AGC, - .tracking_filter = MXL_TF_DEFAULT, - .rssi_enable = MXL_RSSI_ENABLE, - .cap_select = MXL_CAP_SEL_ENABLE, - .div_out = MXL_DIV_OUT_4, - .clock_out = MXL_CLOCK_OUT_DISABLE, - .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, - .top = MXL5005S_TOP_25P2, - .mod_mode = MXL_DIGITAL_MODE, - .if_mode = MXL_ZERO_IF, - .AgcMasterByte = 0x00, -}; - -static int ce6230_mxl5003s_tuner_attach(struct dvb_usb_adapter *adap) -{ - int ret; - deb_info("%s:\n", __func__); - ret = dvb_attach(mxl5005s_attach, adap->fe_adap[0].fe, &adap->dev->i2c_adap, - &ce6230_mxl5003s_config) == NULL ? -ENODEV : 0; - return ret; -} - -static int ce6230_power_ctrl(struct dvb_usb_device *d, int onoff) -{ - int ret; - deb_info("%s: onoff:%d\n", __func__, onoff); - - /* InterfaceNumber 1 / AlternateSetting 0 idle - InterfaceNumber 1 / AlternateSetting 1 streaming */ - ret = usb_set_interface(d->udev, 1, onoff); - if (ret) - err("usb_set_interface failed with error:%d", ret); - - return ret; -} - -/* DVB USB Driver stuff */ -static struct dvb_usb_device_properties ce6230_properties; - -static int ce6230_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - int ret = 0; - struct dvb_usb_device *d = NULL; - - deb_info("%s: interface:%d\n", __func__, - intf->cur_altsetting->desc.bInterfaceNumber); - - if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { - ret = dvb_usb_device_init(intf, &ce6230_properties, THIS_MODULE, - &d, adapter_nr); - if (ret) - err("init failed with error:%d\n", ret); - } - - return ret; -} - -static struct usb_device_id ce6230_table[] = { - { USB_DEVICE(USB_VID_INTEL, USB_PID_INTEL_CE9500) }, - { USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A310) }, - { } /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(usb, ce6230_table); - -static struct dvb_usb_device_properties ce6230_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - .no_reconnect = 1, - - .size_of_priv = 0, - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = {{ - .frontend_attach = ce6230_zl10353_frontend_attach, - .tuner_attach = ce6230_mxl5003s_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x82, - .u = { - .bulk = { - .buffersize = (16*512), - } - } - }, - }}, - } - }, - - .power_ctrl = ce6230_power_ctrl, - - .i2c_algo = &ce6230_i2c_algo, - - .num_device_descs = 2, - .devices = { - { - .name = "Intel CE9500 reference design", - .cold_ids = {NULL}, - .warm_ids = {&ce6230_table[0], NULL}, - }, - { - .name = "AVerMedia A310 USB 2.0 DVB-T tuner", - .cold_ids = {NULL}, - .warm_ids = {&ce6230_table[1], NULL}, - }, - } -}; - -static struct usb_driver ce6230_driver = { - .name = "dvb_usb_ce6230", - .probe = ce6230_probe, - .disconnect = dvb_usb_device_exit, - .id_table = ce6230_table, -}; - -module_usb_driver(ce6230_driver); - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Driver for Intel CE6230 DVB-T USB2.0"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/ce6230.h b/drivers/media/dvb/dvb-usb/ce6230.h deleted file mode 100644 index 97c42482ccb3..000000000000 --- a/drivers/media/dvb/dvb-usb/ce6230.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * DVB USB Linux driver for Intel CE6230 DVB-T USB2.0 receiver - * - * Copyright (C) 2009 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _DVB_USB_CE6230_H_ -#define _DVB_USB_CE6230_H_ - -#define DVB_USB_LOG_PREFIX "ce6230" -#include "dvb-usb.h" - -#define deb_info(args...) dprintk(dvb_usb_ce6230_debug, 0x01, args) -#define deb_rc(args...) dprintk(dvb_usb_ce6230_debug, 0x02, args) -#define deb_xfer(args...) dprintk(dvb_usb_ce6230_debug, 0x04, args) -#define deb_reg(args...) dprintk(dvb_usb_ce6230_debug, 0x08, args) -#define deb_i2c(args...) dprintk(dvb_usb_ce6230_debug, 0x10, args) -#define deb_fw(args...) dprintk(dvb_usb_ce6230_debug, 0x20, args) - -#define ce6230_debug_dump(r, t, v, i, b, l, func) { \ - int loop_; \ - func("%02x %02x %02x %02x %02x %02x %02x %02x", \ - t, r, v & 0xff, v >> 8, i & 0xff, i >> 8, l & 0xff, l >> 8); \ - if (t == (USB_TYPE_VENDOR | USB_DIR_OUT)) \ - func(" >>> "); \ - else \ - func(" <<< "); \ - for (loop_ = 0; loop_ < l; loop_++) \ - func("%02x ", b[loop_]); \ - func("\n");\ -} - -#define CE6230_USB_TIMEOUT 1000 - -struct req_t { - u8 cmd; /* [1] */ - u16 value; /* [2|3] */ - u16 index; /* [4|5] */ - u16 data_len; /* [6|7] */ - u8 *data; -}; - -enum ce6230_cmd { - CONFIG_READ = 0xd0, /* rd 0 (unclear) */ - UNKNOWN_WRITE = 0xc7, /* wr 7 (unclear) */ - I2C_READ = 0xd9, /* rd 9 (unclear) */ - I2C_WRITE = 0xca, /* wr a */ - DEMOD_READ = 0xdb, /* rd b */ - DEMOD_WRITE = 0xcc, /* wr c */ - REG_READ = 0xde, /* rd e */ - REG_WRITE = 0xcf, /* wr f */ -}; - -#endif diff --git a/drivers/media/dvb/dvb-usb/dib0700_core.c b/drivers/media/dvb/dvb-usb/dib0700_core.c deleted file mode 100644 index 7e9e00fae04e..000000000000 --- a/drivers/media/dvb/dvb-usb/dib0700_core.c +++ /dev/null @@ -1,845 +0,0 @@ -/* Linux driver for devices based on the DiBcom DiB0700 USB bridge - * - * 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, version 2. - * - * Copyright (C) 2005-6 DiBcom, SA - */ -#include "dib0700.h" - -/* debug */ -int dvb_usb_dib0700_debug; -module_param_named(debug,dvb_usb_dib0700_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=info,2=fw,4=fwdata,8=data (or-able))." DVB_USB_DEBUG_STATUS); - -static int nb_packet_buffer_size = 21; -module_param(nb_packet_buffer_size, int, 0644); -MODULE_PARM_DESC(nb_packet_buffer_size, - "Set the dib0700 driver data buffer size. This parameter " - "corresponds to the number of TS packets. The actual size of " - "the data buffer corresponds to this parameter " - "multiplied by 188 (default: 21)"); - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - - -int dib0700_get_version(struct dvb_usb_device *d, u32 *hwversion, - u32 *romversion, u32 *ramversion, u32 *fwtype) -{ - struct dib0700_state *st = d->priv; - int ret; - - if (mutex_lock_interruptible(&d->usb_mutex) < 0) { - err("could not acquire lock"); - return -EINTR; - } - - ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), - REQUEST_GET_VERSION, - USB_TYPE_VENDOR | USB_DIR_IN, 0, 0, - st->buf, 16, USB_CTRL_GET_TIMEOUT); - if (hwversion != NULL) - *hwversion = (st->buf[0] << 24) | (st->buf[1] << 16) | - (st->buf[2] << 8) | st->buf[3]; - if (romversion != NULL) - *romversion = (st->buf[4] << 24) | (st->buf[5] << 16) | - (st->buf[6] << 8) | st->buf[7]; - if (ramversion != NULL) - *ramversion = (st->buf[8] << 24) | (st->buf[9] << 16) | - (st->buf[10] << 8) | st->buf[11]; - if (fwtype != NULL) - *fwtype = (st->buf[12] << 24) | (st->buf[13] << 16) | - (st->buf[14] << 8) | st->buf[15]; - mutex_unlock(&d->usb_mutex); - return ret; -} - -/* expecting rx buffer: request data[0] data[1] ... data[2] */ -static int dib0700_ctrl_wr(struct dvb_usb_device *d, u8 *tx, u8 txlen) -{ - int status; - - deb_data(">>> "); - debug_dump(tx, txlen, deb_data); - - status = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev,0), - tx[0], USB_TYPE_VENDOR | USB_DIR_OUT, 0, 0, tx, txlen, - USB_CTRL_GET_TIMEOUT); - - if (status != txlen) - deb_data("ep 0 write error (status = %d, len: %d)\n",status,txlen); - - return status < 0 ? status : 0; -} - -/* expecting tx buffer: request data[0] ... data[n] (n <= 4) */ -int dib0700_ctrl_rd(struct dvb_usb_device *d, u8 *tx, u8 txlen, u8 *rx, u8 rxlen) -{ - u16 index, value; - int status; - - if (txlen < 2) { - err("tx buffer length is smaller than 2. Makes no sense."); - return -EINVAL; - } - if (txlen > 4) { - err("tx buffer length is larger than 4. Not supported."); - return -EINVAL; - } - - deb_data(">>> "); - debug_dump(tx,txlen,deb_data); - - value = ((txlen - 2) << 8) | tx[1]; - index = 0; - if (txlen > 2) - index |= (tx[2] << 8); - if (txlen > 3) - index |= tx[3]; - - status = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev,0), tx[0], - USB_TYPE_VENDOR | USB_DIR_IN, value, index, rx, rxlen, - USB_CTRL_GET_TIMEOUT); - - if (status < 0) - deb_info("ep 0 read error (status = %d)\n",status); - - deb_data("<<< "); - debug_dump(rx, rxlen, deb_data); - - return status; /* length in case of success */ -} - -int dib0700_set_gpio(struct dvb_usb_device *d, enum dib07x0_gpios gpio, u8 gpio_dir, u8 gpio_val) -{ - struct dib0700_state *st = d->priv; - int ret; - - if (mutex_lock_interruptible(&d->usb_mutex) < 0) { - err("could not acquire lock"); - return -EINTR; - } - - st->buf[0] = REQUEST_SET_GPIO; - st->buf[1] = gpio; - st->buf[2] = ((gpio_dir & 0x01) << 7) | ((gpio_val & 0x01) << 6); - - ret = dib0700_ctrl_wr(d, st->buf, 3); - - mutex_unlock(&d->usb_mutex); - return ret; -} - -static int dib0700_set_usb_xfer_len(struct dvb_usb_device *d, u16 nb_ts_packets) -{ - struct dib0700_state *st = d->priv; - int ret; - - if (st->fw_version >= 0x10201) { - if (mutex_lock_interruptible(&d->usb_mutex) < 0) { - err("could not acquire lock"); - return -EINTR; - } - - st->buf[0] = REQUEST_SET_USB_XFER_LEN; - st->buf[1] = (nb_ts_packets >> 8) & 0xff; - st->buf[2] = nb_ts_packets & 0xff; - - deb_info("set the USB xfer len to %i Ts packet\n", nb_ts_packets); - - ret = dib0700_ctrl_wr(d, st->buf, 3); - mutex_unlock(&d->usb_mutex); - } else { - deb_info("this firmware does not allow to change the USB xfer len\n"); - ret = -EIO; - } - - return ret; -} - -/* - * I2C master xfer function (supported in 1.20 firmware) - */ -static int dib0700_i2c_xfer_new(struct i2c_adapter *adap, struct i2c_msg *msg, - int num) -{ - /* The new i2c firmware messages are more reliable and in particular - properly support i2c read calls not preceded by a write */ - - struct dvb_usb_device *d = i2c_get_adapdata(adap); - struct dib0700_state *st = d->priv; - uint8_t bus_mode = 1; /* 0=eeprom bus, 1=frontend bus */ - uint8_t gen_mode = 0; /* 0=master i2c, 1=gpio i2c */ - uint8_t en_start = 0; - uint8_t en_stop = 0; - int result, i; - - /* Ensure nobody else hits the i2c bus while we're sending our - sequence of messages, (such as the remote control thread) */ - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EINTR; - - for (i = 0; i < num; i++) { - if (i == 0) { - /* First message in the transaction */ - en_start = 1; - } else if (!(msg[i].flags & I2C_M_NOSTART)) { - /* Device supports repeated-start */ - en_start = 1; - } else { - /* Not the first packet and device doesn't support - repeated start */ - en_start = 0; - } - if (i == (num - 1)) { - /* Last message in the transaction */ - en_stop = 1; - } - - if (msg[i].flags & I2C_M_RD) { - /* Read request */ - u16 index, value; - uint8_t i2c_dest; - - i2c_dest = (msg[i].addr << 1); - value = ((en_start << 7) | (en_stop << 6) | - (msg[i].len & 0x3F)) << 8 | i2c_dest; - /* I2C ctrl + FE bus; */ - index = ((gen_mode << 6) & 0xC0) | - ((bus_mode << 4) & 0x30); - - result = usb_control_msg(d->udev, - usb_rcvctrlpipe(d->udev, 0), - REQUEST_NEW_I2C_READ, - USB_TYPE_VENDOR | USB_DIR_IN, - value, index, msg[i].buf, - msg[i].len, - USB_CTRL_GET_TIMEOUT); - if (result < 0) { - deb_info("i2c read error (status = %d)\n", result); - break; - } - - deb_data("<<< "); - debug_dump(msg[i].buf, msg[i].len, deb_data); - - } else { - /* Write request */ - if (mutex_lock_interruptible(&d->usb_mutex) < 0) { - err("could not acquire lock"); - mutex_unlock(&d->i2c_mutex); - return -EINTR; - } - st->buf[0] = REQUEST_NEW_I2C_WRITE; - st->buf[1] = msg[i].addr << 1; - st->buf[2] = (en_start << 7) | (en_stop << 6) | - (msg[i].len & 0x3F); - /* I2C ctrl + FE bus; */ - st->buf[3] = ((gen_mode << 6) & 0xC0) | - ((bus_mode << 4) & 0x30); - /* The Actual i2c payload */ - memcpy(&st->buf[4], msg[i].buf, msg[i].len); - - deb_data(">>> "); - debug_dump(st->buf, msg[i].len + 4, deb_data); - - result = usb_control_msg(d->udev, - usb_sndctrlpipe(d->udev, 0), - REQUEST_NEW_I2C_WRITE, - USB_TYPE_VENDOR | USB_DIR_OUT, - 0, 0, st->buf, msg[i].len + 4, - USB_CTRL_GET_TIMEOUT); - mutex_unlock(&d->usb_mutex); - if (result < 0) { - deb_info("i2c write error (status = %d)\n", result); - break; - } - } - } - mutex_unlock(&d->i2c_mutex); - return i; -} - -/* - * I2C master xfer function (pre-1.20 firmware) - */ -static int dib0700_i2c_xfer_legacy(struct i2c_adapter *adap, - struct i2c_msg *msg, int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - struct dib0700_state *st = d->priv; - int i,len; - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EINTR; - if (mutex_lock_interruptible(&d->usb_mutex) < 0) { - err("could not acquire lock"); - mutex_unlock(&d->i2c_mutex); - return -EINTR; - } - - for (i = 0; i < num; i++) { - /* fill in the address */ - st->buf[1] = msg[i].addr << 1; - /* fill the buffer */ - memcpy(&st->buf[2], msg[i].buf, msg[i].len); - - /* write/read request */ - if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) { - st->buf[0] = REQUEST_I2C_READ; - st->buf[1] |= 1; - - /* special thing in the current firmware: when length is zero the read-failed */ - len = dib0700_ctrl_rd(d, st->buf, msg[i].len + 2, - msg[i+1].buf, msg[i+1].len); - if (len <= 0) { - deb_info("I2C read failed on address 0x%02x\n", - msg[i].addr); - break; - } - - msg[i+1].len = len; - - i++; - } else { - st->buf[0] = REQUEST_I2C_WRITE; - if (dib0700_ctrl_wr(d, st->buf, msg[i].len + 2) < 0) - break; - } - } - mutex_unlock(&d->usb_mutex); - mutex_unlock(&d->i2c_mutex); - - return i; -} - -static int dib0700_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - struct dib0700_state *st = d->priv; - - if (st->fw_use_new_i2c_api == 1) { - /* User running at least fw 1.20 */ - return dib0700_i2c_xfer_new(adap, msg, num); - } else { - /* Use legacy calls */ - return dib0700_i2c_xfer_legacy(adap, msg, num); - } -} - -static u32 dib0700_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -struct i2c_algorithm dib0700_i2c_algo = { - .master_xfer = dib0700_i2c_xfer, - .functionality = dib0700_i2c_func, -}; - -int dib0700_identify_state(struct usb_device *udev, struct dvb_usb_device_properties *props, - struct dvb_usb_device_description **desc, int *cold) -{ - s16 ret; - u8 *b; - - b = kmalloc(16, GFP_KERNEL); - if (!b) - return -ENOMEM; - - - ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), - REQUEST_GET_VERSION, USB_TYPE_VENDOR | USB_DIR_IN, 0, 0, b, 16, USB_CTRL_GET_TIMEOUT); - - deb_info("FW GET_VERSION length: %d\n",ret); - - *cold = ret <= 0; - deb_info("cold: %d\n", *cold); - - kfree(b); - return 0; -} - -static int dib0700_set_clock(struct dvb_usb_device *d, u8 en_pll, - u8 pll_src, u8 pll_range, u8 clock_gpio3, u16 pll_prediv, - u16 pll_loopdiv, u16 free_div, u16 dsuScaler) -{ - struct dib0700_state *st = d->priv; - int ret; - - if (mutex_lock_interruptible(&d->usb_mutex) < 0) { - err("could not acquire lock"); - return -EINTR; - } - - st->buf[0] = REQUEST_SET_CLOCK; - st->buf[1] = (en_pll << 7) | (pll_src << 6) | - (pll_range << 5) | (clock_gpio3 << 4); - st->buf[2] = (pll_prediv >> 8) & 0xff; /* MSB */ - st->buf[3] = pll_prediv & 0xff; /* LSB */ - st->buf[4] = (pll_loopdiv >> 8) & 0xff; /* MSB */ - st->buf[5] = pll_loopdiv & 0xff; /* LSB */ - st->buf[6] = (free_div >> 8) & 0xff; /* MSB */ - st->buf[7] = free_div & 0xff; /* LSB */ - st->buf[8] = (dsuScaler >> 8) & 0xff; /* MSB */ - st->buf[9] = dsuScaler & 0xff; /* LSB */ - - ret = dib0700_ctrl_wr(d, st->buf, 10); - mutex_unlock(&d->usb_mutex); - - return ret; -} - -int dib0700_set_i2c_speed(struct dvb_usb_device *d, u16 scl_kHz) -{ - struct dib0700_state *st = d->priv; - u16 divider; - int ret; - - if (scl_kHz == 0) - return -EINVAL; - - if (mutex_lock_interruptible(&d->usb_mutex) < 0) { - err("could not acquire lock"); - return -EINTR; - } - - st->buf[0] = REQUEST_SET_I2C_PARAM; - divider = (u16) (30000 / scl_kHz); - st->buf[1] = 0; - st->buf[2] = (u8) (divider >> 8); - st->buf[3] = (u8) (divider & 0xff); - divider = (u16) (72000 / scl_kHz); - st->buf[4] = (u8) (divider >> 8); - st->buf[5] = (u8) (divider & 0xff); - divider = (u16) (72000 / scl_kHz); /* clock: 72MHz */ - st->buf[6] = (u8) (divider >> 8); - st->buf[7] = (u8) (divider & 0xff); - - deb_info("setting I2C speed: %04x %04x %04x (%d kHz).", - (st->buf[2] << 8) | (st->buf[3]), (st->buf[4] << 8) | - st->buf[5], (st->buf[6] << 8) | st->buf[7], scl_kHz); - - ret = dib0700_ctrl_wr(d, st->buf, 8); - mutex_unlock(&d->usb_mutex); - - return ret; -} - - -int dib0700_ctrl_clock(struct dvb_usb_device *d, u32 clk_MHz, u8 clock_out_gp3) -{ - switch (clk_MHz) { - case 72: dib0700_set_clock(d, 1, 0, 1, clock_out_gp3, 2, 24, 0, 0x4c); break; - default: return -EINVAL; - } - return 0; -} - -static int dib0700_jumpram(struct usb_device *udev, u32 address) -{ - int ret = 0, actlen; - u8 *buf; - - buf = kmalloc(8, GFP_KERNEL); - if (!buf) - return -ENOMEM; - buf[0] = REQUEST_JUMPRAM; - buf[1] = 0; - buf[2] = 0; - buf[3] = 0; - buf[4] = (address >> 24) & 0xff; - buf[5] = (address >> 16) & 0xff; - buf[6] = (address >> 8) & 0xff; - buf[7] = address & 0xff; - - if ((ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, 0x01),buf,8,&actlen,1000)) < 0) { - deb_fw("jumpram to 0x%x failed\n",address); - goto out; - } - if (actlen != 8) { - deb_fw("jumpram to 0x%x failed\n",address); - ret = -EIO; - goto out; - } -out: - kfree(buf); - return ret; -} - -int dib0700_download_firmware(struct usb_device *udev, const struct firmware *fw) -{ - struct hexline hx; - int pos = 0, ret, act_len, i, adap_num; - u8 *buf; - u32 fw_version; - - buf = kmalloc(260, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - while ((ret = dvb_usb_get_hexline(fw, &hx, &pos)) > 0) { - deb_fwdata("writing to address 0x%08x (buffer: 0x%02x %02x)\n", - hx.addr, hx.len, hx.chk); - - buf[0] = hx.len; - buf[1] = (hx.addr >> 8) & 0xff; - buf[2] = hx.addr & 0xff; - buf[3] = hx.type; - memcpy(&buf[4],hx.data,hx.len); - buf[4+hx.len] = hx.chk; - - ret = usb_bulk_msg(udev, - usb_sndbulkpipe(udev, 0x01), - buf, - hx.len + 5, - &act_len, - 1000); - - if (ret < 0) { - err("firmware download failed at %d with %d",pos,ret); - goto out; - } - } - - if (ret == 0) { - /* start the firmware */ - if ((ret = dib0700_jumpram(udev, 0x70000000)) == 0) { - info("firmware started successfully."); - msleep(500); - } - } else - ret = -EIO; - - /* the number of ts packet has to be at least 1 */ - if (nb_packet_buffer_size < 1) - nb_packet_buffer_size = 1; - - /* get the fimware version */ - usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), - REQUEST_GET_VERSION, - USB_TYPE_VENDOR | USB_DIR_IN, 0, 0, - buf, 16, USB_CTRL_GET_TIMEOUT); - fw_version = (buf[8] << 24) | (buf[9] << 16) | (buf[10] << 8) | buf[11]; - - /* set the buffer size - DVB-USB is allocating URB buffers - * only after the firwmare download was successful */ - for (i = 0; i < dib0700_device_count; i++) { - for (adap_num = 0; adap_num < dib0700_devices[i].num_adapters; - adap_num++) { - if (fw_version >= 0x10201) { - dib0700_devices[i].adapter[adap_num].fe[0].stream.u.bulk.buffersize = 188*nb_packet_buffer_size; - } else { - /* for fw version older than 1.20.1, - * the buffersize has to be n times 512 */ - dib0700_devices[i].adapter[adap_num].fe[0].stream.u.bulk.buffersize = ((188*nb_packet_buffer_size+188/2)/512)*512; - if (dib0700_devices[i].adapter[adap_num].fe[0].stream.u.bulk.buffersize < 512) - dib0700_devices[i].adapter[adap_num].fe[0].stream.u.bulk.buffersize = 512; - } - } - } -out: - kfree(buf); - return ret; -} - -int dib0700_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct dib0700_state *st = adap->dev->priv; - int ret; - - if ((onoff != 0) && (st->fw_version >= 0x10201)) { - /* for firmware later than 1.20.1, - * the USB xfer length can be set */ - ret = dib0700_set_usb_xfer_len(adap->dev, - st->nb_packet_buffer_size); - if (ret < 0) { - deb_info("can not set the USB xfer len\n"); - return ret; - } - } - - if (mutex_lock_interruptible(&adap->dev->usb_mutex) < 0) { - err("could not acquire lock"); - return -EINTR; - } - - st->buf[0] = REQUEST_ENABLE_VIDEO; - /* this bit gives a kind of command, - * rather than enabling something or not */ - st->buf[1] = (onoff << 4) | 0x00; - - if (st->disable_streaming_master_mode == 1) - st->buf[2] = 0x00; - else - st->buf[2] = 0x01 << 4; /* Master mode */ - - st->buf[3] = 0x00; - - deb_info("modifying (%d) streaming state for %d\n", onoff, adap->id); - - st->channel_state &= ~0x3; - if ((adap->fe_adap[0].stream.props.endpoint != 2) - && (adap->fe_adap[0].stream.props.endpoint != 3)) { - deb_info("the endpoint number (%i) is not correct, use the adapter id instead", adap->fe_adap[0].stream.props.endpoint); - if (onoff) - st->channel_state |= 1 << (adap->id); - else - st->channel_state |= 1 << ~(adap->id); - } else { - if (onoff) - st->channel_state |= 1 << (adap->fe_adap[0].stream.props.endpoint-2); - else - st->channel_state |= 1 << (3-adap->fe_adap[0].stream.props.endpoint); - } - - st->buf[2] |= st->channel_state; - - deb_info("data for streaming: %x %x\n", st->buf[1], st->buf[2]); - - ret = dib0700_ctrl_wr(adap->dev, st->buf, 4); - mutex_unlock(&adap->dev->usb_mutex); - - return ret; -} - -int dib0700_change_protocol(struct rc_dev *rc, u64 rc_type) -{ - struct dvb_usb_device *d = rc->priv; - struct dib0700_state *st = d->priv; - int new_proto, ret; - - if (mutex_lock_interruptible(&d->usb_mutex) < 0) { - err("could not acquire lock"); - return -EINTR; - } - - st->buf[0] = REQUEST_SET_RC; - st->buf[1] = 0; - st->buf[2] = 0; - - /* Set the IR mode */ - if (rc_type == RC_TYPE_RC5) - new_proto = 1; - else if (rc_type == RC_TYPE_NEC) - new_proto = 0; - else if (rc_type == RC_TYPE_RC6) { - if (st->fw_version < 0x10200) { - ret = -EINVAL; - goto out; - } - - new_proto = 2; - } else { - ret = -EINVAL; - goto out; - } - - st->buf[1] = new_proto; - - ret = dib0700_ctrl_wr(d, st->buf, 3); - if (ret < 0) { - err("ir protocol setup failed"); - goto out; - } - - d->props.rc.core.protocol = rc_type; - -out: - mutex_unlock(&d->usb_mutex); - return ret; -} - -/* Number of keypresses to ignore before start repeating */ -#define RC_REPEAT_DELAY_V1_20 10 - -/* This is the structure of the RC response packet starting in firmware 1.20 */ -struct dib0700_rc_response { - u8 report_id; - u8 data_state; - union { - u16 system16; - struct { - u8 not_system; - u8 system; - }; - }; - u8 data; - u8 not_data; -}; -#define RC_MSG_SIZE_V1_20 6 - -static void dib0700_rc_urb_completion(struct urb *purb) -{ - struct dvb_usb_device *d = purb->context; - struct dib0700_rc_response *poll_reply; - u32 uninitialized_var(keycode); - u8 toggle; - - deb_info("%s()\n", __func__); - if (d->rc_dev == NULL) { - /* This will occur if disable_rc_polling=1 */ - kfree(purb->transfer_buffer); - usb_free_urb(purb); - return; - } - - poll_reply = purb->transfer_buffer; - - if (purb->status < 0) { - deb_info("discontinuing polling\n"); - kfree(purb->transfer_buffer); - usb_free_urb(purb); - return; - } - - if (purb->actual_length != RC_MSG_SIZE_V1_20) { - deb_info("malformed rc msg size=%d\n", purb->actual_length); - goto resubmit; - } - - deb_data("IR ID = %02X state = %02X System = %02X %02X Cmd = %02X %02X (len %d)\n", - poll_reply->report_id, poll_reply->data_state, - poll_reply->system, poll_reply->not_system, - poll_reply->data, poll_reply->not_data, - purb->actual_length); - - switch (d->props.rc.core.protocol) { - case RC_TYPE_NEC: - toggle = 0; - - /* NEC protocol sends repeat code as 0 0 0 FF */ - if ((poll_reply->system == 0x00) && (poll_reply->data == 0x00) - && (poll_reply->not_data == 0xff)) { - poll_reply->data_state = 2; - break; - } - - if ((poll_reply->system ^ poll_reply->not_system) != 0xff) { - deb_data("NEC extended protocol\n"); - /* NEC extended code - 24 bits */ - keycode = be16_to_cpu(poll_reply->system16) << 8 | poll_reply->data; - } else { - deb_data("NEC normal protocol\n"); - /* normal NEC code - 16 bits */ - keycode = poll_reply->system << 8 | poll_reply->data; - } - - break; - default: - deb_data("RC5 protocol\n"); - /* RC5 Protocol */ - toggle = poll_reply->report_id; - keycode = poll_reply->system << 8 | poll_reply->data; - - break; - } - - if ((poll_reply->data + poll_reply->not_data) != 0xff) { - /* Key failed integrity check */ - err("key failed integrity check: %04x %02x %02x", - poll_reply->system, - poll_reply->data, poll_reply->not_data); - goto resubmit; - } - - rc_keydown(d->rc_dev, keycode, toggle); - -resubmit: - /* Clean the buffer before we requeue */ - memset(purb->transfer_buffer, 0, RC_MSG_SIZE_V1_20); - - /* Requeue URB */ - usb_submit_urb(purb, GFP_ATOMIC); -} - -int dib0700_rc_setup(struct dvb_usb_device *d) -{ - struct dib0700_state *st = d->priv; - struct urb *purb; - int ret; - - /* Poll-based. Don't initialize bulk mode */ - if (st->fw_version < 0x10200) - return 0; - - /* Starting in firmware 1.20, the RC info is provided on a bulk pipe */ - purb = usb_alloc_urb(0, GFP_KERNEL); - if (purb == NULL) { - err("rc usb alloc urb failed\n"); - return -ENOMEM; - } - - purb->transfer_buffer = kzalloc(RC_MSG_SIZE_V1_20, GFP_KERNEL); - if (purb->transfer_buffer == NULL) { - err("rc kzalloc failed\n"); - usb_free_urb(purb); - return -ENOMEM; - } - - purb->status = -EINPROGRESS; - usb_fill_bulk_urb(purb, d->udev, usb_rcvbulkpipe(d->udev, 1), - purb->transfer_buffer, RC_MSG_SIZE_V1_20, - dib0700_rc_urb_completion, d); - - ret = usb_submit_urb(purb, GFP_ATOMIC); - if (ret) { - err("rc submit urb failed\n"); - kfree(purb->transfer_buffer); - usb_free_urb(purb); - } - - return ret; -} - -static int dib0700_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - int i; - struct dvb_usb_device *dev; - - for (i = 0; i < dib0700_device_count; i++) - if (dvb_usb_device_init(intf, &dib0700_devices[i], THIS_MODULE, - &dev, adapter_nr) == 0) { - struct dib0700_state *st = dev->priv; - u32 hwversion, romversion, fw_version, fwtype; - - dib0700_get_version(dev, &hwversion, &romversion, - &fw_version, &fwtype); - - deb_info("Firmware version: %x, %d, 0x%x, %d\n", - hwversion, romversion, fw_version, fwtype); - - st->fw_version = fw_version; - st->nb_packet_buffer_size = (u32)nb_packet_buffer_size; - - /* Disable polling mode on newer firmwares */ - if (st->fw_version >= 0x10200) - dev->props.rc.core.bulk_mode = true; - else - dev->props.rc.core.bulk_mode = false; - - dib0700_rc_setup(dev); - - return 0; - } - - return -ENODEV; -} - -static struct usb_driver dib0700_driver = { - .name = "dvb_usb_dib0700", - .probe = dib0700_probe, - .disconnect = dvb_usb_device_exit, - .id_table = dib0700_usb_id_table, -}; - -module_usb_driver(dib0700_driver); - -MODULE_FIRMWARE("dvb-usb-dib0700-1.20.fw"); -MODULE_AUTHOR("Patrick Boettcher "); -MODULE_DESCRIPTION("Driver for devices based on DiBcom DiB0700 - USB bridge"); -MODULE_VERSION("1.0"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/dvb-usb-dvb.c b/drivers/media/dvb/dvb-usb/dvb-usb-dvb.c deleted file mode 100644 index ddf282f355b3..000000000000 --- a/drivers/media/dvb/dvb-usb/dvb-usb-dvb.c +++ /dev/null @@ -1,289 +0,0 @@ -/* dvb-usb-dvb.c is part of the DVB USB library. - * - * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@desy.de) - * see dvb-usb-init.c for copyright information. - * - * This file contains functions for initializing and handling the - * linux-dvb API. - */ -#include "dvb-usb-common.h" - -/* does the complete input transfer handling */ -static int dvb_usb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff) -{ - struct dvb_usb_adapter *adap = dvbdmxfeed->demux->priv; - int newfeedcount, ret; - - if (adap == NULL) - return -ENODEV; - - if ((adap->active_fe < 0) || - (adap->active_fe >= adap->num_frontends_initialized)) { - return -EINVAL; - } - - newfeedcount = adap->feedcount + (onoff ? 1 : -1); - - /* stop feed before setting a new pid if there will be no pid anymore */ - if (newfeedcount == 0) { - deb_ts("stop feeding\n"); - usb_urb_kill(&adap->fe_adap[adap->active_fe].stream); - - if (adap->props.fe[adap->active_fe].streaming_ctrl != NULL) { - ret = adap->props.fe[adap->active_fe].streaming_ctrl(adap, 0); - if (ret < 0) { - err("error while stopping stream."); - return ret; - } - } - } - - adap->feedcount = newfeedcount; - - /* activate the pid on the device specific pid_filter */ - deb_ts("setting pid (%s): %5d %04x at index %d '%s'\n", - adap->fe_adap[adap->active_fe].pid_filtering ? - "yes" : "no", dvbdmxfeed->pid, dvbdmxfeed->pid, - dvbdmxfeed->index, onoff ? "on" : "off"); - if (adap->props.fe[adap->active_fe].caps & DVB_USB_ADAP_HAS_PID_FILTER && - adap->fe_adap[adap->active_fe].pid_filtering && - adap->props.fe[adap->active_fe].pid_filter != NULL) - adap->props.fe[adap->active_fe].pid_filter(adap, dvbdmxfeed->index, dvbdmxfeed->pid, onoff); - - /* start the feed if this was the first feed and there is still a feed - * for reception. - */ - if (adap->feedcount == onoff && adap->feedcount > 0) { - deb_ts("submitting all URBs\n"); - usb_urb_submit(&adap->fe_adap[adap->active_fe].stream); - - deb_ts("controlling pid parser\n"); - if (adap->props.fe[adap->active_fe].caps & DVB_USB_ADAP_HAS_PID_FILTER && - adap->props.fe[adap->active_fe].caps & - DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF && - adap->props.fe[adap->active_fe].pid_filter_ctrl != NULL) { - ret = adap->props.fe[adap->active_fe].pid_filter_ctrl(adap, - adap->fe_adap[adap->active_fe].pid_filtering); - if (ret < 0) { - err("could not handle pid_parser"); - return ret; - } - } - deb_ts("start feeding\n"); - if (adap->props.fe[adap->active_fe].streaming_ctrl != NULL) { - ret = adap->props.fe[adap->active_fe].streaming_ctrl(adap, 1); - if (ret < 0) { - err("error while enabling fifo."); - return ret; - } - } - - } - return 0; -} - -static int dvb_usb_start_feed(struct dvb_demux_feed *dvbdmxfeed) -{ - deb_ts("start pid: 0x%04x, feedtype: %d\n", dvbdmxfeed->pid,dvbdmxfeed->type); - return dvb_usb_ctrl_feed(dvbdmxfeed,1); -} - -static int dvb_usb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) -{ - deb_ts("stop pid: 0x%04x, feedtype: %d\n", dvbdmxfeed->pid, dvbdmxfeed->type); - return dvb_usb_ctrl_feed(dvbdmxfeed,0); -} - -int dvb_usb_adapter_dvb_init(struct dvb_usb_adapter *adap, short *adapter_nums) -{ - int i; - int ret = dvb_register_adapter(&adap->dvb_adap, adap->dev->desc->name, - adap->dev->owner, &adap->dev->udev->dev, - adapter_nums); - - if (ret < 0) { - deb_info("dvb_register_adapter failed: error %d", ret); - goto err; - } - adap->dvb_adap.priv = adap; - adap->dvb_adap.fe_ioctl_override = adap->props.fe_ioctl_override; - - if (adap->dev->props.read_mac_address) { - if (adap->dev->props.read_mac_address(adap->dev,adap->dvb_adap.proposed_mac) == 0) - info("MAC address: %pM",adap->dvb_adap.proposed_mac); - else - err("MAC address reading failed."); - } - - - adap->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING; - adap->demux.priv = adap; - - adap->demux.filternum = 0; - for (i = 0; i < adap->props.num_frontends; i++) { - if (adap->demux.filternum < adap->fe_adap[i].max_feed_count) - adap->demux.filternum = adap->fe_adap[i].max_feed_count; - } - adap->demux.feednum = adap->demux.filternum; - adap->demux.start_feed = dvb_usb_start_feed; - adap->demux.stop_feed = dvb_usb_stop_feed; - adap->demux.write_to_decoder = NULL; - if ((ret = dvb_dmx_init(&adap->demux)) < 0) { - err("dvb_dmx_init failed: error %d",ret); - goto err_dmx; - } - - adap->dmxdev.filternum = adap->demux.filternum; - adap->dmxdev.demux = &adap->demux.dmx; - adap->dmxdev.capabilities = 0; - if ((ret = dvb_dmxdev_init(&adap->dmxdev, &adap->dvb_adap)) < 0) { - err("dvb_dmxdev_init failed: error %d",ret); - goto err_dmx_dev; - } - - if ((ret = dvb_net_init(&adap->dvb_adap, &adap->dvb_net, - &adap->demux.dmx)) < 0) { - err("dvb_net_init failed: error %d",ret); - goto err_net_init; - } - - adap->state |= DVB_USB_ADAP_STATE_DVB; - return 0; - -err_net_init: - dvb_dmxdev_release(&adap->dmxdev); -err_dmx_dev: - dvb_dmx_release(&adap->demux); -err_dmx: - dvb_unregister_adapter(&adap->dvb_adap); -err: - return ret; -} - -int dvb_usb_adapter_dvb_exit(struct dvb_usb_adapter *adap) -{ - if (adap->state & DVB_USB_ADAP_STATE_DVB) { - deb_info("unregistering DVB part\n"); - dvb_net_release(&adap->dvb_net); - adap->demux.dmx.close(&adap->demux.dmx); - dvb_dmxdev_release(&adap->dmxdev); - dvb_dmx_release(&adap->demux); - dvb_unregister_adapter(&adap->dvb_adap); - adap->state &= ~DVB_USB_ADAP_STATE_DVB; - } - return 0; -} - -static int dvb_usb_set_active_fe(struct dvb_frontend *fe, int onoff) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - - int ret = (adap->props.frontend_ctrl) ? - adap->props.frontend_ctrl(fe, onoff) : 0; - - if (ret < 0) { - err("frontend_ctrl request failed"); - return ret; - } - if (onoff) - adap->active_fe = fe->id; - - return 0; -} - -static int dvb_usb_fe_wakeup(struct dvb_frontend *fe) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - - dvb_usb_device_power_ctrl(adap->dev, 1); - - dvb_usb_set_active_fe(fe, 1); - - if (adap->fe_adap[fe->id].fe_init) - adap->fe_adap[fe->id].fe_init(fe); - - return 0; -} - -static int dvb_usb_fe_sleep(struct dvb_frontend *fe) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - - if (adap->fe_adap[fe->id].fe_sleep) - adap->fe_adap[fe->id].fe_sleep(fe); - - dvb_usb_set_active_fe(fe, 0); - - return dvb_usb_device_power_ctrl(adap->dev, 0); -} - -int dvb_usb_adapter_frontend_init(struct dvb_usb_adapter *adap) -{ - int ret, i; - - /* register all given adapter frontends */ - for (i = 0; i < adap->props.num_frontends; i++) { - - if (adap->props.fe[i].frontend_attach == NULL) { - err("strange: '%s' #%d,%d " - "doesn't want to attach a frontend.", - adap->dev->desc->name, adap->id, i); - - return 0; - } - - ret = adap->props.fe[i].frontend_attach(adap); - if (ret || adap->fe_adap[i].fe == NULL) { - /* only print error when there is no FE at all */ - if (i == 0) - err("no frontend was attached by '%s'", - adap->dev->desc->name); - - return 0; - } - - adap->fe_adap[i].fe->id = i; - - /* re-assign sleep and wakeup functions */ - adap->fe_adap[i].fe_init = adap->fe_adap[i].fe->ops.init; - adap->fe_adap[i].fe->ops.init = dvb_usb_fe_wakeup; - adap->fe_adap[i].fe_sleep = adap->fe_adap[i].fe->ops.sleep; - adap->fe_adap[i].fe->ops.sleep = dvb_usb_fe_sleep; - - if (dvb_register_frontend(&adap->dvb_adap, adap->fe_adap[i].fe)) { - err("Frontend %d registration failed.", i); - dvb_frontend_detach(adap->fe_adap[i].fe); - adap->fe_adap[i].fe = NULL; - /* In error case, do not try register more FEs, - * still leaving already registered FEs alive. */ - if (i == 0) - return -ENODEV; - else - return 0; - } - - /* only attach the tuner if the demod is there */ - if (adap->props.fe[i].tuner_attach != NULL) - adap->props.fe[i].tuner_attach(adap); - - adap->num_frontends_initialized++; - } - - return 0; -} - -int dvb_usb_adapter_frontend_exit(struct dvb_usb_adapter *adap) -{ - int i = adap->num_frontends_initialized - 1; - - /* unregister all given adapter frontends */ - for (; i >= 0; i--) { - if (adap->fe_adap[i].fe != NULL) { - dvb_unregister_frontend(adap->fe_adap[i].fe); - dvb_frontend_detach(adap->fe_adap[i].fe); - } - } - adap->num_frontends_initialized = 0; - - return 0; -} diff --git a/drivers/media/dvb/dvb-usb/dvb-usb-ids.h b/drivers/media/dvb/dvb-usb/dvb-usb-ids.h deleted file mode 100644 index 26c44818a5ab..000000000000 --- a/drivers/media/dvb/dvb-usb/dvb-usb-ids.h +++ /dev/null @@ -1,361 +0,0 @@ -/* dvb-usb-ids.h is part of the DVB USB library. - * - * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) see - * dvb-usb-init.c for copyright information. - * - * a header file containing define's for the USB device supported by the - * various drivers. - */ -#ifndef _DVB_USB_IDS_H_ -#define _DVB_USB_IDS_H_ - -/* Vendor IDs */ -#define USB_VID_ADSTECH 0x06e1 -#define USB_VID_AFATECH 0x15a4 -#define USB_VID_ALCOR_MICRO 0x058f -#define USB_VID_ALINK 0x05e3 -#define USB_VID_AMT 0x1c73 -#define USB_VID_ANCHOR 0x0547 -#define USB_VID_ANSONIC 0x10b9 -#define USB_VID_ANUBIS_ELECTRONIC 0x10fd -#define USB_VID_ASUS 0x0b05 -#define USB_VID_AVERMEDIA 0x07ca -#define USB_VID_COMPRO 0x185b -#define USB_VID_COMPRO_UNK 0x145f -#define USB_VID_CONEXANT 0x0572 -#define USB_VID_CYPRESS 0x04b4 -#define USB_VID_DIBCOM 0x10b8 -#define USB_VID_DPOSH 0x1498 -#define USB_VID_DVICO 0x0fe9 -#define USB_VID_E3C 0x18b4 -#define USB_VID_ELGATO 0x0fd9 -#define USB_VID_EMPIA 0xeb1a -#define USB_VID_GENPIX 0x09c0 -#define USB_VID_GRANDTEC 0x5032 -#define USB_VID_GTEK 0x1f4d -#define USB_VID_HANFTEK 0x15f4 -#define USB_VID_HAUPPAUGE 0x2040 -#define USB_VID_HYPER_PALTEK 0x1025 -#define USB_VID_INTEL 0x8086 -#define USB_VID_ITETECH 0x048d -#define USB_VID_KWORLD 0xeb2a -#define USB_VID_KWORLD_2 0x1b80 -#define USB_VID_KYE 0x0458 -#define USB_VID_LEADTEK 0x0413 -#define USB_VID_LITEON 0x04ca -#define USB_VID_MEDION 0x1660 -#define USB_VID_MIGLIA 0x18f3 -#define USB_VID_MSI 0x0db0 -#define USB_VID_MSI_2 0x1462 -#define USB_VID_OPERA1 0x695c -#define USB_VID_PINNACLE 0x2304 -#define USB_VID_PCTV 0x2013 -#define USB_VID_PIXELVIEW 0x1554 -#define USB_VID_REALTEK 0x0bda -#define USB_VID_TECHNOTREND 0x0b48 -#define USB_VID_TERRATEC 0x0ccd -#define USB_VID_TELESTAR 0x10b9 -#define USB_VID_VISIONPLUS 0x13d3 -#define USB_VID_SONY 0x1415 -#define USB_VID_TWINHAN 0x1822 -#define USB_VID_ULTIMA_ELECTRONIC 0x05d8 -#define USB_VID_UNIWILL 0x1584 -#define USB_VID_WIDEVIEW 0x14aa -#define USB_VID_GIGABYTE 0x1044 -#define USB_VID_YUAN 0x1164 -#define USB_VID_XTENSIONS 0x1ae7 -#define USB_VID_HUMAX_COEX 0x10b9 -#define USB_VID_774 0x7a69 -#define USB_VID_EVOLUTEPC 0x1e59 -#define USB_VID_AZUREWAVE 0x13d3 -#define USB_VID_TECHNISAT 0x14f7 - -/* Product IDs */ -#define USB_PID_ADSTECH_USB2_COLD 0xa333 -#define USB_PID_ADSTECH_USB2_WARM 0xa334 -#define USB_PID_AFATECH_AF9005 0x9020 -#define USB_PID_AFATECH_AF9015_9015 0x9015 -#define USB_PID_AFATECH_AF9015_9016 0x9016 -#define USB_PID_AFATECH_AF9035_1000 0x1000 -#define USB_PID_AFATECH_AF9035_1001 0x1001 -#define USB_PID_AFATECH_AF9035_1002 0x1002 -#define USB_PID_AFATECH_AF9035_1003 0x1003 -#define USB_PID_AFATECH_AF9035_9035 0x9035 -#define USB_PID_TREKSTOR_DVBT 0x901b -#define USB_VID_ALINK_DTU 0xf170 -#define USB_PID_ANSONIC_DVBT_USB 0x6000 -#define USB_PID_ANYSEE 0x861f -#define USB_PID_AZUREWAVE_AD_TU700 0x3237 -#define USB_PID_AZUREWAVE_6007 0x0ccd -#define USB_PID_AVERMEDIA_DVBT_USB_COLD 0x0001 -#define USB_PID_AVERMEDIA_DVBT_USB_WARM 0x0002 -#define USB_PID_AVERMEDIA_DVBT_USB2_COLD 0xa800 -#define USB_PID_AVERMEDIA_DVBT_USB2_WARM 0xa801 -#define USB_PID_COMPRO_DVBU2000_COLD 0xd000 -#define USB_PID_COMPRO_DVBU2000_WARM 0xd001 -#define USB_PID_COMPRO_DVBU2000_UNK_COLD 0x010c -#define USB_PID_COMPRO_DVBU2000_UNK_WARM 0x010d -#define USB_PID_COMPRO_VIDEOMATE_U500 0x1e78 -#define USB_PID_COMPRO_VIDEOMATE_U500_PC 0x1e80 -#define USB_PID_CONCEPTRONIC_CTVDIGRCU 0xe397 -#define USB_PID_CONEXANT_D680_DMB 0x86d6 -#define USB_PID_CREATIX_CTX1921 0x1921 -#define USB_PID_DELOCK_USB2_DVBT 0xb803 -#define USB_PID_DIBCOM_HOOK_DEFAULT 0x0064 -#define USB_PID_DIBCOM_HOOK_DEFAULT_REENUM 0x0065 -#define USB_PID_DIBCOM_MOD3000_COLD 0x0bb8 -#define USB_PID_DIBCOM_MOD3000_WARM 0x0bb9 -#define USB_PID_DIBCOM_MOD3001_COLD 0x0bc6 -#define USB_PID_DIBCOM_MOD3001_WARM 0x0bc7 -#define USB_PID_DIBCOM_STK7700P 0x1e14 -#define USB_PID_DIBCOM_STK7700P_PC 0x1e78 -#define USB_PID_DIBCOM_STK7700D 0x1ef0 -#define USB_PID_DIBCOM_STK7700_U7000 0x7001 -#define USB_PID_DIBCOM_STK7070P 0x1ebc -#define USB_PID_DIBCOM_STK7070PD 0x1ebe -#define USB_PID_DIBCOM_STK807XP 0x1f90 -#define USB_PID_DIBCOM_STK807XPVR 0x1f98 -#define USB_PID_DIBCOM_STK8096GP 0x1fa0 -#define USB_PID_DIBCOM_NIM8096MD 0x1fa8 -#define USB_PID_DIBCOM_TFE8096P 0x1f9C -#define USB_PID_DIBCOM_ANCHOR_2135_COLD 0x2131 -#define USB_PID_DIBCOM_STK7770P 0x1e80 -#define USB_PID_DIBCOM_NIM7090 0x1bb2 -#define USB_PID_DIBCOM_TFE7090PVR 0x1bb4 -#define USB_PID_DIBCOM_TFE7090E 0x1bb7 -#define USB_PID_DIBCOM_TFE7790E 0x1e6e -#define USB_PID_DIBCOM_NIM9090M 0x2383 -#define USB_PID_DIBCOM_NIM9090MD 0x2384 -#define USB_PID_DPOSH_M9206_COLD 0x9206 -#define USB_PID_DPOSH_M9206_WARM 0xa090 -#define USB_PID_E3C_EC168 0x1689 -#define USB_PID_E3C_EC168_2 0xfffa -#define USB_PID_E3C_EC168_3 0xfffb -#define USB_PID_E3C_EC168_4 0x1001 -#define USB_PID_E3C_EC168_5 0x1002 -#define USB_PID_FREECOM_DVBT 0x0160 -#define USB_PID_FREECOM_DVBT_2 0x0161 -#define USB_PID_UNIWILL_STK7700P 0x6003 -#define USB_PID_GENIUS_TVGO_DVB_T03 0x4012 -#define USB_PID_GRANDTEC_DVBT_USB_COLD 0x0fa0 -#define USB_PID_GRANDTEC_DVBT_USB_WARM 0x0fa1 -#define USB_PID_INTEL_CE9500 0x9500 -#define USB_PID_ITETECH_IT9135 0x9135 -#define USB_PID_ITETECH_IT9135_9005 0x9005 -#define USB_PID_ITETECH_IT9135_9006 0x9006 -#define USB_PID_KWORLD_399U 0xe399 -#define USB_PID_KWORLD_399U_2 0xe400 -#define USB_PID_KWORLD_395U 0xe396 -#define USB_PID_KWORLD_395U_2 0xe39b -#define USB_PID_KWORLD_395U_3 0xe395 -#define USB_PID_KWORLD_395U_4 0xe39a -#define USB_PID_KWORLD_MC810 0xc810 -#define USB_PID_KWORLD_PC160_2T 0xc160 -#define USB_PID_KWORLD_PC160_T 0xc161 -#define USB_PID_KWORLD_UB383_T 0xe383 -#define USB_PID_KWORLD_UB499_2T_T09 0xe409 -#define USB_PID_KWORLD_VSTREAM_COLD 0x17de -#define USB_PID_KWORLD_VSTREAM_WARM 0x17df -#define USB_PID_TERRATEC_CINERGY_T_USB_XE 0x0055 -#define USB_PID_TERRATEC_CINERGY_T_USB_XE_REV2 0x0069 -#define USB_PID_TERRATEC_CINERGY_T_STICK 0x0093 -#define USB_PID_TERRATEC_CINERGY_T_STICK_RC 0x0097 -#define USB_PID_TERRATEC_CINERGY_T_STICK_DUAL_RC 0x0099 -#define USB_PID_TERRATEC_CINERGY_T_STICK_BLACK_REV1 0x00a9 -#define USB_PID_TWINHAN_VP7041_COLD 0x3201 -#define USB_PID_TWINHAN_VP7041_WARM 0x3202 -#define USB_PID_TWINHAN_VP7020_COLD 0x3203 -#define USB_PID_TWINHAN_VP7020_WARM 0x3204 -#define USB_PID_TWINHAN_VP7045_COLD 0x3205 -#define USB_PID_TWINHAN_VP7045_WARM 0x3206 -#define USB_PID_TWINHAN_VP7021_COLD 0x3207 -#define USB_PID_TWINHAN_VP7021_WARM 0x3208 -#define USB_PID_TINYTWIN 0x3226 -#define USB_PID_TINYTWIN_2 0xe402 -#define USB_PID_TINYTWIN_3 0x9016 -#define USB_PID_DNTV_TINYUSB2_COLD 0x3223 -#define USB_PID_DNTV_TINYUSB2_WARM 0x3224 -#define USB_PID_ULTIMA_TVBOX_COLD 0x8105 -#define USB_PID_ULTIMA_TVBOX_WARM 0x8106 -#define USB_PID_ULTIMA_TVBOX_AN2235_COLD 0x8107 -#define USB_PID_ULTIMA_TVBOX_AN2235_WARM 0x8108 -#define USB_PID_ULTIMA_TVBOX_ANCHOR_COLD 0x2235 -#define USB_PID_ULTIMA_TVBOX_USB2_COLD 0x8109 -#define USB_PID_ULTIMA_TVBOX_USB2_WARM 0x810a -#define USB_PID_ARTEC_T14_COLD 0x810b -#define USB_PID_ARTEC_T14_WARM 0x810c -#define USB_PID_ARTEC_T14BR 0x810f -#define USB_PID_ULTIMA_TVBOX_USB2_FX_COLD 0x8613 -#define USB_PID_ULTIMA_TVBOX_USB2_FX_WARM 0x1002 -#define USB_PID_UNK_HYPER_PALTEK_COLD 0x005e -#define USB_PID_UNK_HYPER_PALTEK_WARM 0x005f -#define USB_PID_HANFTEK_UMT_010_COLD 0x0001 -#define USB_PID_HANFTEK_UMT_010_WARM 0x0015 -#define USB_PID_DTT200U_COLD 0x0201 -#define USB_PID_DTT200U_WARM 0x0301 -#define USB_PID_WT220U_ZAP250_COLD 0x0220 -#define USB_PID_WT220U_COLD 0x0222 -#define USB_PID_WT220U_WARM 0x0221 -#define USB_PID_WT220U_FC_COLD 0x0225 -#define USB_PID_WT220U_FC_WARM 0x0226 -#define USB_PID_WT220U_ZL0353_COLD 0x022a -#define USB_PID_WT220U_ZL0353_WARM 0x022b -#define USB_PID_WINTV_NOVA_T_USB2_COLD 0x9300 -#define USB_PID_WINTV_NOVA_T_USB2_WARM 0x9301 -#define USB_PID_HAUPPAUGE_NOVA_T_500 0x9941 -#define USB_PID_HAUPPAUGE_NOVA_T_500_2 0x9950 -#define USB_PID_HAUPPAUGE_NOVA_T_500_3 0x8400 -#define USB_PID_HAUPPAUGE_NOVA_T_STICK 0x7050 -#define USB_PID_HAUPPAUGE_NOVA_T_STICK_2 0x7060 -#define USB_PID_HAUPPAUGE_NOVA_T_STICK_3 0x7070 -#define USB_PID_HAUPPAUGE_MYTV_T 0x7080 -#define USB_PID_HAUPPAUGE_NOVA_TD_STICK 0x9580 -#define USB_PID_HAUPPAUGE_NOVA_TD_STICK_52009 0x5200 -#define USB_PID_HAUPPAUGE_TIGER_ATSC 0xb200 -#define USB_PID_HAUPPAUGE_TIGER_ATSC_B210 0xb210 -#define USB_PID_AVERMEDIA_EXPRESS 0xb568 -#define USB_PID_AVERMEDIA_VOLAR 0xa807 -#define USB_PID_AVERMEDIA_VOLAR_2 0xb808 -#define USB_PID_AVERMEDIA_VOLAR_A868R 0xa868 -#define USB_PID_AVERMEDIA_MCE_USB_M038 0x1228 -#define USB_PID_AVERMEDIA_HYBRID_ULTRA_USB_M039R 0x0039 -#define USB_PID_AVERMEDIA_HYBRID_ULTRA_USB_M039R_ATSC 0x1039 -#define USB_PID_AVERMEDIA_HYBRID_ULTRA_USB_M039R_DVBT 0x2039 -#define USB_PID_AVERMEDIA_VOLAR_X 0xa815 -#define USB_PID_AVERMEDIA_VOLAR_X_2 0x8150 -#define USB_PID_AVERMEDIA_A309 0xa309 -#define USB_PID_AVERMEDIA_A310 0xa310 -#define USB_PID_AVERMEDIA_A850 0x850a -#define USB_PID_AVERMEDIA_A850T 0x850b -#define USB_PID_AVERMEDIA_A805 0xa805 -#define USB_PID_AVERMEDIA_A815M 0x815a -#define USB_PID_AVERMEDIA_A835 0xa835 -#define USB_PID_AVERMEDIA_B835 0xb835 -#define USB_PID_AVERMEDIA_1867 0x1867 -#define USB_PID_AVERMEDIA_A867 0xa867 -#define USB_PID_AVERMEDIA_TWINSTAR 0x0825 -#define USB_PID_TECHNOTREND_CONNECT_S2400 0x3006 -#define USB_PID_TECHNOTREND_CONNECT_CT3650 0x300d -#define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY 0x005a -#define USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY_2 0x0081 -#define USB_PID_TERRATEC_CINERGY_HT_USB_XE 0x0058 -#define USB_PID_TERRATEC_CINERGY_HT_EXPRESS 0x0060 -#define USB_PID_TERRATEC_CINERGY_T_EXPRESS 0x0062 -#define USB_PID_TERRATEC_CINERGY_T_XXS 0x0078 -#define USB_PID_TERRATEC_CINERGY_T_XXS_2 0x00ab -#define USB_PID_TERRATEC_H7 0x10b4 -#define USB_PID_TERRATEC_H7_2 0x10a3 -#define USB_PID_TERRATEC_T3 0x10a0 -#define USB_PID_TERRATEC_T5 0x10a1 -#define USB_PID_NOXON_DAB_STICK 0x00b3 -#define USB_PID_PINNACLE_EXPRESSCARD_320CX 0x022e -#define USB_PID_PINNACLE_PCTV2000E 0x022c -#define USB_PID_PINNACLE_PCTV_DVB_T_FLASH 0x0228 -#define USB_PID_PINNACLE_PCTV_DUAL_DIVERSITY_DVB_T 0x0229 -#define USB_PID_PINNACLE_PCTV71E 0x022b -#define USB_PID_PINNACLE_PCTV72E 0x0236 -#define USB_PID_PINNACLE_PCTV73E 0x0237 -#define USB_PID_PINNACLE_PCTV310E 0x3211 -#define USB_PID_PINNACLE_PCTV801E 0x023a -#define USB_PID_PINNACLE_PCTV801E_SE 0x023b -#define USB_PID_PINNACLE_PCTV340E 0x023d -#define USB_PID_PINNACLE_PCTV340E_SE 0x023e -#define USB_PID_PINNACLE_PCTV73A 0x0243 -#define USB_PID_PINNACLE_PCTV73ESE 0x0245 -#define USB_PID_PINNACLE_PCTV74E 0x0246 -#define USB_PID_PINNACLE_PCTV282E 0x0248 -#define USB_PID_PIXELVIEW_SBTVD 0x5010 -#define USB_PID_PCTV_200E 0x020e -#define USB_PID_PCTV_400E 0x020f -#define USB_PID_PCTV_450E 0x0222 -#define USB_PID_PCTV_452E 0x021f -#define USB_PID_REALTEK_RTL2831U 0x2831 -#define USB_PID_REALTEK_RTL2832U 0x2832 -#define USB_PID_TECHNOTREND_CONNECT_S2_3600 0x3007 -#define USB_PID_TECHNOTREND_CONNECT_S2_3650_CI 0x300a -#define USB_PID_NEBULA_DIGITV 0x0201 -#define USB_PID_DVICO_BLUEBIRD_LGDT 0xd820 -#define USB_PID_DVICO_BLUEBIRD_LG064F_COLD 0xd500 -#define USB_PID_DVICO_BLUEBIRD_LG064F_WARM 0xd501 -#define USB_PID_DVICO_BLUEBIRD_LGZ201_COLD 0xdb00 -#define USB_PID_DVICO_BLUEBIRD_LGZ201_WARM 0xdb01 -#define USB_PID_DVICO_BLUEBIRD_TH7579_COLD 0xdb10 -#define USB_PID_DVICO_BLUEBIRD_TH7579_WARM 0xdb11 -#define USB_PID_DVICO_BLUEBIRD_DUAL_1_COLD 0xdb50 -#define USB_PID_DVICO_BLUEBIRD_DUAL_1_WARM 0xdb51 -#define USB_PID_DVICO_BLUEBIRD_DUAL_2_COLD 0xdb58 -#define USB_PID_DVICO_BLUEBIRD_DUAL_2_WARM 0xdb59 -#define USB_PID_DVICO_BLUEBIRD_DUAL_4 0xdb78 -#define USB_PID_DVICO_BLUEBIRD_DUAL_4_REV_2 0xdb98 -#define USB_PID_DVICO_BLUEBIRD_DVB_T_NANO_2 0xdb70 -#define USB_PID_DVICO_BLUEBIRD_DVB_T_NANO_2_NFW_WARM 0xdb71 -#define USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_COLD 0xdb54 -#define USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_WARM 0xdb55 -#define USB_PID_MEDION_MD95700 0x0932 -#define USB_PID_MSI_MEGASKY580 0x5580 -#define USB_PID_MSI_MEGASKY580_55801 0x5581 -#define USB_PID_KYE_DVB_T_COLD 0x701e -#define USB_PID_KYE_DVB_T_WARM 0x701f -#define USB_PID_LITEON_DVB_T_COLD 0xf000 -#define USB_PID_LITEON_DVB_T_WARM 0xf001 -#define USB_PID_DIGIVOX_MINI_SL_COLD 0xe360 -#define USB_PID_DIGIVOX_MINI_SL_WARM 0xe361 -#define USB_PID_GRANDTEC_DVBT_USB2_COLD 0x0bc6 -#define USB_PID_GRANDTEC_DVBT_USB2_WARM 0x0bc7 -#define USB_PID_WINFAST_DTV2000DS 0x6a04 -#define USB_PID_WINFAST_DTV_DONGLE_COLD 0x6025 -#define USB_PID_WINFAST_DTV_DONGLE_WARM 0x6026 -#define USB_PID_WINFAST_DTV_DONGLE_STK7700P 0x6f00 -#define USB_PID_WINFAST_DTV_DONGLE_H 0x60f6 -#define USB_PID_WINFAST_DTV_DONGLE_STK7700P_2 0x6f01 -#define USB_PID_WINFAST_DTV_DONGLE_GOLD 0x6029 -#define USB_PID_GENPIX_8PSK_REV_1_COLD 0x0200 -#define USB_PID_GENPIX_8PSK_REV_1_WARM 0x0201 -#define USB_PID_GENPIX_8PSK_REV_2 0x0202 -#define USB_PID_GENPIX_SKYWALKER_1 0x0203 -#define USB_PID_GENPIX_SKYWALKER_CW3K 0x0204 -#define USB_PID_GENPIX_SKYWALKER_2 0x0206 -#define USB_PID_SIGMATEK_DVB_110 0x6610 -#define USB_PID_MSI_DIGI_VOX_MINI_II 0x1513 -#define USB_PID_MSI_DIGIVOX_DUO 0x8801 -#define USB_PID_OPERA1_COLD 0x2830 -#define USB_PID_OPERA1_WARM 0x3829 -#define USB_PID_LIFEVIEW_TV_WALKER_TWIN_COLD 0x0514 -#define USB_PID_LIFEVIEW_TV_WALKER_TWIN_WARM 0x0513 -#define USB_PID_GIGABYTE_U7000 0x7001 -#define USB_PID_GIGABYTE_U8000 0x7002 -#define USB_PID_ASUS_U3000 0x171f -#define USB_PID_ASUS_U3000H 0x1736 -#define USB_PID_ASUS_U3100 0x173f -#define USB_PID_YUAN_EC372S 0x1edc -#define USB_PID_YUAN_STK7700PH 0x1f08 -#define USB_PID_YUAN_PD378S 0x2edc -#define USB_PID_YUAN_MC770 0x0871 -#define USB_PID_YUAN_STK7700D 0x1efc -#define USB_PID_YUAN_STK7700D_2 0x1e8c -#define USB_PID_DW2102 0x2102 -#define USB_PID_XTENSIONS_XD_380 0x0381 -#define USB_PID_TELESTAR_STARSTICK_2 0x8000 -#define USB_PID_MSI_DIGI_VOX_MINI_III 0x8807 -#define USB_PID_SONY_PLAYTV 0x0003 -#define USB_PID_MYGICA_D689 0xd811 -#define USB_PID_ELGATO_EYETV_DIVERSITY 0x0011 -#define USB_PID_ELGATO_EYETV_DTT 0x0021 -#define USB_PID_ELGATO_EYETV_DTT_2 0x003f -#define USB_PID_ELGATO_EYETV_DTT_Dlx 0x0020 -#define USB_PID_ELGATO_EYETV_SAT 0x002a -#define USB_PID_DVB_T_USB_STICK_HIGH_SPEED_COLD 0x5000 -#define USB_PID_DVB_T_USB_STICK_HIGH_SPEED_WARM 0x5001 -#define USB_PID_FRIIO_WHITE 0x0001 -#define USB_PID_TVWAY_PLUS 0x0002 -#define USB_PID_SVEON_STV20 0xe39d -#define USB_PID_SVEON_STV22 0xe401 -#define USB_PID_SVEON_STV22_IT9137 0xe411 -#define USB_PID_AZUREWAVE_AZ6027 0x3275 -#define USB_PID_TERRATEC_DVBS2CI_V1 0x10a4 -#define USB_PID_TERRATEC_DVBS2CI_V2 0x10ac -#define USB_PID_TECHNISAT_USB2_HDCI_V1 0x0001 -#define USB_PID_TECHNISAT_USB2_HDCI_V2 0x0002 -#define USB_PID_TECHNISAT_AIRSTAR_TELESTICK_2 0x0004 -#define USB_PID_TECHNISAT_USB2_DVB_S2 0x0500 -#endif diff --git a/drivers/media/dvb/dvb-usb/dvb-usb.h b/drivers/media/dvb/dvb-usb/dvb-usb.h deleted file mode 100644 index 99f94409efa1..000000000000 --- a/drivers/media/dvb/dvb-usb/dvb-usb.h +++ /dev/null @@ -1,485 +0,0 @@ -/* dvb-usb.h is part of the DVB USB library. - * - * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@desy.de) - * see dvb-usb-init.c for copyright information. - * - * the headerfile, all dvb-usb-drivers have to include. - * - * TODO: clean-up the structures for unused fields and update the comments - */ -#ifndef __DVB_USB_H__ -#define __DVB_USB_H__ - -#include -#include -#include -#include -#include - -#include "dvb_frontend.h" -#include "dvb_demux.h" -#include "dvb_net.h" -#include "dmxdev.h" - -#include "dvb-pll.h" - -#include "dvb-usb-ids.h" - -/* debug */ -#ifdef CONFIG_DVB_USB_DEBUG -#define dprintk(var,level,args...) \ - do { if ((var & level)) { printk(args); } } while (0) - -#define debug_dump(b,l,func) {\ - int loop_; \ - for (loop_ = 0; loop_ < l; loop_++) func("%02x ", b[loop_]); \ - func("\n");\ -} -#define DVB_USB_DEBUG_STATUS -#else -#define dprintk(args...) -#define debug_dump(b,l,func) - -#define DVB_USB_DEBUG_STATUS " (debugging is not enabled)" - -#endif - -/* generic log methods - taken from usb.h */ -#ifndef DVB_USB_LOG_PREFIX - #define DVB_USB_LOG_PREFIX "dvb-usb (please define a log prefix)" -#endif - -#undef err -#define err(format, arg...) printk(KERN_ERR DVB_USB_LOG_PREFIX ": " format "\n" , ## arg) -#undef info -#define info(format, arg...) printk(KERN_INFO DVB_USB_LOG_PREFIX ": " format "\n" , ## arg) -#undef warn -#define warn(format, arg...) printk(KERN_WARNING DVB_USB_LOG_PREFIX ": " format "\n" , ## arg) - -/** - * struct dvb_usb_device_description - name and its according USB IDs - * @name: real name of the box, regardless which DVB USB device class is in use - * @cold_ids: array of struct usb_device_id which describe the device in - * pre-firmware state - * @warm_ids: array of struct usb_device_id which describe the device in - * post-firmware state - * - * Each DVB USB device class can have one or more actual devices, this struct - * assigns a name to it. - */ -struct dvb_usb_device_description { - const char *name; - -#define DVB_USB_ID_MAX_NUM 15 - struct usb_device_id *cold_ids[DVB_USB_ID_MAX_NUM]; - struct usb_device_id *warm_ids[DVB_USB_ID_MAX_NUM]; -}; - -static inline u8 rc5_custom(struct rc_map_table *key) -{ - return (key->scancode >> 8) & 0xff; -} - -static inline u8 rc5_data(struct rc_map_table *key) -{ - return key->scancode & 0xff; -} - -static inline u16 rc5_scan(struct rc_map_table *key) -{ - return key->scancode & 0xffff; -} - -struct dvb_usb_device; -struct dvb_usb_adapter; -struct usb_data_stream; - -/** - * Properties of USB streaming - TODO this structure should be somewhere else - * describes the kind of USB transfer used for data-streaming. - * (BULK or ISOC) - */ -struct usb_data_stream_properties { -#define USB_BULK 1 -#define USB_ISOC 2 - int type; - int count; - int endpoint; - - union { - struct { - int buffersize; /* per URB */ - } bulk; - struct { - int framesperurb; - int framesize; - int interval; - } isoc; - } u; -}; - -/** - * struct dvb_usb_adapter_properties - properties of a dvb-usb-adapter. - * A DVB-USB-Adapter is basically a dvb_adapter which is present on a USB-device. - * @caps: capabilities of the DVB USB device. - * @pid_filter_count: number of PID filter position in the optional hardware - * PID-filter. - * @num_frontends: number of frontends of the DVB USB adapter. - * @frontend_ctrl: called to power on/off active frontend. - * @streaming_ctrl: called to start and stop the MPEG2-TS streaming of the - * device (not URB submitting/killing). - * @pid_filter_ctrl: called to en/disable the PID filter, if any. - * @pid_filter: called to set/unset a PID for filtering. - * @frontend_attach: called to attach the possible frontends (fill fe-field - * of struct dvb_usb_device). - * @tuner_attach: called to attach the correct tuner and to fill pll_addr, - * pll_desc and pll_init_buf of struct dvb_usb_device). - * @stream: configuration of the USB streaming - */ -struct dvb_usb_adapter_fe_properties { -#define DVB_USB_ADAP_HAS_PID_FILTER 0x01 -#define DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF 0x02 -#define DVB_USB_ADAP_NEED_PID_FILTERING 0x04 -#define DVB_USB_ADAP_RECEIVES_204_BYTE_TS 0x08 -#define DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD 0x10 - int caps; - int pid_filter_count; - - int (*streaming_ctrl) (struct dvb_usb_adapter *, int); - int (*pid_filter_ctrl) (struct dvb_usb_adapter *, int); - int (*pid_filter) (struct dvb_usb_adapter *, int, u16, int); - - int (*frontend_attach) (struct dvb_usb_adapter *); - int (*tuner_attach) (struct dvb_usb_adapter *); - - struct usb_data_stream_properties stream; - - int size_of_priv; -}; - -#define MAX_NO_OF_FE_PER_ADAP 3 -struct dvb_usb_adapter_properties { - int size_of_priv; - - int (*frontend_ctrl) (struct dvb_frontend *, int); - int (*fe_ioctl_override) (struct dvb_frontend *, - unsigned int, void *, unsigned int); - - int num_frontends; - struct dvb_usb_adapter_fe_properties fe[MAX_NO_OF_FE_PER_ADAP]; -}; - -/** - * struct dvb_rc_legacy - old properties of remote controller - * @rc_map_table: a hard-wired array of struct rc_map_table (NULL to disable - * remote control handling). - * @rc_map_size: number of items in @rc_map_table. - * @rc_query: called to query an event event. - * @rc_interval: time in ms between two queries. - */ -struct dvb_rc_legacy { -/* remote control properties */ -#define REMOTE_NO_KEY_PRESSED 0x00 -#define REMOTE_KEY_PRESSED 0x01 -#define REMOTE_KEY_REPEAT 0x02 - struct rc_map_table *rc_map_table; - int rc_map_size; - int (*rc_query) (struct dvb_usb_device *, u32 *, int *); - int rc_interval; -}; - -/** - * struct dvb_rc properties of remote controller, using rc-core - * @rc_codes: name of rc codes table - * @protocol: type of protocol(s) currently used by the driver - * @allowed_protos: protocol(s) supported by the driver - * @driver_type: Used to point if a device supports raw mode - * @change_protocol: callback to change protocol - * @rc_query: called to query an event event. - * @rc_interval: time in ms between two queries. - * @bulk_mode: device supports bulk mode for RC (disable polling mode) - */ -struct dvb_rc { - char *rc_codes; - u64 protocol; - u64 allowed_protos; - enum rc_driver_type driver_type; - int (*change_protocol)(struct rc_dev *dev, u64 rc_type); - char *module_name; - int (*rc_query) (struct dvb_usb_device *d); - int rc_interval; - bool bulk_mode; /* uses bulk mode */ -}; - -/** - * enum dvb_usb_mode - Specifies if it is using a legacy driver or a new one - * based on rc-core - * This is initialized/used only inside dvb-usb-remote.c. - * It shouldn't be set by the drivers. - */ -enum dvb_usb_mode { - DVB_RC_LEGACY, - DVB_RC_CORE, -}; - -/** - * struct dvb_usb_device_properties - properties of a dvb-usb-device - * @usb_ctrl: which USB device-side controller is in use. Needed for firmware - * download. - * @firmware: name of the firmware file. - * @download_firmware: called to download the firmware when the usb_ctrl is - * DEVICE_SPECIFIC. - * @no_reconnect: device doesn't do a reconnect after downloading the firmware, - * so do the warm initialization right after it - * - * @size_of_priv: how many bytes shall be allocated for the private field - * of struct dvb_usb_device. - * - * @power_ctrl: called to enable/disable power of the device. - * @read_mac_address: called to read the MAC address of the device. - * @identify_state: called to determine the state (cold or warm), when it - * is not distinguishable by the USB IDs. - * - * @rc: remote controller properties - * - * @i2c_algo: i2c_algorithm if the device has I2CoverUSB. - * - * @generic_bulk_ctrl_endpoint: most of the DVB USB devices have a generic - * endpoint which received control messages with bulk transfers. When this - * is non-zero, one can use dvb_usb_generic_rw and dvb_usb_generic_write- - * helper functions. - * - * @generic_bulk_ctrl_endpoint_response: some DVB USB devices use a separate - * endpoint for responses to control messages sent with bulk transfers via - * the generic_bulk_ctrl_endpoint. When this is non-zero, this will be used - * instead of the generic_bulk_ctrl_endpoint when reading usb responses in - * the dvb_usb_generic_rw helper function. - * - * @num_device_descs: number of struct dvb_usb_device_description in @devices - * @devices: array of struct dvb_usb_device_description compatibles with these - * properties. - */ -#define MAX_NO_OF_ADAPTER_PER_DEVICE 2 -struct dvb_usb_device_properties { - -#define DVB_USB_IS_AN_I2C_ADAPTER 0x01 - int caps; - -#define DEVICE_SPECIFIC 0 -#define CYPRESS_AN2135 1 -#define CYPRESS_AN2235 2 -#define CYPRESS_FX2 3 - int usb_ctrl; - int (*download_firmware) (struct usb_device *, const struct firmware *); - const char *firmware; - int no_reconnect; - - int size_of_priv; - - int num_adapters; - struct dvb_usb_adapter_properties adapter[MAX_NO_OF_ADAPTER_PER_DEVICE]; - - int (*power_ctrl) (struct dvb_usb_device *, int); - int (*read_mac_address) (struct dvb_usb_device *, u8 []); - int (*identify_state) (struct usb_device *, struct dvb_usb_device_properties *, - struct dvb_usb_device_description **, int *); - - struct { - enum dvb_usb_mode mode; /* Drivers shouldn't touch on it */ - struct dvb_rc_legacy legacy; - struct dvb_rc core; - } rc; - - struct i2c_algorithm *i2c_algo; - - int generic_bulk_ctrl_endpoint; - int generic_bulk_ctrl_endpoint_response; - - int num_device_descs; - struct dvb_usb_device_description devices[12]; -}; - -/** - * struct usb_data_stream - generic object of an USB stream - * @buf_num: number of buffer allocated. - * @buf_size: size of each buffer in buf_list. - * @buf_list: array containing all allocate buffers for streaming. - * @dma_addr: list of dma_addr_t for each buffer in buf_list. - * - * @urbs_initialized: number of URBs initialized. - * @urbs_submitted: number of URBs submitted. - */ -#define MAX_NO_URBS_FOR_DATA_STREAM 10 -struct usb_data_stream { - struct usb_device *udev; - struct usb_data_stream_properties props; - -#define USB_STATE_INIT 0x00 -#define USB_STATE_URB_BUF 0x01 - int state; - - void (*complete) (struct usb_data_stream *, u8 *, size_t); - - struct urb *urb_list[MAX_NO_URBS_FOR_DATA_STREAM]; - int buf_num; - unsigned long buf_size; - u8 *buf_list[MAX_NO_URBS_FOR_DATA_STREAM]; - dma_addr_t dma_addr[MAX_NO_URBS_FOR_DATA_STREAM]; - - int urbs_initialized; - int urbs_submitted; - - void *user_priv; -}; - -/** - * struct dvb_usb_adapter - a DVB adapter on a USB device - * @id: index of this adapter (starting with 0). - * - * @feedcount: number of reqested feeds (used for streaming-activation) - * @pid_filtering: is hardware pid_filtering used or not. - * - * @pll_addr: I2C address of the tuner for programming - * @pll_init: array containing the initialization buffer - * @pll_desc: pointer to the appropriate struct dvb_pll_desc - * @tuner_pass_ctrl: called to (de)activate tuner passthru of the demod or the board - * - * @dvb_adap: device's dvb_adapter. - * @dmxdev: device's dmxdev. - * @demux: device's software demuxer. - * @dvb_net: device's dvb_net interfaces. - * @dvb_frontend: device's frontend. - * @max_feed_count: how many feeds can be handled simultaneously by this - * device - * - * @fe_init: rerouted frontend-init (wakeup) function. - * @fe_sleep: rerouted frontend-sleep function. - * - * @stream: the usb data stream. - */ -struct dvb_usb_fe_adapter { - struct dvb_frontend *fe; - - int (*fe_init) (struct dvb_frontend *); - int (*fe_sleep) (struct dvb_frontend *); - - struct usb_data_stream stream; - - int pid_filtering; - int max_feed_count; - - void *priv; -}; - -struct dvb_usb_adapter { - struct dvb_usb_device *dev; - struct dvb_usb_adapter_properties props; - -#define DVB_USB_ADAP_STATE_INIT 0x000 -#define DVB_USB_ADAP_STATE_DVB 0x001 - int state; - - u8 id; - - int feedcount; - - /* dvb */ - struct dvb_adapter dvb_adap; - struct dmxdev dmxdev; - struct dvb_demux demux; - struct dvb_net dvb_net; - - struct dvb_usb_fe_adapter fe_adap[MAX_NO_OF_FE_PER_ADAP]; - int active_fe; - int num_frontends_initialized; - - void *priv; -}; - -/** - * struct dvb_usb_device - object of a DVB USB device - * @props: copy of the struct dvb_usb_properties this device belongs to. - * @desc: pointer to the device's struct dvb_usb_device_description. - * @state: initialization and runtime state of the device. - * - * @powered: indicated whether the device is power or not. - * Powered is in/decremented for each call to modify the state. - * @udev: pointer to the device's struct usb_device. - * - * @usb_mutex: semaphore of USB control messages (reading needs two messages) - * @i2c_mutex: semaphore for i2c-transfers - * - * @i2c_adap: device's i2c_adapter if it uses I2CoverUSB - * - * @rc_dev: rc device for the remote control (rc-core mode) - * @input_dev: input device for the remote control (legacy mode) - * @rc_query_work: struct work_struct frequent rc queries - * @last_event: last triggered event - * @last_state: last state (no, pressed, repeat) - * @owner: owner of the dvb_adapter - * @priv: private data of the actual driver (allocate by dvb-usb, size defined - * in size_of_priv of dvb_usb_properties). - */ -struct dvb_usb_device { - struct dvb_usb_device_properties props; - struct dvb_usb_device_description *desc; - - struct usb_device *udev; - -#define DVB_USB_STATE_INIT 0x000 -#define DVB_USB_STATE_I2C 0x001 -#define DVB_USB_STATE_DVB 0x002 -#define DVB_USB_STATE_REMOTE 0x004 - int state; - - int powered; - - /* locking */ - struct mutex usb_mutex; - - /* i2c */ - struct mutex i2c_mutex; - struct i2c_adapter i2c_adap; - - int num_adapters_initialized; - struct dvb_usb_adapter adapter[MAX_NO_OF_ADAPTER_PER_DEVICE]; - - /* remote control */ - struct rc_dev *rc_dev; - struct input_dev *input_dev; - char rc_phys[64]; - struct delayed_work rc_query_work; - u32 last_event; - int last_state; - - struct module *owner; - - void *priv; -}; - -extern int dvb_usb_device_init(struct usb_interface *, - struct dvb_usb_device_properties *, - struct module *, struct dvb_usb_device **, - short *adapter_nums); -extern void dvb_usb_device_exit(struct usb_interface *); - -/* the generic read/write method for device control */ -extern int dvb_usb_generic_rw(struct dvb_usb_device *, u8 *, u16, u8 *, u16,int); -extern int dvb_usb_generic_write(struct dvb_usb_device *, u8 *, u16); - -/* commonly used remote control parsing */ -extern int dvb_usb_nec_rc_key_to_event(struct dvb_usb_device *, u8[], u32 *, int *); - -/* commonly used firmware download types and function */ -struct hexline { - u8 len; - u32 addr; - u8 type; - u8 data[255]; - u8 chk; -}; -extern int usb_cypress_load_firmware(struct usb_device *udev, const struct firmware *fw, int type); -extern int dvb_usb_get_hexline(const struct firmware *fw, struct hexline *hx, int *pos); - - -#endif diff --git a/drivers/media/dvb/dvb-usb/ec168.c b/drivers/media/dvb/dvb-usb/ec168.c deleted file mode 100644 index b4989ba8897d..000000000000 --- a/drivers/media/dvb/dvb-usb/ec168.c +++ /dev/null @@ -1,435 +0,0 @@ -/* - * E3C EC168 DVB USB driver - * - * Copyright (C) 2009 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "ec168.h" -#include "ec100.h" -#include "mxl5005s.h" - -/* debug */ -static int dvb_usb_ec168_debug; -module_param_named(debug, dvb_usb_ec168_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level" DVB_USB_DEBUG_STATUS); -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -static struct ec100_config ec168_ec100_config; - -static int ec168_rw_udev(struct usb_device *udev, struct ec168_req *req) -{ - int ret; - unsigned int pipe; - u8 request, requesttype; - u8 *buf; - - - - switch (req->cmd) { - case DOWNLOAD_FIRMWARE: - case GPIO: - case WRITE_I2C: - case STREAMING_CTRL: - requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); - request = req->cmd; - break; - case READ_I2C: - requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); - request = req->cmd; - break; - case GET_CONFIG: - requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); - request = CONFIG; - break; - case SET_CONFIG: - requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); - request = CONFIG; - break; - case WRITE_DEMOD: - requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); - request = DEMOD_RW; - break; - case READ_DEMOD: - requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); - request = DEMOD_RW; - break; - default: - err("unknown command:%02x", req->cmd); - ret = -EPERM; - goto error; - } - - buf = kmalloc(req->size, GFP_KERNEL); - if (!buf) { - ret = -ENOMEM; - goto error; - } - - if (requesttype == (USB_TYPE_VENDOR | USB_DIR_OUT)) { - /* write */ - memcpy(buf, req->data, req->size); - pipe = usb_sndctrlpipe(udev, 0); - } else { - /* read */ - pipe = usb_rcvctrlpipe(udev, 0); - } - - msleep(1); /* avoid I2C errors */ - - ret = usb_control_msg(udev, pipe, request, requesttype, req->value, - req->index, buf, req->size, EC168_USB_TIMEOUT); - - ec168_debug_dump(request, requesttype, req->value, req->index, buf, - req->size, deb_xfer); - - if (ret < 0) - goto err_dealloc; - else - ret = 0; - - /* read request, copy returned data to return buf */ - if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN)) - memcpy(req->data, buf, req->size); - - kfree(buf); - return ret; - -err_dealloc: - kfree(buf); -error: - deb_info("%s: failed:%d\n", __func__, ret); - return ret; -} - -static int ec168_ctrl_msg(struct dvb_usb_device *d, struct ec168_req *req) -{ - return ec168_rw_udev(d->udev, req); -} - -/* I2C */ -static int ec168_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - struct ec168_req req; - int i = 0; - int ret; - - if (num > 2) - return -EINVAL; - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - while (i < num) { - if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { - if (msg[i].addr == ec168_ec100_config.demod_address) { - req.cmd = READ_DEMOD; - req.value = 0; - req.index = 0xff00 + msg[i].buf[0]; /* reg */ - req.size = msg[i+1].len; /* bytes to read */ - req.data = &msg[i+1].buf[0]; - ret = ec168_ctrl_msg(d, &req); - i += 2; - } else { - err("I2C read not implemented"); - ret = -ENOSYS; - i += 2; - } - } else { - if (msg[i].addr == ec168_ec100_config.demod_address) { - req.cmd = WRITE_DEMOD; - req.value = msg[i].buf[1]; /* val */ - req.index = 0xff00 + msg[i].buf[0]; /* reg */ - req.size = 0; - req.data = NULL; - ret = ec168_ctrl_msg(d, &req); - i += 1; - } else { - req.cmd = WRITE_I2C; - req.value = msg[i].buf[0]; /* val */ - req.index = 0x0100 + msg[i].addr; /* I2C addr */ - req.size = msg[i].len-1; - req.data = &msg[i].buf[1]; - ret = ec168_ctrl_msg(d, &req); - i += 1; - } - } - if (ret) - goto error; - - } - ret = i; - -error: - mutex_unlock(&d->i2c_mutex); - return i; -} - - -static u32 ec168_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm ec168_i2c_algo = { - .master_xfer = ec168_i2c_xfer, - .functionality = ec168_i2c_func, -}; - -/* Callbacks for DVB USB */ -static struct ec100_config ec168_ec100_config = { - .demod_address = 0xff, /* not real address, demod is integrated */ -}; - -static int ec168_ec100_frontend_attach(struct dvb_usb_adapter *adap) -{ - deb_info("%s:\n", __func__); - adap->fe_adap[0].fe = dvb_attach(ec100_attach, &ec168_ec100_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe == NULL) - return -ENODEV; - - return 0; -} - -static struct mxl5005s_config ec168_mxl5003s_config = { - .i2c_address = 0xc6, - .if_freq = IF_FREQ_4570000HZ, - .xtal_freq = CRYSTAL_FREQ_16000000HZ, - .agc_mode = MXL_SINGLE_AGC, - .tracking_filter = MXL_TF_OFF, - .rssi_enable = MXL_RSSI_ENABLE, - .cap_select = MXL_CAP_SEL_ENABLE, - .div_out = MXL_DIV_OUT_4, - .clock_out = MXL_CLOCK_OUT_DISABLE, - .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, - .top = MXL5005S_TOP_25P2, - .mod_mode = MXL_DIGITAL_MODE, - .if_mode = MXL_ZERO_IF, - .AgcMasterByte = 0x00, -}; - -static int ec168_mxl5003s_tuner_attach(struct dvb_usb_adapter *adap) -{ - deb_info("%s:\n", __func__); - return dvb_attach(mxl5005s_attach, adap->fe_adap[0].fe, &adap->dev->i2c_adap, - &ec168_mxl5003s_config) == NULL ? -ENODEV : 0; -} - -static int ec168_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct ec168_req req = {STREAMING_CTRL, 0x7f01, 0x0202, 0, NULL}; - deb_info("%s: onoff:%d\n", __func__, onoff); - if (onoff) - req.index = 0x0102; - return ec168_ctrl_msg(adap->dev, &req); -} - -static int ec168_download_firmware(struct usb_device *udev, - const struct firmware *fw) -{ - int i, len, packets, remainder, ret; - u16 addr = 0x0000; /* firmware start address */ - struct ec168_req req = {DOWNLOAD_FIRMWARE, 0, 0, 0, NULL}; - deb_info("%s:\n", __func__); - - #define FW_PACKET_MAX_DATA 2048 - packets = fw->size / FW_PACKET_MAX_DATA; - remainder = fw->size % FW_PACKET_MAX_DATA; - len = FW_PACKET_MAX_DATA; - for (i = 0; i <= packets; i++) { - if (i == packets) /* set size of the last packet */ - len = remainder; - - req.size = len; - req.data = (u8 *)(fw->data + i * FW_PACKET_MAX_DATA); - req.index = addr; - addr += FW_PACKET_MAX_DATA; - - ret = ec168_rw_udev(udev, &req); - if (ret) { - err("firmware download failed:%d packet:%d", ret, i); - goto error; - } - } - req.size = 0; - - /* set "warm"? */ - req.cmd = SET_CONFIG; - req.value = 0; - req.index = 0x0001; - ret = ec168_rw_udev(udev, &req); - if (ret) - goto error; - - /* really needed - no idea what does */ - req.cmd = GPIO; - req.value = 0; - req.index = 0x0206; - ret = ec168_rw_udev(udev, &req); - if (ret) - goto error; - - /* activate tuner I2C? */ - req.cmd = WRITE_I2C; - req.value = 0; - req.index = 0x00c6; - ret = ec168_rw_udev(udev, &req); - if (ret) - goto error; - - return ret; -error: - deb_info("%s: failed:%d\n", __func__, ret); - return ret; -} - -static int ec168_identify_state(struct usb_device *udev, - struct dvb_usb_device_properties *props, - struct dvb_usb_device_description **desc, int *cold) -{ - int ret; - u8 reply; - struct ec168_req req = {GET_CONFIG, 0, 1, sizeof(reply), &reply}; - deb_info("%s:\n", __func__); - - ret = ec168_rw_udev(udev, &req); - if (ret) - goto error; - - deb_info("%s: reply:%02x\n", __func__, reply); - - if (reply == 0x01) - *cold = 0; - else - *cold = 1; - - return ret; -error: - deb_info("%s: failed:%d\n", __func__, ret); - return ret; -} - -/* DVB USB Driver stuff */ -static struct dvb_usb_device_properties ec168_properties; - -static int ec168_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - int ret; - deb_info("%s: interface:%d\n", __func__, - intf->cur_altsetting->desc.bInterfaceNumber); - - ret = dvb_usb_device_init(intf, &ec168_properties, THIS_MODULE, NULL, - adapter_nr); - if (ret) - goto error; - - return ret; -error: - deb_info("%s: failed:%d\n", __func__, ret); - return ret; -} - -#define E3C_EC168_1689 0 -#define E3C_EC168_FFFA 1 -#define E3C_EC168_FFFB 2 -#define E3C_EC168_1001 3 -#define E3C_EC168_1002 4 - -static struct usb_device_id ec168_id[] = { - [E3C_EC168_1689] = - {USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168)}, - [E3C_EC168_FFFA] = - {USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_2)}, - [E3C_EC168_FFFB] = - {USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_3)}, - [E3C_EC168_1001] = - {USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_4)}, - [E3C_EC168_1002] = - {USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_5)}, - {} /* terminating entry */ -}; - -MODULE_DEVICE_TABLE(usb, ec168_id); - -static struct dvb_usb_device_properties ec168_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - .download_firmware = ec168_download_firmware, - .firmware = "dvb-usb-ec168.fw", - .no_reconnect = 1, - - .size_of_priv = 0, - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = {{ - .streaming_ctrl = ec168_streaming_ctrl, - .frontend_attach = ec168_ec100_frontend_attach, - .tuner_attach = ec168_mxl5003s_tuner_attach, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x82, - .u = { - .bulk = { - .buffersize = (32*512), - } - } - }, - }}, - } - }, - - .identify_state = ec168_identify_state, - - .i2c_algo = &ec168_i2c_algo, - - .num_device_descs = 1, - .devices = { - { - .name = "E3C EC168 DVB-T USB2.0 reference design", - .cold_ids = { - &ec168_id[E3C_EC168_1689], - &ec168_id[E3C_EC168_FFFA], - &ec168_id[E3C_EC168_FFFB], - &ec168_id[E3C_EC168_1001], - &ec168_id[E3C_EC168_1002], - NULL}, - .warm_ids = {NULL}, - }, - } -}; - -static struct usb_driver ec168_driver = { - .name = "dvb_usb_ec168", - .probe = ec168_probe, - .disconnect = dvb_usb_device_exit, - .id_table = ec168_id, -}; - -module_usb_driver(ec168_driver); - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("E3C EC168 DVB-T USB2.0 driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/ec168.h b/drivers/media/dvb/dvb-usb/ec168.h deleted file mode 100644 index e7e0b831314e..000000000000 --- a/drivers/media/dvb/dvb-usb/ec168.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * E3C EC168 DVB USB driver - * - * Copyright (C) 2009 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef EC168_H -#define EC168_H - -#define DVB_USB_LOG_PREFIX "ec168" -#include "dvb-usb.h" - -#define deb_info(args...) dprintk(dvb_usb_ec168_debug, 0x01, args) -#define deb_rc(args...) dprintk(dvb_usb_ec168_debug, 0x02, args) -#define deb_xfer(args...) dprintk(dvb_usb_ec168_debug, 0x04, args) -#define deb_reg(args...) dprintk(dvb_usb_ec168_debug, 0x08, args) -#define deb_i2c(args...) dprintk(dvb_usb_ec168_debug, 0x10, args) -#define deb_fw(args...) dprintk(dvb_usb_ec168_debug, 0x20, args) - -#define ec168_debug_dump(r, t, v, i, b, l, func) { \ - int loop_; \ - func("%02x %02x %02x %02x %02x %02x %02x %02x", \ - t, r, v & 0xff, v >> 8, i & 0xff, i >> 8, l & 0xff, l >> 8); \ - if (t == (USB_TYPE_VENDOR | USB_DIR_OUT)) \ - func(" >>> "); \ - else \ - func(" <<< "); \ - for (loop_ = 0; loop_ < l; loop_++) \ - func("%02x ", b[loop_]); \ - func("\n");\ -} - -#define EC168_USB_TIMEOUT 1000 - -struct ec168_req { - u8 cmd; /* [1] */ - u16 value; /* [2|3] */ - u16 index; /* [4|5] */ - u16 size; /* [6|7] */ - u8 *data; -}; - -enum ec168_cmd { - DOWNLOAD_FIRMWARE = 0x00, - CONFIG = 0x01, - DEMOD_RW = 0x03, - GPIO = 0x04, - STREAMING_CTRL = 0x10, - READ_I2C = 0x20, - WRITE_I2C = 0x21, - HID_DOWNLOAD = 0x30, - GET_CONFIG, - SET_CONFIG, - READ_DEMOD, - WRITE_DEMOD, -}; - -#endif diff --git a/drivers/media/dvb/dvb-usb/gl861.c b/drivers/media/dvb/dvb-usb/gl861.c deleted file mode 100644 index c1f5582e1cdf..000000000000 --- a/drivers/media/dvb/dvb-usb/gl861.c +++ /dev/null @@ -1,217 +0,0 @@ -/* DVB USB compliant linux driver for GL861 USB2.0 devices. - * - * 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, version 2. - * - * see Documentation/dvb/README.dvb-usb for more information - */ -#include "gl861.h" - -#include "zl10353.h" -#include "qt1010.h" - -/* debug */ -static int dvb_usb_gl861_debug; -module_param_named(debug, dvb_usb_gl861_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=rc (or-able))." - DVB_USB_DEBUG_STATUS); -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -static int gl861_i2c_msg(struct dvb_usb_device *d, u8 addr, - u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) -{ - u16 index; - u16 value = addr << (8 + 1); - int wo = (rbuf == NULL || rlen == 0); /* write-only */ - u8 req, type; - - if (wo) { - req = GL861_REQ_I2C_WRITE; - type = GL861_WRITE; - } else { /* rw */ - req = GL861_REQ_I2C_READ; - type = GL861_READ; - } - - switch (wlen) { - case 1: - index = wbuf[0]; - break; - case 2: - index = wbuf[0]; - value = value + wbuf[1]; - break; - default: - warn("wlen = %x, aborting.", wlen); - return -EINVAL; - } - - msleep(1); /* avoid I2C errors */ - - return usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), req, type, - value, index, rbuf, rlen, 2000); -} - -/* I2C */ -static int gl861_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - int i; - - if (num > 2) - return -EINVAL; - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - for (i = 0; i < num; i++) { - /* write/read request */ - if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) { - if (gl861_i2c_msg(d, msg[i].addr, msg[i].buf, - msg[i].len, msg[i+1].buf, msg[i+1].len) < 0) - break; - i++; - } else - if (gl861_i2c_msg(d, msg[i].addr, msg[i].buf, - msg[i].len, NULL, 0) < 0) - break; - } - - mutex_unlock(&d->i2c_mutex); - return i; -} - -static u32 gl861_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm gl861_i2c_algo = { - .master_xfer = gl861_i2c_xfer, - .functionality = gl861_i2c_func, -}; - -/* Callbacks for DVB USB */ -static struct zl10353_config gl861_zl10353_config = { - .demod_address = 0x0f, - .no_tuner = 1, - .parallel_ts = 1, -}; - -static int gl861_frontend_attach(struct dvb_usb_adapter *adap) -{ - - adap->fe_adap[0].fe = dvb_attach(zl10353_attach, &gl861_zl10353_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe == NULL) - return -EIO; - - return 0; -} - -static struct qt1010_config gl861_qt1010_config = { - .i2c_address = 0x62 -}; - -static int gl861_tuner_attach(struct dvb_usb_adapter *adap) -{ - return dvb_attach(qt1010_attach, - adap->fe_adap[0].fe, &adap->dev->i2c_adap, - &gl861_qt1010_config) == NULL ? -ENODEV : 0; -} - -/* DVB USB Driver stuff */ -static struct dvb_usb_device_properties gl861_properties; - -static int gl861_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - struct dvb_usb_device *d; - struct usb_host_interface *alt; - int ret; - - if (intf->num_altsetting < 2) - return -ENODEV; - - ret = dvb_usb_device_init(intf, &gl861_properties, THIS_MODULE, &d, - adapter_nr); - if (ret == 0) { - alt = usb_altnum_to_altsetting(intf, 0); - - if (alt == NULL) { - deb_rc("not alt found!\n"); - return -ENODEV; - } - - ret = usb_set_interface(d->udev, alt->desc.bInterfaceNumber, - alt->desc.bAlternateSetting); - } - - return ret; -} - -static struct usb_device_id gl861_table [] = { - { USB_DEVICE(USB_VID_MSI, USB_PID_MSI_MEGASKY580_55801) }, - { USB_DEVICE(USB_VID_ALINK, USB_VID_ALINK_DTU) }, - { } /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(usb, gl861_table); - -static struct dvb_usb_device_properties gl861_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - .usb_ctrl = DEVICE_SPECIFIC, - - .size_of_priv = 0, - - .num_adapters = 1, - .adapter = {{ - .num_frontends = 1, - .fe = {{ - - .frontend_attach = gl861_frontend_attach, - .tuner_attach = gl861_tuner_attach, - - .stream = { - .type = USB_BULK, - .count = 7, - .endpoint = 0x81, - .u = { - .bulk = { - .buffersize = 512, - } - } - }, - }}, - } }, - .i2c_algo = &gl861_i2c_algo, - - .num_device_descs = 2, - .devices = { - { - .name = "MSI Mega Sky 55801 DVB-T USB2.0", - .cold_ids = { NULL }, - .warm_ids = { &gl861_table[0], NULL }, - }, - { - .name = "A-LINK DTU DVB-T USB2.0", - .cold_ids = { NULL }, - .warm_ids = { &gl861_table[1], NULL }, - }, - } -}; - -static struct usb_driver gl861_driver = { - .name = "dvb_usb_gl861", - .probe = gl861_probe, - .disconnect = dvb_usb_device_exit, - .id_table = gl861_table, -}; - -module_usb_driver(gl861_driver); - -MODULE_AUTHOR("Carl Lundqvist "); -MODULE_DESCRIPTION("Driver MSI Mega Sky 580 DVB-T USB2.0 / GL861"); -MODULE_VERSION("0.1"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/gl861.h b/drivers/media/dvb/dvb-usb/gl861.h deleted file mode 100644 index c54855e2c233..000000000000 --- a/drivers/media/dvb/dvb-usb/gl861.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _DVB_USB_GL861_H_ -#define _DVB_USB_GL861_H_ - -#define DVB_USB_LOG_PREFIX "gl861" -#include "dvb-usb.h" - -#define deb_rc(args...) dprintk(dvb_usb_gl861_debug, 0x01, args) - -#define GL861_WRITE 0x40 -#define GL861_READ 0xc0 - -#define GL861_REQ_I2C_WRITE 0x01 -#define GL861_REQ_I2C_READ 0x02 - -#endif diff --git a/drivers/media/dvb/dvb-usb/it913x.c b/drivers/media/dvb/dvb-usb/it913x.c deleted file mode 100644 index 6244fe9d1a3a..000000000000 --- a/drivers/media/dvb/dvb-usb/it913x.c +++ /dev/null @@ -1,931 +0,0 @@ -/* DVB USB compliant linux driver for IT9137 - * - * Copyright (C) 2011 Malcolm Priestley (tvboxspy@gmail.com) - * IT9137 (C) ITE Tech Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2, as - * published by the Free Software Foundation. - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * - * see Documentation/dvb/README.dvb-usb for more information - * see Documentation/dvb/it9137.txt for firmware information - * - */ -#define DVB_USB_LOG_PREFIX "it913x" - -#include -#include -#include - -#include "dvb-usb.h" -#include "it913x-fe.h" - -/* debug */ -static int dvb_usb_it913x_debug; -#define l_dprintk(var, level, args...) do { \ - if ((var >= level)) \ - printk(KERN_DEBUG DVB_USB_LOG_PREFIX ": " args); \ -} while (0) - -#define deb_info(level, args...) l_dprintk(dvb_usb_it913x_debug, level, args) -#define debug_data_snipet(level, name, p) \ - deb_info(level, name" (%02x%02x%02x%02x%02x%02x%02x%02x)", \ - *p, *(p+1), *(p+2), *(p+3), *(p+4), \ - *(p+5), *(p+6), *(p+7)); - - -module_param_named(debug, dvb_usb_it913x_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))." - DVB_USB_DEBUG_STATUS); - -static int pid_filter; -module_param_named(pid, pid_filter, int, 0644); -MODULE_PARM_DESC(pid, "set default 0=on 1=off"); - -static int dvb_usb_it913x_firmware; -module_param_named(firmware, dvb_usb_it913x_firmware, int, 0644); -MODULE_PARM_DESC(firmware, "set firmware 0=auto 1=IT9137 2=IT9135V1"); - - -int cmd_counter; - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -struct it913x_state { - u8 id; - struct ite_config it913x_config; - u8 pid_filter_onoff; -}; - -struct ite_config it913x_config; - -#define IT913X_RETRY 10 -#define IT913X_SND_TIMEOUT 100 -#define IT913X_RCV_TIMEOUT 200 - -static int it913x_bulk_write(struct usb_device *dev, - u8 *snd, int len, u8 pipe) -{ - int ret, actual_l, i; - - for (i = 0; i < IT913X_RETRY; i++) { - ret = usb_bulk_msg(dev, usb_sndbulkpipe(dev, pipe), - snd, len , &actual_l, IT913X_SND_TIMEOUT); - if (ret != -EBUSY && ret != -ETIMEDOUT) - break; - } - - if (len != actual_l && ret == 0) - ret = -EAGAIN; - - return ret; -} - -static int it913x_bulk_read(struct usb_device *dev, - u8 *rev, int len, u8 pipe) -{ - int ret, actual_l, i; - - for (i = 0; i < IT913X_RETRY; i++) { - ret = usb_bulk_msg(dev, usb_rcvbulkpipe(dev, pipe), - rev, len , &actual_l, IT913X_RCV_TIMEOUT); - if (ret != -EBUSY && ret != -ETIMEDOUT) - break; - } - - if (len != actual_l && ret == 0) - ret = -EAGAIN; - - return ret; -} - -static u16 check_sum(u8 *p, u8 len) -{ - u16 sum = 0; - u8 i = 1; - while (i < len) - sum += (i++ & 1) ? (*p++) << 8 : *p++; - return ~sum; -} - -static int it913x_usb_talk(struct usb_device *udev, u8 mode, u8 pro, - u8 cmd, u32 reg, u8 addr, u8 *data, u8 len) -{ - int ret = 0, i, buf_size = 1; - u8 *buff; - u8 rlen; - u16 chk_sum; - - buff = kzalloc(256, GFP_KERNEL); - if (!buff) { - info("USB Buffer Failed"); - return -ENOMEM; - } - - buff[buf_size++] = pro; - buff[buf_size++] = cmd; - buff[buf_size++] = cmd_counter; - - switch (mode) { - case READ_LONG: - case WRITE_LONG: - buff[buf_size++] = len; - buff[buf_size++] = 2; - buff[buf_size++] = (reg >> 24); - buff[buf_size++] = (reg >> 16) & 0xff; - buff[buf_size++] = (reg >> 8) & 0xff; - buff[buf_size++] = reg & 0xff; - break; - case READ_SHORT: - buff[buf_size++] = addr; - break; - case WRITE_SHORT: - buff[buf_size++] = len; - buff[buf_size++] = addr; - buff[buf_size++] = (reg >> 8) & 0xff; - buff[buf_size++] = reg & 0xff; - break; - case READ_DATA: - case WRITE_DATA: - break; - case WRITE_CMD: - mode = 7; - break; - default: - kfree(buff); - return -EINVAL; - } - - if (mode & 1) { - for (i = 0; i < len ; i++) - buff[buf_size++] = data[i]; - } - chk_sum = check_sum(&buff[1], buf_size); - - buff[buf_size++] = chk_sum >> 8; - buff[0] = buf_size; - buff[buf_size++] = (chk_sum & 0xff); - - ret = it913x_bulk_write(udev, buff, buf_size , 0x02); - if (ret < 0) - goto error; - - ret = it913x_bulk_read(udev, buff, (mode & 1) ? - 5 : len + 5 , 0x01); - if (ret < 0) - goto error; - - rlen = (mode & 0x1) ? 0x1 : len; - - if (mode & 1) - ret = buff[2]; - else - memcpy(data, &buff[3], rlen); - - cmd_counter++; - -error: kfree(buff); - - return ret; -} - -static int it913x_io(struct usb_device *udev, u8 mode, u8 pro, - u8 cmd, u32 reg, u8 addr, u8 *data, u8 len) -{ - int ret, i; - - for (i = 0; i < IT913X_RETRY; i++) { - ret = it913x_usb_talk(udev, mode, pro, - cmd, reg, addr, data, len); - if (ret != -EAGAIN) - break; - } - - return ret; -} - -static int it913x_wr_reg(struct usb_device *udev, u8 pro, u32 reg , u8 data) -{ - int ret; - u8 b[1]; - b[0] = data; - ret = it913x_io(udev, WRITE_LONG, pro, - CMD_DEMOD_WRITE, reg, 0, b, sizeof(b)); - - return ret; -} - -static int it913x_read_reg(struct usb_device *udev, u32 reg) -{ - int ret; - u8 data[1]; - - ret = it913x_io(udev, READ_LONG, DEV_0, - CMD_DEMOD_READ, reg, 0, &data[0], 1); - - return (ret < 0) ? ret : data[0]; -} - -static u32 it913x_query(struct usb_device *udev, u8 pro) -{ - int ret, i; - u8 data[4]; - u8 ver; - - for (i = 0; i < 5; i++) { - ret = it913x_io(udev, READ_LONG, pro, CMD_DEMOD_READ, - 0x1222, 0, &data[0], 3); - ver = data[0]; - if (ver > 0 && ver < 3) - break; - msleep(100); - } - - if (ver < 1 || ver > 2) { - info("Failed to identify chip version applying 1"); - it913x_config.chip_ver = 0x1; - it913x_config.chip_type = 0x9135; - return 0; - } - - it913x_config.chip_ver = ver; - it913x_config.chip_type = (u16)(data[2] << 8) + data[1]; - - info("Chip Version=%02x Chip Type=%04x", it913x_config.chip_ver, - it913x_config.chip_type); - - ret |= it913x_io(udev, READ_SHORT, pro, - CMD_QUERYINFO, 0, 0x1, &data[0], 4); - - it913x_config.firmware = (data[0] << 24) + (data[1] << 16) + - (data[2] << 8) + data[3]; - - return (ret < 0) ? 0 : it913x_config.firmware; -} - -static int it913x_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct it913x_state *st = adap->dev->priv; - struct usb_device *udev = adap->dev->udev; - int ret; - u8 pro = (adap->id == 0) ? DEV_0_DMOD : DEV_1_DMOD; - - mutex_lock(&adap->dev->i2c_mutex); - - deb_info(1, "PID_C (%02x)", onoff); - - ret = it913x_wr_reg(udev, pro, PID_EN, st->pid_filter_onoff); - - mutex_unlock(&adap->dev->i2c_mutex); - return ret; -} - -static int it913x_pid_filter(struct dvb_usb_adapter *adap, - int index, u16 pid, int onoff) -{ - struct it913x_state *st = adap->dev->priv; - struct usb_device *udev = adap->dev->udev; - int ret; - u8 pro = (adap->id == 0) ? DEV_0_DMOD : DEV_1_DMOD; - - mutex_lock(&adap->dev->i2c_mutex); - - deb_info(1, "PID_F (%02x)", onoff); - - ret = it913x_wr_reg(udev, pro, PID_LSB, (u8)(pid & 0xff)); - - ret |= it913x_wr_reg(udev, pro, PID_MSB, (u8)(pid >> 8)); - - ret |= it913x_wr_reg(udev, pro, PID_INX_EN, (u8)onoff); - - ret |= it913x_wr_reg(udev, pro, PID_INX, (u8)(index & 0x1f)); - - if (udev->speed == USB_SPEED_HIGH && pid == 0x2000) { - ret |= it913x_wr_reg(udev, pro, PID_EN, !onoff); - st->pid_filter_onoff = !onoff; - } else - st->pid_filter_onoff = - adap->fe_adap[adap->active_fe].pid_filtering; - - mutex_unlock(&adap->dev->i2c_mutex); - return 0; -} - - -static int it913x_return_status(struct usb_device *udev) -{ - u32 firm = 0; - - firm = it913x_query(udev, DEV_0); - if (firm > 0) - info("Firmware Version %d", firm); - - return (firm > 0) ? firm : 0; -} - -static int it913x_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - static u8 data[256]; - int ret; - u32 reg; - u8 pro; - - mutex_lock(&d->i2c_mutex); - - debug_data_snipet(1, "Message out", msg[0].buf); - deb_info(2, "num of messages %d address %02x", num, msg[0].addr); - - pro = (msg[0].addr & 0x2) ? DEV_0_DMOD : 0x0; - pro |= (msg[0].addr & 0x20) ? DEV_1 : DEV_0; - memcpy(data, msg[0].buf, msg[0].len); - reg = (data[0] << 24) + (data[1] << 16) + - (data[2] << 8) + data[3]; - if (num == 2) { - ret = it913x_io(d->udev, READ_LONG, pro, - CMD_DEMOD_READ, reg, 0, data, msg[1].len); - memcpy(msg[1].buf, data, msg[1].len); - } else - ret = it913x_io(d->udev, WRITE_LONG, pro, CMD_DEMOD_WRITE, - reg, 0, &data[4], msg[0].len - 4); - - mutex_unlock(&d->i2c_mutex); - - return ret; -} - -static u32 it913x_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm it913x_i2c_algo = { - .master_xfer = it913x_i2c_xfer, - .functionality = it913x_i2c_func, -}; - -/* Callbacks for DVB USB */ -#define IT913X_POLL 250 -static int it913x_rc_query(struct dvb_usb_device *d) -{ - u8 ibuf[4]; - int ret; - u32 key; - /* Avoid conflict with frontends*/ - mutex_lock(&d->i2c_mutex); - - ret = it913x_io(d->udev, READ_LONG, PRO_LINK, CMD_IR_GET, - 0, 0, &ibuf[0], sizeof(ibuf)); - - if ((ibuf[2] + ibuf[3]) == 0xff) { - key = ibuf[2]; - key += ibuf[0] << 16; - key += ibuf[1] << 8; - deb_info(1, "NEC Extended Key =%08x", key); - if (d->rc_dev != NULL) - rc_keydown(d->rc_dev, key, 0); - } - - mutex_unlock(&d->i2c_mutex); - - return ret; -} - -/* Firmware sets raw */ -const char fw_it9135_v1[] = "dvb-usb-it9135-01.fw"; -const char fw_it9135_v2[] = "dvb-usb-it9135-02.fw"; -const char fw_it9137[] = "dvb-usb-it9137-01.fw"; - -static int ite_firmware_select(struct usb_device *udev, - struct dvb_usb_device_properties *props) -{ - int sw; - /* auto switch */ - if (le16_to_cpu(udev->descriptor.idVendor) == USB_VID_KWORLD_2) - sw = IT9137_FW; - else if (it913x_config.chip_ver == 1) - sw = IT9135_V1_FW; - else - sw = IT9135_V2_FW; - - /* force switch */ - if (dvb_usb_it913x_firmware != IT9135_AUTO) - sw = dvb_usb_it913x_firmware; - - switch (sw) { - case IT9135_V1_FW: - it913x_config.firmware_ver = 1; - it913x_config.adc_x2 = 1; - it913x_config.read_slevel = false; - props->firmware = fw_it9135_v1; - break; - case IT9135_V2_FW: - it913x_config.firmware_ver = 1; - it913x_config.adc_x2 = 1; - it913x_config.read_slevel = false; - props->firmware = fw_it9135_v2; - switch (it913x_config.tuner_id_0) { - case IT9135_61: - case IT9135_62: - break; - default: - info("Unknown tuner ID applying default 0x60"); - case IT9135_60: - it913x_config.tuner_id_0 = IT9135_60; - } - break; - case IT9137_FW: - default: - it913x_config.firmware_ver = 0; - it913x_config.adc_x2 = 0; - it913x_config.read_slevel = true; - props->firmware = fw_it9137; - } - - return 0; -} - -static void it913x_select_remote(struct usb_device *udev, - struct dvb_usb_device_properties *props) -{ - switch (le16_to_cpu(udev->descriptor.idProduct)) { - case USB_PID_ITETECH_IT9135_9005: - props->rc.core.rc_codes = RC_MAP_IT913X_V2; - return; - default: - props->rc.core.rc_codes = RC_MAP_IT913X_V1; - } - return; -} - -#define TS_MPEG_PKT_SIZE 188 -#define EP_LOW 21 -#define TS_BUFFER_SIZE_PID (EP_LOW*TS_MPEG_PKT_SIZE) -#define EP_HIGH 348 -#define TS_BUFFER_SIZE_MAX (EP_HIGH*TS_MPEG_PKT_SIZE) - -static int it913x_select_config(struct usb_device *udev, - struct dvb_usb_device_properties *props) -{ - int ret = 0, reg; - bool proprietary_ir = false; - - if (it913x_config.chip_ver == 0x02 - && it913x_config.chip_type == 0x9135) - reg = it913x_read_reg(udev, 0x461d); - else - reg = it913x_read_reg(udev, 0x461b); - - if (reg < 0) - return reg; - - if (reg == 0) { - it913x_config.dual_mode = 0; - it913x_config.tuner_id_0 = IT9135_38; - proprietary_ir = true; - } else { - /* TS mode */ - reg = it913x_read_reg(udev, 0x49c5); - if (reg < 0) - return reg; - it913x_config.dual_mode = reg; - - /* IR mode type */ - reg = it913x_read_reg(udev, 0x49ac); - if (reg < 0) - return reg; - if (reg == 5) { - info("Remote propriety (raw) mode"); - proprietary_ir = true; - } else if (reg == 1) { - info("Remote HID mode NOT SUPPORTED"); - proprietary_ir = false; - props->rc.core.rc_codes = NULL; - } else - props->rc.core.rc_codes = NULL; - - /* Tuner_id */ - reg = it913x_read_reg(udev, 0x49d0); - if (reg < 0) - return reg; - it913x_config.tuner_id_0 = reg; - } - - if (proprietary_ir) - it913x_select_remote(udev, props); - - if (udev->speed != USB_SPEED_HIGH) { - props->adapter[0].fe[0].pid_filter_count = 5; - info("USB 1 low speed mode - connect to USB 2 port"); - if (pid_filter > 0) - pid_filter = 0; - if (it913x_config.dual_mode) { - it913x_config.dual_mode = 0; - info("Dual mode not supported in USB 1"); - } - } else /* For replugging */ - if(props->adapter[0].fe[0].pid_filter_count == 5) - props->adapter[0].fe[0].pid_filter_count = 31; - - /* Select Stream Buffer Size and pid filter option*/ - if (pid_filter) { - props->adapter[0].fe[0].stream.u.bulk.buffersize = - TS_BUFFER_SIZE_MAX; - props->adapter[0].fe[0].caps &= - ~DVB_USB_ADAP_NEED_PID_FILTERING; - } else - props->adapter[0].fe[0].stream.u.bulk.buffersize = - TS_BUFFER_SIZE_PID; - - if (it913x_config.dual_mode) { - props->adapter[1].fe[0].stream.u.bulk.buffersize = - props->adapter[0].fe[0].stream.u.bulk.buffersize; - props->num_adapters = 2; - if (pid_filter) - props->adapter[1].fe[0].caps = - props->adapter[0].fe[0].caps; - } else - props->num_adapters = 1; - - info("Dual mode=%x Tuner Type=%x", it913x_config.dual_mode, - it913x_config.tuner_id_0); - - ret = ite_firmware_select(udev, props); - - return ret; -} - -static int it913x_identify_state(struct usb_device *udev, - struct dvb_usb_device_properties *props, - struct dvb_usb_device_description **desc, - int *cold) -{ - int ret = 0, firm_no; - u8 reg; - - firm_no = it913x_return_status(udev); - - /* Read and select config */ - ret = it913x_select_config(udev, props); - if (ret < 0) - return ret; - - if (firm_no > 0) { - *cold = 0; - return 0; - } - - if (it913x_config.dual_mode) { - it913x_config.tuner_id_1 = it913x_read_reg(udev, 0x49e0); - ret = it913x_wr_reg(udev, DEV_0, GPIOH1_EN, 0x1); - ret |= it913x_wr_reg(udev, DEV_0, GPIOH1_ON, 0x1); - ret |= it913x_wr_reg(udev, DEV_0, GPIOH1_O, 0x1); - msleep(50); - ret |= it913x_wr_reg(udev, DEV_0, GPIOH1_O, 0x0); - msleep(50); - reg = it913x_read_reg(udev, GPIOH1_O); - if (reg == 0) { - ret |= it913x_wr_reg(udev, DEV_0, GPIOH1_O, 0x1); - ret |= it913x_return_status(udev); - if (ret != 0) - ret = it913x_wr_reg(udev, DEV_0, - GPIOH1_O, 0x0); - } - } - - reg = it913x_read_reg(udev, IO_MUX_POWER_CLK); - - if (it913x_config.dual_mode) { - ret |= it913x_wr_reg(udev, DEV_0, 0x4bfb, CHIP2_I2C_ADDR); - if (it913x_config.firmware_ver == 1) - ret |= it913x_wr_reg(udev, DEV_0, 0xcfff, 0x1); - else - ret |= it913x_wr_reg(udev, DEV_0, CLK_O_EN, 0x1); - } else { - ret |= it913x_wr_reg(udev, DEV_0, 0x4bfb, 0x0); - if (it913x_config.firmware_ver == 1) - ret |= it913x_wr_reg(udev, DEV_0, 0xcfff, 0x0); - else - ret |= it913x_wr_reg(udev, DEV_0, CLK_O_EN, 0x0); - } - - *cold = 1; - - return (ret < 0) ? -ENODEV : 0; -} - -static int it913x_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct it913x_state *st = adap->dev->priv; - int ret = 0; - u8 pro = (adap->id == 0) ? DEV_0_DMOD : DEV_1_DMOD; - - deb_info(1, "STM (%02x)", onoff); - - if (!onoff) { - mutex_lock(&adap->dev->i2c_mutex); - - ret = it913x_wr_reg(adap->dev->udev, pro, PID_RST, 0x1); - - mutex_unlock(&adap->dev->i2c_mutex); - st->pid_filter_onoff = - adap->fe_adap[adap->active_fe].pid_filtering; - - } - - return ret; -} - -static int it913x_download_firmware(struct usb_device *udev, - const struct firmware *fw) -{ - int ret = 0, i = 0, pos = 0; - u8 packet_size, min_pkt; - u8 *fw_data; - - ret = it913x_wr_reg(udev, DEV_0, I2C_CLK, I2C_CLK_100); - - info("FRM Starting Firmware Download"); - - /* Multi firmware loader */ - /* This uses scatter write firmware headers */ - /* The firmware must start with 03 XX 00 */ - /* and be the extact firmware length */ - - if (it913x_config.chip_ver == 2) - min_pkt = 0x11; - else - min_pkt = 0x19; - - while (i <= fw->size) { - if (((fw->data[i] == 0x3) && (fw->data[i + 2] == 0x0)) - || (i == fw->size)) { - packet_size = i - pos; - if ((packet_size > min_pkt) || (i == fw->size)) { - fw_data = (u8 *)(fw->data + pos); - pos += packet_size; - if (packet_size > 0) { - ret = it913x_io(udev, WRITE_DATA, - DEV_0, CMD_SCATTER_WRITE, 0, - 0, fw_data, packet_size); - if (ret < 0) - break; - } - udelay(1000); - } - } - i++; - } - - if (ret < 0) - info("FRM Firmware Download Failed (%d)" , ret); - else - info("FRM Firmware Download Completed - Resetting Device"); - - msleep(30); - - ret = it913x_io(udev, WRITE_CMD, DEV_0, CMD_BOOT, 0, 0, NULL, 0); - if (ret < 0) - info("FRM Device not responding to reboot"); - - ret = it913x_return_status(udev); - if (ret == 0) { - info("FRM Failed to reboot device"); - return -ENODEV; - } - - msleep(30); - - ret = it913x_wr_reg(udev, DEV_0, I2C_CLK, I2C_CLK_400); - - msleep(30); - - /* Tuner function */ - if (it913x_config.dual_mode) - ret |= it913x_wr_reg(udev, DEV_0_DMOD , 0xec4c, 0xa0); - else - ret |= it913x_wr_reg(udev, DEV_0_DMOD , 0xec4c, 0x68); - - if ((it913x_config.chip_ver == 1) && - (it913x_config.chip_type == 0x9135)) { - ret |= it913x_wr_reg(udev, DEV_0, PADODPU, 0x0); - ret |= it913x_wr_reg(udev, DEV_0, AGC_O_D, 0x0); - if (it913x_config.dual_mode) { - ret |= it913x_wr_reg(udev, DEV_1, PADODPU, 0x0); - ret |= it913x_wr_reg(udev, DEV_1, AGC_O_D, 0x0); - } - } - - return (ret < 0) ? -ENODEV : 0; -} - -static int it913x_name(struct dvb_usb_adapter *adap) -{ - const char *desc = adap->dev->desc->name; - char *fe_name[] = {"_1", "_2", "_3", "_4"}; - char *name = adap->fe_adap[0].fe->ops.info.name; - - strlcpy(name, desc, 128); - strlcat(name, fe_name[adap->id], 128); - - return 0; -} - -static int it913x_frontend_attach(struct dvb_usb_adapter *adap) -{ - struct usb_device *udev = adap->dev->udev; - struct it913x_state *st = adap->dev->priv; - int ret = 0; - u8 adap_addr = I2C_BASE_ADDR + (adap->id << 5); - u16 ep_size = adap->props.fe[0].stream.u.bulk.buffersize / 4; - u8 pkt_size = 0x80; - - if (adap->dev->udev->speed != USB_SPEED_HIGH) - pkt_size = 0x10; - - it913x_config.adf = it913x_read_reg(udev, IO_MUX_POWER_CLK); - - if (adap->id == 0) - memcpy(&st->it913x_config, &it913x_config, - sizeof(struct ite_config)); - - adap->fe_adap[0].fe = dvb_attach(it913x_fe_attach, - &adap->dev->i2c_adap, adap_addr, &st->it913x_config); - - if (adap->id == 0 && adap->fe_adap[0].fe) { - ret = it913x_wr_reg(udev, DEV_0_DMOD, MP2_SW_RST, 0x1); - ret = it913x_wr_reg(udev, DEV_0_DMOD, MP2IF2_SW_RST, 0x1); - ret = it913x_wr_reg(udev, DEV_0, EP0_TX_EN, 0x0f); - ret = it913x_wr_reg(udev, DEV_0, EP0_TX_NAK, 0x1b); - ret = it913x_wr_reg(udev, DEV_0, EP0_TX_EN, 0x2f); - ret = it913x_wr_reg(udev, DEV_0, EP4_TX_LEN_LSB, - ep_size & 0xff); - ret = it913x_wr_reg(udev, DEV_0, EP4_TX_LEN_MSB, ep_size >> 8); - ret = it913x_wr_reg(udev, DEV_0, EP4_MAX_PKT, pkt_size); - } else if (adap->id == 1 && adap->fe_adap[0].fe) { - ret = it913x_wr_reg(udev, DEV_0, EP0_TX_EN, 0x6f); - ret = it913x_wr_reg(udev, DEV_0, EP5_TX_LEN_LSB, - ep_size & 0xff); - ret = it913x_wr_reg(udev, DEV_0, EP5_TX_LEN_MSB, ep_size >> 8); - ret = it913x_wr_reg(udev, DEV_0, EP5_MAX_PKT, pkt_size); - ret = it913x_wr_reg(udev, DEV_0_DMOD, MP2IF2_EN, 0x1); - ret = it913x_wr_reg(udev, DEV_1_DMOD, MP2IF_SERIAL, 0x1); - ret = it913x_wr_reg(udev, DEV_1, TOP_HOSTB_SER_MODE, 0x1); - ret = it913x_wr_reg(udev, DEV_0_DMOD, TSIS_ENABLE, 0x1); - ret = it913x_wr_reg(udev, DEV_0_DMOD, MP2_SW_RST, 0x0); - ret = it913x_wr_reg(udev, DEV_0_DMOD, MP2IF2_SW_RST, 0x0); - ret = it913x_wr_reg(udev, DEV_0_DMOD, MP2IF2_HALF_PSB, 0x0); - ret = it913x_wr_reg(udev, DEV_0_DMOD, MP2IF_STOP_EN, 0x1); - ret = it913x_wr_reg(udev, DEV_1_DMOD, MPEG_FULL_SPEED, 0x0); - ret = it913x_wr_reg(udev, DEV_1_DMOD, MP2IF_STOP_EN, 0x0); - } else - return -ENODEV; - - ret = it913x_name(adap); - - return ret; -} - -/* DVB USB Driver */ -static struct dvb_usb_device_properties it913x_properties; - -static int it913x_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - cmd_counter = 0; - if (0 == dvb_usb_device_init(intf, &it913x_properties, - THIS_MODULE, NULL, adapter_nr)) { - info("DEV registering device driver"); - return 0; - } - - info("DEV it913x Error"); - return -ENODEV; - -} - -static struct usb_device_id it913x_table[] = { - { USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_UB499_2T_T09) }, - { USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135) }, - { USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV22_IT9137) }, - { USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135_9005) }, - { USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135_9006) }, - {} /* Terminating entry */ -}; - -MODULE_DEVICE_TABLE(usb, it913x_table); - -static struct dvb_usb_device_properties it913x_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - .usb_ctrl = DEVICE_SPECIFIC, - .download_firmware = it913x_download_firmware, - .firmware = "dvb-usb-it9137-01.fw", - .no_reconnect = 1, - .size_of_priv = sizeof(struct it913x_state), - .num_adapters = 2, - .adapter = { - { - .num_frontends = 1, - .fe = {{ - .caps = DVB_USB_ADAP_HAS_PID_FILTER| - DVB_USB_ADAP_NEED_PID_FILTERING| - DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, - .streaming_ctrl = it913x_streaming_ctrl, - .pid_filter_count = 31, - .pid_filter = it913x_pid_filter, - .pid_filter_ctrl = it913x_pid_filter_ctrl, - .frontend_attach = it913x_frontend_attach, - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_BULK, - .count = 10, - .endpoint = 0x04, - .u = {/* Keep Low if PID filter on */ - .bulk = { - .buffersize = - TS_BUFFER_SIZE_PID, - } - } - } - }}, - }, - { - .num_frontends = 1, - .fe = {{ - .caps = DVB_USB_ADAP_HAS_PID_FILTER| - DVB_USB_ADAP_NEED_PID_FILTERING| - DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, - .streaming_ctrl = it913x_streaming_ctrl, - .pid_filter_count = 31, - .pid_filter = it913x_pid_filter, - .pid_filter_ctrl = it913x_pid_filter_ctrl, - .frontend_attach = it913x_frontend_attach, - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_BULK, - .count = 5, - .endpoint = 0x05, - .u = { - .bulk = { - .buffersize = - TS_BUFFER_SIZE_PID, - } - } - } - }}, - } - }, - .identify_state = it913x_identify_state, - .rc.core = { - .protocol = RC_TYPE_NEC, - .module_name = "it913x", - .rc_query = it913x_rc_query, - .rc_interval = IT913X_POLL, - .allowed_protos = RC_TYPE_NEC, - .rc_codes = RC_MAP_IT913X_V1, - }, - .i2c_algo = &it913x_i2c_algo, - .num_device_descs = 5, - .devices = { - { "Kworld UB499-2T T09(IT9137)", - { &it913x_table[0], NULL }, - }, - { "ITE 9135 Generic", - { &it913x_table[1], NULL }, - }, - { "Sveon STV22 Dual DVB-T HDTV(IT9137)", - { &it913x_table[2], NULL }, - }, - { "ITE 9135(9005) Generic", - { &it913x_table[3], NULL }, - }, - { "ITE 9135(9006) Generic", - { &it913x_table[4], NULL }, - }, - } -}; - -static struct usb_driver it913x_driver = { - .name = "it913x", - .probe = it913x_probe, - .disconnect = dvb_usb_device_exit, - .id_table = it913x_table, -}; - -module_usb_driver(it913x_driver); - -MODULE_AUTHOR("Malcolm Priestley "); -MODULE_DESCRIPTION("it913x USB 2 Driver"); -MODULE_VERSION("1.28"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/lmedm04.c b/drivers/media/dvb/dvb-usb/lmedm04.c deleted file mode 100644 index 25d1031460f8..000000000000 --- a/drivers/media/dvb/dvb-usb/lmedm04.c +++ /dev/null @@ -1,1407 +0,0 @@ -/* DVB USB compliant linux driver for - * - * DM04/QQBOX DVB-S USB BOX LME2510C + SHARP:BS2F7HZ7395 - * LME2510C + LG TDQY-P001F - * LME2510C + BS2F7HZ0194 - * LME2510 + LG TDQY-P001F - * LME2510 + BS2F7HZ0194 - * - * MVB7395 (LME2510C+SHARP:BS2F7HZ7395) - * SHARP:BS2F7HZ7395 = (STV0288+Sharp IX2505V) - * - * MV001F (LME2510+LGTDQY-P001F) - * LG TDQY - P001F =(TDA8263 + TDA10086H) - * - * MVB0001F (LME2510C+LGTDQT-P001F) - * - * MV0194 (LME2510+SHARP:BS2F7HZ0194) - * SHARP:BS2F7HZ0194 = (STV0299+IX2410) - * - * MVB0194 (LME2510C+SHARP0194) - * - * For firmware see Documentation/dvb/lmedm04.txt - * - * I2C addresses: - * 0xd0 - STV0288 - Demodulator - * 0xc0 - Sharp IX2505V - Tuner - * -- - * 0x1c - TDA10086 - Demodulator - * 0xc0 - TDA8263 - Tuner - * -- - * 0xd0 - STV0299 - Demodulator - * 0xc0 - IX2410 - Tuner - * - * - * VID = 3344 PID LME2510=1122 LME2510C=1120 - * - * Copyright (C) 2010 Malcolm Priestley (tvboxspy@gmail.com) - * LME2510(C)(C) Leaguerme (Shenzhen) MicroElectronics Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2, as - * published by the Free Software Foundation. - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * - * see Documentation/dvb/README.dvb-usb for more information - * - * Known Issues : - * LME2510: Non Intel USB chipsets fail to maintain High Speed on - * Boot or Hot Plug. - * - * QQbox suffers from noise on LNB voltage. - * - * LME2510: SHARP:BS2F7HZ0194(MV0194) cannot cold reset and share system - * with other tuners. After a cold reset streaming will not start. - * - */ -#define DVB_USB_LOG_PREFIX "LME2510(C)" -#include -#include -#include - -#include "dvb-usb.h" -#include "lmedm04.h" -#include "tda826x.h" -#include "tda10086.h" -#include "stv0288.h" -#include "ix2505v.h" -#include "stv0299.h" -#include "dvb-pll.h" -#include "z0194a.h" -#include "m88rs2000.h" - - - -/* debug */ -static int dvb_usb_lme2510_debug; -#define l_dprintk(var, level, args...) do { \ - if ((var >= level)) \ - printk(KERN_DEBUG DVB_USB_LOG_PREFIX ": " args); \ -} while (0) - -#define deb_info(level, args...) l_dprintk(dvb_usb_lme2510_debug, level, args) -#define debug_data_snipet(level, name, p) \ - deb_info(level, name" (%02x%02x%02x%02x%02x%02x%02x%02x)", \ - *p, *(p+1), *(p+2), *(p+3), *(p+4), \ - *(p+5), *(p+6), *(p+7)); - - -module_param_named(debug, dvb_usb_lme2510_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))." - DVB_USB_DEBUG_STATUS); - -static int dvb_usb_lme2510_firmware; -module_param_named(firmware, dvb_usb_lme2510_firmware, int, 0644); -MODULE_PARM_DESC(firmware, "set default firmware 0=Sharp7395 1=LG"); - -static int pid_filter; -module_param_named(pid, pid_filter, int, 0644); -MODULE_PARM_DESC(pid, "set default 0=default 1=off 2=on"); - - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -#define TUNER_DEFAULT 0x0 -#define TUNER_LG 0x1 -#define TUNER_S7395 0x2 -#define TUNER_S0194 0x3 -#define TUNER_RS2000 0x4 - -struct lme2510_state { - u8 id; - u8 tuner_config; - u8 signal_lock; - u8 signal_level; - u8 signal_sn; - u8 time_key; - u8 last_key; - u8 key_timeout; - u8 i2c_talk_onoff; - u8 i2c_gate; - u8 i2c_tuner_gate_w; - u8 i2c_tuner_gate_r; - u8 i2c_tuner_addr; - u8 stream_on; - u8 pid_size; - u8 pid_off; - void *buffer; - struct urb *lme_urb; - void *usb_buffer; - -}; - -static int lme2510_bulk_write(struct usb_device *dev, - u8 *snd, int len, u8 pipe) -{ - int ret, actual_l; - - ret = usb_bulk_msg(dev, usb_sndbulkpipe(dev, pipe), - snd, len , &actual_l, 100); - return ret; -} - -static int lme2510_bulk_read(struct usb_device *dev, - u8 *rev, int len, u8 pipe) -{ - int ret, actual_l; - - ret = usb_bulk_msg(dev, usb_rcvbulkpipe(dev, pipe), - rev, len , &actual_l, 200); - return ret; -} - -static int lme2510_usb_talk(struct dvb_usb_device *d, - u8 *wbuf, int wlen, u8 *rbuf, int rlen) -{ - struct lme2510_state *st = d->priv; - u8 *buff; - int ret = 0; - - if (st->usb_buffer == NULL) { - st->usb_buffer = kmalloc(64, GFP_KERNEL); - if (st->usb_buffer == NULL) { - info("MEM Error no memory"); - return -ENOMEM; - } - } - buff = st->usb_buffer; - - ret = mutex_lock_interruptible(&d->usb_mutex); - - if (ret < 0) - return -EAGAIN; - - /* the read/write capped at 64 */ - memcpy(buff, wbuf, (wlen < 64) ? wlen : 64); - - ret |= lme2510_bulk_write(d->udev, buff, wlen , 0x01); - - ret |= lme2510_bulk_read(d->udev, buff, (rlen < 64) ? - rlen : 64 , 0x01); - - if (rlen > 0) - memcpy(rbuf, buff, rlen); - - mutex_unlock(&d->usb_mutex); - - return (ret < 0) ? -ENODEV : 0; -} - -static int lme2510_stream_restart(struct dvb_usb_device *d) -{ - struct lme2510_state *st = d->priv; - u8 all_pids[] = LME_ALL_PIDS; - u8 stream_on[] = LME_ST_ON_W; - int ret; - u8 rbuff[1]; - if (st->pid_off) - ret = lme2510_usb_talk(d, all_pids, sizeof(all_pids), - rbuff, sizeof(rbuff)); - /*Restart Stream Command*/ - ret = lme2510_usb_talk(d, stream_on, sizeof(stream_on), - rbuff, sizeof(rbuff)); - return ret; -} - -static int lme2510_enable_pid(struct dvb_usb_device *d, u8 index, u16 pid_out) -{ - struct lme2510_state *st = d->priv; - static u8 pid_buff[] = LME_ZERO_PID; - static u8 rbuf[1]; - u8 pid_no = index * 2; - u8 pid_len = pid_no + 2; - int ret = 0; - deb_info(1, "PID Setting Pid %04x", pid_out); - - if (st->pid_size == 0) - ret |= lme2510_stream_restart(d); - - pid_buff[2] = pid_no; - pid_buff[3] = (u8)pid_out & 0xff; - pid_buff[4] = pid_no + 1; - pid_buff[5] = (u8)(pid_out >> 8); - - if (pid_len > st->pid_size) - st->pid_size = pid_len; - pid_buff[7] = 0x80 + st->pid_size; - - ret |= lme2510_usb_talk(d, pid_buff , - sizeof(pid_buff) , rbuf, sizeof(rbuf)); - - if (st->stream_on) - ret |= lme2510_stream_restart(d); - - return ret; -} - -static void lme2510_int_response(struct urb *lme_urb) -{ - struct dvb_usb_adapter *adap = lme_urb->context; - struct lme2510_state *st = adap->dev->priv; - static u8 *ibuf, *rbuf; - int i = 0, offset; - u32 key; - - switch (lme_urb->status) { - case 0: - case -ETIMEDOUT: - break; - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: - return; - default: - info("Error %x", lme_urb->status); - break; - } - - rbuf = (u8 *) lme_urb->transfer_buffer; - - offset = ((lme_urb->actual_length/8) > 4) - ? 4 : (lme_urb->actual_length/8) ; - - for (i = 0; i < offset; ++i) { - ibuf = (u8 *)&rbuf[i*8]; - deb_info(5, "INT O/S C =%02x C/O=%02x Type =%02x%02x", - offset, i, ibuf[0], ibuf[1]); - - switch (ibuf[0]) { - case 0xaa: - debug_data_snipet(1, "INT Remote data snipet", ibuf); - if ((ibuf[4] + ibuf[5]) == 0xff) { - key = ibuf[5]; - key += (ibuf[3] > 0) - ? (ibuf[3] ^ 0xff) << 8 : 0; - key += (ibuf[2] ^ 0xff) << 16; - deb_info(1, "INT Key =%08x", key); - if (adap->dev->rc_dev != NULL) - rc_keydown(adap->dev->rc_dev, key, 0); - } - break; - case 0xbb: - switch (st->tuner_config) { - case TUNER_LG: - if (ibuf[2] > 0) - st->signal_lock = ibuf[2]; - st->signal_level = ibuf[4]; - st->signal_sn = ibuf[3]; - st->time_key = ibuf[7]; - break; - case TUNER_S7395: - case TUNER_S0194: - /* Tweak for earlier firmware*/ - if (ibuf[1] == 0x03) { - if (ibuf[2] > 1) - st->signal_lock = ibuf[2]; - st->signal_level = ibuf[3]; - st->signal_sn = ibuf[4]; - } else { - st->signal_level = ibuf[4]; - st->signal_sn = ibuf[5]; - st->signal_lock = - (st->signal_lock & 0xf7) + - ((ibuf[2] & 0x01) << 0x03); - } - break; - case TUNER_RS2000: - if (ibuf[2] > 0) - st->signal_lock = 0xff; - else - st->signal_lock = 0xf0; - st->signal_level = ibuf[4]; - st->signal_sn = ibuf[5]; - st->time_key = ibuf[7]; - default: - break; - } - debug_data_snipet(5, "INT Remote data snipet in", ibuf); - break; - case 0xcc: - debug_data_snipet(1, "INT Control data snipet", ibuf); - break; - default: - debug_data_snipet(1, "INT Unknown data snipet", ibuf); - break; - } - } - usb_submit_urb(lme_urb, GFP_ATOMIC); -} - -static int lme2510_int_read(struct dvb_usb_adapter *adap) -{ - struct lme2510_state *lme_int = adap->dev->priv; - - lme_int->lme_urb = usb_alloc_urb(0, GFP_ATOMIC); - - if (lme_int->lme_urb == NULL) - return -ENOMEM; - - lme_int->buffer = usb_alloc_coherent(adap->dev->udev, 128, GFP_ATOMIC, - &lme_int->lme_urb->transfer_dma); - - if (lme_int->buffer == NULL) - return -ENOMEM; - - usb_fill_int_urb(lme_int->lme_urb, - adap->dev->udev, - usb_rcvintpipe(adap->dev->udev, 0xa), - lme_int->buffer, - 128, - lme2510_int_response, - adap, - 8); - - lme_int->lme_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - - usb_submit_urb(lme_int->lme_urb, GFP_ATOMIC); - info("INT Interrupt Service Started"); - - return 0; -} - -static int lme2510_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct lme2510_state *st = adap->dev->priv; - static u8 clear_pid_reg[] = LME_ALL_PIDS; - static u8 rbuf[1]; - int ret = 0; - - deb_info(1, "PID Clearing Filter"); - - mutex_lock(&adap->dev->i2c_mutex); - - if (!onoff) { - ret |= lme2510_usb_talk(adap->dev, clear_pid_reg, - sizeof(clear_pid_reg), rbuf, sizeof(rbuf)); - st->pid_off = true; - } else - st->pid_off = false; - - st->pid_size = 0; - - mutex_unlock(&adap->dev->i2c_mutex); - - return 0; -} - -static int lme2510_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid, - int onoff) -{ - int ret = 0; - - deb_info(3, "%s PID=%04x Index=%04x onoff=%02x", __func__, - pid, index, onoff); - - if (onoff) { - mutex_lock(&adap->dev->i2c_mutex); - ret |= lme2510_enable_pid(adap->dev, index, pid); - mutex_unlock(&adap->dev->i2c_mutex); - } - - - return ret; -} - - -static int lme2510_return_status(struct usb_device *dev) -{ - int ret = 0; - u8 *data; - - data = kzalloc(10, GFP_KERNEL); - if (!data) - return -ENOMEM; - - ret |= usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - 0x06, 0x80, 0x0302, 0x00, data, 0x0006, 200); - info("Firmware Status: %x (%x)", ret , data[2]); - - ret = (ret < 0) ? -ENODEV : data[2]; - kfree(data); - return ret; -} - -static int lme2510_msg(struct dvb_usb_device *d, - u8 *wbuf, int wlen, u8 *rbuf, int rlen) -{ - int ret = 0; - struct lme2510_state *st = d->priv; - - if (st->i2c_talk_onoff == 1) { - - ret = lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); - - switch (st->tuner_config) { - case TUNER_LG: - if (wbuf[2] == 0x1c) { - if (wbuf[3] == 0x0e) { - st->signal_lock = rbuf[1]; - if ((st->stream_on & 1) && - (st->signal_lock & 0x10)) { - lme2510_stream_restart(d); - st->i2c_talk_onoff = 0; - } - msleep(80); - } - } - break; - case TUNER_S7395: - if (wbuf[2] == 0xd0) { - if (wbuf[3] == 0x24) { - st->signal_lock = rbuf[1]; - if ((st->stream_on & 1) && - (st->signal_lock & 0x8)) { - lme2510_stream_restart(d); - st->i2c_talk_onoff = 0; - } - } - } - break; - case TUNER_S0194: - if (wbuf[2] == 0xd0) { - if (wbuf[3] == 0x1b) { - st->signal_lock = rbuf[1]; - if ((st->stream_on & 1) && - (st->signal_lock & 0x8)) { - lme2510_stream_restart(d); - st->i2c_talk_onoff = 0; - } - } - } - break; - case TUNER_RS2000: - default: - break; - } - } else { - /* TODO rewrite this section */ - switch (st->tuner_config) { - case TUNER_LG: - switch (wbuf[3]) { - case 0x0e: - rbuf[0] = 0x55; - rbuf[1] = st->signal_lock; - break; - case 0x43: - rbuf[0] = 0x55; - rbuf[1] = st->signal_level; - break; - case 0x1c: - rbuf[0] = 0x55; - rbuf[1] = st->signal_sn; - break; - case 0x15: - case 0x16: - case 0x17: - case 0x18: - rbuf[0] = 0x55; - rbuf[1] = 0x00; - break; - default: - lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); - st->i2c_talk_onoff = 1; - break; - } - break; - case TUNER_S7395: - switch (wbuf[3]) { - case 0x10: - rbuf[0] = 0x55; - rbuf[1] = (st->signal_level & 0x80) - ? 0 : (st->signal_level * 2); - break; - case 0x2d: - rbuf[0] = 0x55; - rbuf[1] = st->signal_sn; - break; - case 0x24: - rbuf[0] = 0x55; - rbuf[1] = st->signal_lock; - break; - case 0x2e: - case 0x26: - case 0x27: - rbuf[0] = 0x55; - rbuf[1] = 0x00; - break; - default: - lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); - st->i2c_talk_onoff = 1; - break; - } - break; - case TUNER_S0194: - switch (wbuf[3]) { - case 0x18: - rbuf[0] = 0x55; - rbuf[1] = (st->signal_level & 0x80) - ? 0 : (st->signal_level * 2); - break; - case 0x24: - rbuf[0] = 0x55; - rbuf[1] = st->signal_sn; - break; - case 0x1b: - rbuf[0] = 0x55; - rbuf[1] = st->signal_lock; - break; - case 0x19: - case 0x25: - case 0x1e: - case 0x1d: - rbuf[0] = 0x55; - rbuf[1] = 0x00; - break; - default: - lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); - st->i2c_talk_onoff = 1; - break; - } - break; - case TUNER_RS2000: - switch (wbuf[3]) { - case 0x8c: - rbuf[0] = 0x55; - rbuf[1] = 0xff; - if (st->last_key == st->time_key) { - st->key_timeout++; - if (st->key_timeout > 5) - rbuf[1] = 0; - } else - st->key_timeout = 0; - st->last_key = st->time_key; - break; - default: - lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); - st->i2c_talk_onoff = 1; - break; - } - default: - break; - } - - deb_info(4, "I2C From Interrupt Message out(%02x) in(%02x)", - wbuf[3], rbuf[1]); - - } - - return ret; -} - - -static int lme2510_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - struct lme2510_state *st = d->priv; - static u8 obuf[64], ibuf[64]; - int i, read, read_o; - u16 len; - u8 gate = st->i2c_gate; - - mutex_lock(&d->i2c_mutex); - - if (gate == 0) - gate = 5; - - if (num > 2) - warn("more than 2 i2c messages" - "at a time is not handled yet. TODO."); - - for (i = 0; i < num; i++) { - read_o = 1 & (msg[i].flags & I2C_M_RD); - read = i+1 < num && (msg[i+1].flags & I2C_M_RD); - read |= read_o; - gate = (msg[i].addr == st->i2c_tuner_addr) - ? (read) ? st->i2c_tuner_gate_r - : st->i2c_tuner_gate_w - : st->i2c_gate; - obuf[0] = gate | (read << 7); - - if (gate == 5) - obuf[1] = (read) ? 2 : msg[i].len + 1; - else - obuf[1] = msg[i].len + read + 1; - - obuf[2] = msg[i].addr; - if (read) { - if (read_o) - len = 3; - else { - memcpy(&obuf[3], msg[i].buf, msg[i].len); - obuf[msg[i].len+3] = msg[i+1].len; - len = msg[i].len+4; - } - } else { - memcpy(&obuf[3], msg[i].buf, msg[i].len); - len = msg[i].len+3; - } - - if (lme2510_msg(d, obuf, len, ibuf, 64) < 0) { - deb_info(1, "i2c transfer failed."); - mutex_unlock(&d->i2c_mutex); - return -EAGAIN; - } - - if (read) { - if (read_o) - memcpy(msg[i].buf, &ibuf[1], msg[i].len); - else { - memcpy(msg[i+1].buf, &ibuf[1], msg[i+1].len); - i++; - } - } - } - - mutex_unlock(&d->i2c_mutex); - return i; -} - -static u32 lme2510_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm lme2510_i2c_algo = { - .master_xfer = lme2510_i2c_xfer, - .functionality = lme2510_i2c_func, -}; - -/* Callbacks for DVB USB */ -static int lme2510_identify_state(struct usb_device *udev, - struct dvb_usb_device_properties *props, - struct dvb_usb_device_description **desc, - int *cold) -{ - if (pid_filter != 2) - props->adapter[0].fe[0].caps &= - ~DVB_USB_ADAP_NEED_PID_FILTERING; - *cold = 0; - return 0; -} - -static int lme2510_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct lme2510_state *st = adap->dev->priv; - static u8 clear_reg_3[] = LME_ALL_PIDS; - static u8 rbuf[1]; - int ret = 0, rlen = sizeof(rbuf); - - deb_info(1, "STM (%02x)", onoff); - - /* Streaming is started by FE_HAS_LOCK */ - if (onoff == 1) - st->stream_on = 1; - else { - deb_info(1, "STM Steam Off"); - /* mutex is here only to avoid collision with I2C */ - mutex_lock(&adap->dev->i2c_mutex); - - ret = lme2510_usb_talk(adap->dev, clear_reg_3, - sizeof(clear_reg_3), rbuf, rlen); - st->stream_on = 0; - st->i2c_talk_onoff = 1; - - mutex_unlock(&adap->dev->i2c_mutex); - } - - return (ret < 0) ? -ENODEV : 0; -} - -static u8 check_sum(u8 *p, u8 len) -{ - u8 sum = 0; - while (len--) - sum += *p++; - return sum; -} - -static int lme2510_download_firmware(struct usb_device *dev, - const struct firmware *fw) -{ - int ret = 0; - u8 *data; - u16 j, wlen, len_in, start, end; - u8 packet_size, dlen, i; - u8 *fw_data; - - packet_size = 0x31; - len_in = 1; - - data = kzalloc(512, GFP_KERNEL); - if (!data) { - info("FRM Could not start Firmware Download (Buffer allocation failed)"); - return -ENOMEM; - } - - info("FRM Starting Firmware Download"); - - for (i = 1; i < 3; i++) { - start = (i == 1) ? 0 : 512; - end = (i == 1) ? 512 : fw->size; - for (j = start; j < end; j += (packet_size+1)) { - fw_data = (u8 *)(fw->data + j); - if ((end - j) > packet_size) { - data[0] = i; - dlen = packet_size; - } else { - data[0] = i | 0x80; - dlen = (u8)(end - j)-1; - } - data[1] = dlen; - memcpy(&data[2], fw_data, dlen+1); - wlen = (u8) dlen + 4; - data[wlen-1] = check_sum(fw_data, dlen+1); - deb_info(1, "Data S=%02x:E=%02x CS= %02x", data[3], - data[dlen+2], data[dlen+3]); - ret |= lme2510_bulk_write(dev, data, wlen, 1); - ret |= lme2510_bulk_read(dev, data, len_in , 1); - ret |= (data[0] == 0x88) ? 0 : -1; - } - } - - usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - 0x06, 0x80, 0x0200, 0x00, data, 0x0109, 1000); - - - data[0] = 0x8a; - len_in = 1; - msleep(2000); - ret |= lme2510_bulk_write(dev, data , len_in, 1); /*Resetting*/ - ret |= lme2510_bulk_read(dev, data, len_in, 1); - msleep(400); - - if (ret < 0) - info("FRM Firmware Download Failed (%04x)" , ret); - else - info("FRM Firmware Download Completed - Resetting Device"); - - kfree(data); - return (ret < 0) ? -ENODEV : 0; -} - -static void lme_coldreset(struct usb_device *dev) -{ - int ret = 0, len_in; - u8 data[512] = {0}; - - data[0] = 0x0a; - len_in = 1; - info("FRM Firmware Cold Reset"); - ret |= lme2510_bulk_write(dev, data , len_in, 1); /*Cold Resetting*/ - ret |= lme2510_bulk_read(dev, data, len_in, 1); - - return; -} - -static int lme_firmware_switch(struct usb_device *udev, int cold) -{ - const struct firmware *fw = NULL; - const char fw_c_s7395[] = "dvb-usb-lme2510c-s7395.fw"; - const char fw_c_lg[] = "dvb-usb-lme2510c-lg.fw"; - const char fw_c_s0194[] = "dvb-usb-lme2510c-s0194.fw"; - const char fw_c_rs2000[] = "dvb-usb-lme2510c-rs2000.fw"; - const char fw_lg[] = "dvb-usb-lme2510-lg.fw"; - const char fw_s0194[] = "dvb-usb-lme2510-s0194.fw"; - const char *fw_lme; - int ret = 0, cold_fw; - - cold = (cold > 0) ? (cold & 1) : 0; - - cold_fw = !cold; - - switch (le16_to_cpu(udev->descriptor.idProduct)) { - case 0x1122: - switch (dvb_usb_lme2510_firmware) { - default: - dvb_usb_lme2510_firmware = TUNER_S0194; - case TUNER_S0194: - fw_lme = fw_s0194; - ret = request_firmware(&fw, fw_lme, &udev->dev); - if (ret == 0) { - cold = 0; - break; - } - dvb_usb_lme2510_firmware = TUNER_LG; - case TUNER_LG: - fw_lme = fw_lg; - ret = request_firmware(&fw, fw_lme, &udev->dev); - if (ret == 0) - break; - info("FRM No Firmware Found - please install"); - dvb_usb_lme2510_firmware = TUNER_DEFAULT; - cold = 0; - cold_fw = 0; - break; - } - break; - case 0x1120: - switch (dvb_usb_lme2510_firmware) { - default: - dvb_usb_lme2510_firmware = TUNER_S7395; - case TUNER_S7395: - fw_lme = fw_c_s7395; - ret = request_firmware(&fw, fw_lme, &udev->dev); - if (ret == 0) { - cold = 0; - break; - } - dvb_usb_lme2510_firmware = TUNER_LG; - case TUNER_LG: - fw_lme = fw_c_lg; - ret = request_firmware(&fw, fw_lme, &udev->dev); - if (ret == 0) - break; - dvb_usb_lme2510_firmware = TUNER_S0194; - case TUNER_S0194: - fw_lme = fw_c_s0194; - ret = request_firmware(&fw, fw_lme, &udev->dev); - if (ret == 0) - break; - info("FRM No Firmware Found - please install"); - dvb_usb_lme2510_firmware = TUNER_DEFAULT; - cold = 0; - cold_fw = 0; - break; - } - break; - case 0x22f0: - fw_lme = fw_c_rs2000; - ret = request_firmware(&fw, fw_lme, &udev->dev); - dvb_usb_lme2510_firmware = TUNER_RS2000; - break; - default: - fw_lme = fw_c_s7395; - } - - - if (cold_fw) { - info("FRM Loading %s file", fw_lme); - ret = lme2510_download_firmware(udev, fw); - } - - release_firmware(fw); - - if (cold) { - info("FRM Changing to %s firmware", fw_lme); - lme_coldreset(udev); - return -ENODEV; - } - - return ret; -} - -static int lme2510_kill_urb(struct usb_data_stream *stream) -{ - int i; - - for (i = 0; i < stream->urbs_submitted; i++) { - deb_info(3, "killing URB no. %d.", i); - /* stop the URB */ - usb_kill_urb(stream->urb_list[i]); - } - stream->urbs_submitted = 0; - - return 0; -} - -static struct tda10086_config tda10086_config = { - .demod_address = 0x1c, - .invert = 0, - .diseqc_tone = 1, - .xtal_freq = TDA10086_XTAL_16M, -}; - -static struct stv0288_config lme_config = { - .demod_address = 0xd0, - .min_delay_ms = 15, - .inittab = s7395_inittab, -}; - -static struct ix2505v_config lme_tuner = { - .tuner_address = 0xc0, - .min_delay_ms = 100, - .tuner_gain = 0x0, - .tuner_chargepump = 0x3, -}; - -static struct stv0299_config sharp_z0194_config = { - .demod_address = 0xd0, - .inittab = sharp_z0194a_inittab, - .mclk = 88000000UL, - .invert = 0, - .skip_reinit = 0, - .lock_output = STV0299_LOCKOUTPUT_1, - .volt13_op0_op1 = STV0299_VOLT13_OP1, - .min_delay_ms = 100, - .set_symbol_rate = sharp_z0194a_set_symbol_rate, -}; - -static int dm04_rs2000_set_ts_param(struct dvb_frontend *fe, - int caller) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct dvb_usb_device *d = adap->dev; - struct lme2510_state *st = d->priv; - - mutex_lock(&d->i2c_mutex); - if ((st->i2c_talk_onoff == 1) && (st->stream_on & 1)) { - st->i2c_talk_onoff = 0; - lme2510_stream_restart(d); - } - mutex_unlock(&d->i2c_mutex); - - return 0; -} - -static struct m88rs2000_config m88rs2000_config = { - .demod_addr = 0xd0, - .tuner_addr = 0xc0, - .set_ts_params = dm04_rs2000_set_ts_param, -}; - -static int dm04_lme2510_set_voltage(struct dvb_frontend *fe, - fe_sec_voltage_t voltage) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - static u8 voltage_low[] = LME_VOLTAGE_L; - static u8 voltage_high[] = LME_VOLTAGE_H; - static u8 rbuf[1]; - int ret = 0, len = 3, rlen = 1; - - mutex_lock(&adap->dev->i2c_mutex); - - switch (voltage) { - case SEC_VOLTAGE_18: - ret |= lme2510_usb_talk(adap->dev, - voltage_high, len, rbuf, rlen); - break; - - case SEC_VOLTAGE_OFF: - case SEC_VOLTAGE_13: - default: - ret |= lme2510_usb_talk(adap->dev, - voltage_low, len, rbuf, rlen); - break; - } - - mutex_unlock(&adap->dev->i2c_mutex); - - return (ret < 0) ? -ENODEV : 0; -} - -static int dm04_rs2000_read_signal_strength(struct dvb_frontend *fe, - u16 *strength) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct lme2510_state *st = adap->dev->priv; - - *strength = (u16)((u32)st->signal_level * 0xffff / 0x7f); - return 0; -} - -static int dm04_rs2000_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct lme2510_state *st = adap->dev->priv; - - *snr = (u16)((u32)st->signal_sn * 0xffff / 0xff); - return 0; -} - -static int lme_name(struct dvb_usb_adapter *adap) -{ - struct lme2510_state *st = adap->dev->priv; - const char *desc = adap->dev->desc->name; - char *fe_name[] = {"", " LG TDQY-P001F", " SHARP:BS2F7HZ7395", - " SHARP:BS2F7HZ0194", " RS2000"}; - char *name = adap->fe_adap[0].fe->ops.info.name; - - strlcpy(name, desc, 128); - strlcat(name, fe_name[st->tuner_config], 128); - - return 0; -} - -static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap) -{ - struct lme2510_state *st = adap->dev->priv; - - int ret = 0; - - st->i2c_talk_onoff = 1; - switch (le16_to_cpu(adap->dev->udev->descriptor.idProduct)) { - case 0x1122: - case 0x1120: - st->i2c_gate = 4; - adap->fe_adap[0].fe = dvb_attach(tda10086_attach, - &tda10086_config, &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe) { - info("TUN Found Frontend TDA10086"); - st->i2c_tuner_gate_w = 4; - st->i2c_tuner_gate_r = 4; - st->i2c_tuner_addr = 0xc0; - st->tuner_config = TUNER_LG; - if (dvb_usb_lme2510_firmware != TUNER_LG) { - dvb_usb_lme2510_firmware = TUNER_LG; - ret = lme_firmware_switch(adap->dev->udev, 1); - } - break; - } - - st->i2c_gate = 4; - adap->fe_adap[0].fe = dvb_attach(stv0299_attach, - &sharp_z0194_config, &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe) { - info("FE Found Stv0299"); - st->i2c_tuner_gate_w = 4; - st->i2c_tuner_gate_r = 5; - st->i2c_tuner_addr = 0xc0; - st->tuner_config = TUNER_S0194; - if (dvb_usb_lme2510_firmware != TUNER_S0194) { - dvb_usb_lme2510_firmware = TUNER_S0194; - ret = lme_firmware_switch(adap->dev->udev, 1); - } - break; - } - - st->i2c_gate = 5; - adap->fe_adap[0].fe = dvb_attach(stv0288_attach, &lme_config, - &adap->dev->i2c_adap); - - if (adap->fe_adap[0].fe) { - info("FE Found Stv0288"); - st->i2c_tuner_gate_w = 4; - st->i2c_tuner_gate_r = 5; - st->i2c_tuner_addr = 0xc0; - st->tuner_config = TUNER_S7395; - if (dvb_usb_lme2510_firmware != TUNER_S7395) { - dvb_usb_lme2510_firmware = TUNER_S7395; - ret = lme_firmware_switch(adap->dev->udev, 1); - } - break; - } - case 0x22f0: - st->i2c_gate = 5; - adap->fe_adap[0].fe = dvb_attach(m88rs2000_attach, - &m88rs2000_config, &adap->dev->i2c_adap); - - if (adap->fe_adap[0].fe) { - info("FE Found M88RS2000"); - st->i2c_tuner_gate_w = 5; - st->i2c_tuner_gate_r = 5; - st->i2c_tuner_addr = 0xc0; - st->tuner_config = TUNER_RS2000; - adap->fe_adap[0].fe->ops.read_signal_strength = - dm04_rs2000_read_signal_strength; - adap->fe_adap[0].fe->ops.read_snr = - dm04_rs2000_read_snr; - } - break; - } - - if (adap->fe_adap[0].fe == NULL) { - info("DM04/QQBOX Not Powered up or not Supported"); - return -ENODEV; - } - - if (ret) { - if (adap->fe_adap[0].fe) { - dvb_frontend_detach(adap->fe_adap[0].fe); - adap->fe_adap[0].fe = NULL; - } - adap->dev->props.rc.core.rc_codes = NULL; - return -ENODEV; - } - - adap->fe_adap[0].fe->ops.set_voltage = dm04_lme2510_set_voltage; - ret = lme_name(adap); - return ret; -} - -static int dm04_lme2510_tuner(struct dvb_usb_adapter *adap) -{ - struct lme2510_state *st = adap->dev->priv; - char *tun_msg[] = {"", "TDA8263", "IX2505V", "DVB_PLL_OPERA", "RS2000"}; - int ret = 0; - - switch (st->tuner_config) { - case TUNER_LG: - if (dvb_attach(tda826x_attach, adap->fe_adap[0].fe, 0xc0, - &adap->dev->i2c_adap, 1)) - ret = st->tuner_config; - break; - case TUNER_S7395: - if (dvb_attach(ix2505v_attach , adap->fe_adap[0].fe, &lme_tuner, - &adap->dev->i2c_adap)) - ret = st->tuner_config; - break; - case TUNER_S0194: - if (dvb_attach(dvb_pll_attach , adap->fe_adap[0].fe, 0xc0, - &adap->dev->i2c_adap, DVB_PLL_OPERA1)) - ret = st->tuner_config; - break; - case TUNER_RS2000: - ret = st->tuner_config; - break; - default: - break; - } - - if (ret) - info("TUN Found %s tuner", tun_msg[ret]); - else { - info("TUN No tuner found --- resetting device"); - lme_coldreset(adap->dev->udev); - return -ENODEV; - } - - /* Start the Interrupt*/ - ret = lme2510_int_read(adap); - if (ret < 0) { - info("INT Unable to start Interrupt Service"); - return -ENODEV; - } - - return ret; -} - -static int lme2510_powerup(struct dvb_usb_device *d, int onoff) -{ - struct lme2510_state *st = d->priv; - static u8 lnb_on[] = LNB_ON; - static u8 lnb_off[] = LNB_OFF; - static u8 rbuf[1]; - int ret = 0, len = 3, rlen = 1; - - mutex_lock(&d->i2c_mutex); - - if (onoff) - ret = lme2510_usb_talk(d, lnb_on, len, rbuf, rlen); - else - ret = lme2510_usb_talk(d, lnb_off, len, rbuf, rlen); - - st->i2c_talk_onoff = 1; - - mutex_unlock(&d->i2c_mutex); - - return ret; -} - -/* DVB USB Driver stuff */ -static struct dvb_usb_device_properties lme2510_properties; -static struct dvb_usb_device_properties lme2510c_properties; - -static int lme2510_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - struct usb_device *udev = interface_to_usbdev(intf); - - usb_reset_configuration(udev); - - usb_set_interface(udev, intf->cur_altsetting->desc.bInterfaceNumber, 1); - - if (udev->speed != USB_SPEED_HIGH) { - usb_reset_device(udev); - info("DEV Failed to connect in HIGH SPEED mode"); - return -ENODEV; - } - - if (lme2510_return_status(udev) == 0x44) { - lme_firmware_switch(udev, 0); - return -ENODEV; - } - - if (0 == dvb_usb_device_init(intf, &lme2510_properties, - THIS_MODULE, NULL, adapter_nr)) { - info("DEV registering device driver"); - return 0; - } - if (0 == dvb_usb_device_init(intf, &lme2510c_properties, - THIS_MODULE, NULL, adapter_nr)) { - info("DEV registering device driver"); - return 0; - } - - info("DEV lme2510 Error"); - return -ENODEV; - -} - -static struct usb_device_id lme2510_table[] = { - { USB_DEVICE(0x3344, 0x1122) }, /* LME2510 */ - { USB_DEVICE(0x3344, 0x1120) }, /* LME2510C */ - { USB_DEVICE(0x3344, 0x22f0) }, /* LME2510C RS2000 */ - {} /* Terminating entry */ -}; - -MODULE_DEVICE_TABLE(usb, lme2510_table); - -static struct dvb_usb_device_properties lme2510_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - .size_of_priv = sizeof(struct lme2510_state), - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = {{ - .caps = DVB_USB_ADAP_HAS_PID_FILTER| - DVB_USB_ADAP_NEED_PID_FILTERING| - DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, - .streaming_ctrl = lme2510_streaming_ctrl, - .pid_filter_count = 32, - .pid_filter = lme2510_pid_filter, - .pid_filter_ctrl = lme2510_pid_filter_ctrl, - .frontend_attach = dm04_lme2510_frontend_attach, - .tuner_attach = dm04_lme2510_tuner, - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_BULK, - .count = 10, - .endpoint = 0x06, - .u = { - .bulk = { - .buffersize = 4096, - - } - } - } - }}, - } - }, - .rc.core = { - .protocol = RC_TYPE_NEC, - .module_name = "LME2510 Remote Control", - .allowed_protos = RC_TYPE_NEC, - .rc_codes = RC_MAP_LME2510, - }, - .power_ctrl = lme2510_powerup, - .identify_state = lme2510_identify_state, - .i2c_algo = &lme2510_i2c_algo, - .generic_bulk_ctrl_endpoint = 0, - .num_device_descs = 1, - .devices = { - { "DM04_LME2510_DVB-S", - { &lme2510_table[0], NULL }, - }, - - } -}; - -static struct dvb_usb_device_properties lme2510c_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - .size_of_priv = sizeof(struct lme2510_state), - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = {{ - .caps = DVB_USB_ADAP_HAS_PID_FILTER| - DVB_USB_ADAP_NEED_PID_FILTERING| - DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, - .streaming_ctrl = lme2510_streaming_ctrl, - .pid_filter_count = 32, - .pid_filter = lme2510_pid_filter, - .pid_filter_ctrl = lme2510_pid_filter_ctrl, - .frontend_attach = dm04_lme2510_frontend_attach, - .tuner_attach = dm04_lme2510_tuner, - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_BULK, - .count = 10, - .endpoint = 0x8, - .u = { - .bulk = { - .buffersize = 4096, - - } - } - } - }}, - } - }, - .rc.core = { - .protocol = RC_TYPE_NEC, - .module_name = "LME2510 Remote Control", - .allowed_protos = RC_TYPE_NEC, - .rc_codes = RC_MAP_LME2510, - }, - .power_ctrl = lme2510_powerup, - .identify_state = lme2510_identify_state, - .i2c_algo = &lme2510_i2c_algo, - .generic_bulk_ctrl_endpoint = 0, - .num_device_descs = 2, - .devices = { - { "DM04_LME2510C_DVB-S", - { &lme2510_table[1], NULL }, - }, - { "DM04_LME2510C_DVB-S RS2000", - { &lme2510_table[2], NULL }, - }, - } -}; - -static void *lme2510_exit_int(struct dvb_usb_device *d) -{ - struct lme2510_state *st = d->priv; - struct dvb_usb_adapter *adap = &d->adapter[0]; - void *buffer = NULL; - - if (adap != NULL) { - lme2510_kill_urb(&adap->fe_adap[0].stream); - adap->feedcount = 0; - } - - if (st->usb_buffer != NULL) { - st->i2c_talk_onoff = 1; - st->signal_lock = 0; - st->signal_level = 0; - st->signal_sn = 0; - buffer = st->usb_buffer; - } - - if (st->lme_urb != NULL) { - usb_kill_urb(st->lme_urb); - usb_free_coherent(d->udev, 128, st->buffer, - st->lme_urb->transfer_dma); - info("Interrupt Service Stopped"); - } - - return buffer; -} - -static void lme2510_exit(struct usb_interface *intf) -{ - struct dvb_usb_device *d = usb_get_intfdata(intf); - void *usb_buffer; - - if (d != NULL) { - usb_buffer = lme2510_exit_int(d); - dvb_usb_device_exit(intf); - if (usb_buffer != NULL) - kfree(usb_buffer); - } -} - -static struct usb_driver lme2510_driver = { - .name = "LME2510C_DVB-S", - .probe = lme2510_probe, - .disconnect = lme2510_exit, - .id_table = lme2510_table, -}; - -module_usb_driver(lme2510_driver); - -MODULE_AUTHOR("Malcolm Priestley "); -MODULE_DESCRIPTION("LME2510(C) DVB-S USB2.0"); -MODULE_VERSION("1.99"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/mxl111sf-tuner.c b/drivers/media/dvb/dvb-usb/mxl111sf-tuner.c deleted file mode 100644 index 74da5bb1ce99..000000000000 --- a/drivers/media/dvb/dvb-usb/mxl111sf-tuner.c +++ /dev/null @@ -1,525 +0,0 @@ -/* - * mxl111sf-tuner.c - driver for the MaxLinear MXL111SF CMOS tuner - * - * Copyright (C) 2010 Michael Krufky - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include "mxl111sf-tuner.h" -#include "mxl111sf-phy.h" -#include "mxl111sf-reg.h" - -/* debug */ -static int mxl111sf_tuner_debug; -module_param_named(debug, mxl111sf_tuner_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); - -#define mxl_dbg(fmt, arg...) \ - if (mxl111sf_tuner_debug) \ - mxl_printk(KERN_DEBUG, fmt, ##arg) - -/* ------------------------------------------------------------------------ */ - -struct mxl111sf_tuner_state { - struct mxl111sf_state *mxl_state; - - struct mxl111sf_tuner_config *cfg; - - enum mxl_if_freq if_freq; - - u32 frequency; - u32 bandwidth; -}; - -static int mxl111sf_tuner_read_reg(struct mxl111sf_tuner_state *state, - u8 addr, u8 *data) -{ - return (state->cfg->read_reg) ? - state->cfg->read_reg(state->mxl_state, addr, data) : - -EINVAL; -} - -static int mxl111sf_tuner_write_reg(struct mxl111sf_tuner_state *state, - u8 addr, u8 data) -{ - return (state->cfg->write_reg) ? - state->cfg->write_reg(state->mxl_state, addr, data) : - -EINVAL; -} - -static int mxl111sf_tuner_program_regs(struct mxl111sf_tuner_state *state, - struct mxl111sf_reg_ctrl_info *ctrl_reg_info) -{ - return (state->cfg->program_regs) ? - state->cfg->program_regs(state->mxl_state, ctrl_reg_info) : - -EINVAL; -} - -static int mxl1x1sf_tuner_top_master_ctrl(struct mxl111sf_tuner_state *state, - int onoff) -{ - return (state->cfg->top_master_ctrl) ? - state->cfg->top_master_ctrl(state->mxl_state, onoff) : - -EINVAL; -} - -/* ------------------------------------------------------------------------ */ - -static struct mxl111sf_reg_ctrl_info mxl_phy_tune_rf[] = { - {0x1d, 0x7f, 0x00}, /* channel bandwidth section 1/2/3, - DIG_MODEINDEX, _A, _CSF, */ - {0x1e, 0xff, 0x00}, /* channel frequency (lo and fractional) */ - {0x1f, 0xff, 0x00}, /* channel frequency (hi for integer portion) */ - {0, 0, 0} -}; - -/* ------------------------------------------------------------------------ */ - -static struct mxl111sf_reg_ctrl_info *mxl111sf_calc_phy_tune_regs(u32 freq, - u8 bw) -{ - u8 filt_bw; - - /* set channel bandwidth */ - switch (bw) { - case 0: /* ATSC */ - filt_bw = 25; - break; - case 1: /* QAM */ - filt_bw = 69; - break; - case 6: - filt_bw = 21; - break; - case 7: - filt_bw = 42; - break; - case 8: - filt_bw = 63; - break; - default: - err("%s: invalid bandwidth setting!", __func__); - return NULL; - } - - /* calculate RF channel */ - freq /= 1000000; - - freq *= 64; -#if 0 - /* do round */ - freq += 0.5; -#endif - /* set bandwidth */ - mxl_phy_tune_rf[0].data = filt_bw; - - /* set RF */ - mxl_phy_tune_rf[1].data = (freq & 0xff); - mxl_phy_tune_rf[2].data = (freq >> 8) & 0xff; - - /* start tune */ - return mxl_phy_tune_rf; -} - -static int mxl1x1sf_tuner_set_if_output_freq(struct mxl111sf_tuner_state *state) -{ - int ret; - u8 ctrl; -#if 0 - u16 iffcw; - u32 if_freq; -#endif - mxl_dbg("(IF polarity = %d, IF freq = 0x%02x)", - state->cfg->invert_spectrum, state->cfg->if_freq); - - /* set IF polarity */ - ctrl = state->cfg->invert_spectrum; - - ctrl |= state->cfg->if_freq; - - ret = mxl111sf_tuner_write_reg(state, V6_TUNER_IF_SEL_REG, ctrl); - if (mxl_fail(ret)) - goto fail; - -#if 0 - if_freq /= 1000000; - - /* do round */ - if_freq += 0.5; - - if (MXL_IF_LO == state->cfg->if_freq) { - ctrl = 0x08; - iffcw = (u16)(if_freq / (108 * 4096)); - } else if (MXL_IF_HI == state->cfg->if_freq) { - ctrl = 0x08; - iffcw = (u16)(if_freq / (216 * 4096)); - } else { - ctrl = 0; - iffcw = 0; - } - - ctrl |= (iffcw >> 8); -#endif - ret = mxl111sf_tuner_read_reg(state, V6_TUNER_IF_FCW_BYP_REG, &ctrl); - if (mxl_fail(ret)) - goto fail; - - ctrl &= 0xf0; - ctrl |= 0x90; - - ret = mxl111sf_tuner_write_reg(state, V6_TUNER_IF_FCW_BYP_REG, ctrl); - if (mxl_fail(ret)) - goto fail; - -#if 0 - ctrl = iffcw & 0x00ff; -#endif - ret = mxl111sf_tuner_write_reg(state, V6_TUNER_IF_FCW_REG, ctrl); - if (mxl_fail(ret)) - goto fail; - - state->if_freq = state->cfg->if_freq; -fail: - return ret; -} - -static int mxl1x1sf_tune_rf(struct dvb_frontend *fe, u32 freq, u8 bw) -{ - struct mxl111sf_tuner_state *state = fe->tuner_priv; - static struct mxl111sf_reg_ctrl_info *reg_ctrl_array; - int ret; - u8 mxl_mode; - - mxl_dbg("(freq = %d, bw = 0x%x)", freq, bw); - - /* stop tune */ - ret = mxl111sf_tuner_write_reg(state, START_TUNE_REG, 0); - if (mxl_fail(ret)) - goto fail; - - /* check device mode */ - ret = mxl111sf_tuner_read_reg(state, MXL_MODE_REG, &mxl_mode); - if (mxl_fail(ret)) - goto fail; - - /* Fill out registers for channel tune */ - reg_ctrl_array = mxl111sf_calc_phy_tune_regs(freq, bw); - if (!reg_ctrl_array) - return -EINVAL; - - ret = mxl111sf_tuner_program_regs(state, reg_ctrl_array); - if (mxl_fail(ret)) - goto fail; - - if ((mxl_mode & MXL_DEV_MODE_MASK) == MXL_TUNER_MODE) { - /* IF tuner mode only */ - mxl1x1sf_tuner_top_master_ctrl(state, 0); - mxl1x1sf_tuner_top_master_ctrl(state, 1); - mxl1x1sf_tuner_set_if_output_freq(state); - } - - ret = mxl111sf_tuner_write_reg(state, START_TUNE_REG, 1); - if (mxl_fail(ret)) - goto fail; - - if (state->cfg->ant_hunt) - state->cfg->ant_hunt(fe); -fail: - return ret; -} - -static int mxl1x1sf_tuner_get_lock_status(struct mxl111sf_tuner_state *state, - int *rf_synth_lock, - int *ref_synth_lock) -{ - int ret; - u8 data; - - *rf_synth_lock = 0; - *ref_synth_lock = 0; - - ret = mxl111sf_tuner_read_reg(state, V6_RF_LOCK_STATUS_REG, &data); - if (mxl_fail(ret)) - goto fail; - - *ref_synth_lock = ((data & 0x03) == 0x03) ? 1 : 0; - *rf_synth_lock = ((data & 0x0c) == 0x0c) ? 1 : 0; -fail: - return ret; -} - -#if 0 -static int mxl1x1sf_tuner_loop_thru_ctrl(struct mxl111sf_tuner_state *state, - int onoff) -{ - return mxl111sf_tuner_write_reg(state, V6_TUNER_LOOP_THRU_CTRL_REG, - onoff ? 1 : 0); -} -#endif - -/* ------------------------------------------------------------------------ */ - -static int mxl111sf_tuner_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u32 delsys = c->delivery_system; - struct mxl111sf_tuner_state *state = fe->tuner_priv; - int ret; - u8 bw; - - mxl_dbg("()"); - - switch (delsys) { - case SYS_ATSC: - case SYS_ATSCMH: - bw = 0; /* ATSC */ - break; - case SYS_DVBC_ANNEX_B: - bw = 1; /* US CABLE */ - break; - case SYS_DVBT: - switch (c->bandwidth_hz) { - case 6000000: - bw = 6; - break; - case 7000000: - bw = 7; - break; - case 8000000: - bw = 8; - break; - default: - err("%s: bandwidth not set!", __func__); - return -EINVAL; - } - break; - default: - err("%s: modulation type not supported!", __func__); - return -EINVAL; - } - ret = mxl1x1sf_tune_rf(fe, c->frequency, bw); - if (mxl_fail(ret)) - goto fail; - - state->frequency = c->frequency; - state->bandwidth = c->bandwidth_hz; -fail: - return ret; -} - -/* ------------------------------------------------------------------------ */ - -#if 0 -static int mxl111sf_tuner_init(struct dvb_frontend *fe) -{ - struct mxl111sf_tuner_state *state = fe->tuner_priv; - int ret; - - /* wake from standby handled by usb driver */ - - return ret; -} - -static int mxl111sf_tuner_sleep(struct dvb_frontend *fe) -{ - struct mxl111sf_tuner_state *state = fe->tuner_priv; - int ret; - - /* enter standby mode handled by usb driver */ - - return ret; -} -#endif - -/* ------------------------------------------------------------------------ */ - -static int mxl111sf_tuner_get_status(struct dvb_frontend *fe, u32 *status) -{ - struct mxl111sf_tuner_state *state = fe->tuner_priv; - int rf_locked, ref_locked, ret; - - *status = 0; - - ret = mxl1x1sf_tuner_get_lock_status(state, &rf_locked, &ref_locked); - if (mxl_fail(ret)) - goto fail; - mxl_info("%s%s", rf_locked ? "rf locked " : "", - ref_locked ? "ref locked" : ""); - - if ((rf_locked) || (ref_locked)) - *status |= TUNER_STATUS_LOCKED; -fail: - return ret; -} - -static int mxl111sf_get_rf_strength(struct dvb_frontend *fe, u16 *strength) -{ - struct mxl111sf_tuner_state *state = fe->tuner_priv; - u8 val1, val2; - int ret; - - *strength = 0; - - ret = mxl111sf_tuner_write_reg(state, 0x00, 0x02); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_tuner_read_reg(state, V6_DIG_RF_PWR_LSB_REG, &val1); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_tuner_read_reg(state, V6_DIG_RF_PWR_MSB_REG, &val2); - if (mxl_fail(ret)) - goto fail; - - *strength = val1 | ((val2 & 0x07) << 8); -fail: - ret = mxl111sf_tuner_write_reg(state, 0x00, 0x00); - mxl_fail(ret); - - return ret; -} - -/* ------------------------------------------------------------------------ */ - -static int mxl111sf_tuner_get_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct mxl111sf_tuner_state *state = fe->tuner_priv; - *frequency = state->frequency; - return 0; -} - -static int mxl111sf_tuner_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) -{ - struct mxl111sf_tuner_state *state = fe->tuner_priv; - *bandwidth = state->bandwidth; - return 0; -} - -static int mxl111sf_tuner_get_if_frequency(struct dvb_frontend *fe, - u32 *frequency) -{ - struct mxl111sf_tuner_state *state = fe->tuner_priv; - - *frequency = 0; - - switch (state->if_freq) { - case MXL_IF_4_0: /* 4.0 MHz */ - *frequency = 4000000; - break; - case MXL_IF_4_5: /* 4.5 MHz */ - *frequency = 4500000; - break; - case MXL_IF_4_57: /* 4.57 MHz */ - *frequency = 4570000; - break; - case MXL_IF_5_0: /* 5.0 MHz */ - *frequency = 5000000; - break; - case MXL_IF_5_38: /* 5.38 MHz */ - *frequency = 5380000; - break; - case MXL_IF_6_0: /* 6.0 MHz */ - *frequency = 6000000; - break; - case MXL_IF_6_28: /* 6.28 MHz */ - *frequency = 6280000; - break; - case MXL_IF_7_2: /* 7.2 MHz */ - *frequency = 7200000; - break; - case MXL_IF_35_25: /* 35.25 MHz */ - *frequency = 35250000; - break; - case MXL_IF_36: /* 36 MHz */ - *frequency = 36000000; - break; - case MXL_IF_36_15: /* 36.15 MHz */ - *frequency = 36150000; - break; - case MXL_IF_44: /* 44 MHz */ - *frequency = 44000000; - break; - } - return 0; -} - -static int mxl111sf_tuner_release(struct dvb_frontend *fe) -{ - struct mxl111sf_tuner_state *state = fe->tuner_priv; - mxl_dbg("()"); - kfree(state); - fe->tuner_priv = NULL; - return 0; -} - -/* ------------------------------------------------------------------------- */ - -static struct dvb_tuner_ops mxl111sf_tuner_tuner_ops = { - .info = { - .name = "MaxLinear MxL111SF", -#if 0 - .frequency_min = , - .frequency_max = , - .frequency_step = , -#endif - }, -#if 0 - .init = mxl111sf_tuner_init, - .sleep = mxl111sf_tuner_sleep, -#endif - .set_params = mxl111sf_tuner_set_params, - .get_status = mxl111sf_tuner_get_status, - .get_rf_strength = mxl111sf_get_rf_strength, - .get_frequency = mxl111sf_tuner_get_frequency, - .get_bandwidth = mxl111sf_tuner_get_bandwidth, - .get_if_frequency = mxl111sf_tuner_get_if_frequency, - .release = mxl111sf_tuner_release, -}; - -struct dvb_frontend *mxl111sf_tuner_attach(struct dvb_frontend *fe, - struct mxl111sf_state *mxl_state, - struct mxl111sf_tuner_config *cfg) -{ - struct mxl111sf_tuner_state *state = NULL; - - mxl_dbg("()"); - - state = kzalloc(sizeof(struct mxl111sf_tuner_state), GFP_KERNEL); - if (state == NULL) - return NULL; - - state->mxl_state = mxl_state; - state->cfg = cfg; - - memcpy(&fe->ops.tuner_ops, &mxl111sf_tuner_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - fe->tuner_priv = state; - return fe; -} -EXPORT_SYMBOL_GPL(mxl111sf_tuner_attach); - -MODULE_DESCRIPTION("MaxLinear MxL111SF CMOS tuner driver"); -MODULE_AUTHOR("Michael Krufky "); -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.1"); - -/* - * Overrides for Emacs so that we follow Linus's tabbing style. - * --------------------------------------------------------------------------- - * Local variables: - * c-basic-offset: 8 - * End: - */ diff --git a/drivers/media/dvb/dvb-usb/mxl111sf.c b/drivers/media/dvb/dvb-usb/mxl111sf.c deleted file mode 100644 index cd842798f5af..000000000000 --- a/drivers/media/dvb/dvb-usb/mxl111sf.c +++ /dev/null @@ -1,1835 +0,0 @@ -/* - * Copyright (C) 2010 Michael Krufky (mkrufky@kernellabs.com) - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, version 2. - * - * see Documentation/dvb/README.dvb-usb for more information - */ - -#include -#include - -#include "mxl111sf.h" -#include "mxl111sf-reg.h" -#include "mxl111sf-phy.h" -#include "mxl111sf-i2c.h" -#include "mxl111sf-gpio.h" - -#include "mxl111sf-demod.h" -#include "mxl111sf-tuner.h" - -#include "lgdt3305.h" -#include "lg2160.h" - -int dvb_usb_mxl111sf_debug; -module_param_named(debug, dvb_usb_mxl111sf_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level " - "(1=info, 2=xfer, 4=i2c, 8=reg, 16=adv (or-able))."); - -int dvb_usb_mxl111sf_isoc; -module_param_named(isoc, dvb_usb_mxl111sf_isoc, int, 0644); -MODULE_PARM_DESC(isoc, "enable usb isoc xfer (0=bulk, 1=isoc)."); - -int dvb_usb_mxl111sf_spi; -module_param_named(spi, dvb_usb_mxl111sf_spi, int, 0644); -MODULE_PARM_DESC(spi, "use spi rather than tp for data xfer (0=tp, 1=spi)."); - -#define ANT_PATH_AUTO 0 -#define ANT_PATH_EXTERNAL 1 -#define ANT_PATH_INTERNAL 2 - -int dvb_usb_mxl111sf_rfswitch = -#if 0 - ANT_PATH_AUTO; -#else - ANT_PATH_EXTERNAL; -#endif - -module_param_named(rfswitch, dvb_usb_mxl111sf_rfswitch, int, 0644); -MODULE_PARM_DESC(rfswitch, "force rf switch position (0=auto, 1=ext, 2=int)."); - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -#define deb_info(args...) dprintk(dvb_usb_mxl111sf_debug, 0x13, args) -#define deb_reg(args...) dprintk(dvb_usb_mxl111sf_debug, 0x08, args) -#define deb_adv(args...) dprintk(dvb_usb_mxl111sf_debug, MXL_ADV_DBG, args) - -int mxl111sf_ctrl_msg(struct dvb_usb_device *d, - u8 cmd, u8 *wbuf, int wlen, u8 *rbuf, int rlen) -{ - int wo = (rbuf == NULL || rlen == 0); /* write-only */ - int ret; - u8 sndbuf[1+wlen]; - - deb_adv("%s(wlen = %d, rlen = %d)\n", __func__, wlen, rlen); - - memset(sndbuf, 0, 1+wlen); - - sndbuf[0] = cmd; - memcpy(&sndbuf[1], wbuf, wlen); - - ret = (wo) ? dvb_usb_generic_write(d, sndbuf, 1+wlen) : - dvb_usb_generic_rw(d, sndbuf, 1+wlen, rbuf, rlen, 0); - mxl_fail(ret); - - return ret; -} - -/* ------------------------------------------------------------------------ */ - -#define MXL_CMD_REG_READ 0xaa -#define MXL_CMD_REG_WRITE 0x55 - -int mxl111sf_read_reg(struct mxl111sf_state *state, u8 addr, u8 *data) -{ - u8 buf[2]; - int ret; - - ret = mxl111sf_ctrl_msg(state->d, MXL_CMD_REG_READ, &addr, 1, buf, 2); - if (mxl_fail(ret)) { - mxl_debug("error reading reg: 0x%02x", addr); - goto fail; - } - - if (buf[0] == addr) - *data = buf[1]; - else { - err("invalid response reading reg: 0x%02x != 0x%02x, 0x%02x", - addr, buf[0], buf[1]); - ret = -EINVAL; - } - - deb_reg("R: (0x%02x, 0x%02x)\n", addr, *data); -fail: - return ret; -} - -int mxl111sf_write_reg(struct mxl111sf_state *state, u8 addr, u8 data) -{ - u8 buf[] = { addr, data }; - int ret; - - deb_reg("W: (0x%02x, 0x%02x)\n", addr, data); - - ret = mxl111sf_ctrl_msg(state->d, MXL_CMD_REG_WRITE, buf, 2, NULL, 0); - if (mxl_fail(ret)) - err("error writing reg: 0x%02x, val: 0x%02x", addr, data); - return ret; -} - -/* ------------------------------------------------------------------------ */ - -int mxl111sf_write_reg_mask(struct mxl111sf_state *state, - u8 addr, u8 mask, u8 data) -{ - int ret; - u8 val; - - if (mask != 0xff) { - ret = mxl111sf_read_reg(state, addr, &val); -#if 1 - /* dont know why this usually errors out on the first try */ - if (mxl_fail(ret)) - err("error writing addr: 0x%02x, mask: 0x%02x, " - "data: 0x%02x, retrying...", addr, mask, data); - - ret = mxl111sf_read_reg(state, addr, &val); -#endif - if (mxl_fail(ret)) - goto fail; - } - val &= ~mask; - val |= data; - - ret = mxl111sf_write_reg(state, addr, val); - mxl_fail(ret); -fail: - return ret; -} - -/* ------------------------------------------------------------------------ */ - -int mxl111sf_ctrl_program_regs(struct mxl111sf_state *state, - struct mxl111sf_reg_ctrl_info *ctrl_reg_info) -{ - int i, ret = 0; - - for (i = 0; ctrl_reg_info[i].addr | - ctrl_reg_info[i].mask | - ctrl_reg_info[i].data; i++) { - - ret = mxl111sf_write_reg_mask(state, - ctrl_reg_info[i].addr, - ctrl_reg_info[i].mask, - ctrl_reg_info[i].data); - if (mxl_fail(ret)) { - err("failed on reg #%d (0x%02x)", i, - ctrl_reg_info[i].addr); - break; - } - } - return ret; -} - -/* ------------------------------------------------------------------------ */ - -static int mxl1x1sf_get_chip_info(struct mxl111sf_state *state) -{ - int ret; - u8 id, ver; - char *mxl_chip, *mxl_rev; - - if ((state->chip_id) && (state->chip_ver)) - return 0; - - ret = mxl111sf_read_reg(state, CHIP_ID_REG, &id); - if (mxl_fail(ret)) - goto fail; - state->chip_id = id; - - ret = mxl111sf_read_reg(state, TOP_CHIP_REV_ID_REG, &ver); - if (mxl_fail(ret)) - goto fail; - state->chip_ver = ver; - - switch (id) { - case 0x61: - mxl_chip = "MxL101SF"; - break; - case 0x63: - mxl_chip = "MxL111SF"; - break; - default: - mxl_chip = "UNKNOWN MxL1X1"; - break; - } - switch (ver) { - case 0x36: - state->chip_rev = MXL111SF_V6; - mxl_rev = "v6"; - break; - case 0x08: - state->chip_rev = MXL111SF_V8_100; - mxl_rev = "v8_100"; - break; - case 0x18: - state->chip_rev = MXL111SF_V8_200; - mxl_rev = "v8_200"; - break; - default: - state->chip_rev = 0; - mxl_rev = "UNKNOWN REVISION"; - break; - } - info("%s detected, %s (0x%x)", mxl_chip, mxl_rev, ver); -fail: - return ret; -} - -#define get_chip_info(state) \ -({ \ - int ___ret; \ - ___ret = mxl1x1sf_get_chip_info(state); \ - if (mxl_fail(___ret)) { \ - mxl_debug("failed to get chip info" \ - " on first probe attempt"); \ - ___ret = mxl1x1sf_get_chip_info(state); \ - if (mxl_fail(___ret)) \ - err("failed to get chip info during probe"); \ - else \ - mxl_debug("probe needed a retry " \ - "in order to succeed."); \ - } \ - ___ret; \ -}) - -/* ------------------------------------------------------------------------ */ - -static int mxl111sf_power_ctrl(struct dvb_usb_device *d, int onoff) -{ - /* power control depends on which adapter is being woken: - * save this for init, instead, via mxl111sf_adap_fe_init */ - return 0; -} - -static int mxl111sf_adap_fe_init(struct dvb_frontend *fe) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - struct mxl111sf_adap_state *adap_state = adap->fe_adap[fe->id].priv; - - int err; - - /* exit if we didnt initialize the driver yet */ - if (!state->chip_id) { - mxl_debug("driver not yet initialized, exit."); - goto fail; - } - - deb_info("%s()\n", __func__); - - mutex_lock(&state->fe_lock); - - state->alt_mode = adap_state->alt_mode; - - if (usb_set_interface(adap->dev->udev, 0, state->alt_mode) < 0) - err("set interface failed"); - - err = mxl1x1sf_soft_reset(state); - mxl_fail(err); - err = mxl111sf_init_tuner_demod(state); - mxl_fail(err); - err = mxl1x1sf_set_device_mode(state, adap_state->device_mode); - - mxl_fail(err); - mxl111sf_enable_usb_output(state); - mxl_fail(err); - mxl1x1sf_top_master_ctrl(state, 1); - mxl_fail(err); - - if ((MXL111SF_GPIO_MOD_DVBT != adap_state->gpio_mode) && - (state->chip_rev > MXL111SF_V6)) { - mxl111sf_config_pin_mux_modes(state, - PIN_MUX_TS_SPI_IN_MODE_1); - mxl_fail(err); - } - err = mxl111sf_init_port_expander(state); - if (!mxl_fail(err)) { - state->gpio_mode = adap_state->gpio_mode; - err = mxl111sf_gpio_mode_switch(state, state->gpio_mode); - mxl_fail(err); -#if 0 - err = fe->ops.init(fe); -#endif - msleep(100); /* add short delay after enabling - * the demod before touching it */ - } - - return (adap_state->fe_init) ? adap_state->fe_init(fe) : 0; -fail: - return -ENODEV; -} - -static int mxl111sf_adap_fe_sleep(struct dvb_frontend *fe) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - struct mxl111sf_adap_state *adap_state = adap->fe_adap[fe->id].priv; - int err; - - /* exit if we didnt initialize the driver yet */ - if (!state->chip_id) { - mxl_debug("driver not yet initialized, exit."); - goto fail; - } - - deb_info("%s()\n", __func__); - - err = (adap_state->fe_sleep) ? adap_state->fe_sleep(fe) : 0; - - mutex_unlock(&state->fe_lock); - - return err; -fail: - return -ENODEV; -} - - -static int mxl111sf_ep6_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - struct mxl111sf_adap_state *adap_state = adap->fe_adap[adap->active_fe].priv; - int ret = 0; - - deb_info("%s(%d)\n", __func__, onoff); - - if (onoff) { - ret = mxl111sf_enable_usb_output(state); - mxl_fail(ret); - ret = mxl111sf_config_mpeg_in(state, 1, 1, - adap_state->ep6_clockphase, - 0, 0); - mxl_fail(ret); -#if 0 - } else { - ret = mxl111sf_disable_656_port(state); - mxl_fail(ret); -#endif - } - - return ret; -} - -static int mxl111sf_ep5_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - int ret = 0; - - deb_info("%s(%d)\n", __func__, onoff); - - if (onoff) { - ret = mxl111sf_enable_usb_output(state); - mxl_fail(ret); - - ret = mxl111sf_init_i2s_port(state, 200); - mxl_fail(ret); - ret = mxl111sf_config_i2s(state, 0, 15); - mxl_fail(ret); - } else { - ret = mxl111sf_disable_i2s_port(state); - mxl_fail(ret); - } - if (state->chip_rev > MXL111SF_V6) - ret = mxl111sf_config_spi(state, onoff); - mxl_fail(ret); - - return ret; -} - -static int mxl111sf_ep4_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) -{ - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - int ret = 0; - - deb_info("%s(%d)\n", __func__, onoff); - - if (onoff) { - ret = mxl111sf_enable_usb_output(state); - mxl_fail(ret); - } - - return ret; -} - -/* ------------------------------------------------------------------------ */ - -static struct lgdt3305_config hauppauge_lgdt3305_config = { - .i2c_addr = 0xb2 >> 1, - .mpeg_mode = LGDT3305_MPEG_SERIAL, - .tpclk_edge = LGDT3305_TPCLK_RISING_EDGE, - .tpvalid_polarity = LGDT3305_TP_VALID_HIGH, - .deny_i2c_rptr = 1, - .spectral_inversion = 0, - .qam_if_khz = 6000, - .vsb_if_khz = 6000, -}; - -static int mxl111sf_lgdt3305_frontend_attach(struct dvb_usb_adapter *adap) -{ - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - int fe_id = adap->num_frontends_initialized; - struct mxl111sf_adap_state *adap_state = adap->fe_adap[fe_id].priv; - int ret; - - deb_adv("%s()\n", __func__); - - /* save a pointer to the dvb_usb_device in device state */ - state->d = d; - adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1; - state->alt_mode = adap_state->alt_mode; - - if (usb_set_interface(adap->dev->udev, 0, state->alt_mode) < 0) - err("set interface failed"); - - state->gpio_mode = MXL111SF_GPIO_MOD_ATSC; - adap_state->gpio_mode = state->gpio_mode; - adap_state->device_mode = MXL_TUNER_MODE; - adap_state->ep6_clockphase = 1; - - ret = mxl1x1sf_soft_reset(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_init_tuner_demod(state); - if (mxl_fail(ret)) - goto fail; - - ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); - if (mxl_fail(ret)) - goto fail; - - ret = mxl111sf_enable_usb_output(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl1x1sf_top_master_ctrl(state, 1); - if (mxl_fail(ret)) - goto fail; - - ret = mxl111sf_init_port_expander(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode); - if (mxl_fail(ret)) - goto fail; - - adap->fe_adap[fe_id].fe = dvb_attach(lgdt3305_attach, - &hauppauge_lgdt3305_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[fe_id].fe) { - adap_state->fe_init = adap->fe_adap[fe_id].fe->ops.init; - adap->fe_adap[fe_id].fe->ops.init = mxl111sf_adap_fe_init; - adap_state->fe_sleep = adap->fe_adap[fe_id].fe->ops.sleep; - adap->fe_adap[fe_id].fe->ops.sleep = mxl111sf_adap_fe_sleep; - return 0; - } - ret = -EIO; -fail: - return ret; -} - -static struct lg2160_config hauppauge_lg2160_config = { - .lg_chip = LG2160, - .i2c_addr = 0x1c >> 1, - .deny_i2c_rptr = 1, - .spectral_inversion = 0, - .if_khz = 6000, -}; - -static int mxl111sf_lg2160_frontend_attach(struct dvb_usb_adapter *adap) -{ - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - int fe_id = adap->num_frontends_initialized; - struct mxl111sf_adap_state *adap_state = adap->fe_adap[fe_id].priv; - int ret; - - deb_adv("%s()\n", __func__); - - /* save a pointer to the dvb_usb_device in device state */ - state->d = d; - adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1; - state->alt_mode = adap_state->alt_mode; - - if (usb_set_interface(adap->dev->udev, 0, state->alt_mode) < 0) - err("set interface failed"); - - state->gpio_mode = MXL111SF_GPIO_MOD_MH; - adap_state->gpio_mode = state->gpio_mode; - adap_state->device_mode = MXL_TUNER_MODE; - adap_state->ep6_clockphase = 1; - - ret = mxl1x1sf_soft_reset(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_init_tuner_demod(state); - if (mxl_fail(ret)) - goto fail; - - ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); - if (mxl_fail(ret)) - goto fail; - - ret = mxl111sf_enable_usb_output(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl1x1sf_top_master_ctrl(state, 1); - if (mxl_fail(ret)) - goto fail; - - ret = mxl111sf_init_port_expander(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode); - if (mxl_fail(ret)) - goto fail; - - ret = get_chip_info(state); - if (mxl_fail(ret)) - goto fail; - - adap->fe_adap[fe_id].fe = dvb_attach(lg2160_attach, - &hauppauge_lg2160_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[fe_id].fe) { - adap_state->fe_init = adap->fe_adap[fe_id].fe->ops.init; - adap->fe_adap[fe_id].fe->ops.init = mxl111sf_adap_fe_init; - adap_state->fe_sleep = adap->fe_adap[fe_id].fe->ops.sleep; - adap->fe_adap[fe_id].fe->ops.sleep = mxl111sf_adap_fe_sleep; - return 0; - } - ret = -EIO; -fail: - return ret; -} - -static struct lg2160_config hauppauge_lg2161_1019_config = { - .lg_chip = LG2161_1019, - .i2c_addr = 0x1c >> 1, - .deny_i2c_rptr = 1, - .spectral_inversion = 0, - .if_khz = 6000, - .output_if = 2, /* LG2161_OIF_SPI_MAS */ -}; - -static struct lg2160_config hauppauge_lg2161_1040_config = { - .lg_chip = LG2161_1040, - .i2c_addr = 0x1c >> 1, - .deny_i2c_rptr = 1, - .spectral_inversion = 0, - .if_khz = 6000, - .output_if = 4, /* LG2161_OIF_SPI_MAS */ -}; - -static int mxl111sf_lg2161_frontend_attach(struct dvb_usb_adapter *adap) -{ - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - int fe_id = adap->num_frontends_initialized; - struct mxl111sf_adap_state *adap_state = adap->fe_adap[fe_id].priv; - int ret; - - deb_adv("%s()\n", __func__); - - /* save a pointer to the dvb_usb_device in device state */ - state->d = d; - adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1; - state->alt_mode = adap_state->alt_mode; - - if (usb_set_interface(adap->dev->udev, 0, state->alt_mode) < 0) - err("set interface failed"); - - state->gpio_mode = MXL111SF_GPIO_MOD_MH; - adap_state->gpio_mode = state->gpio_mode; - adap_state->device_mode = MXL_TUNER_MODE; - adap_state->ep6_clockphase = 1; - - ret = mxl1x1sf_soft_reset(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_init_tuner_demod(state); - if (mxl_fail(ret)) - goto fail; - - ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); - if (mxl_fail(ret)) - goto fail; - - ret = mxl111sf_enable_usb_output(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl1x1sf_top_master_ctrl(state, 1); - if (mxl_fail(ret)) - goto fail; - - ret = mxl111sf_init_port_expander(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode); - if (mxl_fail(ret)) - goto fail; - - ret = get_chip_info(state); - if (mxl_fail(ret)) - goto fail; - - adap->fe_adap[fe_id].fe = dvb_attach(lg2160_attach, - (MXL111SF_V8_200 == state->chip_rev) ? - &hauppauge_lg2161_1040_config : - &hauppauge_lg2161_1019_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[fe_id].fe) { - adap_state->fe_init = adap->fe_adap[fe_id].fe->ops.init; - adap->fe_adap[fe_id].fe->ops.init = mxl111sf_adap_fe_init; - adap_state->fe_sleep = adap->fe_adap[fe_id].fe->ops.sleep; - adap->fe_adap[fe_id].fe->ops.sleep = mxl111sf_adap_fe_sleep; - return 0; - } - ret = -EIO; -fail: - return ret; -} - -static struct lg2160_config hauppauge_lg2161_1019_ep6_config = { - .lg_chip = LG2161_1019, - .i2c_addr = 0x1c >> 1, - .deny_i2c_rptr = 1, - .spectral_inversion = 0, - .if_khz = 6000, - .output_if = 1, /* LG2161_OIF_SERIAL_TS */ -}; - -static struct lg2160_config hauppauge_lg2161_1040_ep6_config = { - .lg_chip = LG2161_1040, - .i2c_addr = 0x1c >> 1, - .deny_i2c_rptr = 1, - .spectral_inversion = 0, - .if_khz = 6000, - .output_if = 7, /* LG2161_OIF_SERIAL_TS */ -}; - -static int mxl111sf_lg2161_ep6_frontend_attach(struct dvb_usb_adapter *adap) -{ - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - int fe_id = adap->num_frontends_initialized; - struct mxl111sf_adap_state *adap_state = adap->fe_adap[fe_id].priv; - int ret; - - deb_adv("%s()\n", __func__); - - /* save a pointer to the dvb_usb_device in device state */ - state->d = d; - adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1; - state->alt_mode = adap_state->alt_mode; - - if (usb_set_interface(adap->dev->udev, 0, state->alt_mode) < 0) - err("set interface failed"); - - state->gpio_mode = MXL111SF_GPIO_MOD_MH; - adap_state->gpio_mode = state->gpio_mode; - adap_state->device_mode = MXL_TUNER_MODE; - adap_state->ep6_clockphase = 0; - - ret = mxl1x1sf_soft_reset(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_init_tuner_demod(state); - if (mxl_fail(ret)) - goto fail; - - ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); - if (mxl_fail(ret)) - goto fail; - - ret = mxl111sf_enable_usb_output(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl1x1sf_top_master_ctrl(state, 1); - if (mxl_fail(ret)) - goto fail; - - ret = mxl111sf_init_port_expander(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode); - if (mxl_fail(ret)) - goto fail; - - ret = get_chip_info(state); - if (mxl_fail(ret)) - goto fail; - - adap->fe_adap[fe_id].fe = dvb_attach(lg2160_attach, - (MXL111SF_V8_200 == state->chip_rev) ? - &hauppauge_lg2161_1040_ep6_config : - &hauppauge_lg2161_1019_ep6_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[fe_id].fe) { - adap_state->fe_init = adap->fe_adap[fe_id].fe->ops.init; - adap->fe_adap[fe_id].fe->ops.init = mxl111sf_adap_fe_init; - adap_state->fe_sleep = adap->fe_adap[fe_id].fe->ops.sleep; - adap->fe_adap[fe_id].fe->ops.sleep = mxl111sf_adap_fe_sleep; - return 0; - } - ret = -EIO; -fail: - return ret; -} - -static struct mxl111sf_demod_config mxl_demod_config = { - .read_reg = mxl111sf_read_reg, - .write_reg = mxl111sf_write_reg, - .program_regs = mxl111sf_ctrl_program_regs, -}; - -static int mxl111sf_attach_demod(struct dvb_usb_adapter *adap) -{ - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - int fe_id = adap->num_frontends_initialized; - struct mxl111sf_adap_state *adap_state = adap->fe_adap[fe_id].priv; - int ret; - - deb_adv("%s()\n", __func__); - - /* save a pointer to the dvb_usb_device in device state */ - state->d = d; - adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 1 : 2; - state->alt_mode = adap_state->alt_mode; - - if (usb_set_interface(adap->dev->udev, 0, state->alt_mode) < 0) - err("set interface failed"); - - state->gpio_mode = MXL111SF_GPIO_MOD_DVBT; - adap_state->gpio_mode = state->gpio_mode; - adap_state->device_mode = MXL_SOC_MODE; - adap_state->ep6_clockphase = 1; - - ret = mxl1x1sf_soft_reset(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl111sf_init_tuner_demod(state); - if (mxl_fail(ret)) - goto fail; - - ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode); - if (mxl_fail(ret)) - goto fail; - - ret = mxl111sf_enable_usb_output(state); - if (mxl_fail(ret)) - goto fail; - ret = mxl1x1sf_top_master_ctrl(state, 1); - if (mxl_fail(ret)) - goto fail; - - /* dont care if this fails */ - mxl111sf_init_port_expander(state); - - adap->fe_adap[fe_id].fe = dvb_attach(mxl111sf_demod_attach, state, - &mxl_demod_config); - if (adap->fe_adap[fe_id].fe) { - adap_state->fe_init = adap->fe_adap[fe_id].fe->ops.init; - adap->fe_adap[fe_id].fe->ops.init = mxl111sf_adap_fe_init; - adap_state->fe_sleep = adap->fe_adap[fe_id].fe->ops.sleep; - adap->fe_adap[fe_id].fe->ops.sleep = mxl111sf_adap_fe_sleep; - return 0; - } - ret = -EIO; -fail: - return ret; -} - -static inline int mxl111sf_set_ant_path(struct mxl111sf_state *state, - int antpath) -{ - return mxl111sf_idac_config(state, 1, 1, - (antpath == ANT_PATH_INTERNAL) ? - 0x3f : 0x00, 0); -} - -#define DbgAntHunt(x, pwr0, pwr1, pwr2, pwr3) \ - err("%s(%d) FINAL input set to %s rxPwr:%d|%d|%d|%d\n", \ - __func__, __LINE__, \ - (ANT_PATH_EXTERNAL == x) ? "EXTERNAL" : "INTERNAL", \ - pwr0, pwr1, pwr2, pwr3) - -#define ANT_HUNT_SLEEP 90 -#define ANT_EXT_TWEAK 0 - -static int mxl111sf_ant_hunt(struct dvb_frontend *fe) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - - int antctrl = dvb_usb_mxl111sf_rfswitch; - - u16 rxPwrA, rxPwr0, rxPwr1, rxPwr2; - - /* FIXME: must force EXTERNAL for QAM - done elsewhere */ - mxl111sf_set_ant_path(state, antctrl == ANT_PATH_AUTO ? - ANT_PATH_EXTERNAL : antctrl); - - if (antctrl == ANT_PATH_AUTO) { -#if 0 - msleep(ANT_HUNT_SLEEP); -#endif - fe->ops.tuner_ops.get_rf_strength(fe, &rxPwrA); - - mxl111sf_set_ant_path(state, ANT_PATH_EXTERNAL); - msleep(ANT_HUNT_SLEEP); - fe->ops.tuner_ops.get_rf_strength(fe, &rxPwr0); - - mxl111sf_set_ant_path(state, ANT_PATH_EXTERNAL); - msleep(ANT_HUNT_SLEEP); - fe->ops.tuner_ops.get_rf_strength(fe, &rxPwr1); - - mxl111sf_set_ant_path(state, ANT_PATH_INTERNAL); - msleep(ANT_HUNT_SLEEP); - fe->ops.tuner_ops.get_rf_strength(fe, &rxPwr2); - - if (rxPwr1+ANT_EXT_TWEAK >= rxPwr2) { - /* return with EXTERNAL enabled */ - mxl111sf_set_ant_path(state, ANT_PATH_EXTERNAL); - DbgAntHunt(ANT_PATH_EXTERNAL, rxPwrA, - rxPwr0, rxPwr1, rxPwr2); - } else { - /* return with INTERNAL enabled */ - DbgAntHunt(ANT_PATH_INTERNAL, rxPwrA, - rxPwr0, rxPwr1, rxPwr2); - } - } - return 0; -} - -static struct mxl111sf_tuner_config mxl_tuner_config = { - .if_freq = MXL_IF_6_0, /* applies to external IF output, only */ - .invert_spectrum = 0, - .read_reg = mxl111sf_read_reg, - .write_reg = mxl111sf_write_reg, - .program_regs = mxl111sf_ctrl_program_regs, - .top_master_ctrl = mxl1x1sf_top_master_ctrl, - .ant_hunt = mxl111sf_ant_hunt, -}; - -static int mxl111sf_attach_tuner(struct dvb_usb_adapter *adap) -{ - struct dvb_usb_device *d = adap->dev; - struct mxl111sf_state *state = d->priv; - int fe_id = adap->num_frontends_initialized; - - deb_adv("%s()\n", __func__); - - if (NULL != dvb_attach(mxl111sf_tuner_attach, - adap->fe_adap[fe_id].fe, state, - &mxl_tuner_config)) - return 0; - - return -EIO; -} - -static int mxl111sf_fe_ioctl_override(struct dvb_frontend *fe, - unsigned int cmd, void *parg, - unsigned int stage) -{ - int err = 0; - - switch (stage) { - case DVB_FE_IOCTL_PRE: - - switch (cmd) { - case FE_READ_SIGNAL_STRENGTH: - err = fe->ops.tuner_ops.get_rf_strength(fe, parg); - /* If no error occurs, prevent dvb-core from handling - * this IOCTL, otherwise return the error */ - if (0 == err) - err = 1; - break; - } - break; - - case DVB_FE_IOCTL_POST: - /* no post-ioctl handling required */ - break; - } - return err; -}; - -static u32 mxl111sf_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -struct i2c_algorithm mxl111sf_i2c_algo = { - .master_xfer = mxl111sf_i2c_xfer, - .functionality = mxl111sf_i2c_func, -#ifdef NEED_ALGO_CONTROL - .algo_control = dummy_algo_control, -#endif -}; - -static struct dvb_usb_device_properties mxl111sf_dvbt_bulk_properties; -static struct dvb_usb_device_properties mxl111sf_dvbt_isoc_properties; -static struct dvb_usb_device_properties mxl111sf_atsc_bulk_properties; -static struct dvb_usb_device_properties mxl111sf_atsc_isoc_properties; -static struct dvb_usb_device_properties mxl111sf_atsc_mh_bulk_properties; -static struct dvb_usb_device_properties mxl111sf_atsc_mh_isoc_properties; -static struct dvb_usb_device_properties mxl111sf_mh_bulk_properties; -static struct dvb_usb_device_properties mxl111sf_mh_isoc_properties; -static struct dvb_usb_device_properties mxl111sf_mercury_spi_bulk_properties; -static struct dvb_usb_device_properties mxl111sf_mercury_spi_isoc_properties; -static struct dvb_usb_device_properties mxl111sf_mercury_tp_bulk_properties; -static struct dvb_usb_device_properties mxl111sf_mercury_tp_isoc_properties; -static struct dvb_usb_device_properties mxl111sf_mercury_mh_spi_bulk_properties; -static struct dvb_usb_device_properties mxl111sf_mercury_mh_spi_isoc_properties; -static struct dvb_usb_device_properties mxl111sf_mercury_mh_tp_bulk_properties; -static struct dvb_usb_device_properties mxl111sf_mercury_mh_tp_isoc_properties; - -static int mxl111sf_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - struct dvb_usb_device *d = NULL; - - deb_adv("%s()\n", __func__); - - if (((dvb_usb_mxl111sf_isoc) && - (0 == dvb_usb_device_init(intf, - &mxl111sf_dvbt_isoc_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_atsc_isoc_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_atsc_mh_isoc_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_mh_isoc_properties, - THIS_MODULE, &d, adapter_nr) || - ((dvb_usb_mxl111sf_spi) && - (0 == dvb_usb_device_init(intf, - &mxl111sf_mercury_spi_isoc_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_mercury_mh_spi_isoc_properties, - THIS_MODULE, &d, adapter_nr))) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_mercury_tp_isoc_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_mercury_mh_tp_isoc_properties, - THIS_MODULE, &d, adapter_nr))) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_dvbt_bulk_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_atsc_bulk_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_atsc_mh_bulk_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_mh_bulk_properties, - THIS_MODULE, &d, adapter_nr) || - ((dvb_usb_mxl111sf_spi) && - (0 == dvb_usb_device_init(intf, - &mxl111sf_mercury_spi_bulk_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_mercury_mh_spi_bulk_properties, - THIS_MODULE, &d, adapter_nr))) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_mercury_tp_bulk_properties, - THIS_MODULE, &d, adapter_nr) || - 0 == dvb_usb_device_init(intf, - &mxl111sf_mercury_mh_tp_bulk_properties, - THIS_MODULE, &d, adapter_nr) || 0) { - - struct mxl111sf_state *state = d->priv; - static u8 eeprom[256]; - struct i2c_client c; - int ret; - - ret = get_chip_info(state); - if (mxl_fail(ret)) - err("failed to get chip info during probe"); - - mutex_init(&state->fe_lock); - - if (state->chip_rev > MXL111SF_V6) - mxl111sf_config_pin_mux_modes(state, - PIN_MUX_TS_SPI_IN_MODE_1); - - c.adapter = &d->i2c_adap; - c.addr = 0xa0 >> 1; - - ret = tveeprom_read(&c, eeprom, sizeof(eeprom)); - if (mxl_fail(ret)) - return 0; - tveeprom_hauppauge_analog(&c, &state->tv, - (0x84 == eeprom[0xa0]) ? - eeprom + 0xa0 : eeprom + 0x80); -#if 0 - switch (state->tv.model) { - case 117001: - case 126001: - case 138001: - break; - default: - printk(KERN_WARNING "%s: warning: " - "unknown hauppauge model #%d\n", - __func__, state->tv.model); - } -#endif - return 0; - } - err("Your device is not yet supported by this driver. " - "See kernellabs.com for more info"); - return -EINVAL; -} - -static struct usb_device_id mxl111sf_table[] = { -/* 0 */ { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc600) }, /* ATSC+ IR */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc601) }, /* ATSC */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc602) }, /* + */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc603) }, /* ATSC+ */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc604) }, /* DVBT */ -/* 5 */ { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc609) }, /* ATSC IR */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60a) }, /* + IR */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60b) }, /* ATSC+ IR */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60c) }, /* DVBT IR */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc653) }, /* ATSC+ */ -/*10 */ { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc65b) }, /* ATSC+ IR */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb700) }, /* ATSC+ sw */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb701) }, /* ATSC sw */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb702) }, /* + sw */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb703) }, /* ATSC+ sw */ -/*15 */ { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb704) }, /* DVBT sw */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb753) }, /* ATSC+ sw */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb763) }, /* ATSC+ no */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb764) }, /* DVBT no */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xd853) }, /* ATSC+ sw */ -/*20 */ { USB_DEVICE(USB_VID_HAUPPAUGE, 0xd854) }, /* DVBT sw */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xd863) }, /* ATSC+ no */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xd864) }, /* DVBT no */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8d3) }, /* ATSC+ sw */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8d4) }, /* DVBT sw */ -/*25 */ { USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8e3) }, /* ATSC+ no */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8e4) }, /* DVBT no */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8ff) }, /* ATSC+ */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc612) }, /* + */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc613) }, /* ATSC+ */ -/*30 */ { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc61a) }, /* + IR */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xc61b) }, /* ATSC+ IR */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb757) }, /* ATSC+DVBT sw */ - { USB_DEVICE(USB_VID_HAUPPAUGE, 0xb767) }, /* ATSC+DVBT no */ - {} /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(usb, mxl111sf_table); - - -#define MXL111SF_EP4_BULK_STREAMING_CONFIG \ - .size_of_priv = sizeof(struct mxl111sf_adap_state), \ - .streaming_ctrl = mxl111sf_ep4_streaming_ctrl, \ - .stream = { \ - .type = USB_BULK, \ - .count = 5, \ - .endpoint = 0x04, \ - .u = { \ - .bulk = { \ - .buffersize = 8192, \ - } \ - } \ - } - -/* FIXME: works for v6 but not v8 silicon */ -#define MXL111SF_EP4_ISOC_STREAMING_CONFIG \ - .size_of_priv = sizeof(struct mxl111sf_adap_state), \ - .streaming_ctrl = mxl111sf_ep4_streaming_ctrl, \ - .stream = { \ - .type = USB_ISOC, \ - .count = 5, \ - .endpoint = 0x04, \ - .u = { \ - .isoc = { \ - .framesperurb = 96, \ - /* FIXME: v6 SILICON: */ \ - .framesize = 564, \ - .interval = 1, \ - } \ - } \ - } - -#define MXL111SF_EP5_BULK_STREAMING_CONFIG \ - .size_of_priv = sizeof(struct mxl111sf_adap_state), \ - .streaming_ctrl = mxl111sf_ep5_streaming_ctrl, \ - .stream = { \ - .type = USB_BULK, \ - .count = 5, \ - .endpoint = 0x05, \ - .u = { \ - .bulk = { \ - .buffersize = 8192, \ - } \ - } \ - } - -#define MXL111SF_EP5_ISOC_STREAMING_CONFIG \ - .size_of_priv = sizeof(struct mxl111sf_adap_state), \ - .streaming_ctrl = mxl111sf_ep5_streaming_ctrl, \ - .stream = { \ - .type = USB_ISOC, \ - .count = 5, \ - .endpoint = 0x05, \ - .u = { \ - .isoc = { \ - .framesperurb = 96, \ - .framesize = 200, \ - .interval = 1, \ - } \ - } \ - } - -#define MXL111SF_EP6_BULK_STREAMING_CONFIG \ - .size_of_priv = sizeof(struct mxl111sf_adap_state), \ - .streaming_ctrl = mxl111sf_ep6_streaming_ctrl, \ - .stream = { \ - .type = USB_BULK, \ - .count = 5, \ - .endpoint = 0x06, \ - .u = { \ - .bulk = { \ - .buffersize = 8192, \ - } \ - } \ - } - -/* FIXME */ -#define MXL111SF_EP6_ISOC_STREAMING_CONFIG \ - .size_of_priv = sizeof(struct mxl111sf_adap_state), \ - .streaming_ctrl = mxl111sf_ep6_streaming_ctrl, \ - .stream = { \ - .type = USB_ISOC, \ - .count = 5, \ - .endpoint = 0x06, \ - .u = { \ - .isoc = { \ - .framesperurb = 24, \ - .framesize = 3072, \ - .interval = 1, \ - } \ - } \ - } - -#define MXL111SF_DEFAULT_DEVICE_PROPERTIES \ - .caps = DVB_USB_IS_AN_I2C_ADAPTER, \ - .usb_ctrl = DEVICE_SPECIFIC, \ - /* use usb alt setting 1 for EP4 ISOC transfer (dvb-t), \ - EP6 BULK transfer (atsc/qam), \ - use usb alt setting 2 for EP4 BULK transfer (dvb-t), \ - EP6 ISOC transfer (atsc/qam), \ - */ \ - .power_ctrl = mxl111sf_power_ctrl, \ - .i2c_algo = &mxl111sf_i2c_algo, \ - .generic_bulk_ctrl_endpoint = MXL_EP2_REG_WRITE, \ - .generic_bulk_ctrl_endpoint_response = MXL_EP1_REG_READ, \ - .size_of_priv = sizeof(struct mxl111sf_state) - -static struct dvb_usb_device_properties mxl111sf_dvbt_bulk_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 1, - .fe = {{ - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_BULK_STREAMING_CONFIG, - } }, - }, - }, - .num_device_descs = 3, - .devices = { - { "Hauppauge 126xxx DVBT (bulk)", - { NULL }, - { &mxl111sf_table[4], &mxl111sf_table[8], - NULL }, - }, - { "Hauppauge 117xxx DVBT (bulk)", - { NULL }, - { &mxl111sf_table[15], &mxl111sf_table[18], - NULL }, - }, - { "Hauppauge 138xxx DVBT (bulk)", - { NULL }, - { &mxl111sf_table[20], &mxl111sf_table[22], - &mxl111sf_table[24], &mxl111sf_table[26], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_dvbt_isoc_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 1, - .fe = {{ - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_ISOC_STREAMING_CONFIG, - } }, - }, - }, - .num_device_descs = 3, - .devices = { - { "Hauppauge 126xxx DVBT (isoc)", - { NULL }, - { &mxl111sf_table[4], &mxl111sf_table[8], - NULL }, - }, - { "Hauppauge 117xxx DVBT (isoc)", - { NULL }, - { &mxl111sf_table[15], &mxl111sf_table[18], - NULL }, - }, - { "Hauppauge 138xxx DVBT (isoc)", - { NULL }, - { &mxl111sf_table[20], &mxl111sf_table[22], - &mxl111sf_table[24], &mxl111sf_table[26], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_atsc_bulk_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 1, - .fe = {{ - .frontend_attach = mxl111sf_lgdt3305_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_BULK_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "Hauppauge 126xxx ATSC (bulk)", - { NULL }, - { &mxl111sf_table[1], &mxl111sf_table[5], - NULL }, - }, - { "Hauppauge 117xxx ATSC (bulk)", - { NULL }, - { &mxl111sf_table[12], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_atsc_isoc_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 1, - .fe = {{ - .frontend_attach = mxl111sf_lgdt3305_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_ISOC_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "Hauppauge 126xxx ATSC (isoc)", - { NULL }, - { &mxl111sf_table[1], &mxl111sf_table[5], - NULL }, - }, - { "Hauppauge 117xxx ATSC (isoc)", - { NULL }, - { &mxl111sf_table[12], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_mh_bulk_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 1, - .fe = {{ - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2160_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP5_BULK_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "HCW 126xxx (bulk)", - { NULL }, - { &mxl111sf_table[2], &mxl111sf_table[6], - NULL }, - }, - { "HCW 117xxx (bulk)", - { NULL }, - { &mxl111sf_table[13], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_mh_isoc_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 1, - .fe = {{ - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2160_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP5_ISOC_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "HCW 126xxx (isoc)", - { NULL }, - { &mxl111sf_table[2], &mxl111sf_table[6], - NULL }, - }, - { "HCW 117xxx (isoc)", - { NULL }, - { &mxl111sf_table[13], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_atsc_mh_bulk_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 3, - .fe = {{ - .frontend_attach = mxl111sf_lgdt3305_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_BULK_STREAMING_CONFIG, - }, - { - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_BULK_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2160_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP5_BULK_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "Hauppauge 126xxx ATSC+ (bulk)", - { NULL }, - { &mxl111sf_table[0], &mxl111sf_table[3], - &mxl111sf_table[7], &mxl111sf_table[9], - &mxl111sf_table[10], NULL }, - }, - { "Hauppauge 117xxx ATSC+ (bulk)", - { NULL }, - { &mxl111sf_table[11], &mxl111sf_table[14], - &mxl111sf_table[16], &mxl111sf_table[17], - &mxl111sf_table[32], &mxl111sf_table[33], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_atsc_mh_isoc_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 3, - .fe = {{ - .frontend_attach = mxl111sf_lgdt3305_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_ISOC_STREAMING_CONFIG, - }, - { - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_ISOC_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2160_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP5_ISOC_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "Hauppauge 126xxx ATSC+ (isoc)", - { NULL }, - { &mxl111sf_table[0], &mxl111sf_table[3], - &mxl111sf_table[7], &mxl111sf_table[9], - &mxl111sf_table[10], NULL }, - }, - { "Hauppauge 117xxx ATSC+ (isoc)", - { NULL }, - { &mxl111sf_table[11], &mxl111sf_table[14], - &mxl111sf_table[16], &mxl111sf_table[17], - &mxl111sf_table[32], &mxl111sf_table[33], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_mercury_spi_bulk_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 3, - .fe = {{ - .frontend_attach = mxl111sf_lgdt3305_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_BULK_STREAMING_CONFIG, - }, - { - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_BULK_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2161_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP5_BULK_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "Hauppauge Mercury (spi-bulk)", - { NULL }, - { &mxl111sf_table[19], &mxl111sf_table[21], - &mxl111sf_table[23], &mxl111sf_table[25], - NULL }, - }, - { "Hauppauge WinTV-Aero-M (spi-bulk)", - { NULL }, - { &mxl111sf_table[29], &mxl111sf_table[31], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_mercury_spi_isoc_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 3, - .fe = {{ - .frontend_attach = mxl111sf_lgdt3305_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_ISOC_STREAMING_CONFIG, - }, - { - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_ISOC_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2161_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP5_ISOC_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "Hauppauge Mercury (spi-isoc)", - { NULL }, - { &mxl111sf_table[19], &mxl111sf_table[21], - &mxl111sf_table[23], &mxl111sf_table[25], - NULL }, - }, - { "Hauppauge WinTV-Aero-M (spi-isoc)", - { NULL }, - { &mxl111sf_table[29], &mxl111sf_table[31], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_mercury_tp_bulk_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 3, - .fe = {{ - .frontend_attach = mxl111sf_lgdt3305_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_BULK_STREAMING_CONFIG, - }, - { - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_BULK_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2161_ep6_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_BULK_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "Hauppauge Mercury (tp-bulk)", - { NULL }, - { &mxl111sf_table[19], &mxl111sf_table[21], - &mxl111sf_table[23], &mxl111sf_table[25], - &mxl111sf_table[27], NULL }, - }, - { "Hauppauge WinTV-Aero-M", - { NULL }, - { &mxl111sf_table[29], &mxl111sf_table[31], - NULL }, - }, - } -}; - -static struct dvb_usb_device_properties mxl111sf_mercury_tp_isoc_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 3, - .fe = {{ - .frontend_attach = mxl111sf_lgdt3305_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_ISOC_STREAMING_CONFIG, - }, - { - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_ISOC_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2161_ep6_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_ISOC_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 2, - .devices = { - { "Hauppauge Mercury (tp-isoc)", - { NULL }, - { &mxl111sf_table[19], &mxl111sf_table[21], - &mxl111sf_table[23], &mxl111sf_table[25], - &mxl111sf_table[27], NULL }, - }, - { "Hauppauge WinTV-Aero-M (tp-isoc)", - { NULL }, - { &mxl111sf_table[29], &mxl111sf_table[31], - NULL }, - }, - } -}; - -static -struct dvb_usb_device_properties mxl111sf_mercury_mh_tp_bulk_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 2, - .fe = {{ - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_BULK_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2161_ep6_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_BULK_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 1, - .devices = { - { "Hauppauge 126xxx (tp-bulk)", - { NULL }, - { &mxl111sf_table[28], &mxl111sf_table[30], - NULL }, - }, - } -}; - -static -struct dvb_usb_device_properties mxl111sf_mercury_mh_tp_isoc_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 2, - .fe = {{ - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_ISOC_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2161_ep6_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP6_ISOC_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 1, - .devices = { - { "Hauppauge 126xxx (tp-isoc)", - { NULL }, - { &mxl111sf_table[28], &mxl111sf_table[30], - NULL }, - }, - } -}; - -static -struct dvb_usb_device_properties mxl111sf_mercury_mh_spi_bulk_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 2, - .fe = {{ - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_BULK_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2161_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP5_BULK_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 1, - .devices = { - { "Hauppauge 126xxx (spi-bulk)", - { NULL }, - { &mxl111sf_table[28], &mxl111sf_table[30], - NULL }, - }, - } -}; - -static -struct dvb_usb_device_properties mxl111sf_mercury_mh_spi_isoc_properties = { - MXL111SF_DEFAULT_DEVICE_PROPERTIES, - - .num_adapters = 1, - .adapter = { - { - .fe_ioctl_override = mxl111sf_fe_ioctl_override, - .num_frontends = 2, - .fe = {{ - .frontend_attach = mxl111sf_attach_demod, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP4_ISOC_STREAMING_CONFIG, - }, - { - .caps = DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD, - - .frontend_attach = mxl111sf_lg2161_frontend_attach, - .tuner_attach = mxl111sf_attach_tuner, - - MXL111SF_EP5_ISOC_STREAMING_CONFIG, - }}, - }, - }, - .num_device_descs = 1, - .devices = { - { "Hauppauge 126xxx (spi-isoc)", - { NULL }, - { &mxl111sf_table[28], &mxl111sf_table[30], - NULL }, - }, - } -}; - -static struct usb_driver mxl111sf_driver = { - .name = "dvb_usb_mxl111sf", - .probe = mxl111sf_probe, - .disconnect = dvb_usb_device_exit, - .id_table = mxl111sf_table, -}; - -module_usb_driver(mxl111sf_driver); - -MODULE_AUTHOR("Michael Krufky "); -MODULE_DESCRIPTION("Driver for MaxLinear MxL111SF"); -MODULE_VERSION("1.0"); -MODULE_LICENSE("GPL"); - -/* - * Local variables: - * c-basic-offset: 8 - * End: - */ diff --git a/drivers/media/dvb/dvb-usb/mxl111sf.h b/drivers/media/dvb/dvb-usb/mxl111sf.h deleted file mode 100644 index 364d89f826bd..000000000000 --- a/drivers/media/dvb/dvb-usb/mxl111sf.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2010 Michael Krufky (mkrufky@kernellabs.com) - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, version 2. - * - * see Documentation/dvb/README.dvb-usb for more information - */ - -#ifndef _DVB_USB_MXL111SF_H_ -#define _DVB_USB_MXL111SF_H_ - -#ifdef DVB_USB_LOG_PREFIX -#undef DVB_USB_LOG_PREFIX -#endif -#define DVB_USB_LOG_PREFIX "mxl111sf" -#include "dvb-usb.h" -#include - -#define MXL_EP1_REG_READ 1 -#define MXL_EP2_REG_WRITE 2 -#define MXL_EP3_INTERRUPT 3 -#define MXL_EP4_MPEG2 4 -#define MXL_EP5_I2S 5 -#define MXL_EP6_656 6 -#define MXL_EP6_MPEG2 6 - -#ifdef USING_ENUM_mxl111sf_current_mode -enum mxl111sf_current_mode { - mxl_mode_dvbt = MXL_EP4_MPEG2, - mxl_mode_mh = MXL_EP5_I2S, - mxl_mode_atsc = MXL_EP6_MPEG2, -}; -#endif - -enum mxl111sf_gpio_port_expander { - mxl111sf_gpio_hw, - mxl111sf_PCA9534, -}; - -struct mxl111sf_state { - struct dvb_usb_device *d; - - enum mxl111sf_gpio_port_expander gpio_port_expander; - u8 port_expander_addr; - - u8 chip_id; - u8 chip_ver; -#define MXL111SF_V6 1 -#define MXL111SF_V8_100 2 -#define MXL111SF_V8_200 3 - u8 chip_rev; - -#ifdef USING_ENUM_mxl111sf_current_mode - enum mxl111sf_current_mode current_mode; -#endif - -#define MXL_TUNER_MODE 0 -#define MXL_SOC_MODE 1 -#define MXL_DEV_MODE_MASK 0x01 -#if 1 - int device_mode; -#endif - /* use usb alt setting 1 for EP4 ISOC transfer (dvb-t), - EP5 BULK transfer (atsc-mh), - EP6 BULK transfer (atsc/qam), - use usb alt setting 2 for EP4 BULK transfer (dvb-t), - EP5 ISOC transfer (atsc-mh), - EP6 ISOC transfer (atsc/qam), - */ - int alt_mode; - int gpio_mode; - struct tveeprom tv; - - struct mutex fe_lock; -}; - -struct mxl111sf_adap_state { - int alt_mode; - int gpio_mode; - int device_mode; - int ep6_clockphase; - int (*fe_init)(struct dvb_frontend *); - int (*fe_sleep)(struct dvb_frontend *); -}; - -int mxl111sf_read_reg(struct mxl111sf_state *state, u8 addr, u8 *data); -int mxl111sf_write_reg(struct mxl111sf_state *state, u8 addr, u8 data); - -struct mxl111sf_reg_ctrl_info { - u8 addr; - u8 mask; - u8 data; -}; - -int mxl111sf_write_reg_mask(struct mxl111sf_state *state, - u8 addr, u8 mask, u8 data); -int mxl111sf_ctrl_program_regs(struct mxl111sf_state *state, - struct mxl111sf_reg_ctrl_info *ctrl_reg_info); - -/* needed for hardware i2c functions in mxl111sf-i2c.c: - * mxl111sf_i2c_send_data / mxl111sf_i2c_get_data */ -int mxl111sf_ctrl_msg(struct dvb_usb_device *d, - u8 cmd, u8 *wbuf, int wlen, u8 *rbuf, int rlen); - -#define mxl_printk(kern, fmt, arg...) \ - printk(kern "%s: " fmt "\n", __func__, ##arg) - -#define mxl_info(fmt, arg...) \ - mxl_printk(KERN_INFO, fmt, ##arg) - -extern int dvb_usb_mxl111sf_debug; -#define mxl_debug(fmt, arg...) \ - if (dvb_usb_mxl111sf_debug) \ - mxl_printk(KERN_DEBUG, fmt, ##arg) - -#define MXL_I2C_DBG 0x04 -#define MXL_ADV_DBG 0x10 -#define mxl_debug_adv(fmt, arg...) \ - if (dvb_usb_mxl111sf_debug & MXL_ADV_DBG) \ - mxl_printk(KERN_DEBUG, fmt, ##arg) - -#define mxl_i2c(fmt, arg...) \ - if (dvb_usb_mxl111sf_debug & MXL_I2C_DBG) \ - mxl_printk(KERN_DEBUG, fmt, ##arg) - -#define mxl_i2c_adv(fmt, arg...) \ - if ((dvb_usb_mxl111sf_debug & (MXL_I2C_DBG | MXL_ADV_DBG)) == \ - (MXL_I2C_DBG | MXL_ADV_DBG)) \ - mxl_printk(KERN_DEBUG, fmt, ##arg) - -/* The following allows the mxl_fail() macro defined below to work - * in externel modules, such as mxl111sf-tuner.ko, even though - * dvb_usb_mxl111sf_debug is not defined within those modules */ -#if (defined(__MXL111SF_TUNER_H__)) || (defined(__MXL111SF_DEMOD_H__)) -#define MXL_ADV_DEBUG_ENABLED MXL_ADV_DBG -#else -#define MXL_ADV_DEBUG_ENABLED dvb_usb_mxl111sf_debug -#endif - -#define mxl_fail(ret) \ -({ \ - int __ret; \ - __ret = (ret < 0); \ - if ((__ret) && (MXL_ADV_DEBUG_ENABLED & MXL_ADV_DBG)) \ - mxl_printk(KERN_ERR, "error %d on line %d", \ - ret, __LINE__); \ - __ret; \ -}) - -#endif /* _DVB_USB_MXL111SF_H_ */ - -/* - * Local variables: - * c-basic-offset: 8 - * End: - */ diff --git a/drivers/media/dvb/dvb-usb/pctv452e.c b/drivers/media/dvb/dvb-usb/pctv452e.c deleted file mode 100644 index f526eb05cc7a..000000000000 --- a/drivers/media/dvb/dvb-usb/pctv452e.c +++ /dev/null @@ -1,1064 +0,0 @@ -/* - * PCTV 452e DVB driver - * - * Copyright (c) 2006-2008 Dominik Kuhlen - * - * TT connect S2-3650-CI Common Interface support, MAC readout - * Copyright (C) 2008 Michael H. Schimek - * - * 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. - */ - -/* dvb usb framework */ -#define DVB_USB_LOG_PREFIX "pctv452e" -#include "dvb-usb.h" - -/* Demodulator */ -#include "stb0899_drv.h" -#include "stb0899_reg.h" -#include "stb0899_cfg.h" -/* Tuner */ -#include "stb6100.h" -#include "stb6100_cfg.h" -/* FE Power */ -#include "lnbp22.h" - -#include "dvb_ca_en50221.h" -#include "ttpci-eeprom.h" - -static int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -#define ISOC_INTERFACE_ALTERNATIVE 3 - -#define SYNC_BYTE_OUT 0xaa -#define SYNC_BYTE_IN 0x55 - -/* guessed: (copied from ttusb-budget) */ -#define PCTV_CMD_RESET 0x15 -/* command to poll IR receiver */ -#define PCTV_CMD_IR 0x1b -/* command to send I2C */ -#define PCTV_CMD_I2C 0x31 - -#define I2C_ADDR_STB0899 (0xd0 >> 1) -#define I2C_ADDR_STB6100 (0xc0 >> 1) -#define I2C_ADDR_LNBP22 (0x10 >> 1) -#define I2C_ADDR_24C16 (0xa0 >> 1) -#define I2C_ADDR_24C64 (0xa2 >> 1) - - -/* pctv452e sends us this amount of data for each issued usb-command */ -#define PCTV_ANSWER_LEN 64 -/* Wait up to 1000ms for device */ -#define PCTV_TIMEOUT 1000 - - -#define PCTV_LED_GPIO STB0899_GPIO01 -#define PCTV_LED_GREEN 0x82 -#define PCTV_LED_ORANGE 0x02 - -#define ci_dbg(format, arg...) \ -do { \ - if (0) \ - printk(KERN_DEBUG DVB_USB_LOG_PREFIX \ - ": " format "\n" , ## arg); \ -} while (0) - -enum { - TT3650_CMD_CI_TEST = 0x40, - TT3650_CMD_CI_RD_CTRL, - TT3650_CMD_CI_WR_CTRL, - TT3650_CMD_CI_RD_ATTR, - TT3650_CMD_CI_WR_ATTR, - TT3650_CMD_CI_RESET, - TT3650_CMD_CI_SET_VIDEO_PORT -}; - - -static struct stb0899_postproc pctv45e_postproc[] = { - { PCTV_LED_GPIO, STB0899_GPIOPULLUP }, - { 0, 0 } -}; - -/* - * stores all private variables for communication with the PCTV452e DVB-S2 - */ -struct pctv452e_state { - struct dvb_ca_en50221 ca; - struct mutex ca_mutex; - - u8 c; /* transaction counter, wraps around... */ - u8 initialized; /* set to 1 if 0x15 has been sent */ - u16 last_rc_key; -}; - -static int tt3650_ci_msg(struct dvb_usb_device *d, u8 cmd, u8 *data, - unsigned int write_len, unsigned int read_len) -{ - struct pctv452e_state *state = (struct pctv452e_state *)d->priv; - u8 buf[64]; - u8 id; - unsigned int rlen; - int ret; - - BUG_ON(NULL == data && 0 != (write_len | read_len)); - BUG_ON(write_len > 64 - 4); - BUG_ON(read_len > 64 - 4); - - id = state->c++; - - buf[0] = SYNC_BYTE_OUT; - buf[1] = id; - buf[2] = cmd; - buf[3] = write_len; - - memcpy(buf + 4, data, write_len); - - rlen = (read_len > 0) ? 64 : 0; - ret = dvb_usb_generic_rw(d, buf, 4 + write_len, - buf, rlen, /* delay_ms */ 0); - if (0 != ret) - goto failed; - - ret = -EIO; - if (SYNC_BYTE_IN != buf[0] || id != buf[1]) - goto failed; - - memcpy(data, buf + 4, read_len); - - return 0; - -failed: - err("CI error %d; %02X %02X %02X -> %02X %02X %02X.", - ret, SYNC_BYTE_OUT, id, cmd, buf[0], buf[1], buf[2]); - - return ret; -} - -static int tt3650_ci_msg_locked(struct dvb_ca_en50221 *ca, - u8 cmd, u8 *data, unsigned int write_len, - unsigned int read_len) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - struct pctv452e_state *state = (struct pctv452e_state *)d->priv; - int ret; - - mutex_lock(&state->ca_mutex); - ret = tt3650_ci_msg(d, cmd, data, write_len, read_len); - mutex_unlock(&state->ca_mutex); - - return ret; -} - -static int tt3650_ci_read_attribute_mem(struct dvb_ca_en50221 *ca, - int slot, int address) -{ - u8 buf[3]; - int ret; - - if (0 != slot) - return -EINVAL; - - buf[0] = (address >> 8) & 0x0F; - buf[1] = address; - - ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_RD_ATTR, buf, 2, 3); - - ci_dbg("%s %04x -> %d 0x%02x", - __func__, address, ret, buf[2]); - - if (ret < 0) - return ret; - - return buf[2]; -} - -static int tt3650_ci_write_attribute_mem(struct dvb_ca_en50221 *ca, - int slot, int address, u8 value) -{ - u8 buf[3]; - - ci_dbg("%s %d 0x%04x 0x%02x", - __func__, slot, address, value); - - if (0 != slot) - return -EINVAL; - - buf[0] = (address >> 8) & 0x0F; - buf[1] = address; - buf[2] = value; - - return tt3650_ci_msg_locked(ca, TT3650_CMD_CI_WR_ATTR, buf, 3, 3); -} - -static int tt3650_ci_read_cam_control(struct dvb_ca_en50221 *ca, - int slot, - u8 address) -{ - u8 buf[2]; - int ret; - - if (0 != slot) - return -EINVAL; - - buf[0] = address & 3; - - ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_RD_CTRL, buf, 1, 2); - - ci_dbg("%s 0x%02x -> %d 0x%02x", - __func__, address, ret, buf[1]); - - if (ret < 0) - return ret; - - return buf[1]; -} - -static int tt3650_ci_write_cam_control(struct dvb_ca_en50221 *ca, - int slot, - u8 address, - u8 value) -{ - u8 buf[2]; - - ci_dbg("%s %d 0x%02x 0x%02x", - __func__, slot, address, value); - - if (0 != slot) - return -EINVAL; - - buf[0] = address; - buf[1] = value; - - return tt3650_ci_msg_locked(ca, TT3650_CMD_CI_WR_CTRL, buf, 2, 2); -} - -static int tt3650_ci_set_video_port(struct dvb_ca_en50221 *ca, - int slot, - int enable) -{ - u8 buf[1]; - int ret; - - ci_dbg("%s %d %d", __func__, slot, enable); - - if (0 != slot) - return -EINVAL; - - enable = !!enable; - buf[0] = enable; - - ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_SET_VIDEO_PORT, buf, 1, 1); - if (ret < 0) - return ret; - - if (enable != buf[0]) { - err("CI not %sabled.", enable ? "en" : "dis"); - return -EIO; - } - - return 0; -} - -static int tt3650_ci_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) -{ - return tt3650_ci_set_video_port(ca, slot, /* enable */ 0); -} - -static int tt3650_ci_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) -{ - return tt3650_ci_set_video_port(ca, slot, /* enable */ 1); -} - -static int tt3650_ci_slot_reset(struct dvb_ca_en50221 *ca, int slot) -{ - struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; - struct pctv452e_state *state = (struct pctv452e_state *)d->priv; - u8 buf[1]; - int ret; - - ci_dbg("%s %d", __func__, slot); - - if (0 != slot) - return -EINVAL; - - buf[0] = 0; - - mutex_lock(&state->ca_mutex); - - ret = tt3650_ci_msg(d, TT3650_CMD_CI_RESET, buf, 1, 1); - if (0 != ret) - goto failed; - - msleep(500); - - buf[0] = 1; - - ret = tt3650_ci_msg(d, TT3650_CMD_CI_RESET, buf, 1, 1); - if (0 != ret) - goto failed; - - msleep(500); - - buf[0] = 0; /* FTA */ - - ret = tt3650_ci_msg(d, TT3650_CMD_CI_SET_VIDEO_PORT, buf, 1, 1); - - failed: - mutex_unlock(&state->ca_mutex); - - return ret; -} - -static int tt3650_ci_poll_slot_status(struct dvb_ca_en50221 *ca, - int slot, - int open) -{ - u8 buf[1]; - int ret; - - if (0 != slot) - return -EINVAL; - - ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_TEST, buf, 0, 1); - if (0 != ret) - return ret; - - if (1 == buf[0]) - return DVB_CA_EN50221_POLL_CAM_PRESENT | - DVB_CA_EN50221_POLL_CAM_READY; - - return 0; - -} - -static void tt3650_ci_uninit(struct dvb_usb_device *d) -{ - struct pctv452e_state *state; - - ci_dbg("%s", __func__); - - if (NULL == d) - return; - - state = (struct pctv452e_state *)d->priv; - if (NULL == state) - return; - - if (NULL == state->ca.data) - return; - - /* Error ignored. */ - tt3650_ci_set_video_port(&state->ca, /* slot */ 0, /* enable */ 0); - - dvb_ca_en50221_release(&state->ca); - - memset(&state->ca, 0, sizeof(state->ca)); -} - -static int tt3650_ci_init(struct dvb_usb_adapter *a) -{ - struct dvb_usb_device *d = a->dev; - struct pctv452e_state *state = (struct pctv452e_state *)d->priv; - int ret; - - ci_dbg("%s", __func__); - - mutex_init(&state->ca_mutex); - - state->ca.owner = THIS_MODULE; - state->ca.read_attribute_mem = tt3650_ci_read_attribute_mem; - state->ca.write_attribute_mem = tt3650_ci_write_attribute_mem; - state->ca.read_cam_control = tt3650_ci_read_cam_control; - state->ca.write_cam_control = tt3650_ci_write_cam_control; - state->ca.slot_reset = tt3650_ci_slot_reset; - state->ca.slot_shutdown = tt3650_ci_slot_shutdown; - state->ca.slot_ts_enable = tt3650_ci_slot_ts_enable; - state->ca.poll_slot_status = tt3650_ci_poll_slot_status; - state->ca.data = d; - - ret = dvb_ca_en50221_init(&a->dvb_adap, - &state->ca, - /* flags */ 0, - /* n_slots */ 1); - if (0 != ret) { - err("Cannot initialize CI: Error %d.", ret); - memset(&state->ca, 0, sizeof(state->ca)); - return ret; - } - - info("CI initialized."); - - return 0; -} - -#define CMD_BUFFER_SIZE 0x28 -static int pctv452e_i2c_msg(struct dvb_usb_device *d, u8 addr, - const u8 *snd_buf, u8 snd_len, - u8 *rcv_buf, u8 rcv_len) -{ - struct pctv452e_state *state = (struct pctv452e_state *)d->priv; - u8 buf[64]; - u8 id; - int ret; - - id = state->c++; - - ret = -EINVAL; - if (snd_len > 64 - 7 || rcv_len > 64 - 7) - goto failed; - - buf[0] = SYNC_BYTE_OUT; - buf[1] = id; - buf[2] = PCTV_CMD_I2C; - buf[3] = snd_len + 3; - buf[4] = addr << 1; - buf[5] = snd_len; - buf[6] = rcv_len; - - memcpy(buf + 7, snd_buf, snd_len); - - ret = dvb_usb_generic_rw(d, buf, 7 + snd_len, - buf, /* rcv_len */ 64, - /* delay_ms */ 0); - if (ret < 0) - goto failed; - - /* TT USB protocol error. */ - ret = -EIO; - if (SYNC_BYTE_IN != buf[0] || id != buf[1]) - goto failed; - - /* I2C device didn't respond as expected. */ - ret = -EREMOTEIO; - if (buf[5] < snd_len || buf[6] < rcv_len) - goto failed; - - memcpy(rcv_buf, buf + 7, rcv_len); - - return rcv_len; - -failed: - err("I2C error %d; %02X %02X %02X %02X %02X -> " - "%02X %02X %02X %02X %02X.", - ret, SYNC_BYTE_OUT, id, addr << 1, snd_len, rcv_len, - buf[0], buf[1], buf[4], buf[5], buf[6]); - - return ret; -} - -static int pctv452e_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msg, - int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adapter); - int i; - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - for (i = 0; i < num; i++) { - u8 addr, snd_len, rcv_len, *snd_buf, *rcv_buf; - int ret; - - if (msg[i].flags & I2C_M_RD) { - addr = msg[i].addr; - snd_buf = NULL; - snd_len = 0; - rcv_buf = msg[i].buf; - rcv_len = msg[i].len; - } else { - addr = msg[i].addr; - snd_buf = msg[i].buf; - snd_len = msg[i].len; - rcv_buf = NULL; - rcv_len = 0; - } - - ret = pctv452e_i2c_msg(d, addr, snd_buf, snd_len, rcv_buf, - rcv_len); - if (ret < rcv_len) - break; - } - - mutex_unlock(&d->i2c_mutex); - return i; -} - -static u32 pctv452e_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static int pctv452e_power_ctrl(struct dvb_usb_device *d, int i) -{ - struct pctv452e_state *state = (struct pctv452e_state *)d->priv; - u8 b0[] = { 0xaa, 0, PCTV_CMD_RESET, 1, 0 }; - u8 rx[PCTV_ANSWER_LEN]; - int ret; - - info("%s: %d\n", __func__, i); - - if (!i) - return 0; - - if (state->initialized) - return 0; - - /* hmm where shoud this should go? */ - ret = usb_set_interface(d->udev, 0, ISOC_INTERFACE_ALTERNATIVE); - if (ret != 0) - info("%s: Warning set interface returned: %d\n", - __func__, ret); - - /* this is a one-time initialization, dont know where to put */ - b0[1] = state->c++; - /* reset board */ - ret = dvb_usb_generic_rw(d, b0, sizeof(b0), rx, PCTV_ANSWER_LEN, 0); - if (ret) - return ret; - - b0[1] = state->c++; - b0[4] = 1; - /* reset board (again?) */ - ret = dvb_usb_generic_rw(d, b0, sizeof(b0), rx, PCTV_ANSWER_LEN, 0); - if (ret) - return ret; - - state->initialized = 1; - - return 0; -} - -static int pctv452e_rc_query(struct dvb_usb_device *d) -{ - struct pctv452e_state *state = (struct pctv452e_state *)d->priv; - u8 b[CMD_BUFFER_SIZE]; - u8 rx[PCTV_ANSWER_LEN]; - int ret, i; - u8 id = state->c++; - - /* prepare command header */ - b[0] = SYNC_BYTE_OUT; - b[1] = id; - b[2] = PCTV_CMD_IR; - b[3] = 0; - - /* send ir request */ - ret = dvb_usb_generic_rw(d, b, 4, rx, PCTV_ANSWER_LEN, 0); - if (ret != 0) - return ret; - - if (debug > 3) { - info("%s: read: %2d: %02x %02x %02x: ", __func__, - ret, rx[0], rx[1], rx[2]); - for (i = 0; (i < rx[3]) && ((i+3) < PCTV_ANSWER_LEN); i++) - info(" %02x", rx[i+3]); - - info("\n"); - } - - if ((rx[3] == 9) && (rx[12] & 0x01)) { - /* got a "press" event */ - state->last_rc_key = (rx[7] << 8) | rx[6]; - if (debug > 2) - info("%s: cmd=0x%02x sys=0x%02x\n", - __func__, rx[6], rx[7]); - - rc_keydown(d->rc_dev, state->last_rc_key, 0); - } else if (state->last_rc_key) { - rc_keyup(d->rc_dev); - state->last_rc_key = 0; - } - - return 0; -} - -static int pctv452e_read_mac_address(struct dvb_usb_device *d, u8 mac[6]) -{ - const u8 mem_addr[] = { 0x1f, 0xcc }; - u8 encoded_mac[20]; - int ret; - - ret = -EAGAIN; - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - goto failed; - - ret = pctv452e_i2c_msg(d, I2C_ADDR_24C16, - mem_addr + 1, /* snd_len */ 1, - encoded_mac, /* rcv_len */ 20); - if (-EREMOTEIO == ret) - /* Caution! A 24C16 interprets 0xA2 0x1F 0xCC as a - byte write if /WC is low. */ - ret = pctv452e_i2c_msg(d, I2C_ADDR_24C64, - mem_addr, 2, - encoded_mac, 20); - - mutex_unlock(&d->i2c_mutex); - - if (20 != ret) - goto failed; - - ret = ttpci_eeprom_decode_mac(mac, encoded_mac); - if (0 != ret) - goto failed; - - return 0; - -failed: - memset(mac, 0, 6); - - return ret; -} - -static const struct stb0899_s1_reg pctv452e_init_dev[] = { - { STB0899_DISCNTRL1, 0x26 }, - { STB0899_DISCNTRL2, 0x80 }, - { STB0899_DISRX_ST0, 0x04 }, - { STB0899_DISRX_ST1, 0x20 }, - { STB0899_DISPARITY, 0x00 }, - { STB0899_DISFIFO, 0x00 }, - { STB0899_DISF22, 0x99 }, - { STB0899_DISF22RX, 0x85 }, /* 0xa8 */ - { STB0899_ACRPRESC, 0x11 }, - { STB0899_ACRDIV1, 0x0a }, - { STB0899_ACRDIV2, 0x05 }, - { STB0899_DACR1 , 0x00 }, - { STB0899_DACR2 , 0x00 }, - { STB0899_OUTCFG, 0x00 }, - { STB0899_MODECFG, 0x00 }, /* Inversion */ - { STB0899_IRQMSK_3, 0xf3 }, - { STB0899_IRQMSK_2, 0xfc }, - { STB0899_IRQMSK_1, 0xff }, - { STB0899_IRQMSK_0, 0xff }, - { STB0899_I2CCFG, 0x88 }, - { STB0899_I2CRPT, 0x58 }, - { STB0899_GPIO00CFG, 0x82 }, - { STB0899_GPIO01CFG, 0x82 }, /* LED: 0x02 green, 0x82 orange */ - { STB0899_GPIO02CFG, 0x82 }, - { STB0899_GPIO03CFG, 0x82 }, - { STB0899_GPIO04CFG, 0x82 }, - { STB0899_GPIO05CFG, 0x82 }, - { STB0899_GPIO06CFG, 0x82 }, - { STB0899_GPIO07CFG, 0x82 }, - { STB0899_GPIO08CFG, 0x82 }, - { STB0899_GPIO09CFG, 0x82 }, - { STB0899_GPIO10CFG, 0x82 }, - { STB0899_GPIO11CFG, 0x82 }, - { STB0899_GPIO12CFG, 0x82 }, - { STB0899_GPIO13CFG, 0x82 }, - { STB0899_GPIO14CFG, 0x82 }, - { STB0899_GPIO15CFG, 0x82 }, - { STB0899_GPIO16CFG, 0x82 }, - { STB0899_GPIO17CFG, 0x82 }, - { STB0899_GPIO18CFG, 0x82 }, - { STB0899_GPIO19CFG, 0x82 }, - { STB0899_GPIO20CFG, 0x82 }, - { STB0899_SDATCFG, 0xb8 }, - { STB0899_SCLTCFG, 0xba }, - { STB0899_AGCRFCFG, 0x1c }, /* 0x11 DVB-S; 0x1c DVB-S2 (1c, rjkm) */ - { STB0899_GPIO22, 0x82 }, - { STB0899_GPIO21, 0x91 }, - { STB0899_DIRCLKCFG, 0x82 }, - { STB0899_CLKOUT27CFG, 0x7e }, - { STB0899_STDBYCFG, 0x82 }, - { STB0899_CS0CFG, 0x82 }, - { STB0899_CS1CFG, 0x82 }, - { STB0899_DISEQCOCFG, 0x20 }, - { STB0899_NCOARSE, 0x15 }, /* 0x15 27Mhz, F/3 198MHz, F/6 108MHz */ - { STB0899_SYNTCTRL, 0x00 }, /* 0x00 CLKI, 0x02 XTALI */ - { STB0899_FILTCTRL, 0x00 }, - { STB0899_SYSCTRL, 0x00 }, - { STB0899_STOPCLK1, 0x20 }, /* orig: 0x00 budget-ci: 0x20 */ - { STB0899_STOPCLK2, 0x00 }, - { STB0899_INTBUFCTRL, 0x0a }, - { STB0899_AGC2I1, 0x00 }, - { STB0899_AGC2I2, 0x00 }, - { STB0899_AGCIQIN, 0x00 }, - { STB0899_TSTRES, 0x40 }, /* rjkm */ - { 0xffff, 0xff }, -}; - -static const struct stb0899_s1_reg pctv452e_init_s1_demod[] = { - { STB0899_DEMOD, 0x00 }, - { STB0899_RCOMPC, 0xc9 }, - { STB0899_AGC1CN, 0x01 }, - { STB0899_AGC1REF, 0x10 }, - { STB0899_RTC, 0x23 }, - { STB0899_TMGCFG, 0x4e }, - { STB0899_AGC2REF, 0x34 }, - { STB0899_TLSR, 0x84 }, - { STB0899_CFD, 0xf7 }, - { STB0899_ACLC, 0x87 }, - { STB0899_BCLC, 0x94 }, - { STB0899_EQON, 0x41 }, - { STB0899_LDT, 0xf1 }, - { STB0899_LDT2, 0xe3 }, - { STB0899_EQUALREF, 0xb4 }, - { STB0899_TMGRAMP, 0x10 }, - { STB0899_TMGTHD, 0x30 }, - { STB0899_IDCCOMP, 0xfd }, - { STB0899_QDCCOMP, 0xff }, - { STB0899_POWERI, 0x0c }, - { STB0899_POWERQ, 0x0f }, - { STB0899_RCOMP, 0x6c }, - { STB0899_AGCIQIN, 0x80 }, - { STB0899_AGC2I1, 0x06 }, - { STB0899_AGC2I2, 0x00 }, - { STB0899_TLIR, 0x30 }, - { STB0899_RTF, 0x7f }, - { STB0899_DSTATUS, 0x00 }, - { STB0899_LDI, 0xbc }, - { STB0899_CFRM, 0xea }, - { STB0899_CFRL, 0x31 }, - { STB0899_NIRM, 0x2b }, - { STB0899_NIRL, 0x80 }, - { STB0899_ISYMB, 0x1d }, - { STB0899_QSYMB, 0xa6 }, - { STB0899_SFRH, 0x2f }, - { STB0899_SFRM, 0x68 }, - { STB0899_SFRL, 0x40 }, - { STB0899_SFRUPH, 0x2f }, - { STB0899_SFRUPM, 0x68 }, - { STB0899_SFRUPL, 0x40 }, - { STB0899_EQUAI1, 0x02 }, - { STB0899_EQUAQ1, 0xff }, - { STB0899_EQUAI2, 0x04 }, - { STB0899_EQUAQ2, 0x05 }, - { STB0899_EQUAI3, 0x02 }, - { STB0899_EQUAQ3, 0xfd }, - { STB0899_EQUAI4, 0x03 }, - { STB0899_EQUAQ4, 0x07 }, - { STB0899_EQUAI5, 0x08 }, - { STB0899_EQUAQ5, 0xf5 }, - { STB0899_DSTATUS2, 0x00 }, - { STB0899_VSTATUS, 0x00 }, - { STB0899_VERROR, 0x86 }, - { STB0899_IQSWAP, 0x2a }, - { STB0899_ECNT1M, 0x00 }, - { STB0899_ECNT1L, 0x00 }, - { STB0899_ECNT2M, 0x00 }, - { STB0899_ECNT2L, 0x00 }, - { STB0899_ECNT3M, 0x0a }, - { STB0899_ECNT3L, 0xad }, - { STB0899_FECAUTO1, 0x06 }, - { STB0899_FECM, 0x01 }, - { STB0899_VTH12, 0xb0 }, - { STB0899_VTH23, 0x7a }, - { STB0899_VTH34, 0x58 }, - { STB0899_VTH56, 0x38 }, - { STB0899_VTH67, 0x34 }, - { STB0899_VTH78, 0x24 }, - { STB0899_PRVIT, 0xff }, - { STB0899_VITSYNC, 0x19 }, - { STB0899_RSULC, 0xb1 }, /* DVB = 0xb1, DSS = 0xa1 */ - { STB0899_TSULC, 0x42 }, - { STB0899_RSLLC, 0x41 }, - { STB0899_TSLPL, 0x12 }, - { STB0899_TSCFGH, 0x0c }, - { STB0899_TSCFGM, 0x00 }, - { STB0899_TSCFGL, 0x00 }, - { STB0899_TSOUT, 0x69 }, /* 0x0d for CAM */ - { STB0899_RSSYNCDEL, 0x00 }, - { STB0899_TSINHDELH, 0x02 }, - { STB0899_TSINHDELM, 0x00 }, - { STB0899_TSINHDELL, 0x00 }, - { STB0899_TSLLSTKM, 0x1b }, - { STB0899_TSLLSTKL, 0xb3 }, - { STB0899_TSULSTKM, 0x00 }, - { STB0899_TSULSTKL, 0x00 }, - { STB0899_PCKLENUL, 0xbc }, - { STB0899_PCKLENLL, 0xcc }, - { STB0899_RSPCKLEN, 0xbd }, - { STB0899_TSSTATUS, 0x90 }, - { STB0899_ERRCTRL1, 0xb6 }, - { STB0899_ERRCTRL2, 0x95 }, - { STB0899_ERRCTRL3, 0x8d }, - { STB0899_DMONMSK1, 0x27 }, - { STB0899_DMONMSK0, 0x03 }, - { STB0899_DEMAPVIT, 0x5c }, - { STB0899_PLPARM, 0x19 }, - { STB0899_PDELCTRL, 0x48 }, - { STB0899_PDELCTRL2, 0x00 }, - { STB0899_BBHCTRL1, 0x00 }, - { STB0899_BBHCTRL2, 0x00 }, - { STB0899_HYSTTHRESH, 0x77 }, - { STB0899_MATCSTM, 0x00 }, - { STB0899_MATCSTL, 0x00 }, - { STB0899_UPLCSTM, 0x00 }, - { STB0899_UPLCSTL, 0x00 }, - { STB0899_DFLCSTM, 0x00 }, - { STB0899_DFLCSTL, 0x00 }, - { STB0899_SYNCCST, 0x00 }, - { STB0899_SYNCDCSTM, 0x00 }, - { STB0899_SYNCDCSTL, 0x00 }, - { STB0899_ISI_ENTRY, 0x00 }, - { STB0899_ISI_BIT_EN, 0x00 }, - { STB0899_MATSTRM, 0xf0 }, - { STB0899_MATSTRL, 0x02 }, - { STB0899_UPLSTRM, 0x45 }, - { STB0899_UPLSTRL, 0x60 }, - { STB0899_DFLSTRM, 0xe3 }, - { STB0899_DFLSTRL, 0x00 }, - { STB0899_SYNCSTR, 0x47 }, - { STB0899_SYNCDSTRM, 0x05 }, - { STB0899_SYNCDSTRL, 0x18 }, - { STB0899_CFGPDELSTATUS1, 0x19 }, - { STB0899_CFGPDELSTATUS2, 0x2b }, - { STB0899_BBFERRORM, 0x00 }, - { STB0899_BBFERRORL, 0x01 }, - { STB0899_UPKTERRORM, 0x00 }, - { STB0899_UPKTERRORL, 0x00 }, - { 0xffff, 0xff }, -}; - -static struct stb0899_config stb0899_config = { - .init_dev = pctv452e_init_dev, - .init_s2_demod = stb0899_s2_init_2, - .init_s1_demod = pctv452e_init_s1_demod, - .init_s2_fec = stb0899_s2_init_4, - .init_tst = stb0899_s1_init_5, - - .demod_address = I2C_ADDR_STB0899, /* I2C Address */ - .block_sync_mode = STB0899_SYNC_FORCED, /* ? */ - - .xtal_freq = 27000000, /* Assume Hz ? */ - .inversion = IQ_SWAP_ON, /* ? */ - - .lo_clk = 76500000, - .hi_clk = 99000000, - - .ts_output_mode = 0, /* Use parallel mode */ - .clock_polarity = 0, - .data_clk_parity = 0, - .fec_mode = 0, - - .esno_ave = STB0899_DVBS2_ESNO_AVE, - .esno_quant = STB0899_DVBS2_ESNO_QUANT, - .avframes_coarse = STB0899_DVBS2_AVFRAMES_COARSE, - .avframes_fine = STB0899_DVBS2_AVFRAMES_FINE, - .miss_threshold = STB0899_DVBS2_MISS_THRESHOLD, - .uwp_threshold_acq = STB0899_DVBS2_UWP_THRESHOLD_ACQ, - .uwp_threshold_track = STB0899_DVBS2_UWP_THRESHOLD_TRACK, - .uwp_threshold_sof = STB0899_DVBS2_UWP_THRESHOLD_SOF, - .sof_search_timeout = STB0899_DVBS2_SOF_SEARCH_TIMEOUT, - - .btr_nco_bits = STB0899_DVBS2_BTR_NCO_BITS, - .btr_gain_shift_offset = STB0899_DVBS2_BTR_GAIN_SHIFT_OFFSET, - .crl_nco_bits = STB0899_DVBS2_CRL_NCO_BITS, - .ldpc_max_iter = STB0899_DVBS2_LDPC_MAX_ITER, - - .tuner_get_frequency = stb6100_get_frequency, - .tuner_set_frequency = stb6100_set_frequency, - .tuner_set_bandwidth = stb6100_set_bandwidth, - .tuner_get_bandwidth = stb6100_get_bandwidth, - .tuner_set_rfsiggain = NULL, - - /* helper for switching LED green/orange */ - .postproc = pctv45e_postproc -}; - -static struct stb6100_config stb6100_config = { - .tuner_address = I2C_ADDR_STB6100, - .refclock = 27000000 -}; - - -static struct i2c_algorithm pctv452e_i2c_algo = { - .master_xfer = pctv452e_i2c_xfer, - .functionality = pctv452e_i2c_func -}; - -static int pctv452e_frontend_attach(struct dvb_usb_adapter *a) -{ - struct usb_device_id *id; - - a->fe_adap[0].fe = dvb_attach(stb0899_attach, &stb0899_config, - &a->dev->i2c_adap); - if (!a->fe_adap[0].fe) - return -ENODEV; - if ((dvb_attach(lnbp22_attach, a->fe_adap[0].fe, - &a->dev->i2c_adap)) == 0) - err("Cannot attach lnbp22\n"); - - id = a->dev->desc->warm_ids[0]; - if (USB_VID_TECHNOTREND == id->idVendor - && USB_PID_TECHNOTREND_CONNECT_S2_3650_CI == id->idProduct) - /* Error ignored. */ - tt3650_ci_init(a); - - return 0; -} - -static int pctv452e_tuner_attach(struct dvb_usb_adapter *a) -{ - if (!a->fe_adap[0].fe) - return -ENODEV; - if (dvb_attach(stb6100_attach, a->fe_adap[0].fe, &stb6100_config, - &a->dev->i2c_adap) == 0) { - err("%s failed\n", __func__); - return -ENODEV; - } - - return 0; -} - -static struct usb_device_id pctv452e_usb_table[] = { - {USB_DEVICE(USB_VID_PINNACLE, USB_PID_PCTV_452E)}, - {USB_DEVICE(USB_VID_TECHNOTREND, USB_PID_TECHNOTREND_CONNECT_S2_3600)}, - {USB_DEVICE(USB_VID_TECHNOTREND, - USB_PID_TECHNOTREND_CONNECT_S2_3650_CI)}, - {} -}; -MODULE_DEVICE_TABLE(usb, pctv452e_usb_table); - -static struct dvb_usb_device_properties pctv452e_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, /* more ? */ - .usb_ctrl = DEVICE_SPECIFIC, - - .size_of_priv = sizeof(struct pctv452e_state), - - .power_ctrl = pctv452e_power_ctrl, - - .rc.core = { - .rc_codes = RC_MAP_DIB0700_RC5_TABLE, - .allowed_protos = RC_TYPE_UNKNOWN, - .rc_query = pctv452e_rc_query, - .rc_interval = 100, - }, - - .num_adapters = 1, - .adapter = {{ - .num_frontends = 1, - .fe = {{ - .frontend_attach = pctv452e_frontend_attach, - .tuner_attach = pctv452e_tuner_attach, - - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_ISOC, - .count = 4, - .endpoint = 0x02, - .u = { - .isoc = { - .framesperurb = 4, - .framesize = 940, - .interval = 1 - } - } - }, - } }, - } }, - - .i2c_algo = &pctv452e_i2c_algo, - - .generic_bulk_ctrl_endpoint = 1, /* allow generice rw function */ - - .num_device_descs = 1, - .devices = { - { .name = "PCTV HDTV USB", - .cold_ids = { NULL, NULL }, /* this is a warm only device */ - .warm_ids = { &pctv452e_usb_table[0], NULL } - }, - { 0 }, - } -}; - -static struct dvb_usb_device_properties tt_connect_s2_3600_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, /* more ? */ - .usb_ctrl = DEVICE_SPECIFIC, - - .size_of_priv = sizeof(struct pctv452e_state), - - .power_ctrl = pctv452e_power_ctrl, - .read_mac_address = pctv452e_read_mac_address, - - .rc.core = { - .rc_codes = RC_MAP_TT_1500, - .allowed_protos = RC_TYPE_UNKNOWN, - .rc_query = pctv452e_rc_query, - .rc_interval = 100, - }, - - .num_adapters = 1, - .adapter = {{ - .num_frontends = 1, - .fe = {{ - .frontend_attach = pctv452e_frontend_attach, - .tuner_attach = pctv452e_tuner_attach, - - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_ISOC, - .count = 7, - .endpoint = 0x02, - .u = { - .isoc = { - .framesperurb = 4, - .framesize = 940, - .interval = 1 - } - } - }, - - } }, - } }, - - .i2c_algo = &pctv452e_i2c_algo, - - .generic_bulk_ctrl_endpoint = 1, /* allow generic rw function*/ - - .num_device_descs = 2, - .devices = { - { .name = "Technotrend TT Connect S2-3600", - .cold_ids = { NULL, NULL }, /* this is a warm only device */ - .warm_ids = { &pctv452e_usb_table[1], NULL } - }, - { .name = "Technotrend TT Connect S2-3650-CI", - .cold_ids = { NULL, NULL }, - .warm_ids = { &pctv452e_usb_table[2], NULL } - }, - { 0 }, - } -}; - -static void pctv452e_usb_disconnect(struct usb_interface *intf) -{ - struct dvb_usb_device *d = usb_get_intfdata(intf); - - tt3650_ci_uninit(d); - dvb_usb_device_exit(intf); -} - -static int pctv452e_usb_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - if (0 == dvb_usb_device_init(intf, &pctv452e_properties, - THIS_MODULE, NULL, adapter_nr) || - 0 == dvb_usb_device_init(intf, &tt_connect_s2_3600_properties, - THIS_MODULE, NULL, adapter_nr)) - return 0; - - return -ENODEV; -} - -static struct usb_driver pctv452e_usb_driver = { - .name = "pctv452e", - .probe = pctv452e_usb_probe, - .disconnect = pctv452e_usb_disconnect, - .id_table = pctv452e_usb_table, -}; - -module_usb_driver(pctv452e_usb_driver); - -MODULE_AUTHOR("Dominik Kuhlen "); -MODULE_AUTHOR("Andre Weidemann "); -MODULE_AUTHOR("Michael H. Schimek "); -MODULE_DESCRIPTION("Pinnacle PCTV HDTV USB DVB / TT connect S2-3600 Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/rtl28xxu.c b/drivers/media/dvb/dvb-usb/rtl28xxu.c deleted file mode 100644 index 6bd0bd792437..000000000000 --- a/drivers/media/dvb/dvb-usb/rtl28xxu.c +++ /dev/null @@ -1,1428 +0,0 @@ -/* - * Realtek RTL28xxU DVB USB driver - * - * Copyright (C) 2009 Antti Palosaari - * Copyright (C) 2011 Antti Palosaari - * Copyright (C) 2012 Thomas Mair - * - * 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. - */ - -#include "rtl28xxu.h" - -#include "rtl2830.h" -#include "rtl2832.h" - -#include "qt1010.h" -#include "mt2060.h" -#include "mxl5005s.h" -#include "fc0012.h" -#include "fc0013.h" - -/* debug */ -static int dvb_usb_rtl28xxu_debug; -module_param_named(debug, dvb_usb_rtl28xxu_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level" DVB_USB_DEBUG_STATUS); -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -static int rtl28xxu_ctrl_msg(struct dvb_usb_device *d, struct rtl28xxu_req *req) -{ - int ret; - unsigned int pipe; - u8 requesttype; - u8 *buf; - - buf = kmalloc(req->size, GFP_KERNEL); - if (!buf) { - ret = -ENOMEM; - goto err; - } - - if (req->index & CMD_WR_FLAG) { - /* write */ - memcpy(buf, req->data, req->size); - requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); - pipe = usb_sndctrlpipe(d->udev, 0); - } else { - /* read */ - requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); - pipe = usb_rcvctrlpipe(d->udev, 0); - } - - ret = usb_control_msg(d->udev, pipe, 0, requesttype, req->value, - req->index, buf, req->size, 1000); - if (ret > 0) - ret = 0; - - deb_dump(0, requesttype, req->value, req->index, buf, req->size, - deb_xfer); - - /* read request, copy returned data to return buf */ - if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN)) - memcpy(req->data, buf, req->size); - - kfree(buf); - - if (ret) - goto err; - - return ret; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static int rtl28xx_wr_regs(struct dvb_usb_device *d, u16 reg, u8 *val, int len) -{ - struct rtl28xxu_req req; - - if (reg < 0x3000) - req.index = CMD_USB_WR; - else if (reg < 0x4000) - req.index = CMD_SYS_WR; - else - req.index = CMD_IR_WR; - - req.value = reg; - req.size = len; - req.data = val; - - return rtl28xxu_ctrl_msg(d, &req); -} - -static int rtl2831_rd_regs(struct dvb_usb_device *d, u16 reg, u8 *val, int len) -{ - struct rtl28xxu_req req; - - if (reg < 0x3000) - req.index = CMD_USB_RD; - else if (reg < 0x4000) - req.index = CMD_SYS_RD; - else - req.index = CMD_IR_RD; - - req.value = reg; - req.size = len; - req.data = val; - - return rtl28xxu_ctrl_msg(d, &req); -} - -static int rtl28xx_wr_reg(struct dvb_usb_device *d, u16 reg, u8 val) -{ - return rtl28xx_wr_regs(d, reg, &val, 1); -} - -static int rtl28xx_rd_reg(struct dvb_usb_device *d, u16 reg, u8 *val) -{ - return rtl2831_rd_regs(d, reg, val, 1); -} - -/* I2C */ -static int rtl28xxu_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], - int num) -{ - int ret; - struct dvb_usb_device *d = i2c_get_adapdata(adap); - struct rtl28xxu_priv *priv = d->priv; - struct rtl28xxu_req req; - - /* - * It is not known which are real I2C bus xfer limits, but testing - * with RTL2831U + MT2060 gives max RD 24 and max WR 22 bytes. - * TODO: find out RTL2832U lens - */ - - /* - * I2C adapter logic looks rather complicated due to fact it handles - * three different access methods. Those methods are; - * 1) integrated demod access - * 2) old I2C access - * 3) new I2C access - * - * Used method is selected in order 1, 2, 3. Method 3 can handle all - * requests but there is two reasons why not use it always; - * 1) It is most expensive, usually two USB messages are needed - * 2) At least RTL2831U does not support it - * - * Method 3 is needed in case of I2C write+read (typical register read) - * where write is more than one byte. - */ - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - if (num == 2 && !(msg[0].flags & I2C_M_RD) && - (msg[1].flags & I2C_M_RD)) { - if (msg[0].len > 24 || msg[1].len > 24) { - /* TODO: check msg[0].len max */ - ret = -EOPNOTSUPP; - goto err_mutex_unlock; - } else if (msg[0].addr == 0x10) { - /* method 1 - integrated demod */ - req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1); - req.index = CMD_DEMOD_RD | priv->page; - req.size = msg[1].len; - req.data = &msg[1].buf[0]; - ret = rtl28xxu_ctrl_msg(d, &req); - } else if (msg[0].len < 2) { - /* method 2 - old I2C */ - req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1); - req.index = CMD_I2C_RD; - req.size = msg[1].len; - req.data = &msg[1].buf[0]; - ret = rtl28xxu_ctrl_msg(d, &req); - } else { - /* method 3 - new I2C */ - req.value = (msg[0].addr << 1); - req.index = CMD_I2C_DA_WR; - req.size = msg[0].len; - req.data = msg[0].buf; - ret = rtl28xxu_ctrl_msg(d, &req); - if (ret) - goto err_mutex_unlock; - - req.value = (msg[0].addr << 1); - req.index = CMD_I2C_DA_RD; - req.size = msg[1].len; - req.data = msg[1].buf; - ret = rtl28xxu_ctrl_msg(d, &req); - } - } else if (num == 1 && !(msg[0].flags & I2C_M_RD)) { - if (msg[0].len > 22) { - /* TODO: check msg[0].len max */ - ret = -EOPNOTSUPP; - goto err_mutex_unlock; - } else if (msg[0].addr == 0x10) { - /* method 1 - integrated demod */ - if (msg[0].buf[0] == 0x00) { - /* save demod page for later demod access */ - priv->page = msg[0].buf[1]; - ret = 0; - } else { - req.value = (msg[0].buf[0] << 8) | - (msg[0].addr << 1); - req.index = CMD_DEMOD_WR | priv->page; - req.size = msg[0].len-1; - req.data = &msg[0].buf[1]; - ret = rtl28xxu_ctrl_msg(d, &req); - } - } else if (msg[0].len < 23) { - /* method 2 - old I2C */ - req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1); - req.index = CMD_I2C_WR; - req.size = msg[0].len-1; - req.data = &msg[0].buf[1]; - ret = rtl28xxu_ctrl_msg(d, &req); - } else { - /* method 3 - new I2C */ - req.value = (msg[0].addr << 1); - req.index = CMD_I2C_DA_WR; - req.size = msg[0].len; - req.data = msg[0].buf; - ret = rtl28xxu_ctrl_msg(d, &req); - } - } else { - ret = -EINVAL; - } - -err_mutex_unlock: - mutex_unlock(&d->i2c_mutex); - - return ret ? ret : num; -} - -static u32 rtl28xxu_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm rtl28xxu_i2c_algo = { - .master_xfer = rtl28xxu_i2c_xfer, - .functionality = rtl28xxu_i2c_func, -}; - -static struct rtl2830_config rtl28xxu_rtl2830_mt2060_config = { - .i2c_addr = 0x10, /* 0x20 */ - .xtal = 28800000, - .ts_mode = 0, - .spec_inv = 1, - .if_dvbt = 36150000, - .vtop = 0x20, - .krf = 0x04, - .agc_targ_val = 0x2d, - -}; - -static struct rtl2830_config rtl28xxu_rtl2830_qt1010_config = { - .i2c_addr = 0x10, /* 0x20 */ - .xtal = 28800000, - .ts_mode = 0, - .spec_inv = 1, - .if_dvbt = 36125000, - .vtop = 0x20, - .krf = 0x04, - .agc_targ_val = 0x2d, -}; - -static struct rtl2830_config rtl28xxu_rtl2830_mxl5005s_config = { - .i2c_addr = 0x10, /* 0x20 */ - .xtal = 28800000, - .ts_mode = 0, - .spec_inv = 0, - .if_dvbt = 4570000, - .vtop = 0x3f, - .krf = 0x04, - .agc_targ_val = 0x3e, -}; - -static int rtl2831u_frontend_attach(struct dvb_usb_adapter *adap) -{ - int ret; - struct rtl28xxu_priv *priv = adap->dev->priv; - u8 buf[1]; - struct rtl2830_config *rtl2830_config; - /* open RTL2831U/RTL2830 I2C gate */ - struct rtl28xxu_req req_gate = { 0x0120, 0x0011, 0x0001, "\x08" }; - /* for MT2060 tuner probe */ - struct rtl28xxu_req req_mt2060 = { 0x00c0, CMD_I2C_RD, 1, buf }; - /* for QT1010 tuner probe */ - struct rtl28xxu_req req_qt1010 = { 0x0fc4, CMD_I2C_RD, 1, buf }; - - deb_info("%s:\n", __func__); - - /* - * RTL2831U GPIOs - * ========================================================= - * GPIO0 | tuner#0 | 0 off | 1 on | MXL5005S (?) - * GPIO2 | LED | 0 off | 1 on | - * GPIO4 | tuner#1 | 0 on | 1 off | MT2060 - */ - - /* GPIO direction */ - ret = rtl28xx_wr_reg(adap->dev, SYS_GPIO_DIR, 0x0a); - if (ret) - goto err; - - /* enable as output GPIO0, GPIO2, GPIO4 */ - ret = rtl28xx_wr_reg(adap->dev, SYS_GPIO_OUT_EN, 0x15); - if (ret) - goto err; - - /* - * Probe used tuner. We need to know used tuner before demod attach - * since there is some demod params needed to set according to tuner. - */ - - /* demod needs some time to wake up */ - msleep(20); - - /* open demod I2C gate */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate); - if (ret) - goto err; - - /* check QT1010 ID(?) register; reg=0f val=2c */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_qt1010); - if (ret == 0 && buf[0] == 0x2c) { - priv->tuner = TUNER_RTL2830_QT1010; - rtl2830_config = &rtl28xxu_rtl2830_qt1010_config; - deb_info("%s: QT1010\n", __func__); - goto found; - } else { - deb_info("%s: QT1010 probe failed=%d - %02x\n", - __func__, ret, buf[0]); - } - - /* open demod I2C gate */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate); - if (ret) - goto err; - - /* check MT2060 ID register; reg=00 val=63 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_mt2060); - if (ret == 0 && buf[0] == 0x63) { - priv->tuner = TUNER_RTL2830_MT2060; - rtl2830_config = &rtl28xxu_rtl2830_mt2060_config; - deb_info("%s: MT2060\n", __func__); - goto found; - } else { - deb_info("%s: MT2060 probe failed=%d - %02x\n", - __func__, ret, buf[0]); - } - - /* assume MXL5005S */ - ret = 0; - priv->tuner = TUNER_RTL2830_MXL5005S; - rtl2830_config = &rtl28xxu_rtl2830_mxl5005s_config; - deb_info("%s: MXL5005S\n", __func__); - goto found; - -found: - /* attach demodulator */ - adap->fe_adap[0].fe = dvb_attach(rtl2830_attach, rtl2830_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe == NULL) { - ret = -ENODEV; - goto err; - } - - return ret; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static struct rtl2832_config rtl28xxu_rtl2832_fc0012_config = { - .i2c_addr = 0x10, /* 0x20 */ - .xtal = 28800000, - .if_dvbt = 0, - .tuner = TUNER_RTL2832_FC0012 -}; - -static struct rtl2832_config rtl28xxu_rtl2832_fc0013_config = { - .i2c_addr = 0x10, /* 0x20 */ - .xtal = 28800000, - .if_dvbt = 0, - .tuner = TUNER_RTL2832_FC0013 -}; - -static int rtl2832u_fc0012_tuner_callback(struct dvb_usb_device *d, - int cmd, int arg) -{ - int ret; - u8 val; - - deb_info("%s cmd=%d arg=%d\n", __func__, cmd, arg); - switch (cmd) { - case FC_FE_CALLBACK_VHF_ENABLE: - /* set output values */ - ret = rtl28xx_rd_reg(d, SYS_GPIO_OUT_VAL, &val); - if (ret) - goto err; - - if (arg) - val &= 0xbf; /* set GPIO6 low */ - else - val |= 0x40; /* set GPIO6 high */ - - - ret = rtl28xx_wr_reg(d, SYS_GPIO_OUT_VAL, val); - if (ret) - goto err; - break; - default: - ret = -EINVAL; - goto err; - } - return 0; - -err: - err("%s: failed=%d\n", __func__, ret); - - return ret; -} - - -static int rtl2832u_fc0013_tuner_callback(struct dvb_usb_device *d, - int cmd, int arg) -{ - /* TODO implement*/ - return 0; -} - -static int rtl2832u_tuner_callback(struct dvb_usb_device *d, int cmd, int arg) -{ - struct rtl28xxu_priv *priv = d->priv; - - switch (priv->tuner) { - case TUNER_RTL2832_FC0012: - return rtl2832u_fc0012_tuner_callback(d, cmd, arg); - - case TUNER_RTL2832_FC0013: - return rtl2832u_fc0013_tuner_callback(d, cmd, arg); - default: - break; - } - - return -ENODEV; -} - -static int rtl2832u_frontend_callback(void *adapter_priv, int component, - int cmd, int arg) -{ - struct i2c_adapter *adap = adapter_priv; - struct dvb_usb_device *d = i2c_get_adapdata(adap); - - switch (component) { - case DVB_FRONTEND_COMPONENT_TUNER: - return rtl2832u_tuner_callback(d, cmd, arg); - default: - break; - } - - return -EINVAL; -} - - - - -static int rtl2832u_frontend_attach(struct dvb_usb_adapter *adap) -{ - int ret; - struct rtl28xxu_priv *priv = adap->dev->priv; - struct rtl2832_config *rtl2832_config; - - u8 buf[2], val; - /* open RTL2832U/RTL2832 I2C gate */ - struct rtl28xxu_req req_gate_open = {0x0120, 0x0011, 0x0001, "\x18"}; - /* close RTL2832U/RTL2832 I2C gate */ - struct rtl28xxu_req req_gate_close = {0x0120, 0x0011, 0x0001, "\x10"}; - /* for FC0012 tuner probe */ - struct rtl28xxu_req req_fc0012 = {0x00c6, CMD_I2C_RD, 1, buf}; - /* for FC0013 tuner probe */ - struct rtl28xxu_req req_fc0013 = {0x00c6, CMD_I2C_RD, 1, buf}; - /* for MT2266 tuner probe */ - struct rtl28xxu_req req_mt2266 = {0x00c0, CMD_I2C_RD, 1, buf}; - /* for FC2580 tuner probe */ - struct rtl28xxu_req req_fc2580 = {0x01ac, CMD_I2C_RD, 1, buf}; - /* for MT2063 tuner probe */ - struct rtl28xxu_req req_mt2063 = {0x00c0, CMD_I2C_RD, 1, buf}; - /* for MAX3543 tuner probe */ - struct rtl28xxu_req req_max3543 = {0x00c0, CMD_I2C_RD, 1, buf}; - /* for TUA9001 tuner probe */ - struct rtl28xxu_req req_tua9001 = {0x7ec0, CMD_I2C_RD, 2, buf}; - /* for MXL5007T tuner probe */ - struct rtl28xxu_req req_mxl5007t = {0xd9c0, CMD_I2C_RD, 1, buf}; - /* for E4000 tuner probe */ - struct rtl28xxu_req req_e4000 = {0x02c8, CMD_I2C_RD, 1, buf}; - /* for TDA18272 tuner probe */ - struct rtl28xxu_req req_tda18272 = {0x00c0, CMD_I2C_RD, 2, buf}; - - deb_info("%s:\n", __func__); - - - ret = rtl28xx_rd_reg(adap->dev, SYS_GPIO_DIR, &val); - if (ret) - goto err; - - val &= 0xbf; - - ret = rtl28xx_wr_reg(adap->dev, SYS_GPIO_DIR, val); - if (ret) - goto err; - - - /* enable as output GPIO3 and GPIO6*/ - ret = rtl28xx_rd_reg(adap->dev, SYS_GPIO_OUT_EN, &val); - if (ret) - goto err; - - val |= 0x48; - - ret = rtl28xx_wr_reg(adap->dev, SYS_GPIO_OUT_EN, val); - if (ret) - goto err; - - - - /* - * Probe used tuner. We need to know used tuner before demod attach - * since there is some demod params needed to set according to tuner. - */ - - /* open demod I2C gate */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate_open); - if (ret) - goto err; - - priv->tuner = TUNER_NONE; - - /* check FC0012 ID register; reg=00 val=a1 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_fc0012); - if (ret == 0 && buf[0] == 0xa1) { - priv->tuner = TUNER_RTL2832_FC0012; - rtl2832_config = &rtl28xxu_rtl2832_fc0012_config; - info("%s: FC0012 tuner found", __func__); - goto found; - } - - /* check FC0013 ID register; reg=00 val=a3 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_fc0013); - if (ret == 0 && buf[0] == 0xa3) { - priv->tuner = TUNER_RTL2832_FC0013; - rtl2832_config = &rtl28xxu_rtl2832_fc0013_config; - info("%s: FC0013 tuner found", __func__); - goto found; - } - - /* check MT2266 ID register; reg=00 val=85 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_mt2266); - if (ret == 0 && buf[0] == 0x85) { - priv->tuner = TUNER_RTL2832_MT2266; - /* TODO implement tuner */ - info("%s: MT2266 tuner found", __func__); - goto unsupported; - } - - /* check FC2580 ID register; reg=01 val=56 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_fc2580); - if (ret == 0 && buf[0] == 0x56) { - priv->tuner = TUNER_RTL2832_FC2580; - /* TODO implement tuner */ - info("%s: FC2580 tuner found", __func__); - goto unsupported; - } - - /* check MT2063 ID register; reg=00 val=9e || 9c */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_mt2063); - if (ret == 0 && (buf[0] == 0x9e || buf[0] == 0x9c)) { - priv->tuner = TUNER_RTL2832_MT2063; - /* TODO implement tuner */ - info("%s: MT2063 tuner found", __func__); - goto unsupported; - } - - /* check MAX3543 ID register; reg=00 val=38 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_max3543); - if (ret == 0 && buf[0] == 0x38) { - priv->tuner = TUNER_RTL2832_MAX3543; - /* TODO implement tuner */ - info("%s: MAX3534 tuner found", __func__); - goto unsupported; - } - - /* check TUA9001 ID register; reg=7e val=2328 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_tua9001); - if (ret == 0 && buf[0] == 0x23 && buf[1] == 0x28) { - priv->tuner = TUNER_RTL2832_TUA9001; - /* TODO implement tuner */ - info("%s: TUA9001 tuner found", __func__); - goto unsupported; - } - - /* check MXL5007R ID register; reg=d9 val=14 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_mxl5007t); - if (ret == 0 && buf[0] == 0x14) { - priv->tuner = TUNER_RTL2832_MXL5007T; - /* TODO implement tuner */ - info("%s: MXL5007T tuner found", __func__); - goto unsupported; - } - - /* check E4000 ID register; reg=02 val=40 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_e4000); - if (ret == 0 && buf[0] == 0x40) { - priv->tuner = TUNER_RTL2832_E4000; - /* TODO implement tuner */ - info("%s: E4000 tuner found", __func__); - goto unsupported; - } - - /* check TDA18272 ID register; reg=00 val=c760 */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_tda18272); - if (ret == 0 && (buf[0] == 0xc7 || buf[1] == 0x60)) { - priv->tuner = TUNER_RTL2832_TDA18272; - /* TODO implement tuner */ - info("%s: TDA18272 tuner found", __func__); - goto unsupported; - } - -unsupported: - /* close demod I2C gate */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate_close); - if (ret) - goto err; - - /* tuner not found */ - deb_info("No compatible tuner found"); - ret = -ENODEV; - return ret; - -found: - /* close demod I2C gate */ - ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate_close); - if (ret) - goto err; - - /* attach demodulator */ - adap->fe_adap[0].fe = dvb_attach(rtl2832_attach, rtl2832_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe == NULL) { - ret = -ENODEV; - goto err; - } - - /* set fe callbacks */ - adap->fe_adap[0].fe->callback = rtl2832u_frontend_callback; - - return ret; - -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static struct qt1010_config rtl28xxu_qt1010_config = { - .i2c_address = 0x62, /* 0xc4 */ -}; - -static struct mt2060_config rtl28xxu_mt2060_config = { - .i2c_address = 0x60, /* 0xc0 */ - .clock_out = 0, -}; - -static struct mxl5005s_config rtl28xxu_mxl5005s_config = { - .i2c_address = 0x63, /* 0xc6 */ - .if_freq = IF_FREQ_4570000HZ, - .xtal_freq = CRYSTAL_FREQ_16000000HZ, - .agc_mode = MXL_SINGLE_AGC, - .tracking_filter = MXL_TF_C_H, - .rssi_enable = MXL_RSSI_ENABLE, - .cap_select = MXL_CAP_SEL_ENABLE, - .div_out = MXL_DIV_OUT_4, - .clock_out = MXL_CLOCK_OUT_DISABLE, - .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, - .top = MXL5005S_TOP_25P2, - .mod_mode = MXL_DIGITAL_MODE, - .if_mode = MXL_ZERO_IF, - .AgcMasterByte = 0x00, -}; - -static int rtl2831u_tuner_attach(struct dvb_usb_adapter *adap) -{ - int ret; - struct rtl28xxu_priv *priv = adap->dev->priv; - struct i2c_adapter *rtl2830_tuner_i2c; - struct dvb_frontend *fe; - - deb_info("%s:\n", __func__); - - /* use rtl2830 driver I2C adapter, for more info see rtl2830 driver */ - rtl2830_tuner_i2c = rtl2830_get_tuner_i2c_adapter(adap->fe_adap[0].fe); - - switch (priv->tuner) { - case TUNER_RTL2830_QT1010: - fe = dvb_attach(qt1010_attach, adap->fe_adap[0].fe, - rtl2830_tuner_i2c, &rtl28xxu_qt1010_config); - break; - case TUNER_RTL2830_MT2060: - fe = dvb_attach(mt2060_attach, adap->fe_adap[0].fe, - rtl2830_tuner_i2c, &rtl28xxu_mt2060_config, - 1220); - break; - case TUNER_RTL2830_MXL5005S: - fe = dvb_attach(mxl5005s_attach, adap->fe_adap[0].fe, - rtl2830_tuner_i2c, &rtl28xxu_mxl5005s_config); - break; - default: - fe = NULL; - err("unknown tuner=%d", priv->tuner); - } - - if (fe == NULL) { - ret = -ENODEV; - goto err; - } - - return 0; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static int rtl2832u_tuner_attach(struct dvb_usb_adapter *adap) -{ - int ret; - struct rtl28xxu_priv *priv = adap->dev->priv; - struct dvb_frontend *fe; - - deb_info("%s:\n", __func__); - - switch (priv->tuner) { - case TUNER_RTL2832_FC0012: - fe = dvb_attach(fc0012_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, 0xc6>>1, 0, FC_XTAL_28_8_MHZ); - - /* since fc0012 includs reading the signal strength delegate - * that to the tuner driver */ - adap->fe_adap[0].fe->ops.read_signal_strength = adap->fe_adap[0]. - fe->ops.tuner_ops.get_rf_strength; - return 0; - break; - case TUNER_RTL2832_FC0013: - fe = dvb_attach(fc0013_attach, adap->fe_adap[0].fe, - &adap->dev->i2c_adap, 0xc6>>1, 0, FC_XTAL_28_8_MHZ); - - /* fc0013 also supports signal strength reading */ - adap->fe_adap[0].fe->ops.read_signal_strength = adap->fe_adap[0] - .fe->ops.tuner_ops.get_rf_strength; - return 0; - default: - fe = NULL; - err("unknown tuner=%d", priv->tuner); - } - - if (fe == NULL) { - ret = -ENODEV; - goto err; - } - - return 0; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static int rtl2831u_streaming_ctrl(struct dvb_usb_adapter *adap , int onoff) -{ - int ret; - u8 buf[2], gpio; - - deb_info("%s: onoff=%d\n", __func__, onoff); - - ret = rtl28xx_rd_reg(adap->dev, SYS_GPIO_OUT_VAL, &gpio); - if (ret) - goto err; - - if (onoff) { - buf[0] = 0x00; - buf[1] = 0x00; - gpio |= 0x04; /* LED on */ - } else { - buf[0] = 0x10; /* stall EPA */ - buf[1] = 0x02; /* reset EPA */ - gpio &= (~0x04); /* LED off */ - } - - ret = rtl28xx_wr_reg(adap->dev, SYS_GPIO_OUT_VAL, gpio); - if (ret) - goto err; - - ret = rtl28xx_wr_regs(adap->dev, USB_EPA_CTL, buf, 2); - if (ret) - goto err; - - return ret; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static int rtl2832u_streaming_ctrl(struct dvb_usb_adapter *adap , int onoff) -{ - int ret; - u8 buf[2]; - - deb_info("%s: onoff=%d\n", __func__, onoff); - - - if (onoff) { - buf[0] = 0x00; - buf[1] = 0x00; - } else { - buf[0] = 0x10; /* stall EPA */ - buf[1] = 0x02; /* reset EPA */ - } - - ret = rtl28xx_wr_regs(adap->dev, USB_EPA_CTL, buf, 2); - if (ret) - goto err; - - return ret; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static int rtl2831u_power_ctrl(struct dvb_usb_device *d, int onoff) -{ - int ret; - u8 gpio, sys0; - - deb_info("%s: onoff=%d\n", __func__, onoff); - - /* demod adc */ - ret = rtl28xx_rd_reg(d, SYS_SYS0, &sys0); - if (ret) - goto err; - - /* tuner power, read GPIOs */ - ret = rtl28xx_rd_reg(d, SYS_GPIO_OUT_VAL, &gpio); - if (ret) - goto err; - - deb_info("%s: RD SYS0=%02x GPIO_OUT_VAL=%02x\n", __func__, sys0, gpio); - - if (onoff) { - gpio |= 0x01; /* GPIO0 = 1 */ - gpio &= (~0x10); /* GPIO4 = 0 */ - sys0 = sys0 & 0x0f; - sys0 |= 0xe0; - } else { - gpio &= (~0x01); /* GPIO0 = 0 */ - gpio |= 0x10; /* GPIO4 = 1 */ - sys0 = sys0 & (~0xc0); - } - - deb_info("%s: WR SYS0=%02x GPIO_OUT_VAL=%02x\n", __func__, sys0, gpio); - - /* demod adc */ - ret = rtl28xx_wr_reg(d, SYS_SYS0, sys0); - if (ret) - goto err; - - /* tuner power, write GPIOs */ - ret = rtl28xx_wr_reg(d, SYS_GPIO_OUT_VAL, gpio); - if (ret) - goto err; - - return ret; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static int rtl2832u_power_ctrl(struct dvb_usb_device *d, int onoff) -{ - int ret; - u8 val; - - deb_info("%s: onoff=%d\n", __func__, onoff); - - if (onoff) { - /* set output values */ - ret = rtl28xx_rd_reg(d, SYS_GPIO_OUT_VAL, &val); - if (ret) - goto err; - - val |= 0x08; - val &= 0xef; - - ret = rtl28xx_wr_reg(d, SYS_GPIO_OUT_VAL, val); - if (ret) - goto err; - - /* demod_ctl_1 */ - ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL1, &val); - if (ret) - goto err; - - val &= 0xef; - - ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL1, val); - if (ret) - goto err; - - /* demod control */ - /* PLL enable */ - ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL, &val); - if (ret) - goto err; - - /* bit 7 to 1 */ - val |= 0x80; - - ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL, val); - if (ret) - goto err; - - /* demod HW reset */ - ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL, &val); - if (ret) - goto err; - /* bit 5 to 0 */ - val &= 0xdf; - - ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL, val); - if (ret) - goto err; - - ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL, &val); - if (ret) - goto err; - - val |= 0x20; - - ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL, val); - if (ret) - goto err; - - mdelay(5); - - /*enable ADC_Q and ADC_I */ - ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL, &val); - if (ret) - goto err; - - val |= 0x48; - - ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL, val); - if (ret) - goto err; - - - } else { - /* demod_ctl_1 */ - ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL1, &val); - if (ret) - goto err; - - val |= 0x0c; - - ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL1, val); - if (ret) - goto err; - - /* set output values */ - ret = rtl28xx_rd_reg(d, SYS_GPIO_OUT_VAL, &val); - if (ret) - goto err; - - val |= 0x10; - - ret = rtl28xx_wr_reg(d, SYS_GPIO_OUT_VAL, val); - if (ret) - goto err; - - /* demod control */ - ret = rtl28xx_rd_reg(d, SYS_DEMOD_CTL, &val); - if (ret) - goto err; - - val &= 0x37; - - ret = rtl28xx_wr_reg(d, SYS_DEMOD_CTL, val); - if (ret) - goto err; - - } - - return ret; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - - -static int rtl2831u_rc_query(struct dvb_usb_device *d) -{ - int ret, i; - struct rtl28xxu_priv *priv = d->priv; - u8 buf[5]; - u32 rc_code; - struct rtl28xxu_reg_val rc_nec_tab[] = { - { 0x3033, 0x80 }, - { 0x3020, 0x43 }, - { 0x3021, 0x16 }, - { 0x3022, 0x16 }, - { 0x3023, 0x5a }, - { 0x3024, 0x2d }, - { 0x3025, 0x16 }, - { 0x3026, 0x01 }, - { 0x3028, 0xb0 }, - { 0x3029, 0x04 }, - { 0x302c, 0x88 }, - { 0x302e, 0x13 }, - { 0x3030, 0xdf }, - { 0x3031, 0x05 }, - }; - - /* init remote controller */ - if (!priv->rc_active) { - for (i = 0; i < ARRAY_SIZE(rc_nec_tab); i++) { - ret = rtl28xx_wr_reg(d, rc_nec_tab[i].reg, - rc_nec_tab[i].val); - if (ret) - goto err; - } - priv->rc_active = true; - } - - ret = rtl2831_rd_regs(d, SYS_IRRC_RP, buf, 5); - if (ret) - goto err; - - if (buf[4] & 0x01) { - if (buf[2] == (u8) ~buf[3]) { - if (buf[0] == (u8) ~buf[1]) { - /* NEC standard (16 bit) */ - rc_code = buf[0] << 8 | buf[2]; - } else { - /* NEC extended (24 bit) */ - rc_code = buf[0] << 16 | - buf[1] << 8 | buf[2]; - } - } else { - /* NEC full (32 bit) */ - rc_code = buf[0] << 24 | buf[1] << 16 | - buf[2] << 8 | buf[3]; - } - - rc_keydown(d->rc_dev, rc_code, 0); - - ret = rtl28xx_wr_reg(d, SYS_IRRC_SR, 1); - if (ret) - goto err; - - /* repeated intentionally to avoid extra keypress */ - ret = rtl28xx_wr_reg(d, SYS_IRRC_SR, 1); - if (ret) - goto err; - } - - return ret; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static int rtl2832u_rc_query(struct dvb_usb_device *d) -{ - int ret, i; - struct rtl28xxu_priv *priv = d->priv; - u8 buf[128]; - int len; - struct rtl28xxu_reg_val rc_nec_tab[] = { - { IR_RX_CTRL, 0x20 }, - { IR_RX_BUF_CTRL, 0x80 }, - { IR_RX_IF, 0xff }, - { IR_RX_IE, 0xff }, - { IR_MAX_DURATION0, 0xd0 }, - { IR_MAX_DURATION1, 0x07 }, - { IR_IDLE_LEN0, 0xc0 }, - { IR_IDLE_LEN1, 0x00 }, - { IR_GLITCH_LEN, 0x03 }, - { IR_RX_CLK, 0x09 }, - { IR_RX_CFG, 0x1c }, - { IR_MAX_H_TOL_LEN, 0x1e }, - { IR_MAX_L_TOL_LEN, 0x1e }, - { IR_RX_CTRL, 0x80 }, - }; - - /* init remote controller */ - if (!priv->rc_active) { - for (i = 0; i < ARRAY_SIZE(rc_nec_tab); i++) { - ret = rtl28xx_wr_reg(d, rc_nec_tab[i].reg, - rc_nec_tab[i].val); - if (ret) - goto err; - } - priv->rc_active = true; - } - - ret = rtl28xx_rd_reg(d, IR_RX_IF, &buf[0]); - if (ret) - goto err; - - if (buf[0] != 0x83) - goto exit; - - ret = rtl28xx_rd_reg(d, IR_RX_BC, &buf[0]); - if (ret) - goto err; - - len = buf[0]; - ret = rtl2831_rd_regs(d, IR_RX_BUF, buf, len); - - /* TODO: pass raw IR to Kernel IR decoder */ - - ret = rtl28xx_wr_reg(d, IR_RX_IF, 0x03); - ret = rtl28xx_wr_reg(d, IR_RX_BUF_CTRL, 0x80); - ret = rtl28xx_wr_reg(d, IR_RX_CTRL, 0x80); - -exit: - return ret; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -enum rtl28xxu_usb_table_entry { - RTL2831U_0BDA_2831, - RTL2831U_14AA_0160, - RTL2831U_14AA_0161, - RTL2832U_0CCD_00A9, - RTL2832U_1F4D_B803, - RTL2832U_0CCD_00B3, -}; - -static struct usb_device_id rtl28xxu_table[] = { - /* RTL2831U */ - [RTL2831U_0BDA_2831] = { - USB_DEVICE(USB_VID_REALTEK, USB_PID_REALTEK_RTL2831U)}, - [RTL2831U_14AA_0160] = { - USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT)}, - [RTL2831U_14AA_0161] = { - USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT_2)}, - - /* RTL2832U */ - [RTL2832U_0CCD_00A9] = { - USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_BLACK_REV1)}, - [RTL2832U_1F4D_B803] = { - USB_DEVICE(USB_VID_GTEK, USB_PID_DELOCK_USB2_DVBT)}, - [RTL2832U_0CCD_00B3] = { - USB_DEVICE(USB_VID_TERRATEC, USB_PID_NOXON_DAB_STICK)}, - {} /* terminating entry */ -}; - -MODULE_DEVICE_TABLE(usb, rtl28xxu_table); - -static struct dvb_usb_device_properties rtl28xxu_properties[] = { - { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - .no_reconnect = 1, - - .size_of_priv = sizeof(struct rtl28xxu_priv), - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = { - { - .frontend_attach = rtl2831u_frontend_attach, - .tuner_attach = rtl2831u_tuner_attach, - .streaming_ctrl = rtl2831u_streaming_ctrl, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x81, - .u = { - .bulk = { - .buffersize = 8*512, - } - } - } - } - } - } - }, - - .power_ctrl = rtl2831u_power_ctrl, - - .rc.core = { - .protocol = RC_TYPE_NEC, - .module_name = "rtl28xxu", - .rc_query = rtl2831u_rc_query, - .rc_interval = 400, - .allowed_protos = RC_TYPE_NEC, - .rc_codes = RC_MAP_EMPTY, - }, - - .i2c_algo = &rtl28xxu_i2c_algo, - - .num_device_descs = 2, - .devices = { - { - .name = "Realtek RTL2831U reference design", - .warm_ids = { - &rtl28xxu_table[RTL2831U_0BDA_2831], - }, - }, - { - .name = "Freecom USB2.0 DVB-T", - .warm_ids = { - &rtl28xxu_table[RTL2831U_14AA_0160], - &rtl28xxu_table[RTL2831U_14AA_0161], - }, - }, - } - }, - { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = DEVICE_SPECIFIC, - .no_reconnect = 1, - - .size_of_priv = sizeof(struct rtl28xxu_priv), - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = { - { - .frontend_attach = rtl2832u_frontend_attach, - .tuner_attach = rtl2832u_tuner_attach, - .streaming_ctrl = rtl2832u_streaming_ctrl, - .stream = { - .type = USB_BULK, - .count = 6, - .endpoint = 0x81, - .u = { - .bulk = { - .buffersize = 8*512, - } - } - } - } - } - } - }, - - .power_ctrl = rtl2832u_power_ctrl, - - .rc.core = { - .protocol = RC_TYPE_NEC, - .module_name = "rtl28xxu", - .rc_query = rtl2832u_rc_query, - .rc_interval = 400, - .allowed_protos = RC_TYPE_NEC, - .rc_codes = RC_MAP_EMPTY, - }, - - .i2c_algo = &rtl28xxu_i2c_algo, - - .num_device_descs = 3, - .devices = { - { - .name = "Terratec Cinergy T Stick Black", - .warm_ids = { - &rtl28xxu_table[RTL2832U_0CCD_00A9], - }, - }, - { - .name = "G-Tek Electronics Group Lifeview LV5TDLX DVB-T", - .warm_ids = { - &rtl28xxu_table[RTL2832U_1F4D_B803], - }, - }, - { - .name = "NOXON DAB/DAB+ USB dongle", - .warm_ids = { - &rtl28xxu_table[RTL2832U_0CCD_00B3], - }, - }, - } - }, - -}; - -static int rtl28xxu_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - int ret, i; - u8 val; - int properties_count = ARRAY_SIZE(rtl28xxu_properties); - struct dvb_usb_device *d; - struct usb_device *udev; - bool found; - - deb_info("%s: interface=%d\n", __func__, - intf->cur_altsetting->desc.bInterfaceNumber); - - if (intf->cur_altsetting->desc.bInterfaceNumber != 0) - return 0; - - /* Dynamic USB ID support. Replaces first device ID with current one .*/ - udev = interface_to_usbdev(intf); - - for (i = 0, found = false; i < ARRAY_SIZE(rtl28xxu_table) - 1; i++) { - if (rtl28xxu_table[i].idVendor == - le16_to_cpu(udev->descriptor.idVendor) && - rtl28xxu_table[i].idProduct == - le16_to_cpu(udev->descriptor.idProduct)) { - found = true; - break; - } - } - - if (!found) { - deb_info("%s: using dynamic ID %04x:%04x\n", __func__, - le16_to_cpu(udev->descriptor.idVendor), - le16_to_cpu(udev->descriptor.idProduct)); - rtl28xxu_properties[0].devices[0].warm_ids[0]->idVendor = - le16_to_cpu(udev->descriptor.idVendor); - rtl28xxu_properties[0].devices[0].warm_ids[0]->idProduct = - le16_to_cpu(udev->descriptor.idProduct); - } - - for (i = 0; i < properties_count; i++) { - ret = dvb_usb_device_init(intf, &rtl28xxu_properties[i], - THIS_MODULE, &d, adapter_nr); - if (ret == 0 || ret != -ENODEV) - break; - } - - if (ret) - goto err; - - - /* init USB endpoints */ - ret = rtl28xx_rd_reg(d, USB_SYSCTL_0, &val); - if (ret) - goto err; - - /* enable DMA and Full Packet Mode*/ - val |= 0x09; - ret = rtl28xx_wr_reg(d, USB_SYSCTL_0, val); - if (ret) - goto err; - - /* set EPA maximum packet size to 0x0200 */ - ret = rtl28xx_wr_regs(d, USB_EPA_MAXPKT, "\x00\x02\x00\x00", 4); - if (ret) - goto err; - - /* change EPA FIFO length */ - ret = rtl28xx_wr_regs(d, USB_EPA_FIFO_CFG, "\x14\x00\x00\x00", 4); - if (ret) - goto err; - - return ret; -err: - deb_info("%s: failed=%d\n", __func__, ret); - return ret; -} - -static struct usb_driver rtl28xxu_driver = { - .name = "dvb_usb_rtl28xxu", - .probe = rtl28xxu_probe, - .disconnect = dvb_usb_device_exit, - .id_table = rtl28xxu_table, -}; - -/* module stuff */ -static int __init rtl28xxu_module_init(void) -{ - int ret; - - deb_info("%s:\n", __func__); - - ret = usb_register(&rtl28xxu_driver); - if (ret) - err("usb_register failed=%d", ret); - - return ret; -} - -static void __exit rtl28xxu_module_exit(void) -{ - deb_info("%s:\n", __func__); - - /* deregister this driver from the USB subsystem */ - usb_deregister(&rtl28xxu_driver); -} - -module_init(rtl28xxu_module_init); -module_exit(rtl28xxu_module_exit); - -MODULE_DESCRIPTION("Realtek RTL28xxU DVB USB driver"); -MODULE_AUTHOR("Antti Palosaari "); -MODULE_AUTHOR("Thomas Mair "); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/rtl28xxu.h b/drivers/media/dvb/dvb-usb/rtl28xxu.h deleted file mode 100644 index 90f3bb4f4c0e..000000000000 --- a/drivers/media/dvb/dvb-usb/rtl28xxu.h +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Realtek RTL28xxU DVB USB driver - * - * Copyright (C) 2009 Antti Palosaari - * Copyright (C) 2011 Antti Palosaari - * - * 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. - */ - -#ifndef RTL28XXU_H -#define RTL28XXU_H - -#define DVB_USB_LOG_PREFIX "rtl28xxu" -#include "dvb-usb.h" - -#define deb_info(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x01, args) -#define deb_rc(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x02, args) -#define deb_xfer(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x04, args) -#define deb_reg(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x08, args) -#define deb_i2c(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x10, args) -#define deb_fw(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x20, args) - -#define deb_dump(r, t, v, i, b, l, func) { \ - int loop_; \ - func("%02x %02x %02x %02x %02x %02x %02x %02x", \ - t, r, v & 0xff, v >> 8, i & 0xff, i >> 8, l & 0xff, l >> 8); \ - if (t == (USB_TYPE_VENDOR | USB_DIR_OUT)) \ - func(" >>> "); \ - else \ - func(" <<< "); \ - for (loop_ = 0; loop_ < l; loop_++) \ - func("%02x ", b[loop_]); \ - func("\n");\ -} - -/* - * USB commands - * (usb_control_msg() index parameter) - */ - -#define DEMOD 0x0000 -#define USB 0x0100 -#define SYS 0x0200 -#define I2C 0x0300 -#define I2C_DA 0x0600 - -#define CMD_WR_FLAG 0x0010 -#define CMD_DEMOD_RD 0x0000 -#define CMD_DEMOD_WR 0x0010 -#define CMD_USB_RD 0x0100 -#define CMD_USB_WR 0x0110 -#define CMD_SYS_RD 0x0200 -#define CMD_IR_RD 0x0201 -#define CMD_IR_WR 0x0211 -#define CMD_SYS_WR 0x0210 -#define CMD_I2C_RD 0x0300 -#define CMD_I2C_WR 0x0310 -#define CMD_I2C_DA_RD 0x0600 -#define CMD_I2C_DA_WR 0x0610 - - -struct rtl28xxu_priv { - u8 chip_id; - u8 tuner; - u8 page; /* integrated demod active register page */ - bool rc_active; -}; - -enum rtl28xxu_chip_id { - CHIP_ID_NONE, - CHIP_ID_RTL2831U, - CHIP_ID_RTL2832U, -}; - -enum rtl28xxu_tuner { - TUNER_NONE, - - TUNER_RTL2830_QT1010, - TUNER_RTL2830_MT2060, - TUNER_RTL2830_MXL5005S, - - TUNER_RTL2832_MT2266, - TUNER_RTL2832_FC2580, - TUNER_RTL2832_MT2063, - TUNER_RTL2832_MAX3543, - TUNER_RTL2832_TUA9001, - TUNER_RTL2832_MXL5007T, - TUNER_RTL2832_FC0012, - TUNER_RTL2832_E4000, - TUNER_RTL2832_TDA18272, - TUNER_RTL2832_FC0013, -}; - -struct rtl28xxu_req { - u16 value; - u16 index; - u16 size; - u8 *data; -}; - -struct rtl28xxu_reg_val { - u16 reg; - u8 val; -}; - -/* - * memory map - * - * 0x0000 DEMOD : demodulator - * 0x2000 USB : SIE, USB endpoint, debug, DMA - * 0x3000 SYS : system - * 0xfc00 RC : remote controller (not RTL2831U) - */ - -/* - * USB registers - */ -/* SIE Control Registers */ -#define USB_SYSCTL 0x2000 /* USB system control */ -#define USB_SYSCTL_0 0x2000 /* USB system control */ -#define USB_SYSCTL_1 0x2001 /* USB system control */ -#define USB_SYSCTL_2 0x2002 /* USB system control */ -#define USB_SYSCTL_3 0x2003 /* USB system control */ -#define USB_IRQSTAT 0x2008 /* SIE interrupt status */ -#define USB_IRQEN 0x200C /* SIE interrupt enable */ -#define USB_CTRL 0x2010 /* USB control */ -#define USB_STAT 0x2014 /* USB status */ -#define USB_DEVADDR 0x2018 /* USB device address */ -#define USB_TEST 0x201C /* USB test mode */ -#define USB_FRAME_NUMBER 0x2020 /* frame number */ -#define USB_FIFO_ADDR 0x2028 /* address of SIE FIFO RAM */ -#define USB_FIFO_CMD 0x202A /* SIE FIFO RAM access command */ -#define USB_FIFO_DATA 0x2030 /* SIE FIFO RAM data */ -/* Endpoint Registers */ -#define EP0_SETUPA 0x20F8 /* EP 0 setup packet lower byte */ -#define EP0_SETUPB 0x20FC /* EP 0 setup packet higher byte */ -#define USB_EP0_CFG 0x2104 /* EP 0 configure */ -#define USB_EP0_CTL 0x2108 /* EP 0 control */ -#define USB_EP0_STAT 0x210C /* EP 0 status */ -#define USB_EP0_IRQSTAT 0x2110 /* EP 0 interrupt status */ -#define USB_EP0_IRQEN 0x2114 /* EP 0 interrupt enable */ -#define USB_EP0_MAXPKT 0x2118 /* EP 0 max packet size */ -#define USB_EP0_BC 0x2120 /* EP 0 FIFO byte counter */ -#define USB_EPA_CFG 0x2144 /* EP A configure */ -#define USB_EPA_CFG_0 0x2144 /* EP A configure */ -#define USB_EPA_CFG_1 0x2145 /* EP A configure */ -#define USB_EPA_CFG_2 0x2146 /* EP A configure */ -#define USB_EPA_CFG_3 0x2147 /* EP A configure */ -#define USB_EPA_CTL 0x2148 /* EP A control */ -#define USB_EPA_CTL_0 0x2148 /* EP A control */ -#define USB_EPA_CTL_1 0x2149 /* EP A control */ -#define USB_EPA_CTL_2 0x214A /* EP A control */ -#define USB_EPA_CTL_3 0x214B /* EP A control */ -#define USB_EPA_STAT 0x214C /* EP A status */ -#define USB_EPA_IRQSTAT 0x2150 /* EP A interrupt status */ -#define USB_EPA_IRQEN 0x2154 /* EP A interrupt enable */ -#define USB_EPA_MAXPKT 0x2158 /* EP A max packet size */ -#define USB_EPA_MAXPKT_0 0x2158 /* EP A max packet size */ -#define USB_EPA_MAXPKT_1 0x2159 /* EP A max packet size */ -#define USB_EPA_MAXPKT_2 0x215A /* EP A max packet size */ -#define USB_EPA_MAXPKT_3 0x215B /* EP A max packet size */ -#define USB_EPA_FIFO_CFG 0x2160 /* EP A FIFO configure */ -#define USB_EPA_FIFO_CFG_0 0x2160 /* EP A FIFO configure */ -#define USB_EPA_FIFO_CFG_1 0x2161 /* EP A FIFO configure */ -#define USB_EPA_FIFO_CFG_2 0x2162 /* EP A FIFO configure */ -#define USB_EPA_FIFO_CFG_3 0x2163 /* EP A FIFO configure */ -/* Debug Registers */ -#define USB_PHYTSTDIS 0x2F04 /* PHY test disable */ -#define USB_TOUT_VAL 0x2F08 /* USB time-out time */ -#define USB_VDRCTRL 0x2F10 /* UTMI vendor signal control */ -#define USB_VSTAIN 0x2F14 /* UTMI vendor signal status in */ -#define USB_VLOADM 0x2F18 /* UTMI load vendor signal status in */ -#define USB_VSTAOUT 0x2F1C /* UTMI vendor signal status out */ -#define USB_UTMI_TST 0x2F80 /* UTMI test */ -#define USB_UTMI_STATUS 0x2F84 /* UTMI status */ -#define USB_TSTCTL 0x2F88 /* test control */ -#define USB_TSTCTL2 0x2F8C /* test control 2 */ -#define USB_PID_FORCE 0x2F90 /* force PID */ -#define USB_PKTERR_CNT 0x2F94 /* packet error counter */ -#define USB_RXERR_CNT 0x2F98 /* RX error counter */ -#define USB_MEM_BIST 0x2F9C /* MEM BIST test */ -#define USB_SLBBIST 0x2FA0 /* self-loop-back BIST */ -#define USB_CNTTEST 0x2FA4 /* counter test */ -#define USB_PHYTST 0x2FC0 /* USB PHY test */ -#define USB_DBGIDX 0x2FF0 /* select individual block debug signal */ -#define USB_DBGMUX 0x2FF4 /* debug signal module mux */ - -/* - * SYS registers - */ -/* demod control registers */ -#define SYS_SYS0 0x3000 /* include DEMOD_CTL, GPO, GPI, GPOE */ -#define SYS_DEMOD_CTL 0x3000 /* control register for DVB-T demodulator */ -/* GPIO registers */ -#define SYS_GPIO_OUT_VAL 0x3001 /* output value of GPIO */ -#define SYS_GPIO_IN_VAL 0x3002 /* input value of GPIO */ -#define SYS_GPIO_OUT_EN 0x3003 /* output enable of GPIO */ -#define SYS_SYS1 0x3004 /* include GPD, SYSINTE, SYSINTS, GP_CFG0 */ -#define SYS_GPIO_DIR 0x3004 /* direction control for GPIO */ -#define SYS_SYSINTE 0x3005 /* system interrupt enable */ -#define SYS_SYSINTS 0x3006 /* system interrupt status */ -#define SYS_GPIO_CFG0 0x3007 /* PAD configuration for GPIO0-GPIO3 */ -#define SYS_SYS2 0x3008 /* include GP_CFG1 and 3 reserved bytes */ -#define SYS_GPIO_CFG1 0x3008 /* PAD configuration for GPIO4 */ -#define SYS_DEMOD_CTL1 0x300B - -/* IrDA registers */ -#define SYS_IRRC_PSR 0x3020 /* IR protocol selection */ -#define SYS_IRRC_PER 0x3024 /* IR protocol extension */ -#define SYS_IRRC_SF 0x3028 /* IR sampling frequency */ -#define SYS_IRRC_DPIR 0x302C /* IR data package interval */ -#define SYS_IRRC_CR 0x3030 /* IR control */ -#define SYS_IRRC_RP 0x3034 /* IR read port */ -#define SYS_IRRC_SR 0x3038 /* IR status */ -/* I2C master registers */ -#define SYS_I2CCR 0x3040 /* I2C clock */ -#define SYS_I2CMCR 0x3044 /* I2C master control */ -#define SYS_I2CMSTR 0x3048 /* I2C master SCL timing */ -#define SYS_I2CMSR 0x304C /* I2C master status */ -#define SYS_I2CMFR 0x3050 /* I2C master FIFO */ - -/* - * IR registers - */ -#define IR_RX_BUF 0xFC00 -#define IR_RX_IE 0xFD00 -#define IR_RX_IF 0xFD01 -#define IR_RX_CTRL 0xFD02 -#define IR_RX_CFG 0xFD03 -#define IR_MAX_DURATION0 0xFD04 -#define IR_MAX_DURATION1 0xFD05 -#define IR_IDLE_LEN0 0xFD06 -#define IR_IDLE_LEN1 0xFD07 -#define IR_GLITCH_LEN 0xFD08 -#define IR_RX_BUF_CTRL 0xFD09 -#define IR_RX_BUF_DATA 0xFD0A -#define IR_RX_BC 0xFD0B -#define IR_RX_CLK 0xFD0C -#define IR_RX_C_COUNT_L 0xFD0D -#define IR_RX_C_COUNT_H 0xFD0E -#define IR_SUSPEND_CTRL 0xFD10 -#define IR_ERR_TOL_CTRL 0xFD11 -#define IR_UNIT_LEN 0xFD12 -#define IR_ERR_TOL_LEN 0xFD13 -#define IR_MAX_H_TOL_LEN 0xFD14 -#define IR_MAX_L_TOL_LEN 0xFD15 -#define IR_MASK_CTRL 0xFD16 -#define IR_MASK_DATA 0xFD17 -#define IR_RES_MASK_ADDR 0xFD18 -#define IR_RES_MASK_T_LEN 0xFD19 - -#endif diff --git a/drivers/media/dvb/dvb-usb/ttusb2.c b/drivers/media/dvb/dvb-usb/ttusb2.c deleted file mode 100644 index e53a1061cb8e..000000000000 --- a/drivers/media/dvb/dvb-usb/ttusb2.c +++ /dev/null @@ -1,820 +0,0 @@ -/* DVB USB compliant linux driver for Technotrend DVB USB boxes and clones - * (e.g. Pinnacle 400e DVB-S USB2.0). - * - * The Pinnacle 400e uses the same protocol as the Technotrend USB1.1 boxes. - * - * TDA8263 + TDA10086 - * - * I2C addresses: - * 0x08 - LNBP21PD - LNB power supply - * 0x0e - TDA10086 - Demodulator - * 0x50 - FX2 eeprom - * 0x60 - TDA8263 - Tuner - * 0x78 ??? - * - * Copyright (c) 2002 Holger Waechtler - * Copyright (c) 2003 Felix Domke - * Copyright (C) 2005-6 Patrick Boettcher - * - * 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, version 2. - * - * see Documentation/dvb/README.dvb-usb for more information - */ -#define DVB_USB_LOG_PREFIX "ttusb2" -#include "dvb-usb.h" - -#include "ttusb2.h" - -#include "tda826x.h" -#include "tda10086.h" -#include "tda1002x.h" -#include "tda10048.h" -#include "tda827x.h" -#include "lnbp21.h" -/* CA */ -#include "dvb_ca_en50221.h" - -/* debug */ -static int dvb_usb_ttusb2_debug; -#define deb_info(args...) dprintk(dvb_usb_ttusb2_debug,0x01,args) -module_param_named(debug,dvb_usb_ttusb2_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))." DVB_USB_DEBUG_STATUS); -static int dvb_usb_ttusb2_debug_ci; -module_param_named(debug_ci,dvb_usb_ttusb2_debug_ci, int, 0644); -MODULE_PARM_DESC(debug_ci, "set debugging ci." DVB_USB_DEBUG_STATUS); - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -#define ci_dbg(format, arg...) \ -do { \ - if (dvb_usb_ttusb2_debug_ci) \ - printk(KERN_DEBUG DVB_USB_LOG_PREFIX \ - ": %s " format "\n" , __func__, ## arg); \ -} while (0) - -enum { - TT3650_CMD_CI_TEST = 0x40, - TT3650_CMD_CI_RD_CTRL, - TT3650_CMD_CI_WR_CTRL, - TT3650_CMD_CI_RD_ATTR, - TT3650_CMD_CI_WR_ATTR, - TT3650_CMD_CI_RESET, - TT3650_CMD_CI_SET_VIDEO_PORT -}; - -struct ttusb2_state { - struct dvb_ca_en50221 ca; - struct mutex ca_mutex; - u8 id; - u16 last_rc_key; -}; - -static int ttusb2_msg(struct dvb_usb_device *d, u8 cmd, - u8 *wbuf, int wlen, u8 *rbuf, int rlen) -{ - struct ttusb2_state *st = d->priv; - u8 *s, *r = NULL; - int ret = 0; - - s = kzalloc(wlen+4, GFP_KERNEL); - if (!s) - return -ENOMEM; - - r = kzalloc(64, GFP_KERNEL); - if (!r) { - kfree(s); - return -ENOMEM; - } - - s[0] = 0xaa; - s[1] = ++st->id; - s[2] = cmd; - s[3] = wlen; - memcpy(&s[4],wbuf,wlen); - - ret = dvb_usb_generic_rw(d, s, wlen+4, r, 64, 0); - - if (ret != 0 || - r[0] != 0x55 || - r[1] != s[1] || - r[2] != cmd || - (rlen > 0 && r[3] != rlen)) { - warn("there might have been an error during control message transfer. (rlen = %d, was %d)",rlen,r[3]); - kfree(s); - kfree(r); - return -EIO; - } - - if (rlen > 0) - memcpy(rbuf, &r[4], rlen); - - kfree(s); - kfree(r); - - return 0; -} - -/* ci */ -static int tt3650_ci_msg(struct dvb_usb_device *d, u8 cmd, u8 *data, unsigned int write_len, unsigned int read_len) -{ - int ret; - u8 rx[60];/* (64 -4) */ - ret = ttusb2_msg(d, cmd, data, write_len, rx, read_len); - if (!ret) - memcpy(data, rx, read_len); - return ret; -} - -static int tt3650_ci_msg_locked(struct dvb_ca_en50221 *ca, u8 cmd, u8 *data, unsigned int write_len, unsigned int read_len) -{ - struct dvb_usb_device *d = ca->data; - struct ttusb2_state *state = d->priv; - int ret; - - mutex_lock(&state->ca_mutex); - ret = tt3650_ci_msg(d, cmd, data, write_len, read_len); - mutex_unlock(&state->ca_mutex); - - return ret; -} - -static int tt3650_ci_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address) -{ - u8 buf[3]; - int ret = 0; - - if (slot) - return -EINVAL; - - buf[0] = (address >> 8) & 0x0F; - buf[1] = address; - - - ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_RD_ATTR, buf, 2, 3); - - ci_dbg("%04x -> %d 0x%02x", address, ret, buf[2]); - - if (ret < 0) - return ret; - - return buf[2]; -} - -static int tt3650_ci_write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address, u8 value) -{ - u8 buf[3]; - - ci_dbg("%d 0x%04x 0x%02x", slot, address, value); - - if (slot) - return -EINVAL; - - buf[0] = (address >> 8) & 0x0F; - buf[1] = address; - buf[2] = value; - - return tt3650_ci_msg_locked(ca, TT3650_CMD_CI_WR_ATTR, buf, 3, 3); -} - -static int tt3650_ci_read_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address) -{ - u8 buf[2]; - int ret; - - if (slot) - return -EINVAL; - - buf[0] = address & 3; - - ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_RD_CTRL, buf, 1, 2); - - ci_dbg("0x%02x -> %d 0x%02x", address, ret, buf[1]); - - if (ret < 0) - return ret; - - return buf[1]; -} - -static int tt3650_ci_write_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address, u8 value) -{ - u8 buf[2]; - - ci_dbg("%d 0x%02x 0x%02x", slot, address, value); - - if (slot) - return -EINVAL; - - buf[0] = address; - buf[1] = value; - - return tt3650_ci_msg_locked(ca, TT3650_CMD_CI_WR_CTRL, buf, 2, 2); -} - -static int tt3650_ci_set_video_port(struct dvb_ca_en50221 *ca, int slot, int enable) -{ - u8 buf[1]; - int ret; - - ci_dbg("%d %d", slot, enable); - - if (slot) - return -EINVAL; - - buf[0] = enable; - - ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_SET_VIDEO_PORT, buf, 1, 1); - if (ret < 0) - return ret; - - if (enable != buf[0]) { - err("CI not %sabled.", enable ? "en" : "dis"); - return -EIO; - } - - return 0; -} - -static int tt3650_ci_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) -{ - return tt3650_ci_set_video_port(ca, slot, 0); -} - -static int tt3650_ci_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) -{ - return tt3650_ci_set_video_port(ca, slot, 1); -} - -static int tt3650_ci_slot_reset(struct dvb_ca_en50221 *ca, int slot) -{ - struct dvb_usb_device *d = ca->data; - struct ttusb2_state *state = d->priv; - u8 buf[1]; - int ret; - - ci_dbg("%d", slot); - - if (slot) - return -EINVAL; - - buf[0] = 0; - - mutex_lock(&state->ca_mutex); - - ret = tt3650_ci_msg(d, TT3650_CMD_CI_RESET, buf, 1, 1); - if (ret) - goto failed; - - msleep(500); - - buf[0] = 1; - - ret = tt3650_ci_msg(d, TT3650_CMD_CI_RESET, buf, 1, 1); - if (ret) - goto failed; - - msleep(500); - - buf[0] = 0; /* FTA */ - - ret = tt3650_ci_msg(d, TT3650_CMD_CI_SET_VIDEO_PORT, buf, 1, 1); - - msleep(1100); - - failed: - mutex_unlock(&state->ca_mutex); - - return ret; -} - -static int tt3650_ci_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) -{ - u8 buf[1]; - int ret; - - if (slot) - return -EINVAL; - - ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_TEST, buf, 0, 1); - if (ret) - return ret; - - if (1 == buf[0]) { - return DVB_CA_EN50221_POLL_CAM_PRESENT | - DVB_CA_EN50221_POLL_CAM_READY; - } - return 0; -} - -static void tt3650_ci_uninit(struct dvb_usb_device *d) -{ - struct ttusb2_state *state; - - ci_dbg(""); - - if (NULL == d) - return; - - state = d->priv; - if (NULL == state) - return; - - if (NULL == state->ca.data) - return; - - dvb_ca_en50221_release(&state->ca); - - memset(&state->ca, 0, sizeof(state->ca)); -} - -static int tt3650_ci_init(struct dvb_usb_adapter *a) -{ - struct dvb_usb_device *d = a->dev; - struct ttusb2_state *state = d->priv; - int ret; - - ci_dbg(""); - - mutex_init(&state->ca_mutex); - - state->ca.owner = THIS_MODULE; - state->ca.read_attribute_mem = tt3650_ci_read_attribute_mem; - state->ca.write_attribute_mem = tt3650_ci_write_attribute_mem; - state->ca.read_cam_control = tt3650_ci_read_cam_control; - state->ca.write_cam_control = tt3650_ci_write_cam_control; - state->ca.slot_reset = tt3650_ci_slot_reset; - state->ca.slot_shutdown = tt3650_ci_slot_shutdown; - state->ca.slot_ts_enable = tt3650_ci_slot_ts_enable; - state->ca.poll_slot_status = tt3650_ci_poll_slot_status; - state->ca.data = d; - - ret = dvb_ca_en50221_init(&a->dvb_adap, - &state->ca, - /* flags */ 0, - /* n_slots */ 1); - if (ret) { - err("Cannot initialize CI: Error %d.", ret); - memset(&state->ca, 0, sizeof(state->ca)); - return ret; - } - - info("CI initialized."); - - return 0; -} - -static int ttusb2_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg msg[],int num) -{ - struct dvb_usb_device *d = i2c_get_adapdata(adap); - static u8 obuf[60], ibuf[60]; - int i, write_read, read; - - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - - if (num > 2) - warn("more than 2 i2c messages at a time is not handled yet. TODO."); - - for (i = 0; i < num; i++) { - write_read = i+1 < num && (msg[i+1].flags & I2C_M_RD); - read = msg[i].flags & I2C_M_RD; - - obuf[0] = (msg[i].addr << 1) | (write_read | read); - if (read) - obuf[1] = 0; - else - obuf[1] = msg[i].len; - - /* read request */ - if (write_read) - obuf[2] = msg[i+1].len; - else if (read) - obuf[2] = msg[i].len; - else - obuf[2] = 0; - - memcpy(&obuf[3], msg[i].buf, msg[i].len); - - if (ttusb2_msg(d, CMD_I2C_XFER, obuf, obuf[1]+3, ibuf, obuf[2] + 3) < 0) { - err("i2c transfer failed."); - break; - } - - if (write_read) { - memcpy(msg[i+1].buf, &ibuf[3], msg[i+1].len); - i++; - } else if (read) - memcpy(msg[i].buf, &ibuf[3], msg[i].len); - } - - mutex_unlock(&d->i2c_mutex); - return i; -} - -static u32 ttusb2_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static struct i2c_algorithm ttusb2_i2c_algo = { - .master_xfer = ttusb2_i2c_xfer, - .functionality = ttusb2_i2c_func, -}; - -/* command to poll IR receiver (copied from pctv452e.c) */ -#define CMD_GET_IR_CODE 0x1b - -/* IR */ -static int tt3650_rc_query(struct dvb_usb_device *d) -{ - int ret; - u8 rx[9]; /* A CMD_GET_IR_CODE reply is 9 bytes long */ - struct ttusb2_state *st = d->priv; - ret = ttusb2_msg(d, CMD_GET_IR_CODE, NULL, 0, rx, sizeof(rx)); - if (ret != 0) - return ret; - - if (rx[8] & 0x01) { - /* got a "press" event */ - st->last_rc_key = (rx[3] << 8) | rx[2]; - deb_info("%s: cmd=0x%02x sys=0x%02x\n", __func__, rx[2], rx[3]); - rc_keydown(d->rc_dev, st->last_rc_key, 0); - } else if (st->last_rc_key) { - rc_keyup(d->rc_dev); - st->last_rc_key = 0; - } - - return 0; -} - - -/* Callbacks for DVB USB */ -static int ttusb2_identify_state (struct usb_device *udev, struct - dvb_usb_device_properties *props, struct dvb_usb_device_description **desc, - int *cold) -{ - *cold = udev->descriptor.iManufacturer == 0 && udev->descriptor.iProduct == 0; - return 0; -} - -static int ttusb2_power_ctrl(struct dvb_usb_device *d, int onoff) -{ - u8 b = onoff; - ttusb2_msg(d, CMD_POWER, &b, 0, NULL, 0); - return ttusb2_msg(d, CMD_POWER, &b, 1, NULL, 0); -} - - -static struct tda10086_config tda10086_config = { - .demod_address = 0x0e, - .invert = 0, - .diseqc_tone = 1, - .xtal_freq = TDA10086_XTAL_16M, -}; - -static struct tda10023_config tda10023_config = { - .demod_address = 0x0c, - .invert = 0, - .xtal = 16000000, - .pll_m = 11, - .pll_p = 3, - .pll_n = 1, - .deltaf = 0xa511, -}; - -static struct tda10048_config tda10048_config = { - .demod_address = 0x10 >> 1, - .output_mode = TDA10048_PARALLEL_OUTPUT, - .inversion = TDA10048_INVERSION_ON, - .dtv6_if_freq_khz = TDA10048_IF_4000, - .dtv7_if_freq_khz = TDA10048_IF_4500, - .dtv8_if_freq_khz = TDA10048_IF_5000, - .clk_freq_khz = TDA10048_CLK_16000, - .no_firmware = 1, - .set_pll = true , - .pll_m = 5, - .pll_n = 3, - .pll_p = 0, -}; - -static struct tda827x_config tda827x_config = { - .config = 0, -}; - -static int ttusb2_frontend_tda10086_attach(struct dvb_usb_adapter *adap) -{ - if (usb_set_interface(adap->dev->udev,0,3) < 0) - err("set interface to alts=3 failed"); - - if ((adap->fe_adap[0].fe = dvb_attach(tda10086_attach, &tda10086_config, &adap->dev->i2c_adap)) == NULL) { - deb_info("TDA10086 attach failed\n"); - return -ENODEV; - } - - return 0; -} - -static int ttusb2_ct3650_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct dvb_usb_adapter *adap = fe->dvb->priv; - - return adap->fe_adap[0].fe->ops.i2c_gate_ctrl(adap->fe_adap[0].fe, enable); -} - -static int ttusb2_frontend_tda10023_attach(struct dvb_usb_adapter *adap) -{ - if (usb_set_interface(adap->dev->udev, 0, 3) < 0) - err("set interface to alts=3 failed"); - - if (adap->fe_adap[0].fe == NULL) { - /* FE 0 DVB-C */ - adap->fe_adap[0].fe = dvb_attach(tda10023_attach, - &tda10023_config, &adap->dev->i2c_adap, 0x48); - - if (adap->fe_adap[0].fe == NULL) { - deb_info("TDA10023 attach failed\n"); - return -ENODEV; - } - tt3650_ci_init(adap); - } else { - adap->fe_adap[1].fe = dvb_attach(tda10048_attach, - &tda10048_config, &adap->dev->i2c_adap); - - if (adap->fe_adap[1].fe == NULL) { - deb_info("TDA10048 attach failed\n"); - return -ENODEV; - } - - /* tuner is behind TDA10023 I2C-gate */ - adap->fe_adap[1].fe->ops.i2c_gate_ctrl = ttusb2_ct3650_i2c_gate_ctrl; - - } - - return 0; -} - -static int ttusb2_tuner_tda827x_attach(struct dvb_usb_adapter *adap) -{ - struct dvb_frontend *fe; - - /* MFE: select correct FE to attach tuner since that's called twice */ - if (adap->fe_adap[1].fe == NULL) - fe = adap->fe_adap[0].fe; - else - fe = adap->fe_adap[1].fe; - - /* attach tuner */ - if (dvb_attach(tda827x_attach, fe, 0x61, &adap->dev->i2c_adap, &tda827x_config) == NULL) { - printk(KERN_ERR "%s: No tda827x found!\n", __func__); - return -ENODEV; - } - return 0; -} - -static int ttusb2_tuner_tda826x_attach(struct dvb_usb_adapter *adap) -{ - if (dvb_attach(tda826x_attach, adap->fe_adap[0].fe, 0x60, &adap->dev->i2c_adap, 0) == NULL) { - deb_info("TDA8263 attach failed\n"); - return -ENODEV; - } - - if (dvb_attach(lnbp21_attach, adap->fe_adap[0].fe, &adap->dev->i2c_adap, 0, 0) == NULL) { - deb_info("LNBP21 attach failed\n"); - return -ENODEV; - } - return 0; -} - -/* DVB USB Driver stuff */ -static struct dvb_usb_device_properties ttusb2_properties; -static struct dvb_usb_device_properties ttusb2_properties_s2400; -static struct dvb_usb_device_properties ttusb2_properties_ct3650; - -static void ttusb2_usb_disconnect(struct usb_interface *intf) -{ - struct dvb_usb_device *d = usb_get_intfdata(intf); - - tt3650_ci_uninit(d); - dvb_usb_device_exit(intf); -} - -static int ttusb2_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - if (0 == dvb_usb_device_init(intf, &ttusb2_properties, - THIS_MODULE, NULL, adapter_nr) || - 0 == dvb_usb_device_init(intf, &ttusb2_properties_s2400, - THIS_MODULE, NULL, adapter_nr) || - 0 == dvb_usb_device_init(intf, &ttusb2_properties_ct3650, - THIS_MODULE, NULL, adapter_nr)) - return 0; - return -ENODEV; -} - -static struct usb_device_id ttusb2_table [] = { - { USB_DEVICE(USB_VID_PINNACLE, USB_PID_PCTV_400E) }, - { USB_DEVICE(USB_VID_PINNACLE, USB_PID_PCTV_450E) }, - { USB_DEVICE(USB_VID_TECHNOTREND, - USB_PID_TECHNOTREND_CONNECT_S2400) }, - { USB_DEVICE(USB_VID_TECHNOTREND, - USB_PID_TECHNOTREND_CONNECT_CT3650) }, - {} /* Terminating entry */ -}; -MODULE_DEVICE_TABLE (usb, ttusb2_table); - -static struct dvb_usb_device_properties ttusb2_properties = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = CYPRESS_FX2, - .firmware = "dvb-usb-pctv-400e-01.fw", - - .size_of_priv = sizeof(struct ttusb2_state), - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = {{ - .streaming_ctrl = NULL, // ttusb2_streaming_ctrl, - - .frontend_attach = ttusb2_frontend_tda10086_attach, - .tuner_attach = ttusb2_tuner_tda826x_attach, - - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_ISOC, - .count = 5, - .endpoint = 0x02, - .u = { - .isoc = { - .framesperurb = 4, - .framesize = 940, - .interval = 1, - } - } - } - }}, - } - }, - - .power_ctrl = ttusb2_power_ctrl, - .identify_state = ttusb2_identify_state, - - .i2c_algo = &ttusb2_i2c_algo, - - .generic_bulk_ctrl_endpoint = 0x01, - - .num_device_descs = 2, - .devices = { - { "Pinnacle 400e DVB-S USB2.0", - { &ttusb2_table[0], NULL }, - { NULL }, - }, - { "Pinnacle 450e DVB-S USB2.0", - { &ttusb2_table[1], NULL }, - { NULL }, - }, - } -}; - -static struct dvb_usb_device_properties ttusb2_properties_s2400 = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = CYPRESS_FX2, - .firmware = "dvb-usb-tt-s2400-01.fw", - - .size_of_priv = sizeof(struct ttusb2_state), - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 1, - .fe = {{ - .streaming_ctrl = NULL, - - .frontend_attach = ttusb2_frontend_tda10086_attach, - .tuner_attach = ttusb2_tuner_tda826x_attach, - - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_ISOC, - .count = 5, - .endpoint = 0x02, - .u = { - .isoc = { - .framesperurb = 4, - .framesize = 940, - .interval = 1, - } - } - } - }}, - } - }, - - .power_ctrl = ttusb2_power_ctrl, - .identify_state = ttusb2_identify_state, - - .i2c_algo = &ttusb2_i2c_algo, - - .generic_bulk_ctrl_endpoint = 0x01, - - .num_device_descs = 1, - .devices = { - { "Technotrend TT-connect S-2400", - { &ttusb2_table[2], NULL }, - { NULL }, - }, - } -}; - -static struct dvb_usb_device_properties ttusb2_properties_ct3650 = { - .caps = DVB_USB_IS_AN_I2C_ADAPTER, - - .usb_ctrl = CYPRESS_FX2, - - .size_of_priv = sizeof(struct ttusb2_state), - - .rc.core = { - .rc_interval = 150, /* Less than IR_KEYPRESS_TIMEOUT */ - .rc_codes = RC_MAP_TT_1500, - .rc_query = tt3650_rc_query, - .allowed_protos = RC_TYPE_UNKNOWN, - }, - - .num_adapters = 1, - .adapter = { - { - .num_frontends = 2, - .fe = {{ - .streaming_ctrl = NULL, - - .frontend_attach = ttusb2_frontend_tda10023_attach, - .tuner_attach = ttusb2_tuner_tda827x_attach, - - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_ISOC, - .count = 5, - .endpoint = 0x02, - .u = { - .isoc = { - .framesperurb = 4, - .framesize = 940, - .interval = 1, - } - } - } - }, { - .streaming_ctrl = NULL, - - .frontend_attach = ttusb2_frontend_tda10023_attach, - .tuner_attach = ttusb2_tuner_tda827x_attach, - - /* parameter for the MPEG2-data transfer */ - .stream = { - .type = USB_ISOC, - .count = 5, - .endpoint = 0x02, - .u = { - .isoc = { - .framesperurb = 4, - .framesize = 940, - .interval = 1, - } - } - } - }}, - }, - }, - - .power_ctrl = ttusb2_power_ctrl, - .identify_state = ttusb2_identify_state, - - .i2c_algo = &ttusb2_i2c_algo, - - .generic_bulk_ctrl_endpoint = 0x01, - - .num_device_descs = 1, - .devices = { - { "Technotrend TT-connect CT-3650", - .warm_ids = { &ttusb2_table[3], NULL }, - }, - } -}; - -static struct usb_driver ttusb2_driver = { - .name = "dvb_usb_ttusb2", - .probe = ttusb2_probe, - .disconnect = ttusb2_usb_disconnect, - .id_table = ttusb2_table, -}; - -module_usb_driver(ttusb2_driver); - -MODULE_AUTHOR("Patrick Boettcher "); -MODULE_DESCRIPTION("Driver for Pinnacle PCTV 400e DVB-S USB2.0"); -MODULE_VERSION("1.0"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/firewire/Makefile b/drivers/media/dvb/firewire/Makefile deleted file mode 100644 index 357b3aab186b..000000000000 --- a/drivers/media/dvb/firewire/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -obj-$(CONFIG_DVB_FIREDTV) += firedtv.o - -firedtv-y := firedtv-avc.o firedtv-ci.o firedtv-dvb.o firedtv-fe.o firedtv-fw.o -firedtv-$(CONFIG_DVB_FIREDTV_INPUT) += firedtv-rc.o - -ccflags-y += -Idrivers/media/dvb/dvb-core diff --git a/drivers/media/dvb/frontends/Kconfig b/drivers/media/dvb/frontends/Kconfig deleted file mode 100644 index a08c2152d0ee..000000000000 --- a/drivers/media/dvb/frontends/Kconfig +++ /dev/null @@ -1,756 +0,0 @@ -config DVB_FE_CUSTOMISE - bool "Customise the frontend modules to build" - depends on DVB_CORE - depends on EXPERT - default y if EXPERT - help - This allows the user to select/deselect frontend drivers for their - hardware from the build. - - Use this option with care as deselecting frontends which are in fact - necessary will result in DVB devices which cannot be tuned due to lack - of driver support. - - If unsure say N. - -menu "Customise DVB Frontends" - visible if DVB_FE_CUSTOMISE - -comment "Multistandard (satellite) frontends" - depends on DVB_CORE - -config DVB_STB0899 - tristate "STB0899 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S/S2/DSS Multistandard demodulator. Say Y when you want - to support this demodulator based frontends - -config DVB_STB6100 - tristate "STB6100 based tuners" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A Silicon tuner from ST used in conjunction with the STB0899 - demodulator. Say Y when you want to support this tuner. - -config DVB_STV090x - tristate "STV0900/STV0903(A/B) based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - DVB-S/S2/DSS Multistandard Professional/Broadcast demodulators. - Say Y when you want to support these frontends. - -config DVB_STV6110x - tristate "STV6110/(A) based tuners" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A Silicon tuner that supports DVB-S and DVB-S2 modes - -comment "Multistandard (cable + terrestrial) frontends" - depends on DVB_CORE - -config DVB_DRXK - tristate "Micronas DRXK based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - Micronas DRX-K DVB-C/T demodulator. - - Say Y when you want to support this frontend. - -config DVB_TDA18271C2DD - tristate "NXP TDA18271C2 silicon tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - NXP TDA18271 silicon tuner. - - Say Y when you want to support this tuner. - -comment "DVB-S (satellite) frontends" - depends on DVB_CORE - -config DVB_CX24110 - tristate "Conexant CX24110 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_CX24123 - tristate "Conexant CX24123 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_MT312 - tristate "Zarlink VP310/MT312/ZL10313 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_ZL10036 - tristate "Zarlink ZL10036 silicon tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_ZL10039 - tristate "Zarlink ZL10039 silicon tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_S5H1420 - tristate "Samsung S5H1420 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_STV0288 - tristate "ST STV0288 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_STB6000 - tristate "ST STB6000 silicon tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S silicon tuner module. Say Y when you want to support this tuner. - -config DVB_STV0299 - tristate "ST STV0299 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_STV6110 - tristate "ST STV6110 silicon tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S silicon tuner module. Say Y when you want to support this tuner. - -config DVB_STV0900 - tristate "ST STV0900 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S/S2 demodulator. Say Y when you want to support this frontend. - -config DVB_TDA8083 - tristate "Philips TDA8083 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_TDA10086 - tristate "Philips TDA10086 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_TDA8261 - tristate "Philips TDA8261 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_VES1X93 - tristate "VLSI VES1893 or VES1993 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_TUNER_ITD1000 - tristate "Integrant ITD1000 Zero IF tuner for DVB-S/DSS" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_TUNER_CX24113 - tristate "Conexant CX24113/CX24128 tuner for DVB-S/DSS" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - - -config DVB_TDA826X - tristate "Philips TDA826X silicon tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S silicon tuner module. Say Y when you want to support this tuner. - -config DVB_TUA6100 - tristate "Infineon TUA6100 PLL" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S PLL chip. - -config DVB_CX24116 - tristate "Conexant CX24116 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S/S2 tuner module. Say Y when you want to support this frontend. - -config DVB_SI21XX - tristate "Silicon Labs SI21XX based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_DS3000 - tristate "Montage Tehnology DS3000 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S/S2 tuner module. Say Y when you want to support this frontend. - -config DVB_MB86A16 - tristate "Fujitsu MB86A16 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S/DSS Direct Conversion reveiver. - Say Y when you want to support this frontend. - -config DVB_TDA10071 - tristate "NXP TDA10071" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - Say Y when you want to support this frontend. - -comment "DVB-T (terrestrial) frontends" - depends on DVB_CORE - -config DVB_SP8870 - tristate "Spase sp8870 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - - This driver needs external firmware. Please use the command - "/Documentation/dvb/get_dvb_firmware sp8870" to - download/extract it, and then copy it to /usr/lib/hotplug/firmware - or /lib/firmware (depending on configuration of firmware hotplug). - -config DVB_SP887X - tristate "Spase sp887x based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - - This driver needs external firmware. Please use the command - "/Documentation/dvb/get_dvb_firmware sp887x" to - download/extract it, and then copy it to /usr/lib/hotplug/firmware - or /lib/firmware (depending on configuration of firmware hotplug). - -config DVB_CX22700 - tristate "Conexant CX22700 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - -config DVB_CX22702 - tristate "Conexant cx22702 demodulator (OFDM)" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - -config DVB_S5H1432 - tristate "Samsung s5h1432 demodulator (OFDM)" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - -config DVB_DRXD - tristate "Micronas DRXD driver" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - - Note: this driver was based on vendor driver reference code (released - under the GPL) as opposed to the existing drx397xd driver, which - was written via reverse engineering. - -config DVB_L64781 - tristate "LSI L64781" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - -config DVB_TDA1004X - tristate "Philips TDA10045H/TDA10046H based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - - This driver needs external firmware. Please use the commands - "/Documentation/dvb/get_dvb_firmware tda10045", - "/Documentation/dvb/get_dvb_firmware tda10046" to - download/extract them, and then copy them to /usr/lib/hotplug/firmware - or /lib/firmware (depending on configuration of firmware hotplug). - -config DVB_NXT6000 - tristate "NxtWave Communications NXT6000 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - -config DVB_MT352 - tristate "Zarlink MT352 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - -config DVB_ZL10353 - tristate "Zarlink ZL10353 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - -config DVB_DIB3000MB - tristate "DiBcom 3000M-B" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Designed for mobile usage. Say Y when you want - to support this frontend. - -config DVB_DIB3000MC - tristate "DiBcom 3000P/M-C" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Designed for mobile usage. Say Y when you want - to support this frontend. - -config DVB_DIB7000M - tristate "DiBcom 7000MA/MB/PA/PB/MC" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Designed for mobile usage. Say Y when you want - to support this frontend. - -config DVB_DIB7000P - tristate "DiBcom 7000PC" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Designed for mobile usage. Say Y when you want - to support this frontend. - -config DVB_DIB9000 - tristate "DiBcom 9000" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Designed for mobile usage. Say Y when you want - to support this frontend. - -config DVB_TDA10048 - tristate "Philips TDA10048HN based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. Say Y when you want to support this frontend. - -config DVB_AF9013 - tristate "Afatech AF9013 demodulator" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - Say Y when you want to support this frontend. - -config DVB_EC100 - tristate "E3C EC100" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - Say Y when you want to support this frontend. - -config DVB_HD29L2 - tristate "HDIC HD29L2" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - Say Y when you want to support this frontend. - -config DVB_STV0367 - tristate "ST STV0367 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T/C tuner module. Say Y when you want to support this frontend. - -config DVB_CXD2820R - tristate "Sony CXD2820R" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - Say Y when you want to support this frontend. - -config DVB_RTL2830 - tristate "Realtek RTL2830 DVB-T" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - Say Y when you want to support this frontend. - -config DVB_RTL2832 - tristate "Realtek RTL2832 DVB-T" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - Say Y when you want to support this frontend. - -comment "DVB-C (cable) frontends" - depends on DVB_CORE - -config DVB_VES1820 - tristate "VLSI VES1820 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-C tuner module. Say Y when you want to support this frontend. - -config DVB_TDA10021 - tristate "Philips TDA10021 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-C tuner module. Say Y when you want to support this frontend. - -config DVB_TDA10023 - tristate "Philips TDA10023 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-C tuner module. Say Y when you want to support this frontend. - -config DVB_STV0297 - tristate "ST STV0297 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-C tuner module. Say Y when you want to support this frontend. - -comment "ATSC (North American/Korean Terrestrial/Cable DTV) frontends" - depends on DVB_CORE - -config DVB_NXT200X - tristate "NxtWave Communications NXT2002/NXT2004 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want - to support this frontend. - - This driver needs external firmware. Please use the commands - "/Documentation/dvb/get_dvb_firmware nxt2002" and - "/Documentation/dvb/get_dvb_firmware nxt2004" to - download/extract them, and then copy them to /usr/lib/hotplug/firmware - or /lib/firmware (depending on configuration of firmware hotplug). - -config DVB_OR51211 - tristate "Oren OR51211 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB tuner module. Say Y when you want to support this frontend. - - This driver needs external firmware. Please use the command - "/Documentation/dvb/get_dvb_firmware or51211" to - download it, and then copy it to /usr/lib/hotplug/firmware - or /lib/firmware (depending on configuration of firmware hotplug). - -config DVB_OR51132 - tristate "Oren OR51132 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want - to support this frontend. - - This driver needs external firmware. Please use the commands - "/Documentation/dvb/get_dvb_firmware or51132_vsb" and/or - "/Documentation/dvb/get_dvb_firmware or51132_qam" to - download firmwares for 8VSB and QAM64/256, respectively. Copy them to - /usr/lib/hotplug/firmware or /lib/firmware (depending on - configuration of firmware hotplug). - -config DVB_BCM3510 - tristate "Broadcom BCM3510" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB/16VSB and QAM64/256 tuner module. Say Y when you want to - support this frontend. - -config DVB_LGDT330X - tristate "LG Electronics LGDT3302/LGDT3303 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want - to support this frontend. - -config DVB_LGDT3305 - tristate "LG Electronics LGDT3304 and LGDT3305 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want - to support this frontend. - -config DVB_LG2160 - tristate "LG Electronics LG216x based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An ATSC/MH demodulator module. Say Y when you want - to support this frontend. - -config DVB_S5H1409 - tristate "Samsung S5H1409 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want - to support this frontend. - -config DVB_AU8522 - depends on I2C - tristate - -config DVB_AU8522_DTV - tristate "Auvitek AU8522 based DTV demod" - depends on DVB_CORE && I2C - select DVB_AU8522 - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB, QAM64/256 & NTSC demodulator module. Say Y when - you want to enable DTV demodulation support for this frontend. - -config DVB_AU8522_V4L - tristate "Auvitek AU8522 based ATV demod" - depends on VIDEO_V4L2 && I2C - select DVB_AU8522 - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB, QAM64/256 & NTSC demodulator module. Say Y when - you want to enable ATV demodulation support for this frontend. - -config DVB_S5H1411 - tristate "Samsung S5H1411 based" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want - to support this frontend. - -comment "ISDB-T (terrestrial) frontends" - depends on DVB_CORE - -config DVB_S921 - tristate "Sharp S921 frontend" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - AN ISDB-T DQPSK, QPSK, 16QAM and 64QAM 1seg tuner module. - Say Y when you want to support this frontend. - -config DVB_DIB8000 - tristate "DiBcom 8000MB/MC" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A driver for DiBcom's DiB8000 ISDB-T/ISDB-Tsb demodulator. - Say Y when you want to support this frontend. - -config DVB_MB86A20S - tristate "Fujitsu mb86a20s" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator. - Say Y when you want to support this frontend. - -comment "Digital terrestrial only tuners/PLL" - depends on DVB_CORE - -config DVB_PLL - tristate "Generic I2C PLL based tuners" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - This module drives a number of tuners based on PLL chips with a - common I2C interface. Say Y when you want to support these tuners. - -config DVB_TUNER_DIB0070 - tristate "DiBcom DiB0070 silicon base-band tuner" - depends on I2C - default m if DVB_FE_CUSTOMISE - help - A driver for the silicon baseband tuner DiB0070 from DiBcom. - This device is only used inside a SiP called together with a - demodulator for now. - -config DVB_TUNER_DIB0090 - tristate "DiBcom DiB0090 silicon base-band tuner" - depends on I2C - default m if DVB_FE_CUSTOMISE - help - A driver for the silicon baseband tuner DiB0090 from DiBcom. - This device is only used inside a SiP called together with a - demodulator for now. - -comment "SEC control devices for DVB-S" - depends on DVB_CORE - -config DVB_LNBP21 - tristate "LNBP21/LNBH24 SEC controllers" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An SEC control chips. - -config DVB_LNBP22 - tristate "LNBP22 SEC controllers" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - LNB power supply and control voltage - regulator chip with step-up converter - and I2C interface. - Say Y when you want to support this chip. - -config DVB_ISL6405 - tristate "ISL6405 SEC controller" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An SEC control chip. - -config DVB_ISL6421 - tristate "ISL6421 SEC controller" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - An SEC control chip. - -config DVB_ISL6423 - tristate "ISL6423 SEC controller" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A SEC controller chip from Intersil - -config DVB_A8293 - tristate "Allegro A8293" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - -config DVB_LGS8GL5 - tristate "Silicon Legend LGS-8GL5 demodulator (OFDM)" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DMB-TH tuner module. Say Y when you want to support this frontend. - -config DVB_LGS8GXX - tristate "Legend Silicon LGS8913/LGS8GL5/LGS8GXX DMB-TH demodulator" - depends on DVB_CORE && I2C - select FW_LOADER - default m if DVB_FE_CUSTOMISE - help - A DMB-TH tuner module. Say Y when you want to support this frontend. - -config DVB_ATBM8830 - tristate "AltoBeam ATBM8830/8831 DMB-TH demodulator" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DMB-TH tuner module. Say Y when you want to support this frontend. - -config DVB_TDA665x - tristate "TDA665x tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - Support for tuner modules based on Philips TDA6650/TDA6651 chips. - Say Y when you want to support this chip. - - Currently supported tuners: - * Panasonic ENV57H12D5 (ET-50DT) - -config DVB_IX2505V - tristate "Sharp IX2505V silicon tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. Say Y when you want to support this frontend. - -config DVB_IT913X_FE - tristate "it913x frontend and it9137 tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-T tuner module. - Say Y when you want to support this frontend. - -config DVB_M88RS2000 - tristate "M88RS2000 DVB-S demodulator and tuner" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - help - A DVB-S tuner module. - Say Y when you want to support this frontend. - -config DVB_AF9033 - tristate "Afatech AF9033 DVB-T demodulator" - depends on DVB_CORE && I2C - default m if DVB_FE_CUSTOMISE - -comment "Tools to develop new frontends" - -config DVB_DUMMY_FE - tristate "Dummy frontend driver" - default n -endmenu diff --git a/drivers/media/dvb/frontends/Makefile b/drivers/media/dvb/frontends/Makefile deleted file mode 100644 index 185bb8b51952..000000000000 --- a/drivers/media/dvb/frontends/Makefile +++ /dev/null @@ -1,105 +0,0 @@ -# -# Makefile for the kernel DVB frontend device drivers. -# - -ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core/ -ccflags-y += -I$(srctree)/drivers/media/common/tuners/ - -stb0899-objs = stb0899_drv.o stb0899_algo.o -stv0900-objs = stv0900_core.o stv0900_sw.o -drxd-objs = drxd_firm.o drxd_hard.o -cxd2820r-objs = cxd2820r_core.o cxd2820r_c.o cxd2820r_t.o cxd2820r_t2.o -drxk-objs := drxk_hard.o - -obj-$(CONFIG_DVB_PLL) += dvb-pll.o -obj-$(CONFIG_DVB_STV0299) += stv0299.o -obj-$(CONFIG_DVB_STB0899) += stb0899.o -obj-$(CONFIG_DVB_STB6100) += stb6100.o -obj-$(CONFIG_DVB_SP8870) += sp8870.o -obj-$(CONFIG_DVB_CX22700) += cx22700.o -obj-$(CONFIG_DVB_S5H1432) += s5h1432.o -obj-$(CONFIG_DVB_CX24110) += cx24110.o -obj-$(CONFIG_DVB_TDA8083) += tda8083.o -obj-$(CONFIG_DVB_L64781) += l64781.o -obj-$(CONFIG_DVB_DIB3000MB) += dib3000mb.o -obj-$(CONFIG_DVB_DIB3000MC) += dib3000mc.o dibx000_common.o -obj-$(CONFIG_DVB_DIB7000M) += dib7000m.o dibx000_common.o -obj-$(CONFIG_DVB_DIB7000P) += dib7000p.o dibx000_common.o -obj-$(CONFIG_DVB_DIB8000) += dib8000.o dibx000_common.o -obj-$(CONFIG_DVB_DIB9000) += dib9000.o dibx000_common.o -obj-$(CONFIG_DVB_MT312) += mt312.o -obj-$(CONFIG_DVB_VES1820) += ves1820.o -obj-$(CONFIG_DVB_VES1X93) += ves1x93.o -obj-$(CONFIG_DVB_TDA1004X) += tda1004x.o -obj-$(CONFIG_DVB_SP887X) += sp887x.o -obj-$(CONFIG_DVB_NXT6000) += nxt6000.o -obj-$(CONFIG_DVB_MT352) += mt352.o -obj-$(CONFIG_DVB_ZL10036) += zl10036.o -obj-$(CONFIG_DVB_ZL10039) += zl10039.o -obj-$(CONFIG_DVB_ZL10353) += zl10353.o -obj-$(CONFIG_DVB_CX22702) += cx22702.o -obj-$(CONFIG_DVB_DRXD) += drxd.o -obj-$(CONFIG_DVB_TDA10021) += tda10021.o -obj-$(CONFIG_DVB_TDA10023) += tda10023.o -obj-$(CONFIG_DVB_STV0297) += stv0297.o -obj-$(CONFIG_DVB_NXT200X) += nxt200x.o -obj-$(CONFIG_DVB_OR51211) += or51211.o -obj-$(CONFIG_DVB_OR51132) += or51132.o -obj-$(CONFIG_DVB_BCM3510) += bcm3510.o -obj-$(CONFIG_DVB_S5H1420) += s5h1420.o -obj-$(CONFIG_DVB_LGDT330X) += lgdt330x.o -obj-$(CONFIG_DVB_LGDT3305) += lgdt3305.o -obj-$(CONFIG_DVB_LG2160) += lg2160.o -obj-$(CONFIG_DVB_CX24123) += cx24123.o -obj-$(CONFIG_DVB_LNBP21) += lnbp21.o -obj-$(CONFIG_DVB_LNBP22) += lnbp22.o -obj-$(CONFIG_DVB_ISL6405) += isl6405.o -obj-$(CONFIG_DVB_ISL6421) += isl6421.o -obj-$(CONFIG_DVB_TDA10086) += tda10086.o -obj-$(CONFIG_DVB_TDA826X) += tda826x.o -obj-$(CONFIG_DVB_TDA8261) += tda8261.o -obj-$(CONFIG_DVB_TUNER_DIB0070) += dib0070.o -obj-$(CONFIG_DVB_TUNER_DIB0090) += dib0090.o -obj-$(CONFIG_DVB_TUA6100) += tua6100.o -obj-$(CONFIG_DVB_S5H1409) += s5h1409.o -obj-$(CONFIG_DVB_TUNER_ITD1000) += itd1000.o -obj-$(CONFIG_DVB_AU8522) += au8522_common.o -obj-$(CONFIG_DVB_AU8522_DTV) += au8522_dig.o -obj-$(CONFIG_DVB_AU8522_V4L) += au8522_decoder.o -obj-$(CONFIG_DVB_TDA10048) += tda10048.o -obj-$(CONFIG_DVB_TUNER_CX24113) += cx24113.o -obj-$(CONFIG_DVB_S5H1411) += s5h1411.o -obj-$(CONFIG_DVB_LGS8GL5) += lgs8gl5.o -obj-$(CONFIG_DVB_TDA665x) += tda665x.o -obj-$(CONFIG_DVB_LGS8GXX) += lgs8gxx.o -obj-$(CONFIG_DVB_ATBM8830) += atbm8830.o -obj-$(CONFIG_DVB_DUMMY_FE) += dvb_dummy_fe.o -obj-$(CONFIG_DVB_AF9013) += af9013.o -obj-$(CONFIG_DVB_CX24116) += cx24116.o -obj-$(CONFIG_DVB_SI21XX) += si21xx.o -obj-$(CONFIG_DVB_STV0288) += stv0288.o -obj-$(CONFIG_DVB_STB6000) += stb6000.o -obj-$(CONFIG_DVB_S921) += s921.o -obj-$(CONFIG_DVB_STV6110) += stv6110.o -obj-$(CONFIG_DVB_STV0900) += stv0900.o -obj-$(CONFIG_DVB_STV090x) += stv090x.o -obj-$(CONFIG_DVB_STV6110x) += stv6110x.o -obj-$(CONFIG_DVB_ISL6423) += isl6423.o -obj-$(CONFIG_DVB_EC100) += ec100.o -obj-$(CONFIG_DVB_HD29L2) += hd29l2.o -obj-$(CONFIG_DVB_DS3000) += ds3000.o -obj-$(CONFIG_DVB_MB86A16) += mb86a16.o -obj-$(CONFIG_DVB_MB86A20S) += mb86a20s.o -obj-$(CONFIG_DVB_IX2505V) += ix2505v.o -obj-$(CONFIG_DVB_STV0367) += stv0367.o -obj-$(CONFIG_DVB_CXD2820R) += cxd2820r.o -obj-$(CONFIG_DVB_DRXK) += drxk.o -obj-$(CONFIG_DVB_TDA18271C2DD) += tda18271c2dd.o -obj-$(CONFIG_DVB_IT913X_FE) += it913x-fe.o -obj-$(CONFIG_DVB_A8293) += a8293.o -obj-$(CONFIG_DVB_TDA10071) += tda10071.o -obj-$(CONFIG_DVB_RTL2830) += rtl2830.o -obj-$(CONFIG_DVB_RTL2832) += rtl2832.o -obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o -obj-$(CONFIG_DVB_AF9033) += af9033.o - diff --git a/drivers/media/dvb/frontends/af9013.c b/drivers/media/dvb/frontends/af9013.c deleted file mode 100644 index 5bc570d77846..000000000000 --- a/drivers/media/dvb/frontends/af9013.c +++ /dev/null @@ -1,1524 +0,0 @@ -/* - * Afatech AF9013 demodulator driver - * - * Copyright (C) 2007 Antti Palosaari - * Copyright (C) 2011 Antti Palosaari - * - * Thanks to Afatech who kindly provided information. - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "af9013_priv.h" - -int af9013_debug; -module_param_named(debug, af9013_debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -struct af9013_state { - struct i2c_adapter *i2c; - struct dvb_frontend fe; - struct af9013_config config; - - /* tuner/demod RF and IF AGC limits used for signal strength calc */ - u8 signal_strength_en, rf_50, rf_80, if_50, if_80; - u16 signal_strength; - u32 ber; - u32 ucblocks; - u16 snr; - u32 bandwidth_hz; - fe_status_t fe_status; - unsigned long set_frontend_jiffies; - unsigned long read_status_jiffies; - bool first_tune; - bool i2c_gate_state; - unsigned int statistics_step:3; - struct delayed_work statistics_work; -}; - -/* write multiple registers */ -static int af9013_wr_regs_i2c(struct af9013_state *priv, u8 mbox, u16 reg, - const u8 *val, int len) -{ - int ret; - u8 buf[3+len]; - struct i2c_msg msg[1] = { - { - .addr = priv->config.i2c_addr, - .flags = 0, - .len = sizeof(buf), - .buf = buf, - } - }; - - buf[0] = (reg >> 8) & 0xff; - buf[1] = (reg >> 0) & 0xff; - buf[2] = mbox; - memcpy(&buf[3], val, len); - - ret = i2c_transfer(priv->i2c, msg, 1); - if (ret == 1) { - ret = 0; - } else { - warn("i2c wr failed=%d reg=%04x len=%d", ret, reg, len); - ret = -EREMOTEIO; - } - return ret; -} - -/* read multiple registers */ -static int af9013_rd_regs_i2c(struct af9013_state *priv, u8 mbox, u16 reg, - u8 *val, int len) -{ - int ret; - u8 buf[3]; - struct i2c_msg msg[2] = { - { - .addr = priv->config.i2c_addr, - .flags = 0, - .len = 3, - .buf = buf, - }, { - .addr = priv->config.i2c_addr, - .flags = I2C_M_RD, - .len = len, - .buf = val, - } - }; - - buf[0] = (reg >> 8) & 0xff; - buf[1] = (reg >> 0) & 0xff; - buf[2] = mbox; - - ret = i2c_transfer(priv->i2c, msg, 2); - if (ret == 2) { - ret = 0; - } else { - warn("i2c rd failed=%d reg=%04x len=%d", ret, reg, len); - ret = -EREMOTEIO; - } - return ret; -} - -/* write multiple registers */ -static int af9013_wr_regs(struct af9013_state *priv, u16 reg, const u8 *val, - int len) -{ - int ret, i; - u8 mbox = (0 << 7)|(0 << 6)|(1 << 1)|(1 << 0); - - if ((priv->config.ts_mode == AF9013_TS_USB) && - ((reg & 0xff00) != 0xff00) && ((reg & 0xff00) != 0xae00)) { - mbox |= ((len - 1) << 2); - ret = af9013_wr_regs_i2c(priv, mbox, reg, val, len); - } else { - for (i = 0; i < len; i++) { - ret = af9013_wr_regs_i2c(priv, mbox, reg+i, val+i, 1); - if (ret) - goto err; - } - } - -err: - return 0; -} - -/* read multiple registers */ -static int af9013_rd_regs(struct af9013_state *priv, u16 reg, u8 *val, int len) -{ - int ret, i; - u8 mbox = (0 << 7)|(0 << 6)|(1 << 1)|(0 << 0); - - if ((priv->config.ts_mode == AF9013_TS_USB) && - ((reg & 0xff00) != 0xff00) && ((reg & 0xff00) != 0xae00)) { - mbox |= ((len - 1) << 2); - ret = af9013_rd_regs_i2c(priv, mbox, reg, val, len); - } else { - for (i = 0; i < len; i++) { - ret = af9013_rd_regs_i2c(priv, mbox, reg+i, val+i, 1); - if (ret) - goto err; - } - } - -err: - return 0; -} - -/* write single register */ -static int af9013_wr_reg(struct af9013_state *priv, u16 reg, u8 val) -{ - return af9013_wr_regs(priv, reg, &val, 1); -} - -/* read single register */ -static int af9013_rd_reg(struct af9013_state *priv, u16 reg, u8 *val) -{ - return af9013_rd_regs(priv, reg, val, 1); -} - -static int af9013_write_ofsm_regs(struct af9013_state *state, u16 reg, u8 *val, - u8 len) -{ - u8 mbox = (1 << 7)|(1 << 6)|((len - 1) << 2)|(1 << 1)|(1 << 0); - return af9013_wr_regs_i2c(state, mbox, reg, val, len); -} - -static int af9013_wr_reg_bits(struct af9013_state *state, u16 reg, int pos, - int len, u8 val) -{ - int ret; - u8 tmp, mask; - - /* no need for read if whole reg is written */ - if (len != 8) { - ret = af9013_rd_reg(state, reg, &tmp); - if (ret) - return ret; - - mask = (0xff >> (8 - len)) << pos; - val <<= pos; - tmp &= ~mask; - val |= tmp; - } - - return af9013_wr_reg(state, reg, val); -} - -static int af9013_rd_reg_bits(struct af9013_state *state, u16 reg, int pos, - int len, u8 *val) -{ - int ret; - u8 tmp; - - ret = af9013_rd_reg(state, reg, &tmp); - if (ret) - return ret; - - *val = (tmp >> pos); - *val &= (0xff >> (8 - len)); - - return 0; -} - -static int af9013_set_gpio(struct af9013_state *state, u8 gpio, u8 gpioval) -{ - int ret; - u8 pos; - u16 addr; - - dbg("%s: gpio=%d gpioval=%02x", __func__, gpio, gpioval); - - /* - * GPIO0 & GPIO1 0xd735 - * GPIO2 & GPIO3 0xd736 - */ - - switch (gpio) { - case 0: - case 1: - addr = 0xd735; - break; - case 2: - case 3: - addr = 0xd736; - break; - - default: - err("invalid gpio:%d\n", gpio); - ret = -EINVAL; - goto err; - }; - - switch (gpio) { - case 0: - case 2: - pos = 0; - break; - case 1: - case 3: - default: - pos = 4; - break; - }; - - ret = af9013_wr_reg_bits(state, addr, pos, 4, gpioval); - if (ret) - goto err; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static u32 af913_div(u32 a, u32 b, u32 x) -{ - u32 r = 0, c = 0, i; - - dbg("%s: a=%d b=%d x=%d", __func__, a, b, x); - - if (a > b) { - c = a / b; - a = a - c * b; - } - - for (i = 0; i < x; i++) { - if (a >= b) { - r += 1; - a -= b; - } - a <<= 1; - r <<= 1; - } - r = (c << (u32)x) + r; - - dbg("%s: a=%d b=%d x=%d r=%x", __func__, a, b, x, r); - return r; -} - -static int af9013_power_ctrl(struct af9013_state *state, u8 onoff) -{ - int ret, i; - u8 tmp; - - dbg("%s: onoff=%d", __func__, onoff); - - /* enable reset */ - ret = af9013_wr_reg_bits(state, 0xd417, 4, 1, 1); - if (ret) - goto err; - - /* start reset mechanism */ - ret = af9013_wr_reg(state, 0xaeff, 1); - if (ret) - goto err; - - /* wait reset performs */ - for (i = 0; i < 150; i++) { - ret = af9013_rd_reg_bits(state, 0xd417, 1, 1, &tmp); - if (ret) - goto err; - - if (tmp) - break; /* reset done */ - - usleep_range(5000, 25000); - } - - if (!tmp) - return -ETIMEDOUT; - - if (onoff) { - /* clear reset */ - ret = af9013_wr_reg_bits(state, 0xd417, 1, 1, 0); - if (ret) - goto err; - - /* disable reset */ - ret = af9013_wr_reg_bits(state, 0xd417, 4, 1, 0); - - /* power on */ - ret = af9013_wr_reg_bits(state, 0xd73a, 3, 1, 0); - } else { - /* power off */ - ret = af9013_wr_reg_bits(state, 0xd73a, 3, 1, 1); - } - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_statistics_ber_unc_start(struct dvb_frontend *fe) -{ - struct af9013_state *state = fe->demodulator_priv; - int ret; - - dbg("%s", __func__); - - /* reset and start BER counter */ - ret = af9013_wr_reg_bits(state, 0xd391, 4, 1, 1); - if (ret) - goto err; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_statistics_ber_unc_result(struct dvb_frontend *fe) -{ - struct af9013_state *state = fe->demodulator_priv; - int ret; - u8 buf[5]; - - dbg("%s", __func__); - - /* check if error bit count is ready */ - ret = af9013_rd_reg_bits(state, 0xd391, 4, 1, &buf[0]); - if (ret) - goto err; - - if (!buf[0]) { - dbg("%s: not ready", __func__); - return 0; - } - - ret = af9013_rd_regs(state, 0xd387, buf, 5); - if (ret) - goto err; - - state->ber = (buf[2] << 16) | (buf[1] << 8) | buf[0]; - state->ucblocks += (buf[4] << 8) | buf[3]; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_statistics_snr_start(struct dvb_frontend *fe) -{ - struct af9013_state *state = fe->demodulator_priv; - int ret; - - dbg("%s", __func__); - - /* start SNR meas */ - ret = af9013_wr_reg_bits(state, 0xd2e1, 3, 1, 1); - if (ret) - goto err; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_statistics_snr_result(struct dvb_frontend *fe) -{ - struct af9013_state *state = fe->demodulator_priv; - int ret, i, len; - u8 buf[3], tmp; - u32 snr_val; - const struct af9013_snr *uninitialized_var(snr_lut); - - dbg("%s", __func__); - - /* check if SNR ready */ - ret = af9013_rd_reg_bits(state, 0xd2e1, 3, 1, &tmp); - if (ret) - goto err; - - if (!tmp) { - dbg("%s: not ready", __func__); - return 0; - } - - /* read value */ - ret = af9013_rd_regs(state, 0xd2e3, buf, 3); - if (ret) - goto err; - - snr_val = (buf[2] << 16) | (buf[1] << 8) | buf[0]; - - /* read current modulation */ - ret = af9013_rd_reg(state, 0xd3c1, &tmp); - if (ret) - goto err; - - switch ((tmp >> 6) & 3) { - case 0: - len = ARRAY_SIZE(qpsk_snr_lut); - snr_lut = qpsk_snr_lut; - break; - case 1: - len = ARRAY_SIZE(qam16_snr_lut); - snr_lut = qam16_snr_lut; - break; - case 2: - len = ARRAY_SIZE(qam64_snr_lut); - snr_lut = qam64_snr_lut; - break; - default: - goto err; - break; - } - - for (i = 0; i < len; i++) { - tmp = snr_lut[i].snr; - - if (snr_val < snr_lut[i].val) - break; - } - state->snr = tmp * 10; /* dB/10 */ - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_statistics_signal_strength(struct dvb_frontend *fe) -{ - struct af9013_state *state = fe->demodulator_priv; - int ret = 0; - u8 buf[2], rf_gain, if_gain; - int signal_strength; - - dbg("%s", __func__); - - if (!state->signal_strength_en) - return 0; - - ret = af9013_rd_regs(state, 0xd07c, buf, 2); - if (ret) - goto err; - - rf_gain = buf[0]; - if_gain = buf[1]; - - signal_strength = (0xffff / \ - (9 * (state->rf_50 + state->if_50) - \ - 11 * (state->rf_80 + state->if_80))) * \ - (10 * (rf_gain + if_gain) - \ - 11 * (state->rf_80 + state->if_80)); - if (signal_strength < 0) - signal_strength = 0; - else if (signal_strength > 0xffff) - signal_strength = 0xffff; - - state->signal_strength = signal_strength; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static void af9013_statistics_work(struct work_struct *work) -{ - struct af9013_state *state = container_of(work, - struct af9013_state, statistics_work.work); - unsigned int next_msec; - - /* update only signal strength when demod is not locked */ - if (!(state->fe_status & FE_HAS_LOCK)) { - state->statistics_step = 0; - state->ber = 0; - state->snr = 0; - } - - switch (state->statistics_step) { - default: - state->statistics_step = 0; - case 0: - af9013_statistics_signal_strength(&state->fe); - state->statistics_step++; - next_msec = 300; - break; - case 1: - af9013_statistics_snr_start(&state->fe); - state->statistics_step++; - next_msec = 200; - break; - case 2: - af9013_statistics_ber_unc_start(&state->fe); - state->statistics_step++; - next_msec = 1000; - break; - case 3: - af9013_statistics_snr_result(&state->fe); - state->statistics_step++; - next_msec = 400; - break; - case 4: - af9013_statistics_ber_unc_result(&state->fe); - state->statistics_step++; - next_msec = 100; - break; - } - - schedule_delayed_work(&state->statistics_work, - msecs_to_jiffies(next_msec)); -} - -static int af9013_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *fesettings) -{ - fesettings->min_delay_ms = 800; - fesettings->step_size = 0; - fesettings->max_drift = 0; - - return 0; -} - -static int af9013_set_frontend(struct dvb_frontend *fe) -{ - struct af9013_state *state = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i, sampling_freq; - bool auto_mode, spec_inv; - u8 buf[6]; - u32 if_frequency, freq_cw; - - dbg("%s: frequency=%d bandwidth_hz=%d", __func__, - c->frequency, c->bandwidth_hz); - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - - /* program CFOE coefficients */ - if (c->bandwidth_hz != state->bandwidth_hz) { - for (i = 0; i < ARRAY_SIZE(coeff_lut); i++) { - if (coeff_lut[i].clock == state->config.clock && - coeff_lut[i].bandwidth_hz == c->bandwidth_hz) { - break; - } - } - - ret = af9013_wr_regs(state, 0xae00, coeff_lut[i].val, - sizeof(coeff_lut[i].val)); - } - - /* program frequency control */ - if (c->bandwidth_hz != state->bandwidth_hz || state->first_tune) { - /* get used IF frequency */ - if (fe->ops.tuner_ops.get_if_frequency) - fe->ops.tuner_ops.get_if_frequency(fe, &if_frequency); - else - if_frequency = state->config.if_frequency; - - sampling_freq = if_frequency; - - while (sampling_freq > (state->config.clock / 2)) - sampling_freq -= state->config.clock; - - if (sampling_freq < 0) { - sampling_freq *= -1; - spec_inv = state->config.spec_inv; - } else { - spec_inv = !state->config.spec_inv; - } - - freq_cw = af913_div(sampling_freq, state->config.clock, 23); - - if (spec_inv) - freq_cw = 0x800000 - freq_cw; - - buf[0] = (freq_cw >> 0) & 0xff; - buf[1] = (freq_cw >> 8) & 0xff; - buf[2] = (freq_cw >> 16) & 0x7f; - - freq_cw = 0x800000 - freq_cw; - - buf[3] = (freq_cw >> 0) & 0xff; - buf[4] = (freq_cw >> 8) & 0xff; - buf[5] = (freq_cw >> 16) & 0x7f; - - ret = af9013_wr_regs(state, 0xd140, buf, 3); - if (ret) - goto err; - - ret = af9013_wr_regs(state, 0x9be7, buf, 6); - if (ret) - goto err; - } - - /* clear TPS lock flag */ - ret = af9013_wr_reg_bits(state, 0xd330, 3, 1, 1); - if (ret) - goto err; - - /* clear MPEG2 lock flag */ - ret = af9013_wr_reg_bits(state, 0xd507, 6, 1, 0); - if (ret) - goto err; - - /* empty channel function */ - ret = af9013_wr_reg_bits(state, 0x9bfe, 0, 1, 0); - if (ret) - goto err; - - /* empty DVB-T channel function */ - ret = af9013_wr_reg_bits(state, 0x9bc2, 0, 1, 0); - if (ret) - goto err; - - /* transmission parameters */ - auto_mode = false; - memset(buf, 0, 3); - - switch (c->transmission_mode) { - case TRANSMISSION_MODE_AUTO: - auto_mode = 1; - break; - case TRANSMISSION_MODE_2K: - break; - case TRANSMISSION_MODE_8K: - buf[0] |= (1 << 0); - break; - default: - dbg("%s: invalid transmission_mode", __func__); - auto_mode = 1; - } - - switch (c->guard_interval) { - case GUARD_INTERVAL_AUTO: - auto_mode = 1; - break; - case GUARD_INTERVAL_1_32: - break; - case GUARD_INTERVAL_1_16: - buf[0] |= (1 << 2); - break; - case GUARD_INTERVAL_1_8: - buf[0] |= (2 << 2); - break; - case GUARD_INTERVAL_1_4: - buf[0] |= (3 << 2); - break; - default: - dbg("%s: invalid guard_interval", __func__); - auto_mode = 1; - } - - switch (c->hierarchy) { - case HIERARCHY_AUTO: - auto_mode = 1; - break; - case HIERARCHY_NONE: - break; - case HIERARCHY_1: - buf[0] |= (1 << 4); - break; - case HIERARCHY_2: - buf[0] |= (2 << 4); - break; - case HIERARCHY_4: - buf[0] |= (3 << 4); - break; - default: - dbg("%s: invalid hierarchy", __func__); - auto_mode = 1; - }; - - switch (c->modulation) { - case QAM_AUTO: - auto_mode = 1; - break; - case QPSK: - break; - case QAM_16: - buf[1] |= (1 << 6); - break; - case QAM_64: - buf[1] |= (2 << 6); - break; - default: - dbg("%s: invalid modulation", __func__); - auto_mode = 1; - } - - /* Use HP. How and which case we can switch to LP? */ - buf[1] |= (1 << 4); - - switch (c->code_rate_HP) { - case FEC_AUTO: - auto_mode = 1; - break; - case FEC_1_2: - break; - case FEC_2_3: - buf[2] |= (1 << 0); - break; - case FEC_3_4: - buf[2] |= (2 << 0); - break; - case FEC_5_6: - buf[2] |= (3 << 0); - break; - case FEC_7_8: - buf[2] |= (4 << 0); - break; - default: - dbg("%s: invalid code_rate_HP", __func__); - auto_mode = 1; - } - - switch (c->code_rate_LP) { - case FEC_AUTO: - auto_mode = 1; - break; - case FEC_1_2: - break; - case FEC_2_3: - buf[2] |= (1 << 3); - break; - case FEC_3_4: - buf[2] |= (2 << 3); - break; - case FEC_5_6: - buf[2] |= (3 << 3); - break; - case FEC_7_8: - buf[2] |= (4 << 3); - break; - case FEC_NONE: - break; - default: - dbg("%s: invalid code_rate_LP", __func__); - auto_mode = 1; - } - - switch (c->bandwidth_hz) { - case 6000000: - break; - case 7000000: - buf[1] |= (1 << 2); - break; - case 8000000: - buf[1] |= (2 << 2); - break; - default: - dbg("%s: invalid bandwidth_hz", __func__); - ret = -EINVAL; - goto err; - } - - ret = af9013_wr_regs(state, 0xd3c0, buf, 3); - if (ret) - goto err; - - if (auto_mode) { - /* clear easy mode flag */ - ret = af9013_wr_reg(state, 0xaefd, 0); - if (ret) - goto err; - - dbg("%s: auto params", __func__); - } else { - /* set easy mode flag */ - ret = af9013_wr_reg(state, 0xaefd, 1); - if (ret) - goto err; - - ret = af9013_wr_reg(state, 0xaefe, 0); - if (ret) - goto err; - - dbg("%s: manual params", __func__); - } - - /* tune */ - ret = af9013_wr_reg(state, 0xffff, 0); - if (ret) - goto err; - - state->bandwidth_hz = c->bandwidth_hz; - state->set_frontend_jiffies = jiffies; - state->first_tune = false; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_get_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct af9013_state *state = fe->demodulator_priv; - int ret; - u8 buf[3]; - - dbg("%s", __func__); - - ret = af9013_rd_regs(state, 0xd3c0, buf, 3); - if (ret) - goto err; - - switch ((buf[1] >> 6) & 3) { - case 0: - c->modulation = QPSK; - break; - case 1: - c->modulation = QAM_16; - break; - case 2: - c->modulation = QAM_64; - break; - } - - switch ((buf[0] >> 0) & 3) { - case 0: - c->transmission_mode = TRANSMISSION_MODE_2K; - break; - case 1: - c->transmission_mode = TRANSMISSION_MODE_8K; - } - - switch ((buf[0] >> 2) & 3) { - case 0: - c->guard_interval = GUARD_INTERVAL_1_32; - break; - case 1: - c->guard_interval = GUARD_INTERVAL_1_16; - break; - case 2: - c->guard_interval = GUARD_INTERVAL_1_8; - break; - case 3: - c->guard_interval = GUARD_INTERVAL_1_4; - break; - } - - switch ((buf[0] >> 4) & 7) { - case 0: - c->hierarchy = HIERARCHY_NONE; - break; - case 1: - c->hierarchy = HIERARCHY_1; - break; - case 2: - c->hierarchy = HIERARCHY_2; - break; - case 3: - c->hierarchy = HIERARCHY_4; - break; - } - - switch ((buf[2] >> 0) & 7) { - case 0: - c->code_rate_HP = FEC_1_2; - break; - case 1: - c->code_rate_HP = FEC_2_3; - break; - case 2: - c->code_rate_HP = FEC_3_4; - break; - case 3: - c->code_rate_HP = FEC_5_6; - break; - case 4: - c->code_rate_HP = FEC_7_8; - break; - } - - switch ((buf[2] >> 3) & 7) { - case 0: - c->code_rate_LP = FEC_1_2; - break; - case 1: - c->code_rate_LP = FEC_2_3; - break; - case 2: - c->code_rate_LP = FEC_3_4; - break; - case 3: - c->code_rate_LP = FEC_5_6; - break; - case 4: - c->code_rate_LP = FEC_7_8; - break; - } - - switch ((buf[1] >> 2) & 3) { - case 0: - c->bandwidth_hz = 6000000; - break; - case 1: - c->bandwidth_hz = 7000000; - break; - case 2: - c->bandwidth_hz = 8000000; - break; - } - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct af9013_state *state = fe->demodulator_priv; - int ret; - u8 tmp; - - /* - * Return status from the cache if it is younger than 2000ms with the - * exception of last tune is done during 4000ms. - */ - if (time_is_after_jiffies( - state->read_status_jiffies + msecs_to_jiffies(2000)) && - time_is_before_jiffies( - state->set_frontend_jiffies + msecs_to_jiffies(4000)) - ) { - *status = state->fe_status; - return 0; - } else { - *status = 0; - } - - /* MPEG2 lock */ - ret = af9013_rd_reg_bits(state, 0xd507, 6, 1, &tmp); - if (ret) - goto err; - - if (tmp) - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | - FE_HAS_SYNC | FE_HAS_LOCK; - - if (!*status) { - /* TPS lock */ - ret = af9013_rd_reg_bits(state, 0xd330, 3, 1, &tmp); - if (ret) - goto err; - - if (tmp) - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI; - } - - state->fe_status = *status; - state->read_status_jiffies = jiffies; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct af9013_state *state = fe->demodulator_priv; - *snr = state->snr; - return 0; -} - -static int af9013_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - struct af9013_state *state = fe->demodulator_priv; - *strength = state->signal_strength; - return 0; -} - -static int af9013_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct af9013_state *state = fe->demodulator_priv; - *ber = state->ber; - return 0; -} - -static int af9013_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - struct af9013_state *state = fe->demodulator_priv; - *ucblocks = state->ucblocks; - return 0; -} - -static int af9013_init(struct dvb_frontend *fe) -{ - struct af9013_state *state = fe->demodulator_priv; - int ret, i, len; - u8 buf[3], tmp; - u32 adc_cw; - const struct af9013_reg_bit *init; - - dbg("%s", __func__); - - /* power on */ - ret = af9013_power_ctrl(state, 1); - if (ret) - goto err; - - /* enable ADC */ - ret = af9013_wr_reg(state, 0xd73a, 0xa4); - if (ret) - goto err; - - /* write API version to firmware */ - ret = af9013_wr_regs(state, 0x9bf2, state->config.api_version, 4); - if (ret) - goto err; - - /* program ADC control */ - switch (state->config.clock) { - case 28800000: /* 28.800 MHz */ - tmp = 0; - break; - case 20480000: /* 20.480 MHz */ - tmp = 1; - break; - case 28000000: /* 28.000 MHz */ - tmp = 2; - break; - case 25000000: /* 25.000 MHz */ - tmp = 3; - break; - default: - err("invalid clock"); - return -EINVAL; - } - - adc_cw = af913_div(state->config.clock, 1000000ul, 19); - buf[0] = (adc_cw >> 0) & 0xff; - buf[1] = (adc_cw >> 8) & 0xff; - buf[2] = (adc_cw >> 16) & 0xff; - - ret = af9013_wr_regs(state, 0xd180, buf, 3); - if (ret) - goto err; - - ret = af9013_wr_reg_bits(state, 0x9bd2, 0, 4, tmp); - if (ret) - goto err; - - /* set I2C master clock */ - ret = af9013_wr_reg(state, 0xd416, 0x14); - if (ret) - goto err; - - /* set 16 embx */ - ret = af9013_wr_reg_bits(state, 0xd700, 1, 1, 1); - if (ret) - goto err; - - /* set no trigger */ - ret = af9013_wr_reg_bits(state, 0xd700, 2, 1, 0); - if (ret) - goto err; - - /* set read-update bit for constellation */ - ret = af9013_wr_reg_bits(state, 0xd371, 1, 1, 1); - if (ret) - goto err; - - /* settings for mp2if */ - if (state->config.ts_mode == AF9013_TS_USB) { - /* AF9015 split PSB to 1.5k + 0.5k */ - ret = af9013_wr_reg_bits(state, 0xd50b, 2, 1, 1); - if (ret) - goto err; - } else { - /* AF9013 change the output bit to data7 */ - ret = af9013_wr_reg_bits(state, 0xd500, 3, 1, 1); - if (ret) - goto err; - - /* AF9013 set mpeg to full speed */ - ret = af9013_wr_reg_bits(state, 0xd502, 4, 1, 1); - if (ret) - goto err; - } - - ret = af9013_wr_reg_bits(state, 0xd520, 4, 1, 1); - if (ret) - goto err; - - /* load OFSM settings */ - dbg("%s: load ofsm settings", __func__); - len = ARRAY_SIZE(ofsm_init); - init = ofsm_init; - for (i = 0; i < len; i++) { - ret = af9013_wr_reg_bits(state, init[i].addr, init[i].pos, - init[i].len, init[i].val); - if (ret) - goto err; - } - - /* load tuner specific settings */ - dbg("%s: load tuner specific settings", __func__); - switch (state->config.tuner) { - case AF9013_TUNER_MXL5003D: - len = ARRAY_SIZE(tuner_init_mxl5003d); - init = tuner_init_mxl5003d; - break; - case AF9013_TUNER_MXL5005D: - case AF9013_TUNER_MXL5005R: - case AF9013_TUNER_MXL5007T: - len = ARRAY_SIZE(tuner_init_mxl5005); - init = tuner_init_mxl5005; - break; - case AF9013_TUNER_ENV77H11D5: - len = ARRAY_SIZE(tuner_init_env77h11d5); - init = tuner_init_env77h11d5; - break; - case AF9013_TUNER_MT2060: - len = ARRAY_SIZE(tuner_init_mt2060); - init = tuner_init_mt2060; - break; - case AF9013_TUNER_MC44S803: - len = ARRAY_SIZE(tuner_init_mc44s803); - init = tuner_init_mc44s803; - break; - case AF9013_TUNER_QT1010: - case AF9013_TUNER_QT1010A: - len = ARRAY_SIZE(tuner_init_qt1010); - init = tuner_init_qt1010; - break; - case AF9013_TUNER_MT2060_2: - len = ARRAY_SIZE(tuner_init_mt2060_2); - init = tuner_init_mt2060_2; - break; - case AF9013_TUNER_TDA18271: - case AF9013_TUNER_TDA18218: - len = ARRAY_SIZE(tuner_init_tda18271); - init = tuner_init_tda18271; - break; - case AF9013_TUNER_UNKNOWN: - default: - len = ARRAY_SIZE(tuner_init_unknown); - init = tuner_init_unknown; - break; - } - - for (i = 0; i < len; i++) { - ret = af9013_wr_reg_bits(state, init[i].addr, init[i].pos, - init[i].len, init[i].val); - if (ret) - goto err; - } - - /* TS mode */ - ret = af9013_wr_reg_bits(state, 0xd500, 1, 2, state->config.ts_mode); - if (ret) - goto err; - - /* enable lock led */ - ret = af9013_wr_reg_bits(state, 0xd730, 0, 1, 1); - if (ret) - goto err; - - /* check if we support signal strength */ - if (!state->signal_strength_en) { - ret = af9013_rd_reg_bits(state, 0x9bee, 0, 1, - &state->signal_strength_en); - if (ret) - goto err; - } - - /* read values needed for signal strength calculation */ - if (state->signal_strength_en && !state->rf_50) { - ret = af9013_rd_reg(state, 0x9bbd, &state->rf_50); - if (ret) - goto err; - - ret = af9013_rd_reg(state, 0x9bd0, &state->rf_80); - if (ret) - goto err; - - ret = af9013_rd_reg(state, 0x9be2, &state->if_50); - if (ret) - goto err; - - ret = af9013_rd_reg(state, 0x9be4, &state->if_80); - if (ret) - goto err; - } - - /* SNR */ - ret = af9013_wr_reg(state, 0xd2e2, 1); - if (ret) - goto err; - - /* BER / UCB */ - buf[0] = (10000 >> 0) & 0xff; - buf[1] = (10000 >> 8) & 0xff; - ret = af9013_wr_regs(state, 0xd385, buf, 2); - if (ret) - goto err; - - /* enable FEC monitor */ - ret = af9013_wr_reg_bits(state, 0xd392, 1, 1, 1); - if (ret) - goto err; - - state->first_tune = true; - schedule_delayed_work(&state->statistics_work, msecs_to_jiffies(400)); - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_sleep(struct dvb_frontend *fe) -{ - struct af9013_state *state = fe->demodulator_priv; - int ret; - - dbg("%s", __func__); - - /* stop statistics polling */ - cancel_delayed_work_sync(&state->statistics_work); - - /* disable lock led */ - ret = af9013_wr_reg_bits(state, 0xd730, 0, 1, 0); - if (ret) - goto err; - - /* power off */ - ret = af9013_power_ctrl(state, 0); - if (ret) - goto err; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int af9013_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - int ret; - struct af9013_state *state = fe->demodulator_priv; - - dbg("%s: enable=%d", __func__, enable); - - /* gate already open or close */ - if (state->i2c_gate_state == enable) - return 0; - - if (state->config.ts_mode == AF9013_TS_USB) - ret = af9013_wr_reg_bits(state, 0xd417, 3, 1, enable); - else - ret = af9013_wr_reg_bits(state, 0xd607, 2, 1, enable); - if (ret) - goto err; - - state->i2c_gate_state = enable; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static void af9013_release(struct dvb_frontend *fe) -{ - struct af9013_state *state = fe->demodulator_priv; - kfree(state); -} - -static struct dvb_frontend_ops af9013_ops; - -static int af9013_download_firmware(struct af9013_state *state) -{ - int i, len, remaining, ret; - const struct firmware *fw; - u16 checksum = 0; - u8 val; - u8 fw_params[4]; - u8 *fw_file = AF9013_DEFAULT_FIRMWARE; - - msleep(100); - /* check whether firmware is already running */ - ret = af9013_rd_reg(state, 0x98be, &val); - if (ret) - goto err; - else - dbg("%s: firmware status=%02x", __func__, val); - - if (val == 0x0c) /* fw is running, no need for download */ - goto exit; - - info("found a '%s' in cold state, will try to load a firmware", - af9013_ops.info.name); - - /* request the firmware, this will block and timeout */ - ret = request_firmware(&fw, fw_file, state->i2c->dev.parent); - if (ret) { - err("did not find the firmware file. (%s) " - "Please see linux/Documentation/dvb/ for more details" \ - " on firmware-problems. (%d)", - fw_file, ret); - goto err; - } - - info("downloading firmware from file '%s'", fw_file); - - /* calc checksum */ - for (i = 0; i < fw->size; i++) - checksum += fw->data[i]; - - fw_params[0] = checksum >> 8; - fw_params[1] = checksum & 0xff; - fw_params[2] = fw->size >> 8; - fw_params[3] = fw->size & 0xff; - - /* write fw checksum & size */ - ret = af9013_write_ofsm_regs(state, 0x50fc, - fw_params, sizeof(fw_params)); - if (ret) - goto err_release; - - #define FW_ADDR 0x5100 /* firmware start address */ - #define LEN_MAX 16 /* max packet size */ - for (remaining = fw->size; remaining > 0; remaining -= LEN_MAX) { - len = remaining; - if (len > LEN_MAX) - len = LEN_MAX; - - ret = af9013_write_ofsm_regs(state, - FW_ADDR + fw->size - remaining, - (u8 *) &fw->data[fw->size - remaining], len); - if (ret) { - err("firmware download failed:%d", ret); - goto err_release; - } - } - - /* request boot firmware */ - ret = af9013_wr_reg(state, 0xe205, 1); - if (ret) - goto err_release; - - for (i = 0; i < 15; i++) { - msleep(100); - - /* check firmware status */ - ret = af9013_rd_reg(state, 0x98be, &val); - if (ret) - goto err_release; - - dbg("%s: firmware status=%02x", __func__, val); - - if (val == 0x0c || val == 0x04) /* success or fail */ - break; - } - - if (val == 0x04) { - err("firmware did not run"); - ret = -ENODEV; - } else if (val != 0x0c) { - err("firmware boot timeout"); - ret = -ENODEV; - } - -err_release: - release_firmware(fw); -err: -exit: - if (!ret) - info("found a '%s' in warm state.", af9013_ops.info.name); - return ret; -} - -struct dvb_frontend *af9013_attach(const struct af9013_config *config, - struct i2c_adapter *i2c) -{ - int ret; - struct af9013_state *state = NULL; - u8 buf[4], i; - - /* allocate memory for the internal state */ - state = kzalloc(sizeof(struct af9013_state), GFP_KERNEL); - if (state == NULL) - goto err; - - /* setup the state */ - state->i2c = i2c; - memcpy(&state->config, config, sizeof(struct af9013_config)); - - /* download firmware */ - if (state->config.ts_mode != AF9013_TS_USB) { - ret = af9013_download_firmware(state); - if (ret) - goto err; - } - - /* firmware version */ - ret = af9013_rd_regs(state, 0x5103, buf, 4); - if (ret) - goto err; - - info("firmware version %d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3]); - - /* set GPIOs */ - for (i = 0; i < sizeof(state->config.gpio); i++) { - ret = af9013_set_gpio(state, i, state->config.gpio[i]); - if (ret) - goto err; - } - - /* create dvb_frontend */ - memcpy(&state->fe.ops, &af9013_ops, - sizeof(struct dvb_frontend_ops)); - state->fe.demodulator_priv = state; - - INIT_DELAYED_WORK(&state->statistics_work, af9013_statistics_work); - - return &state->fe; -err: - kfree(state); - return NULL; -} -EXPORT_SYMBOL(af9013_attach); - -static struct dvb_frontend_ops af9013_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "Afatech AF9013", - .frequency_min = 174000000, - .frequency_max = 862000000, - .frequency_stepsize = 250000, - .frequency_tolerance = 0, - .caps = FE_CAN_FEC_1_2 | - FE_CAN_FEC_2_3 | - FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | - FE_CAN_FEC_7_8 | - FE_CAN_FEC_AUTO | - FE_CAN_QPSK | - FE_CAN_QAM_16 | - FE_CAN_QAM_64 | - FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO | - FE_CAN_RECOVER | - FE_CAN_MUTE_TS - }, - - .release = af9013_release, - - .init = af9013_init, - .sleep = af9013_sleep, - - .get_tune_settings = af9013_get_tune_settings, - .set_frontend = af9013_set_frontend, - .get_frontend = af9013_get_frontend, - - .read_status = af9013_read_status, - .read_snr = af9013_read_snr, - .read_signal_strength = af9013_read_signal_strength, - .read_ber = af9013_read_ber, - .read_ucblocks = af9013_read_ucblocks, - - .i2c_gate_ctrl = af9013_i2c_gate_ctrl, -}; - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Afatech AF9013 DVB-T demodulator driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/af9013.h b/drivers/media/dvb/frontends/af9013.h deleted file mode 100644 index b973fc5a0384..000000000000 --- a/drivers/media/dvb/frontends/af9013.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Afatech AF9013 demodulator driver - * - * Copyright (C) 2007 Antti Palosaari - * Copyright (C) 2011 Antti Palosaari - * - * Thanks to Afatech who kindly provided information. - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef AF9013_H -#define AF9013_H - -#include - -/* AF9013/5 GPIOs (mostly guessed) - demod#1-gpio#0 - set demod#2 i2c-addr for dual devices - demod#1-gpio#1 - xtal setting (?) - demod#1-gpio#3 - tuner#1 - demod#2-gpio#0 - tuner#2 - demod#2-gpio#1 - xtal setting (?) -*/ - -struct af9013_config { - /* - * I2C address - */ - u8 i2c_addr; - - /* - * clock - * 20480000, 25000000, 28000000, 28800000 - */ - u32 clock; - - /* - * tuner - */ -#define AF9013_TUNER_MXL5003D 3 /* MaxLinear */ -#define AF9013_TUNER_MXL5005D 13 /* MaxLinear */ -#define AF9013_TUNER_MXL5005R 30 /* MaxLinear */ -#define AF9013_TUNER_ENV77H11D5 129 /* Panasonic */ -#define AF9013_TUNER_MT2060 130 /* Microtune */ -#define AF9013_TUNER_MC44S803 133 /* Freescale */ -#define AF9013_TUNER_QT1010 134 /* Quantek */ -#define AF9013_TUNER_UNKNOWN 140 /* for can tuners ? */ -#define AF9013_TUNER_MT2060_2 147 /* Microtune */ -#define AF9013_TUNER_TDA18271 156 /* NXP */ -#define AF9013_TUNER_QT1010A 162 /* Quantek */ -#define AF9013_TUNER_MXL5007T 177 /* MaxLinear */ -#define AF9013_TUNER_TDA18218 179 /* NXP */ - u8 tuner; - - /* - * IF frequency - */ - u32 if_frequency; - - /* - * TS settings - */ -#define AF9013_TS_USB 0 -#define AF9013_TS_PARALLEL 1 -#define AF9013_TS_SERIAL 2 - u8 ts_mode:2; - - /* - * input spectrum inversion - */ - bool spec_inv; - - /* - * firmware API version - */ - u8 api_version[4]; - - /* - * GPIOs - */ -#define AF9013_GPIO_ON (1 << 0) -#define AF9013_GPIO_EN (1 << 1) -#define AF9013_GPIO_O (1 << 2) -#define AF9013_GPIO_I (1 << 3) -#define AF9013_GPIO_LO (AF9013_GPIO_ON|AF9013_GPIO_EN) -#define AF9013_GPIO_HI (AF9013_GPIO_ON|AF9013_GPIO_EN|AF9013_GPIO_O) -#define AF9013_GPIO_TUNER_ON (AF9013_GPIO_ON|AF9013_GPIO_EN) -#define AF9013_GPIO_TUNER_OFF (AF9013_GPIO_ON|AF9013_GPIO_EN|AF9013_GPIO_O) - u8 gpio[4]; -}; - -#if defined(CONFIG_DVB_AF9013) || \ - (defined(CONFIG_DVB_AF9013_MODULE) && defined(MODULE)) -extern struct dvb_frontend *af9013_attach(const struct af9013_config *config, - struct i2c_adapter *i2c); -#else -static inline struct dvb_frontend *af9013_attach( -const struct af9013_config *config, struct i2c_adapter *i2c) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif /* CONFIG_DVB_AF9013 */ - -#endif /* AF9013_H */ diff --git a/drivers/media/dvb/frontends/af9013_priv.h b/drivers/media/dvb/frontends/af9013_priv.h deleted file mode 100644 index fa848af6e9b4..000000000000 --- a/drivers/media/dvb/frontends/af9013_priv.h +++ /dev/null @@ -1,922 +0,0 @@ -/* - * Afatech AF9013 demodulator driver - * - * Copyright (C) 2007 Antti Palosaari - * Copyright (C) 2011 Antti Palosaari - * - * Thanks to Afatech who kindly provided information. - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef AF9013_PRIV_H -#define AF9013_PRIV_H - -#include "dvb_frontend.h" -#include "af9013.h" -#include - -#define LOG_PREFIX "af9013" - -#undef dbg -#define dbg(f, arg...) \ - if (af9013_debug) \ - printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef err -#define err(f, arg...) printk(KERN_ERR LOG_PREFIX": " f "\n" , ## arg) -#undef info -#define info(f, arg...) printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef warn -#define warn(f, arg...) printk(KERN_WARNING LOG_PREFIX": " f "\n" , ## arg) - -#define AF9013_DEFAULT_FIRMWARE "dvb-fe-af9013.fw" - -struct af9013_reg_bit { - u16 addr; - u8 pos:4; - u8 len:4; - u8 val; -}; - -struct af9013_snr { - u32 val; - u8 snr; -}; - -struct af9013_coeff { - u32 clock; - u32 bandwidth_hz; - u8 val[24]; -}; - -/* pre-calculated coeff lookup table */ -static const struct af9013_coeff coeff_lut[] = { - /* 28.800 MHz */ - { 28800000, 8000000, { 0x02, 0x8a, 0x28, 0xa3, 0x05, 0x14, - 0x51, 0x11, 0x00, 0xa2, 0x8f, 0x3d, 0x00, 0xa2, 0x8a, - 0x29, 0x00, 0xa2, 0x85, 0x14, 0x01, 0x45, 0x14, 0x14 } }, - { 28800000, 7000000, { 0x02, 0x38, 0xe3, 0x8e, 0x04, 0x71, - 0xc7, 0x07, 0x00, 0x8e, 0x3d, 0x55, 0x00, 0x8e, 0x38, - 0xe4, 0x00, 0x8e, 0x34, 0x72, 0x01, 0x1c, 0x71, 0x32 } }, - { 28800000, 6000000, { 0x01, 0xe7, 0x9e, 0x7a, 0x03, 0xcf, - 0x3c, 0x3d, 0x00, 0x79, 0xeb, 0x6e, 0x00, 0x79, 0xe7, - 0x9e, 0x00, 0x79, 0xe3, 0xcf, 0x00, 0xf3, 0xcf, 0x0f } }, - /* 20.480 MHz */ - { 20480000, 8000000, { 0x03, 0x92, 0x49, 0x26, 0x07, 0x24, - 0x92, 0x13, 0x00, 0xe4, 0x99, 0x6e, 0x00, 0xe4, 0x92, - 0x49, 0x00, 0xe4, 0x8b, 0x25, 0x01, 0xc9, 0x24, 0x25 } }, - { 20480000, 7000000, { 0x03, 0x20, 0x00, 0x01, 0x06, 0x40, - 0x00, 0x00, 0x00, 0xc8, 0x06, 0x40, 0x00, 0xc8, 0x00, - 0x00, 0x00, 0xc7, 0xf9, 0xc0, 0x01, 0x90, 0x00, 0x00 } }, - { 20480000, 6000000, { 0x02, 0xad, 0xb6, 0xdc, 0x05, 0x5b, - 0x6d, 0x2e, 0x00, 0xab, 0x73, 0x13, 0x00, 0xab, 0x6d, - 0xb7, 0x00, 0xab, 0x68, 0x5c, 0x01, 0x56, 0xdb, 0x1c } }, - /* 28.000 MHz */ - { 28000000, 8000000, { 0x02, 0x9c, 0xbc, 0x15, 0x05, 0x39, - 0x78, 0x0a, 0x00, 0xa7, 0x34, 0x3f, 0x00, 0xa7, 0x2f, - 0x05, 0x00, 0xa7, 0x29, 0xcc, 0x01, 0x4e, 0x5e, 0x03 } }, - { 28000000, 7000000, { 0x02, 0x49, 0x24, 0x92, 0x04, 0x92, - 0x49, 0x09, 0x00, 0x92, 0x4d, 0xb7, 0x00, 0x92, 0x49, - 0x25, 0x00, 0x92, 0x44, 0x92, 0x01, 0x24, 0x92, 0x12 } }, - { 28000000, 6000000, { 0x01, 0xf5, 0x8d, 0x10, 0x03, 0xeb, - 0x1a, 0x08, 0x00, 0x7d, 0x67, 0x2f, 0x00, 0x7d, 0x63, - 0x44, 0x00, 0x7d, 0x5f, 0x59, 0x00, 0xfa, 0xc6, 0x22 } }, - /* 25.000 MHz */ - { 25000000, 8000000, { 0x02, 0xec, 0xfb, 0x9d, 0x05, 0xd9, - 0xf7, 0x0e, 0x00, 0xbb, 0x44, 0xc1, 0x00, 0xbb, 0x3e, - 0xe7, 0x00, 0xbb, 0x39, 0x0d, 0x01, 0x76, 0x7d, 0x34 } }, - { 25000000, 7000000, { 0x02, 0x8f, 0x5c, 0x29, 0x05, 0x1e, - 0xb8, 0x14, 0x00, 0xa3, 0xdc, 0x29, 0x00, 0xa3, 0xd7, - 0x0a, 0x00, 0xa3, 0xd1, 0xec, 0x01, 0x47, 0xae, 0x05 } }, - { 25000000, 6000000, { 0x02, 0x31, 0xbc, 0xb5, 0x04, 0x63, - 0x79, 0x1b, 0x00, 0x8c, 0x73, 0x91, 0x00, 0x8c, 0x6f, - 0x2d, 0x00, 0x8c, 0x6a, 0xca, 0x01, 0x18, 0xde, 0x17 } }, -}; - -/* QPSK SNR lookup table */ -static const struct af9013_snr qpsk_snr_lut[] = { - { 0x000000, 0 }, - { 0x0b4771, 0 }, - { 0x0c1aed, 1 }, - { 0x0d0d27, 2 }, - { 0x0e4d19, 3 }, - { 0x0e5da8, 4 }, - { 0x107097, 5 }, - { 0x116975, 6 }, - { 0x1252d9, 7 }, - { 0x131fa4, 8 }, - { 0x13d5e1, 9 }, - { 0x148e53, 10 }, - { 0x15358b, 11 }, - { 0x15dd29, 12 }, - { 0x168112, 13 }, - { 0x170b61, 14 }, - { 0xffffff, 15 }, -}; - -/* QAM16 SNR lookup table */ -static const struct af9013_snr qam16_snr_lut[] = { - { 0x000000, 0 }, - { 0x05eb62, 5 }, - { 0x05fecf, 6 }, - { 0x060b80, 7 }, - { 0x062501, 8 }, - { 0x064865, 9 }, - { 0x069604, 10 }, - { 0x06f356, 11 }, - { 0x07706a, 12 }, - { 0x0804d3, 13 }, - { 0x089d1a, 14 }, - { 0x093e3d, 15 }, - { 0x09e35d, 16 }, - { 0x0a7c3c, 17 }, - { 0x0afaf8, 18 }, - { 0x0b719d, 19 }, - { 0xffffff, 20 }, -}; - -/* QAM64 SNR lookup table */ -static const struct af9013_snr qam64_snr_lut[] = { - { 0x000000, 0 }, - { 0x03109b, 12 }, - { 0x0310d4, 13 }, - { 0x031920, 14 }, - { 0x0322d0, 15 }, - { 0x0339fc, 16 }, - { 0x0364a1, 17 }, - { 0x038bcc, 18 }, - { 0x03c7d3, 19 }, - { 0x0408cc, 20 }, - { 0x043bed, 21 }, - { 0x048061, 22 }, - { 0x04be95, 23 }, - { 0x04fa7d, 24 }, - { 0x052405, 25 }, - { 0x05570d, 26 }, - { 0xffffff, 27 }, -}; - -static const struct af9013_reg_bit ofsm_init[] = { - { 0xd73a, 0, 8, 0xa1 }, - { 0xd73b, 0, 8, 0x1f }, - { 0xd73c, 4, 4, 0x0a }, - { 0xd732, 3, 1, 0x00 }, - { 0xd731, 4, 2, 0x03 }, - { 0xd73d, 7, 1, 0x01 }, - { 0xd740, 0, 1, 0x00 }, - { 0xd740, 1, 1, 0x00 }, - { 0xd740, 2, 1, 0x00 }, - { 0xd740, 3, 1, 0x01 }, - { 0xd3c1, 4, 1, 0x01 }, - { 0x9124, 0, 8, 0x58 }, - { 0x9125, 0, 2, 0x02 }, - { 0xd3a2, 0, 8, 0x00 }, - { 0xd3a3, 0, 8, 0x04 }, - { 0xd305, 0, 8, 0x32 }, - { 0xd306, 0, 8, 0x10 }, - { 0xd304, 0, 8, 0x04 }, - { 0x9112, 0, 1, 0x01 }, - { 0x911d, 0, 1, 0x01 }, - { 0x911a, 0, 1, 0x01 }, - { 0x911b, 0, 1, 0x01 }, - { 0x9bce, 0, 4, 0x02 }, - { 0x9116, 0, 1, 0x01 }, - { 0x9122, 0, 8, 0xd0 }, - { 0xd2e0, 0, 8, 0xd0 }, - { 0xd2e9, 0, 4, 0x0d }, - { 0xd38c, 0, 8, 0xfc }, - { 0xd38d, 0, 8, 0x00 }, - { 0xd38e, 0, 8, 0x7e }, - { 0xd38f, 0, 8, 0x00 }, - { 0xd390, 0, 8, 0x2f }, - { 0xd145, 4, 1, 0x01 }, - { 0xd1a9, 4, 1, 0x01 }, - { 0xd158, 5, 3, 0x01 }, - { 0xd159, 0, 6, 0x06 }, - { 0xd167, 0, 8, 0x00 }, - { 0xd168, 0, 4, 0x07 }, - { 0xd1c3, 5, 3, 0x00 }, - { 0xd1c4, 0, 6, 0x00 }, - { 0xd1c5, 0, 7, 0x10 }, - { 0xd1c6, 0, 3, 0x02 }, - { 0xd080, 2, 5, 0x03 }, - { 0xd081, 4, 4, 0x09 }, - { 0xd098, 4, 4, 0x0f }, - { 0xd098, 0, 4, 0x03 }, - { 0xdbc0, 4, 1, 0x01 }, - { 0xdbc7, 0, 8, 0x08 }, - { 0xdbc8, 4, 4, 0x00 }, - { 0xdbc9, 0, 5, 0x01 }, - { 0xd280, 0, 8, 0xe0 }, - { 0xd281, 0, 8, 0xff }, - { 0xd282, 0, 8, 0xff }, - { 0xd283, 0, 8, 0xc3 }, - { 0xd284, 0, 8, 0xff }, - { 0xd285, 0, 4, 0x01 }, - { 0xd0f0, 0, 7, 0x1a }, - { 0xd0f1, 4, 1, 0x01 }, - { 0xd0f2, 0, 8, 0x0c }, - { 0xd101, 5, 3, 0x06 }, - { 0xd103, 0, 4, 0x08 }, - { 0xd0f8, 0, 7, 0x20 }, - { 0xd111, 5, 1, 0x00 }, - { 0xd111, 6, 1, 0x00 }, - { 0x910b, 0, 8, 0x0a }, - { 0x9115, 0, 8, 0x02 }, - { 0x910c, 0, 8, 0x02 }, - { 0x910d, 0, 8, 0x08 }, - { 0x910e, 0, 8, 0x0a }, - { 0x9bf6, 0, 8, 0x06 }, - { 0x9bf8, 0, 8, 0x02 }, - { 0x9bf7, 0, 8, 0x05 }, - { 0x9bf9, 0, 8, 0x0f }, - { 0x9bfc, 0, 8, 0x13 }, - { 0x9bd3, 0, 8, 0xff }, - { 0x9bbe, 0, 1, 0x01 }, - { 0x9bcc, 0, 1, 0x01 }, -}; - -/* Panasonic ENV77H11D5 tuner init - AF9013_TUNER_ENV77H11D5 = 129 */ -static const struct af9013_reg_bit tuner_init_env77h11d5[] = { - { 0x9bd5, 0, 8, 0x01 }, - { 0x9bd6, 0, 8, 0x03 }, - { 0x9bbe, 0, 8, 0x01 }, - { 0xd1a0, 1, 1, 0x01 }, - { 0xd000, 0, 1, 0x01 }, - { 0xd000, 1, 1, 0x00 }, - { 0xd001, 1, 1, 0x01 }, - { 0xd001, 0, 1, 0x00 }, - { 0xd001, 5, 1, 0x00 }, - { 0xd002, 0, 5, 0x19 }, - { 0xd003, 0, 5, 0x1a }, - { 0xd004, 0, 5, 0x19 }, - { 0xd005, 0, 5, 0x1a }, - { 0xd00e, 0, 5, 0x10 }, - { 0xd00f, 0, 3, 0x04 }, - { 0xd00f, 3, 3, 0x05 }, - { 0xd010, 0, 3, 0x04 }, - { 0xd010, 3, 3, 0x05 }, - { 0xd016, 4, 4, 0x03 }, - { 0xd01f, 0, 6, 0x0a }, - { 0xd020, 0, 6, 0x0a }, - { 0x9bda, 0, 8, 0x00 }, - { 0x9be3, 0, 8, 0x00 }, - { 0xd015, 0, 8, 0x50 }, - { 0xd016, 0, 1, 0x00 }, - { 0xd044, 0, 8, 0x46 }, - { 0xd045, 0, 1, 0x00 }, - { 0xd008, 0, 8, 0xdf }, - { 0xd009, 0, 2, 0x02 }, - { 0xd006, 0, 8, 0x44 }, - { 0xd007, 0, 2, 0x01 }, - { 0xd00c, 0, 8, 0xeb }, - { 0xd00d, 0, 2, 0x02 }, - { 0xd00a, 0, 8, 0xf4 }, - { 0xd00b, 0, 2, 0x01 }, - { 0x9bba, 0, 8, 0xf9 }, - { 0x9bc3, 0, 8, 0xdf }, - { 0x9bc4, 0, 8, 0x02 }, - { 0x9bc5, 0, 8, 0xeb }, - { 0x9bc6, 0, 8, 0x02 }, - { 0x9bc9, 0, 8, 0x52 }, - { 0xd011, 0, 8, 0x3c }, - { 0xd012, 0, 2, 0x01 }, - { 0xd013, 0, 8, 0xf7 }, - { 0xd014, 0, 2, 0x02 }, - { 0xd040, 0, 8, 0x0b }, - { 0xd041, 0, 2, 0x02 }, - { 0xd042, 0, 8, 0x4d }, - { 0xd043, 0, 2, 0x00 }, - { 0xd045, 1, 1, 0x00 }, - { 0x9bcf, 0, 1, 0x01 }, - { 0xd045, 2, 1, 0x01 }, - { 0xd04f, 0, 8, 0x9a }, - { 0xd050, 0, 1, 0x01 }, - { 0xd051, 0, 8, 0x5a }, - { 0xd052, 0, 1, 0x01 }, - { 0xd053, 0, 8, 0x50 }, - { 0xd054, 0, 8, 0x46 }, - { 0x9bd7, 0, 8, 0x0a }, - { 0x9bd8, 0, 8, 0x14 }, - { 0x9bd9, 0, 8, 0x08 }, -}; - -/* Microtune MT2060 tuner init - AF9013_TUNER_MT2060 = 130 */ -static const struct af9013_reg_bit tuner_init_mt2060[] = { - { 0x9bd5, 0, 8, 0x01 }, - { 0x9bd6, 0, 8, 0x07 }, - { 0xd1a0, 1, 1, 0x01 }, - { 0xd000, 0, 1, 0x01 }, - { 0xd000, 1, 1, 0x00 }, - { 0xd001, 1, 1, 0x01 }, - { 0xd001, 0, 1, 0x00 }, - { 0xd001, 5, 1, 0x00 }, - { 0xd002, 0, 5, 0x19 }, - { 0xd003, 0, 5, 0x1a }, - { 0xd004, 0, 5, 0x19 }, - { 0xd005, 0, 5, 0x1a }, - { 0xd00e, 0, 5, 0x10 }, - { 0xd00f, 0, 3, 0x04 }, - { 0xd00f, 3, 3, 0x05 }, - { 0xd010, 0, 3, 0x04 }, - { 0xd010, 3, 3, 0x05 }, - { 0xd016, 4, 4, 0x03 }, - { 0xd01f, 0, 6, 0x0a }, - { 0xd020, 0, 6, 0x0a }, - { 0x9bda, 0, 8, 0x00 }, - { 0x9be3, 0, 8, 0x00 }, - { 0x9bbe, 0, 1, 0x00 }, - { 0x9bcc, 0, 1, 0x00 }, - { 0x9bb9, 0, 8, 0x75 }, - { 0x9bcd, 0, 8, 0x24 }, - { 0x9bff, 0, 8, 0x30 }, - { 0xd015, 0, 8, 0x46 }, - { 0xd016, 0, 1, 0x00 }, - { 0xd044, 0, 8, 0x46 }, - { 0xd045, 0, 1, 0x00 }, - { 0xd008, 0, 8, 0x0f }, - { 0xd009, 0, 2, 0x02 }, - { 0xd006, 0, 8, 0x32 }, - { 0xd007, 0, 2, 0x01 }, - { 0xd00c, 0, 8, 0x36 }, - { 0xd00d, 0, 2, 0x03 }, - { 0xd00a, 0, 8, 0x35 }, - { 0xd00b, 0, 2, 0x01 }, - { 0x9bc7, 0, 8, 0x07 }, - { 0x9bc8, 0, 8, 0x90 }, - { 0x9bc3, 0, 8, 0x0f }, - { 0x9bc4, 0, 8, 0x02 }, - { 0x9bc5, 0, 8, 0x36 }, - { 0x9bc6, 0, 8, 0x03 }, - { 0x9bba, 0, 8, 0xc9 }, - { 0x9bc9, 0, 8, 0x79 }, - { 0xd011, 0, 8, 0x10 }, - { 0xd012, 0, 2, 0x01 }, - { 0xd013, 0, 8, 0x45 }, - { 0xd014, 0, 2, 0x03 }, - { 0xd040, 0, 8, 0x98 }, - { 0xd041, 0, 2, 0x00 }, - { 0xd042, 0, 8, 0xcf }, - { 0xd043, 0, 2, 0x03 }, - { 0xd045, 1, 1, 0x00 }, - { 0x9bcf, 0, 1, 0x01 }, - { 0xd045, 2, 1, 0x01 }, - { 0xd04f, 0, 8, 0x9a }, - { 0xd050, 0, 1, 0x01 }, - { 0xd051, 0, 8, 0x5a }, - { 0xd052, 0, 1, 0x01 }, - { 0xd053, 0, 8, 0x50 }, - { 0xd054, 0, 8, 0x46 }, - { 0x9bd7, 0, 8, 0x0a }, - { 0x9bd8, 0, 8, 0x14 }, - { 0x9bd9, 0, 8, 0x08 }, - { 0x9bd0, 0, 8, 0xcc }, - { 0x9be4, 0, 8, 0xa0 }, - { 0x9bbd, 0, 8, 0x8e }, - { 0x9be2, 0, 8, 0x4d }, - { 0x9bee, 0, 1, 0x01 }, -}; - -/* Microtune MT2060 tuner init - AF9013_TUNER_MT2060_2 = 147 */ -static const struct af9013_reg_bit tuner_init_mt2060_2[] = { - { 0x9bd5, 0, 8, 0x01 }, - { 0x9bd6, 0, 8, 0x06 }, - { 0x9bbe, 0, 8, 0x01 }, - { 0xd1a0, 1, 1, 0x01 }, - { 0xd000, 0, 1, 0x01 }, - { 0xd000, 1, 1, 0x00 }, - { 0xd001, 1, 1, 0x01 }, - { 0xd001, 0, 1, 0x00 }, - { 0xd001, 5, 1, 0x00 }, - { 0xd002, 0, 5, 0x19 }, - { 0xd003, 0, 5, 0x1a }, - { 0xd004, 0, 5, 0x19 }, - { 0xd005, 0, 5, 0x1a }, - { 0xd00e, 0, 5, 0x10 }, - { 0xd00f, 0, 3, 0x04 }, - { 0xd00f, 3, 3, 0x05 }, - { 0xd010, 0, 3, 0x04 }, - { 0xd010, 3, 3, 0x05 }, - { 0xd016, 4, 4, 0x03 }, - { 0xd01f, 0, 6, 0x0a }, - { 0xd020, 0, 6, 0x0a }, - { 0xd015, 0, 8, 0x46 }, - { 0xd016, 0, 1, 0x00 }, - { 0xd044, 0, 8, 0x46 }, - { 0xd045, 0, 1, 0x00 }, - { 0xd008, 0, 8, 0x0f }, - { 0xd009, 0, 2, 0x02 }, - { 0xd006, 0, 8, 0x32 }, - { 0xd007, 0, 2, 0x01 }, - { 0xd00c, 0, 8, 0x36 }, - { 0xd00d, 0, 2, 0x03 }, - { 0xd00a, 0, 8, 0x35 }, - { 0xd00b, 0, 2, 0x01 }, - { 0x9bc7, 0, 8, 0x07 }, - { 0x9bc8, 0, 8, 0x90 }, - { 0x9bc3, 0, 8, 0x0f }, - { 0x9bc4, 0, 8, 0x02 }, - { 0x9bc5, 0, 8, 0x36 }, - { 0x9bc6, 0, 8, 0x03 }, - { 0x9bba, 0, 8, 0xc9 }, - { 0x9bc9, 0, 8, 0x79 }, - { 0xd011, 0, 8, 0x10 }, - { 0xd012, 0, 2, 0x01 }, - { 0xd013, 0, 8, 0x45 }, - { 0xd014, 0, 2, 0x03 }, - { 0xd040, 0, 8, 0x98 }, - { 0xd041, 0, 2, 0x00 }, - { 0xd042, 0, 8, 0xcf }, - { 0xd043, 0, 2, 0x03 }, - { 0xd045, 1, 1, 0x00 }, - { 0x9bcf, 0, 8, 0x01 }, - { 0xd045, 2, 1, 0x01 }, - { 0xd04f, 0, 8, 0x9a }, - { 0xd050, 0, 1, 0x01 }, - { 0xd051, 0, 8, 0x5a }, - { 0xd052, 0, 1, 0x01 }, - { 0xd053, 0, 8, 0x96 }, - { 0xd054, 0, 8, 0x46 }, - { 0xd045, 7, 1, 0x00 }, - { 0x9bd7, 0, 8, 0x0a }, - { 0x9bd8, 0, 8, 0x14 }, - { 0x9bd9, 0, 8, 0x08 }, -}; - -/* MaxLinear MXL5003 tuner init - AF9013_TUNER_MXL5003D = 3 */ -static const struct af9013_reg_bit tuner_init_mxl5003d[] = { - { 0x9bd5, 0, 8, 0x01 }, - { 0x9bd6, 0, 8, 0x09 }, - { 0xd1a0, 1, 1, 0x01 }, - { 0xd000, 0, 1, 0x01 }, - { 0xd000, 1, 1, 0x00 }, - { 0xd001, 1, 1, 0x01 }, - { 0xd001, 0, 1, 0x00 }, - { 0xd001, 5, 1, 0x00 }, - { 0xd002, 0, 5, 0x19 }, - { 0xd003, 0, 5, 0x1a }, - { 0xd004, 0, 5, 0x19 }, - { 0xd005, 0, 5, 0x1a }, - { 0xd00e, 0, 5, 0x10 }, - { 0xd00f, 0, 3, 0x04 }, - { 0xd00f, 3, 3, 0x05 }, - { 0xd010, 0, 3, 0x04 }, - { 0xd010, 3, 3, 0x05 }, - { 0xd016, 4, 4, 0x03 }, - { 0xd01f, 0, 6, 0x0a }, - { 0xd020, 0, 6, 0x0a }, - { 0x9bda, 0, 8, 0x00 }, - { 0x9be3, 0, 8, 0x00 }, - { 0x9bfc, 0, 8, 0x0f }, - { 0x9bf6, 0, 8, 0x01 }, - { 0x9bbe, 0, 1, 0x01 }, - { 0xd015, 0, 8, 0x33 }, - { 0xd016, 0, 1, 0x00 }, - { 0xd044, 0, 8, 0x40 }, - { 0xd045, 0, 1, 0x00 }, - { 0xd008, 0, 8, 0x0f }, - { 0xd009, 0, 2, 0x02 }, - { 0xd006, 0, 8, 0x6c }, - { 0xd007, 0, 2, 0x00 }, - { 0xd00c, 0, 8, 0x3d }, - { 0xd00d, 0, 2, 0x00 }, - { 0xd00a, 0, 8, 0x45 }, - { 0xd00b, 0, 2, 0x01 }, - { 0x9bc7, 0, 8, 0x07 }, - { 0x9bc8, 0, 8, 0x52 }, - { 0x9bc3, 0, 8, 0x0f }, - { 0x9bc4, 0, 8, 0x02 }, - { 0x9bc5, 0, 8, 0x3d }, - { 0x9bc6, 0, 8, 0x00 }, - { 0x9bba, 0, 8, 0xa2 }, - { 0x9bc9, 0, 8, 0xa0 }, - { 0xd011, 0, 8, 0x56 }, - { 0xd012, 0, 2, 0x00 }, - { 0xd013, 0, 8, 0x50 }, - { 0xd014, 0, 2, 0x00 }, - { 0xd040, 0, 8, 0x56 }, - { 0xd041, 0, 2, 0x00 }, - { 0xd042, 0, 8, 0x50 }, - { 0xd043, 0, 2, 0x00 }, - { 0xd045, 1, 1, 0x00 }, - { 0x9bcf, 0, 8, 0x01 }, - { 0xd045, 2, 1, 0x01 }, - { 0xd04f, 0, 8, 0x9a }, - { 0xd050, 0, 1, 0x01 }, - { 0xd051, 0, 8, 0x5a }, - { 0xd052, 0, 1, 0x01 }, - { 0xd053, 0, 8, 0x50 }, - { 0xd054, 0, 8, 0x46 }, - { 0x9bd7, 0, 8, 0x0a }, - { 0x9bd8, 0, 8, 0x14 }, - { 0x9bd9, 0, 8, 0x08 }, -}; - -/* MaxLinear MXL5005S & MXL5007T tuner init - AF9013_TUNER_MXL5005D = 13 - AF9013_TUNER_MXL5005R = 30 - AF9013_TUNER_MXL5007T = 177 */ -static const struct af9013_reg_bit tuner_init_mxl5005[] = { - { 0x9bd5, 0, 8, 0x01 }, - { 0x9bd6, 0, 8, 0x07 }, - { 0xd1a0, 1, 1, 0x01 }, - { 0xd000, 0, 1, 0x01 }, - { 0xd000, 1, 1, 0x00 }, - { 0xd001, 1, 1, 0x01 }, - { 0xd001, 0, 1, 0x00 }, - { 0xd001, 5, 1, 0x00 }, - { 0xd002, 0, 5, 0x19 }, - { 0xd003, 0, 5, 0x1a }, - { 0xd004, 0, 5, 0x19 }, - { 0xd005, 0, 5, 0x1a }, - { 0xd00e, 0, 5, 0x10 }, - { 0xd00f, 0, 3, 0x04 }, - { 0xd00f, 3, 3, 0x05 }, - { 0xd010, 0, 3, 0x04 }, - { 0xd010, 3, 3, 0x05 }, - { 0xd016, 4, 4, 0x03 }, - { 0xd01f, 0, 6, 0x0a }, - { 0xd020, 0, 6, 0x0a }, - { 0x9bda, 0, 8, 0x01 }, - { 0x9be3, 0, 8, 0x01 }, - { 0x9bbe, 0, 1, 0x01 }, - { 0x9bcc, 0, 1, 0x01 }, - { 0x9bb9, 0, 8, 0x00 }, - { 0x9bcd, 0, 8, 0x28 }, - { 0x9bff, 0, 8, 0x24 }, - { 0xd015, 0, 8, 0x40 }, - { 0xd016, 0, 1, 0x00 }, - { 0xd044, 0, 8, 0x40 }, - { 0xd045, 0, 1, 0x00 }, - { 0xd008, 0, 8, 0x0f }, - { 0xd009, 0, 2, 0x02 }, - { 0xd006, 0, 8, 0x73 }, - { 0xd007, 0, 2, 0x01 }, - { 0xd00c, 0, 8, 0xfa }, - { 0xd00d, 0, 2, 0x01 }, - { 0xd00a, 0, 8, 0xff }, - { 0xd00b, 0, 2, 0x01 }, - { 0x9bc7, 0, 8, 0x23 }, - { 0x9bc8, 0, 8, 0x55 }, - { 0x9bc3, 0, 8, 0x01 }, - { 0x9bc4, 0, 8, 0x02 }, - { 0x9bc5, 0, 8, 0xfa }, - { 0x9bc6, 0, 8, 0x01 }, - { 0x9bba, 0, 8, 0xff }, - { 0x9bc9, 0, 8, 0xff }, - { 0x9bd3, 0, 8, 0x95 }, - { 0xd011, 0, 8, 0x70 }, - { 0xd012, 0, 2, 0x01 }, - { 0xd013, 0, 8, 0xfb }, - { 0xd014, 0, 2, 0x01 }, - { 0xd040, 0, 8, 0x70 }, - { 0xd041, 0, 2, 0x01 }, - { 0xd042, 0, 8, 0xfb }, - { 0xd043, 0, 2, 0x01 }, - { 0xd045, 1, 1, 0x00 }, - { 0x9bcf, 0, 1, 0x01 }, - { 0xd045, 2, 1, 0x01 }, - { 0xd04f, 0, 8, 0x9a }, - { 0xd050, 0, 1, 0x01 }, - { 0xd051, 0, 8, 0x5a }, - { 0xd052, 0, 1, 0x01 }, - { 0xd053, 0, 8, 0x50 }, - { 0xd054, 0, 8, 0x46 }, - { 0x9bd7, 0, 8, 0x0a }, - { 0x9bd8, 0, 8, 0x14 }, - { 0x9bd9, 0, 8, 0x08 }, - { 0x9bd0, 0, 8, 0x93 }, - { 0x9be4, 0, 8, 0xfe }, - { 0x9bbd, 0, 8, 0x63 }, - { 0x9be2, 0, 8, 0xfe }, - { 0x9bee, 0, 1, 0x01 }, -}; - -/* Quantek QT1010 tuner init - AF9013_TUNER_QT1010 = 134 - AF9013_TUNER_QT1010A = 162 */ -static const struct af9013_reg_bit tuner_init_qt1010[] = { - { 0x9bd5, 0, 8, 0x01 }, - { 0x9bd6, 0, 8, 0x09 }, - { 0xd1a0, 1, 1, 0x01 }, - { 0xd000, 0, 1, 0x01 }, - { 0xd000, 1, 1, 0x00 }, - { 0xd001, 1, 1, 0x01 }, - { 0xd001, 0, 1, 0x00 }, - { 0xd001, 5, 1, 0x00 }, - { 0xd002, 0, 5, 0x19 }, - { 0xd003, 0, 5, 0x1a }, - { 0xd004, 0, 5, 0x19 }, - { 0xd005, 0, 5, 0x1a }, - { 0xd00e, 0, 5, 0x10 }, - { 0xd00f, 0, 3, 0x04 }, - { 0xd00f, 3, 3, 0x05 }, - { 0xd010, 0, 3, 0x04 }, - { 0xd010, 3, 3, 0x05 }, - { 0xd016, 4, 4, 0x03 }, - { 0xd01f, 0, 6, 0x0a }, - { 0xd020, 0, 6, 0x0a }, - { 0x9bda, 0, 8, 0x01 }, - { 0x9be3, 0, 8, 0x01 }, - { 0xd015, 0, 8, 0x46 }, - { 0xd016, 0, 1, 0x00 }, - { 0xd044, 0, 8, 0x46 }, - { 0xd045, 0, 1, 0x00 }, - { 0x9bbe, 0, 1, 0x01 }, - { 0x9bcc, 0, 1, 0x01 }, - { 0x9bb9, 0, 8, 0x00 }, - { 0x9bcd, 0, 8, 0x28 }, - { 0x9bff, 0, 8, 0x20 }, - { 0xd008, 0, 8, 0x0f }, - { 0xd009, 0, 2, 0x02 }, - { 0xd006, 0, 8, 0x99 }, - { 0xd007, 0, 2, 0x01 }, - { 0xd00c, 0, 8, 0x0f }, - { 0xd00d, 0, 2, 0x02 }, - { 0xd00a, 0, 8, 0x50 }, - { 0xd00b, 0, 2, 0x01 }, - { 0x9bc7, 0, 8, 0x00 }, - { 0x9bc8, 0, 8, 0x00 }, - { 0x9bc3, 0, 8, 0x0f }, - { 0x9bc4, 0, 8, 0x02 }, - { 0x9bc5, 0, 8, 0x0f }, - { 0x9bc6, 0, 8, 0x02 }, - { 0x9bba, 0, 8, 0xc5 }, - { 0x9bc9, 0, 8, 0xff }, - { 0xd011, 0, 8, 0x58 }, - { 0xd012, 0, 2, 0x02 }, - { 0xd013, 0, 8, 0x89 }, - { 0xd014, 0, 2, 0x01 }, - { 0xd040, 0, 8, 0x58 }, - { 0xd041, 0, 2, 0x02 }, - { 0xd042, 0, 8, 0x89 }, - { 0xd043, 0, 2, 0x01 }, - { 0xd045, 1, 1, 0x00 }, - { 0x9bcf, 0, 1, 0x01 }, - { 0xd045, 2, 1, 0x01 }, - { 0xd04f, 0, 8, 0x9a }, - { 0xd050, 0, 1, 0x01 }, - { 0xd051, 0, 8, 0x5a }, - { 0xd052, 0, 1, 0x01 }, - { 0xd053, 0, 8, 0x50 }, - { 0xd054, 0, 8, 0x46 }, - { 0x9bd7, 0, 8, 0x0a }, - { 0x9bd8, 0, 8, 0x14 }, - { 0x9bd9, 0, 8, 0x08 }, - { 0x9bd0, 0, 8, 0xcd }, - { 0x9be4, 0, 8, 0xbb }, - { 0x9bbd, 0, 8, 0x93 }, - { 0x9be2, 0, 8, 0x80 }, - { 0x9bee, 0, 1, 0x01 }, -}; - -/* Freescale MC44S803 tuner init - AF9013_TUNER_MC44S803 = 133 */ -static const struct af9013_reg_bit tuner_init_mc44s803[] = { - { 0x9bd5, 0, 8, 0x01 }, - { 0x9bd6, 0, 8, 0x06 }, - { 0xd1a0, 1, 1, 0x01 }, - { 0xd000, 0, 1, 0x01 }, - { 0xd000, 1, 1, 0x00 }, - { 0xd001, 1, 1, 0x01 }, - { 0xd001, 0, 1, 0x00 }, - { 0xd001, 5, 1, 0x00 }, - { 0xd002, 0, 5, 0x19 }, - { 0xd003, 0, 5, 0x1a }, - { 0xd004, 0, 5, 0x19 }, - { 0xd005, 0, 5, 0x1a }, - { 0xd00e, 0, 5, 0x10 }, - { 0xd00f, 0, 3, 0x04 }, - { 0xd00f, 3, 3, 0x05 }, - { 0xd010, 0, 3, 0x04 }, - { 0xd010, 3, 3, 0x05 }, - { 0xd016, 4, 4, 0x03 }, - { 0xd01f, 0, 6, 0x0a }, - { 0xd020, 0, 6, 0x0a }, - { 0x9bda, 0, 8, 0x00 }, - { 0x9be3, 0, 8, 0x00 }, - { 0x9bf6, 0, 8, 0x01 }, - { 0x9bf8, 0, 8, 0x02 }, - { 0x9bf9, 0, 8, 0x02 }, - { 0x9bfc, 0, 8, 0x1f }, - { 0x9bbe, 0, 1, 0x01 }, - { 0x9bcc, 0, 1, 0x01 }, - { 0x9bb9, 0, 8, 0x00 }, - { 0x9bcd, 0, 8, 0x24 }, - { 0x9bff, 0, 8, 0x24 }, - { 0xd015, 0, 8, 0x46 }, - { 0xd016, 0, 1, 0x00 }, - { 0xd044, 0, 8, 0x46 }, - { 0xd045, 0, 1, 0x00 }, - { 0xd008, 0, 8, 0x01 }, - { 0xd009, 0, 2, 0x02 }, - { 0xd006, 0, 8, 0x7b }, - { 0xd007, 0, 2, 0x00 }, - { 0xd00c, 0, 8, 0x7c }, - { 0xd00d, 0, 2, 0x02 }, - { 0xd00a, 0, 8, 0xfe }, - { 0xd00b, 0, 2, 0x01 }, - { 0x9bc7, 0, 8, 0x08 }, - { 0x9bc8, 0, 8, 0x9a }, - { 0x9bc3, 0, 8, 0x01 }, - { 0x9bc4, 0, 8, 0x02 }, - { 0x9bc5, 0, 8, 0x7c }, - { 0x9bc6, 0, 8, 0x02 }, - { 0x9bba, 0, 8, 0xfc }, - { 0x9bc9, 0, 8, 0xaa }, - { 0xd011, 0, 8, 0x6b }, - { 0xd012, 0, 2, 0x00 }, - { 0xd013, 0, 8, 0x88 }, - { 0xd014, 0, 2, 0x02 }, - { 0xd040, 0, 8, 0x6b }, - { 0xd041, 0, 2, 0x00 }, - { 0xd042, 0, 8, 0x7c }, - { 0xd043, 0, 2, 0x02 }, - { 0xd045, 1, 1, 0x00 }, - { 0x9bcf, 0, 1, 0x01 }, - { 0xd045, 2, 1, 0x01 }, - { 0xd04f, 0, 8, 0x9a }, - { 0xd050, 0, 1, 0x01 }, - { 0xd051, 0, 8, 0x5a }, - { 0xd052, 0, 1, 0x01 }, - { 0xd053, 0, 8, 0x50 }, - { 0xd054, 0, 8, 0x46 }, - { 0x9bd7, 0, 8, 0x0a }, - { 0x9bd8, 0, 8, 0x14 }, - { 0x9bd9, 0, 8, 0x08 }, - { 0x9bd0, 0, 8, 0x9e }, - { 0x9be4, 0, 8, 0xff }, - { 0x9bbd, 0, 8, 0x9e }, - { 0x9be2, 0, 8, 0x25 }, - { 0x9bee, 0, 1, 0x01 }, - { 0xd73b, 3, 1, 0x00 }, -}; - -/* unknown, probably for tin can tuner, tuner init - AF9013_TUNER_UNKNOWN = 140 */ -static const struct af9013_reg_bit tuner_init_unknown[] = { - { 0x9bd5, 0, 8, 0x01 }, - { 0x9bd6, 0, 8, 0x02 }, - { 0xd1a0, 1, 1, 0x01 }, - { 0xd000, 0, 1, 0x01 }, - { 0xd000, 1, 1, 0x00 }, - { 0xd001, 1, 1, 0x01 }, - { 0xd001, 0, 1, 0x00 }, - { 0xd001, 5, 1, 0x00 }, - { 0xd002, 0, 5, 0x19 }, - { 0xd003, 0, 5, 0x1a }, - { 0xd004, 0, 5, 0x19 }, - { 0xd005, 0, 5, 0x1a }, - { 0xd00e, 0, 5, 0x10 }, - { 0xd00f, 0, 3, 0x04 }, - { 0xd00f, 3, 3, 0x05 }, - { 0xd010, 0, 3, 0x04 }, - { 0xd010, 3, 3, 0x05 }, - { 0xd016, 4, 4, 0x03 }, - { 0xd01f, 0, 6, 0x0a }, - { 0xd020, 0, 6, 0x0a }, - { 0x9bda, 0, 8, 0x01 }, - { 0x9be3, 0, 8, 0x01 }, - { 0xd1a0, 1, 1, 0x00 }, - { 0x9bbe, 0, 1, 0x01 }, - { 0x9bcc, 0, 1, 0x01 }, - { 0x9bb9, 0, 8, 0x00 }, - { 0x9bcd, 0, 8, 0x18 }, - { 0x9bff, 0, 8, 0x2c }, - { 0xd015, 0, 8, 0x46 }, - { 0xd016, 0, 1, 0x00 }, - { 0xd044, 0, 8, 0x46 }, - { 0xd045, 0, 1, 0x00 }, - { 0xd008, 0, 8, 0xdf }, - { 0xd009, 0, 2, 0x02 }, - { 0xd006, 0, 8, 0x44 }, - { 0xd007, 0, 2, 0x01 }, - { 0xd00c, 0, 8, 0x00 }, - { 0xd00d, 0, 2, 0x02 }, - { 0xd00a, 0, 8, 0xf6 }, - { 0xd00b, 0, 2, 0x01 }, - { 0x9bba, 0, 8, 0xf9 }, - { 0x9bc8, 0, 8, 0xaa }, - { 0x9bc3, 0, 8, 0xdf }, - { 0x9bc4, 0, 8, 0x02 }, - { 0x9bc5, 0, 8, 0x00 }, - { 0x9bc6, 0, 8, 0x02 }, - { 0x9bc9, 0, 8, 0xf0 }, - { 0xd011, 0, 8, 0x3c }, - { 0xd012, 0, 2, 0x01 }, - { 0xd013, 0, 8, 0xf7 }, - { 0xd014, 0, 2, 0x02 }, - { 0xd040, 0, 8, 0x0b }, - { 0xd041, 0, 2, 0x02 }, - { 0xd042, 0, 8, 0x4d }, - { 0xd043, 0, 2, 0x00 }, - { 0xd045, 1, 1, 0x00 }, - { 0x9bcf, 0, 1, 0x01 }, - { 0xd045, 2, 1, 0x01 }, - { 0xd04f, 0, 8, 0x9a }, - { 0xd050, 0, 1, 0x01 }, - { 0xd051, 0, 8, 0x5a }, - { 0xd052, 0, 1, 0x01 }, - { 0xd053, 0, 8, 0x50 }, - { 0xd054, 0, 8, 0x46 }, - { 0x9bd7, 0, 8, 0x0a }, - { 0x9bd8, 0, 8, 0x14 }, - { 0x9bd9, 0, 8, 0x08 }, -}; - -/* NXP TDA18271 & TDA18218 tuner init - AF9013_TUNER_TDA18271 = 156 - AF9013_TUNER_TDA18218 = 179 */ -static const struct af9013_reg_bit tuner_init_tda18271[] = { - { 0x9bd5, 0, 8, 0x01 }, - { 0x9bd6, 0, 8, 0x04 }, - { 0xd1a0, 1, 1, 0x01 }, - { 0xd000, 0, 1, 0x01 }, - { 0xd000, 1, 1, 0x00 }, - { 0xd001, 1, 1, 0x01 }, - { 0xd001, 0, 1, 0x00 }, - { 0xd001, 5, 1, 0x00 }, - { 0xd002, 0, 5, 0x19 }, - { 0xd003, 0, 5, 0x1a }, - { 0xd004, 0, 5, 0x19 }, - { 0xd005, 0, 5, 0x1a }, - { 0xd00e, 0, 5, 0x10 }, - { 0xd00f, 0, 3, 0x04 }, - { 0xd00f, 3, 3, 0x05 }, - { 0xd010, 0, 3, 0x04 }, - { 0xd010, 3, 3, 0x05 }, - { 0xd016, 4, 4, 0x03 }, - { 0xd01f, 0, 6, 0x0a }, - { 0xd020, 0, 6, 0x0a }, - { 0x9bda, 0, 8, 0x01 }, - { 0x9be3, 0, 8, 0x01 }, - { 0xd1a0, 1, 1, 0x00 }, - { 0x9bbe, 0, 1, 0x01 }, - { 0x9bcc, 0, 1, 0x01 }, - { 0x9bb9, 0, 8, 0x00 }, - { 0x9bcd, 0, 8, 0x18 }, - { 0x9bff, 0, 8, 0x2c }, - { 0xd015, 0, 8, 0x46 }, - { 0xd016, 0, 1, 0x00 }, - { 0xd044, 0, 8, 0x46 }, - { 0xd045, 0, 1, 0x00 }, - { 0xd008, 0, 8, 0xdf }, - { 0xd009, 0, 2, 0x02 }, - { 0xd006, 0, 8, 0x44 }, - { 0xd007, 0, 2, 0x01 }, - { 0xd00c, 0, 8, 0x00 }, - { 0xd00d, 0, 2, 0x02 }, - { 0xd00a, 0, 8, 0xf6 }, - { 0xd00b, 0, 2, 0x01 }, - { 0x9bba, 0, 8, 0xf9 }, - { 0x9bc8, 0, 8, 0xaa }, - { 0x9bc3, 0, 8, 0xdf }, - { 0x9bc4, 0, 8, 0x02 }, - { 0x9bc5, 0, 8, 0x00 }, - { 0x9bc6, 0, 8, 0x02 }, - { 0x9bc9, 0, 8, 0xf0 }, - { 0xd011, 0, 8, 0x3c }, - { 0xd012, 0, 2, 0x01 }, - { 0xd013, 0, 8, 0xf7 }, - { 0xd014, 0, 2, 0x02 }, - { 0xd040, 0, 8, 0x0b }, - { 0xd041, 0, 2, 0x02 }, - { 0xd042, 0, 8, 0x4d }, - { 0xd043, 0, 2, 0x00 }, - { 0xd045, 1, 1, 0x00 }, - { 0x9bcf, 0, 1, 0x01 }, - { 0xd045, 2, 1, 0x01 }, - { 0xd04f, 0, 8, 0x9a }, - { 0xd050, 0, 1, 0x01 }, - { 0xd051, 0, 8, 0x5a }, - { 0xd052, 0, 1, 0x01 }, - { 0xd053, 0, 8, 0x50 }, - { 0xd054, 0, 8, 0x46 }, - { 0x9bd7, 0, 8, 0x0a }, - { 0x9bd8, 0, 8, 0x14 }, - { 0x9bd9, 0, 8, 0x08 }, - { 0x9bd0, 0, 8, 0xa8 }, - { 0x9be4, 0, 8, 0x7f }, - { 0x9bbd, 0, 8, 0xa8 }, - { 0x9be2, 0, 8, 0x20 }, - { 0x9bee, 0, 1, 0x01 }, -}; - -#endif /* AF9013_PRIV_H */ diff --git a/drivers/media/dvb/frontends/af9033.c b/drivers/media/dvb/frontends/af9033.c deleted file mode 100644 index a38998286260..000000000000 --- a/drivers/media/dvb/frontends/af9033.c +++ /dev/null @@ -1,980 +0,0 @@ -/* - * Afatech AF9033 demodulator driver - * - * Copyright (C) 2009 Antti Palosaari - * Copyright (C) 2012 Antti Palosaari - * - * 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. - */ - -#include "af9033_priv.h" - -struct af9033_state { - struct i2c_adapter *i2c; - struct dvb_frontend fe; - struct af9033_config cfg; - - u32 bandwidth_hz; - bool ts_mode_parallel; - bool ts_mode_serial; - - u32 ber; - u32 ucb; - unsigned long last_stat_check; -}; - -/* write multiple registers */ -static int af9033_wr_regs(struct af9033_state *state, u32 reg, const u8 *val, - int len) -{ - int ret; - u8 buf[3 + len]; - struct i2c_msg msg[1] = { - { - .addr = state->cfg.i2c_addr, - .flags = 0, - .len = sizeof(buf), - .buf = buf, - } - }; - - buf[0] = (reg >> 16) & 0xff; - buf[1] = (reg >> 8) & 0xff; - buf[2] = (reg >> 0) & 0xff; - memcpy(&buf[3], val, len); - - ret = i2c_transfer(state->i2c, msg, 1); - if (ret == 1) { - ret = 0; - } else { - printk(KERN_WARNING "%s: i2c wr failed=%d reg=%06x len=%d\n", - __func__, ret, reg, len); - ret = -EREMOTEIO; - } - - return ret; -} - -/* read multiple registers */ -static int af9033_rd_regs(struct af9033_state *state, u32 reg, u8 *val, int len) -{ - int ret; - u8 buf[3] = { (reg >> 16) & 0xff, (reg >> 8) & 0xff, - (reg >> 0) & 0xff }; - struct i2c_msg msg[2] = { - { - .addr = state->cfg.i2c_addr, - .flags = 0, - .len = sizeof(buf), - .buf = buf - }, { - .addr = state->cfg.i2c_addr, - .flags = I2C_M_RD, - .len = len, - .buf = val - } - }; - - ret = i2c_transfer(state->i2c, msg, 2); - if (ret == 2) { - ret = 0; - } else { - printk(KERN_WARNING "%s: i2c rd failed=%d reg=%06x len=%d\n", - __func__, ret, reg, len); - ret = -EREMOTEIO; - } - - return ret; -} - - -/* write single register */ -static int af9033_wr_reg(struct af9033_state *state, u32 reg, u8 val) -{ - return af9033_wr_regs(state, reg, &val, 1); -} - -/* read single register */ -static int af9033_rd_reg(struct af9033_state *state, u32 reg, u8 *val) -{ - return af9033_rd_regs(state, reg, val, 1); -} - -/* write single register with mask */ -static int af9033_wr_reg_mask(struct af9033_state *state, u32 reg, u8 val, - u8 mask) -{ - int ret; - u8 tmp; - - /* no need for read if whole reg is written */ - if (mask != 0xff) { - ret = af9033_rd_regs(state, reg, &tmp, 1); - if (ret) - return ret; - - val &= mask; - tmp &= ~mask; - val |= tmp; - } - - return af9033_wr_regs(state, reg, &val, 1); -} - -/* read single register with mask */ -static int af9033_rd_reg_mask(struct af9033_state *state, u32 reg, u8 *val, - u8 mask) -{ - int ret, i; - u8 tmp; - - ret = af9033_rd_regs(state, reg, &tmp, 1); - if (ret) - return ret; - - tmp &= mask; - - /* find position of the first bit */ - for (i = 0; i < 8; i++) { - if ((mask >> i) & 0x01) - break; - } - *val = tmp >> i; - - return 0; -} - -static u32 af9033_div(u32 a, u32 b, u32 x) -{ - u32 r = 0, c = 0, i; - - pr_debug("%s: a=%d b=%d x=%d\n", __func__, a, b, x); - - if (a > b) { - c = a / b; - a = a - c * b; - } - - for (i = 0; i < x; i++) { - if (a >= b) { - r += 1; - a -= b; - } - a <<= 1; - r <<= 1; - } - r = (c << (u32)x) + r; - - pr_debug("%s: a=%d b=%d x=%d r=%d r=%x\n", __func__, a, b, x, r, r); - - return r; -} - -static void af9033_release(struct dvb_frontend *fe) -{ - struct af9033_state *state = fe->demodulator_priv; - - kfree(state); -} - -static int af9033_init(struct dvb_frontend *fe) -{ - struct af9033_state *state = fe->demodulator_priv; - int ret, i, len; - const struct reg_val *init; - u8 buf[4]; - u32 adc_cw, clock_cw; - struct reg_val_mask tab[] = { - { 0x80fb24, 0x00, 0x08 }, - { 0x80004c, 0x00, 0xff }, - { 0x00f641, state->cfg.tuner, 0xff }, - { 0x80f5ca, 0x01, 0x01 }, - { 0x80f715, 0x01, 0x01 }, - { 0x00f41f, 0x04, 0x04 }, - { 0x00f41a, 0x01, 0x01 }, - { 0x80f731, 0x00, 0x01 }, - { 0x00d91e, 0x00, 0x01 }, - { 0x00d919, 0x00, 0x01 }, - { 0x80f732, 0x00, 0x01 }, - { 0x00d91f, 0x00, 0x01 }, - { 0x00d91a, 0x00, 0x01 }, - { 0x80f730, 0x00, 0x01 }, - { 0x80f778, 0x00, 0xff }, - { 0x80f73c, 0x01, 0x01 }, - { 0x80f776, 0x00, 0x01 }, - { 0x00d8fd, 0x01, 0xff }, - { 0x00d830, 0x01, 0xff }, - { 0x00d831, 0x00, 0xff }, - { 0x00d832, 0x00, 0xff }, - { 0x80f985, state->ts_mode_serial, 0x01 }, - { 0x80f986, state->ts_mode_parallel, 0x01 }, - { 0x00d827, 0x00, 0xff }, - { 0x00d829, 0x00, 0xff }, - }; - - /* program clock control */ - clock_cw = af9033_div(state->cfg.clock, 1000000ul, 19ul); - buf[0] = (clock_cw >> 0) & 0xff; - buf[1] = (clock_cw >> 8) & 0xff; - buf[2] = (clock_cw >> 16) & 0xff; - buf[3] = (clock_cw >> 24) & 0xff; - - pr_debug("%s: clock=%d clock_cw=%08x\n", __func__, state->cfg.clock, - clock_cw); - - ret = af9033_wr_regs(state, 0x800025, buf, 4); - if (ret < 0) - goto err; - - /* program ADC control */ - for (i = 0; i < ARRAY_SIZE(clock_adc_lut); i++) { - if (clock_adc_lut[i].clock == state->cfg.clock) - break; - } - - adc_cw = af9033_div(clock_adc_lut[i].adc, 1000000ul, 19ul); - buf[0] = (adc_cw >> 0) & 0xff; - buf[1] = (adc_cw >> 8) & 0xff; - buf[2] = (adc_cw >> 16) & 0xff; - - pr_debug("%s: adc=%d adc_cw=%06x\n", __func__, clock_adc_lut[i].adc, - adc_cw); - - ret = af9033_wr_regs(state, 0x80f1cd, buf, 3); - if (ret < 0) - goto err; - - /* program register table */ - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = af9033_wr_reg_mask(state, tab[i].reg, tab[i].val, - tab[i].mask); - if (ret < 0) - goto err; - } - - /* settings for TS interface */ - if (state->cfg.ts_mode == AF9033_TS_MODE_USB) { - ret = af9033_wr_reg_mask(state, 0x80f9a5, 0x00, 0x01); - if (ret < 0) - goto err; - - ret = af9033_wr_reg_mask(state, 0x80f9b5, 0x01, 0x01); - if (ret < 0) - goto err; - } else { - ret = af9033_wr_reg_mask(state, 0x80f990, 0x00, 0x01); - if (ret < 0) - goto err; - - ret = af9033_wr_reg_mask(state, 0x80f9b5, 0x00, 0x01); - if (ret < 0) - goto err; - } - - /* load OFSM settings */ - pr_debug("%s: load ofsm settings\n", __func__); - len = ARRAY_SIZE(ofsm_init); - init = ofsm_init; - for (i = 0; i < len; i++) { - ret = af9033_wr_reg(state, init[i].reg, init[i].val); - if (ret < 0) - goto err; - } - - /* load tuner specific settings */ - pr_debug("%s: load tuner specific settings\n", - __func__); - switch (state->cfg.tuner) { - case AF9033_TUNER_TUA9001: - len = ARRAY_SIZE(tuner_init_tua9001); - init = tuner_init_tua9001; - break; - case AF9033_TUNER_FC0011: - len = ARRAY_SIZE(tuner_init_fc0011); - init = tuner_init_fc0011; - break; - case AF9033_TUNER_MXL5007T: - len = ARRAY_SIZE(tuner_init_mxl5007t); - init = tuner_init_mxl5007t; - break; - case AF9033_TUNER_TDA18218: - len = ARRAY_SIZE(tuner_init_tda18218); - init = tuner_init_tda18218; - break; - default: - pr_debug("%s: unsupported tuner ID=%d\n", __func__, - state->cfg.tuner); - ret = -ENODEV; - goto err; - } - - for (i = 0; i < len; i++) { - ret = af9033_wr_reg(state, init[i].reg, init[i].val); - if (ret < 0) - goto err; - } - - state->bandwidth_hz = 0; /* force to program all parameters */ - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9033_sleep(struct dvb_frontend *fe) -{ - struct af9033_state *state = fe->demodulator_priv; - int ret, i; - u8 tmp; - - ret = af9033_wr_reg(state, 0x80004c, 1); - if (ret < 0) - goto err; - - ret = af9033_wr_reg(state, 0x800000, 0); - if (ret < 0) - goto err; - - for (i = 100, tmp = 1; i && tmp; i--) { - ret = af9033_rd_reg(state, 0x80004c, &tmp); - if (ret < 0) - goto err; - - usleep_range(200, 10000); - } - - pr_debug("%s: loop=%d\n", __func__, i); - - if (i == 0) { - ret = -ETIMEDOUT; - goto err; - } - - ret = af9033_wr_reg_mask(state, 0x80fb24, 0x08, 0x08); - if (ret < 0) - goto err; - - /* prevent current leak (?) */ - if (state->cfg.ts_mode == AF9033_TS_MODE_SERIAL) { - /* enable parallel TS */ - ret = af9033_wr_reg_mask(state, 0x00d917, 0x00, 0x01); - if (ret < 0) - goto err; - - ret = af9033_wr_reg_mask(state, 0x00d916, 0x01, 0x01); - if (ret < 0) - goto err; - } - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9033_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *fesettings) -{ - fesettings->min_delay_ms = 800; - fesettings->step_size = 0; - fesettings->max_drift = 0; - - return 0; -} - -static int af9033_set_frontend(struct dvb_frontend *fe) -{ - struct af9033_state *state = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i, spec_inv; - u8 tmp, buf[3], bandwidth_reg_val; - u32 if_frequency, freq_cw, adc_freq; - - pr_debug("%s: frequency=%d bandwidth_hz=%d\n", __func__, c->frequency, - c->bandwidth_hz); - - /* check bandwidth */ - switch (c->bandwidth_hz) { - case 6000000: - bandwidth_reg_val = 0x00; - break; - case 7000000: - bandwidth_reg_val = 0x01; - break; - case 8000000: - bandwidth_reg_val = 0x02; - break; - default: - pr_debug("%s: invalid bandwidth_hz\n", __func__); - ret = -EINVAL; - goto err; - } - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - - /* program CFOE coefficients */ - if (c->bandwidth_hz != state->bandwidth_hz) { - for (i = 0; i < ARRAY_SIZE(coeff_lut); i++) { - if (coeff_lut[i].clock == state->cfg.clock && - coeff_lut[i].bandwidth_hz == c->bandwidth_hz) { - break; - } - } - ret = af9033_wr_regs(state, 0x800001, - coeff_lut[i].val, sizeof(coeff_lut[i].val)); - } - - /* program frequency control */ - if (c->bandwidth_hz != state->bandwidth_hz) { - spec_inv = state->cfg.spec_inv ? -1 : 1; - - for (i = 0; i < ARRAY_SIZE(clock_adc_lut); i++) { - if (clock_adc_lut[i].clock == state->cfg.clock) - break; - } - adc_freq = clock_adc_lut[i].adc; - - /* get used IF frequency */ - if (fe->ops.tuner_ops.get_if_frequency) - fe->ops.tuner_ops.get_if_frequency(fe, &if_frequency); - else - if_frequency = 0; - - while (if_frequency > (adc_freq / 2)) - if_frequency -= adc_freq; - - if (if_frequency >= 0) - spec_inv *= -1; - else - if_frequency *= -1; - - freq_cw = af9033_div(if_frequency, adc_freq, 23ul); - - if (spec_inv == -1) - freq_cw *= -1; - - /* get adc multiplies */ - ret = af9033_rd_reg(state, 0x800045, &tmp); - if (ret < 0) - goto err; - - if (tmp == 1) - freq_cw /= 2; - - buf[0] = (freq_cw >> 0) & 0xff; - buf[1] = (freq_cw >> 8) & 0xff; - buf[2] = (freq_cw >> 16) & 0x7f; - ret = af9033_wr_regs(state, 0x800029, buf, 3); - if (ret < 0) - goto err; - - state->bandwidth_hz = c->bandwidth_hz; - } - - ret = af9033_wr_reg_mask(state, 0x80f904, bandwidth_reg_val, 0x03); - if (ret < 0) - goto err; - - ret = af9033_wr_reg(state, 0x800040, 0x00); - if (ret < 0) - goto err; - - ret = af9033_wr_reg(state, 0x800047, 0x00); - if (ret < 0) - goto err; - - ret = af9033_wr_reg_mask(state, 0x80f999, 0x00, 0x01); - if (ret < 0) - goto err; - - if (c->frequency <= 230000000) - tmp = 0x00; /* VHF */ - else - tmp = 0x01; /* UHF */ - - ret = af9033_wr_reg(state, 0x80004b, tmp); - if (ret < 0) - goto err; - - ret = af9033_wr_reg(state, 0x800000, 0x00); - if (ret < 0) - goto err; - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9033_get_frontend(struct dvb_frontend *fe) -{ - struct af9033_state *state = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret; - u8 buf[8]; - - pr_debug("%s\n", __func__); - - /* read all needed registers */ - ret = af9033_rd_regs(state, 0x80f900, buf, sizeof(buf)); - if (ret < 0) - goto err; - - switch ((buf[0] >> 0) & 3) { - case 0: - c->transmission_mode = TRANSMISSION_MODE_2K; - break; - case 1: - c->transmission_mode = TRANSMISSION_MODE_8K; - break; - } - - switch ((buf[1] >> 0) & 3) { - case 0: - c->guard_interval = GUARD_INTERVAL_1_32; - break; - case 1: - c->guard_interval = GUARD_INTERVAL_1_16; - break; - case 2: - c->guard_interval = GUARD_INTERVAL_1_8; - break; - case 3: - c->guard_interval = GUARD_INTERVAL_1_4; - break; - } - - switch ((buf[2] >> 0) & 7) { - case 0: - c->hierarchy = HIERARCHY_NONE; - break; - case 1: - c->hierarchy = HIERARCHY_1; - break; - case 2: - c->hierarchy = HIERARCHY_2; - break; - case 3: - c->hierarchy = HIERARCHY_4; - break; - } - - switch ((buf[3] >> 0) & 3) { - case 0: - c->modulation = QPSK; - break; - case 1: - c->modulation = QAM_16; - break; - case 2: - c->modulation = QAM_64; - break; - } - - switch ((buf[4] >> 0) & 3) { - case 0: - c->bandwidth_hz = 6000000; - break; - case 1: - c->bandwidth_hz = 7000000; - break; - case 2: - c->bandwidth_hz = 8000000; - break; - } - - switch ((buf[6] >> 0) & 7) { - case 0: - c->code_rate_HP = FEC_1_2; - break; - case 1: - c->code_rate_HP = FEC_2_3; - break; - case 2: - c->code_rate_HP = FEC_3_4; - break; - case 3: - c->code_rate_HP = FEC_5_6; - break; - case 4: - c->code_rate_HP = FEC_7_8; - break; - case 5: - c->code_rate_HP = FEC_NONE; - break; - } - - switch ((buf[7] >> 0) & 7) { - case 0: - c->code_rate_LP = FEC_1_2; - break; - case 1: - c->code_rate_LP = FEC_2_3; - break; - case 2: - c->code_rate_LP = FEC_3_4; - break; - case 3: - c->code_rate_LP = FEC_5_6; - break; - case 4: - c->code_rate_LP = FEC_7_8; - break; - case 5: - c->code_rate_LP = FEC_NONE; - break; - } - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9033_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct af9033_state *state = fe->demodulator_priv; - int ret; - u8 tmp; - - *status = 0; - - /* radio channel status, 0=no result, 1=has signal, 2=no signal */ - ret = af9033_rd_reg(state, 0x800047, &tmp); - if (ret < 0) - goto err; - - /* has signal */ - if (tmp == 0x01) - *status |= FE_HAS_SIGNAL; - - if (tmp != 0x02) { - /* TPS lock */ - ret = af9033_rd_reg_mask(state, 0x80f5a9, &tmp, 0x01); - if (ret < 0) - goto err; - - if (tmp) - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI; - - /* full lock */ - ret = af9033_rd_reg_mask(state, 0x80f999, &tmp, 0x01); - if (ret < 0) - goto err; - - if (tmp) - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC | - FE_HAS_LOCK; - } - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9033_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct af9033_state *state = fe->demodulator_priv; - int ret, i, len; - u8 buf[3], tmp; - u32 snr_val; - const struct val_snr *uninitialized_var(snr_lut); - - /* read value */ - ret = af9033_rd_regs(state, 0x80002c, buf, 3); - if (ret < 0) - goto err; - - snr_val = (buf[2] << 16) | (buf[1] << 8) | buf[0]; - - /* read current modulation */ - ret = af9033_rd_reg(state, 0x80f903, &tmp); - if (ret < 0) - goto err; - - switch ((tmp >> 0) & 3) { - case 0: - len = ARRAY_SIZE(qpsk_snr_lut); - snr_lut = qpsk_snr_lut; - break; - case 1: - len = ARRAY_SIZE(qam16_snr_lut); - snr_lut = qam16_snr_lut; - break; - case 2: - len = ARRAY_SIZE(qam64_snr_lut); - snr_lut = qam64_snr_lut; - break; - default: - goto err; - } - - for (i = 0; i < len; i++) { - tmp = snr_lut[i].snr; - - if (snr_val < snr_lut[i].val) - break; - } - - *snr = tmp * 10; /* dB/10 */ - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9033_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - struct af9033_state *state = fe->demodulator_priv; - int ret; - u8 strength2; - - /* read signal strength of 0-100 scale */ - ret = af9033_rd_reg(state, 0x800048, &strength2); - if (ret < 0) - goto err; - - /* scale value to 0x0000-0xffff */ - *strength = strength2 * 0xffff / 100; - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static int af9033_update_ch_stat(struct af9033_state *state) -{ - int ret = 0; - u32 err_cnt, bit_cnt; - u16 abort_cnt; - u8 buf[7]; - - /* only update data every half second */ - if (time_after(jiffies, state->last_stat_check + msecs_to_jiffies(500))) { - ret = af9033_rd_regs(state, 0x800032, buf, sizeof(buf)); - if (ret < 0) - goto err; - /* in 8 byte packets? */ - abort_cnt = (buf[1] << 8) + buf[0]; - /* in bits */ - err_cnt = (buf[4] << 16) + (buf[3] << 8) + buf[2]; - /* in 8 byte packets? always(?) 0x2710 = 10000 */ - bit_cnt = (buf[6] << 8) + buf[5]; - - if (bit_cnt < abort_cnt) { - abort_cnt = 1000; - state->ber = 0xffffffff; - } else { - /* 8 byte packets, that have not been rejected already */ - bit_cnt -= (u32)abort_cnt; - if (bit_cnt == 0) { - state->ber = 0xffffffff; - } else { - err_cnt -= (u32)abort_cnt * 8 * 8; - bit_cnt *= 8 * 8; - state->ber = err_cnt * (0xffffffff / bit_cnt); - } - } - state->ucb += abort_cnt; - state->last_stat_check = jiffies; - } - - return 0; -err: - pr_debug("%s: failed=%d\n", __func__, ret); - return ret; -} - -static int af9033_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct af9033_state *state = fe->demodulator_priv; - int ret; - - ret = af9033_update_ch_stat(state); - if (ret < 0) - return ret; - - *ber = state->ber; - - return 0; -} - -static int af9033_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - struct af9033_state *state = fe->demodulator_priv; - int ret; - - ret = af9033_update_ch_stat(state); - if (ret < 0) - return ret; - - *ucblocks = state->ucb; - - return 0; -} - -static int af9033_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct af9033_state *state = fe->demodulator_priv; - int ret; - - pr_debug("%s: enable=%d\n", __func__, enable); - - ret = af9033_wr_reg_mask(state, 0x00fa04, enable, 0x01); - if (ret < 0) - goto err; - - return 0; - -err: - pr_debug("%s: failed=%d\n", __func__, ret); - - return ret; -} - -static struct dvb_frontend_ops af9033_ops; - -struct dvb_frontend *af9033_attach(const struct af9033_config *config, - struct i2c_adapter *i2c) -{ - int ret; - struct af9033_state *state; - u8 buf[8]; - - pr_debug("%s:\n", __func__); - - /* allocate memory for the internal state */ - state = kzalloc(sizeof(struct af9033_state), GFP_KERNEL); - if (state == NULL) - goto err; - - /* setup the state */ - state->i2c = i2c; - memcpy(&state->cfg, config, sizeof(struct af9033_config)); - - if (state->cfg.clock != 12000000) { - printk(KERN_INFO "af9033: unsupported clock=%d, only " \ - "12000000 Hz is supported currently\n", - state->cfg.clock); - goto err; - } - - /* firmware version */ - ret = af9033_rd_regs(state, 0x0083e9, &buf[0], 4); - if (ret < 0) - goto err; - - ret = af9033_rd_regs(state, 0x804191, &buf[4], 4); - if (ret < 0) - goto err; - - printk(KERN_INFO "af9033: firmware version: LINK=%d.%d.%d.%d " \ - "OFDM=%d.%d.%d.%d\n", buf[0], buf[1], buf[2], buf[3], - buf[4], buf[5], buf[6], buf[7]); - - /* configure internal TS mode */ - switch (state->cfg.ts_mode) { - case AF9033_TS_MODE_PARALLEL: - state->ts_mode_parallel = true; - break; - case AF9033_TS_MODE_SERIAL: - state->ts_mode_serial = true; - break; - case AF9033_TS_MODE_USB: - /* usb mode for AF9035 */ - default: - break; - } - - /* create dvb_frontend */ - memcpy(&state->fe.ops, &af9033_ops, sizeof(struct dvb_frontend_ops)); - state->fe.demodulator_priv = state; - - return &state->fe; - -err: - kfree(state); - return NULL; -} -EXPORT_SYMBOL(af9033_attach); - -static struct dvb_frontend_ops af9033_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "Afatech AF9033 (DVB-T)", - .frequency_min = 174000000, - .frequency_max = 862000000, - .frequency_stepsize = 250000, - .frequency_tolerance = 0, - .caps = FE_CAN_FEC_1_2 | - FE_CAN_FEC_2_3 | - FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | - FE_CAN_FEC_7_8 | - FE_CAN_FEC_AUTO | - FE_CAN_QPSK | - FE_CAN_QAM_16 | - FE_CAN_QAM_64 | - FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO | - FE_CAN_RECOVER | - FE_CAN_MUTE_TS - }, - - .release = af9033_release, - - .init = af9033_init, - .sleep = af9033_sleep, - - .get_tune_settings = af9033_get_tune_settings, - .set_frontend = af9033_set_frontend, - .get_frontend = af9033_get_frontend, - - .read_status = af9033_read_status, - .read_snr = af9033_read_snr, - .read_signal_strength = af9033_read_signal_strength, - .read_ber = af9033_read_ber, - .read_ucblocks = af9033_read_ucblocks, - - .i2c_gate_ctrl = af9033_i2c_gate_ctrl, -}; - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Afatech AF9033 DVB-T demodulator driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/af9033.h b/drivers/media/dvb/frontends/af9033.h deleted file mode 100644 index 9e302c3f0f7d..000000000000 --- a/drivers/media/dvb/frontends/af9033.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Afatech AF9033 demodulator driver - * - * Copyright (C) 2009 Antti Palosaari - * Copyright (C) 2012 Antti Palosaari - * - * 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. - */ - -#ifndef AF9033_H -#define AF9033_H - -struct af9033_config { - /* - * I2C address - */ - u8 i2c_addr; - - /* - * clock Hz - * 12000000, 22000000, 24000000, 34000000, 32000000, 28000000, 26000000, - * 30000000, 36000000, 20480000, 16384000 - */ - u32 clock; - - /* - * tuner - */ -#define AF9033_TUNER_TUA9001 0x27 /* Infineon TUA 9001 */ -#define AF9033_TUNER_FC0011 0x28 /* Fitipower FC0011 */ -#define AF9033_TUNER_MXL5007T 0xa0 /* MaxLinear MxL5007T */ -#define AF9033_TUNER_TDA18218 0xa1 /* NXP TDA 18218HN */ - u8 tuner; - - /* - * TS settings - */ -#define AF9033_TS_MODE_USB 0 -#define AF9033_TS_MODE_PARALLEL 1 -#define AF9033_TS_MODE_SERIAL 2 - u8 ts_mode:2; - - /* - * input spectrum inversion - */ - bool spec_inv; -}; - - -#if defined(CONFIG_DVB_AF9033) || \ - (defined(CONFIG_DVB_AF9033_MODULE) && defined(MODULE)) -extern struct dvb_frontend *af9033_attach(const struct af9033_config *config, - struct i2c_adapter *i2c); -#else -static inline struct dvb_frontend *af9033_attach( - const struct af9033_config *config, struct i2c_adapter *i2c) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif - -#endif /* AF9033_H */ diff --git a/drivers/media/dvb/frontends/af9033_priv.h b/drivers/media/dvb/frontends/af9033_priv.h deleted file mode 100644 index 0b783b9ed75e..000000000000 --- a/drivers/media/dvb/frontends/af9033_priv.h +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Afatech AF9033 demodulator driver - * - * Copyright (C) 2009 Antti Palosaari - * Copyright (C) 2012 Antti Palosaari - * - * 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. - */ - -#ifndef AF9033_PRIV_H -#define AF9033_PRIV_H - -#include "dvb_frontend.h" -#include "af9033.h" - -struct reg_val { - u32 reg; - u8 val; -}; - -struct reg_val_mask { - u32 reg; - u8 val; - u8 mask; -}; - -struct coeff { - u32 clock; - u32 bandwidth_hz; - u8 val[36]; -}; - -struct clock_adc { - u32 clock; - u32 adc; -}; - -struct val_snr { - u32 val; - u8 snr; -}; - -/* Xtal clock vs. ADC clock lookup table */ -static const struct clock_adc clock_adc_lut[] = { - { 16384000, 20480000 }, - { 20480000, 20480000 }, - { 36000000, 20250000 }, - { 30000000, 20156250 }, - { 26000000, 20583333 }, - { 28000000, 20416667 }, - { 32000000, 20500000 }, - { 34000000, 20187500 }, - { 24000000, 20500000 }, - { 22000000, 20625000 }, - { 12000000, 20250000 }, -}; - -/* pre-calculated coeff lookup table */ -static const struct coeff coeff_lut[] = { - /* 12.000 MHz */ - { 12000000, 8000000, { - 0x01, 0xce, 0x55, 0xc9, 0x00, 0xe7, 0x2a, 0xe4, 0x00, 0x73, - 0x99, 0x0f, 0x00, 0x73, 0x95, 0x72, 0x00, 0x73, 0x91, 0xd5, - 0x00, 0x39, 0xca, 0xb9, 0x00, 0xe7, 0x2a, 0xe4, 0x00, 0x73, - 0x95, 0x72, 0x37, 0x02, 0xce, 0x01 } - }, - { 12000000, 7000000, { - 0x01, 0x94, 0x8b, 0x10, 0x00, 0xca, 0x45, 0x88, 0x00, 0x65, - 0x25, 0xed, 0x00, 0x65, 0x22, 0xc4, 0x00, 0x65, 0x1f, 0x9b, - 0x00, 0x32, 0x91, 0x62, 0x00, 0xca, 0x45, 0x88, 0x00, 0x65, - 0x22, 0xc4, 0x88, 0x02, 0x95, 0x01 } - }, - { 12000000, 6000000, { - 0x01, 0x5a, 0xc0, 0x56, 0x00, 0xad, 0x60, 0x2b, 0x00, 0x56, - 0xb2, 0xcb, 0x00, 0x56, 0xb0, 0x15, 0x00, 0x56, 0xad, 0x60, - 0x00, 0x2b, 0x58, 0x0b, 0x00, 0xad, 0x60, 0x2b, 0x00, 0x56, - 0xb0, 0x15, 0xf4, 0x02, 0x5b, 0x01 } - }, -}; - -/* QPSK SNR lookup table */ -static const struct val_snr qpsk_snr_lut[] = { - { 0x0b4771, 0 }, - { 0x0c1aed, 1 }, - { 0x0d0d27, 2 }, - { 0x0e4d19, 3 }, - { 0x0e5da8, 4 }, - { 0x107097, 5 }, - { 0x116975, 6 }, - { 0x1252d9, 7 }, - { 0x131fa4, 8 }, - { 0x13d5e1, 9 }, - { 0x148e53, 10 }, - { 0x15358b, 11 }, - { 0x15dd29, 12 }, - { 0x168112, 13 }, - { 0x170b61, 14 }, - { 0x17a532, 15 }, - { 0x180f94, 16 }, - { 0x186ed2, 17 }, - { 0x18b271, 18 }, - { 0x18e118, 19 }, - { 0x18ff4b, 20 }, - { 0x190af1, 21 }, - { 0x191451, 22 }, - { 0xffffff, 23 }, -}; - -/* QAM16 SNR lookup table */ -static const struct val_snr qam16_snr_lut[] = { - { 0x04f0d5, 0 }, - { 0x05387a, 1 }, - { 0x0573a4, 2 }, - { 0x05a99e, 3 }, - { 0x05cc80, 4 }, - { 0x05eb62, 5 }, - { 0x05fecf, 6 }, - { 0x060b80, 7 }, - { 0x062501, 8 }, - { 0x064865, 9 }, - { 0x069604, 10 }, - { 0x06f356, 11 }, - { 0x07706a, 12 }, - { 0x0804d3, 13 }, - { 0x089d1a, 14 }, - { 0x093e3d, 15 }, - { 0x09e35d, 16 }, - { 0x0a7c3c, 17 }, - { 0x0afaf8, 18 }, - { 0x0b719d, 19 }, - { 0x0bda6a, 20 }, - { 0x0c0c75, 21 }, - { 0x0c3f7d, 22 }, - { 0x0c5e62, 23 }, - { 0x0c6c31, 24 }, - { 0x0c7925, 25 }, - { 0xffffff, 26 }, -}; - -/* QAM64 SNR lookup table */ -static const struct val_snr qam64_snr_lut[] = { - { 0x0256d0, 0 }, - { 0x027a65, 1 }, - { 0x029873, 2 }, - { 0x02b7fe, 3 }, - { 0x02cf1e, 4 }, - { 0x02e234, 5 }, - { 0x02f409, 6 }, - { 0x030046, 7 }, - { 0x030844, 8 }, - { 0x030a02, 9 }, - { 0x030cde, 10 }, - { 0x031031, 11 }, - { 0x03144c, 12 }, - { 0x0315dd, 13 }, - { 0x031920, 14 }, - { 0x0322d0, 15 }, - { 0x0339fc, 16 }, - { 0x0364a1, 17 }, - { 0x038bcc, 18 }, - { 0x03c7d3, 19 }, - { 0x0408cc, 20 }, - { 0x043bed, 21 }, - { 0x048061, 22 }, - { 0x04be95, 23 }, - { 0x04fa7d, 24 }, - { 0x052405, 25 }, - { 0x05570d, 26 }, - { 0x059feb, 27 }, - { 0x05bf38, 28 }, - { 0xffffff, 29 }, -}; - -static const struct reg_val ofsm_init[] = { - { 0x800051, 0x01 }, - { 0x800070, 0x0a }, - { 0x80007e, 0x04 }, - { 0x800081, 0x0a }, - { 0x80008a, 0x01 }, - { 0x80008e, 0x01 }, - { 0x800092, 0x06 }, - { 0x800099, 0x01 }, - { 0x80009f, 0xe1 }, - { 0x8000a0, 0xcf }, - { 0x8000a3, 0x01 }, - { 0x8000a5, 0x01 }, - { 0x8000a6, 0x01 }, - { 0x8000a9, 0x00 }, - { 0x8000aa, 0x01 }, - { 0x8000ab, 0x01 }, - { 0x8000b0, 0x01 }, - { 0x8000c0, 0x05 }, - { 0x8000c4, 0x19 }, - { 0x80f000, 0x0f }, - { 0x80f016, 0x10 }, - { 0x80f017, 0x04 }, - { 0x80f018, 0x05 }, - { 0x80f019, 0x04 }, - { 0x80f01a, 0x05 }, - { 0x80f021, 0x03 }, - { 0x80f022, 0x0a }, - { 0x80f023, 0x0a }, - { 0x80f02b, 0x00 }, - { 0x80f02c, 0x01 }, - { 0x80f064, 0x03 }, - { 0x80f065, 0xf9 }, - { 0x80f066, 0x03 }, - { 0x80f067, 0x01 }, - { 0x80f06f, 0xe0 }, - { 0x80f070, 0x03 }, - { 0x80f072, 0x0f }, - { 0x80f073, 0x03 }, - { 0x80f078, 0x00 }, - { 0x80f087, 0x00 }, - { 0x80f09b, 0x3f }, - { 0x80f09c, 0x00 }, - { 0x80f09d, 0x20 }, - { 0x80f09e, 0x00 }, - { 0x80f09f, 0x0c }, - { 0x80f0a0, 0x00 }, - { 0x80f130, 0x04 }, - { 0x80f132, 0x04 }, - { 0x80f144, 0x1a }, - { 0x80f146, 0x00 }, - { 0x80f14a, 0x01 }, - { 0x80f14c, 0x00 }, - { 0x80f14d, 0x00 }, - { 0x80f14f, 0x04 }, - { 0x80f158, 0x7f }, - { 0x80f15a, 0x00 }, - { 0x80f15b, 0x08 }, - { 0x80f15d, 0x03 }, - { 0x80f15e, 0x05 }, - { 0x80f163, 0x05 }, - { 0x80f166, 0x01 }, - { 0x80f167, 0x40 }, - { 0x80f168, 0x0f }, - { 0x80f17a, 0x00 }, - { 0x80f17b, 0x00 }, - { 0x80f183, 0x01 }, - { 0x80f19d, 0x40 }, - { 0x80f1bc, 0x36 }, - { 0x80f1bd, 0x00 }, - { 0x80f1cb, 0xa0 }, - { 0x80f1cc, 0x01 }, - { 0x80f204, 0x10 }, - { 0x80f214, 0x00 }, - { 0x80f40e, 0x0a }, - { 0x80f40f, 0x40 }, - { 0x80f410, 0x08 }, - { 0x80f55f, 0x0a }, - { 0x80f561, 0x15 }, - { 0x80f562, 0x20 }, - { 0x80f5df, 0xfb }, - { 0x80f5e0, 0x00 }, - { 0x80f5e3, 0x09 }, - { 0x80f5e4, 0x01 }, - { 0x80f5e5, 0x01 }, - { 0x80f5f8, 0x01 }, - { 0x80f5fd, 0x01 }, - { 0x80f600, 0x05 }, - { 0x80f601, 0x08 }, - { 0x80f602, 0x0b }, - { 0x80f603, 0x0e }, - { 0x80f604, 0x11 }, - { 0x80f605, 0x14 }, - { 0x80f606, 0x17 }, - { 0x80f607, 0x1f }, - { 0x80f60e, 0x00 }, - { 0x80f60f, 0x04 }, - { 0x80f610, 0x32 }, - { 0x80f611, 0x10 }, - { 0x80f707, 0xfc }, - { 0x80f708, 0x00 }, - { 0x80f709, 0x37 }, - { 0x80f70a, 0x00 }, - { 0x80f78b, 0x01 }, - { 0x80f80f, 0x40 }, - { 0x80f810, 0x54 }, - { 0x80f811, 0x5a }, - { 0x80f905, 0x01 }, - { 0x80fb06, 0x03 }, - { 0x80fd8b, 0x00 }, -}; - -/* Infineon TUA 9001 tuner init - AF9033_TUNER_TUA9001 = 0x27 */ -static const struct reg_val tuner_init_tua9001[] = { - { 0x800046, 0x27 }, - { 0x800057, 0x00 }, - { 0x800058, 0x01 }, - { 0x80005f, 0x00 }, - { 0x800060, 0x00 }, - { 0x80006d, 0x00 }, - { 0x800071, 0x05 }, - { 0x800072, 0x02 }, - { 0x800074, 0x01 }, - { 0x800075, 0x03 }, - { 0x800076, 0x02 }, - { 0x800077, 0x00 }, - { 0x800078, 0x01 }, - { 0x800079, 0x00 }, - { 0x80007a, 0x7e }, - { 0x80007b, 0x3e }, - { 0x800093, 0x00 }, - { 0x800094, 0x01 }, - { 0x800095, 0x02 }, - { 0x800096, 0x01 }, - { 0x800098, 0x0a }, - { 0x80009b, 0x05 }, - { 0x80009c, 0x80 }, - { 0x8000b3, 0x00 }, - { 0x8000c1, 0x01 }, - { 0x8000c2, 0x00 }, - { 0x80f007, 0x00 }, - { 0x80f01f, 0x82 }, - { 0x80f020, 0x00 }, - { 0x80f029, 0x82 }, - { 0x80f02a, 0x00 }, - { 0x80f047, 0x00 }, - { 0x80f054, 0x00 }, - { 0x80f055, 0x00 }, - { 0x80f077, 0x01 }, - { 0x80f1e6, 0x00 }, -}; - -/* Fitipower fc0011 tuner init - AF9033_TUNER_FC0011 = 0x28 */ -static const struct reg_val tuner_init_fc0011[] = { - { 0x800046, AF9033_TUNER_FC0011 }, - { 0x800057, 0x00 }, - { 0x800058, 0x01 }, - { 0x80005f, 0x00 }, - { 0x800060, 0x00 }, - { 0x800068, 0xa5 }, - { 0x80006e, 0x01 }, - { 0x800071, 0x0A }, - { 0x800072, 0x02 }, - { 0x800074, 0x01 }, - { 0x800079, 0x01 }, - { 0x800093, 0x00 }, - { 0x800094, 0x00 }, - { 0x800095, 0x00 }, - { 0x800096, 0x00 }, - { 0x80009b, 0x2D }, - { 0x80009c, 0x60 }, - { 0x80009d, 0x23 }, - { 0x8000a4, 0x50 }, - { 0x8000ad, 0x50 }, - { 0x8000b3, 0x01 }, - { 0x8000b7, 0x88 }, - { 0x8000b8, 0xa6 }, - { 0x8000c3, 0x01 }, - { 0x8000c4, 0x01 }, - { 0x8000c7, 0x69 }, - { 0x80F007, 0x00 }, - { 0x80F00A, 0x1B }, - { 0x80F00B, 0x1B }, - { 0x80F00C, 0x1B }, - { 0x80F00D, 0x1B }, - { 0x80F00E, 0xFF }, - { 0x80F00F, 0x01 }, - { 0x80F010, 0x00 }, - { 0x80F011, 0x02 }, - { 0x80F012, 0xFF }, - { 0x80F013, 0x01 }, - { 0x80F014, 0x00 }, - { 0x80F015, 0x02 }, - { 0x80F01B, 0xEF }, - { 0x80F01C, 0x01 }, - { 0x80F01D, 0x0f }, - { 0x80F01E, 0x02 }, - { 0x80F01F, 0x6E }, - { 0x80F020, 0x00 }, - { 0x80F025, 0xDE }, - { 0x80F026, 0x00 }, - { 0x80F027, 0x0A }, - { 0x80F028, 0x03 }, - { 0x80F029, 0x6E }, - { 0x80F02A, 0x00 }, - { 0x80F047, 0x00 }, - { 0x80F054, 0x00 }, - { 0x80F055, 0x00 }, - { 0x80F077, 0x01 }, - { 0x80F1E6, 0x00 }, -}; - -/* MaxLinear MxL5007T tuner init - AF9033_TUNER_MXL5007T = 0xa0 */ -static const struct reg_val tuner_init_mxl5007t[] = { - { 0x800046, 0x1b }, - { 0x800057, 0x01 }, - { 0x800058, 0x01 }, - { 0x80005f, 0x00 }, - { 0x800060, 0x00 }, - { 0x800068, 0x96 }, - { 0x800071, 0x05 }, - { 0x800072, 0x02 }, - { 0x800074, 0x01 }, - { 0x800079, 0x01 }, - { 0x800093, 0x00 }, - { 0x800094, 0x00 }, - { 0x800095, 0x00 }, - { 0x800096, 0x00 }, - { 0x8000b3, 0x01 }, - { 0x8000c1, 0x01 }, - { 0x8000c2, 0x00 }, - { 0x80f007, 0x00 }, - { 0x80f00c, 0x19 }, - { 0x80f00d, 0x1a }, - { 0x80f012, 0xda }, - { 0x80f013, 0x00 }, - { 0x80f014, 0x00 }, - { 0x80f015, 0x02 }, - { 0x80f01f, 0x82 }, - { 0x80f020, 0x00 }, - { 0x80f029, 0x82 }, - { 0x80f02a, 0x00 }, - { 0x80f077, 0x02 }, - { 0x80f1e6, 0x00 }, -}; - -/* NXP TDA 18218HN tuner init - AF9033_TUNER_TDA18218 = 0xa1 */ -static const struct reg_val tuner_init_tda18218[] = { - {0x800046, 0xa1}, - {0x800057, 0x01}, - {0x800058, 0x01}, - {0x80005f, 0x00}, - {0x800060, 0x00}, - {0x800071, 0x05}, - {0x800072, 0x02}, - {0x800074, 0x01}, - {0x800079, 0x01}, - {0x800093, 0x00}, - {0x800094, 0x00}, - {0x800095, 0x00}, - {0x800096, 0x00}, - {0x8000b3, 0x01}, - {0x8000c3, 0x01}, - {0x8000c4, 0x00}, - {0x80f007, 0x00}, - {0x80f00c, 0x19}, - {0x80f00d, 0x1a}, - {0x80f012, 0xda}, - {0x80f013, 0x00}, - {0x80f014, 0x00}, - {0x80f015, 0x02}, - {0x80f01f, 0x82}, - {0x80f020, 0x00}, - {0x80f029, 0x82}, - {0x80f02a, 0x00}, - {0x80f077, 0x02}, - {0x80f1e6, 0x00}, -}; - -#endif /* AF9033_PRIV_H */ - diff --git a/drivers/media/dvb/frontends/atbm8830.c b/drivers/media/dvb/frontends/atbm8830.c deleted file mode 100644 index a2261ea2cf82..000000000000 --- a/drivers/media/dvb/frontends/atbm8830.c +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Support for AltoBeam GB20600 (a.k.a DMB-TH) demodulator - * ATBM8830, ATBM8831 - * - * Copyright (C) 2009 David T.L. Wong - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include "dvb_frontend.h" - -#include "atbm8830.h" -#include "atbm8830_priv.h" - -#define dprintk(args...) \ - do { \ - if (debug) \ - printk(KERN_DEBUG "atbm8830: " args); \ - } while (0) - -static int debug; - -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -static int atbm8830_write_reg(struct atbm_state *priv, u16 reg, u8 data) -{ - int ret = 0; - u8 dev_addr; - u8 buf1[] = { reg >> 8, reg & 0xFF }; - u8 buf2[] = { data }; - struct i2c_msg msg1 = { .flags = 0, .buf = buf1, .len = 2 }; - struct i2c_msg msg2 = { .flags = 0, .buf = buf2, .len = 1 }; - - dev_addr = priv->config->demod_address; - msg1.addr = dev_addr; - msg2.addr = dev_addr; - - if (debug >= 2) - dprintk("%s: reg=0x%04X, data=0x%02X\n", __func__, reg, data); - - ret = i2c_transfer(priv->i2c, &msg1, 1); - if (ret != 1) - return -EIO; - - ret = i2c_transfer(priv->i2c, &msg2, 1); - return (ret != 1) ? -EIO : 0; -} - -static int atbm8830_read_reg(struct atbm_state *priv, u16 reg, u8 *p_data) -{ - int ret; - u8 dev_addr; - - u8 buf1[] = { reg >> 8, reg & 0xFF }; - u8 buf2[] = { 0 }; - struct i2c_msg msg1 = { .flags = 0, .buf = buf1, .len = 2 }; - struct i2c_msg msg2 = { .flags = I2C_M_RD, .buf = buf2, .len = 1 }; - - dev_addr = priv->config->demod_address; - msg1.addr = dev_addr; - msg2.addr = dev_addr; - - ret = i2c_transfer(priv->i2c, &msg1, 1); - if (ret != 1) { - dprintk("%s: error reg=0x%04x, ret=%i\n", __func__, reg, ret); - return -EIO; - } - - ret = i2c_transfer(priv->i2c, &msg2, 1); - if (ret != 1) - return -EIO; - - *p_data = buf2[0]; - if (debug >= 2) - dprintk("%s: reg=0x%04X, data=0x%02X\n", - __func__, reg, buf2[0]); - - return 0; -} - -/* Lock register latch so that multi-register read is atomic */ -static inline int atbm8830_reglatch_lock(struct atbm_state *priv, int lock) -{ - return atbm8830_write_reg(priv, REG_READ_LATCH, lock ? 1 : 0); -} - -static int set_osc_freq(struct atbm_state *priv, u32 freq /*in kHz*/) -{ - u32 val; - u64 t; - - /* 0x100000 * freq / 30.4MHz */ - t = (u64)0x100000 * freq; - do_div(t, 30400); - val = t; - - atbm8830_write_reg(priv, REG_OSC_CLK, val); - atbm8830_write_reg(priv, REG_OSC_CLK + 1, val >> 8); - atbm8830_write_reg(priv, REG_OSC_CLK + 2, val >> 16); - - return 0; -} - -static int set_if_freq(struct atbm_state *priv, u32 freq /*in kHz*/) -{ - - u32 fs = priv->config->osc_clk_freq; - u64 t; - u32 val; - u8 dat; - - if (freq != 0) { - /* 2 * PI * (freq - fs) / fs * (2 ^ 22) */ - t = (u64) 2 * 31416 * (freq - fs); - t <<= 22; - do_div(t, fs); - do_div(t, 1000); - val = t; - - atbm8830_write_reg(priv, REG_TUNER_BASEBAND, 1); - atbm8830_write_reg(priv, REG_IF_FREQ, val); - atbm8830_write_reg(priv, REG_IF_FREQ+1, val >> 8); - atbm8830_write_reg(priv, REG_IF_FREQ+2, val >> 16); - - atbm8830_read_reg(priv, REG_ADC_CONFIG, &dat); - dat &= 0xFC; - atbm8830_write_reg(priv, REG_ADC_CONFIG, dat); - } else { - /* Zero IF */ - atbm8830_write_reg(priv, REG_TUNER_BASEBAND, 0); - - atbm8830_read_reg(priv, REG_ADC_CONFIG, &dat); - dat &= 0xFC; - dat |= 0x02; - atbm8830_write_reg(priv, REG_ADC_CONFIG, dat); - - if (priv->config->zif_swap_iq) - atbm8830_write_reg(priv, REG_SWAP_I_Q, 0x03); - else - atbm8830_write_reg(priv, REG_SWAP_I_Q, 0x01); - } - - return 0; -} - -static int is_locked(struct atbm_state *priv, u8 *locked) -{ - u8 status; - - atbm8830_read_reg(priv, REG_LOCK_STATUS, &status); - - if (locked != NULL) - *locked = (status == 1); - return 0; -} - -static int set_agc_config(struct atbm_state *priv, - u8 min, u8 max, u8 hold_loop) -{ - /* no effect if both min and max are zero */ - if (!min && !max) - return 0; - - atbm8830_write_reg(priv, REG_AGC_MIN, min); - atbm8830_write_reg(priv, REG_AGC_MAX, max); - atbm8830_write_reg(priv, REG_AGC_HOLD_LOOP, hold_loop); - - return 0; -} - -static int set_static_channel_mode(struct atbm_state *priv) -{ - int i; - - for (i = 0; i < 5; i++) - atbm8830_write_reg(priv, 0x099B + i, 0x08); - - atbm8830_write_reg(priv, 0x095B, 0x7F); - atbm8830_write_reg(priv, 0x09CB, 0x01); - atbm8830_write_reg(priv, 0x09CC, 0x7F); - atbm8830_write_reg(priv, 0x09CD, 0x7F); - atbm8830_write_reg(priv, 0x0E01, 0x20); - - /* For single carrier */ - atbm8830_write_reg(priv, 0x0B03, 0x0A); - atbm8830_write_reg(priv, 0x0935, 0x10); - atbm8830_write_reg(priv, 0x0936, 0x08); - atbm8830_write_reg(priv, 0x093E, 0x08); - atbm8830_write_reg(priv, 0x096E, 0x06); - - /* frame_count_max0 */ - atbm8830_write_reg(priv, 0x0B09, 0x00); - /* frame_count_max1 */ - atbm8830_write_reg(priv, 0x0B0A, 0x08); - - return 0; -} - -static int set_ts_config(struct atbm_state *priv) -{ - const struct atbm8830_config *cfg = priv->config; - - /*Set parallel/serial ts mode*/ - atbm8830_write_reg(priv, REG_TS_SERIAL, cfg->serial_ts ? 1 : 0); - atbm8830_write_reg(priv, REG_TS_CLK_MODE, cfg->serial_ts ? 1 : 0); - /*Set ts sampling edge*/ - atbm8830_write_reg(priv, REG_TS_SAMPLE_EDGE, - cfg->ts_sampling_edge ? 1 : 0); - /*Set ts clock freerun*/ - atbm8830_write_reg(priv, REG_TS_CLK_FREERUN, - cfg->ts_clk_gated ? 0 : 1); - - return 0; -} - -static int atbm8830_init(struct dvb_frontend *fe) -{ - struct atbm_state *priv = fe->demodulator_priv; - const struct atbm8830_config *cfg = priv->config; - - /*Set oscillator frequency*/ - set_osc_freq(priv, cfg->osc_clk_freq); - - /*Set IF frequency*/ - set_if_freq(priv, cfg->if_freq); - - /*Set AGC Config*/ - set_agc_config(priv, cfg->agc_min, cfg->agc_max, - cfg->agc_hold_loop); - - /*Set static channel mode*/ - set_static_channel_mode(priv); - - set_ts_config(priv); - /*Turn off DSP reset*/ - atbm8830_write_reg(priv, 0x000A, 0); - - /*SW version test*/ - atbm8830_write_reg(priv, 0x020C, 11); - - /* Run */ - atbm8830_write_reg(priv, REG_DEMOD_RUN, 1); - - return 0; -} - - -static void atbm8830_release(struct dvb_frontend *fe) -{ - struct atbm_state *state = fe->demodulator_priv; - dprintk("%s\n", __func__); - - kfree(state); -} - -static int atbm8830_set_fe(struct dvb_frontend *fe) -{ - struct atbm_state *priv = fe->demodulator_priv; - int i; - u8 locked = 0; - dprintk("%s\n", __func__); - - /* set frequency */ - if (fe->ops.tuner_ops.set_params) { - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - fe->ops.tuner_ops.set_params(fe); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } - - /* start auto lock */ - for (i = 0; i < 10; i++) { - mdelay(100); - dprintk("Try %d\n", i); - is_locked(priv, &locked); - if (locked != 0) { - dprintk("ATBM8830 locked!\n"); - break; - } - } - - return 0; -} - -static int atbm8830_get_fe(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - dprintk("%s\n", __func__); - - /* TODO: get real readings from device */ - /* inversion status */ - c->inversion = INVERSION_OFF; - - /* bandwidth */ - c->bandwidth_hz = 8000000; - - c->code_rate_HP = FEC_AUTO; - c->code_rate_LP = FEC_AUTO; - - c->modulation = QAM_AUTO; - - /* transmission mode */ - c->transmission_mode = TRANSMISSION_MODE_AUTO; - - /* guard interval */ - c->guard_interval = GUARD_INTERVAL_AUTO; - - /* hierarchy */ - c->hierarchy = HIERARCHY_NONE; - - return 0; -} - -static int atbm8830_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *fesettings) -{ - fesettings->min_delay_ms = 0; - fesettings->step_size = 0; - fesettings->max_drift = 0; - return 0; -} - -static int atbm8830_read_status(struct dvb_frontend *fe, fe_status_t *fe_status) -{ - struct atbm_state *priv = fe->demodulator_priv; - u8 locked = 0; - u8 agc_locked = 0; - - dprintk("%s\n", __func__); - *fe_status = 0; - - is_locked(priv, &locked); - if (locked) { - *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - } - dprintk("%s: fe_status=0x%x\n", __func__, *fe_status); - - atbm8830_read_reg(priv, REG_AGC_LOCK, &agc_locked); - dprintk("AGC Lock: %d\n", agc_locked); - - return 0; -} - -static int atbm8830_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct atbm_state *priv = fe->demodulator_priv; - u32 frame_err; - u8 t; - - dprintk("%s\n", __func__); - - atbm8830_reglatch_lock(priv, 1); - - atbm8830_read_reg(priv, REG_FRAME_ERR_CNT + 1, &t); - frame_err = t & 0x7F; - frame_err <<= 8; - atbm8830_read_reg(priv, REG_FRAME_ERR_CNT, &t); - frame_err |= t; - - atbm8830_reglatch_lock(priv, 0); - - *ber = frame_err * 100 / 32767; - - dprintk("%s: ber=0x%x\n", __func__, *ber); - return 0; -} - -static int atbm8830_read_signal_strength(struct dvb_frontend *fe, u16 *signal) -{ - struct atbm_state *priv = fe->demodulator_priv; - u32 pwm; - u8 t; - - dprintk("%s\n", __func__); - atbm8830_reglatch_lock(priv, 1); - - atbm8830_read_reg(priv, REG_AGC_PWM_VAL + 1, &t); - pwm = t & 0x03; - pwm <<= 8; - atbm8830_read_reg(priv, REG_AGC_PWM_VAL, &t); - pwm |= t; - - atbm8830_reglatch_lock(priv, 0); - - dprintk("AGC PWM = 0x%02X\n", pwm); - pwm = 0x400 - pwm; - - *signal = pwm * 0x10000 / 0x400; - - return 0; -} - -static int atbm8830_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - dprintk("%s\n", __func__); - *snr = 0; - return 0; -} - -static int atbm8830_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - dprintk("%s\n", __func__); - *ucblocks = 0; - return 0; -} - -static int atbm8830_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct atbm_state *priv = fe->demodulator_priv; - - return atbm8830_write_reg(priv, REG_I2C_GATE, enable ? 1 : 0); -} - -static struct dvb_frontend_ops atbm8830_ops = { - .delsys = { SYS_DMBTH }, - .info = { - .name = "AltoBeam ATBM8830/8831 DMB-TH", - .frequency_min = 474000000, - .frequency_max = 858000000, - .frequency_stepsize = 10000, - .caps = - FE_CAN_FEC_AUTO | - FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO - }, - - .release = atbm8830_release, - - .init = atbm8830_init, - .sleep = NULL, - .write = NULL, - .i2c_gate_ctrl = atbm8830_i2c_gate_ctrl, - - .set_frontend = atbm8830_set_fe, - .get_frontend = atbm8830_get_fe, - .get_tune_settings = atbm8830_get_tune_settings, - - .read_status = atbm8830_read_status, - .read_ber = atbm8830_read_ber, - .read_signal_strength = atbm8830_read_signal_strength, - .read_snr = atbm8830_read_snr, - .read_ucblocks = atbm8830_read_ucblocks, -}; - -struct dvb_frontend *atbm8830_attach(const struct atbm8830_config *config, - struct i2c_adapter *i2c) -{ - struct atbm_state *priv = NULL; - u8 data = 0; - - dprintk("%s()\n", __func__); - - if (config == NULL || i2c == NULL) - return NULL; - - priv = kzalloc(sizeof(struct atbm_state), GFP_KERNEL); - if (priv == NULL) - goto error_out; - - priv->config = config; - priv->i2c = i2c; - - /* check if the demod is there */ - if (atbm8830_read_reg(priv, REG_CHIP_ID, &data) != 0) { - dprintk("%s atbm8830/8831 not found at i2c addr 0x%02X\n", - __func__, priv->config->demod_address); - goto error_out; - } - dprintk("atbm8830 chip id: 0x%02X\n", data); - - memcpy(&priv->frontend.ops, &atbm8830_ops, - sizeof(struct dvb_frontend_ops)); - priv->frontend.demodulator_priv = priv; - - atbm8830_init(&priv->frontend); - - atbm8830_i2c_gate_ctrl(&priv->frontend, 1); - - return &priv->frontend; - -error_out: - dprintk("%s() error_out\n", __func__); - kfree(priv); - return NULL; - -} -EXPORT_SYMBOL(atbm8830_attach); - -MODULE_DESCRIPTION("AltoBeam ATBM8830/8831 GB20600 demodulator driver"); -MODULE_AUTHOR("David T. L. Wong "); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/au8522_common.c b/drivers/media/dvb/frontends/au8522_common.c deleted file mode 100644 index 5cfe151ee394..000000000000 --- a/drivers/media/dvb/frontends/au8522_common.c +++ /dev/null @@ -1,259 +0,0 @@ -/* - Auvitek AU8522 QAM/8VSB demodulator driver - - Copyright (C) 2008 Steven Toth - Copyright (C) 2008 Devin Heitmueller - Copyright (C) 2005-2008 Auvitek International, Ltd. - Copyright (C) 2012 Michael Krufky - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include -#include "dvb_frontend.h" -#include "au8522_priv.h" - -MODULE_LICENSE("GPL"); - -static int debug; - -#define dprintk(arg...)\ - do { if (debug)\ - printk(arg);\ - } while (0) - -/* Despite the name "hybrid_tuner", the framework works just as well for - hybrid demodulators as well... */ -static LIST_HEAD(hybrid_tuner_instance_list); -static DEFINE_MUTEX(au8522_list_mutex); - -/* 16 bit registers, 8 bit values */ -int au8522_writereg(struct au8522_state *state, u16 reg, u8 data) -{ - int ret; - u8 buf[] = { (reg >> 8) | 0x80, reg & 0xff, data }; - - struct i2c_msg msg = { .addr = state->config->demod_address, - .flags = 0, .buf = buf, .len = 3 }; - - ret = i2c_transfer(state->i2c, &msg, 1); - - if (ret != 1) - printk("%s: writereg error (reg == 0x%02x, val == 0x%04x, " - "ret == %i)\n", __func__, reg, data, ret); - - return (ret != 1) ? -1 : 0; -} -EXPORT_SYMBOL(au8522_writereg); - -u8 au8522_readreg(struct au8522_state *state, u16 reg) -{ - int ret; - u8 b0[] = { (reg >> 8) | 0x40, reg & 0xff }; - u8 b1[] = { 0 }; - - struct i2c_msg msg[] = { - { .addr = state->config->demod_address, .flags = 0, - .buf = b0, .len = 2 }, - { .addr = state->config->demod_address, .flags = I2C_M_RD, - .buf = b1, .len = 1 } }; - - ret = i2c_transfer(state->i2c, msg, 2); - - if (ret != 2) - printk(KERN_ERR "%s: readreg error (ret == %i)\n", - __func__, ret); - return b1[0]; -} -EXPORT_SYMBOL(au8522_readreg); - -int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct au8522_state *state = fe->demodulator_priv; - - dprintk("%s(%d)\n", __func__, enable); - - if (state->operational_mode == AU8522_ANALOG_MODE) { - /* We're being asked to manage the gate even though we're - not in digital mode. This can occur if we get switched - over to analog mode before the dvb_frontend kernel thread - has completely shutdown */ - return 0; - } - - if (enable) - return au8522_writereg(state, 0x106, 1); - else - return au8522_writereg(state, 0x106, 0); -} -EXPORT_SYMBOL(au8522_i2c_gate_ctrl); - -/* Reset the demod hardware and reset all of the configuration registers - to a default state. */ -int au8522_get_state(struct au8522_state **state, struct i2c_adapter *i2c, - u8 client_address) -{ - int ret; - - mutex_lock(&au8522_list_mutex); - ret = hybrid_tuner_request_state(struct au8522_state, (*state), - hybrid_tuner_instance_list, - i2c, client_address, "au8522"); - mutex_unlock(&au8522_list_mutex); - - return ret; -} -EXPORT_SYMBOL(au8522_get_state); - -void au8522_release_state(struct au8522_state *state) -{ - mutex_lock(&au8522_list_mutex); - if (state != NULL) - hybrid_tuner_release_state(state); - mutex_unlock(&au8522_list_mutex); -} -EXPORT_SYMBOL(au8522_release_state); - -static int au8522_led_gpio_enable(struct au8522_state *state, int onoff) -{ - struct au8522_led_config *led_config = state->config->led_cfg; - u8 val; - - /* bail out if we can't control an LED */ - if (!led_config || !led_config->gpio_output || - !led_config->gpio_output_enable || !led_config->gpio_output_disable) - return 0; - - val = au8522_readreg(state, 0x4000 | - (led_config->gpio_output & ~0xc000)); - if (onoff) { - /* enable GPIO output */ - val &= ~((led_config->gpio_output_enable >> 8) & 0xff); - val |= (led_config->gpio_output_enable & 0xff); - } else { - /* disable GPIO output */ - val &= ~((led_config->gpio_output_disable >> 8) & 0xff); - val |= (led_config->gpio_output_disable & 0xff); - } - return au8522_writereg(state, 0x8000 | - (led_config->gpio_output & ~0xc000), val); -} - -/* led = 0 | off - * led = 1 | signal ok - * led = 2 | signal strong - * led < 0 | only light led if leds are currently off - */ -int au8522_led_ctrl(struct au8522_state *state, int led) -{ - struct au8522_led_config *led_config = state->config->led_cfg; - int i, ret = 0; - - /* bail out if we can't control an LED */ - if (!led_config || !led_config->gpio_leds || - !led_config->num_led_states || !led_config->led_states) - return 0; - - if (led < 0) { - /* if LED is already lit, then leave it as-is */ - if (state->led_state) - return 0; - else - led *= -1; - } - - /* toggle LED if changing state */ - if (state->led_state != led) { - u8 val; - - dprintk("%s: %d\n", __func__, led); - - au8522_led_gpio_enable(state, 1); - - val = au8522_readreg(state, 0x4000 | - (led_config->gpio_leds & ~0xc000)); - - /* start with all leds off */ - for (i = 0; i < led_config->num_led_states; i++) - val &= ~led_config->led_states[i]; - - /* set selected LED state */ - if (led < led_config->num_led_states) - val |= led_config->led_states[led]; - else if (led_config->num_led_states) - val |= - led_config->led_states[led_config->num_led_states - 1]; - - ret = au8522_writereg(state, 0x8000 | - (led_config->gpio_leds & ~0xc000), val); - if (ret < 0) - return ret; - - state->led_state = led; - - if (led == 0) - au8522_led_gpio_enable(state, 0); - } - - return 0; -} -EXPORT_SYMBOL(au8522_led_ctrl); - -int au8522_init(struct dvb_frontend *fe) -{ - struct au8522_state *state = fe->demodulator_priv; - dprintk("%s()\n", __func__); - - state->operational_mode = AU8522_DIGITAL_MODE; - - /* Clear out any state associated with the digital side of the - chip, so that when it gets powered back up it won't think - that it is already tuned */ - state->current_frequency = 0; - - au8522_writereg(state, 0xa4, 1 << 5); - - au8522_i2c_gate_ctrl(fe, 1); - - return 0; -} -EXPORT_SYMBOL(au8522_init); - -int au8522_sleep(struct dvb_frontend *fe) -{ - struct au8522_state *state = fe->demodulator_priv; - dprintk("%s()\n", __func__); - - /* Only power down if the digital side is currently using the chip */ - if (state->operational_mode == AU8522_ANALOG_MODE) { - /* We're not in one of the expected power modes, which means - that the DVB thread is probably telling us to go to sleep - even though the analog frontend has already started using - the chip. So ignore the request */ - return 0; - } - - /* turn off led */ - au8522_led_ctrl(state, 0); - - /* Power down the chip */ - au8522_writereg(state, 0xa4, 1 << 5); - - state->current_frequency = 0; - - return 0; -} -EXPORT_SYMBOL(au8522_sleep); diff --git a/drivers/media/dvb/frontends/au8522_decoder.c b/drivers/media/dvb/frontends/au8522_decoder.c deleted file mode 100644 index 55b6390198e3..000000000000 --- a/drivers/media/dvb/frontends/au8522_decoder.c +++ /dev/null @@ -1,842 +0,0 @@ -/* - * Auvitek AU8522 QAM/8VSB demodulator driver and video decoder - * - * Copyright (C) 2009 Devin Heitmueller - * Copyright (C) 2005-2008 Auvitek International, Ltd. - * - * 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. - */ - -/* Developer notes: - * - * VBI support is not yet working - * Enough is implemented here for CVBS and S-Video inputs, but the actual - * analog demodulator code isn't implemented (not needed for xc5000 since it - * has its own demodulator and outputs CVBS) - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "au8522.h" -#include "au8522_priv.h" - -MODULE_AUTHOR("Devin Heitmueller"); -MODULE_LICENSE("GPL"); - -static int au8522_analog_debug; - - -module_param_named(analog_debug, au8522_analog_debug, int, 0644); - -MODULE_PARM_DESC(analog_debug, - "Analog debugging messages [0=Off (default) 1=On]"); - -struct au8522_register_config { - u16 reg_name; - u8 reg_val[8]; -}; - - -/* Video Decoder Filter Coefficients - The values are as follows from left to right - 0="ATV RF" 1="ATV RF13" 2="CVBS" 3="S-Video" 4="PAL" 5=CVBS13" 6="SVideo13" -*/ -static const struct au8522_register_config filter_coef[] = { - {AU8522_FILTER_COEF_R410, {0x25, 0x00, 0x25, 0x25, 0x00, 0x00, 0x00} }, - {AU8522_FILTER_COEF_R411, {0x20, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00} }, - {AU8522_FILTER_COEF_R412, {0x03, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00} }, - {AU8522_FILTER_COEF_R413, {0xe6, 0x00, 0xe6, 0xe6, 0x00, 0x00, 0x00} }, - {AU8522_FILTER_COEF_R414, {0x40, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00} }, - {AU8522_FILTER_COEF_R415, {0x1b, 0x00, 0x1b, 0x1b, 0x00, 0x00, 0x00} }, - {AU8522_FILTER_COEF_R416, {0xc0, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x00} }, - {AU8522_FILTER_COEF_R417, {0x04, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00} }, - {AU8522_FILTER_COEF_R418, {0x8c, 0x00, 0x8c, 0x8c, 0x00, 0x00, 0x00} }, - {AU8522_FILTER_COEF_R419, {0xa0, 0x40, 0xa0, 0xa0, 0x40, 0x40, 0x40} }, - {AU8522_FILTER_COEF_R41A, {0x21, 0x09, 0x21, 0x21, 0x09, 0x09, 0x09} }, - {AU8522_FILTER_COEF_R41B, {0x6c, 0x38, 0x6c, 0x6c, 0x38, 0x38, 0x38} }, - {AU8522_FILTER_COEF_R41C, {0x03, 0xff, 0x03, 0x03, 0xff, 0xff, 0xff} }, - {AU8522_FILTER_COEF_R41D, {0xbf, 0xc7, 0xbf, 0xbf, 0xc7, 0xc7, 0xc7} }, - {AU8522_FILTER_COEF_R41E, {0xa0, 0xdf, 0xa0, 0xa0, 0xdf, 0xdf, 0xdf} }, - {AU8522_FILTER_COEF_R41F, {0x10, 0x06, 0x10, 0x10, 0x06, 0x06, 0x06} }, - {AU8522_FILTER_COEF_R420, {0xae, 0x30, 0xae, 0xae, 0x30, 0x30, 0x30} }, - {AU8522_FILTER_COEF_R421, {0xc4, 0x01, 0xc4, 0xc4, 0x01, 0x01, 0x01} }, - {AU8522_FILTER_COEF_R422, {0x54, 0xdd, 0x54, 0x54, 0xdd, 0xdd, 0xdd} }, - {AU8522_FILTER_COEF_R423, {0xd0, 0xaf, 0xd0, 0xd0, 0xaf, 0xaf, 0xaf} }, - {AU8522_FILTER_COEF_R424, {0x1c, 0xf7, 0x1c, 0x1c, 0xf7, 0xf7, 0xf7} }, - {AU8522_FILTER_COEF_R425, {0x76, 0xdb, 0x76, 0x76, 0xdb, 0xdb, 0xdb} }, - {AU8522_FILTER_COEF_R426, {0x61, 0xc0, 0x61, 0x61, 0xc0, 0xc0, 0xc0} }, - {AU8522_FILTER_COEF_R427, {0xd1, 0x2f, 0xd1, 0xd1, 0x2f, 0x2f, 0x2f} }, - {AU8522_FILTER_COEF_R428, {0x84, 0xd8, 0x84, 0x84, 0xd8, 0xd8, 0xd8} }, - {AU8522_FILTER_COEF_R429, {0x06, 0xfb, 0x06, 0x06, 0xfb, 0xfb, 0xfb} }, - {AU8522_FILTER_COEF_R42A, {0x21, 0xd5, 0x21, 0x21, 0xd5, 0xd5, 0xd5} }, - {AU8522_FILTER_COEF_R42B, {0x0a, 0x3e, 0x0a, 0x0a, 0x3e, 0x3e, 0x3e} }, - {AU8522_FILTER_COEF_R42C, {0xe6, 0x15, 0xe6, 0xe6, 0x15, 0x15, 0x15} }, - {AU8522_FILTER_COEF_R42D, {0x01, 0x34, 0x01, 0x01, 0x34, 0x34, 0x34} }, - -}; -#define NUM_FILTER_COEF (sizeof(filter_coef)\ - / sizeof(struct au8522_register_config)) - - -/* Registers 0x060b through 0x0652 are the LP Filter coefficients - The values are as follows from left to right - 0="SIF" 1="ATVRF/ATVRF13" - Note: the "ATVRF/ATVRF13" mode has never been tested -*/ -static const struct au8522_register_config lpfilter_coef[] = { - {0x060b, {0x21, 0x0b} }, - {0x060c, {0xad, 0xad} }, - {0x060d, {0x70, 0xf0} }, - {0x060e, {0xea, 0xe9} }, - {0x060f, {0xdd, 0xdd} }, - {0x0610, {0x08, 0x64} }, - {0x0611, {0x60, 0x60} }, - {0x0612, {0xf8, 0xb2} }, - {0x0613, {0x01, 0x02} }, - {0x0614, {0xe4, 0xb4} }, - {0x0615, {0x19, 0x02} }, - {0x0616, {0xae, 0x2e} }, - {0x0617, {0xee, 0xc5} }, - {0x0618, {0x56, 0x56} }, - {0x0619, {0x30, 0x58} }, - {0x061a, {0xf9, 0xf8} }, - {0x061b, {0x24, 0x64} }, - {0x061c, {0x07, 0x07} }, - {0x061d, {0x30, 0x30} }, - {0x061e, {0xa9, 0xed} }, - {0x061f, {0x09, 0x0b} }, - {0x0620, {0x42, 0xc2} }, - {0x0621, {0x1d, 0x2a} }, - {0x0622, {0xd6, 0x56} }, - {0x0623, {0x95, 0x8b} }, - {0x0624, {0x2b, 0x2b} }, - {0x0625, {0x30, 0x24} }, - {0x0626, {0x3e, 0x3e} }, - {0x0627, {0x62, 0xe2} }, - {0x0628, {0xe9, 0xf5} }, - {0x0629, {0x99, 0x19} }, - {0x062a, {0xd4, 0x11} }, - {0x062b, {0x03, 0x04} }, - {0x062c, {0xb5, 0x85} }, - {0x062d, {0x1e, 0x20} }, - {0x062e, {0x2a, 0xea} }, - {0x062f, {0xd7, 0xd2} }, - {0x0630, {0x15, 0x15} }, - {0x0631, {0xa3, 0xa9} }, - {0x0632, {0x1f, 0x1f} }, - {0x0633, {0xf9, 0xd1} }, - {0x0634, {0xc0, 0xc3} }, - {0x0635, {0x4d, 0x8d} }, - {0x0636, {0x21, 0x31} }, - {0x0637, {0x83, 0x83} }, - {0x0638, {0x08, 0x8c} }, - {0x0639, {0x19, 0x19} }, - {0x063a, {0x45, 0xa5} }, - {0x063b, {0xef, 0xec} }, - {0x063c, {0x8a, 0x8a} }, - {0x063d, {0xf4, 0xf6} }, - {0x063e, {0x8f, 0x8f} }, - {0x063f, {0x44, 0x0c} }, - {0x0640, {0xef, 0xf0} }, - {0x0641, {0x66, 0x66} }, - {0x0642, {0xcc, 0xd2} }, - {0x0643, {0x41, 0x41} }, - {0x0644, {0x63, 0x93} }, - {0x0645, {0x8e, 0x8e} }, - {0x0646, {0xa2, 0x42} }, - {0x0647, {0x7b, 0x7b} }, - {0x0648, {0x04, 0x04} }, - {0x0649, {0x00, 0x00} }, - {0x064a, {0x40, 0x40} }, - {0x064b, {0x8c, 0x98} }, - {0x064c, {0x00, 0x00} }, - {0x064d, {0x63, 0xc3} }, - {0x064e, {0x04, 0x04} }, - {0x064f, {0x20, 0x20} }, - {0x0650, {0x00, 0x00} }, - {0x0651, {0x40, 0x40} }, - {0x0652, {0x01, 0x01} }, -}; -#define NUM_LPFILTER_COEF (sizeof(lpfilter_coef)\ - / sizeof(struct au8522_register_config)) - -static inline struct au8522_state *to_state(struct v4l2_subdev *sd) -{ - return container_of(sd, struct au8522_state, sd); -} - -static void setup_vbi(struct au8522_state *state, int aud_input) -{ - int i; - - /* These are set to zero regardless of what mode we're in */ - au8522_writereg(state, AU8522_TVDEC_VBI_CTRL_H_REG017H, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_CTRL_L_REG018H, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_TOTAL_BITS_REG019H, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_TUNIT_H_REG01AH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_TUNIT_L_REG01BH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_THRESH1_REG01CH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_PAT2_REG01EH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_PAT1_REG01FH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_PAT0_REG020H, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_MASK2_REG021H, - 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_MASK1_REG022H, - 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_MASK0_REG023H, - 0x00); - - /* Setup the VBI registers */ - for (i = 0x30; i < 0x60; i++) - au8522_writereg(state, i, 0x40); - - /* For some reason, every register is 0x40 except register 0x44 - (confirmed via the HVR-950q USB capture) */ - au8522_writereg(state, 0x44, 0x60); - - /* Enable VBI (we always do this regardless of whether the user is - viewing closed caption info) */ - au8522_writereg(state, AU8522_TVDEC_VBI_CTRL_H_REG017H, - AU8522_TVDEC_VBI_CTRL_H_REG017H_CCON); - -} - -static void setup_decoder_defaults(struct au8522_state *state, u8 input_mode) -{ - int i; - int filter_coef_type; - - /* Provide reasonable defaults for picture tuning values */ - au8522_writereg(state, AU8522_TVDEC_SHARPNESSREG009H, 0x07); - au8522_writereg(state, AU8522_TVDEC_BRIGHTNESS_REG00AH, 0xed); - state->brightness = 0xed - 128; - au8522_writereg(state, AU8522_TVDEC_CONTRAST_REG00BH, 0x79); - state->contrast = 0x79; - au8522_writereg(state, AU8522_TVDEC_SATURATION_CB_REG00CH, 0x80); - au8522_writereg(state, AU8522_TVDEC_SATURATION_CR_REG00DH, 0x80); - state->saturation = 0x80; - au8522_writereg(state, AU8522_TVDEC_HUE_H_REG00EH, 0x00); - au8522_writereg(state, AU8522_TVDEC_HUE_L_REG00FH, 0x00); - state->hue = 0x00; - - /* Other decoder registers */ - au8522_writereg(state, AU8522_TVDEC_INT_MASK_REG010H, 0x00); - - if (input_mode == 0x23) { - /* S-Video input mapping */ - au8522_writereg(state, AU8522_VIDEO_MODE_REG011H, 0x04); - } else { - /* All other modes (CVBS/ATVRF etc.) */ - au8522_writereg(state, AU8522_VIDEO_MODE_REG011H, 0x00); - } - - au8522_writereg(state, AU8522_TVDEC_PGA_REG012H, - AU8522_TVDEC_PGA_REG012H_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_MODE_REG015H, - AU8522_TVDEC_COMB_MODE_REG015H_CVBS); - au8522_writereg(state, AU8522_TVDED_DBG_MODE_REG060H, - AU8522_TVDED_DBG_MODE_REG060H_CVBS); - au8522_writereg(state, AU8522_TVDEC_FORMAT_CTRL1_REG061H, - AU8522_TVDEC_FORMAT_CTRL1_REG061H_CVBS13); - au8522_writereg(state, AU8522_TVDEC_FORMAT_CTRL2_REG062H, - AU8522_TVDEC_FORMAT_CTRL2_REG062H_CVBS13); - au8522_writereg(state, AU8522_TVDEC_VCR_DET_LLIM_REG063H, - AU8522_TVDEC_VCR_DET_LLIM_REG063H_CVBS); - au8522_writereg(state, AU8522_TVDEC_VCR_DET_HLIM_REG064H, - AU8522_TVDEC_VCR_DET_HLIM_REG064H_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_VDIF_THR1_REG065H, - AU8522_TVDEC_COMB_VDIF_THR1_REG065H_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_VDIF_THR2_REG066H, - AU8522_TVDEC_COMB_VDIF_THR2_REG066H_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_VDIF_THR3_REG067H, - AU8522_TVDEC_COMB_VDIF_THR3_REG067H_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_NOTCH_THR_REG068H, - AU8522_TVDEC_COMB_NOTCH_THR_REG068H_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_HDIF_THR1_REG069H, - AU8522_TVDEC_COMB_HDIF_THR1_REG069H_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_HDIF_THR2_REG06AH, - AU8522_TVDEC_COMB_HDIF_THR2_REG06AH_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_HDIF_THR3_REG06BH, - AU8522_TVDEC_COMB_HDIF_THR3_REG06BH_CVBS); - if (input_mode == AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13 || - input_mode == AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH24) { - au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH, - AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH_SVIDEO); - au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH, - AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH_SVIDEO); - } else { - au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH, - AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH, - AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH_CVBS); - } - au8522_writereg(state, AU8522_TVDEC_COMB_DCDIF_THR3_REG06EH, - AU8522_TVDEC_COMB_DCDIF_THR3_REG06EH_CVBS); - au8522_writereg(state, AU8522_TVDEC_UV_SEP_THR_REG06FH, - AU8522_TVDEC_UV_SEP_THR_REG06FH_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_DC_THR1_NTSC_REG070H, - AU8522_TVDEC_COMB_DC_THR1_NTSC_REG070H_CVBS); - au8522_writereg(state, AU8522_REG071H, AU8522_REG071H_CVBS); - au8522_writereg(state, AU8522_REG072H, AU8522_REG072H_CVBS); - au8522_writereg(state, AU8522_TVDEC_COMB_DC_THR2_NTSC_REG073H, - AU8522_TVDEC_COMB_DC_THR2_NTSC_REG073H_CVBS); - au8522_writereg(state, AU8522_REG074H, AU8522_REG074H_CVBS); - au8522_writereg(state, AU8522_REG075H, AU8522_REG075H_CVBS); - au8522_writereg(state, AU8522_TVDEC_DCAGC_CTRL_REG077H, - AU8522_TVDEC_DCAGC_CTRL_REG077H_CVBS); - au8522_writereg(state, AU8522_TVDEC_PIC_START_ADJ_REG078H, - AU8522_TVDEC_PIC_START_ADJ_REG078H_CVBS); - au8522_writereg(state, AU8522_TVDEC_AGC_HIGH_LIMIT_REG079H, - AU8522_TVDEC_AGC_HIGH_LIMIT_REG079H_CVBS); - au8522_writereg(state, AU8522_TVDEC_MACROVISION_SYNC_THR_REG07AH, - AU8522_TVDEC_MACROVISION_SYNC_THR_REG07AH_CVBS); - au8522_writereg(state, AU8522_TVDEC_INTRP_CTRL_REG07BH, - AU8522_TVDEC_INTRP_CTRL_REG07BH_CVBS); - au8522_writereg(state, AU8522_TVDEC_AGC_LOW_LIMIT_REG0E4H, - AU8522_TVDEC_AGC_LOW_LIMIT_REG0E4H_CVBS); - au8522_writereg(state, AU8522_TOREGAAGC_REG0E5H, - AU8522_TOREGAAGC_REG0E5H_CVBS); - au8522_writereg(state, AU8522_REG016H, AU8522_REG016H_CVBS); - - setup_vbi(state, 0); - - if (input_mode == AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13 || - input_mode == AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH24) { - /* Despite what the table says, for the HVR-950q we still need - to be in CVBS mode for the S-Video input (reason unknown). */ - /* filter_coef_type = 3; */ - filter_coef_type = 5; - } else { - filter_coef_type = 5; - } - - /* Load the Video Decoder Filter Coefficients */ - for (i = 0; i < NUM_FILTER_COEF; i++) { - au8522_writereg(state, filter_coef[i].reg_name, - filter_coef[i].reg_val[filter_coef_type]); - } - - /* It's not clear what these registers are for, but they are always - set to the same value regardless of what mode we're in */ - au8522_writereg(state, AU8522_REG42EH, 0x87); - au8522_writereg(state, AU8522_REG42FH, 0xa2); - au8522_writereg(state, AU8522_REG430H, 0xbf); - au8522_writereg(state, AU8522_REG431H, 0xcb); - au8522_writereg(state, AU8522_REG432H, 0xa1); - au8522_writereg(state, AU8522_REG433H, 0x41); - au8522_writereg(state, AU8522_REG434H, 0x88); - au8522_writereg(state, AU8522_REG435H, 0xc2); - au8522_writereg(state, AU8522_REG436H, 0x3c); -} - -static void au8522_setup_cvbs_mode(struct au8522_state *state) -{ - /* here we're going to try the pre-programmed route */ - au8522_writereg(state, AU8522_MODULE_CLOCK_CONTROL_REG0A3H, - AU8522_MODULE_CLOCK_CONTROL_REG0A3H_CVBS); - - /* PGA in automatic mode */ - au8522_writereg(state, AU8522_PGA_CONTROL_REG082H, 0x00); - - /* Enable clamping control */ - au8522_writereg(state, AU8522_CLAMPING_CONTROL_REG083H, 0x00); - - au8522_writereg(state, AU8522_INPUT_CONTROL_REG081H, - AU8522_INPUT_CONTROL_REG081H_CVBS_CH1); - - setup_decoder_defaults(state, AU8522_INPUT_CONTROL_REG081H_CVBS_CH1); - - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, - AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS); -} - -static void au8522_setup_cvbs_tuner_mode(struct au8522_state *state) -{ - /* here we're going to try the pre-programmed route */ - au8522_writereg(state, AU8522_MODULE_CLOCK_CONTROL_REG0A3H, - AU8522_MODULE_CLOCK_CONTROL_REG0A3H_CVBS); - - /* It's not clear why we have to have the PGA in automatic mode while - enabling clamp control, but it's what Windows does */ - au8522_writereg(state, AU8522_PGA_CONTROL_REG082H, 0x00); - - /* Enable clamping control */ - au8522_writereg(state, AU8522_CLAMPING_CONTROL_REG083H, 0x0e); - - /* Disable automatic PGA (since the CVBS is coming from the tuner) */ - au8522_writereg(state, AU8522_PGA_CONTROL_REG082H, 0x10); - - /* Set input mode to CVBS on channel 4 with SIF audio input enabled */ - au8522_writereg(state, AU8522_INPUT_CONTROL_REG081H, - AU8522_INPUT_CONTROL_REG081H_CVBS_CH4_SIF); - - setup_decoder_defaults(state, - AU8522_INPUT_CONTROL_REG081H_CVBS_CH4_SIF); - - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, - AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS); -} - -static void au8522_setup_svideo_mode(struct au8522_state *state) -{ - au8522_writereg(state, AU8522_MODULE_CLOCK_CONTROL_REG0A3H, - AU8522_MODULE_CLOCK_CONTROL_REG0A3H_SVIDEO); - - /* Set input to Y on Channe1, C on Channel 3 */ - au8522_writereg(state, AU8522_INPUT_CONTROL_REG081H, - AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13); - - /* PGA in automatic mode */ - au8522_writereg(state, AU8522_PGA_CONTROL_REG082H, 0x00); - - /* Enable clamping control */ - au8522_writereg(state, AU8522_CLAMPING_CONTROL_REG083H, 0x00); - - setup_decoder_defaults(state, - AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13); - - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, - AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS); -} - -/* ----------------------------------------------------------------------- */ - -static void disable_audio_input(struct au8522_state *state) -{ - au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x00); - au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x00); - au8522_writereg(state, AU8522_AUDIO_VOLUME_REG0F4H, 0x00); - - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H, 0x04); - au8522_writereg(state, AU8522_I2S_CTRL_2_REG112H, 0x02); - - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, - AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_SVIDEO); -} - -/* 0=disable, 1=SIF */ -static void set_audio_input(struct au8522_state *state, int aud_input) -{ - int i; - - /* Note that this function needs to be used in conjunction with setting - the input routing via register 0x81 */ - - if (aud_input == AU8522_AUDIO_NONE) { - disable_audio_input(state); - return; - } - - if (aud_input != AU8522_AUDIO_SIF) { - /* The caller asked for a mode we don't currently support */ - printk(KERN_ERR "Unsupported audio mode requested! mode=%d\n", - aud_input); - return; - } - - /* Load the Audio Decoder Filter Coefficients */ - for (i = 0; i < NUM_LPFILTER_COEF; i++) { - au8522_writereg(state, lpfilter_coef[i].reg_name, - lpfilter_coef[i].reg_val[0]); - } - - /* Setup audio */ - au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x00); - au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x00); - au8522_writereg(state, AU8522_AUDIO_VOLUME_REG0F4H, 0x00); - au8522_writereg(state, AU8522_I2C_CONTROL_REG1_REG091H, 0x80); - au8522_writereg(state, AU8522_I2C_CONTROL_REG0_REG090H, 0x84); - msleep(150); - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, 0x00); - msleep(1); - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, 0x9d); - msleep(50); - au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x7F); - au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x7F); - au8522_writereg(state, AU8522_AUDIO_VOLUME_REG0F4H, 0xff); - msleep(80); - au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x7F); - au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x7F); - au8522_writereg(state, AU8522_REG0F9H, AU8522_REG0F9H_AUDIO); - au8522_writereg(state, AU8522_AUDIO_MODE_REG0F1H, 0x82); - msleep(70); - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H, 0x09); - au8522_writereg(state, AU8522_AUDIOFREQ_REG606H, 0x03); - au8522_writereg(state, AU8522_I2S_CTRL_2_REG112H, 0xc2); -} - -/* ----------------------------------------------------------------------- */ - -static int au8522_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct au8522_state *state = to_state(sd); - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - state->brightness = ctrl->value; - au8522_writereg(state, AU8522_TVDEC_BRIGHTNESS_REG00AH, - ctrl->value - 128); - break; - case V4L2_CID_CONTRAST: - state->contrast = ctrl->value; - au8522_writereg(state, AU8522_TVDEC_CONTRAST_REG00BH, - ctrl->value); - break; - case V4L2_CID_SATURATION: - state->saturation = ctrl->value; - au8522_writereg(state, AU8522_TVDEC_SATURATION_CB_REG00CH, - ctrl->value); - au8522_writereg(state, AU8522_TVDEC_SATURATION_CR_REG00DH, - ctrl->value); - break; - case V4L2_CID_HUE: - state->hue = ctrl->value; - au8522_writereg(state, AU8522_TVDEC_HUE_H_REG00EH, - ctrl->value >> 8); - au8522_writereg(state, AU8522_TVDEC_HUE_L_REG00FH, - ctrl->value & 0xFF); - break; - case V4L2_CID_AUDIO_VOLUME: - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - case V4L2_CID_AUDIO_BALANCE: - case V4L2_CID_AUDIO_MUTE: - /* Not yet implemented */ - default: - return -EINVAL; - } - - return 0; -} - -static int au8522_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct au8522_state *state = to_state(sd); - - /* Note that we are using values cached in the state structure instead - of reading the registers due to issues with i2c reads not working - properly/consistently yet on the HVR-950q */ - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - ctrl->value = state->brightness; - break; - case V4L2_CID_CONTRAST: - ctrl->value = state->contrast; - break; - case V4L2_CID_SATURATION: - ctrl->value = state->saturation; - break; - case V4L2_CID_HUE: - ctrl->value = state->hue; - break; - case V4L2_CID_AUDIO_VOLUME: - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - case V4L2_CID_AUDIO_BALANCE: - case V4L2_CID_AUDIO_MUTE: - /* Not yet supported */ - default: - return -EINVAL; - } - - return 0; -} - -/* ----------------------------------------------------------------------- */ - -#ifdef CONFIG_VIDEO_ADV_DEBUG -static int au8522_g_register(struct v4l2_subdev *sd, - struct v4l2_dbg_register *reg) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - struct au8522_state *state = to_state(sd); - - if (!v4l2_chip_match_i2c_client(client, ®->match)) - return -EINVAL; - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - reg->val = au8522_readreg(state, reg->reg & 0xffff); - return 0; -} - -static int au8522_s_register(struct v4l2_subdev *sd, - struct v4l2_dbg_register *reg) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - struct au8522_state *state = to_state(sd); - - if (!v4l2_chip_match_i2c_client(client, ®->match)) - return -EINVAL; - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - au8522_writereg(state, reg->reg, reg->val & 0xff); - return 0; -} -#endif - -static int au8522_s_stream(struct v4l2_subdev *sd, int enable) -{ - struct au8522_state *state = to_state(sd); - - if (enable) { - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, - 0x01); - msleep(1); - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, - AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS); - } else { - /* This does not completely power down the device - (it only reduces it from around 140ma to 80ma) */ - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, - 1 << 5); - } - return 0; -} - -static int au8522_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_CONTRAST: - return v4l2_ctrl_query_fill(qc, 0, 255, 1, - AU8522_TVDEC_CONTRAST_REG00BH_CVBS); - case V4L2_CID_BRIGHTNESS: - return v4l2_ctrl_query_fill(qc, 0, 255, 1, 109); - case V4L2_CID_SATURATION: - return v4l2_ctrl_query_fill(qc, 0, 255, 1, 128); - case V4L2_CID_HUE: - return v4l2_ctrl_query_fill(qc, -32768, 32768, 1, 0); - default: - break; - } - - qc->type = 0; - return -EINVAL; -} - -static int au8522_reset(struct v4l2_subdev *sd, u32 val) -{ - struct au8522_state *state = to_state(sd); - - state->operational_mode = AU8522_ANALOG_MODE; - - /* Clear out any state associated with the digital side of the - chip, so that when it gets powered back up it won't think - that it is already tuned */ - state->current_frequency = 0; - - au8522_writereg(state, 0xa4, 1 << 5); - - return 0; -} - -static int au8522_s_video_routing(struct v4l2_subdev *sd, - u32 input, u32 output, u32 config) -{ - struct au8522_state *state = to_state(sd); - - au8522_reset(sd, 0); - - /* Jam open the i2c gate to the tuner. We do this here to handle the - case where the user went into digital mode (causing the gate to be - closed), and then came back to analog mode */ - au8522_writereg(state, 0x106, 1); - - if (input == AU8522_COMPOSITE_CH1) { - au8522_setup_cvbs_mode(state); - } else if (input == AU8522_SVIDEO_CH13) { - au8522_setup_svideo_mode(state); - } else if (input == AU8522_COMPOSITE_CH4_SIF) { - au8522_setup_cvbs_tuner_mode(state); - } else { - printk(KERN_ERR "au8522 mode not currently supported\n"); - return -EINVAL; - } - return 0; -} - -static int au8522_s_audio_routing(struct v4l2_subdev *sd, - u32 input, u32 output, u32 config) -{ - struct au8522_state *state = to_state(sd); - set_audio_input(state, input); - return 0; -} - -static int au8522_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) -{ - int val = 0; - struct au8522_state *state = to_state(sd); - u8 lock_status; - - /* Interrogate the decoder to see if we are getting a real signal */ - lock_status = au8522_readreg(state, 0x00); - if (lock_status == 0xa2) - vt->signal = 0xffff; - else - vt->signal = 0x00; - - vt->capability |= - V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | - V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; - - val = V4L2_TUNER_SUB_MONO; - vt->rxsubchans = val; - vt->audmode = V4L2_TUNER_MODE_STEREO; - return 0; -} - -static int au8522_g_chip_ident(struct v4l2_subdev *sd, - struct v4l2_dbg_chip_ident *chip) -{ - struct au8522_state *state = to_state(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - - return v4l2_chip_ident_i2c_client(client, chip, state->id, state->rev); -} - -static int au8522_log_status(struct v4l2_subdev *sd) -{ - /* FIXME: Add some status info here */ - return 0; -} - -/* ----------------------------------------------------------------------- */ - -static const struct v4l2_subdev_core_ops au8522_core_ops = { - .log_status = au8522_log_status, - .g_chip_ident = au8522_g_chip_ident, - .g_ctrl = au8522_g_ctrl, - .s_ctrl = au8522_s_ctrl, - .queryctrl = au8522_queryctrl, - .reset = au8522_reset, -#ifdef CONFIG_VIDEO_ADV_DEBUG - .g_register = au8522_g_register, - .s_register = au8522_s_register, -#endif -}; - -static const struct v4l2_subdev_tuner_ops au8522_tuner_ops = { - .g_tuner = au8522_g_tuner, -}; - -static const struct v4l2_subdev_audio_ops au8522_audio_ops = { - .s_routing = au8522_s_audio_routing, -}; - -static const struct v4l2_subdev_video_ops au8522_video_ops = { - .s_routing = au8522_s_video_routing, - .s_stream = au8522_s_stream, -}; - -static const struct v4l2_subdev_ops au8522_ops = { - .core = &au8522_core_ops, - .tuner = &au8522_tuner_ops, - .audio = &au8522_audio_ops, - .video = &au8522_video_ops, -}; - -/* ----------------------------------------------------------------------- */ - -static int au8522_probe(struct i2c_client *client, - const struct i2c_device_id *did) -{ - struct au8522_state *state; - struct v4l2_subdev *sd; - int instance; - struct au8522_config *demod_config; - - /* Check if the adapter supports the needed features */ - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_BYTE_DATA)) { - return -EIO; - } - - /* allocate memory for the internal state */ - instance = au8522_get_state(&state, client->adapter, client->addr); - switch (instance) { - case 0: - printk(KERN_ERR "au8522_decoder allocation failed\n"); - return -EIO; - case 1: - /* new demod instance */ - printk(KERN_INFO "au8522_decoder creating new instance...\n"); - break; - default: - /* existing demod instance */ - printk(KERN_INFO "au8522_decoder attach existing instance.\n"); - break; - } - - demod_config = kzalloc(sizeof(struct au8522_config), GFP_KERNEL); - if (demod_config == NULL) { - if (instance == 1) - kfree(state); - return -ENOMEM; - } - demod_config->demod_address = 0x8e >> 1; - - state->config = demod_config; - state->i2c = client->adapter; - - sd = &state->sd; - v4l2_i2c_subdev_init(sd, client, &au8522_ops); - - state->c = client; - state->vid_input = AU8522_COMPOSITE_CH1; - state->aud_input = AU8522_AUDIO_NONE; - state->id = 8522; - state->rev = 0; - - /* Jam open the i2c gate to the tuner */ - au8522_writereg(state, 0x106, 1); - - return 0; -} - -static int au8522_remove(struct i2c_client *client) -{ - struct v4l2_subdev *sd = i2c_get_clientdata(client); - v4l2_device_unregister_subdev(sd); - au8522_release_state(to_state(sd)); - return 0; -} - -static const struct i2c_device_id au8522_id[] = { - {"au8522", 0}, - {} -}; - -MODULE_DEVICE_TABLE(i2c, au8522_id); - -static struct i2c_driver au8522_driver = { - .driver = { - .owner = THIS_MODULE, - .name = "au8522", - }, - .probe = au8522_probe, - .remove = au8522_remove, - .id_table = au8522_id, -}; - -module_i2c_driver(au8522_driver); diff --git a/drivers/media/dvb/frontends/au8522_dig.c b/drivers/media/dvb/frontends/au8522_dig.c deleted file mode 100644 index 5fc70d6cd04f..000000000000 --- a/drivers/media/dvb/frontends/au8522_dig.c +++ /dev/null @@ -1,826 +0,0 @@ -/* - Auvitek AU8522 QAM/8VSB demodulator driver - - Copyright (C) 2008 Steven Toth - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include -#include -#include -#include -#include -#include "dvb_frontend.h" -#include "au8522.h" -#include "au8522_priv.h" - -static int debug; - -#define dprintk(arg...)\ - do { if (debug)\ - printk(arg);\ - } while (0) - -struct mse2snr_tab { - u16 val; - u16 data; -}; - -/* VSB SNR lookup table */ -static struct mse2snr_tab vsb_mse2snr_tab[] = { - { 0, 270 }, - { 2, 250 }, - { 3, 240 }, - { 5, 230 }, - { 7, 220 }, - { 9, 210 }, - { 12, 200 }, - { 13, 195 }, - { 15, 190 }, - { 17, 185 }, - { 19, 180 }, - { 21, 175 }, - { 24, 170 }, - { 27, 165 }, - { 31, 160 }, - { 32, 158 }, - { 33, 156 }, - { 36, 152 }, - { 37, 150 }, - { 39, 148 }, - { 40, 146 }, - { 41, 144 }, - { 43, 142 }, - { 44, 140 }, - { 48, 135 }, - { 50, 130 }, - { 43, 142 }, - { 53, 125 }, - { 56, 120 }, - { 256, 115 }, -}; - -/* QAM64 SNR lookup table */ -static struct mse2snr_tab qam64_mse2snr_tab[] = { - { 15, 0 }, - { 16, 290 }, - { 17, 288 }, - { 18, 286 }, - { 19, 284 }, - { 20, 282 }, - { 21, 281 }, - { 22, 279 }, - { 23, 277 }, - { 24, 275 }, - { 25, 273 }, - { 26, 271 }, - { 27, 269 }, - { 28, 268 }, - { 29, 266 }, - { 30, 264 }, - { 31, 262 }, - { 32, 260 }, - { 33, 259 }, - { 34, 258 }, - { 35, 256 }, - { 36, 255 }, - { 37, 254 }, - { 38, 252 }, - { 39, 251 }, - { 40, 250 }, - { 41, 249 }, - { 42, 248 }, - { 43, 246 }, - { 44, 245 }, - { 45, 244 }, - { 46, 242 }, - { 47, 241 }, - { 48, 240 }, - { 50, 239 }, - { 51, 238 }, - { 53, 237 }, - { 54, 236 }, - { 56, 235 }, - { 57, 234 }, - { 59, 233 }, - { 60, 232 }, - { 62, 231 }, - { 63, 230 }, - { 65, 229 }, - { 67, 228 }, - { 68, 227 }, - { 70, 226 }, - { 71, 225 }, - { 73, 224 }, - { 74, 223 }, - { 76, 222 }, - { 78, 221 }, - { 80, 220 }, - { 82, 219 }, - { 85, 218 }, - { 88, 217 }, - { 90, 216 }, - { 92, 215 }, - { 93, 214 }, - { 94, 212 }, - { 95, 211 }, - { 97, 210 }, - { 99, 209 }, - { 101, 208 }, - { 102, 207 }, - { 104, 206 }, - { 107, 205 }, - { 111, 204 }, - { 114, 203 }, - { 118, 202 }, - { 122, 201 }, - { 125, 200 }, - { 128, 199 }, - { 130, 198 }, - { 132, 197 }, - { 256, 190 }, -}; - -/* QAM256 SNR lookup table */ -static struct mse2snr_tab qam256_mse2snr_tab[] = { - { 16, 0 }, - { 17, 400 }, - { 18, 398 }, - { 19, 396 }, - { 20, 394 }, - { 21, 392 }, - { 22, 390 }, - { 23, 388 }, - { 24, 386 }, - { 25, 384 }, - { 26, 382 }, - { 27, 380 }, - { 28, 379 }, - { 29, 378 }, - { 30, 377 }, - { 31, 376 }, - { 32, 375 }, - { 33, 374 }, - { 34, 373 }, - { 35, 372 }, - { 36, 371 }, - { 37, 370 }, - { 38, 362 }, - { 39, 354 }, - { 40, 346 }, - { 41, 338 }, - { 42, 330 }, - { 43, 328 }, - { 44, 326 }, - { 45, 324 }, - { 46, 322 }, - { 47, 320 }, - { 48, 319 }, - { 49, 318 }, - { 50, 317 }, - { 51, 316 }, - { 52, 315 }, - { 53, 314 }, - { 54, 313 }, - { 55, 312 }, - { 56, 311 }, - { 57, 310 }, - { 58, 308 }, - { 59, 306 }, - { 60, 304 }, - { 61, 302 }, - { 62, 300 }, - { 63, 298 }, - { 65, 295 }, - { 68, 294 }, - { 70, 293 }, - { 73, 292 }, - { 76, 291 }, - { 78, 290 }, - { 79, 289 }, - { 81, 288 }, - { 82, 287 }, - { 83, 286 }, - { 84, 285 }, - { 85, 284 }, - { 86, 283 }, - { 88, 282 }, - { 89, 281 }, - { 256, 280 }, -}; - -static int au8522_mse2snr_lookup(struct mse2snr_tab *tab, int sz, int mse, - u16 *snr) -{ - int i, ret = -EINVAL; - dprintk("%s()\n", __func__); - - for (i = 0; i < sz; i++) { - if (mse < tab[i].val) { - *snr = tab[i].data; - ret = 0; - break; - } - } - dprintk("%s() snr=%d\n", __func__, *snr); - return ret; -} - -static int au8522_set_if(struct dvb_frontend *fe, enum au8522_if_freq if_freq) -{ - struct au8522_state *state = fe->demodulator_priv; - u8 r0b5, r0b6, r0b7; - char *ifmhz; - - switch (if_freq) { - case AU8522_IF_3_25MHZ: - ifmhz = "3.25"; - r0b5 = 0x00; - r0b6 = 0x3d; - r0b7 = 0xa0; - break; - case AU8522_IF_4MHZ: - ifmhz = "4.00"; - r0b5 = 0x00; - r0b6 = 0x4b; - r0b7 = 0xd9; - break; - case AU8522_IF_6MHZ: - ifmhz = "6.00"; - r0b5 = 0xfb; - r0b6 = 0x8e; - r0b7 = 0x39; - break; - default: - dprintk("%s() IF Frequency not supported\n", __func__); - return -EINVAL; - } - dprintk("%s() %s MHz\n", __func__, ifmhz); - au8522_writereg(state, 0x80b5, r0b5); - au8522_writereg(state, 0x80b6, r0b6); - au8522_writereg(state, 0x80b7, r0b7); - - return 0; -} - -/* VSB Modulation table */ -static struct { - u16 reg; - u16 data; -} VSB_mod_tab[] = { - { 0x8090, 0x84 }, - { 0x4092, 0x11 }, - { 0x2005, 0x00 }, - { 0x8091, 0x80 }, - { 0x80a3, 0x0c }, - { 0x80a4, 0xe8 }, - { 0x8081, 0xc4 }, - { 0x80a5, 0x40 }, - { 0x80a7, 0x40 }, - { 0x80a6, 0x67 }, - { 0x8262, 0x20 }, - { 0x821c, 0x30 }, - { 0x80d8, 0x1a }, - { 0x8227, 0xa0 }, - { 0x8121, 0xff }, - { 0x80a8, 0xf0 }, - { 0x80a9, 0x05 }, - { 0x80aa, 0x77 }, - { 0x80ab, 0xf0 }, - { 0x80ac, 0x05 }, - { 0x80ad, 0x77 }, - { 0x80ae, 0x41 }, - { 0x80af, 0x66 }, - { 0x821b, 0xcc }, - { 0x821d, 0x80 }, - { 0x80a4, 0xe8 }, - { 0x8231, 0x13 }, -}; - -/* QAM64 Modulation table */ -static struct { - u16 reg; - u16 data; -} QAM64_mod_tab[] = { - { 0x00a3, 0x09 }, - { 0x00a4, 0x00 }, - { 0x0081, 0xc4 }, - { 0x00a5, 0x40 }, - { 0x00aa, 0x77 }, - { 0x00ad, 0x77 }, - { 0x00a6, 0x67 }, - { 0x0262, 0x20 }, - { 0x021c, 0x30 }, - { 0x00b8, 0x3e }, - { 0x00b9, 0xf0 }, - { 0x00ba, 0x01 }, - { 0x00bb, 0x18 }, - { 0x00bc, 0x50 }, - { 0x00bd, 0x00 }, - { 0x00be, 0xea }, - { 0x00bf, 0xef }, - { 0x00c0, 0xfc }, - { 0x00c1, 0xbd }, - { 0x00c2, 0x1f }, - { 0x00c3, 0xfc }, - { 0x00c4, 0xdd }, - { 0x00c5, 0xaf }, - { 0x00c6, 0x00 }, - { 0x00c7, 0x38 }, - { 0x00c8, 0x30 }, - { 0x00c9, 0x05 }, - { 0x00ca, 0x4a }, - { 0x00cb, 0xd0 }, - { 0x00cc, 0x01 }, - { 0x00cd, 0xd9 }, - { 0x00ce, 0x6f }, - { 0x00cf, 0xf9 }, - { 0x00d0, 0x70 }, - { 0x00d1, 0xdf }, - { 0x00d2, 0xf7 }, - { 0x00d3, 0xc2 }, - { 0x00d4, 0xdf }, - { 0x00d5, 0x02 }, - { 0x00d6, 0x9a }, - { 0x00d7, 0xd0 }, - { 0x0250, 0x0d }, - { 0x0251, 0xcd }, - { 0x0252, 0xe0 }, - { 0x0253, 0x05 }, - { 0x0254, 0xa7 }, - { 0x0255, 0xff }, - { 0x0256, 0xed }, - { 0x0257, 0x5b }, - { 0x0258, 0xae }, - { 0x0259, 0xe6 }, - { 0x025a, 0x3d }, - { 0x025b, 0x0f }, - { 0x025c, 0x0d }, - { 0x025d, 0xea }, - { 0x025e, 0xf2 }, - { 0x025f, 0x51 }, - { 0x0260, 0xf5 }, - { 0x0261, 0x06 }, - { 0x021a, 0x00 }, - { 0x0546, 0x40 }, - { 0x0210, 0xc7 }, - { 0x0211, 0xaa }, - { 0x0212, 0xab }, - { 0x0213, 0x02 }, - { 0x0502, 0x00 }, - { 0x0121, 0x04 }, - { 0x0122, 0x04 }, - { 0x052e, 0x10 }, - { 0x00a4, 0xca }, - { 0x00a7, 0x40 }, - { 0x0526, 0x01 }, -}; - -/* QAM256 Modulation table */ -static struct { - u16 reg; - u16 data; -} QAM256_mod_tab[] = { - { 0x80a3, 0x09 }, - { 0x80a4, 0x00 }, - { 0x8081, 0xc4 }, - { 0x80a5, 0x40 }, - { 0x80aa, 0x77 }, - { 0x80ad, 0x77 }, - { 0x80a6, 0x67 }, - { 0x8262, 0x20 }, - { 0x821c, 0x30 }, - { 0x80b8, 0x3e }, - { 0x80b9, 0xf0 }, - { 0x80ba, 0x01 }, - { 0x80bb, 0x18 }, - { 0x80bc, 0x50 }, - { 0x80bd, 0x00 }, - { 0x80be, 0xea }, - { 0x80bf, 0xef }, - { 0x80c0, 0xfc }, - { 0x80c1, 0xbd }, - { 0x80c2, 0x1f }, - { 0x80c3, 0xfc }, - { 0x80c4, 0xdd }, - { 0x80c5, 0xaf }, - { 0x80c6, 0x00 }, - { 0x80c7, 0x38 }, - { 0x80c8, 0x30 }, - { 0x80c9, 0x05 }, - { 0x80ca, 0x4a }, - { 0x80cb, 0xd0 }, - { 0x80cc, 0x01 }, - { 0x80cd, 0xd9 }, - { 0x80ce, 0x6f }, - { 0x80cf, 0xf9 }, - { 0x80d0, 0x70 }, - { 0x80d1, 0xdf }, - { 0x80d2, 0xf7 }, - { 0x80d3, 0xc2 }, - { 0x80d4, 0xdf }, - { 0x80d5, 0x02 }, - { 0x80d6, 0x9a }, - { 0x80d7, 0xd0 }, - { 0x8250, 0x0d }, - { 0x8251, 0xcd }, - { 0x8252, 0xe0 }, - { 0x8253, 0x05 }, - { 0x8254, 0xa7 }, - { 0x8255, 0xff }, - { 0x8256, 0xed }, - { 0x8257, 0x5b }, - { 0x8258, 0xae }, - { 0x8259, 0xe6 }, - { 0x825a, 0x3d }, - { 0x825b, 0x0f }, - { 0x825c, 0x0d }, - { 0x825d, 0xea }, - { 0x825e, 0xf2 }, - { 0x825f, 0x51 }, - { 0x8260, 0xf5 }, - { 0x8261, 0x06 }, - { 0x821a, 0x00 }, - { 0x8546, 0x40 }, - { 0x8210, 0x26 }, - { 0x8211, 0xf6 }, - { 0x8212, 0x84 }, - { 0x8213, 0x02 }, - { 0x8502, 0x01 }, - { 0x8121, 0x04 }, - { 0x8122, 0x04 }, - { 0x852e, 0x10 }, - { 0x80a4, 0xca }, - { 0x80a7, 0x40 }, - { 0x8526, 0x01 }, -}; - -static int au8522_enable_modulation(struct dvb_frontend *fe, - fe_modulation_t m) -{ - struct au8522_state *state = fe->demodulator_priv; - int i; - - dprintk("%s(0x%08x)\n", __func__, m); - - switch (m) { - case VSB_8: - dprintk("%s() VSB_8\n", __func__); - for (i = 0; i < ARRAY_SIZE(VSB_mod_tab); i++) - au8522_writereg(state, - VSB_mod_tab[i].reg, - VSB_mod_tab[i].data); - au8522_set_if(fe, state->config->vsb_if); - break; - case QAM_64: - dprintk("%s() QAM 64\n", __func__); - for (i = 0; i < ARRAY_SIZE(QAM64_mod_tab); i++) - au8522_writereg(state, - QAM64_mod_tab[i].reg, - QAM64_mod_tab[i].data); - au8522_set_if(fe, state->config->qam_if); - break; - case QAM_256: - dprintk("%s() QAM 256\n", __func__); - for (i = 0; i < ARRAY_SIZE(QAM256_mod_tab); i++) - au8522_writereg(state, - QAM256_mod_tab[i].reg, - QAM256_mod_tab[i].data); - au8522_set_if(fe, state->config->qam_if); - break; - default: - dprintk("%s() Invalid modulation\n", __func__); - return -EINVAL; - } - - state->current_modulation = m; - - return 0; -} - -/* Talk to the demod, set the FEC, GUARD, QAM settings etc */ -static int au8522_set_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct au8522_state *state = fe->demodulator_priv; - int ret = -EINVAL; - - dprintk("%s(frequency=%d)\n", __func__, c->frequency); - - if ((state->current_frequency == c->frequency) && - (state->current_modulation == c->modulation)) - return 0; - - if (fe->ops.tuner_ops.set_params) { - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - ret = fe->ops.tuner_ops.set_params(fe); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } - - if (ret < 0) - return ret; - - /* Allow the tuner to settle */ - msleep(100); - - au8522_enable_modulation(fe, c->modulation); - - state->current_frequency = c->frequency; - - return 0; -} - -static int au8522_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct au8522_state *state = fe->demodulator_priv; - u8 reg; - u32 tuner_status = 0; - - *status = 0; - - if (state->current_modulation == VSB_8) { - dprintk("%s() Checking VSB_8\n", __func__); - reg = au8522_readreg(state, 0x4088); - if ((reg & 0x03) == 0x03) - *status |= FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI; - } else { - dprintk("%s() Checking QAM\n", __func__); - reg = au8522_readreg(state, 0x4541); - if (reg & 0x80) - *status |= FE_HAS_VITERBI; - if (reg & 0x20) - *status |= FE_HAS_LOCK | FE_HAS_SYNC; - } - - switch (state->config->status_mode) { - case AU8522_DEMODLOCKING: - dprintk("%s() DEMODLOCKING\n", __func__); - if (*status & FE_HAS_VITERBI) - *status |= FE_HAS_CARRIER | FE_HAS_SIGNAL; - break; - case AU8522_TUNERLOCKING: - /* Get the tuner status */ - dprintk("%s() TUNERLOCKING\n", __func__); - if (fe->ops.tuner_ops.get_status) { - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - - fe->ops.tuner_ops.get_status(fe, &tuner_status); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } - if (tuner_status) - *status |= FE_HAS_CARRIER | FE_HAS_SIGNAL; - break; - } - state->fe_status = *status; - - if (*status & FE_HAS_LOCK) - /* turn on LED, if it isn't on already */ - au8522_led_ctrl(state, -1); - else - /* turn off LED */ - au8522_led_ctrl(state, 0); - - dprintk("%s() status 0x%08x\n", __func__, *status); - - return 0; -} - -static int au8522_led_status(struct au8522_state *state, const u16 *snr) -{ - struct au8522_led_config *led_config = state->config->led_cfg; - int led; - u16 strong; - - /* bail out if we can't control an LED */ - if (!led_config) - return 0; - - if (0 == (state->fe_status & FE_HAS_LOCK)) - return au8522_led_ctrl(state, 0); - else if (state->current_modulation == QAM_256) - strong = led_config->qam256_strong; - else if (state->current_modulation == QAM_64) - strong = led_config->qam64_strong; - else /* (state->current_modulation == VSB_8) */ - strong = led_config->vsb8_strong; - - if (*snr >= strong) - led = 2; - else - led = 1; - - if ((state->led_state) && - (((strong < *snr) ? (*snr - strong) : (strong - *snr)) <= 10)) - /* snr didn't change enough to bother - * changing the color of the led */ - return 0; - - return au8522_led_ctrl(state, led); -} - -static int au8522_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct au8522_state *state = fe->demodulator_priv; - int ret = -EINVAL; - - dprintk("%s()\n", __func__); - - if (state->current_modulation == QAM_256) - ret = au8522_mse2snr_lookup(qam256_mse2snr_tab, - ARRAY_SIZE(qam256_mse2snr_tab), - au8522_readreg(state, 0x4522), - snr); - else if (state->current_modulation == QAM_64) - ret = au8522_mse2snr_lookup(qam64_mse2snr_tab, - ARRAY_SIZE(qam64_mse2snr_tab), - au8522_readreg(state, 0x4522), - snr); - else /* VSB_8 */ - ret = au8522_mse2snr_lookup(vsb_mse2snr_tab, - ARRAY_SIZE(vsb_mse2snr_tab), - au8522_readreg(state, 0x4311), - snr); - - if (state->config->led_cfg) - au8522_led_status(state, snr); - - return ret; -} - -static int au8522_read_signal_strength(struct dvb_frontend *fe, - u16 *signal_strength) -{ - /* borrowed from lgdt330x.c - * - * Calculate strength from SNR up to 35dB - * Even though the SNR can go higher than 35dB, - * there is some comfort factor in having a range of - * strong signals that can show at 100% - */ - u16 snr; - u32 tmp; - int ret = au8522_read_snr(fe, &snr); - - *signal_strength = 0; - - if (0 == ret) { - /* The following calculation method was chosen - * purely for the sake of code re-use from the - * other demod drivers that use this method */ - - /* Convert from SNR in dB * 10 to 8.24 fixed-point */ - tmp = (snr * ((1 << 24) / 10)); - - /* Convert from 8.24 fixed-point to - * scale the range 0 - 35*2^24 into 0 - 65535*/ - if (tmp >= 8960 * 0x10000) - *signal_strength = 0xffff; - else - *signal_strength = tmp / 8960; - } - - return ret; -} - -static int au8522_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - struct au8522_state *state = fe->demodulator_priv; - - if (state->current_modulation == VSB_8) - *ucblocks = au8522_readreg(state, 0x4087); - else - *ucblocks = au8522_readreg(state, 0x4543); - - return 0; -} - -static int au8522_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - return au8522_read_ucblocks(fe, ber); -} - -static int au8522_get_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct au8522_state *state = fe->demodulator_priv; - - c->frequency = state->current_frequency; - c->modulation = state->current_modulation; - - return 0; -} - -static int au8522_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *tune) -{ - tune->min_delay_ms = 1000; - return 0; -} - -static struct dvb_frontend_ops au8522_ops; - - -static void au8522_release(struct dvb_frontend *fe) -{ - struct au8522_state *state = fe->demodulator_priv; - au8522_release_state(state); -} - -struct dvb_frontend *au8522_attach(const struct au8522_config *config, - struct i2c_adapter *i2c) -{ - struct au8522_state *state = NULL; - int instance; - - /* allocate memory for the internal state */ - instance = au8522_get_state(&state, i2c, config->demod_address); - switch (instance) { - case 0: - dprintk("%s state allocation failed\n", __func__); - break; - case 1: - /* new demod instance */ - dprintk("%s using new instance\n", __func__); - break; - default: - /* existing demod instance */ - dprintk("%s using existing instance\n", __func__); - break; - } - - /* setup the state */ - state->config = config; - state->i2c = i2c; - state->operational_mode = AU8522_DIGITAL_MODE; - - /* create dvb_frontend */ - memcpy(&state->frontend.ops, &au8522_ops, - sizeof(struct dvb_frontend_ops)); - state->frontend.demodulator_priv = state; - - if (au8522_init(&state->frontend) != 0) { - printk(KERN_ERR "%s: Failed to initialize correctly\n", - __func__); - goto error; - } - - /* Note: Leaving the I2C gate open here. */ - au8522_i2c_gate_ctrl(&state->frontend, 1); - - return &state->frontend; - -error: - au8522_release_state(state); - return NULL; -} -EXPORT_SYMBOL(au8522_attach); - -static struct dvb_frontend_ops au8522_ops = { - .delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B }, - .info = { - .name = "Auvitek AU8522 QAM/8VSB Frontend", - .frequency_min = 54000000, - .frequency_max = 858000000, - .frequency_stepsize = 62500, - .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB - }, - - .init = au8522_init, - .sleep = au8522_sleep, - .i2c_gate_ctrl = au8522_i2c_gate_ctrl, - .set_frontend = au8522_set_frontend, - .get_frontend = au8522_get_frontend, - .get_tune_settings = au8522_get_tune_settings, - .read_status = au8522_read_status, - .read_ber = au8522_read_ber, - .read_signal_strength = au8522_read_signal_strength, - .read_snr = au8522_read_snr, - .read_ucblocks = au8522_read_ucblocks, - .release = au8522_release, -}; - -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Enable verbose debug messages"); - -MODULE_DESCRIPTION("Auvitek AU8522 QAM-B/ATSC Demodulator driver"); -MODULE_AUTHOR("Steven Toth"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/au8522_priv.h b/drivers/media/dvb/frontends/au8522_priv.h deleted file mode 100644 index 6e4a438732b5..000000000000 --- a/drivers/media/dvb/frontends/au8522_priv.h +++ /dev/null @@ -1,423 +0,0 @@ -/* - Auvitek AU8522 QAM/8VSB demodulator driver - - Copyright (C) 2008 Steven Toth - Copyright (C) 2008 Devin Heitmueller - Copyright (C) 2005-2008 Auvitek International, Ltd. - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dvb_frontend.h" -#include "au8522.h" -#include "tuner-i2c.h" - -#define AU8522_ANALOG_MODE 0 -#define AU8522_DIGITAL_MODE 1 - -struct au8522_state { - struct i2c_client *c; - struct i2c_adapter *i2c; - - u8 operational_mode; - - /* Used for sharing of the state between analog and digital mode */ - struct tuner_i2c_props i2c_props; - struct list_head hybrid_tuner_instance_list; - - /* configuration settings */ - const struct au8522_config *config; - - struct dvb_frontend frontend; - - u32 current_frequency; - fe_modulation_t current_modulation; - - u32 fe_status; - unsigned int led_state; - - /* Analog settings */ - struct v4l2_subdev sd; - v4l2_std_id std; - int vid_input; - int aud_input; - u32 id; - u32 rev; - u8 brightness; - u8 contrast; - u8 saturation; - s16 hue; -}; - -/* These are routines shared by both the VSB/QAM demodulator and the analog - decoder */ -int au8522_writereg(struct au8522_state *state, u16 reg, u8 data); -u8 au8522_readreg(struct au8522_state *state, u16 reg); -int au8522_init(struct dvb_frontend *fe); -int au8522_sleep(struct dvb_frontend *fe); - -int au8522_get_state(struct au8522_state **state, struct i2c_adapter *i2c, - u8 client_address); -void au8522_release_state(struct au8522_state *state); -int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable); -int au8522_led_ctrl(struct au8522_state *state, int led); - -/* REGISTERS */ -#define AU8522_INPUT_CONTROL_REG081H 0x081 -#define AU8522_PGA_CONTROL_REG082H 0x082 -#define AU8522_CLAMPING_CONTROL_REG083H 0x083 - -#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H 0x0A3 -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H 0x0A4 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H 0x0A5 -#define AU8522_AGC_CONTROL_RANGE_REG0A6H 0x0A6 -#define AU8522_SYSTEM_GAIN_CONTROL_REG0A7H 0x0A7 -#define AU8522_TUNER_AGC_RF_STOP_REG0A8H 0x0A8 -#define AU8522_TUNER_AGC_RF_START_REG0A9H 0x0A9 -#define AU8522_TUNER_RF_AGC_DEFAULT_REG0AAH 0x0AA -#define AU8522_TUNER_AGC_IF_STOP_REG0ABH 0x0AB -#define AU8522_TUNER_AGC_IF_START_REG0ACH 0x0AC -#define AU8522_TUNER_AGC_IF_DEFAULT_REG0ADH 0x0AD -#define AU8522_TUNER_AGC_STEP_REG0AEH 0x0AE -#define AU8522_TUNER_GAIN_STEP_REG0AFH 0x0AF - -/* Receiver registers */ -#define AU8522_FRMREGTHRD1_REG0B0H 0x0B0 -#define AU8522_FRMREGAGC1H_REG0B1H 0x0B1 -#define AU8522_FRMREGSHIFT1_REG0B2H 0x0B2 -#define AU8522_TOREGAGC1_REG0B3H 0x0B3 -#define AU8522_TOREGASHIFT1_REG0B4H 0x0B4 -#define AU8522_FRMREGBBH_REG0B5H 0x0B5 -#define AU8522_FRMREGBBM_REG0B6H 0x0B6 -#define AU8522_FRMREGBBL_REG0B7H 0x0B7 -/* 0xB8 TO 0xD7 are the filter coefficients */ -#define AU8522_FRMREGTHRD2_REG0D8H 0x0D8 -#define AU8522_FRMREGAGC2H_REG0D9H 0x0D9 -#define AU8522_TOREGAGC2_REG0DAH 0x0DA -#define AU8522_TOREGSHIFT2_REG0DBH 0x0DB -#define AU8522_FRMREGPILOTH_REG0DCH 0x0DC -#define AU8522_FRMREGPILOTM_REG0DDH 0x0DD -#define AU8522_FRMREGPILOTL_REG0DEH 0x0DE -#define AU8522_TOREGFREQ_REG0DFH 0x0DF - -#define AU8522_RX_PGA_RFOUT_REG0EBH 0x0EB -#define AU8522_RX_PGA_IFOUT_REG0ECH 0x0EC -#define AU8522_RX_PGA_PGAOUT_REG0EDH 0x0ED - -#define AU8522_CHIP_MODE_REG0FEH 0x0FE - -/* I2C bus control registers */ -#define AU8522_I2C_CONTROL_REG0_REG090H 0x090 -#define AU8522_I2C_CONTROL_REG1_REG091H 0x091 -#define AU8522_I2C_STATUS_REG092H 0x092 -#define AU8522_I2C_WR_DATA0_REG093H 0x093 -#define AU8522_I2C_WR_DATA1_REG094H 0x094 -#define AU8522_I2C_WR_DATA2_REG095H 0x095 -#define AU8522_I2C_WR_DATA3_REG096H 0x096 -#define AU8522_I2C_WR_DATA4_REG097H 0x097 -#define AU8522_I2C_WR_DATA5_REG098H 0x098 -#define AU8522_I2C_WR_DATA6_REG099H 0x099 -#define AU8522_I2C_WR_DATA7_REG09AH 0x09A -#define AU8522_I2C_RD_DATA0_REG09BH 0x09B -#define AU8522_I2C_RD_DATA1_REG09CH 0x09C -#define AU8522_I2C_RD_DATA2_REG09DH 0x09D -#define AU8522_I2C_RD_DATA3_REG09EH 0x09E -#define AU8522_I2C_RD_DATA4_REG09FH 0x09F -#define AU8522_I2C_RD_DATA5_REG0A0H 0x0A0 -#define AU8522_I2C_RD_DATA6_REG0A1H 0x0A1 -#define AU8522_I2C_RD_DATA7_REG0A2H 0x0A2 - -#define AU8522_ENA_USB_REG101H 0x101 - -#define AU8522_I2S_CTRL_0_REG110H 0x110 -#define AU8522_I2S_CTRL_1_REG111H 0x111 -#define AU8522_I2S_CTRL_2_REG112H 0x112 - -#define AU8522_FRMREGFFECONTROL_REG121H 0x121 -#define AU8522_FRMREGDFECONTROL_REG122H 0x122 - -#define AU8522_CARRFREQOFFSET0_REG201H 0x201 -#define AU8522_CARRFREQOFFSET1_REG202H 0x202 - -#define AU8522_DECIMATION_GAIN_REG21AH 0x21A -#define AU8522_FRMREGIFSLP_REG21BH 0x21B -#define AU8522_FRMREGTHRDL2_REG21CH 0x21C -#define AU8522_FRMREGSTEP3DB_REG21DH 0x21D -#define AU8522_DAGC_GAIN_ADJUSTMENT_REG21EH 0x21E -#define AU8522_FRMREGPLLMODE_REG21FH 0x21F -#define AU8522_FRMREGCSTHRD_REG220H 0x220 -#define AU8522_FRMREGCRLOCKDMAX_REG221H 0x221 -#define AU8522_FRMREGCRPERIODMASK_REG222H 0x222 -#define AU8522_FRMREGCRLOCK0THH_REG223H 0x223 -#define AU8522_FRMREGCRLOCK1THH_REG224H 0x224 -#define AU8522_FRMREGCRLOCK0THL_REG225H 0x225 -#define AU8522_FRMREGCRLOCK1THL_REG226H 0x226 -#define AU_FRMREGPLLACQPHASESCL_REG227H 0x227 -#define AU8522_FRMREGFREQFBCTRL_REG228H 0x228 - -/* Analog TV Decoder */ -#define AU8522_TVDEC_STATUS_REG000H 0x000 -#define AU8522_TVDEC_INT_STATUS_REG001H 0x001 -#define AU8522_TVDEC_MACROVISION_STATUS_REG002H 0x002 -#define AU8522_TVDEC_SHARPNESSREG009H 0x009 -#define AU8522_TVDEC_BRIGHTNESS_REG00AH 0x00A -#define AU8522_TVDEC_CONTRAST_REG00BH 0x00B -#define AU8522_TVDEC_SATURATION_CB_REG00CH 0x00C -#define AU8522_TVDEC_SATURATION_CR_REG00DH 0x00D -#define AU8522_TVDEC_HUE_H_REG00EH 0x00E -#define AU8522_TVDEC_HUE_L_REG00FH 0x00F -#define AU8522_TVDEC_INT_MASK_REG010H 0x010 -#define AU8522_VIDEO_MODE_REG011H 0x011 -#define AU8522_TVDEC_PGA_REG012H 0x012 -#define AU8522_TVDEC_COMB_MODE_REG015H 0x015 -#define AU8522_REG016H 0x016 -#define AU8522_TVDED_DBG_MODE_REG060H 0x060 -#define AU8522_TVDEC_FORMAT_CTRL1_REG061H 0x061 -#define AU8522_TVDEC_FORMAT_CTRL2_REG062H 0x062 -#define AU8522_TVDEC_VCR_DET_LLIM_REG063H 0x063 -#define AU8522_TVDEC_VCR_DET_HLIM_REG064H 0x064 -#define AU8522_TVDEC_COMB_VDIF_THR1_REG065H 0x065 -#define AU8522_TVDEC_COMB_VDIF_THR2_REG066H 0x066 -#define AU8522_TVDEC_COMB_VDIF_THR3_REG067H 0x067 -#define AU8522_TVDEC_COMB_NOTCH_THR_REG068H 0x068 -#define AU8522_TVDEC_COMB_HDIF_THR1_REG069H 0x069 -#define AU8522_TVDEC_COMB_HDIF_THR2_REG06AH 0x06A -#define AU8522_TVDEC_COMB_HDIF_THR3_REG06BH 0x06B -#define AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH 0x06C -#define AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH 0x06D -#define AU8522_TVDEC_COMB_DCDIF_THR3_REG06EH 0x06E -#define AU8522_TVDEC_UV_SEP_THR_REG06FH 0x06F -#define AU8522_TVDEC_COMB_DC_THR1_NTSC_REG070H 0x070 -#define AU8522_TVDEC_COMB_DC_THR2_NTSC_REG073H 0x073 -#define AU8522_TVDEC_DCAGC_CTRL_REG077H 0x077 -#define AU8522_TVDEC_PIC_START_ADJ_REG078H 0x078 -#define AU8522_TVDEC_AGC_HIGH_LIMIT_REG079H 0x079 -#define AU8522_TVDEC_MACROVISION_SYNC_THR_REG07AH 0x07A -#define AU8522_TVDEC_INTRP_CTRL_REG07BH 0x07B -#define AU8522_TVDEC_PLL_STATUS_REG07EH 0x07E -#define AU8522_TVDEC_FSC_FREQ_REG07FH 0x07F - -#define AU8522_TVDEC_AGC_LOW_LIMIT_REG0E4H 0x0E4 -#define AU8522_TOREGAAGC_REG0E5H 0x0E5 - -#define AU8522_TVDEC_CHROMA_AGC_REG401H 0x401 -#define AU8522_TVDEC_CHROMA_SFT_REG402H 0x402 -#define AU8522_FILTER_COEF_R410 0x410 -#define AU8522_FILTER_COEF_R411 0x411 -#define AU8522_FILTER_COEF_R412 0x412 -#define AU8522_FILTER_COEF_R413 0x413 -#define AU8522_FILTER_COEF_R414 0x414 -#define AU8522_FILTER_COEF_R415 0x415 -#define AU8522_FILTER_COEF_R416 0x416 -#define AU8522_FILTER_COEF_R417 0x417 -#define AU8522_FILTER_COEF_R418 0x418 -#define AU8522_FILTER_COEF_R419 0x419 -#define AU8522_FILTER_COEF_R41A 0x41A -#define AU8522_FILTER_COEF_R41B 0x41B -#define AU8522_FILTER_COEF_R41C 0x41C -#define AU8522_FILTER_COEF_R41D 0x41D -#define AU8522_FILTER_COEF_R41E 0x41E -#define AU8522_FILTER_COEF_R41F 0x41F -#define AU8522_FILTER_COEF_R420 0x420 -#define AU8522_FILTER_COEF_R421 0x421 -#define AU8522_FILTER_COEF_R422 0x422 -#define AU8522_FILTER_COEF_R423 0x423 -#define AU8522_FILTER_COEF_R424 0x424 -#define AU8522_FILTER_COEF_R425 0x425 -#define AU8522_FILTER_COEF_R426 0x426 -#define AU8522_FILTER_COEF_R427 0x427 -#define AU8522_FILTER_COEF_R428 0x428 -#define AU8522_FILTER_COEF_R429 0x429 -#define AU8522_FILTER_COEF_R42A 0x42A -#define AU8522_FILTER_COEF_R42B 0x42B -#define AU8522_FILTER_COEF_R42C 0x42C -#define AU8522_FILTER_COEF_R42D 0x42D - -/* VBI Control Registers */ -#define AU8522_TVDEC_VBI_RX_FIFO_CONTAIN_REG004H 0x004 -#define AU8522_TVDEC_VBI_TX_FIFO_CONTAIN_REG005H 0x005 -#define AU8522_TVDEC_VBI_RX_FIFO_READ_REG006H 0x006 -#define AU8522_TVDEC_VBI_FIFO_STATUS_REG007H 0x007 -#define AU8522_TVDEC_VBI_CTRL_H_REG017H 0x017 -#define AU8522_TVDEC_VBI_CTRL_L_REG018H 0x018 -#define AU8522_TVDEC_VBI_USER_TOTAL_BITS_REG019H 0x019 -#define AU8522_TVDEC_VBI_USER_TUNIT_H_REG01AH 0x01A -#define AU8522_TVDEC_VBI_USER_TUNIT_L_REG01BH 0x01B -#define AU8522_TVDEC_VBI_USER_THRESH1_REG01CH 0x01C -#define AU8522_TVDEC_VBI_USER_FRAME_PAT2_REG01EH 0x01E -#define AU8522_TVDEC_VBI_USER_FRAME_PAT1_REG01FH 0x01F -#define AU8522_TVDEC_VBI_USER_FRAME_PAT0_REG020H 0x020 -#define AU8522_TVDEC_VBI_USER_FRAME_MASK2_REG021H 0x021 -#define AU8522_TVDEC_VBI_USER_FRAME_MASK1_REG022H 0x022 -#define AU8522_TVDEC_VBI_USER_FRAME_MASK0_REG023H 0x023 - -#define AU8522_REG071H 0x071 -#define AU8522_REG072H 0x072 -#define AU8522_REG074H 0x074 -#define AU8522_REG075H 0x075 - -/* Digital Demodulator Registers */ -#define AU8522_FRAME_COUNT0_REG084H 0x084 -#define AU8522_RS_STATUS_G0_REG085H 0x085 -#define AU8522_RS_STATUS_B0_REG086H 0x086 -#define AU8522_RS_STATUS_E_REG087H 0x087 -#define AU8522_DEMODULATION_STATUS_REG088H 0x088 -#define AU8522_TOREGTRESTATUS_REG0E6H 0x0E6 -#define AU8522_TSPORT_CONTROL_REG10BH 0x10B -#define AU8522_TSTHES_REG10CH 0x10C -#define AU8522_FRMREGDFEKEEP_REG301H 0x301 -#define AU8522_DFE_AVERAGE_REG302H 0x302 -#define AU8522_FRMREGEQLERRWIN_REG303H 0x303 -#define AU8522_FRMREGFFEKEEP_REG304H 0x304 -#define AU8522_FRMREGDFECONTROL1_REG305H 0x305 -#define AU8522_FRMREGEQLERRLOW_REG306H 0x306 - -#define AU8522_REG42EH 0x42E -#define AU8522_REG42FH 0x42F -#define AU8522_REG430H 0x430 -#define AU8522_REG431H 0x431 -#define AU8522_REG432H 0x432 -#define AU8522_REG433H 0x433 -#define AU8522_REG434H 0x434 -#define AU8522_REG435H 0x435 -#define AU8522_REG436H 0x436 - -/* GPIO Registers */ -#define AU8522_GPIO_CONTROL_REG0E0H 0x0E0 -#define AU8522_GPIO_STATUS_REG0E1H 0x0E1 -#define AU8522_GPIO_DATA_REG0E2H 0x0E2 - -/* Audio Control Registers */ -#define AU8522_AUDIOAGC_REG0EEH 0x0EE -#define AU8522_AUDIO_STATUS_REG0F0H 0x0F0 -#define AU8522_AUDIO_MODE_REG0F1H 0x0F1 -#define AU8522_AUDIO_VOLUME_L_REG0F2H 0x0F2 -#define AU8522_AUDIO_VOLUME_R_REG0F3H 0x0F3 -#define AU8522_AUDIO_VOLUME_REG0F4H 0x0F4 -#define AU8522_FRMREGAUPHASE_REG0F7H 0x0F7 -#define AU8522_REG0F9H 0x0F9 - -#define AU8522_AUDIOAGC2_REG605H 0x605 -#define AU8522_AUDIOFREQ_REG606H 0x606 - - -/**************************************************************/ - -#define AU8522_INPUT_CONTROL_REG081H_ATSC 0xC4 -#define AU8522_INPUT_CONTROL_REG081H_ATVRF 0xC4 -#define AU8522_INPUT_CONTROL_REG081H_ATVRF13 0xC4 -#define AU8522_INPUT_CONTROL_REG081H_J83B64 0xC4 -#define AU8522_INPUT_CONTROL_REG081H_J83B256 0xC4 -#define AU8522_INPUT_CONTROL_REG081H_CVBS 0x20 -#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH1 0xA2 -#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH2 0xA0 -#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH3 0x69 -#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH4 0x68 -#define AU8522_INPUT_CONTROL_REG081H_CVBS_CH4_SIF 0x28 -/* CH1 AS Y,CH3 AS C */ -#define AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH13 0x23 -/* CH2 AS Y,CH4 AS C */ -#define AU8522_INPUT_CONTROL_REG081H_SVIDEO_CH24 0x20 -#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_ATSC 0x0C -#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_J83B64 0x09 -#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_J83B256 0x09 -#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_CVBS 0x12 -#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_ATVRF 0x1A -#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_ATVRF13 0x1A -#define AU8522_MODULE_CLOCK_CONTROL_REG0A3H_SVIDEO 0x02 - -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CLEAR 0x00 -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_SVIDEO 0x9C -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS 0x9D -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_ATSC 0xE8 -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_J83B256 0xCA -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_J83B64 0xCA -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_ATVRF 0xDD -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_ATVRF13 0xDD -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_PAL 0xDD -#define AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_FM 0xDD - -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_ATSC 0x80 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_J83B256 0x80 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_J83B64 0x80 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_DONGLE_ATSC 0x40 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_DONGLE_J83B256 0x40 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_DONGLE_J83B64 0x40 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_DONGLE_CLEAR 0x00 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_ATVRF 0x01 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_ATVRF13 0x01 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_SVIDEO 0x04 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_CVBS 0x01 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_PWM 0x03 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_IIS 0x09 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_PAL 0x01 -#define AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H_FM 0x01 - -/* STILL NEED TO BE REFACTORED @@@@@@@@@@@@@@ */ -#define AU8522_TVDEC_CONTRAST_REG00BH_CVBS 0x79 -#define AU8522_TVDEC_SATURATION_CB_REG00CH_CVBS 0x80 -#define AU8522_TVDEC_SATURATION_CR_REG00DH_CVBS 0x80 -#define AU8522_TVDEC_HUE_H_REG00EH_CVBS 0x00 -#define AU8522_TVDEC_HUE_L_REG00FH_CVBS 0x00 -#define AU8522_TVDEC_PGA_REG012H_CVBS 0x0F -#define AU8522_TVDEC_COMB_MODE_REG015H_CVBS 0x00 -#define AU8522_REG016H_CVBS 0x00 -#define AU8522_TVDED_DBG_MODE_REG060H_CVBS 0x00 -#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_CVBS 0x0B -#define AU8522_TVDEC_FORMAT_CTRL1_REG061H_CVBS13 0x03 -#define AU8522_TVDEC_FORMAT_CTRL2_REG062H_CVBS13 0x00 -#define AU8522_TVDEC_VCR_DET_LLIM_REG063H_CVBS 0x19 -#define AU8522_REG0F9H_AUDIO 0x20 -#define AU8522_TVDEC_VCR_DET_HLIM_REG064H_CVBS 0xA7 -#define AU8522_TVDEC_COMB_VDIF_THR1_REG065H_CVBS 0x0A -#define AU8522_TVDEC_COMB_VDIF_THR2_REG066H_CVBS 0x32 -#define AU8522_TVDEC_COMB_VDIF_THR3_REG067H_CVBS 0x19 -#define AU8522_TVDEC_COMB_NOTCH_THR_REG068H_CVBS 0x23 -#define AU8522_TVDEC_COMB_HDIF_THR1_REG069H_CVBS 0x41 -#define AU8522_TVDEC_COMB_HDIF_THR2_REG06AH_CVBS 0x0A -#define AU8522_TVDEC_COMB_HDIF_THR3_REG06BH_CVBS 0x32 -#define AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH_CVBS 0x34 -#define AU8522_TVDEC_COMB_DCDIF_THR1_REG06CH_SVIDEO 0x2a -#define AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH_CVBS 0x05 -#define AU8522_TVDEC_COMB_DCDIF_THR2_REG06DH_SVIDEO 0x15 -#define AU8522_TVDEC_COMB_DCDIF_THR3_REG06EH_CVBS 0x6E -#define AU8522_TVDEC_UV_SEP_THR_REG06FH_CVBS 0x0F -#define AU8522_TVDEC_COMB_DC_THR1_NTSC_REG070H_CVBS 0x80 -#define AU8522_REG071H_CVBS 0x18 -#define AU8522_REG072H_CVBS 0x30 -#define AU8522_TVDEC_COMB_DC_THR2_NTSC_REG073H_CVBS 0xF0 -#define AU8522_REG074H_CVBS 0x80 -#define AU8522_REG075H_CVBS 0xF0 -#define AU8522_TVDEC_DCAGC_CTRL_REG077H_CVBS 0xFB -#define AU8522_TVDEC_PIC_START_ADJ_REG078H_CVBS 0x04 -#define AU8522_TVDEC_AGC_HIGH_LIMIT_REG079H_CVBS 0x00 -#define AU8522_TVDEC_MACROVISION_SYNC_THR_REG07AH_CVBS 0x00 -#define AU8522_TVDEC_INTRP_CTRL_REG07BH_CVBS 0xEE -#define AU8522_TVDEC_AGC_LOW_LIMIT_REG0E4H_CVBS 0xFE -#define AU8522_TOREGAAGC_REG0E5H_CVBS 0x00 -#define AU8522_TVDEC_VBI6A_REG035H_CVBS 0x40 - -/* Enables Closed captioning */ -#define AU8522_TVDEC_VBI_CTRL_H_REG017H_CCON 0x21 diff --git a/drivers/media/dvb/frontends/cxd2820r.h b/drivers/media/dvb/frontends/cxd2820r.h deleted file mode 100644 index 5aa306ebb7ef..000000000000 --- a/drivers/media/dvb/frontends/cxd2820r.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Sony CXD2820R demodulator driver - * - * Copyright (C) 2010 Antti Palosaari - * - * 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. - */ - - -#ifndef CXD2820R_H -#define CXD2820R_H - -#include - -#define CXD2820R_GPIO_D (0 << 0) /* disable */ -#define CXD2820R_GPIO_E (1 << 0) /* enable */ -#define CXD2820R_GPIO_O (0 << 1) /* output */ -#define CXD2820R_GPIO_I (1 << 1) /* input */ -#define CXD2820R_GPIO_L (0 << 2) /* output low */ -#define CXD2820R_GPIO_H (1 << 2) /* output high */ - -#define CXD2820R_TS_SERIAL 0x08 -#define CXD2820R_TS_SERIAL_MSB 0x28 -#define CXD2820R_TS_PARALLEL 0x30 -#define CXD2820R_TS_PARALLEL_MSB 0x70 - -struct cxd2820r_config { - /* Demodulator I2C address. - * Driver determines DVB-C slave I2C address automatically from master - * address. - * Default: none, must set - * Values: 0x6c, 0x6d - */ - u8 i2c_address; - - /* TS output mode. - * Default: none, must set. - * Values: - */ - u8 ts_mode; - - /* IF AGC polarity. - * Default: 0 - * Values: 0, 1 - */ - bool if_agc_polarity; - - /* Spectrum inversion. - * Default: 0 - * Values: 0, 1 - */ - bool spec_inv; - - /* GPIOs for all used modes. - * Default: none, disabled - * Values: - */ - u8 gpio_dvbt[3]; - u8 gpio_dvbt2[3]; - u8 gpio_dvbc[3]; -}; - - -#if defined(CONFIG_DVB_CXD2820R) || \ - (defined(CONFIG_DVB_CXD2820R_MODULE) && defined(MODULE)) -extern struct dvb_frontend *cxd2820r_attach( - const struct cxd2820r_config *config, - struct i2c_adapter *i2c -); -#else -static inline struct dvb_frontend *cxd2820r_attach( - const struct cxd2820r_config *config, - struct i2c_adapter *i2c -) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} - -#endif - -#endif /* CXD2820R_H */ diff --git a/drivers/media/dvb/frontends/cxd2820r_c.c b/drivers/media/dvb/frontends/cxd2820r_c.c deleted file mode 100644 index ed3b0ba624de..000000000000 --- a/drivers/media/dvb/frontends/cxd2820r_c.c +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Sony CXD2820R demodulator driver - * - * Copyright (C) 2010 Antti Palosaari - * - * 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. - */ - - -#include "cxd2820r_priv.h" - -int cxd2820r_set_frontend_c(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i; - u8 buf[2]; - u32 if_freq; - u16 if_ctl; - u64 num; - struct reg_val_mask tab[] = { - { 0x00080, 0x01, 0xff }, - { 0x00081, 0x05, 0xff }, - { 0x00085, 0x07, 0xff }, - { 0x00088, 0x01, 0xff }, - - { 0x00082, 0x20, 0x60 }, - { 0x1016a, 0x48, 0xff }, - { 0x100a5, 0x00, 0x01 }, - { 0x10020, 0x06, 0x07 }, - { 0x10059, 0x50, 0xff }, - { 0x10087, 0x0c, 0x3c }, - { 0x1008b, 0x07, 0xff }, - { 0x1001f, priv->cfg.if_agc_polarity << 7, 0x80 }, - { 0x10070, priv->cfg.ts_mode, 0xff }, - }; - - dbg("%s: RF=%d SR=%d", __func__, c->frequency, c->symbol_rate); - - /* update GPIOs */ - ret = cxd2820r_gpio(fe); - if (ret) - goto error; - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - - if (priv->delivery_system != SYS_DVBC_ANNEX_A) { - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, - tab[i].val, tab[i].mask); - if (ret) - goto error; - } - } - - priv->delivery_system = SYS_DVBC_ANNEX_A; - priv->ber_running = 0; /* tune stops BER counter */ - - /* program IF frequency */ - if (fe->ops.tuner_ops.get_if_frequency) { - ret = fe->ops.tuner_ops.get_if_frequency(fe, &if_freq); - if (ret) - goto error; - } else - if_freq = 0; - - dbg("%s: if_freq=%d", __func__, if_freq); - - num = if_freq / 1000; /* Hz => kHz */ - num *= 0x4000; - if_ctl = cxd2820r_div_u64_round_closest(num, 41000); - buf[0] = (if_ctl >> 8) & 0x3f; - buf[1] = (if_ctl >> 0) & 0xff; - - ret = cxd2820r_wr_regs(priv, 0x10042, buf, 2); - if (ret) - goto error; - - ret = cxd2820r_wr_reg(priv, 0x000ff, 0x08); - if (ret) - goto error; - - ret = cxd2820r_wr_reg(priv, 0x000fe, 0x01); - if (ret) - goto error; - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_get_frontend_c(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret; - u8 buf[2]; - - ret = cxd2820r_rd_regs(priv, 0x1001a, buf, 2); - if (ret) - goto error; - - c->symbol_rate = 2500 * ((buf[0] & 0x0f) << 8 | buf[1]); - - ret = cxd2820r_rd_reg(priv, 0x10019, &buf[0]); - if (ret) - goto error; - - switch ((buf[0] >> 0) & 0x07) { - case 0: - c->modulation = QAM_16; - break; - case 1: - c->modulation = QAM_32; - break; - case 2: - c->modulation = QAM_64; - break; - case 3: - c->modulation = QAM_128; - break; - case 4: - c->modulation = QAM_256; - break; - } - - switch ((buf[0] >> 7) & 0x01) { - case 0: - c->inversion = INVERSION_OFF; - break; - case 1: - c->inversion = INVERSION_ON; - break; - } - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_ber_c(struct dvb_frontend *fe, u32 *ber) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[3], start_ber = 0; - *ber = 0; - - if (priv->ber_running) { - ret = cxd2820r_rd_regs(priv, 0x10076, buf, sizeof(buf)); - if (ret) - goto error; - - if ((buf[2] >> 7) & 0x01 || (buf[2] >> 4) & 0x01) { - *ber = (buf[2] & 0x0f) << 16 | buf[1] << 8 | buf[0]; - start_ber = 1; - } - } else { - priv->ber_running = 1; - start_ber = 1; - } - - if (start_ber) { - /* (re)start BER */ - ret = cxd2820r_wr_reg(priv, 0x10079, 0x01); - if (ret) - goto error; - } - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_signal_strength_c(struct dvb_frontend *fe, - u16 *strength) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[2]; - u16 tmp; - - ret = cxd2820r_rd_regs(priv, 0x10049, buf, sizeof(buf)); - if (ret) - goto error; - - tmp = (buf[0] & 0x03) << 8 | buf[1]; - tmp = (~tmp & 0x03ff); - - if (tmp == 512) - /* ~no signal */ - tmp = 0; - else if (tmp > 350) - tmp = 350; - - /* scale value to 0x0000-0xffff */ - *strength = tmp * 0xffff / (350-0); - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_snr_c(struct dvb_frontend *fe, u16 *snr) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 tmp; - unsigned int A, B; - /* report SNR in dB * 10 */ - - ret = cxd2820r_rd_reg(priv, 0x10019, &tmp); - if (ret) - goto error; - - if (((tmp >> 0) & 0x03) % 2) { - A = 875; - B = 650; - } else { - A = 950; - B = 760; - } - - ret = cxd2820r_rd_reg(priv, 0x1004d, &tmp); - if (ret) - goto error; - - #define CXD2820R_LOG2_E_24 24204406 /* log2(e) << 24 */ - if (tmp) - *snr = A * (intlog2(B / tmp) >> 5) / (CXD2820R_LOG2_E_24 >> 5) - / 10; - else - *snr = 0; - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_ucblocks_c(struct dvb_frontend *fe, u32 *ucblocks) -{ - *ucblocks = 0; - /* no way to read ? */ - return 0; -} - -int cxd2820r_read_status_c(struct dvb_frontend *fe, fe_status_t *status) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[2]; - *status = 0; - - ret = cxd2820r_rd_regs(priv, 0x10088, buf, sizeof(buf)); - if (ret) - goto error; - - if (((buf[0] >> 0) & 0x01) == 1) { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC; - - if (((buf[1] >> 3) & 0x01) == 1) { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - } - } - - dbg("%s: lock=%02x %02x", __func__, buf[0], buf[1]); - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_init_c(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - - ret = cxd2820r_wr_reg(priv, 0x00085, 0x07); - if (ret) - goto error; - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_sleep_c(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret, i; - struct reg_val_mask tab[] = { - { 0x000ff, 0x1f, 0xff }, - { 0x00085, 0x00, 0xff }, - { 0x00088, 0x01, 0xff }, - { 0x00081, 0x00, 0xff }, - { 0x00080, 0x00, 0xff }, - }; - - dbg("%s", __func__); - - priv->delivery_system = SYS_UNDEFINED; - - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, tab[i].val, - tab[i].mask); - if (ret) - goto error; - } - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_get_tune_settings_c(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s) -{ - s->min_delay_ms = 500; - s->step_size = 0; /* no zigzag */ - s->max_drift = 0; - - return 0; -} diff --git a/drivers/media/dvb/frontends/cxd2820r_core.c b/drivers/media/dvb/frontends/cxd2820r_core.c deleted file mode 100644 index 3bba37d74f57..000000000000 --- a/drivers/media/dvb/frontends/cxd2820r_core.c +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Sony CXD2820R demodulator driver - * - * Copyright (C) 2010 Antti Palosaari - * - * 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. - */ - - -#include "cxd2820r_priv.h" - -int cxd2820r_debug; -module_param_named(debug, cxd2820r_debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -/* write multiple registers */ -static int cxd2820r_wr_regs_i2c(struct cxd2820r_priv *priv, u8 i2c, u8 reg, - u8 *val, int len) -{ - int ret; - u8 buf[len+1]; - struct i2c_msg msg[1] = { - { - .addr = i2c, - .flags = 0, - .len = sizeof(buf), - .buf = buf, - } - }; - - buf[0] = reg; - memcpy(&buf[1], val, len); - - ret = i2c_transfer(priv->i2c, msg, 1); - if (ret == 1) { - ret = 0; - } else { - warn("i2c wr failed ret:%d reg:%02x len:%d", ret, reg, len); - ret = -EREMOTEIO; - } - return ret; -} - -/* read multiple registers */ -static int cxd2820r_rd_regs_i2c(struct cxd2820r_priv *priv, u8 i2c, u8 reg, - u8 *val, int len) -{ - int ret; - u8 buf[len]; - struct i2c_msg msg[2] = { - { - .addr = i2c, - .flags = 0, - .len = 1, - .buf = ®, - }, { - .addr = i2c, - .flags = I2C_M_RD, - .len = sizeof(buf), - .buf = buf, - } - }; - - ret = i2c_transfer(priv->i2c, msg, 2); - if (ret == 2) { - memcpy(val, buf, len); - ret = 0; - } else { - warn("i2c rd failed ret:%d reg:%02x len:%d", ret, reg, len); - ret = -EREMOTEIO; - } - - return ret; -} - -/* write multiple registers */ -int cxd2820r_wr_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, - int len) -{ - int ret; - u8 i2c_addr; - u8 reg = (reginfo >> 0) & 0xff; - u8 bank = (reginfo >> 8) & 0xff; - u8 i2c = (reginfo >> 16) & 0x01; - - /* select I2C */ - if (i2c) - i2c_addr = priv->cfg.i2c_address | (1 << 1); /* DVB-C */ - else - i2c_addr = priv->cfg.i2c_address; /* DVB-T/T2 */ - - /* switch bank if needed */ - if (bank != priv->bank[i2c]) { - ret = cxd2820r_wr_regs_i2c(priv, i2c_addr, 0x00, &bank, 1); - if (ret) - return ret; - priv->bank[i2c] = bank; - } - return cxd2820r_wr_regs_i2c(priv, i2c_addr, reg, val, len); -} - -/* read multiple registers */ -int cxd2820r_rd_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, - int len) -{ - int ret; - u8 i2c_addr; - u8 reg = (reginfo >> 0) & 0xff; - u8 bank = (reginfo >> 8) & 0xff; - u8 i2c = (reginfo >> 16) & 0x01; - - /* select I2C */ - if (i2c) - i2c_addr = priv->cfg.i2c_address | (1 << 1); /* DVB-C */ - else - i2c_addr = priv->cfg.i2c_address; /* DVB-T/T2 */ - - /* switch bank if needed */ - if (bank != priv->bank[i2c]) { - ret = cxd2820r_wr_regs_i2c(priv, i2c_addr, 0x00, &bank, 1); - if (ret) - return ret; - priv->bank[i2c] = bank; - } - return cxd2820r_rd_regs_i2c(priv, i2c_addr, reg, val, len); -} - -/* write single register */ -int cxd2820r_wr_reg(struct cxd2820r_priv *priv, u32 reg, u8 val) -{ - return cxd2820r_wr_regs(priv, reg, &val, 1); -} - -/* read single register */ -int cxd2820r_rd_reg(struct cxd2820r_priv *priv, u32 reg, u8 *val) -{ - return cxd2820r_rd_regs(priv, reg, val, 1); -} - -/* write single register with mask */ -int cxd2820r_wr_reg_mask(struct cxd2820r_priv *priv, u32 reg, u8 val, - u8 mask) -{ - int ret; - u8 tmp; - - /* no need for read if whole reg is written */ - if (mask != 0xff) { - ret = cxd2820r_rd_reg(priv, reg, &tmp); - if (ret) - return ret; - - val &= mask; - tmp &= ~mask; - val |= tmp; - } - - return cxd2820r_wr_reg(priv, reg, val); -} - -int cxd2820r_gpio(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret, i; - u8 *gpio, tmp0, tmp1; - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - - switch (fe->dtv_property_cache.delivery_system) { - case SYS_DVBT: - gpio = priv->cfg.gpio_dvbt; - break; - case SYS_DVBT2: - gpio = priv->cfg.gpio_dvbt2; - break; - case SYS_DVBC_ANNEX_AC: - gpio = priv->cfg.gpio_dvbc; - break; - default: - ret = -EINVAL; - goto error; - } - - /* update GPIOs only when needed */ - if (!memcmp(gpio, priv->gpio, sizeof(priv->gpio))) - return 0; - - tmp0 = 0x00; - tmp1 = 0x00; - for (i = 0; i < sizeof(priv->gpio); i++) { - /* enable / disable */ - if (gpio[i] & CXD2820R_GPIO_E) - tmp0 |= (2 << 6) >> (2 * i); - else - tmp0 |= (1 << 6) >> (2 * i); - - /* input / output */ - if (gpio[i] & CXD2820R_GPIO_I) - tmp1 |= (1 << (3 + i)); - else - tmp1 |= (0 << (3 + i)); - - /* high / low */ - if (gpio[i] & CXD2820R_GPIO_H) - tmp1 |= (1 << (0 + i)); - else - tmp1 |= (0 << (0 + i)); - - dbg("%s: GPIO i=%d %02x %02x", __func__, i, tmp0, tmp1); - } - - dbg("%s: wr gpio=%02x %02x", __func__, tmp0, tmp1); - - /* write bits [7:2] */ - ret = cxd2820r_wr_reg_mask(priv, 0x00089, tmp0, 0xfc); - if (ret) - goto error; - - /* write bits [5:0] */ - ret = cxd2820r_wr_reg_mask(priv, 0x0008e, tmp1, 0x3f); - if (ret) - goto error; - - memcpy(priv->gpio, gpio, sizeof(priv->gpio)); - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -/* 64 bit div with round closest, like DIV_ROUND_CLOSEST but 64 bit */ -u32 cxd2820r_div_u64_round_closest(u64 dividend, u32 divisor) -{ - return div_u64(dividend + (divisor / 2), divisor); -} - -static int cxd2820r_set_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret; - - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - switch (c->delivery_system) { - case SYS_DVBT: - ret = cxd2820r_init_t(fe); - if (ret < 0) - goto err; - ret = cxd2820r_set_frontend_t(fe); - if (ret < 0) - goto err; - break; - case SYS_DVBT2: - ret = cxd2820r_init_t(fe); - if (ret < 0) - goto err; - ret = cxd2820r_set_frontend_t2(fe); - if (ret < 0) - goto err; - break; - case SYS_DVBC_ANNEX_A: - ret = cxd2820r_init_c(fe); - if (ret < 0) - goto err; - ret = cxd2820r_set_frontend_c(fe); - if (ret < 0) - goto err; - break; - default: - dbg("%s: error state=%d", __func__, fe->dtv_property_cache.delivery_system); - ret = -EINVAL; - break; - } -err: - return ret; -} -static int cxd2820r_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - int ret; - - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - switch (fe->dtv_property_cache.delivery_system) { - case SYS_DVBT: - ret = cxd2820r_read_status_t(fe, status); - break; - case SYS_DVBT2: - ret = cxd2820r_read_status_t2(fe, status); - break; - case SYS_DVBC_ANNEX_A: - ret = cxd2820r_read_status_c(fe, status); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int cxd2820r_get_frontend(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - - if (priv->delivery_system == SYS_UNDEFINED) - return 0; - - switch (fe->dtv_property_cache.delivery_system) { - case SYS_DVBT: - ret = cxd2820r_get_frontend_t(fe); - break; - case SYS_DVBT2: - ret = cxd2820r_get_frontend_t2(fe); - break; - case SYS_DVBC_ANNEX_A: - ret = cxd2820r_get_frontend_c(fe); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int cxd2820r_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - int ret; - - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - switch (fe->dtv_property_cache.delivery_system) { - case SYS_DVBT: - ret = cxd2820r_read_ber_t(fe, ber); - break; - case SYS_DVBT2: - ret = cxd2820r_read_ber_t2(fe, ber); - break; - case SYS_DVBC_ANNEX_A: - ret = cxd2820r_read_ber_c(fe, ber); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int cxd2820r_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - int ret; - - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - switch (fe->dtv_property_cache.delivery_system) { - case SYS_DVBT: - ret = cxd2820r_read_signal_strength_t(fe, strength); - break; - case SYS_DVBT2: - ret = cxd2820r_read_signal_strength_t2(fe, strength); - break; - case SYS_DVBC_ANNEX_A: - ret = cxd2820r_read_signal_strength_c(fe, strength); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int cxd2820r_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - int ret; - - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - switch (fe->dtv_property_cache.delivery_system) { - case SYS_DVBT: - ret = cxd2820r_read_snr_t(fe, snr); - break; - case SYS_DVBT2: - ret = cxd2820r_read_snr_t2(fe, snr); - break; - case SYS_DVBC_ANNEX_A: - ret = cxd2820r_read_snr_c(fe, snr); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int cxd2820r_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - int ret; - - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - switch (fe->dtv_property_cache.delivery_system) { - case SYS_DVBT: - ret = cxd2820r_read_ucblocks_t(fe, ucblocks); - break; - case SYS_DVBT2: - ret = cxd2820r_read_ucblocks_t2(fe, ucblocks); - break; - case SYS_DVBC_ANNEX_A: - ret = cxd2820r_read_ucblocks_c(fe, ucblocks); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int cxd2820r_init(struct dvb_frontend *fe) -{ - return 0; -} - -static int cxd2820r_sleep(struct dvb_frontend *fe) -{ - int ret; - - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - switch (fe->dtv_property_cache.delivery_system) { - case SYS_DVBT: - ret = cxd2820r_sleep_t(fe); - break; - case SYS_DVBT2: - ret = cxd2820r_sleep_t2(fe); - break; - case SYS_DVBC_ANNEX_A: - ret = cxd2820r_sleep_c(fe); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static int cxd2820r_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s) -{ - int ret; - - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - switch (fe->dtv_property_cache.delivery_system) { - case SYS_DVBT: - ret = cxd2820r_get_tune_settings_t(fe, s); - break; - case SYS_DVBT2: - ret = cxd2820r_get_tune_settings_t2(fe, s); - break; - case SYS_DVBC_ANNEX_A: - ret = cxd2820r_get_tune_settings_c(fe, s); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static enum dvbfe_search cxd2820r_search(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i; - fe_status_t status = 0; - dbg("%s: delsys=%d", __func__, fe->dtv_property_cache.delivery_system); - - /* switch between DVB-T and DVB-T2 when tune fails */ - if (priv->last_tune_failed) { - if (priv->delivery_system == SYS_DVBT) { - ret = cxd2820r_sleep_t(fe); - if (ret) - goto error; - - c->delivery_system = SYS_DVBT2; - } else if (priv->delivery_system == SYS_DVBT2) { - ret = cxd2820r_sleep_t2(fe); - if (ret) - goto error; - - c->delivery_system = SYS_DVBT; - } - } - - /* set frontend */ - ret = cxd2820r_set_frontend(fe); - if (ret) - goto error; - - - /* frontend lock wait loop count */ - switch (priv->delivery_system) { - case SYS_DVBT: - case SYS_DVBC_ANNEX_A: - i = 20; - break; - case SYS_DVBT2: - i = 40; - break; - case SYS_UNDEFINED: - default: - i = 0; - break; - } - - /* wait frontend lock */ - for (; i > 0; i--) { - dbg("%s: LOOP=%d", __func__, i); - msleep(50); - ret = cxd2820r_read_status(fe, &status); - if (ret) - goto error; - - if (status & FE_HAS_LOCK) - break; - } - - /* check if we have a valid signal */ - if (status & FE_HAS_LOCK) { - priv->last_tune_failed = 0; - return DVBFE_ALGO_SEARCH_SUCCESS; - } else { - priv->last_tune_failed = 1; - return DVBFE_ALGO_SEARCH_AGAIN; - } - -error: - dbg("%s: failed:%d", __func__, ret); - return DVBFE_ALGO_SEARCH_ERROR; -} - -static int cxd2820r_get_frontend_algo(struct dvb_frontend *fe) -{ - return DVBFE_ALGO_CUSTOM; -} - -static void cxd2820r_release(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - dbg("%s", __func__); - - kfree(priv); - return; -} - -static int cxd2820r_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - dbg("%s: %d", __func__, enable); - - /* Bit 0 of reg 0xdb in bank 0x00 controls I2C repeater */ - return cxd2820r_wr_reg_mask(priv, 0xdb, enable ? 1 : 0, 0x1); -} - -static const struct dvb_frontend_ops cxd2820r_ops = { - .delsys = { SYS_DVBT, SYS_DVBT2, SYS_DVBC_ANNEX_A }, - /* default: DVB-T/T2 */ - .info = { - .name = "Sony CXD2820R", - - .caps = FE_CAN_FEC_1_2 | - FE_CAN_FEC_2_3 | - FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | - FE_CAN_FEC_7_8 | - FE_CAN_FEC_AUTO | - FE_CAN_QPSK | - FE_CAN_QAM_16 | - FE_CAN_QAM_32 | - FE_CAN_QAM_64 | - FE_CAN_QAM_128 | - FE_CAN_QAM_256 | - FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO | - FE_CAN_MUTE_TS | - FE_CAN_2G_MODULATION - }, - - .release = cxd2820r_release, - .init = cxd2820r_init, - .sleep = cxd2820r_sleep, - - .get_tune_settings = cxd2820r_get_tune_settings, - .i2c_gate_ctrl = cxd2820r_i2c_gate_ctrl, - - .get_frontend = cxd2820r_get_frontend, - - .get_frontend_algo = cxd2820r_get_frontend_algo, - .search = cxd2820r_search, - - .read_status = cxd2820r_read_status, - .read_snr = cxd2820r_read_snr, - .read_ber = cxd2820r_read_ber, - .read_ucblocks = cxd2820r_read_ucblocks, - .read_signal_strength = cxd2820r_read_signal_strength, -}; - -struct dvb_frontend *cxd2820r_attach(const struct cxd2820r_config *cfg, - struct i2c_adapter *i2c) -{ - struct cxd2820r_priv *priv = NULL; - int ret; - u8 tmp; - - priv = kzalloc(sizeof (struct cxd2820r_priv), GFP_KERNEL); - if (!priv) - goto error; - - priv->i2c = i2c; - memcpy(&priv->cfg, cfg, sizeof (struct cxd2820r_config)); - - priv->bank[0] = priv->bank[1] = 0xff; - ret = cxd2820r_rd_reg(priv, 0x000fd, &tmp); - dbg("%s: chip id=%02x", __func__, tmp); - if (ret || tmp != 0xe1) - goto error; - - memcpy(&priv->fe.ops, &cxd2820r_ops, sizeof (struct dvb_frontend_ops)); - priv->fe.demodulator_priv = priv; - return &priv->fe; -error: - kfree(priv); - return NULL; -} -EXPORT_SYMBOL(cxd2820r_attach); - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Sony CXD2820R demodulator driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/cxd2820r_priv.h b/drivers/media/dvb/frontends/cxd2820r_priv.h deleted file mode 100644 index 9a9822cad9cd..000000000000 --- a/drivers/media/dvb/frontends/cxd2820r_priv.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Sony CXD2820R demodulator driver - * - * Copyright (C) 2010 Antti Palosaari - * - * 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. - */ - - -#ifndef CXD2820R_PRIV_H -#define CXD2820R_PRIV_H - -#include -#include "dvb_frontend.h" -#include "dvb_math.h" -#include "cxd2820r.h" - -#define LOG_PREFIX "cxd2820r" - -#undef dbg -#define dbg(f, arg...) \ - if (cxd2820r_debug) \ - printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef err -#define err(f, arg...) printk(KERN_ERR LOG_PREFIX": " f "\n" , ## arg) -#undef info -#define info(f, arg...) printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef warn -#define warn(f, arg...) printk(KERN_WARNING LOG_PREFIX": " f "\n" , ## arg) - -struct reg_val_mask { - u32 reg; - u8 val; - u8 mask; -}; - -struct cxd2820r_priv { - struct i2c_adapter *i2c; - struct dvb_frontend fe; - struct cxd2820r_config cfg; - - bool ber_running; - - u8 bank[2]; - u8 gpio[3]; - - fe_delivery_system_t delivery_system; - bool last_tune_failed; /* for switch between T and T2 tune */ -}; - -/* cxd2820r_core.c */ - -extern int cxd2820r_debug; - -int cxd2820r_gpio(struct dvb_frontend *fe); - -int cxd2820r_wr_reg_mask(struct cxd2820r_priv *priv, u32 reg, u8 val, - u8 mask); - -int cxd2820r_wr_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, - int len); - -u32 cxd2820r_div_u64_round_closest(u64 dividend, u32 divisor); - -int cxd2820r_wr_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, - int len); - -int cxd2820r_rd_regs(struct cxd2820r_priv *priv, u32 reginfo, u8 *val, - int len); - -int cxd2820r_wr_reg(struct cxd2820r_priv *priv, u32 reg, u8 val); - -int cxd2820r_rd_reg(struct cxd2820r_priv *priv, u32 reg, u8 *val); - -/* cxd2820r_c.c */ - -int cxd2820r_get_frontend_c(struct dvb_frontend *fe); - -int cxd2820r_set_frontend_c(struct dvb_frontend *fe); - -int cxd2820r_read_status_c(struct dvb_frontend *fe, fe_status_t *status); - -int cxd2820r_read_ber_c(struct dvb_frontend *fe, u32 *ber); - -int cxd2820r_read_signal_strength_c(struct dvb_frontend *fe, u16 *strength); - -int cxd2820r_read_snr_c(struct dvb_frontend *fe, u16 *snr); - -int cxd2820r_read_ucblocks_c(struct dvb_frontend *fe, u32 *ucblocks); - -int cxd2820r_init_c(struct dvb_frontend *fe); - -int cxd2820r_sleep_c(struct dvb_frontend *fe); - -int cxd2820r_get_tune_settings_c(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s); - -/* cxd2820r_t.c */ - -int cxd2820r_get_frontend_t(struct dvb_frontend *fe); - -int cxd2820r_set_frontend_t(struct dvb_frontend *fe); - -int cxd2820r_read_status_t(struct dvb_frontend *fe, fe_status_t *status); - -int cxd2820r_read_ber_t(struct dvb_frontend *fe, u32 *ber); - -int cxd2820r_read_signal_strength_t(struct dvb_frontend *fe, u16 *strength); - -int cxd2820r_read_snr_t(struct dvb_frontend *fe, u16 *snr); - -int cxd2820r_read_ucblocks_t(struct dvb_frontend *fe, u32 *ucblocks); - -int cxd2820r_init_t(struct dvb_frontend *fe); - -int cxd2820r_sleep_t(struct dvb_frontend *fe); - -int cxd2820r_get_tune_settings_t(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s); - -/* cxd2820r_t2.c */ - -int cxd2820r_get_frontend_t2(struct dvb_frontend *fe); - -int cxd2820r_set_frontend_t2(struct dvb_frontend *fe); - -int cxd2820r_read_status_t2(struct dvb_frontend *fe, fe_status_t *status); - -int cxd2820r_read_ber_t2(struct dvb_frontend *fe, u32 *ber); - -int cxd2820r_read_signal_strength_t2(struct dvb_frontend *fe, u16 *strength); - -int cxd2820r_read_snr_t2(struct dvb_frontend *fe, u16 *snr); - -int cxd2820r_read_ucblocks_t2(struct dvb_frontend *fe, u32 *ucblocks); - -int cxd2820r_init_t2(struct dvb_frontend *fe); - -int cxd2820r_sleep_t2(struct dvb_frontend *fe); - -int cxd2820r_get_tune_settings_t2(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s); - -#endif /* CXD2820R_PRIV_H */ diff --git a/drivers/media/dvb/frontends/cxd2820r_t.c b/drivers/media/dvb/frontends/cxd2820r_t.c deleted file mode 100644 index 1a026239cdcc..000000000000 --- a/drivers/media/dvb/frontends/cxd2820r_t.c +++ /dev/null @@ -1,453 +0,0 @@ -/* - * Sony CXD2820R demodulator driver - * - * Copyright (C) 2010 Antti Palosaari - * - * 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. - */ - - -#include "cxd2820r_priv.h" - -int cxd2820r_set_frontend_t(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i, bw_i; - u32 if_freq, if_ctl; - u64 num; - u8 buf[3], bw_param; - u8 bw_params1[][5] = { - { 0x17, 0xea, 0xaa, 0xaa, 0xaa }, /* 6 MHz */ - { 0x14, 0x80, 0x00, 0x00, 0x00 }, /* 7 MHz */ - { 0x11, 0xf0, 0x00, 0x00, 0x00 }, /* 8 MHz */ - }; - u8 bw_params2[][2] = { - { 0x1f, 0xdc }, /* 6 MHz */ - { 0x12, 0xf8 }, /* 7 MHz */ - { 0x01, 0xe0 }, /* 8 MHz */ - }; - struct reg_val_mask tab[] = { - { 0x00080, 0x00, 0xff }, - { 0x00081, 0x03, 0xff }, - { 0x00085, 0x07, 0xff }, - { 0x00088, 0x01, 0xff }, - - { 0x00070, priv->cfg.ts_mode, 0xff }, - { 0x000cb, priv->cfg.if_agc_polarity << 6, 0x40 }, - { 0x000a5, 0x00, 0x01 }, - { 0x00082, 0x20, 0x60 }, - { 0x000c2, 0xc3, 0xff }, - { 0x0016a, 0x50, 0xff }, - { 0x00427, 0x41, 0xff }, - }; - - dbg("%s: RF=%d BW=%d", __func__, c->frequency, c->bandwidth_hz); - - switch (c->bandwidth_hz) { - case 6000000: - bw_i = 0; - bw_param = 2; - break; - case 7000000: - bw_i = 1; - bw_param = 1; - break; - case 8000000: - bw_i = 2; - bw_param = 0; - break; - default: - return -EINVAL; - } - - /* update GPIOs */ - ret = cxd2820r_gpio(fe); - if (ret) - goto error; - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - - if (priv->delivery_system != SYS_DVBT) { - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, - tab[i].val, tab[i].mask); - if (ret) - goto error; - } - } - - priv->delivery_system = SYS_DVBT; - priv->ber_running = 0; /* tune stops BER counter */ - - /* program IF frequency */ - if (fe->ops.tuner_ops.get_if_frequency) { - ret = fe->ops.tuner_ops.get_if_frequency(fe, &if_freq); - if (ret) - goto error; - } else - if_freq = 0; - - dbg("%s: if_freq=%d", __func__, if_freq); - - num = if_freq / 1000; /* Hz => kHz */ - num *= 0x1000000; - if_ctl = cxd2820r_div_u64_round_closest(num, 41000); - buf[0] = ((if_ctl >> 16) & 0xff); - buf[1] = ((if_ctl >> 8) & 0xff); - buf[2] = ((if_ctl >> 0) & 0xff); - - ret = cxd2820r_wr_regs(priv, 0x000b6, buf, 3); - if (ret) - goto error; - - ret = cxd2820r_wr_regs(priv, 0x0009f, bw_params1[bw_i], 5); - if (ret) - goto error; - - ret = cxd2820r_wr_reg_mask(priv, 0x000d7, bw_param << 6, 0xc0); - if (ret) - goto error; - - ret = cxd2820r_wr_regs(priv, 0x000d9, bw_params2[bw_i], 2); - if (ret) - goto error; - - ret = cxd2820r_wr_reg(priv, 0x000ff, 0x08); - if (ret) - goto error; - - ret = cxd2820r_wr_reg(priv, 0x000fe, 0x01); - if (ret) - goto error; - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_get_frontend_t(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret; - u8 buf[2]; - - ret = cxd2820r_rd_regs(priv, 0x0002f, buf, sizeof(buf)); - if (ret) - goto error; - - switch ((buf[0] >> 6) & 0x03) { - case 0: - c->modulation = QPSK; - break; - case 1: - c->modulation = QAM_16; - break; - case 2: - c->modulation = QAM_64; - break; - } - - switch ((buf[1] >> 1) & 0x03) { - case 0: - c->transmission_mode = TRANSMISSION_MODE_2K; - break; - case 1: - c->transmission_mode = TRANSMISSION_MODE_8K; - break; - } - - switch ((buf[1] >> 3) & 0x03) { - case 0: - c->guard_interval = GUARD_INTERVAL_1_32; - break; - case 1: - c->guard_interval = GUARD_INTERVAL_1_16; - break; - case 2: - c->guard_interval = GUARD_INTERVAL_1_8; - break; - case 3: - c->guard_interval = GUARD_INTERVAL_1_4; - break; - } - - switch ((buf[0] >> 3) & 0x07) { - case 0: - c->hierarchy = HIERARCHY_NONE; - break; - case 1: - c->hierarchy = HIERARCHY_1; - break; - case 2: - c->hierarchy = HIERARCHY_2; - break; - case 3: - c->hierarchy = HIERARCHY_4; - break; - } - - switch ((buf[0] >> 0) & 0x07) { - case 0: - c->code_rate_HP = FEC_1_2; - break; - case 1: - c->code_rate_HP = FEC_2_3; - break; - case 2: - c->code_rate_HP = FEC_3_4; - break; - case 3: - c->code_rate_HP = FEC_5_6; - break; - case 4: - c->code_rate_HP = FEC_7_8; - break; - } - - switch ((buf[1] >> 5) & 0x07) { - case 0: - c->code_rate_LP = FEC_1_2; - break; - case 1: - c->code_rate_LP = FEC_2_3; - break; - case 2: - c->code_rate_LP = FEC_3_4; - break; - case 3: - c->code_rate_LP = FEC_5_6; - break; - case 4: - c->code_rate_LP = FEC_7_8; - break; - } - - ret = cxd2820r_rd_reg(priv, 0x007c6, &buf[0]); - if (ret) - goto error; - - switch ((buf[0] >> 0) & 0x01) { - case 0: - c->inversion = INVERSION_OFF; - break; - case 1: - c->inversion = INVERSION_ON; - break; - } - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_ber_t(struct dvb_frontend *fe, u32 *ber) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[3], start_ber = 0; - *ber = 0; - - if (priv->ber_running) { - ret = cxd2820r_rd_regs(priv, 0x00076, buf, sizeof(buf)); - if (ret) - goto error; - - if ((buf[2] >> 7) & 0x01 || (buf[2] >> 4) & 0x01) { - *ber = (buf[2] & 0x0f) << 16 | buf[1] << 8 | buf[0]; - start_ber = 1; - } - } else { - priv->ber_running = 1; - start_ber = 1; - } - - if (start_ber) { - /* (re)start BER */ - ret = cxd2820r_wr_reg(priv, 0x00079, 0x01); - if (ret) - goto error; - } - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_signal_strength_t(struct dvb_frontend *fe, - u16 *strength) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[2]; - u16 tmp; - - ret = cxd2820r_rd_regs(priv, 0x00026, buf, sizeof(buf)); - if (ret) - goto error; - - tmp = (buf[0] & 0x0f) << 8 | buf[1]; - tmp = ~tmp & 0x0fff; - - /* scale value to 0x0000-0xffff from 0x0000-0x0fff */ - *strength = tmp * 0xffff / 0x0fff; - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_snr_t(struct dvb_frontend *fe, u16 *snr) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[2]; - u16 tmp; - /* report SNR in dB * 10 */ - - ret = cxd2820r_rd_regs(priv, 0x00028, buf, sizeof(buf)); - if (ret) - goto error; - - tmp = (buf[0] & 0x1f) << 8 | buf[1]; - #define CXD2820R_LOG10_8_24 15151336 /* log10(8) << 24 */ - if (tmp) - *snr = (intlog10(tmp) - CXD2820R_LOG10_8_24) / ((1 << 24) - / 100); - else - *snr = 0; - - dbg("%s: dBx10=%d val=%04x", __func__, *snr, tmp); - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_ucblocks_t(struct dvb_frontend *fe, u32 *ucblocks) -{ - *ucblocks = 0; - /* no way to read ? */ - return 0; -} - -int cxd2820r_read_status_t(struct dvb_frontend *fe, fe_status_t *status) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[4]; - *status = 0; - - ret = cxd2820r_rd_reg(priv, 0x00010, &buf[0]); - if (ret) - goto error; - - if ((buf[0] & 0x07) == 6) { - ret = cxd2820r_rd_reg(priv, 0x00073, &buf[1]); - if (ret) - goto error; - - if (((buf[1] >> 3) & 0x01) == 1) { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - } else { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC; - } - } else { - ret = cxd2820r_rd_reg(priv, 0x00014, &buf[2]); - if (ret) - goto error; - - if ((buf[2] & 0x0f) >= 4) { - ret = cxd2820r_rd_reg(priv, 0x00a14, &buf[3]); - if (ret) - goto error; - - if (((buf[3] >> 4) & 0x01) == 1) - *status |= FE_HAS_SIGNAL; - } - } - - dbg("%s: lock=%02x %02x %02x %02x", __func__, - buf[0], buf[1], buf[2], buf[3]); - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_init_t(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - - ret = cxd2820r_wr_reg(priv, 0x00085, 0x07); - if (ret) - goto error; - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_sleep_t(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret, i; - struct reg_val_mask tab[] = { - { 0x000ff, 0x1f, 0xff }, - { 0x00085, 0x00, 0xff }, - { 0x00088, 0x01, 0xff }, - { 0x00081, 0x00, 0xff }, - { 0x00080, 0x00, 0xff }, - }; - - dbg("%s", __func__); - - priv->delivery_system = SYS_UNDEFINED; - - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, tab[i].val, - tab[i].mask); - if (ret) - goto error; - } - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_get_tune_settings_t(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s) -{ - s->min_delay_ms = 500; - s->step_size = fe->ops.info.frequency_stepsize * 2; - s->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; - - return 0; -} diff --git a/drivers/media/dvb/frontends/cxd2820r_t2.c b/drivers/media/dvb/frontends/cxd2820r_t2.c deleted file mode 100644 index 3a5759e0d235..000000000000 --- a/drivers/media/dvb/frontends/cxd2820r_t2.c +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Sony CXD2820R demodulator driver - * - * Copyright (C) 2010 Antti Palosaari - * - * 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. - */ - - -#include "cxd2820r_priv.h" - -int cxd2820r_set_frontend_t2(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i, bw_i; - u32 if_freq, if_ctl; - u64 num; - u8 buf[3], bw_param; - u8 bw_params1[][5] = { - { 0x1c, 0xb3, 0x33, 0x33, 0x33 }, /* 5 MHz */ - { 0x17, 0xea, 0xaa, 0xaa, 0xaa }, /* 6 MHz */ - { 0x14, 0x80, 0x00, 0x00, 0x00 }, /* 7 MHz */ - { 0x11, 0xf0, 0x00, 0x00, 0x00 }, /* 8 MHz */ - }; - struct reg_val_mask tab[] = { - { 0x00080, 0x02, 0xff }, - { 0x00081, 0x20, 0xff }, - { 0x00085, 0x07, 0xff }, - { 0x00088, 0x01, 0xff }, - { 0x02069, 0x01, 0xff }, - - { 0x0207f, 0x2a, 0xff }, - { 0x02082, 0x0a, 0xff }, - { 0x02083, 0x0a, 0xff }, - { 0x020cb, priv->cfg.if_agc_polarity << 6, 0x40 }, - { 0x02070, priv->cfg.ts_mode, 0xff }, - { 0x020b5, priv->cfg.spec_inv << 4, 0x10 }, - { 0x02567, 0x07, 0x0f }, - { 0x02569, 0x03, 0x03 }, - { 0x02595, 0x1a, 0xff }, - { 0x02596, 0x50, 0xff }, - { 0x02a8c, 0x00, 0xff }, - { 0x02a8d, 0x34, 0xff }, - { 0x02a45, 0x06, 0x07 }, - { 0x03f10, 0x0d, 0xff }, - { 0x03f11, 0x02, 0xff }, - { 0x03f12, 0x01, 0xff }, - { 0x03f23, 0x2c, 0xff }, - { 0x03f51, 0x13, 0xff }, - { 0x03f52, 0x01, 0xff }, - { 0x03f53, 0x00, 0xff }, - { 0x027e6, 0x14, 0xff }, - { 0x02786, 0x02, 0x07 }, - { 0x02787, 0x40, 0xe0 }, - { 0x027ef, 0x10, 0x18 }, - }; - - dbg("%s: RF=%d BW=%d", __func__, c->frequency, c->bandwidth_hz); - - switch (c->bandwidth_hz) { - case 5000000: - bw_i = 0; - bw_param = 3; - break; - case 6000000: - bw_i = 1; - bw_param = 2; - break; - case 7000000: - bw_i = 2; - bw_param = 1; - break; - case 8000000: - bw_i = 3; - bw_param = 0; - break; - default: - return -EINVAL; - } - - /* update GPIOs */ - ret = cxd2820r_gpio(fe); - if (ret) - goto error; - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - - if (priv->delivery_system != SYS_DVBT2) { - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, - tab[i].val, tab[i].mask); - if (ret) - goto error; - } - } - - priv->delivery_system = SYS_DVBT2; - - /* program IF frequency */ - if (fe->ops.tuner_ops.get_if_frequency) { - ret = fe->ops.tuner_ops.get_if_frequency(fe, &if_freq); - if (ret) - goto error; - } else - if_freq = 0; - - dbg("%s: if_freq=%d", __func__, if_freq); - - num = if_freq / 1000; /* Hz => kHz */ - num *= 0x1000000; - if_ctl = cxd2820r_div_u64_round_closest(num, 41000); - buf[0] = ((if_ctl >> 16) & 0xff); - buf[1] = ((if_ctl >> 8) & 0xff); - buf[2] = ((if_ctl >> 0) & 0xff); - - ret = cxd2820r_wr_regs(priv, 0x020b6, buf, 3); - if (ret) - goto error; - - ret = cxd2820r_wr_regs(priv, 0x0209f, bw_params1[bw_i], 5); - if (ret) - goto error; - - ret = cxd2820r_wr_reg_mask(priv, 0x020d7, bw_param << 6, 0xc0); - if (ret) - goto error; - - ret = cxd2820r_wr_reg(priv, 0x000ff, 0x08); - if (ret) - goto error; - - ret = cxd2820r_wr_reg(priv, 0x000fe, 0x01); - if (ret) - goto error; - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; - -} - -int cxd2820r_get_frontend_t2(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret; - u8 buf[2]; - - ret = cxd2820r_rd_regs(priv, 0x0205c, buf, 2); - if (ret) - goto error; - - switch ((buf[0] >> 0) & 0x07) { - case 0: - c->transmission_mode = TRANSMISSION_MODE_2K; - break; - case 1: - c->transmission_mode = TRANSMISSION_MODE_8K; - break; - case 2: - c->transmission_mode = TRANSMISSION_MODE_4K; - break; - case 3: - c->transmission_mode = TRANSMISSION_MODE_1K; - break; - case 4: - c->transmission_mode = TRANSMISSION_MODE_16K; - break; - case 5: - c->transmission_mode = TRANSMISSION_MODE_32K; - break; - } - - switch ((buf[1] >> 4) & 0x07) { - case 0: - c->guard_interval = GUARD_INTERVAL_1_32; - break; - case 1: - c->guard_interval = GUARD_INTERVAL_1_16; - break; - case 2: - c->guard_interval = GUARD_INTERVAL_1_8; - break; - case 3: - c->guard_interval = GUARD_INTERVAL_1_4; - break; - case 4: - c->guard_interval = GUARD_INTERVAL_1_128; - break; - case 5: - c->guard_interval = GUARD_INTERVAL_19_128; - break; - case 6: - c->guard_interval = GUARD_INTERVAL_19_256; - break; - } - - ret = cxd2820r_rd_regs(priv, 0x0225b, buf, 2); - if (ret) - goto error; - - switch ((buf[0] >> 0) & 0x07) { - case 0: - c->fec_inner = FEC_1_2; - break; - case 1: - c->fec_inner = FEC_3_5; - break; - case 2: - c->fec_inner = FEC_2_3; - break; - case 3: - c->fec_inner = FEC_3_4; - break; - case 4: - c->fec_inner = FEC_4_5; - break; - case 5: - c->fec_inner = FEC_5_6; - break; - } - - switch ((buf[1] >> 0) & 0x07) { - case 0: - c->modulation = QPSK; - break; - case 1: - c->modulation = QAM_16; - break; - case 2: - c->modulation = QAM_64; - break; - case 3: - c->modulation = QAM_256; - break; - } - - ret = cxd2820r_rd_reg(priv, 0x020b5, &buf[0]); - if (ret) - goto error; - - switch ((buf[0] >> 4) & 0x01) { - case 0: - c->inversion = INVERSION_OFF; - break; - case 1: - c->inversion = INVERSION_ON; - break; - } - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_status_t2(struct dvb_frontend *fe, fe_status_t *status) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[1]; - *status = 0; - - ret = cxd2820r_rd_reg(priv, 0x02010 , &buf[0]); - if (ret) - goto error; - - if ((buf[0] & 0x07) == 6) { - if (((buf[0] >> 5) & 0x01) == 1) { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - } else { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC; - } - } - - dbg("%s: lock=%02x", __func__, buf[0]); - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_ber_t2(struct dvb_frontend *fe, u32 *ber) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[4]; - unsigned int errbits; - *ber = 0; - /* FIXME: correct calculation */ - - ret = cxd2820r_rd_regs(priv, 0x02039, buf, sizeof(buf)); - if (ret) - goto error; - - if ((buf[0] >> 4) & 0x01) { - errbits = (buf[0] & 0x0f) << 24 | buf[1] << 16 | - buf[2] << 8 | buf[3]; - - if (errbits) - *ber = errbits * 64 / 16588800; - } - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_signal_strength_t2(struct dvb_frontend *fe, - u16 *strength) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[2]; - u16 tmp; - - ret = cxd2820r_rd_regs(priv, 0x02026, buf, sizeof(buf)); - if (ret) - goto error; - - tmp = (buf[0] & 0x0f) << 8 | buf[1]; - tmp = ~tmp & 0x0fff; - - /* scale value to 0x0000-0xffff from 0x0000-0x0fff */ - *strength = tmp * 0xffff / 0x0fff; - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_snr_t2(struct dvb_frontend *fe, u16 *snr) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[2]; - u16 tmp; - /* report SNR in dB * 10 */ - - ret = cxd2820r_rd_regs(priv, 0x02028, buf, sizeof(buf)); - if (ret) - goto error; - - tmp = (buf[0] & 0x0f) << 8 | buf[1]; - #define CXD2820R_LOG10_8_24 15151336 /* log10(8) << 24 */ - if (tmp) - *snr = (intlog10(tmp) - CXD2820R_LOG10_8_24) / ((1 << 24) - / 100); - else - *snr = 0; - - dbg("%s: dBx10=%d val=%04x", __func__, *snr, tmp); - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_read_ucblocks_t2(struct dvb_frontend *fe, u32 *ucblocks) -{ - *ucblocks = 0; - /* no way to read ? */ - return 0; -} - -int cxd2820r_sleep_t2(struct dvb_frontend *fe) -{ - struct cxd2820r_priv *priv = fe->demodulator_priv; - int ret, i; - struct reg_val_mask tab[] = { - { 0x000ff, 0x1f, 0xff }, - { 0x00085, 0x00, 0xff }, - { 0x00088, 0x01, 0xff }, - { 0x02069, 0x00, 0xff }, - { 0x00081, 0x00, 0xff }, - { 0x00080, 0x00, 0xff }, - }; - - dbg("%s", __func__); - - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = cxd2820r_wr_reg_mask(priv, tab[i].reg, tab[i].val, - tab[i].mask); - if (ret) - goto error; - } - - priv->delivery_system = SYS_UNDEFINED; - - return ret; -error: - dbg("%s: failed:%d", __func__, ret); - return ret; -} - -int cxd2820r_get_tune_settings_t2(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s) -{ - s->min_delay_ms = 1500; - s->step_size = fe->ops.info.frequency_stepsize * 2; - s->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; - - return 0; -} diff --git a/drivers/media/dvb/frontends/drxk.h b/drivers/media/dvb/frontends/drxk.h deleted file mode 100644 index d615d7d055a2..000000000000 --- a/drivers/media/dvb/frontends/drxk.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef _DRXK_H_ -#define _DRXK_H_ - -#include -#include - -/** - * struct drxk_config - Configure the initial parameters for DRX-K - * - * @adr: I2C Address of the DRX-K - * @parallel_ts: True means that the device uses parallel TS, - * Serial otherwise. - * @dynamic_clk: True means that the clock will be dynamically - * adjusted. Static clock otherwise. - * @enable_merr_cfg: Enable SIO_PDR_PERR_CFG/SIO_PDR_MVAL_CFG. - * @single_master: Device is on the single master mode - * @no_i2c_bridge: Don't switch the I2C bridge to talk with tuner - * @antenna_gpio: GPIO bit used to control the antenna - * @antenna_dvbt: GPIO bit for changing antenna to DVB-C. A value of 1 - * means that 1=DVBC, 0 = DVBT. Zero means the opposite. - * @mpeg_out_clk_strength: DRXK Mpeg output clock drive strength. - * @microcode_name: Name of the firmware file with the microcode - * @qam_demod_parameter_count: The number of parameters used for the command - * to set the demodulator parameters. All - * firmwares are using the 2-parameter commmand. - * An exception is the "drxk_a3.mc" firmware, - * which uses the 4-parameter command. - * A value of 0 (default) or lower indicates that - * the correct number of parameters will be - * automatically detected. - * - * On the *_gpio vars, bit 0 is UIO-1, bit 1 is UIO-2 and bit 2 is - * UIO-3. - */ -struct drxk_config { - u8 adr; - bool single_master; - bool no_i2c_bridge; - bool parallel_ts; - bool dynamic_clk; - bool enable_merr_cfg; - - bool antenna_dvbt; - u16 antenna_gpio; - - u8 mpeg_out_clk_strength; - int chunk_size; - - const char *microcode_name; - int qam_demod_parameter_count; -}; - -#if defined(CONFIG_DVB_DRXK) || (defined(CONFIG_DVB_DRXK_MODULE) \ - && defined(MODULE)) -extern struct dvb_frontend *drxk_attach(const struct drxk_config *config, - struct i2c_adapter *i2c); -#else -static inline struct dvb_frontend *drxk_attach(const struct drxk_config *config, - struct i2c_adapter *i2c) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif - -#endif diff --git a/drivers/media/dvb/frontends/drxk_hard.c b/drivers/media/dvb/frontends/drxk_hard.c deleted file mode 100644 index 1ab8154542da..000000000000 --- a/drivers/media/dvb/frontends/drxk_hard.c +++ /dev/null @@ -1,6637 +0,0 @@ -/* - * drxk_hard: DRX-K DVB-C/T demodulator driver - * - * Copyright (C) 2010-2011 Digital Devices GmbH - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 only, as published by the Free Software Foundation. - * - * - * 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 - * Or, point your browser to http://www.gnu.org/copyleft/gpl.html - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dvb_frontend.h" -#include "drxk.h" -#include "drxk_hard.h" - -static int PowerDownDVBT(struct drxk_state *state, bool setPowerMode); -static int PowerDownQAM(struct drxk_state *state); -static int SetDVBTStandard(struct drxk_state *state, - enum OperationMode oMode); -static int SetQAMStandard(struct drxk_state *state, - enum OperationMode oMode); -static int SetQAM(struct drxk_state *state, u16 IntermediateFreqkHz, - s32 tunerFreqOffset); -static int SetDVBTStandard(struct drxk_state *state, - enum OperationMode oMode); -static int DVBTStart(struct drxk_state *state); -static int SetDVBT(struct drxk_state *state, u16 IntermediateFreqkHz, - s32 tunerFreqOffset); -static int GetQAMLockStatus(struct drxk_state *state, u32 *pLockStatus); -static int GetDVBTLockStatus(struct drxk_state *state, u32 *pLockStatus); -static int SwitchAntennaToQAM(struct drxk_state *state); -static int SwitchAntennaToDVBT(struct drxk_state *state); - -static bool IsDVBT(struct drxk_state *state) -{ - return state->m_OperationMode == OM_DVBT; -} - -static bool IsQAM(struct drxk_state *state) -{ - return state->m_OperationMode == OM_QAM_ITU_A || - state->m_OperationMode == OM_QAM_ITU_B || - state->m_OperationMode == OM_QAM_ITU_C; -} - -bool IsA1WithPatchCode(struct drxk_state *state) -{ - return state->m_DRXK_A1_PATCH_CODE; -} - -bool IsA1WithRomCode(struct drxk_state *state) -{ - return state->m_DRXK_A1_ROM_CODE; -} - -#define NOA1ROM 0 - -#define DRXDAP_FASI_SHORT_FORMAT(addr) (((addr) & 0xFC30FF80) == 0) -#define DRXDAP_FASI_LONG_FORMAT(addr) (((addr) & 0xFC30FF80) != 0) - -#define DEFAULT_MER_83 165 -#define DEFAULT_MER_93 250 - -#ifndef DRXK_MPEG_SERIAL_OUTPUT_PIN_DRIVE_STRENGTH -#define DRXK_MPEG_SERIAL_OUTPUT_PIN_DRIVE_STRENGTH (0x02) -#endif - -#ifndef DRXK_MPEG_PARALLEL_OUTPUT_PIN_DRIVE_STRENGTH -#define DRXK_MPEG_PARALLEL_OUTPUT_PIN_DRIVE_STRENGTH (0x03) -#endif - -#define DEFAULT_DRXK_MPEG_LOCK_TIMEOUT 700 -#define DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT 500 - -#ifndef DRXK_KI_RAGC_ATV -#define DRXK_KI_RAGC_ATV 4 -#endif -#ifndef DRXK_KI_IAGC_ATV -#define DRXK_KI_IAGC_ATV 6 -#endif -#ifndef DRXK_KI_DAGC_ATV -#define DRXK_KI_DAGC_ATV 7 -#endif - -#ifndef DRXK_KI_RAGC_QAM -#define DRXK_KI_RAGC_QAM 3 -#endif -#ifndef DRXK_KI_IAGC_QAM -#define DRXK_KI_IAGC_QAM 4 -#endif -#ifndef DRXK_KI_DAGC_QAM -#define DRXK_KI_DAGC_QAM 7 -#endif -#ifndef DRXK_KI_RAGC_DVBT -#define DRXK_KI_RAGC_DVBT (IsA1WithPatchCode(state) ? 3 : 2) -#endif -#ifndef DRXK_KI_IAGC_DVBT -#define DRXK_KI_IAGC_DVBT (IsA1WithPatchCode(state) ? 4 : 2) -#endif -#ifndef DRXK_KI_DAGC_DVBT -#define DRXK_KI_DAGC_DVBT (IsA1WithPatchCode(state) ? 10 : 7) -#endif - -#ifndef DRXK_AGC_DAC_OFFSET -#define DRXK_AGC_DAC_OFFSET (0x800) -#endif - -#ifndef DRXK_BANDWIDTH_8MHZ_IN_HZ -#define DRXK_BANDWIDTH_8MHZ_IN_HZ (0x8B8249L) -#endif - -#ifndef DRXK_BANDWIDTH_7MHZ_IN_HZ -#define DRXK_BANDWIDTH_7MHZ_IN_HZ (0x7A1200L) -#endif - -#ifndef DRXK_BANDWIDTH_6MHZ_IN_HZ -#define DRXK_BANDWIDTH_6MHZ_IN_HZ (0x68A1B6L) -#endif - -#ifndef DRXK_QAM_SYMBOLRATE_MAX -#define DRXK_QAM_SYMBOLRATE_MAX (7233000) -#endif - -#define DRXK_BL_ROM_OFFSET_TAPS_DVBT 56 -#define DRXK_BL_ROM_OFFSET_TAPS_ITU_A 64 -#define DRXK_BL_ROM_OFFSET_TAPS_ITU_C 0x5FE0 -#define DRXK_BL_ROM_OFFSET_TAPS_BG 24 -#define DRXK_BL_ROM_OFFSET_TAPS_DKILLP 32 -#define DRXK_BL_ROM_OFFSET_TAPS_NTSC 40 -#define DRXK_BL_ROM_OFFSET_TAPS_FM 48 -#define DRXK_BL_ROM_OFFSET_UCODE 0 - -#define DRXK_BLC_TIMEOUT 100 - -#define DRXK_BLCC_NR_ELEMENTS_TAPS 2 -#define DRXK_BLCC_NR_ELEMENTS_UCODE 6 - -#define DRXK_BLDC_NR_ELEMENTS_TAPS 28 - -#ifndef DRXK_OFDM_NE_NOTCH_WIDTH -#define DRXK_OFDM_NE_NOTCH_WIDTH (4) -#endif - -#define DRXK_QAM_SL_SIG_POWER_QAM16 (40960) -#define DRXK_QAM_SL_SIG_POWER_QAM32 (20480) -#define DRXK_QAM_SL_SIG_POWER_QAM64 (43008) -#define DRXK_QAM_SL_SIG_POWER_QAM128 (20992) -#define DRXK_QAM_SL_SIG_POWER_QAM256 (43520) - -static unsigned int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "enable debug messages"); - -#define dprintk(level, fmt, arg...) do { \ -if (debug >= level) \ - printk(KERN_DEBUG "drxk: %s" fmt, __func__, ## arg); \ -} while (0) - - -static inline u32 MulDiv32(u32 a, u32 b, u32 c) -{ - u64 tmp64; - - tmp64 = (u64) a * (u64) b; - do_div(tmp64, c); - - return (u32) tmp64; -} - -inline u32 Frac28a(u32 a, u32 c) -{ - int i = 0; - u32 Q1 = 0; - u32 R0 = 0; - - R0 = (a % c) << 4; /* 32-28 == 4 shifts possible at max */ - Q1 = a / c; /* integer part, only the 4 least significant bits - will be visible in the result */ - - /* division using radix 16, 7 nibbles in the result */ - for (i = 0; i < 7; i++) { - Q1 = (Q1 << 4) | (R0 / c); - R0 = (R0 % c) << 4; - } - /* rounding */ - if ((R0 >> 3) >= c) - Q1++; - - return Q1; -} - -static u32 Log10Times100(u32 x) -{ - static const u8 scale = 15; - static const u8 indexWidth = 5; - u8 i = 0; - u32 y = 0; - u32 d = 0; - u32 k = 0; - u32 r = 0; - /* - log2lut[n] = (1< 0; k--) { - if (x & (((u32) 1) << scale)) - break; - x <<= 1; - } - } else { - for (k = scale; k < 31; k++) { - if ((x & (((u32) (-1)) << (scale + 1))) == 0) - break; - x >>= 1; - } - } - /* - Now x has binary point between bit[scale] and bit[scale-1] - and 1.0 <= x < 2.0 */ - - /* correction for divison: log(x) = log(x/y)+log(y) */ - y = k * ((((u32) 1) << scale) * 200); - - /* remove integer part */ - x &= ((((u32) 1) << scale) - 1); - /* get index */ - i = (u8) (x >> (scale - indexWidth)); - /* compute delta (x - a) */ - d = x & ((((u32) 1) << (scale - indexWidth)) - 1); - /* compute log, multiplication (d* (..)) must be within range ! */ - y += log2lut[i] + - ((d * (log2lut[i + 1] - log2lut[i])) >> (scale - indexWidth)); - /* Conver to log10() */ - y /= 108853; /* (log2(10) << scale) */ - r = (y >> 1); - /* rounding */ - if (y & ((u32) 1)) - r++; - return r; -} - -/****************************************************************************/ -/* I2C **********************************************************************/ -/****************************************************************************/ - -static int drxk_i2c_lock(struct drxk_state *state) -{ - i2c_lock_adapter(state->i2c); - state->drxk_i2c_exclusive_lock = true; - - return 0; -} - -static void drxk_i2c_unlock(struct drxk_state *state) -{ - if (!state->drxk_i2c_exclusive_lock) - return; - - i2c_unlock_adapter(state->i2c); - state->drxk_i2c_exclusive_lock = false; -} - -static int drxk_i2c_transfer(struct drxk_state *state, struct i2c_msg *msgs, - unsigned len) -{ - if (state->drxk_i2c_exclusive_lock) - return __i2c_transfer(state->i2c, msgs, len); - else - return i2c_transfer(state->i2c, msgs, len); -} - -static int i2c_read1(struct drxk_state *state, u8 adr, u8 *val) -{ - struct i2c_msg msgs[1] = { {.addr = adr, .flags = I2C_M_RD, - .buf = val, .len = 1} - }; - - return drxk_i2c_transfer(state, msgs, 1); -} - -static int i2c_write(struct drxk_state *state, u8 adr, u8 *data, int len) -{ - int status; - struct i2c_msg msg = { - .addr = adr, .flags = 0, .buf = data, .len = len }; - - dprintk(3, ":"); - if (debug > 2) { - int i; - for (i = 0; i < len; i++) - printk(KERN_CONT " %02x", data[i]); - printk(KERN_CONT "\n"); - } - status = drxk_i2c_transfer(state, &msg, 1); - if (status >= 0 && status != 1) - status = -EIO; - - if (status < 0) - printk(KERN_ERR "drxk: i2c write error at addr 0x%02x\n", adr); - - return status; -} - -static int i2c_read(struct drxk_state *state, - u8 adr, u8 *msg, int len, u8 *answ, int alen) -{ - int status; - struct i2c_msg msgs[2] = { - {.addr = adr, .flags = 0, - .buf = msg, .len = len}, - {.addr = adr, .flags = I2C_M_RD, - .buf = answ, .len = alen} - }; - - status = drxk_i2c_transfer(state, msgs, 2); - if (status != 2) { - if (debug > 2) - printk(KERN_CONT ": ERROR!\n"); - if (status >= 0) - status = -EIO; - - printk(KERN_ERR "drxk: i2c read error at addr 0x%02x\n", adr); - return status; - } - if (debug > 2) { - int i; - dprintk(2, ": read from"); - for (i = 0; i < len; i++) - printk(KERN_CONT " %02x", msg[i]); - printk(KERN_CONT ", value = "); - for (i = 0; i < alen; i++) - printk(KERN_CONT " %02x", answ[i]); - printk(KERN_CONT "\n"); - } - return 0; -} - -static int read16_flags(struct drxk_state *state, u32 reg, u16 *data, u8 flags) -{ - int status; - u8 adr = state->demod_address, mm1[4], mm2[2], len; - - if (state->single_master) - flags |= 0xC0; - - if (DRXDAP_FASI_LONG_FORMAT(reg) || (flags != 0)) { - mm1[0] = (((reg << 1) & 0xFF) | 0x01); - mm1[1] = ((reg >> 16) & 0xFF); - mm1[2] = ((reg >> 24) & 0xFF) | flags; - mm1[3] = ((reg >> 7) & 0xFF); - len = 4; - } else { - mm1[0] = ((reg << 1) & 0xFF); - mm1[1] = (((reg >> 16) & 0x0F) | ((reg >> 18) & 0xF0)); - len = 2; - } - dprintk(2, "(0x%08x, 0x%02x)\n", reg, flags); - status = i2c_read(state, adr, mm1, len, mm2, 2); - if (status < 0) - return status; - if (data) - *data = mm2[0] | (mm2[1] << 8); - - return 0; -} - -static int read16(struct drxk_state *state, u32 reg, u16 *data) -{ - return read16_flags(state, reg, data, 0); -} - -static int read32_flags(struct drxk_state *state, u32 reg, u32 *data, u8 flags) -{ - int status; - u8 adr = state->demod_address, mm1[4], mm2[4], len; - - if (state->single_master) - flags |= 0xC0; - - if (DRXDAP_FASI_LONG_FORMAT(reg) || (flags != 0)) { - mm1[0] = (((reg << 1) & 0xFF) | 0x01); - mm1[1] = ((reg >> 16) & 0xFF); - mm1[2] = ((reg >> 24) & 0xFF) | flags; - mm1[3] = ((reg >> 7) & 0xFF); - len = 4; - } else { - mm1[0] = ((reg << 1) & 0xFF); - mm1[1] = (((reg >> 16) & 0x0F) | ((reg >> 18) & 0xF0)); - len = 2; - } - dprintk(2, "(0x%08x, 0x%02x)\n", reg, flags); - status = i2c_read(state, adr, mm1, len, mm2, 4); - if (status < 0) - return status; - if (data) - *data = mm2[0] | (mm2[1] << 8) | - (mm2[2] << 16) | (mm2[3] << 24); - - return 0; -} - -static int read32(struct drxk_state *state, u32 reg, u32 *data) -{ - return read32_flags(state, reg, data, 0); -} - -static int write16_flags(struct drxk_state *state, u32 reg, u16 data, u8 flags) -{ - u8 adr = state->demod_address, mm[6], len; - - if (state->single_master) - flags |= 0xC0; - if (DRXDAP_FASI_LONG_FORMAT(reg) || (flags != 0)) { - mm[0] = (((reg << 1) & 0xFF) | 0x01); - mm[1] = ((reg >> 16) & 0xFF); - mm[2] = ((reg >> 24) & 0xFF) | flags; - mm[3] = ((reg >> 7) & 0xFF); - len = 4; - } else { - mm[0] = ((reg << 1) & 0xFF); - mm[1] = (((reg >> 16) & 0x0F) | ((reg >> 18) & 0xF0)); - len = 2; - } - mm[len] = data & 0xff; - mm[len + 1] = (data >> 8) & 0xff; - - dprintk(2, "(0x%08x, 0x%04x, 0x%02x)\n", reg, data, flags); - return i2c_write(state, adr, mm, len + 2); -} - -static int write16(struct drxk_state *state, u32 reg, u16 data) -{ - return write16_flags(state, reg, data, 0); -} - -static int write32_flags(struct drxk_state *state, u32 reg, u32 data, u8 flags) -{ - u8 adr = state->demod_address, mm[8], len; - - if (state->single_master) - flags |= 0xC0; - if (DRXDAP_FASI_LONG_FORMAT(reg) || (flags != 0)) { - mm[0] = (((reg << 1) & 0xFF) | 0x01); - mm[1] = ((reg >> 16) & 0xFF); - mm[2] = ((reg >> 24) & 0xFF) | flags; - mm[3] = ((reg >> 7) & 0xFF); - len = 4; - } else { - mm[0] = ((reg << 1) & 0xFF); - mm[1] = (((reg >> 16) & 0x0F) | ((reg >> 18) & 0xF0)); - len = 2; - } - mm[len] = data & 0xff; - mm[len + 1] = (data >> 8) & 0xff; - mm[len + 2] = (data >> 16) & 0xff; - mm[len + 3] = (data >> 24) & 0xff; - dprintk(2, "(0x%08x, 0x%08x, 0x%02x)\n", reg, data, flags); - - return i2c_write(state, adr, mm, len + 4); -} - -static int write32(struct drxk_state *state, u32 reg, u32 data) -{ - return write32_flags(state, reg, data, 0); -} - -static int write_block(struct drxk_state *state, u32 Address, - const int BlockSize, const u8 pBlock[]) -{ - int status = 0, BlkSize = BlockSize; - u8 Flags = 0; - - if (state->single_master) - Flags |= 0xC0; - - while (BlkSize > 0) { - int Chunk = BlkSize > state->m_ChunkSize ? - state->m_ChunkSize : BlkSize; - u8 *AdrBuf = &state->Chunk[0]; - u32 AdrLength = 0; - - if (DRXDAP_FASI_LONG_FORMAT(Address) || (Flags != 0)) { - AdrBuf[0] = (((Address << 1) & 0xFF) | 0x01); - AdrBuf[1] = ((Address >> 16) & 0xFF); - AdrBuf[2] = ((Address >> 24) & 0xFF); - AdrBuf[3] = ((Address >> 7) & 0xFF); - AdrBuf[2] |= Flags; - AdrLength = 4; - if (Chunk == state->m_ChunkSize) - Chunk -= 2; - } else { - AdrBuf[0] = ((Address << 1) & 0xFF); - AdrBuf[1] = (((Address >> 16) & 0x0F) | - ((Address >> 18) & 0xF0)); - AdrLength = 2; - } - memcpy(&state->Chunk[AdrLength], pBlock, Chunk); - dprintk(2, "(0x%08x, 0x%02x)\n", Address, Flags); - if (debug > 1) { - int i; - if (pBlock) - for (i = 0; i < Chunk; i++) - printk(KERN_CONT " %02x", pBlock[i]); - printk(KERN_CONT "\n"); - } - status = i2c_write(state, state->demod_address, - &state->Chunk[0], Chunk + AdrLength); - if (status < 0) { - printk(KERN_ERR "drxk: %s: i2c write error at addr 0x%02x\n", - __func__, Address); - break; - } - pBlock += Chunk; - Address += (Chunk >> 1); - BlkSize -= Chunk; - } - return status; -} - -#ifndef DRXK_MAX_RETRIES_POWERUP -#define DRXK_MAX_RETRIES_POWERUP 20 -#endif - -int PowerUpDevice(struct drxk_state *state) -{ - int status; - u8 data = 0; - u16 retryCount = 0; - - dprintk(1, "\n"); - - status = i2c_read1(state, state->demod_address, &data); - if (status < 0) { - do { - data = 0; - status = i2c_write(state, state->demod_address, - &data, 1); - msleep(10); - retryCount++; - if (status < 0) - continue; - status = i2c_read1(state, state->demod_address, - &data); - } while (status < 0 && - (retryCount < DRXK_MAX_RETRIES_POWERUP)); - if (status < 0 && retryCount >= DRXK_MAX_RETRIES_POWERUP) - goto error; - } - - /* Make sure all clk domains are active */ - status = write16(state, SIO_CC_PWD_MODE__A, SIO_CC_PWD_MODE_LEVEL_NONE); - if (status < 0) - goto error; - status = write16(state, SIO_CC_UPDATE__A, SIO_CC_UPDATE_KEY); - if (status < 0) - goto error; - /* Enable pll lock tests */ - status = write16(state, SIO_CC_PLL_LOCK__A, 1); - if (status < 0) - goto error; - - state->m_currentPowerMode = DRX_POWER_UP; - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - - -static int init_state(struct drxk_state *state) -{ - /* - * FIXME: most (all?) of the values bellow should be moved into - * struct drxk_config, as they are probably board-specific - */ - u32 ulVSBIfAgcMode = DRXK_AGC_CTRL_AUTO; - u32 ulVSBIfAgcOutputLevel = 0; - u32 ulVSBIfAgcMinLevel = 0; - u32 ulVSBIfAgcMaxLevel = 0x7FFF; - u32 ulVSBIfAgcSpeed = 3; - - u32 ulVSBRfAgcMode = DRXK_AGC_CTRL_AUTO; - u32 ulVSBRfAgcOutputLevel = 0; - u32 ulVSBRfAgcMinLevel = 0; - u32 ulVSBRfAgcMaxLevel = 0x7FFF; - u32 ulVSBRfAgcSpeed = 3; - u32 ulVSBRfAgcTop = 9500; - u32 ulVSBRfAgcCutOffCurrent = 4000; - - u32 ulATVIfAgcMode = DRXK_AGC_CTRL_AUTO; - u32 ulATVIfAgcOutputLevel = 0; - u32 ulATVIfAgcMinLevel = 0; - u32 ulATVIfAgcMaxLevel = 0; - u32 ulATVIfAgcSpeed = 3; - - u32 ulATVRfAgcMode = DRXK_AGC_CTRL_OFF; - u32 ulATVRfAgcOutputLevel = 0; - u32 ulATVRfAgcMinLevel = 0; - u32 ulATVRfAgcMaxLevel = 0; - u32 ulATVRfAgcTop = 9500; - u32 ulATVRfAgcCutOffCurrent = 4000; - u32 ulATVRfAgcSpeed = 3; - - u32 ulQual83 = DEFAULT_MER_83; - u32 ulQual93 = DEFAULT_MER_93; - - u32 ulMpegLockTimeOut = DEFAULT_DRXK_MPEG_LOCK_TIMEOUT; - u32 ulDemodLockTimeOut = DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT; - - /* io_pad_cfg register (8 bit reg.) MSB bit is 1 (default value) */ - /* io_pad_cfg_mode output mode is drive always */ - /* io_pad_cfg_drive is set to power 2 (23 mA) */ - u32 ulGPIOCfg = 0x0113; - u32 ulInvertTSClock = 0; - u32 ulTSDataStrength = DRXK_MPEG_SERIAL_OUTPUT_PIN_DRIVE_STRENGTH; - u32 ulDVBTBitrate = 50000000; - u32 ulDVBCBitrate = DRXK_QAM_SYMBOLRATE_MAX * 8; - - u32 ulInsertRSByte = 0; - - u32 ulRfMirror = 1; - u32 ulPowerDown = 0; - - dprintk(1, "\n"); - - state->m_hasLNA = false; - state->m_hasDVBT = false; - state->m_hasDVBC = false; - state->m_hasATV = false; - state->m_hasOOB = false; - state->m_hasAudio = false; - - if (!state->m_ChunkSize) - state->m_ChunkSize = 124; - - state->m_oscClockFreq = 0; - state->m_smartAntInverted = false; - state->m_bPDownOpenBridge = false; - - /* real system clock frequency in kHz */ - state->m_sysClockFreq = 151875; - /* Timing div, 250ns/Psys */ - /* Timing div, = (delay (nano seconds) * sysclk (kHz))/ 1000 */ - state->m_HICfgTimingDiv = ((state->m_sysClockFreq / 1000) * - HI_I2C_DELAY) / 1000; - /* Clipping */ - if (state->m_HICfgTimingDiv > SIO_HI_RA_RAM_PAR_2_CFG_DIV__M) - state->m_HICfgTimingDiv = SIO_HI_RA_RAM_PAR_2_CFG_DIV__M; - state->m_HICfgWakeUpKey = (state->demod_address << 1); - /* port/bridge/power down ctrl */ - state->m_HICfgCtrl = SIO_HI_RA_RAM_PAR_5_CFG_SLV0_SLAVE; - - state->m_bPowerDown = (ulPowerDown != 0); - - state->m_DRXK_A1_PATCH_CODE = false; - state->m_DRXK_A1_ROM_CODE = false; - state->m_DRXK_A2_ROM_CODE = false; - state->m_DRXK_A3_ROM_CODE = false; - state->m_DRXK_A2_PATCH_CODE = false; - state->m_DRXK_A3_PATCH_CODE = false; - - /* Init AGC and PGA parameters */ - /* VSB IF */ - state->m_vsbIfAgcCfg.ctrlMode = (ulVSBIfAgcMode); - state->m_vsbIfAgcCfg.outputLevel = (ulVSBIfAgcOutputLevel); - state->m_vsbIfAgcCfg.minOutputLevel = (ulVSBIfAgcMinLevel); - state->m_vsbIfAgcCfg.maxOutputLevel = (ulVSBIfAgcMaxLevel); - state->m_vsbIfAgcCfg.speed = (ulVSBIfAgcSpeed); - state->m_vsbPgaCfg = 140; - - /* VSB RF */ - state->m_vsbRfAgcCfg.ctrlMode = (ulVSBRfAgcMode); - state->m_vsbRfAgcCfg.outputLevel = (ulVSBRfAgcOutputLevel); - state->m_vsbRfAgcCfg.minOutputLevel = (ulVSBRfAgcMinLevel); - state->m_vsbRfAgcCfg.maxOutputLevel = (ulVSBRfAgcMaxLevel); - state->m_vsbRfAgcCfg.speed = (ulVSBRfAgcSpeed); - state->m_vsbRfAgcCfg.top = (ulVSBRfAgcTop); - state->m_vsbRfAgcCfg.cutOffCurrent = (ulVSBRfAgcCutOffCurrent); - state->m_vsbPreSawCfg.reference = 0x07; - state->m_vsbPreSawCfg.usePreSaw = true; - - state->m_Quality83percent = DEFAULT_MER_83; - state->m_Quality93percent = DEFAULT_MER_93; - if (ulQual93 <= 500 && ulQual83 < ulQual93) { - state->m_Quality83percent = ulQual83; - state->m_Quality93percent = ulQual93; - } - - /* ATV IF */ - state->m_atvIfAgcCfg.ctrlMode = (ulATVIfAgcMode); - state->m_atvIfAgcCfg.outputLevel = (ulATVIfAgcOutputLevel); - state->m_atvIfAgcCfg.minOutputLevel = (ulATVIfAgcMinLevel); - state->m_atvIfAgcCfg.maxOutputLevel = (ulATVIfAgcMaxLevel); - state->m_atvIfAgcCfg.speed = (ulATVIfAgcSpeed); - - /* ATV RF */ - state->m_atvRfAgcCfg.ctrlMode = (ulATVRfAgcMode); - state->m_atvRfAgcCfg.outputLevel = (ulATVRfAgcOutputLevel); - state->m_atvRfAgcCfg.minOutputLevel = (ulATVRfAgcMinLevel); - state->m_atvRfAgcCfg.maxOutputLevel = (ulATVRfAgcMaxLevel); - state->m_atvRfAgcCfg.speed = (ulATVRfAgcSpeed); - state->m_atvRfAgcCfg.top = (ulATVRfAgcTop); - state->m_atvRfAgcCfg.cutOffCurrent = (ulATVRfAgcCutOffCurrent); - state->m_atvPreSawCfg.reference = 0x04; - state->m_atvPreSawCfg.usePreSaw = true; - - - /* DVBT RF */ - state->m_dvbtRfAgcCfg.ctrlMode = DRXK_AGC_CTRL_OFF; - state->m_dvbtRfAgcCfg.outputLevel = 0; - state->m_dvbtRfAgcCfg.minOutputLevel = 0; - state->m_dvbtRfAgcCfg.maxOutputLevel = 0xFFFF; - state->m_dvbtRfAgcCfg.top = 0x2100; - state->m_dvbtRfAgcCfg.cutOffCurrent = 4000; - state->m_dvbtRfAgcCfg.speed = 1; - - - /* DVBT IF */ - state->m_dvbtIfAgcCfg.ctrlMode = DRXK_AGC_CTRL_AUTO; - state->m_dvbtIfAgcCfg.outputLevel = 0; - state->m_dvbtIfAgcCfg.minOutputLevel = 0; - state->m_dvbtIfAgcCfg.maxOutputLevel = 9000; - state->m_dvbtIfAgcCfg.top = 13424; - state->m_dvbtIfAgcCfg.cutOffCurrent = 0; - state->m_dvbtIfAgcCfg.speed = 3; - state->m_dvbtIfAgcCfg.FastClipCtrlDelay = 30; - state->m_dvbtIfAgcCfg.IngainTgtMax = 30000; - /* state->m_dvbtPgaCfg = 140; */ - - state->m_dvbtPreSawCfg.reference = 4; - state->m_dvbtPreSawCfg.usePreSaw = false; - - /* QAM RF */ - state->m_qamRfAgcCfg.ctrlMode = DRXK_AGC_CTRL_OFF; - state->m_qamRfAgcCfg.outputLevel = 0; - state->m_qamRfAgcCfg.minOutputLevel = 6023; - state->m_qamRfAgcCfg.maxOutputLevel = 27000; - state->m_qamRfAgcCfg.top = 0x2380; - state->m_qamRfAgcCfg.cutOffCurrent = 4000; - state->m_qamRfAgcCfg.speed = 3; - - /* QAM IF */ - state->m_qamIfAgcCfg.ctrlMode = DRXK_AGC_CTRL_AUTO; - state->m_qamIfAgcCfg.outputLevel = 0; - state->m_qamIfAgcCfg.minOutputLevel = 0; - state->m_qamIfAgcCfg.maxOutputLevel = 9000; - state->m_qamIfAgcCfg.top = 0x0511; - state->m_qamIfAgcCfg.cutOffCurrent = 0; - state->m_qamIfAgcCfg.speed = 3; - state->m_qamIfAgcCfg.IngainTgtMax = 5119; - state->m_qamIfAgcCfg.FastClipCtrlDelay = 50; - - state->m_qamPgaCfg = 140; - state->m_qamPreSawCfg.reference = 4; - state->m_qamPreSawCfg.usePreSaw = false; - - state->m_OperationMode = OM_NONE; - state->m_DrxkState = DRXK_UNINITIALIZED; - - /* MPEG output configuration */ - state->m_enableMPEGOutput = true; /* If TRUE; enable MPEG ouput */ - state->m_insertRSByte = false; /* If TRUE; insert RS byte */ - state->m_invertDATA = false; /* If TRUE; invert DATA signals */ - state->m_invertERR = false; /* If TRUE; invert ERR signal */ - state->m_invertSTR = false; /* If TRUE; invert STR signals */ - state->m_invertVAL = false; /* If TRUE; invert VAL signals */ - state->m_invertCLK = (ulInvertTSClock != 0); /* If TRUE; invert CLK signals */ - - /* If TRUE; static MPEG clockrate will be used; - otherwise clockrate will adapt to the bitrate of the TS */ - - state->m_DVBTBitrate = ulDVBTBitrate; - state->m_DVBCBitrate = ulDVBCBitrate; - - state->m_TSDataStrength = (ulTSDataStrength & 0x07); - - /* Maximum bitrate in b/s in case static clockrate is selected */ - state->m_mpegTsStaticBitrate = 19392658; - state->m_disableTEIhandling = false; - - if (ulInsertRSByte) - state->m_insertRSByte = true; - - state->m_MpegLockTimeOut = DEFAULT_DRXK_MPEG_LOCK_TIMEOUT; - if (ulMpegLockTimeOut < 10000) - state->m_MpegLockTimeOut = ulMpegLockTimeOut; - state->m_DemodLockTimeOut = DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT; - if (ulDemodLockTimeOut < 10000) - state->m_DemodLockTimeOut = ulDemodLockTimeOut; - - /* QAM defaults */ - state->m_Constellation = DRX_CONSTELLATION_AUTO; - state->m_qamInterleaveMode = DRXK_QAM_I12_J17; - state->m_fecRsPlen = 204 * 8; /* fecRsPlen annex A */ - state->m_fecRsPrescale = 1; - - state->m_sqiSpeed = DRXK_DVBT_SQI_SPEED_MEDIUM; - state->m_agcFastClipCtrlDelay = 0; - - state->m_GPIOCfg = (ulGPIOCfg); - - state->m_bPowerDown = false; - state->m_currentPowerMode = DRX_POWER_DOWN; - - state->m_rfmirror = (ulRfMirror == 0); - state->m_IfAgcPol = false; - return 0; -} - -static int DRXX_Open(struct drxk_state *state) -{ - int status = 0; - u32 jtag = 0; - u16 bid = 0; - u16 key = 0; - - dprintk(1, "\n"); - /* stop lock indicator process */ - status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); - if (status < 0) - goto error; - /* Check device id */ - status = read16(state, SIO_TOP_COMM_KEY__A, &key); - if (status < 0) - goto error; - status = write16(state, SIO_TOP_COMM_KEY__A, SIO_TOP_COMM_KEY_KEY); - if (status < 0) - goto error; - status = read32(state, SIO_TOP_JTAGID_LO__A, &jtag); - if (status < 0) - goto error; - status = read16(state, SIO_PDR_UIO_IN_HI__A, &bid); - if (status < 0) - goto error; - status = write16(state, SIO_TOP_COMM_KEY__A, key); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int GetDeviceCapabilities(struct drxk_state *state) -{ - u16 sioPdrOhwCfg = 0; - u32 sioTopJtagidLo = 0; - int status; - const char *spin = ""; - - dprintk(1, "\n"); - - /* driver 0.9.0 */ - /* stop lock indicator process */ - status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); - if (status < 0) - goto error; - status = write16(state, SIO_TOP_COMM_KEY__A, 0xFABA); - if (status < 0) - goto error; - status = read16(state, SIO_PDR_OHW_CFG__A, &sioPdrOhwCfg); - if (status < 0) - goto error; - status = write16(state, SIO_TOP_COMM_KEY__A, 0x0000); - if (status < 0) - goto error; - - switch ((sioPdrOhwCfg & SIO_PDR_OHW_CFG_FREF_SEL__M)) { - case 0: - /* ignore (bypass ?) */ - break; - case 1: - /* 27 MHz */ - state->m_oscClockFreq = 27000; - break; - case 2: - /* 20.25 MHz */ - state->m_oscClockFreq = 20250; - break; - case 3: - /* 4 MHz */ - state->m_oscClockFreq = 20250; - break; - default: - printk(KERN_ERR "drxk: Clock Frequency is unkonwn\n"); - return -EINVAL; - } - /* - Determine device capabilities - Based on pinning v14 - */ - status = read32(state, SIO_TOP_JTAGID_LO__A, &sioTopJtagidLo); - if (status < 0) - goto error; - - printk(KERN_INFO "drxk: status = 0x%08x\n", sioTopJtagidLo); - - /* driver 0.9.0 */ - switch ((sioTopJtagidLo >> 29) & 0xF) { - case 0: - state->m_deviceSpin = DRXK_SPIN_A1; - spin = "A1"; - break; - case 2: - state->m_deviceSpin = DRXK_SPIN_A2; - spin = "A2"; - break; - case 3: - state->m_deviceSpin = DRXK_SPIN_A3; - spin = "A3"; - break; - default: - state->m_deviceSpin = DRXK_SPIN_UNKNOWN; - status = -EINVAL; - printk(KERN_ERR "drxk: Spin %d unknown\n", - (sioTopJtagidLo >> 29) & 0xF); - goto error2; - } - switch ((sioTopJtagidLo >> 12) & 0xFF) { - case 0x13: - /* typeId = DRX3913K_TYPE_ID */ - state->m_hasLNA = false; - state->m_hasOOB = false; - state->m_hasATV = false; - state->m_hasAudio = false; - state->m_hasDVBT = true; - state->m_hasDVBC = true; - state->m_hasSAWSW = true; - state->m_hasGPIO2 = false; - state->m_hasGPIO1 = false; - state->m_hasIRQN = false; - break; - case 0x15: - /* typeId = DRX3915K_TYPE_ID */ - state->m_hasLNA = false; - state->m_hasOOB = false; - state->m_hasATV = true; - state->m_hasAudio = false; - state->m_hasDVBT = true; - state->m_hasDVBC = false; - state->m_hasSAWSW = true; - state->m_hasGPIO2 = true; - state->m_hasGPIO1 = true; - state->m_hasIRQN = false; - break; - case 0x16: - /* typeId = DRX3916K_TYPE_ID */ - state->m_hasLNA = false; - state->m_hasOOB = false; - state->m_hasATV = true; - state->m_hasAudio = false; - state->m_hasDVBT = true; - state->m_hasDVBC = false; - state->m_hasSAWSW = true; - state->m_hasGPIO2 = true; - state->m_hasGPIO1 = true; - state->m_hasIRQN = false; - break; - case 0x18: - /* typeId = DRX3918K_TYPE_ID */ - state->m_hasLNA = false; - state->m_hasOOB = false; - state->m_hasATV = true; - state->m_hasAudio = true; - state->m_hasDVBT = true; - state->m_hasDVBC = false; - state->m_hasSAWSW = true; - state->m_hasGPIO2 = true; - state->m_hasGPIO1 = true; - state->m_hasIRQN = false; - break; - case 0x21: - /* typeId = DRX3921K_TYPE_ID */ - state->m_hasLNA = false; - state->m_hasOOB = false; - state->m_hasATV = true; - state->m_hasAudio = true; - state->m_hasDVBT = true; - state->m_hasDVBC = true; - state->m_hasSAWSW = true; - state->m_hasGPIO2 = true; - state->m_hasGPIO1 = true; - state->m_hasIRQN = false; - break; - case 0x23: - /* typeId = DRX3923K_TYPE_ID */ - state->m_hasLNA = false; - state->m_hasOOB = false; - state->m_hasATV = true; - state->m_hasAudio = true; - state->m_hasDVBT = true; - state->m_hasDVBC = true; - state->m_hasSAWSW = true; - state->m_hasGPIO2 = true; - state->m_hasGPIO1 = true; - state->m_hasIRQN = false; - break; - case 0x25: - /* typeId = DRX3925K_TYPE_ID */ - state->m_hasLNA = false; - state->m_hasOOB = false; - state->m_hasATV = true; - state->m_hasAudio = true; - state->m_hasDVBT = true; - state->m_hasDVBC = true; - state->m_hasSAWSW = true; - state->m_hasGPIO2 = true; - state->m_hasGPIO1 = true; - state->m_hasIRQN = false; - break; - case 0x26: - /* typeId = DRX3926K_TYPE_ID */ - state->m_hasLNA = false; - state->m_hasOOB = false; - state->m_hasATV = true; - state->m_hasAudio = false; - state->m_hasDVBT = true; - state->m_hasDVBC = true; - state->m_hasSAWSW = true; - state->m_hasGPIO2 = true; - state->m_hasGPIO1 = true; - state->m_hasIRQN = false; - break; - default: - printk(KERN_ERR "drxk: DeviceID 0x%02x not supported\n", - ((sioTopJtagidLo >> 12) & 0xFF)); - status = -EINVAL; - goto error2; - } - - printk(KERN_INFO - "drxk: detected a drx-39%02xk, spin %s, xtal %d.%03d MHz\n", - ((sioTopJtagidLo >> 12) & 0xFF), spin, - state->m_oscClockFreq / 1000, - state->m_oscClockFreq % 1000); - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - -error2: - return status; -} - -static int HI_Command(struct drxk_state *state, u16 cmd, u16 *pResult) -{ - int status; - bool powerdown_cmd; - - dprintk(1, "\n"); - - /* Write command */ - status = write16(state, SIO_HI_RA_RAM_CMD__A, cmd); - if (status < 0) - goto error; - if (cmd == SIO_HI_RA_RAM_CMD_RESET) - msleep(1); - - powerdown_cmd = - (bool) ((cmd == SIO_HI_RA_RAM_CMD_CONFIG) && - ((state->m_HICfgCtrl) & - SIO_HI_RA_RAM_PAR_5_CFG_SLEEP__M) == - SIO_HI_RA_RAM_PAR_5_CFG_SLEEP_ZZZ); - if (powerdown_cmd == false) { - /* Wait until command rdy */ - u32 retryCount = 0; - u16 waitCmd; - - do { - msleep(1); - retryCount += 1; - status = read16(state, SIO_HI_RA_RAM_CMD__A, - &waitCmd); - } while ((status < 0) && (retryCount < DRXK_MAX_RETRIES) - && (waitCmd != 0)); - if (status < 0) - goto error; - status = read16(state, SIO_HI_RA_RAM_RES__A, pResult); - } -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -static int HI_CfgCommand(struct drxk_state *state) -{ - int status; - - dprintk(1, "\n"); - - mutex_lock(&state->mutex); - - status = write16(state, SIO_HI_RA_RAM_PAR_6__A, state->m_HICfgTimeout); - if (status < 0) - goto error; - status = write16(state, SIO_HI_RA_RAM_PAR_5__A, state->m_HICfgCtrl); - if (status < 0) - goto error; - status = write16(state, SIO_HI_RA_RAM_PAR_4__A, state->m_HICfgWakeUpKey); - if (status < 0) - goto error; - status = write16(state, SIO_HI_RA_RAM_PAR_3__A, state->m_HICfgBridgeDelay); - if (status < 0) - goto error; - status = write16(state, SIO_HI_RA_RAM_PAR_2__A, state->m_HICfgTimingDiv); - if (status < 0) - goto error; - status = write16(state, SIO_HI_RA_RAM_PAR_1__A, SIO_HI_RA_RAM_PAR_1_PAR1_SEC_KEY); - if (status < 0) - goto error; - status = HI_Command(state, SIO_HI_RA_RAM_CMD_CONFIG, 0); - if (status < 0) - goto error; - - state->m_HICfgCtrl &= ~SIO_HI_RA_RAM_PAR_5_CFG_SLEEP_ZZZ; -error: - mutex_unlock(&state->mutex); - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int InitHI(struct drxk_state *state) -{ - dprintk(1, "\n"); - - state->m_HICfgWakeUpKey = (state->demod_address << 1); - state->m_HICfgTimeout = 0x96FF; - /* port/bridge/power down ctrl */ - state->m_HICfgCtrl = SIO_HI_RA_RAM_PAR_5_CFG_SLV0_SLAVE; - - return HI_CfgCommand(state); -} - -static int MPEGTSConfigurePins(struct drxk_state *state, bool mpegEnable) -{ - int status = -1; - u16 sioPdrMclkCfg = 0; - u16 sioPdrMdxCfg = 0; - u16 err_cfg = 0; - - dprintk(1, ": mpeg %s, %s mode\n", - mpegEnable ? "enable" : "disable", - state->m_enableParallel ? "parallel" : "serial"); - - /* stop lock indicator process */ - status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); - if (status < 0) - goto error; - - /* MPEG TS pad configuration */ - status = write16(state, SIO_TOP_COMM_KEY__A, 0xFABA); - if (status < 0) - goto error; - - if (mpegEnable == false) { - /* Set MPEG TS pads to inputmode */ - status = write16(state, SIO_PDR_MSTRT_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MERR_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MCLK_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MVAL_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD0_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD1_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD2_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD3_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD4_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD5_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD6_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD7_CFG__A, 0x0000); - if (status < 0) - goto error; - } else { - /* Enable MPEG output */ - sioPdrMdxCfg = - ((state->m_TSDataStrength << - SIO_PDR_MD0_CFG_DRIVE__B) | 0x0003); - sioPdrMclkCfg = ((state->m_TSClockkStrength << - SIO_PDR_MCLK_CFG_DRIVE__B) | - 0x0003); - - status = write16(state, SIO_PDR_MSTRT_CFG__A, sioPdrMdxCfg); - if (status < 0) - goto error; - - if (state->enable_merr_cfg) - err_cfg = sioPdrMdxCfg; - - status = write16(state, SIO_PDR_MERR_CFG__A, err_cfg); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MVAL_CFG__A, err_cfg); - if (status < 0) - goto error; - - if (state->m_enableParallel == true) { - /* paralel -> enable MD1 to MD7 */ - status = write16(state, SIO_PDR_MD1_CFG__A, sioPdrMdxCfg); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD2_CFG__A, sioPdrMdxCfg); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD3_CFG__A, sioPdrMdxCfg); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD4_CFG__A, sioPdrMdxCfg); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD5_CFG__A, sioPdrMdxCfg); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD6_CFG__A, sioPdrMdxCfg); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD7_CFG__A, sioPdrMdxCfg); - if (status < 0) - goto error; - } else { - sioPdrMdxCfg = ((state->m_TSDataStrength << - SIO_PDR_MD0_CFG_DRIVE__B) - | 0x0003); - /* serial -> disable MD1 to MD7 */ - status = write16(state, SIO_PDR_MD1_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD2_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD3_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD4_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD5_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD6_CFG__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD7_CFG__A, 0x0000); - if (status < 0) - goto error; - } - status = write16(state, SIO_PDR_MCLK_CFG__A, sioPdrMclkCfg); - if (status < 0) - goto error; - status = write16(state, SIO_PDR_MD0_CFG__A, sioPdrMdxCfg); - if (status < 0) - goto error; - } - /* Enable MB output over MPEG pads and ctl input */ - status = write16(state, SIO_PDR_MON_CFG__A, 0x0000); - if (status < 0) - goto error; - /* Write nomagic word to enable pdr reg write */ - status = write16(state, SIO_TOP_COMM_KEY__A, 0x0000); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int MPEGTSDisable(struct drxk_state *state) -{ - dprintk(1, "\n"); - - return MPEGTSConfigurePins(state, false); -} - -static int BLChainCmd(struct drxk_state *state, - u16 romOffset, u16 nrOfElements, u32 timeOut) -{ - u16 blStatus = 0; - int status; - unsigned long end; - - dprintk(1, "\n"); - mutex_lock(&state->mutex); - status = write16(state, SIO_BL_MODE__A, SIO_BL_MODE_CHAIN); - if (status < 0) - goto error; - status = write16(state, SIO_BL_CHAIN_ADDR__A, romOffset); - if (status < 0) - goto error; - status = write16(state, SIO_BL_CHAIN_LEN__A, nrOfElements); - if (status < 0) - goto error; - status = write16(state, SIO_BL_ENABLE__A, SIO_BL_ENABLE_ON); - if (status < 0) - goto error; - - end = jiffies + msecs_to_jiffies(timeOut); - do { - msleep(1); - status = read16(state, SIO_BL_STATUS__A, &blStatus); - if (status < 0) - goto error; - } while ((blStatus == 0x1) && - ((time_is_after_jiffies(end)))); - - if (blStatus == 0x1) { - printk(KERN_ERR "drxk: SIO not ready\n"); - status = -EINVAL; - goto error2; - } -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); -error2: - mutex_unlock(&state->mutex); - return status; -} - - -static int DownloadMicrocode(struct drxk_state *state, - const u8 pMCImage[], u32 Length) -{ - const u8 *pSrc = pMCImage; - u32 Address; - u16 nBlocks; - u16 BlockSize; - u32 offset = 0; - u32 i; - int status = 0; - - dprintk(1, "\n"); - - /* down the drain (we don't care about MAGIC_WORD) */ -#if 0 - /* For future reference */ - Drain = (pSrc[0] << 8) | pSrc[1]; -#endif - pSrc += sizeof(u16); - offset += sizeof(u16); - nBlocks = (pSrc[0] << 8) | pSrc[1]; - pSrc += sizeof(u16); - offset += sizeof(u16); - - for (i = 0; i < nBlocks; i += 1) { - Address = (pSrc[0] << 24) | (pSrc[1] << 16) | - (pSrc[2] << 8) | pSrc[3]; - pSrc += sizeof(u32); - offset += sizeof(u32); - - BlockSize = ((pSrc[0] << 8) | pSrc[1]) * sizeof(u16); - pSrc += sizeof(u16); - offset += sizeof(u16); - -#if 0 - /* For future reference */ - Flags = (pSrc[0] << 8) | pSrc[1]; -#endif - pSrc += sizeof(u16); - offset += sizeof(u16); - -#if 0 - /* For future reference */ - BlockCRC = (pSrc[0] << 8) | pSrc[1]; -#endif - pSrc += sizeof(u16); - offset += sizeof(u16); - - if (offset + BlockSize > Length) { - printk(KERN_ERR "drxk: Firmware is corrupted.\n"); - return -EINVAL; - } - - status = write_block(state, Address, BlockSize, pSrc); - if (status < 0) { - printk(KERN_ERR "drxk: Error %d while loading firmware\n", status); - break; - } - pSrc += BlockSize; - offset += BlockSize; - } - return status; -} - -static int DVBTEnableOFDMTokenRing(struct drxk_state *state, bool enable) -{ - int status; - u16 data = 0; - u16 desiredCtrl = SIO_OFDM_SH_OFDM_RING_ENABLE_ON; - u16 desiredStatus = SIO_OFDM_SH_OFDM_RING_STATUS_ENABLED; - unsigned long end; - - dprintk(1, "\n"); - - if (enable == false) { - desiredCtrl = SIO_OFDM_SH_OFDM_RING_ENABLE_OFF; - desiredStatus = SIO_OFDM_SH_OFDM_RING_STATUS_DOWN; - } - - status = read16(state, SIO_OFDM_SH_OFDM_RING_STATUS__A, &data); - if (status >= 0 && data == desiredStatus) { - /* tokenring already has correct status */ - return status; - } - /* Disable/enable dvbt tokenring bridge */ - status = write16(state, SIO_OFDM_SH_OFDM_RING_ENABLE__A, desiredCtrl); - - end = jiffies + msecs_to_jiffies(DRXK_OFDM_TR_SHUTDOWN_TIMEOUT); - do { - status = read16(state, SIO_OFDM_SH_OFDM_RING_STATUS__A, &data); - if ((status >= 0 && data == desiredStatus) || time_is_after_jiffies(end)) - break; - msleep(1); - } while (1); - if (data != desiredStatus) { - printk(KERN_ERR "drxk: SIO not ready\n"); - return -EINVAL; - } - return status; -} - -static int MPEGTSStop(struct drxk_state *state) -{ - int status = 0; - u16 fecOcSncMode = 0; - u16 fecOcIprMode = 0; - - dprintk(1, "\n"); - - /* Gracefull shutdown (byte boundaries) */ - status = read16(state, FEC_OC_SNC_MODE__A, &fecOcSncMode); - if (status < 0) - goto error; - fecOcSncMode |= FEC_OC_SNC_MODE_SHUTDOWN__M; - status = write16(state, FEC_OC_SNC_MODE__A, fecOcSncMode); - if (status < 0) - goto error; - - /* Suppress MCLK during absence of data */ - status = read16(state, FEC_OC_IPR_MODE__A, &fecOcIprMode); - if (status < 0) - goto error; - fecOcIprMode |= FEC_OC_IPR_MODE_MCLK_DIS_DAT_ABS__M; - status = write16(state, FEC_OC_IPR_MODE__A, fecOcIprMode); - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -static int scu_command(struct drxk_state *state, - u16 cmd, u8 parameterLen, - u16 *parameter, u8 resultLen, u16 *result) -{ -#if (SCU_RAM_PARAM_0__A - SCU_RAM_PARAM_15__A) != 15 -#error DRXK register mapping no longer compatible with this routine! -#endif - u16 curCmd = 0; - int status = -EINVAL; - unsigned long end; - u8 buffer[34]; - int cnt = 0, ii; - const char *p; - char errname[30]; - - dprintk(1, "\n"); - - if ((cmd == 0) || ((parameterLen > 0) && (parameter == NULL)) || - ((resultLen > 0) && (result == NULL))) { - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; - } - - mutex_lock(&state->mutex); - - /* assume that the command register is ready - since it is checked afterwards */ - for (ii = parameterLen - 1; ii >= 0; ii -= 1) { - buffer[cnt++] = (parameter[ii] & 0xFF); - buffer[cnt++] = ((parameter[ii] >> 8) & 0xFF); - } - buffer[cnt++] = (cmd & 0xFF); - buffer[cnt++] = ((cmd >> 8) & 0xFF); - - write_block(state, SCU_RAM_PARAM_0__A - - (parameterLen - 1), cnt, buffer); - /* Wait until SCU has processed command */ - end = jiffies + msecs_to_jiffies(DRXK_MAX_WAITTIME); - do { - msleep(1); - status = read16(state, SCU_RAM_COMMAND__A, &curCmd); - if (status < 0) - goto error; - } while (!(curCmd == DRX_SCU_READY) && (time_is_after_jiffies(end))); - if (curCmd != DRX_SCU_READY) { - printk(KERN_ERR "drxk: SCU not ready\n"); - status = -EIO; - goto error2; - } - /* read results */ - if ((resultLen > 0) && (result != NULL)) { - s16 err; - int ii; - - for (ii = resultLen - 1; ii >= 0; ii -= 1) { - status = read16(state, SCU_RAM_PARAM_0__A - ii, &result[ii]); - if (status < 0) - goto error; - } - - /* Check if an error was reported by SCU */ - err = (s16)result[0]; - if (err >= 0) - goto error; - - /* check for the known error codes */ - switch (err) { - case SCU_RESULT_UNKCMD: - p = "SCU_RESULT_UNKCMD"; - break; - case SCU_RESULT_UNKSTD: - p = "SCU_RESULT_UNKSTD"; - break; - case SCU_RESULT_SIZE: - p = "SCU_RESULT_SIZE"; - break; - case SCU_RESULT_INVPAR: - p = "SCU_RESULT_INVPAR"; - break; - default: /* Other negative values are errors */ - sprintf(errname, "ERROR: %d\n", err); - p = errname; - } - printk(KERN_ERR "drxk: %s while sending cmd 0x%04x with params:", p, cmd); - print_hex_dump_bytes("drxk: ", DUMP_PREFIX_NONE, buffer, cnt); - status = -EINVAL; - goto error2; - } - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); -error2: - mutex_unlock(&state->mutex); - return status; -} - -static int SetIqmAf(struct drxk_state *state, bool active) -{ - u16 data = 0; - int status; - - dprintk(1, "\n"); - - /* Configure IQM */ - status = read16(state, IQM_AF_STDBY__A, &data); - if (status < 0) - goto error; - - if (!active) { - data |= (IQM_AF_STDBY_STDBY_ADC_STANDBY - | IQM_AF_STDBY_STDBY_AMP_STANDBY - | IQM_AF_STDBY_STDBY_PD_STANDBY - | IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY - | IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY); - } else { - data &= ((~IQM_AF_STDBY_STDBY_ADC_STANDBY) - & (~IQM_AF_STDBY_STDBY_AMP_STANDBY) - & (~IQM_AF_STDBY_STDBY_PD_STANDBY) - & (~IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY) - & (~IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY) - ); - } - status = write16(state, IQM_AF_STDBY__A, data); - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int CtrlPowerMode(struct drxk_state *state, enum DRXPowerMode *mode) -{ - int status = 0; - u16 sioCcPwdMode = 0; - - dprintk(1, "\n"); - - /* Check arguments */ - if (mode == NULL) - return -EINVAL; - - switch (*mode) { - case DRX_POWER_UP: - sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_NONE; - break; - case DRXK_POWER_DOWN_OFDM: - sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_OFDM; - break; - case DRXK_POWER_DOWN_CORE: - sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_CLOCK; - break; - case DRXK_POWER_DOWN_PLL: - sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_PLL; - break; - case DRX_POWER_DOWN: - sioCcPwdMode = SIO_CC_PWD_MODE_LEVEL_OSC; - break; - default: - /* Unknow sleep mode */ - return -EINVAL; - } - - /* If already in requested power mode, do nothing */ - if (state->m_currentPowerMode == *mode) - return 0; - - /* For next steps make sure to start from DRX_POWER_UP mode */ - if (state->m_currentPowerMode != DRX_POWER_UP) { - status = PowerUpDevice(state); - if (status < 0) - goto error; - status = DVBTEnableOFDMTokenRing(state, true); - if (status < 0) - goto error; - } - - if (*mode == DRX_POWER_UP) { - /* Restore analog & pin configuartion */ - } else { - /* Power down to requested mode */ - /* Backup some register settings */ - /* Set pins with possible pull-ups connected - to them in input mode */ - /* Analog power down */ - /* ADC power down */ - /* Power down device */ - /* stop all comm_exec */ - /* Stop and power down previous standard */ - switch (state->m_OperationMode) { - case OM_DVBT: - status = MPEGTSStop(state); - if (status < 0) - goto error; - status = PowerDownDVBT(state, false); - if (status < 0) - goto error; - break; - case OM_QAM_ITU_A: - case OM_QAM_ITU_C: - status = MPEGTSStop(state); - if (status < 0) - goto error; - status = PowerDownQAM(state); - if (status < 0) - goto error; - break; - default: - break; - } - status = DVBTEnableOFDMTokenRing(state, false); - if (status < 0) - goto error; - status = write16(state, SIO_CC_PWD_MODE__A, sioCcPwdMode); - if (status < 0) - goto error; - status = write16(state, SIO_CC_UPDATE__A, SIO_CC_UPDATE_KEY); - if (status < 0) - goto error; - - if (*mode != DRXK_POWER_DOWN_OFDM) { - state->m_HICfgCtrl |= - SIO_HI_RA_RAM_PAR_5_CFG_SLEEP_ZZZ; - status = HI_CfgCommand(state); - if (status < 0) - goto error; - } - } - state->m_currentPowerMode = *mode; - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -static int PowerDownDVBT(struct drxk_state *state, bool setPowerMode) -{ - enum DRXPowerMode powerMode = DRXK_POWER_DOWN_OFDM; - u16 cmdResult = 0; - u16 data = 0; - int status; - - dprintk(1, "\n"); - - status = read16(state, SCU_COMM_EXEC__A, &data); - if (status < 0) - goto error; - if (data == SCU_COMM_EXEC_ACTIVE) { - /* Send OFDM stop command */ - status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_STOP, 0, NULL, 1, &cmdResult); - if (status < 0) - goto error; - /* Send OFDM reset command */ - status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_RESET, 0, NULL, 1, &cmdResult); - if (status < 0) - goto error; - } - - /* Reset datapath for OFDM, processors first */ - status = write16(state, OFDM_SC_COMM_EXEC__A, OFDM_SC_COMM_EXEC_STOP); - if (status < 0) - goto error; - status = write16(state, OFDM_LC_COMM_EXEC__A, OFDM_LC_COMM_EXEC_STOP); - if (status < 0) - goto error; - status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_STOP); - if (status < 0) - goto error; - - /* powerdown AFE */ - status = SetIqmAf(state, false); - if (status < 0) - goto error; - - /* powerdown to OFDM mode */ - if (setPowerMode) { - status = CtrlPowerMode(state, &powerMode); - if (status < 0) - goto error; - } -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int SetOperationMode(struct drxk_state *state, - enum OperationMode oMode) -{ - int status = 0; - - dprintk(1, "\n"); - /* - Stop and power down previous standard - TODO investigate total power down instead of partial - power down depending on "previous" standard. - */ - - /* disable HW lock indicator */ - status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); - if (status < 0) - goto error; - - /* Device is already at the required mode */ - if (state->m_OperationMode == oMode) - return 0; - - switch (state->m_OperationMode) { - /* OM_NONE was added for start up */ - case OM_NONE: - break; - case OM_DVBT: - status = MPEGTSStop(state); - if (status < 0) - goto error; - status = PowerDownDVBT(state, true); - if (status < 0) - goto error; - state->m_OperationMode = OM_NONE; - break; - case OM_QAM_ITU_A: /* fallthrough */ - case OM_QAM_ITU_C: - status = MPEGTSStop(state); - if (status < 0) - goto error; - status = PowerDownQAM(state); - if (status < 0) - goto error; - state->m_OperationMode = OM_NONE; - break; - case OM_QAM_ITU_B: - default: - status = -EINVAL; - goto error; - } - - /* - Power up new standard - */ - switch (oMode) { - case OM_DVBT: - dprintk(1, ": DVB-T\n"); - state->m_OperationMode = oMode; - status = SetDVBTStandard(state, oMode); - if (status < 0) - goto error; - break; - case OM_QAM_ITU_A: /* fallthrough */ - case OM_QAM_ITU_C: - dprintk(1, ": DVB-C Annex %c\n", - (state->m_OperationMode == OM_QAM_ITU_A) ? 'A' : 'C'); - state->m_OperationMode = oMode; - status = SetQAMStandard(state, oMode); - if (status < 0) - goto error; - break; - case OM_QAM_ITU_B: - default: - status = -EINVAL; - } -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int Start(struct drxk_state *state, s32 offsetFreq, - s32 IntermediateFrequency) -{ - int status = -EINVAL; - - u16 IFreqkHz; - s32 OffsetkHz = offsetFreq / 1000; - - dprintk(1, "\n"); - if (state->m_DrxkState != DRXK_STOPPED && - state->m_DrxkState != DRXK_DTV_STARTED) - goto error; - - state->m_bMirrorFreqSpect = (state->props.inversion == INVERSION_ON); - - if (IntermediateFrequency < 0) { - state->m_bMirrorFreqSpect = !state->m_bMirrorFreqSpect; - IntermediateFrequency = -IntermediateFrequency; - } - - switch (state->m_OperationMode) { - case OM_QAM_ITU_A: - case OM_QAM_ITU_C: - IFreqkHz = (IntermediateFrequency / 1000); - status = SetQAM(state, IFreqkHz, OffsetkHz); - if (status < 0) - goto error; - state->m_DrxkState = DRXK_DTV_STARTED; - break; - case OM_DVBT: - IFreqkHz = (IntermediateFrequency / 1000); - status = MPEGTSStop(state); - if (status < 0) - goto error; - status = SetDVBT(state, IFreqkHz, OffsetkHz); - if (status < 0) - goto error; - status = DVBTStart(state); - if (status < 0) - goto error; - state->m_DrxkState = DRXK_DTV_STARTED; - break; - default: - break; - } -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int ShutDown(struct drxk_state *state) -{ - dprintk(1, "\n"); - - MPEGTSStop(state); - return 0; -} - -static int GetLockStatus(struct drxk_state *state, u32 *pLockStatus, - u32 Time) -{ - int status = -EINVAL; - - dprintk(1, "\n"); - - if (pLockStatus == NULL) - goto error; - - *pLockStatus = NOT_LOCKED; - - /* define the SCU command code */ - switch (state->m_OperationMode) { - case OM_QAM_ITU_A: - case OM_QAM_ITU_B: - case OM_QAM_ITU_C: - status = GetQAMLockStatus(state, pLockStatus); - break; - case OM_DVBT: - status = GetDVBTLockStatus(state, pLockStatus); - break; - default: - break; - } -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int MPEGTSStart(struct drxk_state *state) -{ - int status; - - u16 fecOcSncMode = 0; - - /* Allow OC to sync again */ - status = read16(state, FEC_OC_SNC_MODE__A, &fecOcSncMode); - if (status < 0) - goto error; - fecOcSncMode &= ~FEC_OC_SNC_MODE_SHUTDOWN__M; - status = write16(state, FEC_OC_SNC_MODE__A, fecOcSncMode); - if (status < 0) - goto error; - status = write16(state, FEC_OC_SNC_UNLOCK__A, 1); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int MPEGTSDtoInit(struct drxk_state *state) -{ - int status; - - dprintk(1, "\n"); - - /* Rate integration settings */ - status = write16(state, FEC_OC_RCN_CTL_STEP_LO__A, 0x0000); - if (status < 0) - goto error; - status = write16(state, FEC_OC_RCN_CTL_STEP_HI__A, 0x000C); - if (status < 0) - goto error; - status = write16(state, FEC_OC_RCN_GAIN__A, 0x000A); - if (status < 0) - goto error; - status = write16(state, FEC_OC_AVR_PARM_A__A, 0x0008); - if (status < 0) - goto error; - status = write16(state, FEC_OC_AVR_PARM_B__A, 0x0006); - if (status < 0) - goto error; - status = write16(state, FEC_OC_TMD_HI_MARGIN__A, 0x0680); - if (status < 0) - goto error; - status = write16(state, FEC_OC_TMD_LO_MARGIN__A, 0x0080); - if (status < 0) - goto error; - status = write16(state, FEC_OC_TMD_COUNT__A, 0x03F4); - if (status < 0) - goto error; - - /* Additional configuration */ - status = write16(state, FEC_OC_OCR_INVERT__A, 0); - if (status < 0) - goto error; - status = write16(state, FEC_OC_SNC_LWM__A, 2); - if (status < 0) - goto error; - status = write16(state, FEC_OC_SNC_HWM__A, 12); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -static int MPEGTSDtoSetup(struct drxk_state *state, - enum OperationMode oMode) -{ - int status; - - u16 fecOcRegMode = 0; /* FEC_OC_MODE register value */ - u16 fecOcRegIprMode = 0; /* FEC_OC_IPR_MODE register value */ - u16 fecOcDtoMode = 0; /* FEC_OC_IPR_INVERT register value */ - u16 fecOcFctMode = 0; /* FEC_OC_IPR_INVERT register value */ - u16 fecOcDtoPeriod = 2; /* FEC_OC_IPR_INVERT register value */ - u16 fecOcDtoBurstLen = 188; /* FEC_OC_IPR_INVERT register value */ - u32 fecOcRcnCtlRate = 0; /* FEC_OC_IPR_INVERT register value */ - u16 fecOcTmdMode = 0; - u16 fecOcTmdIntUpdRate = 0; - u32 maxBitRate = 0; - bool staticCLK = false; - - dprintk(1, "\n"); - - /* Check insertion of the Reed-Solomon parity bytes */ - status = read16(state, FEC_OC_MODE__A, &fecOcRegMode); - if (status < 0) - goto error; - status = read16(state, FEC_OC_IPR_MODE__A, &fecOcRegIprMode); - if (status < 0) - goto error; - fecOcRegMode &= (~FEC_OC_MODE_PARITY__M); - fecOcRegIprMode &= (~FEC_OC_IPR_MODE_MVAL_DIS_PAR__M); - if (state->m_insertRSByte == true) { - /* enable parity symbol forward */ - fecOcRegMode |= FEC_OC_MODE_PARITY__M; - /* MVAL disable during parity bytes */ - fecOcRegIprMode |= FEC_OC_IPR_MODE_MVAL_DIS_PAR__M; - /* TS burst length to 204 */ - fecOcDtoBurstLen = 204; - } - - /* Check serial or parrallel output */ - fecOcRegIprMode &= (~(FEC_OC_IPR_MODE_SERIAL__M)); - if (state->m_enableParallel == false) { - /* MPEG data output is serial -> set ipr_mode[0] */ - fecOcRegIprMode |= FEC_OC_IPR_MODE_SERIAL__M; - } - - switch (oMode) { - case OM_DVBT: - maxBitRate = state->m_DVBTBitrate; - fecOcTmdMode = 3; - fecOcRcnCtlRate = 0xC00000; - staticCLK = state->m_DVBTStaticCLK; - break; - case OM_QAM_ITU_A: /* fallthrough */ - case OM_QAM_ITU_C: - fecOcTmdMode = 0x0004; - fecOcRcnCtlRate = 0xD2B4EE; /* good for >63 Mb/s */ - maxBitRate = state->m_DVBCBitrate; - staticCLK = state->m_DVBCStaticCLK; - break; - default: - status = -EINVAL; - } /* switch (standard) */ - if (status < 0) - goto error; - - /* Configure DTO's */ - if (staticCLK) { - u32 bitRate = 0; - - /* Rational DTO for MCLK source (static MCLK rate), - Dynamic DTO for optimal grouping - (avoid intra-packet gaps), - DTO offset enable to sync TS burst with MSTRT */ - fecOcDtoMode = (FEC_OC_DTO_MODE_DYNAMIC__M | - FEC_OC_DTO_MODE_OFFSET_ENABLE__M); - fecOcFctMode = (FEC_OC_FCT_MODE_RAT_ENA__M | - FEC_OC_FCT_MODE_VIRT_ENA__M); - - /* Check user defined bitrate */ - bitRate = maxBitRate; - if (bitRate > 75900000UL) { /* max is 75.9 Mb/s */ - bitRate = 75900000UL; - } - /* Rational DTO period: - dto_period = (Fsys / bitrate) - 2 - - Result should be floored, - to make sure >= requested bitrate - */ - fecOcDtoPeriod = (u16) (((state->m_sysClockFreq) - * 1000) / bitRate); - if (fecOcDtoPeriod <= 2) - fecOcDtoPeriod = 0; - else - fecOcDtoPeriod -= 2; - fecOcTmdIntUpdRate = 8; - } else { - /* (commonAttr->staticCLK == false) => dynamic mode */ - fecOcDtoMode = FEC_OC_DTO_MODE_DYNAMIC__M; - fecOcFctMode = FEC_OC_FCT_MODE__PRE; - fecOcTmdIntUpdRate = 5; - } - - /* Write appropriate registers with requested configuration */ - status = write16(state, FEC_OC_DTO_BURST_LEN__A, fecOcDtoBurstLen); - if (status < 0) - goto error; - status = write16(state, FEC_OC_DTO_PERIOD__A, fecOcDtoPeriod); - if (status < 0) - goto error; - status = write16(state, FEC_OC_DTO_MODE__A, fecOcDtoMode); - if (status < 0) - goto error; - status = write16(state, FEC_OC_FCT_MODE__A, fecOcFctMode); - if (status < 0) - goto error; - status = write16(state, FEC_OC_MODE__A, fecOcRegMode); - if (status < 0) - goto error; - status = write16(state, FEC_OC_IPR_MODE__A, fecOcRegIprMode); - if (status < 0) - goto error; - - /* Rate integration settings */ - status = write32(state, FEC_OC_RCN_CTL_RATE_LO__A, fecOcRcnCtlRate); - if (status < 0) - goto error; - status = write16(state, FEC_OC_TMD_INT_UPD_RATE__A, fecOcTmdIntUpdRate); - if (status < 0) - goto error; - status = write16(state, FEC_OC_TMD_MODE__A, fecOcTmdMode); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int MPEGTSConfigurePolarity(struct drxk_state *state) -{ - u16 fecOcRegIprInvert = 0; - - /* Data mask for the output data byte */ - u16 InvertDataMask = - FEC_OC_IPR_INVERT_MD7__M | FEC_OC_IPR_INVERT_MD6__M | - FEC_OC_IPR_INVERT_MD5__M | FEC_OC_IPR_INVERT_MD4__M | - FEC_OC_IPR_INVERT_MD3__M | FEC_OC_IPR_INVERT_MD2__M | - FEC_OC_IPR_INVERT_MD1__M | FEC_OC_IPR_INVERT_MD0__M; - - dprintk(1, "\n"); - - /* Control selective inversion of output bits */ - fecOcRegIprInvert &= (~(InvertDataMask)); - if (state->m_invertDATA == true) - fecOcRegIprInvert |= InvertDataMask; - fecOcRegIprInvert &= (~(FEC_OC_IPR_INVERT_MERR__M)); - if (state->m_invertERR == true) - fecOcRegIprInvert |= FEC_OC_IPR_INVERT_MERR__M; - fecOcRegIprInvert &= (~(FEC_OC_IPR_INVERT_MSTRT__M)); - if (state->m_invertSTR == true) - fecOcRegIprInvert |= FEC_OC_IPR_INVERT_MSTRT__M; - fecOcRegIprInvert &= (~(FEC_OC_IPR_INVERT_MVAL__M)); - if (state->m_invertVAL == true) - fecOcRegIprInvert |= FEC_OC_IPR_INVERT_MVAL__M; - fecOcRegIprInvert &= (~(FEC_OC_IPR_INVERT_MCLK__M)); - if (state->m_invertCLK == true) - fecOcRegIprInvert |= FEC_OC_IPR_INVERT_MCLK__M; - - return write16(state, FEC_OC_IPR_INVERT__A, fecOcRegIprInvert); -} - -#define SCU_RAM_AGC_KI_INV_RF_POL__M 0x4000 - -static int SetAgcRf(struct drxk_state *state, - struct SCfgAgc *pAgcCfg, bool isDTV) -{ - int status = -EINVAL; - u16 data = 0; - struct SCfgAgc *pIfAgcSettings; - - dprintk(1, "\n"); - - if (pAgcCfg == NULL) - goto error; - - switch (pAgcCfg->ctrlMode) { - case DRXK_AGC_CTRL_AUTO: - /* Enable RF AGC DAC */ - status = read16(state, IQM_AF_STDBY__A, &data); - if (status < 0) - goto error; - data &= ~IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY; - status = write16(state, IQM_AF_STDBY__A, data); - if (status < 0) - goto error; - status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); - if (status < 0) - goto error; - - /* Enable SCU RF AGC loop */ - data &= ~SCU_RAM_AGC_CONFIG_DISABLE_RF_AGC__M; - - /* Polarity */ - if (state->m_RfAgcPol) - data |= SCU_RAM_AGC_CONFIG_INV_RF_POL__M; - else - data &= ~SCU_RAM_AGC_CONFIG_INV_RF_POL__M; - status = write16(state, SCU_RAM_AGC_CONFIG__A, data); - if (status < 0) - goto error; - - /* Set speed (using complementary reduction value) */ - status = read16(state, SCU_RAM_AGC_KI_RED__A, &data); - if (status < 0) - goto error; - - data &= ~SCU_RAM_AGC_KI_RED_RAGC_RED__M; - data |= (~(pAgcCfg->speed << - SCU_RAM_AGC_KI_RED_RAGC_RED__B) - & SCU_RAM_AGC_KI_RED_RAGC_RED__M); - - status = write16(state, SCU_RAM_AGC_KI_RED__A, data); - if (status < 0) - goto error; - - if (IsDVBT(state)) - pIfAgcSettings = &state->m_dvbtIfAgcCfg; - else if (IsQAM(state)) - pIfAgcSettings = &state->m_qamIfAgcCfg; - else - pIfAgcSettings = &state->m_atvIfAgcCfg; - if (pIfAgcSettings == NULL) { - status = -EINVAL; - goto error; - } - - /* Set TOP, only if IF-AGC is in AUTO mode */ - if (pIfAgcSettings->ctrlMode == DRXK_AGC_CTRL_AUTO) - status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MAX__A, pAgcCfg->top); - if (status < 0) - goto error; - - /* Cut-Off current */ - status = write16(state, SCU_RAM_AGC_RF_IACCU_HI_CO__A, pAgcCfg->cutOffCurrent); - if (status < 0) - goto error; - - /* Max. output level */ - status = write16(state, SCU_RAM_AGC_RF_MAX__A, pAgcCfg->maxOutputLevel); - if (status < 0) - goto error; - - break; - - case DRXK_AGC_CTRL_USER: - /* Enable RF AGC DAC */ - status = read16(state, IQM_AF_STDBY__A, &data); - if (status < 0) - goto error; - data &= ~IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY; - status = write16(state, IQM_AF_STDBY__A, data); - if (status < 0) - goto error; - - /* Disable SCU RF AGC loop */ - status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); - if (status < 0) - goto error; - data |= SCU_RAM_AGC_CONFIG_DISABLE_RF_AGC__M; - if (state->m_RfAgcPol) - data |= SCU_RAM_AGC_CONFIG_INV_RF_POL__M; - else - data &= ~SCU_RAM_AGC_CONFIG_INV_RF_POL__M; - status = write16(state, SCU_RAM_AGC_CONFIG__A, data); - if (status < 0) - goto error; - - /* SCU c.o.c. to 0, enabling full control range */ - status = write16(state, SCU_RAM_AGC_RF_IACCU_HI_CO__A, 0); - if (status < 0) - goto error; - - /* Write value to output pin */ - status = write16(state, SCU_RAM_AGC_RF_IACCU_HI__A, pAgcCfg->outputLevel); - if (status < 0) - goto error; - break; - - case DRXK_AGC_CTRL_OFF: - /* Disable RF AGC DAC */ - status = read16(state, IQM_AF_STDBY__A, &data); - if (status < 0) - goto error; - data |= IQM_AF_STDBY_STDBY_TAGC_RF_STANDBY; - status = write16(state, IQM_AF_STDBY__A, data); - if (status < 0) - goto error; - - /* Disable SCU RF AGC loop */ - status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); - if (status < 0) - goto error; - data |= SCU_RAM_AGC_CONFIG_DISABLE_RF_AGC__M; - status = write16(state, SCU_RAM_AGC_CONFIG__A, data); - if (status < 0) - goto error; - break; - - default: - status = -EINVAL; - - } -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -#define SCU_RAM_AGC_KI_INV_IF_POL__M 0x2000 - -static int SetAgcIf(struct drxk_state *state, - struct SCfgAgc *pAgcCfg, bool isDTV) -{ - u16 data = 0; - int status = 0; - struct SCfgAgc *pRfAgcSettings; - - dprintk(1, "\n"); - - switch (pAgcCfg->ctrlMode) { - case DRXK_AGC_CTRL_AUTO: - - /* Enable IF AGC DAC */ - status = read16(state, IQM_AF_STDBY__A, &data); - if (status < 0) - goto error; - data &= ~IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY; - status = write16(state, IQM_AF_STDBY__A, data); - if (status < 0) - goto error; - - status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); - if (status < 0) - goto error; - - /* Enable SCU IF AGC loop */ - data &= ~SCU_RAM_AGC_CONFIG_DISABLE_IF_AGC__M; - - /* Polarity */ - if (state->m_IfAgcPol) - data |= SCU_RAM_AGC_CONFIG_INV_IF_POL__M; - else - data &= ~SCU_RAM_AGC_CONFIG_INV_IF_POL__M; - status = write16(state, SCU_RAM_AGC_CONFIG__A, data); - if (status < 0) - goto error; - - /* Set speed (using complementary reduction value) */ - status = read16(state, SCU_RAM_AGC_KI_RED__A, &data); - if (status < 0) - goto error; - data &= ~SCU_RAM_AGC_KI_RED_IAGC_RED__M; - data |= (~(pAgcCfg->speed << - SCU_RAM_AGC_KI_RED_IAGC_RED__B) - & SCU_RAM_AGC_KI_RED_IAGC_RED__M); - - status = write16(state, SCU_RAM_AGC_KI_RED__A, data); - if (status < 0) - goto error; - - if (IsQAM(state)) - pRfAgcSettings = &state->m_qamRfAgcCfg; - else - pRfAgcSettings = &state->m_atvRfAgcCfg; - if (pRfAgcSettings == NULL) - return -1; - /* Restore TOP */ - status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MAX__A, pRfAgcSettings->top); - if (status < 0) - goto error; - break; - - case DRXK_AGC_CTRL_USER: - - /* Enable IF AGC DAC */ - status = read16(state, IQM_AF_STDBY__A, &data); - if (status < 0) - goto error; - data &= ~IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY; - status = write16(state, IQM_AF_STDBY__A, data); - if (status < 0) - goto error; - - status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); - if (status < 0) - goto error; - - /* Disable SCU IF AGC loop */ - data |= SCU_RAM_AGC_CONFIG_DISABLE_IF_AGC__M; - - /* Polarity */ - if (state->m_IfAgcPol) - data |= SCU_RAM_AGC_CONFIG_INV_IF_POL__M; - else - data &= ~SCU_RAM_AGC_CONFIG_INV_IF_POL__M; - status = write16(state, SCU_RAM_AGC_CONFIG__A, data); - if (status < 0) - goto error; - - /* Write value to output pin */ - status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MAX__A, pAgcCfg->outputLevel); - if (status < 0) - goto error; - break; - - case DRXK_AGC_CTRL_OFF: - - /* Disable If AGC DAC */ - status = read16(state, IQM_AF_STDBY__A, &data); - if (status < 0) - goto error; - data |= IQM_AF_STDBY_STDBY_TAGC_IF_STANDBY; - status = write16(state, IQM_AF_STDBY__A, data); - if (status < 0) - goto error; - - /* Disable SCU IF AGC loop */ - status = read16(state, SCU_RAM_AGC_CONFIG__A, &data); - if (status < 0) - goto error; - data |= SCU_RAM_AGC_CONFIG_DISABLE_IF_AGC__M; - status = write16(state, SCU_RAM_AGC_CONFIG__A, data); - if (status < 0) - goto error; - break; - } /* switch (agcSettingsIf->ctrlMode) */ - - /* always set the top to support - configurations without if-loop */ - status = write16(state, SCU_RAM_AGC_INGAIN_TGT_MIN__A, pAgcCfg->top); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int ReadIFAgc(struct drxk_state *state, u32 *pValue) -{ - u16 agcDacLvl; - int status; - u16 Level = 0; - - dprintk(1, "\n"); - - status = read16(state, IQM_AF_AGC_IF__A, &agcDacLvl); - if (status < 0) { - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; - } - - *pValue = 0; - - if (agcDacLvl > DRXK_AGC_DAC_OFFSET) - Level = agcDacLvl - DRXK_AGC_DAC_OFFSET; - if (Level < 14000) - *pValue = (14000 - Level) / 4; - else - *pValue = 0; - - return status; -} - -static int GetQAMSignalToNoise(struct drxk_state *state, - s32 *pSignalToNoise) -{ - int status = 0; - u16 qamSlErrPower = 0; /* accum. error between - raw and sliced symbols */ - u32 qamSlSigPower = 0; /* used for MER, depends of - QAM modulation */ - u32 qamSlMer = 0; /* QAM MER */ - - dprintk(1, "\n"); - - /* MER calculation */ - - /* get the register value needed for MER */ - status = read16(state, QAM_SL_ERR_POWER__A, &qamSlErrPower); - if (status < 0) { - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return -EINVAL; - } - - switch (state->props.modulation) { - case QAM_16: - qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM16 << 2; - break; - case QAM_32: - qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM32 << 2; - break; - case QAM_64: - qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM64 << 2; - break; - case QAM_128: - qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM128 << 2; - break; - default: - case QAM_256: - qamSlSigPower = DRXK_QAM_SL_SIG_POWER_QAM256 << 2; - break; - } - - if (qamSlErrPower > 0) { - qamSlMer = Log10Times100(qamSlSigPower) - - Log10Times100((u32) qamSlErrPower); - } - *pSignalToNoise = qamSlMer; - - return status; -} - -static int GetDVBTSignalToNoise(struct drxk_state *state, - s32 *pSignalToNoise) -{ - int status; - u16 regData = 0; - u32 EqRegTdSqrErrI = 0; - u32 EqRegTdSqrErrQ = 0; - u16 EqRegTdSqrErrExp = 0; - u16 EqRegTdTpsPwrOfs = 0; - u16 EqRegTdReqSmbCnt = 0; - u32 tpsCnt = 0; - u32 SqrErrIQ = 0; - u32 a = 0; - u32 b = 0; - u32 c = 0; - u32 iMER = 0; - u16 transmissionParams = 0; - - dprintk(1, "\n"); - - status = read16(state, OFDM_EQ_TOP_TD_TPS_PWR_OFS__A, &EqRegTdTpsPwrOfs); - if (status < 0) - goto error; - status = read16(state, OFDM_EQ_TOP_TD_REQ_SMB_CNT__A, &EqRegTdReqSmbCnt); - if (status < 0) - goto error; - status = read16(state, OFDM_EQ_TOP_TD_SQR_ERR_EXP__A, &EqRegTdSqrErrExp); - if (status < 0) - goto error; - status = read16(state, OFDM_EQ_TOP_TD_SQR_ERR_I__A, ®Data); - if (status < 0) - goto error; - /* Extend SQR_ERR_I operational range */ - EqRegTdSqrErrI = (u32) regData; - if ((EqRegTdSqrErrExp > 11) && - (EqRegTdSqrErrI < 0x00000FFFUL)) { - EqRegTdSqrErrI += 0x00010000UL; - } - status = read16(state, OFDM_EQ_TOP_TD_SQR_ERR_Q__A, ®Data); - if (status < 0) - goto error; - /* Extend SQR_ERR_Q operational range */ - EqRegTdSqrErrQ = (u32) regData; - if ((EqRegTdSqrErrExp > 11) && - (EqRegTdSqrErrQ < 0x00000FFFUL)) - EqRegTdSqrErrQ += 0x00010000UL; - - status = read16(state, OFDM_SC_RA_RAM_OP_PARAM__A, &transmissionParams); - if (status < 0) - goto error; - - /* Check input data for MER */ - - /* MER calculation (in 0.1 dB) without math.h */ - if ((EqRegTdTpsPwrOfs == 0) || (EqRegTdReqSmbCnt == 0)) - iMER = 0; - else if ((EqRegTdSqrErrI + EqRegTdSqrErrQ) == 0) { - /* No error at all, this must be the HW reset value - * Apparently no first measurement yet - * Set MER to 0.0 */ - iMER = 0; - } else { - SqrErrIQ = (EqRegTdSqrErrI + EqRegTdSqrErrQ) << - EqRegTdSqrErrExp; - if ((transmissionParams & - OFDM_SC_RA_RAM_OP_PARAM_MODE__M) - == OFDM_SC_RA_RAM_OP_PARAM_MODE_2K) - tpsCnt = 17; - else - tpsCnt = 68; - - /* IMER = 100 * log10 (x) - where x = (EqRegTdTpsPwrOfs^2 * - EqRegTdReqSmbCnt * tpsCnt)/SqrErrIQ - - => IMER = a + b -c - where a = 100 * log10 (EqRegTdTpsPwrOfs^2) - b = 100 * log10 (EqRegTdReqSmbCnt * tpsCnt) - c = 100 * log10 (SqrErrIQ) - */ - - /* log(x) x = 9bits * 9bits->18 bits */ - a = Log10Times100(EqRegTdTpsPwrOfs * - EqRegTdTpsPwrOfs); - /* log(x) x = 16bits * 7bits->23 bits */ - b = Log10Times100(EqRegTdReqSmbCnt * tpsCnt); - /* log(x) x = (16bits + 16bits) << 15 ->32 bits */ - c = Log10Times100(SqrErrIQ); - - iMER = a + b; - /* No negative MER, clip to zero */ - if (iMER > c) - iMER -= c; - else - iMER = 0; - } - *pSignalToNoise = iMER; - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int GetSignalToNoise(struct drxk_state *state, s32 *pSignalToNoise) -{ - dprintk(1, "\n"); - - *pSignalToNoise = 0; - switch (state->m_OperationMode) { - case OM_DVBT: - return GetDVBTSignalToNoise(state, pSignalToNoise); - case OM_QAM_ITU_A: - case OM_QAM_ITU_C: - return GetQAMSignalToNoise(state, pSignalToNoise); - default: - break; - } - return 0; -} - -#if 0 -static int GetDVBTQuality(struct drxk_state *state, s32 *pQuality) -{ - /* SNR Values for quasi errorfree reception rom Nordig 2.2 */ - int status = 0; - - dprintk(1, "\n"); - - static s32 QE_SN[] = { - 51, /* QPSK 1/2 */ - 69, /* QPSK 2/3 */ - 79, /* QPSK 3/4 */ - 89, /* QPSK 5/6 */ - 97, /* QPSK 7/8 */ - 108, /* 16-QAM 1/2 */ - 131, /* 16-QAM 2/3 */ - 146, /* 16-QAM 3/4 */ - 156, /* 16-QAM 5/6 */ - 160, /* 16-QAM 7/8 */ - 165, /* 64-QAM 1/2 */ - 187, /* 64-QAM 2/3 */ - 202, /* 64-QAM 3/4 */ - 216, /* 64-QAM 5/6 */ - 225, /* 64-QAM 7/8 */ - }; - - *pQuality = 0; - - do { - s32 SignalToNoise = 0; - u16 Constellation = 0; - u16 CodeRate = 0; - u32 SignalToNoiseRel; - u32 BERQuality; - - status = GetDVBTSignalToNoise(state, &SignalToNoise); - if (status < 0) - break; - status = read16(state, OFDM_EQ_TOP_TD_TPS_CONST__A, &Constellation); - if (status < 0) - break; - Constellation &= OFDM_EQ_TOP_TD_TPS_CONST__M; - - status = read16(state, OFDM_EQ_TOP_TD_TPS_CODE_HP__A, &CodeRate); - if (status < 0) - break; - CodeRate &= OFDM_EQ_TOP_TD_TPS_CODE_HP__M; - - if (Constellation > OFDM_EQ_TOP_TD_TPS_CONST_64QAM || - CodeRate > OFDM_EQ_TOP_TD_TPS_CODE_LP_7_8) - break; - SignalToNoiseRel = SignalToNoise - - QE_SN[Constellation * 5 + CodeRate]; - BERQuality = 100; - - if (SignalToNoiseRel < -70) - *pQuality = 0; - else if (SignalToNoiseRel < 30) - *pQuality = ((SignalToNoiseRel + 70) * - BERQuality) / 100; - else - *pQuality = BERQuality; - } while (0); - return 0; -}; - -static int GetDVBCQuality(struct drxk_state *state, s32 *pQuality) -{ - int status = 0; - *pQuality = 0; - - dprintk(1, "\n"); - - do { - u32 SignalToNoise = 0; - u32 BERQuality = 100; - u32 SignalToNoiseRel = 0; - - status = GetQAMSignalToNoise(state, &SignalToNoise); - if (status < 0) - break; - - switch (state->props.modulation) { - case QAM_16: - SignalToNoiseRel = SignalToNoise - 200; - break; - case QAM_32: - SignalToNoiseRel = SignalToNoise - 230; - break; /* Not in NorDig */ - case QAM_64: - SignalToNoiseRel = SignalToNoise - 260; - break; - case QAM_128: - SignalToNoiseRel = SignalToNoise - 290; - break; - default: - case QAM_256: - SignalToNoiseRel = SignalToNoise - 320; - break; - } - - if (SignalToNoiseRel < -70) - *pQuality = 0; - else if (SignalToNoiseRel < 30) - *pQuality = ((SignalToNoiseRel + 70) * - BERQuality) / 100; - else - *pQuality = BERQuality; - } while (0); - - return status; -} - -static int GetQuality(struct drxk_state *state, s32 *pQuality) -{ - dprintk(1, "\n"); - - switch (state->m_OperationMode) { - case OM_DVBT: - return GetDVBTQuality(state, pQuality); - case OM_QAM_ITU_A: - return GetDVBCQuality(state, pQuality); - default: - break; - } - - return 0; -} -#endif - -/* Free data ram in SIO HI */ -#define SIO_HI_RA_RAM_USR_BEGIN__A 0x420040 -#define SIO_HI_RA_RAM_USR_END__A 0x420060 - -#define DRXK_HI_ATOMIC_BUF_START (SIO_HI_RA_RAM_USR_BEGIN__A) -#define DRXK_HI_ATOMIC_BUF_END (SIO_HI_RA_RAM_USR_BEGIN__A + 7) -#define DRXK_HI_ATOMIC_READ SIO_HI_RA_RAM_PAR_3_ACP_RW_READ -#define DRXK_HI_ATOMIC_WRITE SIO_HI_RA_RAM_PAR_3_ACP_RW_WRITE - -#define DRXDAP_FASI_ADDR2BLOCK(addr) (((addr) >> 22) & 0x3F) -#define DRXDAP_FASI_ADDR2BANK(addr) (((addr) >> 16) & 0x3F) -#define DRXDAP_FASI_ADDR2OFFSET(addr) ((addr) & 0x7FFF) - -static int ConfigureI2CBridge(struct drxk_state *state, bool bEnableBridge) -{ - int status = -EINVAL; - - dprintk(1, "\n"); - - if (state->m_DrxkState == DRXK_UNINITIALIZED) - return 0; - if (state->m_DrxkState == DRXK_POWERED_DOWN) - goto error; - - if (state->no_i2c_bridge) - return 0; - - status = write16(state, SIO_HI_RA_RAM_PAR_1__A, SIO_HI_RA_RAM_PAR_1_PAR1_SEC_KEY); - if (status < 0) - goto error; - if (bEnableBridge) { - status = write16(state, SIO_HI_RA_RAM_PAR_2__A, SIO_HI_RA_RAM_PAR_2_BRD_CFG_CLOSED); - if (status < 0) - goto error; - } else { - status = write16(state, SIO_HI_RA_RAM_PAR_2__A, SIO_HI_RA_RAM_PAR_2_BRD_CFG_OPEN); - if (status < 0) - goto error; - } - - status = HI_Command(state, SIO_HI_RA_RAM_CMD_BRDCTRL, 0); - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int SetPreSaw(struct drxk_state *state, - struct SCfgPreSaw *pPreSawCfg) -{ - int status = -EINVAL; - - dprintk(1, "\n"); - - if ((pPreSawCfg == NULL) - || (pPreSawCfg->reference > IQM_AF_PDREF__M)) - goto error; - - status = write16(state, IQM_AF_PDREF__A, pPreSawCfg->reference); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int BLDirectCmd(struct drxk_state *state, u32 targetAddr, - u16 romOffset, u16 nrOfElements, u32 timeOut) -{ - u16 blStatus = 0; - u16 offset = (u16) ((targetAddr >> 0) & 0x00FFFF); - u16 blockbank = (u16) ((targetAddr >> 16) & 0x000FFF); - int status; - unsigned long end; - - dprintk(1, "\n"); - - mutex_lock(&state->mutex); - status = write16(state, SIO_BL_MODE__A, SIO_BL_MODE_DIRECT); - if (status < 0) - goto error; - status = write16(state, SIO_BL_TGT_HDR__A, blockbank); - if (status < 0) - goto error; - status = write16(state, SIO_BL_TGT_ADDR__A, offset); - if (status < 0) - goto error; - status = write16(state, SIO_BL_SRC_ADDR__A, romOffset); - if (status < 0) - goto error; - status = write16(state, SIO_BL_SRC_LEN__A, nrOfElements); - if (status < 0) - goto error; - status = write16(state, SIO_BL_ENABLE__A, SIO_BL_ENABLE_ON); - if (status < 0) - goto error; - - end = jiffies + msecs_to_jiffies(timeOut); - do { - status = read16(state, SIO_BL_STATUS__A, &blStatus); - if (status < 0) - goto error; - } while ((blStatus == 0x1) && time_is_after_jiffies(end)); - if (blStatus == 0x1) { - printk(KERN_ERR "drxk: SIO not ready\n"); - status = -EINVAL; - goto error2; - } -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); -error2: - mutex_unlock(&state->mutex); - return status; - -} - -static int ADCSyncMeasurement(struct drxk_state *state, u16 *count) -{ - u16 data = 0; - int status; - - dprintk(1, "\n"); - - /* Start measurement */ - status = write16(state, IQM_AF_COMM_EXEC__A, IQM_AF_COMM_EXEC_ACTIVE); - if (status < 0) - goto error; - status = write16(state, IQM_AF_START_LOCK__A, 1); - if (status < 0) - goto error; - - *count = 0; - status = read16(state, IQM_AF_PHASE0__A, &data); - if (status < 0) - goto error; - if (data == 127) - *count = *count + 1; - status = read16(state, IQM_AF_PHASE1__A, &data); - if (status < 0) - goto error; - if (data == 127) - *count = *count + 1; - status = read16(state, IQM_AF_PHASE2__A, &data); - if (status < 0) - goto error; - if (data == 127) - *count = *count + 1; - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int ADCSynchronization(struct drxk_state *state) -{ - u16 count = 0; - int status; - - dprintk(1, "\n"); - - status = ADCSyncMeasurement(state, &count); - if (status < 0) - goto error; - - if (count == 1) { - /* Try sampling on a diffrent edge */ - u16 clkNeg = 0; - - status = read16(state, IQM_AF_CLKNEG__A, &clkNeg); - if (status < 0) - goto error; - if ((clkNeg & IQM_AF_CLKNEG_CLKNEGDATA__M) == - IQM_AF_CLKNEG_CLKNEGDATA_CLK_ADC_DATA_POS) { - clkNeg &= (~(IQM_AF_CLKNEG_CLKNEGDATA__M)); - clkNeg |= - IQM_AF_CLKNEG_CLKNEGDATA_CLK_ADC_DATA_NEG; - } else { - clkNeg &= (~(IQM_AF_CLKNEG_CLKNEGDATA__M)); - clkNeg |= - IQM_AF_CLKNEG_CLKNEGDATA_CLK_ADC_DATA_POS; - } - status = write16(state, IQM_AF_CLKNEG__A, clkNeg); - if (status < 0) - goto error; - status = ADCSyncMeasurement(state, &count); - if (status < 0) - goto error; - } - - if (count < 2) - status = -EINVAL; -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int SetFrequencyShifter(struct drxk_state *state, - u16 intermediateFreqkHz, - s32 tunerFreqOffset, bool isDTV) -{ - bool selectPosImage = false; - u32 rfFreqResidual = tunerFreqOffset; - u32 fmFrequencyShift = 0; - bool tunerMirror = !state->m_bMirrorFreqSpect; - u32 adcFreq; - bool adcFlip; - int status; - u32 ifFreqActual; - u32 samplingFrequency = (u32) (state->m_sysClockFreq / 3); - u32 frequencyShift; - bool imageToSelect; - - dprintk(1, "\n"); - - /* - Program frequency shifter - No need to account for mirroring on RF - */ - if (isDTV) { - if ((state->m_OperationMode == OM_QAM_ITU_A) || - (state->m_OperationMode == OM_QAM_ITU_C) || - (state->m_OperationMode == OM_DVBT)) - selectPosImage = true; - else - selectPosImage = false; - } - if (tunerMirror) - /* tuner doesn't mirror */ - ifFreqActual = intermediateFreqkHz + - rfFreqResidual + fmFrequencyShift; - else - /* tuner mirrors */ - ifFreqActual = intermediateFreqkHz - - rfFreqResidual - fmFrequencyShift; - if (ifFreqActual > samplingFrequency / 2) { - /* adc mirrors */ - adcFreq = samplingFrequency - ifFreqActual; - adcFlip = true; - } else { - /* adc doesn't mirror */ - adcFreq = ifFreqActual; - adcFlip = false; - } - - frequencyShift = adcFreq; - imageToSelect = state->m_rfmirror ^ tunerMirror ^ - adcFlip ^ selectPosImage; - state->m_IqmFsRateOfs = - Frac28a((frequencyShift), samplingFrequency); - - if (imageToSelect) - state->m_IqmFsRateOfs = ~state->m_IqmFsRateOfs + 1; - - /* Program frequency shifter with tuner offset compensation */ - /* frequencyShift += tunerFreqOffset; TODO */ - status = write32(state, IQM_FS_RATE_OFS_LO__A, - state->m_IqmFsRateOfs); - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int InitAGC(struct drxk_state *state, bool isDTV) -{ - u16 ingainTgt = 0; - u16 ingainTgtMin = 0; - u16 ingainTgtMax = 0; - u16 clpCyclen = 0; - u16 clpSumMin = 0; - u16 clpDirTo = 0; - u16 snsSumMin = 0; - u16 snsSumMax = 0; - u16 clpSumMax = 0; - u16 snsDirTo = 0; - u16 kiInnergainMin = 0; - u16 ifIaccuHiTgt = 0; - u16 ifIaccuHiTgtMin = 0; - u16 ifIaccuHiTgtMax = 0; - u16 data = 0; - u16 fastClpCtrlDelay = 0; - u16 clpCtrlMode = 0; - int status = 0; - - dprintk(1, "\n"); - - /* Common settings */ - snsSumMax = 1023; - ifIaccuHiTgtMin = 2047; - clpCyclen = 500; - clpSumMax = 1023; - - /* AGCInit() not available for DVBT; init done in microcode */ - if (!IsQAM(state)) { - printk(KERN_ERR "drxk: %s: mode %d is not DVB-C\n", __func__, state->m_OperationMode); - return -EINVAL; - } - - /* FIXME: Analog TV AGC require different settings */ - - /* Standard specific settings */ - clpSumMin = 8; - clpDirTo = (u16) -9; - clpCtrlMode = 0; - snsSumMin = 8; - snsDirTo = (u16) -9; - kiInnergainMin = (u16) -1030; - ifIaccuHiTgtMax = 0x2380; - ifIaccuHiTgt = 0x2380; - ingainTgtMin = 0x0511; - ingainTgt = 0x0511; - ingainTgtMax = 5119; - fastClpCtrlDelay = state->m_qamIfAgcCfg.FastClipCtrlDelay; - - status = write16(state, SCU_RAM_AGC_FAST_CLP_CTRL_DELAY__A, fastClpCtrlDelay); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_AGC_CLP_CTRL_MODE__A, clpCtrlMode); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_INGAIN_TGT__A, ingainTgt); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_INGAIN_TGT_MIN__A, ingainTgtMin); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_INGAIN_TGT_MAX__A, ingainTgtMax); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MIN__A, ifIaccuHiTgtMin); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT_MAX__A, ifIaccuHiTgtMax); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_IF_IACCU_HI__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_IF_IACCU_LO__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_RF_IACCU_HI__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_RF_IACCU_LO__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_CLP_SUM_MAX__A, clpSumMax); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_SNS_SUM_MAX__A, snsSumMax); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_AGC_KI_INNERGAIN_MIN__A, kiInnergainMin); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_IF_IACCU_HI_TGT__A, ifIaccuHiTgt); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_CLP_CYCLEN__A, clpCyclen); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_AGC_RF_SNS_DEV_MAX__A, 1023); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_RF_SNS_DEV_MIN__A, (u16) -1023); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_FAST_SNS_CTRL_DELAY__A, 50); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_AGC_KI_MAXMINGAIN_TH__A, 20); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_CLP_SUM_MIN__A, clpSumMin); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_SNS_SUM_MIN__A, snsSumMin); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_CLP_DIR_TO__A, clpDirTo); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_SNS_DIR_TO__A, snsDirTo); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_KI_MINGAIN__A, 0x7fff); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_KI_MAXGAIN__A, 0x0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_KI_MIN__A, 0x0117); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_KI_MAX__A, 0x0657); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_CLP_SUM__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_CLP_CYCCNT__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_CLP_DIR_WD__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_CLP_DIR_STP__A, 1); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_SNS_SUM__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_SNS_CYCCNT__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_SNS_DIR_WD__A, 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_SNS_DIR_STP__A, 1); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_SNS_CYCLEN__A, 500); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_KI_CYCLEN__A, 500); - if (status < 0) - goto error; - - /* Initialize inner-loop KI gain factors */ - status = read16(state, SCU_RAM_AGC_KI__A, &data); - if (status < 0) - goto error; - - data = 0x0657; - data &= ~SCU_RAM_AGC_KI_RF__M; - data |= (DRXK_KI_RAGC_QAM << SCU_RAM_AGC_KI_RF__B); - data &= ~SCU_RAM_AGC_KI_IF__M; - data |= (DRXK_KI_IAGC_QAM << SCU_RAM_AGC_KI_IF__B); - - status = write16(state, SCU_RAM_AGC_KI__A, data); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int DVBTQAMGetAccPktErr(struct drxk_state *state, u16 *packetErr) -{ - int status; - - dprintk(1, "\n"); - if (packetErr == NULL) - status = write16(state, SCU_RAM_FEC_ACCUM_PKT_FAILURES__A, 0); - else - status = read16(state, SCU_RAM_FEC_ACCUM_PKT_FAILURES__A, packetErr); - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int DVBTScCommand(struct drxk_state *state, - u16 cmd, u16 subcmd, - u16 param0, u16 param1, u16 param2, - u16 param3, u16 param4) -{ - u16 curCmd = 0; - u16 errCode = 0; - u16 retryCnt = 0; - u16 scExec = 0; - int status; - - dprintk(1, "\n"); - status = read16(state, OFDM_SC_COMM_EXEC__A, &scExec); - if (scExec != 1) { - /* SC is not running */ - status = -EINVAL; - } - if (status < 0) - goto error; - - /* Wait until sc is ready to receive command */ - retryCnt = 0; - do { - msleep(1); - status = read16(state, OFDM_SC_RA_RAM_CMD__A, &curCmd); - retryCnt++; - } while ((curCmd != 0) && (retryCnt < DRXK_MAX_RETRIES)); - if (retryCnt >= DRXK_MAX_RETRIES && (status < 0)) - goto error; - - /* Write sub-command */ - switch (cmd) { - /* All commands using sub-cmd */ - case OFDM_SC_RA_RAM_CMD_PROC_START: - case OFDM_SC_RA_RAM_CMD_SET_PREF_PARAM: - case OFDM_SC_RA_RAM_CMD_PROGRAM_PARAM: - status = write16(state, OFDM_SC_RA_RAM_CMD_ADDR__A, subcmd); - if (status < 0) - goto error; - break; - default: - /* Do nothing */ - break; - } - - /* Write needed parameters and the command */ - switch (cmd) { - /* All commands using 5 parameters */ - /* All commands using 4 parameters */ - /* All commands using 3 parameters */ - /* All commands using 2 parameters */ - case OFDM_SC_RA_RAM_CMD_PROC_START: - case OFDM_SC_RA_RAM_CMD_SET_PREF_PARAM: - case OFDM_SC_RA_RAM_CMD_PROGRAM_PARAM: - status = write16(state, OFDM_SC_RA_RAM_PARAM1__A, param1); - /* All commands using 1 parameters */ - case OFDM_SC_RA_RAM_CMD_SET_ECHO_TIMING: - case OFDM_SC_RA_RAM_CMD_USER_IO: - status = write16(state, OFDM_SC_RA_RAM_PARAM0__A, param0); - /* All commands using 0 parameters */ - case OFDM_SC_RA_RAM_CMD_GET_OP_PARAM: - case OFDM_SC_RA_RAM_CMD_NULL: - /* Write command */ - status = write16(state, OFDM_SC_RA_RAM_CMD__A, cmd); - break; - default: - /* Unknown command */ - status = -EINVAL; - } - if (status < 0) - goto error; - - /* Wait until sc is ready processing command */ - retryCnt = 0; - do { - msleep(1); - status = read16(state, OFDM_SC_RA_RAM_CMD__A, &curCmd); - retryCnt++; - } while ((curCmd != 0) && (retryCnt < DRXK_MAX_RETRIES)); - if (retryCnt >= DRXK_MAX_RETRIES && (status < 0)) - goto error; - - /* Check for illegal cmd */ - status = read16(state, OFDM_SC_RA_RAM_CMD_ADDR__A, &errCode); - if (errCode == 0xFFFF) { - /* illegal command */ - status = -EINVAL; - } - if (status < 0) - goto error; - - /* Retreive results parameters from SC */ - switch (cmd) { - /* All commands yielding 5 results */ - /* All commands yielding 4 results */ - /* All commands yielding 3 results */ - /* All commands yielding 2 results */ - /* All commands yielding 1 result */ - case OFDM_SC_RA_RAM_CMD_USER_IO: - case OFDM_SC_RA_RAM_CMD_GET_OP_PARAM: - status = read16(state, OFDM_SC_RA_RAM_PARAM0__A, &(param0)); - /* All commands yielding 0 results */ - case OFDM_SC_RA_RAM_CMD_SET_ECHO_TIMING: - case OFDM_SC_RA_RAM_CMD_SET_TIMER: - case OFDM_SC_RA_RAM_CMD_PROC_START: - case OFDM_SC_RA_RAM_CMD_SET_PREF_PARAM: - case OFDM_SC_RA_RAM_CMD_PROGRAM_PARAM: - case OFDM_SC_RA_RAM_CMD_NULL: - break; - default: - /* Unknown command */ - status = -EINVAL; - break; - } /* switch (cmd->cmd) */ -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int PowerUpDVBT(struct drxk_state *state) -{ - enum DRXPowerMode powerMode = DRX_POWER_UP; - int status; - - dprintk(1, "\n"); - status = CtrlPowerMode(state, &powerMode); - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int DVBTCtrlSetIncEnable(struct drxk_state *state, bool *enabled) -{ - int status; - - dprintk(1, "\n"); - if (*enabled == true) - status = write16(state, IQM_CF_BYPASSDET__A, 0); - else - status = write16(state, IQM_CF_BYPASSDET__A, 1); - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -#define DEFAULT_FR_THRES_8K 4000 -static int DVBTCtrlSetFrEnable(struct drxk_state *state, bool *enabled) -{ - - int status; - - dprintk(1, "\n"); - if (*enabled == true) { - /* write mask to 1 */ - status = write16(state, OFDM_SC_RA_RAM_FR_THRES_8K__A, - DEFAULT_FR_THRES_8K); - } else { - /* write mask to 0 */ - status = write16(state, OFDM_SC_RA_RAM_FR_THRES_8K__A, 0); - } - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -static int DVBTCtrlSetEchoThreshold(struct drxk_state *state, - struct DRXKCfgDvbtEchoThres_t *echoThres) -{ - u16 data = 0; - int status; - - dprintk(1, "\n"); - status = read16(state, OFDM_SC_RA_RAM_ECHO_THRES__A, &data); - if (status < 0) - goto error; - - switch (echoThres->fftMode) { - case DRX_FFTMODE_2K: - data &= ~OFDM_SC_RA_RAM_ECHO_THRES_2K__M; - data |= ((echoThres->threshold << - OFDM_SC_RA_RAM_ECHO_THRES_2K__B) - & (OFDM_SC_RA_RAM_ECHO_THRES_2K__M)); - break; - case DRX_FFTMODE_8K: - data &= ~OFDM_SC_RA_RAM_ECHO_THRES_8K__M; - data |= ((echoThres->threshold << - OFDM_SC_RA_RAM_ECHO_THRES_8K__B) - & (OFDM_SC_RA_RAM_ECHO_THRES_8K__M)); - break; - default: - return -EINVAL; - } - - status = write16(state, OFDM_SC_RA_RAM_ECHO_THRES__A, data); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int DVBTCtrlSetSqiSpeed(struct drxk_state *state, - enum DRXKCfgDvbtSqiSpeed *speed) -{ - int status = -EINVAL; - - dprintk(1, "\n"); - - switch (*speed) { - case DRXK_DVBT_SQI_SPEED_FAST: - case DRXK_DVBT_SQI_SPEED_MEDIUM: - case DRXK_DVBT_SQI_SPEED_SLOW: - break; - default: - goto error; - } - status = write16(state, SCU_RAM_FEC_PRE_RS_BER_FILTER_SH__A, - (u16) *speed); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -/*============================================================================*/ - -/** -* \brief Activate DVBT specific presets -* \param demod instance of demodulator. -* \return DRXStatus_t. -* -* Called in DVBTSetStandard -* -*/ -static int DVBTActivatePresets(struct drxk_state *state) -{ - int status; - bool setincenable = false; - bool setfrenable = true; - - struct DRXKCfgDvbtEchoThres_t echoThres2k = { 0, DRX_FFTMODE_2K }; - struct DRXKCfgDvbtEchoThres_t echoThres8k = { 0, DRX_FFTMODE_8K }; - - dprintk(1, "\n"); - status = DVBTCtrlSetIncEnable(state, &setincenable); - if (status < 0) - goto error; - status = DVBTCtrlSetFrEnable(state, &setfrenable); - if (status < 0) - goto error; - status = DVBTCtrlSetEchoThreshold(state, &echoThres2k); - if (status < 0) - goto error; - status = DVBTCtrlSetEchoThreshold(state, &echoThres8k); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_AGC_INGAIN_TGT_MAX__A, state->m_dvbtIfAgcCfg.IngainTgtMax); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -/*============================================================================*/ - -/** -* \brief Initialize channelswitch-independent settings for DVBT. -* \param demod instance of demodulator. -* \return DRXStatus_t. -* -* For ROM code channel filter taps are loaded from the bootloader. For microcode -* the DVB-T taps from the drxk_filters.h are used. -*/ -static int SetDVBTStandard(struct drxk_state *state, - enum OperationMode oMode) -{ - u16 cmdResult = 0; - u16 data = 0; - int status; - - dprintk(1, "\n"); - - PowerUpDVBT(state); - /* added antenna switch */ - SwitchAntennaToDVBT(state); - /* send OFDM reset command */ - status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_RESET, 0, NULL, 1, &cmdResult); - if (status < 0) - goto error; - - /* send OFDM setenv command */ - status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_SET_ENV, 0, NULL, 1, &cmdResult); - if (status < 0) - goto error; - - /* reset datapath for OFDM, processors first */ - status = write16(state, OFDM_SC_COMM_EXEC__A, OFDM_SC_COMM_EXEC_STOP); - if (status < 0) - goto error; - status = write16(state, OFDM_LC_COMM_EXEC__A, OFDM_LC_COMM_EXEC_STOP); - if (status < 0) - goto error; - status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_STOP); - if (status < 0) - goto error; - - /* IQM setup */ - /* synchronize on ofdstate->m_festart */ - status = write16(state, IQM_AF_UPD_SEL__A, 1); - if (status < 0) - goto error; - /* window size for clipping ADC detection */ - status = write16(state, IQM_AF_CLP_LEN__A, 0); - if (status < 0) - goto error; - /* window size for for sense pre-SAW detection */ - status = write16(state, IQM_AF_SNS_LEN__A, 0); - if (status < 0) - goto error; - /* sense threshold for sense pre-SAW detection */ - status = write16(state, IQM_AF_AMUX__A, IQM_AF_AMUX_SIGNAL2ADC); - if (status < 0) - goto error; - status = SetIqmAf(state, true); - if (status < 0) - goto error; - - status = write16(state, IQM_AF_AGC_RF__A, 0); - if (status < 0) - goto error; - - /* Impulse noise cruncher setup */ - status = write16(state, IQM_AF_INC_LCT__A, 0); /* crunch in IQM_CF */ - if (status < 0) - goto error; - status = write16(state, IQM_CF_DET_LCT__A, 0); /* detect in IQM_CF */ - if (status < 0) - goto error; - status = write16(state, IQM_CF_WND_LEN__A, 3); /* peak detector window length */ - if (status < 0) - goto error; - - status = write16(state, IQM_RC_STRETCH__A, 16); - if (status < 0) - goto error; - status = write16(state, IQM_CF_OUT_ENA__A, 0x4); /* enable output 2 */ - if (status < 0) - goto error; - status = write16(state, IQM_CF_DS_ENA__A, 0x4); /* decimate output 2 */ - if (status < 0) - goto error; - status = write16(state, IQM_CF_SCALE__A, 1600); - if (status < 0) - goto error; - status = write16(state, IQM_CF_SCALE_SH__A, 0); - if (status < 0) - goto error; - - /* virtual clipping threshold for clipping ADC detection */ - status = write16(state, IQM_AF_CLP_TH__A, 448); - if (status < 0) - goto error; - status = write16(state, IQM_CF_DATATH__A, 495); /* crunching threshold */ - if (status < 0) - goto error; - - status = BLChainCmd(state, DRXK_BL_ROM_OFFSET_TAPS_DVBT, DRXK_BLCC_NR_ELEMENTS_TAPS, DRXK_BLC_TIMEOUT); - if (status < 0) - goto error; - - status = write16(state, IQM_CF_PKDTH__A, 2); /* peak detector threshold */ - if (status < 0) - goto error; - status = write16(state, IQM_CF_POW_MEAS_LEN__A, 2); - if (status < 0) - goto error; - /* enable power measurement interrupt */ - status = write16(state, IQM_CF_COMM_INT_MSK__A, 1); - if (status < 0) - goto error; - status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_ACTIVE); - if (status < 0) - goto error; - - /* IQM will not be reset from here, sync ADC and update/init AGC */ - status = ADCSynchronization(state); - if (status < 0) - goto error; - status = SetPreSaw(state, &state->m_dvbtPreSawCfg); - if (status < 0) - goto error; - - /* Halt SCU to enable safe non-atomic accesses */ - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_HOLD); - if (status < 0) - goto error; - - status = SetAgcRf(state, &state->m_dvbtRfAgcCfg, true); - if (status < 0) - goto error; - status = SetAgcIf(state, &state->m_dvbtIfAgcCfg, true); - if (status < 0) - goto error; - - /* Set Noise Estimation notch width and enable DC fix */ - status = read16(state, OFDM_SC_RA_RAM_CONFIG__A, &data); - if (status < 0) - goto error; - data |= OFDM_SC_RA_RAM_CONFIG_NE_FIX_ENABLE__M; - status = write16(state, OFDM_SC_RA_RAM_CONFIG__A, data); - if (status < 0) - goto error; - - /* Activate SCU to enable SCU commands */ - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); - if (status < 0) - goto error; - - if (!state->m_DRXK_A3_ROM_CODE) { - /* AGCInit() is not done for DVBT, so set agcFastClipCtrlDelay */ - status = write16(state, SCU_RAM_AGC_FAST_CLP_CTRL_DELAY__A, state->m_dvbtIfAgcCfg.FastClipCtrlDelay); - if (status < 0) - goto error; - } - - /* OFDM_SC setup */ -#ifdef COMPILE_FOR_NONRT - status = write16(state, OFDM_SC_RA_RAM_BE_OPT_DELAY__A, 1); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_BE_OPT_INIT_DELAY__A, 2); - if (status < 0) - goto error; -#endif - - /* FEC setup */ - status = write16(state, FEC_DI_INPUT_CTL__A, 1); /* OFDM input */ - if (status < 0) - goto error; - - -#ifdef COMPILE_FOR_NONRT - status = write16(state, FEC_RS_MEASUREMENT_PERIOD__A, 0x400); - if (status < 0) - goto error; -#else - status = write16(state, FEC_RS_MEASUREMENT_PERIOD__A, 0x1000); - if (status < 0) - goto error; -#endif - status = write16(state, FEC_RS_MEASUREMENT_PRESCALE__A, 0x0001); - if (status < 0) - goto error; - - /* Setup MPEG bus */ - status = MPEGTSDtoSetup(state, OM_DVBT); - if (status < 0) - goto error; - /* Set DVBT Presets */ - status = DVBTActivatePresets(state); - if (status < 0) - goto error; - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -/*============================================================================*/ -/** -* \brief Start dvbt demodulating for channel. -* \param demod instance of demodulator. -* \return DRXStatus_t. -*/ -static int DVBTStart(struct drxk_state *state) -{ - u16 param1; - int status; - /* DRXKOfdmScCmd_t scCmd; */ - - dprintk(1, "\n"); - /* Start correct processes to get in lock */ - /* DRXK: OFDM_SC_RA_RAM_PROC_LOCKTRACK is no longer in mapfile! */ - param1 = OFDM_SC_RA_RAM_LOCKTRACK_MIN; - status = DVBTScCommand(state, OFDM_SC_RA_RAM_CMD_PROC_START, 0, OFDM_SC_RA_RAM_SW_EVENT_RUN_NMASK__M, param1, 0, 0, 0); - if (status < 0) - goto error; - /* Start FEC OC */ - status = MPEGTSStart(state); - if (status < 0) - goto error; - status = write16(state, FEC_COMM_EXEC__A, FEC_COMM_EXEC_ACTIVE); - if (status < 0) - goto error; -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - - -/*============================================================================*/ - -/** -* \brief Set up dvbt demodulator for channel. -* \param demod instance of demodulator. -* \return DRXStatus_t. -* // original DVBTSetChannel() -*/ -static int SetDVBT(struct drxk_state *state, u16 IntermediateFreqkHz, - s32 tunerFreqOffset) -{ - u16 cmdResult = 0; - u16 transmissionParams = 0; - u16 operationMode = 0; - u32 iqmRcRateOfs = 0; - u32 bandwidth = 0; - u16 param1; - int status; - - dprintk(1, "IF =%d, TFO = %d\n", IntermediateFreqkHz, tunerFreqOffset); - - status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_STOP, 0, NULL, 1, &cmdResult); - if (status < 0) - goto error; - - /* Halt SCU to enable safe non-atomic accesses */ - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_HOLD); - if (status < 0) - goto error; - - /* Stop processors */ - status = write16(state, OFDM_SC_COMM_EXEC__A, OFDM_SC_COMM_EXEC_STOP); - if (status < 0) - goto error; - status = write16(state, OFDM_LC_COMM_EXEC__A, OFDM_LC_COMM_EXEC_STOP); - if (status < 0) - goto error; - - /* Mandatory fix, always stop CP, required to set spl offset back to - hardware default (is set to 0 by ucode during pilot detection */ - status = write16(state, OFDM_CP_COMM_EXEC__A, OFDM_CP_COMM_EXEC_STOP); - if (status < 0) - goto error; - - /*== Write channel settings to device =====================================*/ - - /* mode */ - switch (state->props.transmission_mode) { - case TRANSMISSION_MODE_AUTO: - default: - operationMode |= OFDM_SC_RA_RAM_OP_AUTO_MODE__M; - /* fall through , try first guess DRX_FFTMODE_8K */ - case TRANSMISSION_MODE_8K: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_MODE_8K; - break; - case TRANSMISSION_MODE_2K: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_MODE_2K; - break; - } - - /* guard */ - switch (state->props.guard_interval) { - default: - case GUARD_INTERVAL_AUTO: - operationMode |= OFDM_SC_RA_RAM_OP_AUTO_GUARD__M; - /* fall through , try first guess DRX_GUARD_1DIV4 */ - case GUARD_INTERVAL_1_4: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_GUARD_4; - break; - case GUARD_INTERVAL_1_32: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_GUARD_32; - break; - case GUARD_INTERVAL_1_16: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_GUARD_16; - break; - case GUARD_INTERVAL_1_8: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_GUARD_8; - break; - } - - /* hierarchy */ - switch (state->props.hierarchy) { - case HIERARCHY_AUTO: - case HIERARCHY_NONE: - default: - operationMode |= OFDM_SC_RA_RAM_OP_AUTO_HIER__M; - /* fall through , try first guess SC_RA_RAM_OP_PARAM_HIER_NO */ - /* transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_HIER_NO; */ - /* break; */ - case HIERARCHY_1: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_HIER_A1; - break; - case HIERARCHY_2: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_HIER_A2; - break; - case HIERARCHY_4: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_HIER_A4; - break; - } - - - /* modulation */ - switch (state->props.modulation) { - case QAM_AUTO: - default: - operationMode |= OFDM_SC_RA_RAM_OP_AUTO_CONST__M; - /* fall through , try first guess DRX_CONSTELLATION_QAM64 */ - case QAM_64: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_CONST_QAM64; - break; - case QPSK: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_CONST_QPSK; - break; - case QAM_16: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_CONST_QAM16; - break; - } -#if 0 - /* No hierachical channels support in BDA */ - /* Priority (only for hierarchical channels) */ - switch (channel->priority) { - case DRX_PRIORITY_LOW: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_PRIO_LO; - WR16(devAddr, OFDM_EC_SB_PRIOR__A, - OFDM_EC_SB_PRIOR_LO); - break; - case DRX_PRIORITY_HIGH: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_PRIO_HI; - WR16(devAddr, OFDM_EC_SB_PRIOR__A, - OFDM_EC_SB_PRIOR_HI)); - break; - case DRX_PRIORITY_UNKNOWN: /* fall through */ - default: - status = -EINVAL; - goto error; - } -#else - /* Set Priorty high */ - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_PRIO_HI; - status = write16(state, OFDM_EC_SB_PRIOR__A, OFDM_EC_SB_PRIOR_HI); - if (status < 0) - goto error; -#endif - - /* coderate */ - switch (state->props.code_rate_HP) { - case FEC_AUTO: - default: - operationMode |= OFDM_SC_RA_RAM_OP_AUTO_RATE__M; - /* fall through , try first guess DRX_CODERATE_2DIV3 */ - case FEC_2_3: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_2_3; - break; - case FEC_1_2: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_1_2; - break; - case FEC_3_4: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_3_4; - break; - case FEC_5_6: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_5_6; - break; - case FEC_7_8: - transmissionParams |= OFDM_SC_RA_RAM_OP_PARAM_RATE_7_8; - break; - } - - /* SAW filter selection: normaly not necesarry, but if wanted - the application can select a SAW filter via the driver by using UIOs */ - /* First determine real bandwidth (Hz) */ - /* Also set delay for impulse noise cruncher */ - /* Also set parameters for EC_OC fix, note EC_OC_REG_TMD_HIL_MAR is changed - by SC for fix for some 8K,1/8 guard but is restored by InitEC and ResetEC - functions */ - switch (state->props.bandwidth_hz) { - case 0: - state->props.bandwidth_hz = 8000000; - /* fall though */ - case 8000000: - bandwidth = DRXK_BANDWIDTH_8MHZ_IN_HZ; - status = write16(state, OFDM_SC_RA_RAM_SRMM_FIX_FACT_8K__A, 3052); - if (status < 0) - goto error; - /* cochannel protection for PAL 8 MHz */ - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_LEFT__A, 7); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_RIGHT__A, 7); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_LEFT__A, 7); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_RIGHT__A, 1); - if (status < 0) - goto error; - break; - case 7000000: - bandwidth = DRXK_BANDWIDTH_7MHZ_IN_HZ; - status = write16(state, OFDM_SC_RA_RAM_SRMM_FIX_FACT_8K__A, 3491); - if (status < 0) - goto error; - /* cochannel protection for PAL 7 MHz */ - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_LEFT__A, 8); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_RIGHT__A, 8); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_LEFT__A, 4); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_RIGHT__A, 1); - if (status < 0) - goto error; - break; - case 6000000: - bandwidth = DRXK_BANDWIDTH_6MHZ_IN_HZ; - status = write16(state, OFDM_SC_RA_RAM_SRMM_FIX_FACT_8K__A, 4073); - if (status < 0) - goto error; - /* cochannel protection for NTSC 6 MHz */ - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_LEFT__A, 19); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_8K_PER_RIGHT__A, 19); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_LEFT__A, 14); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_RA_RAM_NI_INIT_2K_PER_RIGHT__A, 1); - if (status < 0) - goto error; - break; - default: - status = -EINVAL; - goto error; - } - - if (iqmRcRateOfs == 0) { - /* Now compute IQM_RC_RATE_OFS - (((SysFreq/BandWidth)/2)/2) -1) * 2^23) - => - ((SysFreq / BandWidth) * (2^21)) - (2^23) - */ - /* (SysFreq / BandWidth) * (2^28) */ - /* assert (MAX(sysClk)/MIN(bandwidth) < 16) - => assert(MAX(sysClk) < 16*MIN(bandwidth)) - => assert(109714272 > 48000000) = true so Frac 28 can be used */ - iqmRcRateOfs = Frac28a((u32) - ((state->m_sysClockFreq * - 1000) / 3), bandwidth); - /* (SysFreq / BandWidth) * (2^21), rounding before truncating */ - if ((iqmRcRateOfs & 0x7fL) >= 0x40) - iqmRcRateOfs += 0x80L; - iqmRcRateOfs = iqmRcRateOfs >> 7; - /* ((SysFreq / BandWidth) * (2^21)) - (2^23) */ - iqmRcRateOfs = iqmRcRateOfs - (1 << 23); - } - - iqmRcRateOfs &= - ((((u32) IQM_RC_RATE_OFS_HI__M) << - IQM_RC_RATE_OFS_LO__W) | IQM_RC_RATE_OFS_LO__M); - status = write32(state, IQM_RC_RATE_OFS_LO__A, iqmRcRateOfs); - if (status < 0) - goto error; - - /* Bandwidth setting done */ - -#if 0 - status = DVBTSetFrequencyShift(demod, channel, tunerOffset); - if (status < 0) - goto error; -#endif - status = SetFrequencyShifter(state, IntermediateFreqkHz, tunerFreqOffset, true); - if (status < 0) - goto error; - - /*== Start SC, write channel settings to SC ===============================*/ - - /* Activate SCU to enable SCU commands */ - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); - if (status < 0) - goto error; - - /* Enable SC after setting all other parameters */ - status = write16(state, OFDM_SC_COMM_STATE__A, 0); - if (status < 0) - goto error; - status = write16(state, OFDM_SC_COMM_EXEC__A, 1); - if (status < 0) - goto error; - - - status = scu_command(state, SCU_RAM_COMMAND_STANDARD_OFDM | SCU_RAM_COMMAND_CMD_DEMOD_START, 0, NULL, 1, &cmdResult); - if (status < 0) - goto error; - - /* Write SC parameter registers, set all AUTO flags in operation mode */ - param1 = (OFDM_SC_RA_RAM_OP_AUTO_MODE__M | - OFDM_SC_RA_RAM_OP_AUTO_GUARD__M | - OFDM_SC_RA_RAM_OP_AUTO_CONST__M | - OFDM_SC_RA_RAM_OP_AUTO_HIER__M | - OFDM_SC_RA_RAM_OP_AUTO_RATE__M); - status = DVBTScCommand(state, OFDM_SC_RA_RAM_CMD_SET_PREF_PARAM, - 0, transmissionParams, param1, 0, 0, 0); - if (status < 0) - goto error; - - if (!state->m_DRXK_A3_ROM_CODE) - status = DVBTCtrlSetSqiSpeed(state, &state->m_sqiSpeed); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - - -/*============================================================================*/ - -/** -* \brief Retreive lock status . -* \param demod Pointer to demodulator instance. -* \param lockStat Pointer to lock status structure. -* \return DRXStatus_t. -* -*/ -static int GetDVBTLockStatus(struct drxk_state *state, u32 *pLockStatus) -{ - int status; - const u16 mpeg_lock_mask = (OFDM_SC_RA_RAM_LOCK_MPEG__M | - OFDM_SC_RA_RAM_LOCK_FEC__M); - const u16 fec_lock_mask = (OFDM_SC_RA_RAM_LOCK_FEC__M); - const u16 demod_lock_mask = OFDM_SC_RA_RAM_LOCK_DEMOD__M; - - u16 ScRaRamLock = 0; - u16 ScCommExec = 0; - - dprintk(1, "\n"); - - *pLockStatus = NOT_LOCKED; - /* driver 0.9.0 */ - /* Check if SC is running */ - status = read16(state, OFDM_SC_COMM_EXEC__A, &ScCommExec); - if (status < 0) - goto end; - if (ScCommExec == OFDM_SC_COMM_EXEC_STOP) - goto end; - - status = read16(state, OFDM_SC_RA_RAM_LOCK__A, &ScRaRamLock); - if (status < 0) - goto end; - - if ((ScRaRamLock & mpeg_lock_mask) == mpeg_lock_mask) - *pLockStatus = MPEG_LOCK; - else if ((ScRaRamLock & fec_lock_mask) == fec_lock_mask) - *pLockStatus = FEC_LOCK; - else if ((ScRaRamLock & demod_lock_mask) == demod_lock_mask) - *pLockStatus = DEMOD_LOCK; - else if (ScRaRamLock & OFDM_SC_RA_RAM_LOCK_NODVBT__M) - *pLockStatus = NEVER_LOCK; -end: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -static int PowerUpQAM(struct drxk_state *state) -{ - enum DRXPowerMode powerMode = DRXK_POWER_DOWN_OFDM; - int status; - - dprintk(1, "\n"); - status = CtrlPowerMode(state, &powerMode); - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - - -/** Power Down QAM */ -static int PowerDownQAM(struct drxk_state *state) -{ - u16 data = 0; - u16 cmdResult; - int status = 0; - - dprintk(1, "\n"); - status = read16(state, SCU_COMM_EXEC__A, &data); - if (status < 0) - goto error; - if (data == SCU_COMM_EXEC_ACTIVE) { - /* - STOP demodulator - QAM and HW blocks - */ - /* stop all comstate->m_exec */ - status = write16(state, QAM_COMM_EXEC__A, QAM_COMM_EXEC_STOP); - if (status < 0) - goto error; - status = scu_command(state, SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_STOP, 0, NULL, 1, &cmdResult); - if (status < 0) - goto error; - } - /* powerdown AFE */ - status = SetIqmAf(state, false); - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -/*============================================================================*/ - -/** -* \brief Setup of the QAM Measurement intervals for signal quality -* \param demod instance of demod. -* \param modulation current modulation. -* \return DRXStatus_t. -* -* NOTE: -* Take into account that for certain settings the errorcounters can overflow. -* The implementation does not check this. -* -*/ -static int SetQAMMeasurement(struct drxk_state *state, - enum EDrxkConstellation modulation, - u32 symbolRate) -{ - u32 fecBitsDesired = 0; /* BER accounting period */ - u32 fecRsPeriodTotal = 0; /* Total period */ - u16 fecRsPrescale = 0; /* ReedSolomon Measurement Prescale */ - u16 fecRsPeriod = 0; /* Value for corresponding I2C register */ - int status = 0; - - dprintk(1, "\n"); - - fecRsPrescale = 1; - /* fecBitsDesired = symbolRate [kHz] * - FrameLenght [ms] * - (modulation + 1) * - SyncLoss (== 1) * - ViterbiLoss (==1) - */ - switch (modulation) { - case DRX_CONSTELLATION_QAM16: - fecBitsDesired = 4 * symbolRate; - break; - case DRX_CONSTELLATION_QAM32: - fecBitsDesired = 5 * symbolRate; - break; - case DRX_CONSTELLATION_QAM64: - fecBitsDesired = 6 * symbolRate; - break; - case DRX_CONSTELLATION_QAM128: - fecBitsDesired = 7 * symbolRate; - break; - case DRX_CONSTELLATION_QAM256: - fecBitsDesired = 8 * symbolRate; - break; - default: - status = -EINVAL; - } - if (status < 0) - goto error; - - fecBitsDesired /= 1000; /* symbolRate [Hz] -> symbolRate [kHz] */ - fecBitsDesired *= 500; /* meas. period [ms] */ - - /* Annex A/C: bits/RsPeriod = 204 * 8 = 1632 */ - /* fecRsPeriodTotal = fecBitsDesired / 1632 */ - fecRsPeriodTotal = (fecBitsDesired / 1632UL) + 1; /* roughly ceil */ - - /* fecRsPeriodTotal = fecRsPrescale * fecRsPeriod */ - fecRsPrescale = 1 + (u16) (fecRsPeriodTotal >> 16); - if (fecRsPrescale == 0) { - /* Divide by zero (though impossible) */ - status = -EINVAL; - if (status < 0) - goto error; - } - fecRsPeriod = - ((u16) fecRsPeriodTotal + - (fecRsPrescale >> 1)) / fecRsPrescale; - - /* write corresponding registers */ - status = write16(state, FEC_RS_MEASUREMENT_PERIOD__A, fecRsPeriod); - if (status < 0) - goto error; - status = write16(state, FEC_RS_MEASUREMENT_PRESCALE__A, fecRsPrescale); - if (status < 0) - goto error; - status = write16(state, FEC_OC_SNC_FAIL_PERIOD__A, fecRsPeriod); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int SetQAM16(struct drxk_state *state) -{ - int status = 0; - - dprintk(1, "\n"); - /* QAM Equalizer Setup */ - /* Equalizer */ - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 13517); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 13517); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 13517); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 13517); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 13517); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 13517); - if (status < 0) - goto error; - /* Decision Feedback Equalizer */ - status = write16(state, QAM_DQ_QUAL_FUN0__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN1__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN2__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN3__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN4__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); - if (status < 0) - goto error; - - status = write16(state, QAM_SY_SYNC_HWM__A, 5); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_AWM__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_LWM__A, 3); - if (status < 0) - goto error; - - /* QAM Slicer Settings */ - status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM16); - if (status < 0) - goto error; - - /* QAM Loop Controller Coeficients */ - status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 20); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 80); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 20); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 50); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 32); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 10); - if (status < 0) - goto error; - - - /* QAM State Machine (FSM) Thresholds */ - - status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 140); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 50); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 95); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 120); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 230); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 105); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 4); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 24); - if (status < 0) - goto error; - - - /* QAM FSM Tracking Parameters */ - - status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 220); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) 25); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) 6); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) -24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) -65); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -127); - if (status < 0) - goto error; - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -/*============================================================================*/ - -/** -* \brief QAM32 specific setup -* \param demod instance of demod. -* \return DRXStatus_t. -*/ -static int SetQAM32(struct drxk_state *state) -{ - int status = 0; - - dprintk(1, "\n"); - - /* QAM Equalizer Setup */ - /* Equalizer */ - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 6707); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 6707); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 6707); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 6707); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 6707); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 6707); - if (status < 0) - goto error; - - /* Decision Feedback Equalizer */ - status = write16(state, QAM_DQ_QUAL_FUN0__A, 3); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN1__A, 3); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN2__A, 3); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN3__A, 3); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN4__A, 3); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); - if (status < 0) - goto error; - - status = write16(state, QAM_SY_SYNC_HWM__A, 6); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_AWM__A, 5); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_LWM__A, 3); - if (status < 0) - goto error; - - /* QAM Slicer Settings */ - - status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM32); - if (status < 0) - goto error; - - - /* QAM Loop Controller Coeficients */ - - status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 20); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 80); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 20); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 50); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 0); - if (status < 0) - goto error; - - - /* QAM State Machine (FSM) Thresholds */ - - status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 90); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 50); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 80); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 100); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 170); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 100); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 4); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 10); - if (status < 0) - goto error; - - - /* QAM FSM Tracking Parameters */ - - status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 140); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) -8); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) -16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) -26); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) -56); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -86); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -/*============================================================================*/ - -/** -* \brief QAM64 specific setup -* \param demod instance of demod. -* \return DRXStatus_t. -*/ -static int SetQAM64(struct drxk_state *state) -{ - int status = 0; - - dprintk(1, "\n"); - /* QAM Equalizer Setup */ - /* Equalizer */ - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 13336); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 12618); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 11988); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 13809); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 13809); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 15609); - if (status < 0) - goto error; - - /* Decision Feedback Equalizer */ - status = write16(state, QAM_DQ_QUAL_FUN0__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN1__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN2__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN3__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN4__A, 3); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); - if (status < 0) - goto error; - - status = write16(state, QAM_SY_SYNC_HWM__A, 5); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_AWM__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_LWM__A, 3); - if (status < 0) - goto error; - - /* QAM Slicer Settings */ - status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM64); - if (status < 0) - goto error; - - - /* QAM Loop Controller Coeficients */ - - status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 30); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 100); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 30); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 50); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 25); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 48); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 10); - if (status < 0) - goto error; - - - /* QAM State Machine (FSM) Thresholds */ - - status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 100); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 60); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 80); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 110); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 200); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 95); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 4); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 15); - if (status < 0) - goto error; - - - /* QAM FSM Tracking Parameters */ - - status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 141); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) 7); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) -15); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) -45); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -80); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -/*============================================================================*/ - -/** -* \brief QAM128 specific setup -* \param demod: instance of demod. -* \return DRXStatus_t. -*/ -static int SetQAM128(struct drxk_state *state) -{ - int status = 0; - - dprintk(1, "\n"); - /* QAM Equalizer Setup */ - /* Equalizer */ - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 6564); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 6598); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 6394); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 6409); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 6656); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 7238); - if (status < 0) - goto error; - - /* Decision Feedback Equalizer */ - status = write16(state, QAM_DQ_QUAL_FUN0__A, 6); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN1__A, 6); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN2__A, 6); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN3__A, 6); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN4__A, 5); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); - if (status < 0) - goto error; - - status = write16(state, QAM_SY_SYNC_HWM__A, 6); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_AWM__A, 5); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_LWM__A, 3); - if (status < 0) - goto error; - - - /* QAM Slicer Settings */ - - status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM128); - if (status < 0) - goto error; - - - /* QAM Loop Controller Coeficients */ - - status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 120); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 60); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 25); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 64); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 0); - if (status < 0) - goto error; - - - /* QAM State Machine (FSM) Thresholds */ - - status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 50); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 60); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 80); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 100); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 140); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 100); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 5); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 12); - if (status < 0) - goto error; - - /* QAM FSM Tracking Parameters */ - - status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 8); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 65); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) 3); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) -1); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) -12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -23); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -/*============================================================================*/ - -/** -* \brief QAM256 specific setup -* \param demod: instance of demod. -* \return DRXStatus_t. -*/ -static int SetQAM256(struct drxk_state *state) -{ - int status = 0; - - dprintk(1, "\n"); - /* QAM Equalizer Setup */ - /* Equalizer */ - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD0__A, 11502); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD1__A, 12084); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD2__A, 12543); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD3__A, 12931); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD4__A, 13629); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_EQ_CMA_RAD5__A, 15385); - if (status < 0) - goto error; - - /* Decision Feedback Equalizer */ - status = write16(state, QAM_DQ_QUAL_FUN0__A, 8); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN1__A, 8); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN2__A, 8); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN3__A, 8); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN4__A, 6); - if (status < 0) - goto error; - status = write16(state, QAM_DQ_QUAL_FUN5__A, 0); - if (status < 0) - goto error; - - status = write16(state, QAM_SY_SYNC_HWM__A, 5); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_AWM__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_SY_SYNC_LWM__A, 3); - if (status < 0) - goto error; - - /* QAM Slicer Settings */ - - status = write16(state, SCU_RAM_QAM_SL_SIG_POWER__A, DRXK_QAM_SL_SIG_POWER_QAM256); - if (status < 0) - goto error; - - - /* QAM Loop Controller Coeficients */ - - status = write16(state, SCU_RAM_QAM_LC_CA_FINE__A, 15); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CA_COARSE__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_MEDIUM__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EP_COARSE__A, 24); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_FINE__A, 12); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_MEDIUM__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_EI_COARSE__A, 16); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_LC_CP_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_MEDIUM__A, 50); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CP_COARSE__A, 250); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_MEDIUM__A, 50); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CI_COARSE__A, 125); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_FINE__A, 16); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_MEDIUM__A, 25); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF_COARSE__A, 48); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_FINE__A, 5); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_MEDIUM__A, 10); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_LC_CF1_COARSE__A, 10); - if (status < 0) - goto error; - - - /* QAM State Machine (FSM) Thresholds */ - - status = write16(state, SCU_RAM_QAM_FSM_RTH__A, 50); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_FTH__A, 60); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_CTH__A, 80); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_PTH__A, 100); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_QTH__A, 150); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_MTH__A, 110); - if (status < 0) - goto error; - - status = write16(state, SCU_RAM_QAM_FSM_RATE_LIM__A, 40); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_COUNT_LIM__A, 4); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_FREQ_LIM__A, 12); - if (status < 0) - goto error; - - - /* QAM FSM Tracking Parameters */ - - status = write16(state, SCU_RAM_QAM_FSM_MEDIAN_AV_MULT__A, (u16) 8); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_RADIUS_AV_LIMIT__A, (u16) 74); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET1__A, (u16) 18); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET2__A, (u16) 13); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET3__A, (u16) 7); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET4__A, (u16) 0); - if (status < 0) - goto error; - status = write16(state, SCU_RAM_QAM_FSM_LCAVG_OFFSET5__A, (u16) -8); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - - -/*============================================================================*/ -/** -* \brief Reset QAM block. -* \param demod: instance of demod. -* \param channel: pointer to channel data. -* \return DRXStatus_t. -*/ -static int QAMResetQAM(struct drxk_state *state) -{ - int status; - u16 cmdResult; - - dprintk(1, "\n"); - /* Stop QAM comstate->m_exec */ - status = write16(state, QAM_COMM_EXEC__A, QAM_COMM_EXEC_STOP); - if (status < 0) - goto error; - - status = scu_command(state, SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_RESET, 0, NULL, 1, &cmdResult); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -/*============================================================================*/ - -/** -* \brief Set QAM symbolrate. -* \param demod: instance of demod. -* \param channel: pointer to channel data. -* \return DRXStatus_t. -*/ -static int QAMSetSymbolrate(struct drxk_state *state) -{ - u32 adcFrequency = 0; - u32 symbFreq = 0; - u32 iqmRcRate = 0; - u16 ratesel = 0; - u32 lcSymbRate = 0; - int status; - - dprintk(1, "\n"); - /* Select & calculate correct IQM rate */ - adcFrequency = (state->m_sysClockFreq * 1000) / 3; - ratesel = 0; - /* printk(KERN_DEBUG "drxk: SR %d\n", state->props.symbol_rate); */ - if (state->props.symbol_rate <= 1188750) - ratesel = 3; - else if (state->props.symbol_rate <= 2377500) - ratesel = 2; - else if (state->props.symbol_rate <= 4755000) - ratesel = 1; - status = write16(state, IQM_FD_RATESEL__A, ratesel); - if (status < 0) - goto error; - - /* - IqmRcRate = ((Fadc / (symbolrate * (4<props.symbol_rate * (1 << ratesel); - if (symbFreq == 0) { - /* Divide by zero */ - status = -EINVAL; - goto error; - } - iqmRcRate = (adcFrequency / symbFreq) * (1 << 21) + - (Frac28a((adcFrequency % symbFreq), symbFreq) >> 7) - - (1 << 23); - status = write32(state, IQM_RC_RATE_OFS_LO__A, iqmRcRate); - if (status < 0) - goto error; - state->m_iqmRcRate = iqmRcRate; - /* - LcSymbFreq = round (.125 * symbolrate / adcFreq * (1<<15)) - */ - symbFreq = state->props.symbol_rate; - if (adcFrequency == 0) { - /* Divide by zero */ - status = -EINVAL; - goto error; - } - lcSymbRate = (symbFreq / adcFrequency) * (1 << 12) + - (Frac28a((symbFreq % adcFrequency), adcFrequency) >> - 16); - if (lcSymbRate > 511) - lcSymbRate = 511; - status = write16(state, QAM_LC_SYMBOL_FREQ__A, (u16) lcSymbRate); - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -/*============================================================================*/ - -/** -* \brief Get QAM lock status. -* \param demod: instance of demod. -* \param channel: pointer to channel data. -* \return DRXStatus_t. -*/ - -static int GetQAMLockStatus(struct drxk_state *state, u32 *pLockStatus) -{ - int status; - u16 Result[2] = { 0, 0 }; - - dprintk(1, "\n"); - *pLockStatus = NOT_LOCKED; - status = scu_command(state, - SCU_RAM_COMMAND_STANDARD_QAM | - SCU_RAM_COMMAND_CMD_DEMOD_GET_LOCK, 0, NULL, 2, - Result); - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - if (Result[1] < SCU_RAM_QAM_LOCKED_LOCKED_DEMOD_LOCKED) { - /* 0x0000 NOT LOCKED */ - } else if (Result[1] < SCU_RAM_QAM_LOCKED_LOCKED_LOCKED) { - /* 0x4000 DEMOD LOCKED */ - *pLockStatus = DEMOD_LOCK; - } else if (Result[1] < SCU_RAM_QAM_LOCKED_LOCKED_NEVER_LOCK) { - /* 0x8000 DEMOD + FEC LOCKED (system lock) */ - *pLockStatus = MPEG_LOCK; - } else { - /* 0xC000 NEVER LOCKED */ - /* (system will never be able to lock to the signal) */ - /* TODO: check this, intermediate & standard specific lock states are not - taken into account here */ - *pLockStatus = NEVER_LOCK; - } - return status; -} - -#define QAM_MIRROR__M 0x03 -#define QAM_MIRROR_NORMAL 0x00 -#define QAM_MIRRORED 0x01 -#define QAM_MIRROR_AUTO_ON 0x02 -#define QAM_LOCKRANGE__M 0x10 -#define QAM_LOCKRANGE_NORMAL 0x10 - -static int QAMDemodulatorCommand(struct drxk_state *state, - int numberOfParameters) -{ - int status; - u16 cmdResult; - u16 setParamParameters[4] = { 0, 0, 0, 0 }; - - setParamParameters[0] = state->m_Constellation; /* modulation */ - setParamParameters[1] = DRXK_QAM_I12_J17; /* interleave mode */ - - if (numberOfParameters == 2) { - u16 setEnvParameters[1] = { 0 }; - - if (state->m_OperationMode == OM_QAM_ITU_C) - setEnvParameters[0] = QAM_TOP_ANNEX_C; - else - setEnvParameters[0] = QAM_TOP_ANNEX_A; - - status = scu_command(state, - SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_SET_ENV, - 1, setEnvParameters, 1, &cmdResult); - if (status < 0) - goto error; - - status = scu_command(state, - SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_SET_PARAM, - numberOfParameters, setParamParameters, - 1, &cmdResult); - } else if (numberOfParameters == 4) { - if (state->m_OperationMode == OM_QAM_ITU_C) - setParamParameters[2] = QAM_TOP_ANNEX_C; - else - setParamParameters[2] = QAM_TOP_ANNEX_A; - - setParamParameters[3] |= (QAM_MIRROR_AUTO_ON); - /* Env parameters */ - /* check for LOCKRANGE Extented */ - /* setParamParameters[3] |= QAM_LOCKRANGE_NORMAL; */ - - status = scu_command(state, - SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_SET_PARAM, - numberOfParameters, setParamParameters, - 1, &cmdResult); - } else { - printk(KERN_WARNING "drxk: Unknown QAM demodulator parameter " - "count %d\n", numberOfParameters); - } - -error: - if (status < 0) - printk(KERN_WARNING "drxk: Warning %d on %s\n", - status, __func__); - return status; -} - -static int SetQAM(struct drxk_state *state, u16 IntermediateFreqkHz, - s32 tunerFreqOffset) -{ - int status; - u16 cmdResult; - int qamDemodParamCount = state->qam_demod_parameter_count; - - dprintk(1, "\n"); - /* - * STEP 1: reset demodulator - * resets FEC DI and FEC RS - * resets QAM block - * resets SCU variables - */ - status = write16(state, FEC_DI_COMM_EXEC__A, FEC_DI_COMM_EXEC_STOP); - if (status < 0) - goto error; - status = write16(state, FEC_RS_COMM_EXEC__A, FEC_RS_COMM_EXEC_STOP); - if (status < 0) - goto error; - status = QAMResetQAM(state); - if (status < 0) - goto error; - - /* - * STEP 2: configure demodulator - * -set params; resets IQM,QAM,FEC HW; initializes some - * SCU variables - */ - status = QAMSetSymbolrate(state); - if (status < 0) - goto error; - - /* Set params */ - switch (state->props.modulation) { - case QAM_256: - state->m_Constellation = DRX_CONSTELLATION_QAM256; - break; - case QAM_AUTO: - case QAM_64: - state->m_Constellation = DRX_CONSTELLATION_QAM64; - break; - case QAM_16: - state->m_Constellation = DRX_CONSTELLATION_QAM16; - break; - case QAM_32: - state->m_Constellation = DRX_CONSTELLATION_QAM32; - break; - case QAM_128: - state->m_Constellation = DRX_CONSTELLATION_QAM128; - break; - default: - status = -EINVAL; - break; - } - if (status < 0) - goto error; - - /* Use the 4-parameter if it's requested or we're probing for - * the correct command. */ - if (state->qam_demod_parameter_count == 4 - || !state->qam_demod_parameter_count) { - qamDemodParamCount = 4; - status = QAMDemodulatorCommand(state, qamDemodParamCount); - } - - /* Use the 2-parameter command if it was requested or if we're - * probing for the correct command and the 4-parameter command - * failed. */ - if (state->qam_demod_parameter_count == 2 - || (!state->qam_demod_parameter_count && status < 0)) { - qamDemodParamCount = 2; - status = QAMDemodulatorCommand(state, qamDemodParamCount); - } - - if (status < 0) { - dprintk(1, "Could not set demodulator parameters. Make " - "sure qam_demod_parameter_count (%d) is correct for " - "your firmware (%s).\n", - state->qam_demod_parameter_count, - state->microcode_name); - goto error; - } else if (!state->qam_demod_parameter_count) { - dprintk(1, "Auto-probing the correct QAM demodulator command " - "parameters was successful - using %d parameters.\n", - qamDemodParamCount); - - /* - * One of our commands was successful. We don't need to - * auto-probe anymore, now that we got the correct command. - */ - state->qam_demod_parameter_count = qamDemodParamCount; - } - - /* - * STEP 3: enable the system in a mode where the ADC provides valid - * signal setup modulation independent registers - */ -#if 0 - status = SetFrequency(channel, tunerFreqOffset)); - if (status < 0) - goto error; -#endif - status = SetFrequencyShifter(state, IntermediateFreqkHz, tunerFreqOffset, true); - if (status < 0) - goto error; - - /* Setup BER measurement */ - status = SetQAMMeasurement(state, state->m_Constellation, state->props.symbol_rate); - if (status < 0) - goto error; - - /* Reset default values */ - status = write16(state, IQM_CF_SCALE_SH__A, IQM_CF_SCALE_SH__PRE); - if (status < 0) - goto error; - status = write16(state, QAM_SY_TIMEOUT__A, QAM_SY_TIMEOUT__PRE); - if (status < 0) - goto error; - - /* Reset default LC values */ - status = write16(state, QAM_LC_RATE_LIMIT__A, 3); - if (status < 0) - goto error; - status = write16(state, QAM_LC_LPF_FACTORP__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_LC_LPF_FACTORI__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_LC_MODE__A, 7); - if (status < 0) - goto error; - - status = write16(state, QAM_LC_QUAL_TAB0__A, 1); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB1__A, 1); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB2__A, 1); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB3__A, 1); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB4__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB5__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB6__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB8__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB9__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB10__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB12__A, 2); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB15__A, 3); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB16__A, 3); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB20__A, 4); - if (status < 0) - goto error; - status = write16(state, QAM_LC_QUAL_TAB25__A, 4); - if (status < 0) - goto error; - - /* Mirroring, QAM-block starting point not inverted */ - status = write16(state, QAM_SY_SP_INV__A, QAM_SY_SP_INV_SPECTRUM_INV_DIS); - if (status < 0) - goto error; - - /* Halt SCU to enable safe non-atomic accesses */ - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_HOLD); - if (status < 0) - goto error; - - /* STEP 4: modulation specific setup */ - switch (state->props.modulation) { - case QAM_16: - status = SetQAM16(state); - break; - case QAM_32: - status = SetQAM32(state); - break; - case QAM_AUTO: - case QAM_64: - status = SetQAM64(state); - break; - case QAM_128: - status = SetQAM128(state); - break; - case QAM_256: - status = SetQAM256(state); - break; - default: - status = -EINVAL; - break; - } - if (status < 0) - goto error; - - /* Activate SCU to enable SCU commands */ - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); - if (status < 0) - goto error; - - /* Re-configure MPEG output, requires knowledge of channel bitrate */ - /* extAttr->currentChannel.modulation = channel->modulation; */ - /* extAttr->currentChannel.symbolrate = channel->symbolrate; */ - status = MPEGTSDtoSetup(state, state->m_OperationMode); - if (status < 0) - goto error; - - /* Start processes */ - status = MPEGTSStart(state); - if (status < 0) - goto error; - status = write16(state, FEC_COMM_EXEC__A, FEC_COMM_EXEC_ACTIVE); - if (status < 0) - goto error; - status = write16(state, QAM_COMM_EXEC__A, QAM_COMM_EXEC_ACTIVE); - if (status < 0) - goto error; - status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_ACTIVE); - if (status < 0) - goto error; - - /* STEP 5: start QAM demodulator (starts FEC, QAM and IQM HW) */ - status = scu_command(state, SCU_RAM_COMMAND_STANDARD_QAM | SCU_RAM_COMMAND_CMD_DEMOD_START, 0, NULL, 1, &cmdResult); - if (status < 0) - goto error; - - /* update global DRXK data container */ -/*? extAttr->qamInterleaveMode = DRXK_QAM_I12_J17; */ - -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int SetQAMStandard(struct drxk_state *state, - enum OperationMode oMode) -{ - int status; -#ifdef DRXK_QAM_TAPS -#define DRXK_QAMA_TAPS_SELECT -#include "drxk_filters.h" -#undef DRXK_QAMA_TAPS_SELECT -#endif - - dprintk(1, "\n"); - - /* added antenna switch */ - SwitchAntennaToQAM(state); - - /* Ensure correct power-up mode */ - status = PowerUpQAM(state); - if (status < 0) - goto error; - /* Reset QAM block */ - status = QAMResetQAM(state); - if (status < 0) - goto error; - - /* Setup IQM */ - - status = write16(state, IQM_COMM_EXEC__A, IQM_COMM_EXEC_B_STOP); - if (status < 0) - goto error; - status = write16(state, IQM_AF_AMUX__A, IQM_AF_AMUX_SIGNAL2ADC); - if (status < 0) - goto error; - - /* Upload IQM Channel Filter settings by - boot loader from ROM table */ - switch (oMode) { - case OM_QAM_ITU_A: - status = BLChainCmd(state, DRXK_BL_ROM_OFFSET_TAPS_ITU_A, DRXK_BLCC_NR_ELEMENTS_TAPS, DRXK_BLC_TIMEOUT); - break; - case OM_QAM_ITU_C: - status = BLDirectCmd(state, IQM_CF_TAP_RE0__A, DRXK_BL_ROM_OFFSET_TAPS_ITU_C, DRXK_BLDC_NR_ELEMENTS_TAPS, DRXK_BLC_TIMEOUT); - if (status < 0) - goto error; - status = BLDirectCmd(state, IQM_CF_TAP_IM0__A, DRXK_BL_ROM_OFFSET_TAPS_ITU_C, DRXK_BLDC_NR_ELEMENTS_TAPS, DRXK_BLC_TIMEOUT); - break; - default: - status = -EINVAL; - } - if (status < 0) - goto error; - - status = write16(state, IQM_CF_OUT_ENA__A, (1 << IQM_CF_OUT_ENA_QAM__B)); - if (status < 0) - goto error; - status = write16(state, IQM_CF_SYMMETRIC__A, 0); - if (status < 0) - goto error; - status = write16(state, IQM_CF_MIDTAP__A, ((1 << IQM_CF_MIDTAP_RE__B) | (1 << IQM_CF_MIDTAP_IM__B))); - if (status < 0) - goto error; - - status = write16(state, IQM_RC_STRETCH__A, 21); - if (status < 0) - goto error; - status = write16(state, IQM_AF_CLP_LEN__A, 0); - if (status < 0) - goto error; - status = write16(state, IQM_AF_CLP_TH__A, 448); - if (status < 0) - goto error; - status = write16(state, IQM_AF_SNS_LEN__A, 0); - if (status < 0) - goto error; - status = write16(state, IQM_CF_POW_MEAS_LEN__A, 0); - if (status < 0) - goto error; - - status = write16(state, IQM_FS_ADJ_SEL__A, 1); - if (status < 0) - goto error; - status = write16(state, IQM_RC_ADJ_SEL__A, 1); - if (status < 0) - goto error; - status = write16(state, IQM_CF_ADJ_SEL__A, 1); - if (status < 0) - goto error; - status = write16(state, IQM_AF_UPD_SEL__A, 0); - if (status < 0) - goto error; - - /* IQM Impulse Noise Processing Unit */ - status = write16(state, IQM_CF_CLP_VAL__A, 500); - if (status < 0) - goto error; - status = write16(state, IQM_CF_DATATH__A, 1000); - if (status < 0) - goto error; - status = write16(state, IQM_CF_BYPASSDET__A, 1); - if (status < 0) - goto error; - status = write16(state, IQM_CF_DET_LCT__A, 0); - if (status < 0) - goto error; - status = write16(state, IQM_CF_WND_LEN__A, 1); - if (status < 0) - goto error; - status = write16(state, IQM_CF_PKDTH__A, 1); - if (status < 0) - goto error; - status = write16(state, IQM_AF_INC_BYPASS__A, 1); - if (status < 0) - goto error; - - /* turn on IQMAF. Must be done before setAgc**() */ - status = SetIqmAf(state, true); - if (status < 0) - goto error; - status = write16(state, IQM_AF_START_LOCK__A, 0x01); - if (status < 0) - goto error; - - /* IQM will not be reset from here, sync ADC and update/init AGC */ - status = ADCSynchronization(state); - if (status < 0) - goto error; - - /* Set the FSM step period */ - status = write16(state, SCU_RAM_QAM_FSM_STEP_PERIOD__A, 2000); - if (status < 0) - goto error; - - /* Halt SCU to enable safe non-atomic accesses */ - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_HOLD); - if (status < 0) - goto error; - - /* No more resets of the IQM, current standard correctly set => - now AGCs can be configured. */ - - status = InitAGC(state, true); - if (status < 0) - goto error; - status = SetPreSaw(state, &(state->m_qamPreSawCfg)); - if (status < 0) - goto error; - - /* Configure AGC's */ - status = SetAgcRf(state, &(state->m_qamRfAgcCfg), true); - if (status < 0) - goto error; - status = SetAgcIf(state, &(state->m_qamIfAgcCfg), true); - if (status < 0) - goto error; - - /* Activate SCU to enable SCU commands */ - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int WriteGPIO(struct drxk_state *state) -{ - int status; - u16 value = 0; - - dprintk(1, "\n"); - /* stop lock indicator process */ - status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); - if (status < 0) - goto error; - - /* Write magic word to enable pdr reg write */ - status = write16(state, SIO_TOP_COMM_KEY__A, SIO_TOP_COMM_KEY_KEY); - if (status < 0) - goto error; - - if (state->m_hasSAWSW) { - if (state->UIO_mask & 0x0001) { /* UIO-1 */ - /* write to io pad configuration register - output mode */ - status = write16(state, SIO_PDR_SMA_TX_CFG__A, state->m_GPIOCfg); - if (status < 0) - goto error; - - /* use corresponding bit in io data output registar */ - status = read16(state, SIO_PDR_UIO_OUT_LO__A, &value); - if (status < 0) - goto error; - if ((state->m_GPIO & 0x0001) == 0) - value &= 0x7FFF; /* write zero to 15th bit - 1st UIO */ - else - value |= 0x8000; /* write one to 15th bit - 1st UIO */ - /* write back to io data output register */ - status = write16(state, SIO_PDR_UIO_OUT_LO__A, value); - if (status < 0) - goto error; - } - if (state->UIO_mask & 0x0002) { /* UIO-2 */ - /* write to io pad configuration register - output mode */ - status = write16(state, SIO_PDR_SMA_RX_CFG__A, state->m_GPIOCfg); - if (status < 0) - goto error; - - /* use corresponding bit in io data output registar */ - status = read16(state, SIO_PDR_UIO_OUT_LO__A, &value); - if (status < 0) - goto error; - if ((state->m_GPIO & 0x0002) == 0) - value &= 0xBFFF; /* write zero to 14th bit - 2st UIO */ - else - value |= 0x4000; /* write one to 14th bit - 2st UIO */ - /* write back to io data output register */ - status = write16(state, SIO_PDR_UIO_OUT_LO__A, value); - if (status < 0) - goto error; - } - if (state->UIO_mask & 0x0004) { /* UIO-3 */ - /* write to io pad configuration register - output mode */ - status = write16(state, SIO_PDR_GPIO_CFG__A, state->m_GPIOCfg); - if (status < 0) - goto error; - - /* use corresponding bit in io data output registar */ - status = read16(state, SIO_PDR_UIO_OUT_LO__A, &value); - if (status < 0) - goto error; - if ((state->m_GPIO & 0x0004) == 0) - value &= 0xFFFB; /* write zero to 2nd bit - 3rd UIO */ - else - value |= 0x0004; /* write one to 2nd bit - 3rd UIO */ - /* write back to io data output register */ - status = write16(state, SIO_PDR_UIO_OUT_LO__A, value); - if (status < 0) - goto error; - } - } - /* Write magic word to disable pdr reg write */ - status = write16(state, SIO_TOP_COMM_KEY__A, 0x0000); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int SwitchAntennaToQAM(struct drxk_state *state) -{ - int status = 0; - bool gpio_state; - - dprintk(1, "\n"); - - if (!state->antenna_gpio) - return 0; - - gpio_state = state->m_GPIO & state->antenna_gpio; - - if (state->antenna_dvbt ^ gpio_state) { - /* Antenna is on DVB-T mode. Switch */ - if (state->antenna_dvbt) - state->m_GPIO &= ~state->antenna_gpio; - else - state->m_GPIO |= state->antenna_gpio; - status = WriteGPIO(state); - } - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - -static int SwitchAntennaToDVBT(struct drxk_state *state) -{ - int status = 0; - bool gpio_state; - - dprintk(1, "\n"); - - if (!state->antenna_gpio) - return 0; - - gpio_state = state->m_GPIO & state->antenna_gpio; - - if (!(state->antenna_dvbt ^ gpio_state)) { - /* Antenna is on DVB-C mode. Switch */ - if (state->antenna_dvbt) - state->m_GPIO |= state->antenna_gpio; - else - state->m_GPIO &= ~state->antenna_gpio; - status = WriteGPIO(state); - } - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - return status; -} - - -static int PowerDownDevice(struct drxk_state *state) -{ - /* Power down to requested mode */ - /* Backup some register settings */ - /* Set pins with possible pull-ups connected to them in input mode */ - /* Analog power down */ - /* ADC power down */ - /* Power down device */ - int status; - - dprintk(1, "\n"); - if (state->m_bPDownOpenBridge) { - /* Open I2C bridge before power down of DRXK */ - status = ConfigureI2CBridge(state, true); - if (status < 0) - goto error; - } - /* driver 0.9.0 */ - status = DVBTEnableOFDMTokenRing(state, false); - if (status < 0) - goto error; - - status = write16(state, SIO_CC_PWD_MODE__A, SIO_CC_PWD_MODE_LEVEL_CLOCK); - if (status < 0) - goto error; - status = write16(state, SIO_CC_UPDATE__A, SIO_CC_UPDATE_KEY); - if (status < 0) - goto error; - state->m_HICfgCtrl |= SIO_HI_RA_RAM_PAR_5_CFG_SLEEP_ZZZ; - status = HI_CfgCommand(state); -error: - if (status < 0) - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - - return status; -} - -static int init_drxk(struct drxk_state *state) -{ - int status = 0, n = 0; - enum DRXPowerMode powerMode = DRXK_POWER_DOWN_OFDM; - u16 driverVersion; - - dprintk(1, "\n"); - if ((state->m_DrxkState == DRXK_UNINITIALIZED)) { - drxk_i2c_lock(state); - status = PowerUpDevice(state); - if (status < 0) - goto error; - status = DRXX_Open(state); - if (status < 0) - goto error; - /* Soft reset of OFDM-, sys- and osc-clockdomain */ - status = write16(state, SIO_CC_SOFT_RST__A, SIO_CC_SOFT_RST_OFDM__M | SIO_CC_SOFT_RST_SYS__M | SIO_CC_SOFT_RST_OSC__M); - if (status < 0) - goto error; - status = write16(state, SIO_CC_UPDATE__A, SIO_CC_UPDATE_KEY); - if (status < 0) - goto error; - /* TODO is this needed, if yes how much delay in worst case scenario */ - msleep(1); - state->m_DRXK_A3_PATCH_CODE = true; - status = GetDeviceCapabilities(state); - if (status < 0) - goto error; - - /* Bridge delay, uses oscilator clock */ - /* Delay = (delay (nano seconds) * oscclk (kHz))/ 1000 */ - /* SDA brdige delay */ - state->m_HICfgBridgeDelay = - (u16) ((state->m_oscClockFreq / 1000) * - HI_I2C_BRIDGE_DELAY) / 1000; - /* Clipping */ - if (state->m_HICfgBridgeDelay > - SIO_HI_RA_RAM_PAR_3_CFG_DBL_SDA__M) { - state->m_HICfgBridgeDelay = - SIO_HI_RA_RAM_PAR_3_CFG_DBL_SDA__M; - } - /* SCL bridge delay, same as SDA for now */ - state->m_HICfgBridgeDelay += - state->m_HICfgBridgeDelay << - SIO_HI_RA_RAM_PAR_3_CFG_DBL_SCL__B; - - status = InitHI(state); - if (status < 0) - goto error; - /* disable various processes */ -#if NOA1ROM - if (!(state->m_DRXK_A1_ROM_CODE) - && !(state->m_DRXK_A2_ROM_CODE)) -#endif - { - status = write16(state, SCU_RAM_GPIO__A, SCU_RAM_GPIO_HW_LOCK_IND_DISABLE); - if (status < 0) - goto error; - } - - /* disable MPEG port */ - status = MPEGTSDisable(state); - if (status < 0) - goto error; - - /* Stop AUD and SCU */ - status = write16(state, AUD_COMM_EXEC__A, AUD_COMM_EXEC_STOP); - if (status < 0) - goto error; - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_STOP); - if (status < 0) - goto error; - - /* enable token-ring bus through OFDM block for possible ucode upload */ - status = write16(state, SIO_OFDM_SH_OFDM_RING_ENABLE__A, SIO_OFDM_SH_OFDM_RING_ENABLE_ON); - if (status < 0) - goto error; - - /* include boot loader section */ - status = write16(state, SIO_BL_COMM_EXEC__A, SIO_BL_COMM_EXEC_ACTIVE); - if (status < 0) - goto error; - status = BLChainCmd(state, 0, 6, 100); - if (status < 0) - goto error; - - if (state->fw) { - status = DownloadMicrocode(state, state->fw->data, - state->fw->size); - if (status < 0) - goto error; - } - - /* disable token-ring bus through OFDM block for possible ucode upload */ - status = write16(state, SIO_OFDM_SH_OFDM_RING_ENABLE__A, SIO_OFDM_SH_OFDM_RING_ENABLE_OFF); - if (status < 0) - goto error; - - /* Run SCU for a little while to initialize microcode version numbers */ - status = write16(state, SCU_COMM_EXEC__A, SCU_COMM_EXEC_ACTIVE); - if (status < 0) - goto error; - status = DRXX_Open(state); - if (status < 0) - goto error; - /* added for test */ - msleep(30); - - powerMode = DRXK_POWER_DOWN_OFDM; - status = CtrlPowerMode(state, &powerMode); - if (status < 0) - goto error; - - /* Stamp driver version number in SCU data RAM in BCD code - Done to enable field application engineers to retreive drxdriver version - via I2C from SCU RAM. - Not using SCU command interface for SCU register access since no - microcode may be present. - */ - driverVersion = - (((DRXK_VERSION_MAJOR / 100) % 10) << 12) + - (((DRXK_VERSION_MAJOR / 10) % 10) << 8) + - ((DRXK_VERSION_MAJOR % 10) << 4) + - (DRXK_VERSION_MINOR % 10); - status = write16(state, SCU_RAM_DRIVER_VER_HI__A, driverVersion); - if (status < 0) - goto error; - driverVersion = - (((DRXK_VERSION_PATCH / 1000) % 10) << 12) + - (((DRXK_VERSION_PATCH / 100) % 10) << 8) + - (((DRXK_VERSION_PATCH / 10) % 10) << 4) + - (DRXK_VERSION_PATCH % 10); - status = write16(state, SCU_RAM_DRIVER_VER_LO__A, driverVersion); - if (status < 0) - goto error; - - printk(KERN_INFO "DRXK driver version %d.%d.%d\n", - DRXK_VERSION_MAJOR, DRXK_VERSION_MINOR, - DRXK_VERSION_PATCH); - - /* Dirty fix of default values for ROM/PATCH microcode - Dirty because this fix makes it impossible to setup suitable values - before calling DRX_Open. This solution requires changes to RF AGC speed - to be done via the CTRL function after calling DRX_Open */ - - /* m_dvbtRfAgcCfg.speed = 3; */ - - /* Reset driver debug flags to 0 */ - status = write16(state, SCU_RAM_DRIVER_DEBUG__A, 0); - if (status < 0) - goto error; - /* driver 0.9.0 */ - /* Setup FEC OC: - NOTE: No more full FEC resets allowed afterwards!! */ - status = write16(state, FEC_COMM_EXEC__A, FEC_COMM_EXEC_STOP); - if (status < 0) - goto error; - /* MPEGTS functions are still the same */ - status = MPEGTSDtoInit(state); - if (status < 0) - goto error; - status = MPEGTSStop(state); - if (status < 0) - goto error; - status = MPEGTSConfigurePolarity(state); - if (status < 0) - goto error; - status = MPEGTSConfigurePins(state, state->m_enableMPEGOutput); - if (status < 0) - goto error; - /* added: configure GPIO */ - status = WriteGPIO(state); - if (status < 0) - goto error; - - state->m_DrxkState = DRXK_STOPPED; - - if (state->m_bPowerDown) { - status = PowerDownDevice(state); - if (status < 0) - goto error; - state->m_DrxkState = DRXK_POWERED_DOWN; - } else - state->m_DrxkState = DRXK_STOPPED; - - /* Initialize the supported delivery systems */ - n = 0; - if (state->m_hasDVBC) { - state->frontend.ops.delsys[n++] = SYS_DVBC_ANNEX_A; - state->frontend.ops.delsys[n++] = SYS_DVBC_ANNEX_C; - strlcat(state->frontend.ops.info.name, " DVB-C", - sizeof(state->frontend.ops.info.name)); - } - if (state->m_hasDVBT) { - state->frontend.ops.delsys[n++] = SYS_DVBT; - strlcat(state->frontend.ops.info.name, " DVB-T", - sizeof(state->frontend.ops.info.name)); - } - drxk_i2c_unlock(state); - } -error: - if (status < 0) { - state->m_DrxkState = DRXK_NO_DEV; - drxk_i2c_unlock(state); - printk(KERN_ERR "drxk: Error %d on %s\n", status, __func__); - } - - return status; -} - -static void load_firmware_cb(const struct firmware *fw, - void *context) -{ - struct drxk_state *state = context; - - dprintk(1, ": %s\n", fw ? "firmware loaded" : "firmware not loaded"); - if (!fw) { - printk(KERN_ERR - "drxk: Could not load firmware file %s.\n", - state->microcode_name); - printk(KERN_INFO - "drxk: Copy %s to your hotplug directory!\n", - state->microcode_name); - state->microcode_name = NULL; - - /* - * As firmware is now load asynchronous, it is not possible - * anymore to fail at frontend attach. We might silently - * return here, and hope that the driver won't crash. - * We might also change all DVB callbacks to return -ENODEV - * if the device is not initialized. - * As the DRX-K devices have their own internal firmware, - * let's just hope that it will match a firmware revision - * compatible with this driver and proceed. - */ - } - state->fw = fw; - - init_drxk(state); -} - -static void drxk_release(struct dvb_frontend *fe) -{ - struct drxk_state *state = fe->demodulator_priv; - - dprintk(1, "\n"); - if (state->fw) - release_firmware(state->fw); - - kfree(state); -} - -static int drxk_sleep(struct dvb_frontend *fe) -{ - struct drxk_state *state = fe->demodulator_priv; - - dprintk(1, "\n"); - - if (state->m_DrxkState == DRXK_NO_DEV) - return -ENODEV; - if (state->m_DrxkState == DRXK_UNINITIALIZED) - return 0; - - ShutDown(state); - return 0; -} - -static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct drxk_state *state = fe->demodulator_priv; - - dprintk(1, ": %s\n", enable ? "enable" : "disable"); - - if (state->m_DrxkState == DRXK_NO_DEV) - return -ENODEV; - - return ConfigureI2CBridge(state, enable ? true : false); -} - -static int drxk_set_parameters(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *p = &fe->dtv_property_cache; - u32 delsys = p->delivery_system, old_delsys; - struct drxk_state *state = fe->demodulator_priv; - u32 IF; - - dprintk(1, "\n"); - - if (state->m_DrxkState == DRXK_NO_DEV) - return -ENODEV; - - if (state->m_DrxkState == DRXK_UNINITIALIZED) - return -EAGAIN; - - if (!fe->ops.tuner_ops.get_if_frequency) { - printk(KERN_ERR - "drxk: Error: get_if_frequency() not defined at tuner. Can't work without it!\n"); - return -EINVAL; - } - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - - old_delsys = state->props.delivery_system; - state->props = *p; - - if (old_delsys != delsys) { - ShutDown(state); - switch (delsys) { - case SYS_DVBC_ANNEX_A: - case SYS_DVBC_ANNEX_C: - if (!state->m_hasDVBC) - return -EINVAL; - state->m_itut_annex_c = (delsys == SYS_DVBC_ANNEX_C) ? true : false; - if (state->m_itut_annex_c) - SetOperationMode(state, OM_QAM_ITU_C); - else - SetOperationMode(state, OM_QAM_ITU_A); - break; - case SYS_DVBT: - if (!state->m_hasDVBT) - return -EINVAL; - SetOperationMode(state, OM_DVBT); - break; - default: - return -EINVAL; - } - } - - fe->ops.tuner_ops.get_if_frequency(fe, &IF); - Start(state, 0, IF); - - /* printk(KERN_DEBUG "drxk: %s IF=%d done\n", __func__, IF); */ - - return 0; -} - -static int drxk_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct drxk_state *state = fe->demodulator_priv; - u32 stat; - - dprintk(1, "\n"); - - if (state->m_DrxkState == DRXK_NO_DEV) - return -ENODEV; - if (state->m_DrxkState == DRXK_UNINITIALIZED) - return -EAGAIN; - - *status = 0; - GetLockStatus(state, &stat, 0); - if (stat == MPEG_LOCK) - *status |= 0x1f; - if (stat == FEC_LOCK) - *status |= 0x0f; - if (stat == DEMOD_LOCK) - *status |= 0x07; - return 0; -} - -static int drxk_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct drxk_state *state = fe->demodulator_priv; - - dprintk(1, "\n"); - - if (state->m_DrxkState == DRXK_NO_DEV) - return -ENODEV; - if (state->m_DrxkState == DRXK_UNINITIALIZED) - return -EAGAIN; - - *ber = 0; - return 0; -} - -static int drxk_read_signal_strength(struct dvb_frontend *fe, - u16 *strength) -{ - struct drxk_state *state = fe->demodulator_priv; - u32 val = 0; - - dprintk(1, "\n"); - - if (state->m_DrxkState == DRXK_NO_DEV) - return -ENODEV; - if (state->m_DrxkState == DRXK_UNINITIALIZED) - return -EAGAIN; - - ReadIFAgc(state, &val); - *strength = val & 0xffff; - return 0; -} - -static int drxk_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct drxk_state *state = fe->demodulator_priv; - s32 snr2; - - dprintk(1, "\n"); - - if (state->m_DrxkState == DRXK_NO_DEV) - return -ENODEV; - if (state->m_DrxkState == DRXK_UNINITIALIZED) - return -EAGAIN; - - GetSignalToNoise(state, &snr2); - *snr = snr2 & 0xffff; - return 0; -} - -static int drxk_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - struct drxk_state *state = fe->demodulator_priv; - u16 err; - - dprintk(1, "\n"); - - if (state->m_DrxkState == DRXK_NO_DEV) - return -ENODEV; - if (state->m_DrxkState == DRXK_UNINITIALIZED) - return -EAGAIN; - - DVBTQAMGetAccPktErr(state, &err); - *ucblocks = (u32) err; - return 0; -} - -static int drxk_get_tune_settings(struct dvb_frontend *fe, struct dvb_frontend_tune_settings - *sets) -{ - struct drxk_state *state = fe->demodulator_priv; - struct dtv_frontend_properties *p = &fe->dtv_property_cache; - - dprintk(1, "\n"); - - if (state->m_DrxkState == DRXK_NO_DEV) - return -ENODEV; - if (state->m_DrxkState == DRXK_UNINITIALIZED) - return -EAGAIN; - - switch (p->delivery_system) { - case SYS_DVBC_ANNEX_A: - case SYS_DVBC_ANNEX_C: - case SYS_DVBT: - sets->min_delay_ms = 3000; - sets->max_drift = 0; - sets->step_size = 0; - return 0; - default: - return -EINVAL; - } -} - -static struct dvb_frontend_ops drxk_ops = { - /* .delsys will be filled dynamically */ - .info = { - .name = "DRXK", - .frequency_min = 47000000, - .frequency_max = 865000000, - /* For DVB-C */ - .symbol_rate_min = 870000, - .symbol_rate_max = 11700000, - /* For DVB-T */ - .frequency_stepsize = 166667, - - .caps = FE_CAN_QAM_16 | FE_CAN_QAM_32 | FE_CAN_QAM_64 | - FE_CAN_QAM_128 | FE_CAN_QAM_256 | FE_CAN_FEC_AUTO | - FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_MUTE_TS | - FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_RECOVER | - FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO - }, - - .release = drxk_release, - .sleep = drxk_sleep, - .i2c_gate_ctrl = drxk_gate_ctrl, - - .set_frontend = drxk_set_parameters, - .get_tune_settings = drxk_get_tune_settings, - - .read_status = drxk_read_status, - .read_ber = drxk_read_ber, - .read_signal_strength = drxk_read_signal_strength, - .read_snr = drxk_read_snr, - .read_ucblocks = drxk_read_ucblocks, -}; - -struct dvb_frontend *drxk_attach(const struct drxk_config *config, - struct i2c_adapter *i2c) -{ - struct drxk_state *state = NULL; - u8 adr = config->adr; - int status; - - dprintk(1, "\n"); - state = kzalloc(sizeof(struct drxk_state), GFP_KERNEL); - if (!state) - return NULL; - - state->i2c = i2c; - state->demod_address = adr; - state->single_master = config->single_master; - state->microcode_name = config->microcode_name; - state->qam_demod_parameter_count = config->qam_demod_parameter_count; - state->no_i2c_bridge = config->no_i2c_bridge; - state->antenna_gpio = config->antenna_gpio; - state->antenna_dvbt = config->antenna_dvbt; - state->m_ChunkSize = config->chunk_size; - state->enable_merr_cfg = config->enable_merr_cfg; - - if (config->dynamic_clk) { - state->m_DVBTStaticCLK = 0; - state->m_DVBCStaticCLK = 0; - } else { - state->m_DVBTStaticCLK = 1; - state->m_DVBCStaticCLK = 1; - } - - - if (config->mpeg_out_clk_strength) - state->m_TSClockkStrength = config->mpeg_out_clk_strength & 0x07; - else - state->m_TSClockkStrength = 0x06; - - if (config->parallel_ts) - state->m_enableParallel = true; - else - state->m_enableParallel = false; - - /* NOTE: as more UIO bits will be used, add them to the mask */ - state->UIO_mask = config->antenna_gpio; - - /* Default gpio to DVB-C */ - if (!state->antenna_dvbt && state->antenna_gpio) - state->m_GPIO |= state->antenna_gpio; - else - state->m_GPIO &= ~state->antenna_gpio; - - mutex_init(&state->mutex); - - memcpy(&state->frontend.ops, &drxk_ops, sizeof(drxk_ops)); - state->frontend.demodulator_priv = state; - - init_state(state); - - /* Load firmware and initialize DRX-K */ - if (state->microcode_name) { - status = request_firmware_nowait(THIS_MODULE, 1, - state->microcode_name, - state->i2c->dev.parent, - GFP_KERNEL, - state, load_firmware_cb); - if (status < 0) { - printk(KERN_ERR - "drxk: failed to request a firmware\n"); - return NULL; - } - } else if (init_drxk(state) < 0) - goto error; - - printk(KERN_INFO "drxk: frontend initialized.\n"); - return &state->frontend; - -error: - printk(KERN_ERR "drxk: not found\n"); - kfree(state); - return NULL; -} -EXPORT_SYMBOL(drxk_attach); - -MODULE_DESCRIPTION("DRX-K driver"); -MODULE_AUTHOR("Ralph Metzler"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/dvb-pll.c b/drivers/media/dvb/frontends/dvb-pll.c deleted file mode 100644 index 1ab34838221c..000000000000 --- a/drivers/media/dvb/frontends/dvb-pll.c +++ /dev/null @@ -1,794 +0,0 @@ -/* - * descriptions + helper functions for simple dvb plls. - * - * (c) 2004 Gerd Knorr [SuSE Labs] - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include - -#include "dvb-pll.h" - -struct dvb_pll_priv { - /* pll number */ - int nr; - - /* i2c details */ - int pll_i2c_address; - struct i2c_adapter *i2c; - - /* the PLL descriptor */ - struct dvb_pll_desc *pll_desc; - - /* cached frequency/bandwidth */ - u32 frequency; - u32 bandwidth; -}; - -#define DVB_PLL_MAX 64 - -static unsigned int dvb_pll_devcount; - -static int debug; -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "enable verbose debug messages"); - -static unsigned int id[DVB_PLL_MAX] = - { [ 0 ... (DVB_PLL_MAX-1) ] = DVB_PLL_UNDEFINED }; -module_param_array(id, int, NULL, 0644); -MODULE_PARM_DESC(id, "force pll id to use (DEBUG ONLY)"); - -/* ----------------------------------------------------------- */ - -struct dvb_pll_desc { - char *name; - u32 min; - u32 max; - u32 iffreq; - void (*set)(struct dvb_frontend *fe, u8 *buf); - u8 *initdata; - u8 *initdata2; - u8 *sleepdata; - int count; - struct { - u32 limit; - u32 stepsize; - u8 config; - u8 cb; - } entries[12]; -}; - -/* ----------------------------------------------------------- */ -/* descriptions */ - -static struct dvb_pll_desc dvb_pll_thomson_dtt7579 = { - .name = "Thomson dtt7579", - .min = 177000000, - .max = 858000000, - .iffreq= 36166667, - .sleepdata = (u8[]){ 2, 0xb4, 0x03 }, - .count = 4, - .entries = { - { 443250000, 166667, 0xb4, 0x02 }, - { 542000000, 166667, 0xb4, 0x08 }, - { 771000000, 166667, 0xbc, 0x08 }, - { 999999999, 166667, 0xf4, 0x08 }, - }, -}; - -static void thomson_dtt759x_bw(struct dvb_frontend *fe, u8 *buf) -{ - u32 bw = fe->dtv_property_cache.bandwidth_hz; - if (bw == 7000000) - buf[3] |= 0x10; -} - -static struct dvb_pll_desc dvb_pll_thomson_dtt759x = { - .name = "Thomson dtt759x", - .min = 177000000, - .max = 896000000, - .set = thomson_dtt759x_bw, - .iffreq= 36166667, - .sleepdata = (u8[]){ 2, 0x84, 0x03 }, - .count = 5, - .entries = { - { 264000000, 166667, 0xb4, 0x02 }, - { 470000000, 166667, 0xbc, 0x02 }, - { 735000000, 166667, 0xbc, 0x08 }, - { 835000000, 166667, 0xf4, 0x08 }, - { 999999999, 166667, 0xfc, 0x08 }, - }, -}; - -static struct dvb_pll_desc dvb_pll_lg_z201 = { - .name = "LG z201", - .min = 174000000, - .max = 862000000, - .iffreq= 36166667, - .sleepdata = (u8[]){ 2, 0xbc, 0x03 }, - .count = 5, - .entries = { - { 157500000, 166667, 0xbc, 0x01 }, - { 443250000, 166667, 0xbc, 0x02 }, - { 542000000, 166667, 0xbc, 0x04 }, - { 830000000, 166667, 0xf4, 0x04 }, - { 999999999, 166667, 0xfc, 0x04 }, - }, -}; - -static struct dvb_pll_desc dvb_pll_unknown_1 = { - .name = "unknown 1", /* used by dntv live dvb-t */ - .min = 174000000, - .max = 862000000, - .iffreq= 36166667, - .count = 9, - .entries = { - { 150000000, 166667, 0xb4, 0x01 }, - { 173000000, 166667, 0xbc, 0x01 }, - { 250000000, 166667, 0xb4, 0x02 }, - { 400000000, 166667, 0xbc, 0x02 }, - { 420000000, 166667, 0xf4, 0x02 }, - { 470000000, 166667, 0xfc, 0x02 }, - { 600000000, 166667, 0xbc, 0x08 }, - { 730000000, 166667, 0xf4, 0x08 }, - { 999999999, 166667, 0xfc, 0x08 }, - }, -}; - -/* Infineon TUA6010XS - * used in Thomson Cable Tuner - */ -static struct dvb_pll_desc dvb_pll_tua6010xs = { - .name = "Infineon TUA6010XS", - .min = 44250000, - .max = 858000000, - .iffreq= 36125000, - .count = 3, - .entries = { - { 115750000, 62500, 0x8e, 0x03 }, - { 403250000, 62500, 0x8e, 0x06 }, - { 999999999, 62500, 0x8e, 0x85 }, - }, -}; - -/* Panasonic env57h1xd5 (some Philips PLL ?) */ -static struct dvb_pll_desc dvb_pll_env57h1xd5 = { - .name = "Panasonic ENV57H1XD5", - .min = 44250000, - .max = 858000000, - .iffreq= 36125000, - .count = 4, - .entries = { - { 153000000, 166667, 0xc2, 0x41 }, - { 470000000, 166667, 0xc2, 0x42 }, - { 526000000, 166667, 0xc2, 0x84 }, - { 999999999, 166667, 0xc2, 0xa4 }, - }, -}; - -/* Philips TDA6650/TDA6651 - * used in Panasonic ENV77H11D5 - */ -static void tda665x_bw(struct dvb_frontend *fe, u8 *buf) -{ - u32 bw = fe->dtv_property_cache.bandwidth_hz; - if (bw == 8000000) - buf[3] |= 0x08; -} - -static struct dvb_pll_desc dvb_pll_tda665x = { - .name = "Philips TDA6650/TDA6651", - .min = 44250000, - .max = 858000000, - .set = tda665x_bw, - .iffreq= 36166667, - .initdata = (u8[]){ 4, 0x0b, 0xf5, 0x85, 0xab }, - .count = 12, - .entries = { - { 93834000, 166667, 0xca, 0x61 /* 011 0 0 0 01 */ }, - { 123834000, 166667, 0xca, 0xa1 /* 101 0 0 0 01 */ }, - { 161000000, 166667, 0xca, 0xa1 /* 101 0 0 0 01 */ }, - { 163834000, 166667, 0xca, 0xc2 /* 110 0 0 0 10 */ }, - { 253834000, 166667, 0xca, 0x62 /* 011 0 0 0 10 */ }, - { 383834000, 166667, 0xca, 0xa2 /* 101 0 0 0 10 */ }, - { 443834000, 166667, 0xca, 0xc2 /* 110 0 0 0 10 */ }, - { 444000000, 166667, 0xca, 0xc4 /* 110 0 0 1 00 */ }, - { 583834000, 166667, 0xca, 0x64 /* 011 0 0 1 00 */ }, - { 793834000, 166667, 0xca, 0xa4 /* 101 0 0 1 00 */ }, - { 444834000, 166667, 0xca, 0xc4 /* 110 0 0 1 00 */ }, - { 861000000, 166667, 0xca, 0xe4 /* 111 0 0 1 00 */ }, - } -}; - -/* Infineon TUA6034 - * used in LG TDTP E102P - */ -static void tua6034_bw(struct dvb_frontend *fe, u8 *buf) -{ - u32 bw = fe->dtv_property_cache.bandwidth_hz; - if (bw == 7000000) - buf[3] |= 0x08; -} - -static struct dvb_pll_desc dvb_pll_tua6034 = { - .name = "Infineon TUA6034", - .min = 44250000, - .max = 858000000, - .iffreq= 36166667, - .count = 3, - .set = tua6034_bw, - .entries = { - { 174500000, 62500, 0xce, 0x01 }, - { 230000000, 62500, 0xce, 0x02 }, - { 999999999, 62500, 0xce, 0x04 }, - }, -}; - -/* ALPS TDED4 - * used in Nebula-Cards and USB boxes - */ -static void tded4_bw(struct dvb_frontend *fe, u8 *buf) -{ - u32 bw = fe->dtv_property_cache.bandwidth_hz; - if (bw == 8000000) - buf[3] |= 0x04; -} - -static struct dvb_pll_desc dvb_pll_tded4 = { - .name = "ALPS TDED4", - .min = 47000000, - .max = 863000000, - .iffreq= 36166667, - .set = tded4_bw, - .count = 4, - .entries = { - { 153000000, 166667, 0x85, 0x01 }, - { 470000000, 166667, 0x85, 0x02 }, - { 823000000, 166667, 0x85, 0x08 }, - { 999999999, 166667, 0x85, 0x88 }, - } -}; - -/* ALPS TDHU2 - * used in AverTVHD MCE A180 - */ -static struct dvb_pll_desc dvb_pll_tdhu2 = { - .name = "ALPS TDHU2", - .min = 54000000, - .max = 864000000, - .iffreq= 44000000, - .count = 4, - .entries = { - { 162000000, 62500, 0x85, 0x01 }, - { 426000000, 62500, 0x85, 0x02 }, - { 782000000, 62500, 0x85, 0x08 }, - { 999999999, 62500, 0x85, 0x88 }, - } -}; - -/* Samsung TBMV30111IN / TBMV30712IN1 - * used in Air2PC ATSC - 2nd generation (nxt2002) - */ -static struct dvb_pll_desc dvb_pll_samsung_tbmv = { - .name = "Samsung TBMV30111IN / TBMV30712IN1", - .min = 54000000, - .max = 860000000, - .iffreq= 44000000, - .count = 6, - .entries = { - { 172000000, 166667, 0xb4, 0x01 }, - { 214000000, 166667, 0xb4, 0x02 }, - { 467000000, 166667, 0xbc, 0x02 }, - { 721000000, 166667, 0xbc, 0x08 }, - { 841000000, 166667, 0xf4, 0x08 }, - { 999999999, 166667, 0xfc, 0x02 }, - } -}; - -/* - * Philips SD1878 Tuner. - */ -static struct dvb_pll_desc dvb_pll_philips_sd1878_tda8261 = { - .name = "Philips SD1878", - .min = 950000, - .max = 2150000, - .iffreq= 249, /* zero-IF, offset 249 is to round up */ - .count = 4, - .entries = { - { 1250000, 500, 0xc4, 0x00}, - { 1450000, 500, 0xc4, 0x40}, - { 2050000, 500, 0xc4, 0x80}, - { 2150000, 500, 0xc4, 0xc0}, - }, -}; - -static void opera1_bw(struct dvb_frontend *fe, u8 *buf) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct dvb_pll_priv *priv = fe->tuner_priv; - u32 b_w = (c->symbol_rate * 27) / 32000; - struct i2c_msg msg = { - .addr = priv->pll_i2c_address, - .flags = 0, - .buf = buf, - .len = 4 - }; - int result; - u8 lpf; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - - result = i2c_transfer(priv->i2c, &msg, 1); - if (result != 1) - printk(KERN_ERR "%s: i2c_transfer failed:%d", - __func__, result); - - if (b_w <= 10000) - lpf = 0xc; - else if (b_w <= 12000) - lpf = 0x2; - else if (b_w <= 14000) - lpf = 0xa; - else if (b_w <= 16000) - lpf = 0x6; - else if (b_w <= 18000) - lpf = 0xe; - else if (b_w <= 20000) - lpf = 0x1; - else if (b_w <= 22000) - lpf = 0x9; - else if (b_w <= 24000) - lpf = 0x5; - else if (b_w <= 26000) - lpf = 0xd; - else if (b_w <= 28000) - lpf = 0x3; - else - lpf = 0xb; - buf[2] ^= 0x1c; /* Flip bits 3-5 */ - /* Set lpf */ - buf[2] |= ((lpf >> 2) & 0x3) << 3; - buf[3] |= (lpf & 0x3) << 2; - - return; -} - -static struct dvb_pll_desc dvb_pll_opera1 = { - .name = "Opera Tuner", - .min = 900000, - .max = 2250000, - .initdata = (u8[]){ 4, 0x08, 0xe5, 0xe1, 0x00 }, - .initdata2 = (u8[]){ 4, 0x08, 0xe5, 0xe5, 0x00 }, - .iffreq= 0, - .set = opera1_bw, - .count = 8, - .entries = { - { 1064000, 500, 0xf9, 0xc2 }, - { 1169000, 500, 0xf9, 0xe2 }, - { 1299000, 500, 0xf9, 0x20 }, - { 1444000, 500, 0xf9, 0x40 }, - { 1606000, 500, 0xf9, 0x60 }, - { 1777000, 500, 0xf9, 0x80 }, - { 1941000, 500, 0xf9, 0xa0 }, - { 2250000, 500, 0xf9, 0xc0 }, - } -}; - -static void samsung_dtos403ih102a_set(struct dvb_frontend *fe, u8 *buf) -{ - struct dvb_pll_priv *priv = fe->tuner_priv; - struct i2c_msg msg = { - .addr = priv->pll_i2c_address, - .flags = 0, - .buf = buf, - .len = 4 - }; - int result; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - - result = i2c_transfer(priv->i2c, &msg, 1); - if (result != 1) - printk(KERN_ERR "%s: i2c_transfer failed:%d", - __func__, result); - - buf[2] = 0x9e; - buf[3] = 0x90; - - return; -} - -/* unknown pll used in Samsung DTOS403IH102A DVB-C tuner */ -static struct dvb_pll_desc dvb_pll_samsung_dtos403ih102a = { - .name = "Samsung DTOS403IH102A", - .min = 44250000, - .max = 858000000, - .iffreq = 36125000, - .count = 8, - .set = samsung_dtos403ih102a_set, - .entries = { - { 135000000, 62500, 0xbe, 0x01 }, - { 177000000, 62500, 0xf6, 0x01 }, - { 370000000, 62500, 0xbe, 0x02 }, - { 450000000, 62500, 0xf6, 0x02 }, - { 466000000, 62500, 0xfe, 0x02 }, - { 538000000, 62500, 0xbe, 0x08 }, - { 826000000, 62500, 0xf6, 0x08 }, - { 999999999, 62500, 0xfe, 0x08 }, - } -}; - -/* Samsung TDTC9251DH0 DVB-T NIM, as used on AirStar 2 */ -static struct dvb_pll_desc dvb_pll_samsung_tdtc9251dh0 = { - .name = "Samsung TDTC9251DH0", - .min = 48000000, - .max = 863000000, - .iffreq = 36166667, - .count = 3, - .entries = { - { 157500000, 166667, 0xcc, 0x09 }, - { 443000000, 166667, 0xcc, 0x0a }, - { 863000000, 166667, 0xcc, 0x08 }, - } -}; - -/* Samsung TBDU18132 DVB-S NIM with TSA5059 PLL, used in SkyStar2 DVB-S 2.3 */ -static struct dvb_pll_desc dvb_pll_samsung_tbdu18132 = { - .name = "Samsung TBDU18132", - .min = 950000, - .max = 2150000, /* guesses */ - .iffreq = 0, - .count = 2, - .entries = { - { 1550000, 125, 0x84, 0x82 }, - { 4095937, 125, 0x84, 0x80 }, - } - /* TSA5059 PLL has a 17 bit divisor rather than the 15 bits supported - * by this driver. The two extra bits are 0x60 in the third byte. 15 - * bits is enough for over 4 GHz, which is enough to cover the range - * of this tuner. We could use the additional divisor bits by adding - * more entries, e.g. - { 0x0ffff * 125 + 125/2, 125, 0x84 | 0x20, }, - { 0x17fff * 125 + 125/2, 125, 0x84 | 0x40, }, - { 0x1ffff * 125 + 125/2, 125, 0x84 | 0x60, }, */ -}; - -/* Samsung TBMU24112 DVB-S NIM with SL1935 zero-IF tuner */ -static struct dvb_pll_desc dvb_pll_samsung_tbmu24112 = { - .name = "Samsung TBMU24112", - .min = 950000, - .max = 2150000, /* guesses */ - .iffreq = 0, - .count = 2, - .entries = { - { 1500000, 125, 0x84, 0x18 }, - { 9999999, 125, 0x84, 0x08 }, - } -}; - -/* Alps TDEE4 DVB-C NIM, used on Cablestar 2 */ -/* byte 4 : 1 * * AGD R3 R2 R1 R0 - * byte 5 : C1 * RE RTS BS4 BS3 BS2 BS1 - * AGD = 1, R3 R2 R1 R0 = 0 1 0 1 => byte 4 = 1**10101 = 0x95 - * Range(MHz) C1 * RE RTS BS4 BS3 BS2 BS1 Byte 5 - * 47 - 153 0 * 0 0 0 0 0 1 0x01 - * 153 - 430 0 * 0 0 0 0 1 0 0x02 - * 430 - 822 0 * 0 0 1 0 0 0 0x08 - * 822 - 862 1 * 0 0 1 0 0 0 0x88 */ -static struct dvb_pll_desc dvb_pll_alps_tdee4 = { - .name = "ALPS TDEE4", - .min = 47000000, - .max = 862000000, - .iffreq = 36125000, - .count = 4, - .entries = { - { 153000000, 62500, 0x95, 0x01 }, - { 430000000, 62500, 0x95, 0x02 }, - { 822000000, 62500, 0x95, 0x08 }, - { 999999999, 62500, 0x95, 0x88 }, - } -}; - -/* ----------------------------------------------------------- */ - -static struct dvb_pll_desc *pll_list[] = { - [DVB_PLL_UNDEFINED] = NULL, - [DVB_PLL_THOMSON_DTT7579] = &dvb_pll_thomson_dtt7579, - [DVB_PLL_THOMSON_DTT759X] = &dvb_pll_thomson_dtt759x, - [DVB_PLL_LG_Z201] = &dvb_pll_lg_z201, - [DVB_PLL_UNKNOWN_1] = &dvb_pll_unknown_1, - [DVB_PLL_TUA6010XS] = &dvb_pll_tua6010xs, - [DVB_PLL_ENV57H1XD5] = &dvb_pll_env57h1xd5, - [DVB_PLL_TUA6034] = &dvb_pll_tua6034, - [DVB_PLL_TDA665X] = &dvb_pll_tda665x, - [DVB_PLL_TDED4] = &dvb_pll_tded4, - [DVB_PLL_TDEE4] = &dvb_pll_alps_tdee4, - [DVB_PLL_TDHU2] = &dvb_pll_tdhu2, - [DVB_PLL_SAMSUNG_TBMV] = &dvb_pll_samsung_tbmv, - [DVB_PLL_PHILIPS_SD1878_TDA8261] = &dvb_pll_philips_sd1878_tda8261, - [DVB_PLL_OPERA1] = &dvb_pll_opera1, - [DVB_PLL_SAMSUNG_DTOS403IH102A] = &dvb_pll_samsung_dtos403ih102a, - [DVB_PLL_SAMSUNG_TDTC9251DH0] = &dvb_pll_samsung_tdtc9251dh0, - [DVB_PLL_SAMSUNG_TBDU18132] = &dvb_pll_samsung_tbdu18132, - [DVB_PLL_SAMSUNG_TBMU24112] = &dvb_pll_samsung_tbmu24112, -}; - -/* ----------------------------------------------------------- */ -/* code */ - -static int dvb_pll_configure(struct dvb_frontend *fe, u8 *buf, - const u32 frequency) -{ - struct dvb_pll_priv *priv = fe->tuner_priv; - struct dvb_pll_desc *desc = priv->pll_desc; - u32 div; - int i; - - if (frequency && (frequency < desc->min || frequency > desc->max)) - return -EINVAL; - - for (i = 0; i < desc->count; i++) { - if (frequency > desc->entries[i].limit) - continue; - break; - } - - if (debug) - printk("pll: %s: freq=%d | i=%d/%d\n", desc->name, - frequency, i, desc->count); - if (i == desc->count) - return -EINVAL; - - div = (frequency + desc->iffreq + - desc->entries[i].stepsize/2) / desc->entries[i].stepsize; - buf[0] = div >> 8; - buf[1] = div & 0xff; - buf[2] = desc->entries[i].config; - buf[3] = desc->entries[i].cb; - - if (desc->set) - desc->set(fe, buf); - - if (debug) - printk("pll: %s: div=%d | buf=0x%02x,0x%02x,0x%02x,0x%02x\n", - desc->name, div, buf[0], buf[1], buf[2], buf[3]); - - // calculate the frequency we set it to - return (div * desc->entries[i].stepsize) - desc->iffreq; -} - -static int dvb_pll_release(struct dvb_frontend *fe) -{ - kfree(fe->tuner_priv); - fe->tuner_priv = NULL; - return 0; -} - -static int dvb_pll_sleep(struct dvb_frontend *fe) -{ - struct dvb_pll_priv *priv = fe->tuner_priv; - - if (priv->i2c == NULL) - return -EINVAL; - - if (priv->pll_desc->sleepdata) { - struct i2c_msg msg = { .flags = 0, - .addr = priv->pll_i2c_address, - .buf = priv->pll_desc->sleepdata + 1, - .len = priv->pll_desc->sleepdata[0] }; - - int result; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if ((result = i2c_transfer(priv->i2c, &msg, 1)) != 1) { - return result; - } - return 0; - } - /* Shouldn't be called when initdata is NULL, maybe BUG()? */ - return -EINVAL; -} - -static int dvb_pll_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct dvb_pll_priv *priv = fe->tuner_priv; - u8 buf[4]; - struct i2c_msg msg = - { .addr = priv->pll_i2c_address, .flags = 0, - .buf = buf, .len = sizeof(buf) }; - int result; - u32 frequency = 0; - - if (priv->i2c == NULL) - return -EINVAL; - - result = dvb_pll_configure(fe, buf, c->frequency); - if (result < 0) - return result; - else - frequency = result; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if ((result = i2c_transfer(priv->i2c, &msg, 1)) != 1) { - return result; - } - - priv->frequency = frequency; - priv->bandwidth = c->bandwidth_hz; - - return 0; -} - -static int dvb_pll_calc_regs(struct dvb_frontend *fe, - u8 *buf, int buf_len) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct dvb_pll_priv *priv = fe->tuner_priv; - int result; - u32 frequency = 0; - - if (buf_len < 5) - return -EINVAL; - - result = dvb_pll_configure(fe, buf + 1, c->frequency); - if (result < 0) - return result; - else - frequency = result; - - buf[0] = priv->pll_i2c_address; - - priv->frequency = frequency; - priv->bandwidth = c->bandwidth_hz; - - return 5; -} - -static int dvb_pll_get_frequency(struct dvb_frontend *fe, u32 *frequency) -{ - struct dvb_pll_priv *priv = fe->tuner_priv; - *frequency = priv->frequency; - return 0; -} - -static int dvb_pll_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) -{ - struct dvb_pll_priv *priv = fe->tuner_priv; - *bandwidth = priv->bandwidth; - return 0; -} - -static int dvb_pll_init(struct dvb_frontend *fe) -{ - struct dvb_pll_priv *priv = fe->tuner_priv; - - if (priv->i2c == NULL) - return -EINVAL; - - if (priv->pll_desc->initdata) { - struct i2c_msg msg = { .flags = 0, - .addr = priv->pll_i2c_address, - .buf = priv->pll_desc->initdata + 1, - .len = priv->pll_desc->initdata[0] }; - - int result; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - result = i2c_transfer(priv->i2c, &msg, 1); - if (result != 1) - return result; - if (priv->pll_desc->initdata2) { - msg.buf = priv->pll_desc->initdata2 + 1; - msg.len = priv->pll_desc->initdata2[0]; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - result = i2c_transfer(priv->i2c, &msg, 1); - if (result != 1) - return result; - } - return 0; - } - /* Shouldn't be called when initdata is NULL, maybe BUG()? */ - return -EINVAL; -} - -static struct dvb_tuner_ops dvb_pll_tuner_ops = { - .release = dvb_pll_release, - .sleep = dvb_pll_sleep, - .init = dvb_pll_init, - .set_params = dvb_pll_set_params, - .calc_regs = dvb_pll_calc_regs, - .get_frequency = dvb_pll_get_frequency, - .get_bandwidth = dvb_pll_get_bandwidth, -}; - -struct dvb_frontend *dvb_pll_attach(struct dvb_frontend *fe, int pll_addr, - struct i2c_adapter *i2c, - unsigned int pll_desc_id) -{ - u8 b1 [] = { 0 }; - struct i2c_msg msg = { .addr = pll_addr, .flags = I2C_M_RD, - .buf = b1, .len = 1 }; - struct dvb_pll_priv *priv = NULL; - int ret; - struct dvb_pll_desc *desc; - - if ((id[dvb_pll_devcount] > DVB_PLL_UNDEFINED) && - (id[dvb_pll_devcount] < ARRAY_SIZE(pll_list))) - pll_desc_id = id[dvb_pll_devcount]; - - BUG_ON(pll_desc_id < 1 || pll_desc_id >= ARRAY_SIZE(pll_list)); - - desc = pll_list[pll_desc_id]; - - if (i2c != NULL) { - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - - ret = i2c_transfer (i2c, &msg, 1); - if (ret != 1) - return NULL; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } - - priv = kzalloc(sizeof(struct dvb_pll_priv), GFP_KERNEL); - if (priv == NULL) - return NULL; - - priv->pll_i2c_address = pll_addr; - priv->i2c = i2c; - priv->pll_desc = desc; - priv->nr = dvb_pll_devcount++; - - memcpy(&fe->ops.tuner_ops, &dvb_pll_tuner_ops, - sizeof(struct dvb_tuner_ops)); - - strncpy(fe->ops.tuner_ops.info.name, desc->name, - sizeof(fe->ops.tuner_ops.info.name)); - fe->ops.tuner_ops.info.frequency_min = desc->min; - fe->ops.tuner_ops.info.frequency_max = desc->max; - if (!desc->initdata) - fe->ops.tuner_ops.init = NULL; - if (!desc->sleepdata) - fe->ops.tuner_ops.sleep = NULL; - - fe->tuner_priv = priv; - - if ((debug) || (id[priv->nr] == pll_desc_id)) { - printk("dvb-pll[%d]", priv->nr); - if (i2c != NULL) - printk(" %d-%04x", i2c_adapter_id(i2c), pll_addr); - printk(": id# %d (%s) attached, %s\n", pll_desc_id, desc->name, - id[priv->nr] == pll_desc_id ? - "insmod option" : "autodetected"); - } - - return fe; -} -EXPORT_SYMBOL(dvb_pll_attach); - -MODULE_DESCRIPTION("dvb pll library"); -MODULE_AUTHOR("Gerd Knorr"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/dvb-pll.h b/drivers/media/dvb/frontends/dvb-pll.h deleted file mode 100644 index 086964344c38..000000000000 --- a/drivers/media/dvb/frontends/dvb-pll.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * descriptions + helper functions for simple dvb plls. - */ - -#ifndef __DVB_PLL_H__ -#define __DVB_PLL_H__ - -#include -#include "dvb_frontend.h" - -#define DVB_PLL_UNDEFINED 0 -#define DVB_PLL_THOMSON_DTT7579 1 -#define DVB_PLL_THOMSON_DTT759X 2 -#define DVB_PLL_LG_Z201 3 -#define DVB_PLL_UNKNOWN_1 4 -#define DVB_PLL_TUA6010XS 5 -#define DVB_PLL_ENV57H1XD5 6 -#define DVB_PLL_TUA6034 7 -#define DVB_PLL_TDA665X 8 -#define DVB_PLL_TDED4 9 -#define DVB_PLL_TDHU2 10 -#define DVB_PLL_SAMSUNG_TBMV 11 -#define DVB_PLL_PHILIPS_SD1878_TDA8261 12 -#define DVB_PLL_OPERA1 13 -#define DVB_PLL_SAMSUNG_DTOS403IH102A 14 -#define DVB_PLL_SAMSUNG_TDTC9251DH0 15 -#define DVB_PLL_SAMSUNG_TBDU18132 16 -#define DVB_PLL_SAMSUNG_TBMU24112 17 -#define DVB_PLL_TDEE4 18 - -/** - * Attach a dvb-pll to the supplied frontend structure. - * - * @param fe Frontend to attach to. - * @param pll_addr i2c address of the PLL (if used). - * @param i2c i2c adapter to use (set to NULL if not used). - * @param pll_desc_id dvb_pll_desc to use. - * @return Frontend pointer on success, NULL on failure - */ -#if defined(CONFIG_DVB_PLL) || (defined(CONFIG_DVB_PLL_MODULE) && defined(MODULE)) -extern struct dvb_frontend *dvb_pll_attach(struct dvb_frontend *fe, - int pll_addr, - struct i2c_adapter *i2c, - unsigned int pll_desc_id); -#else -static inline struct dvb_frontend *dvb_pll_attach(struct dvb_frontend *fe, - int pll_addr, - struct i2c_adapter *i2c, - unsigned int pll_desc_id) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif - -#endif diff --git a/drivers/media/dvb/frontends/ec100.c b/drivers/media/dvb/frontends/ec100.c deleted file mode 100644 index c56fddbf53b7..000000000000 --- a/drivers/media/dvb/frontends/ec100.c +++ /dev/null @@ -1,335 +0,0 @@ -/* - * E3C EC100 demodulator driver - * - * Copyright (C) 2009 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "dvb_frontend.h" -#include "ec100_priv.h" -#include "ec100.h" - -int ec100_debug; -module_param_named(debug, ec100_debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -struct ec100_state { - struct i2c_adapter *i2c; - struct dvb_frontend frontend; - struct ec100_config config; - - u16 ber; -}; - -/* write single register */ -static int ec100_write_reg(struct ec100_state *state, u8 reg, u8 val) -{ - u8 buf[2] = {reg, val}; - struct i2c_msg msg = { - .addr = state->config.demod_address, - .flags = 0, - .len = 2, - .buf = buf}; - - if (i2c_transfer(state->i2c, &msg, 1) != 1) { - warn("I2C write failed reg:%02x", reg); - return -EREMOTEIO; - } - return 0; -} - -/* read single register */ -static int ec100_read_reg(struct ec100_state *state, u8 reg, u8 *val) -{ - struct i2c_msg msg[2] = { - { - .addr = state->config.demod_address, - .flags = 0, - .len = 1, - .buf = ® - }, { - .addr = state->config.demod_address, - .flags = I2C_M_RD, - .len = 1, - .buf = val - } - }; - - if (i2c_transfer(state->i2c, msg, 2) != 2) { - warn("I2C read failed reg:%02x", reg); - return -EREMOTEIO; - } - return 0; -} - -static int ec100_set_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct ec100_state *state = fe->demodulator_priv; - int ret; - u8 tmp, tmp2; - - deb_info("%s: freq:%d bw:%d\n", __func__, c->frequency, - c->bandwidth_hz); - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - - ret = ec100_write_reg(state, 0x04, 0x06); - if (ret) - goto error; - ret = ec100_write_reg(state, 0x67, 0x58); - if (ret) - goto error; - ret = ec100_write_reg(state, 0x05, 0x18); - if (ret) - goto error; - - /* reg/bw | 6 | 7 | 8 - -------+------+------+------ - A 0x1b | 0xa1 | 0xe7 | 0x2c - A 0x1c | 0x55 | 0x63 | 0x72 - -------+------+------+------ - B 0x1b | 0xb7 | 0x00 | 0x49 - B 0x1c | 0x55 | 0x64 | 0x72 */ - - switch (c->bandwidth_hz) { - case 6000000: - tmp = 0xb7; - tmp2 = 0x55; - break; - case 7000000: - tmp = 0x00; - tmp2 = 0x64; - break; - case 8000000: - default: - tmp = 0x49; - tmp2 = 0x72; - } - - ret = ec100_write_reg(state, 0x1b, tmp); - if (ret) - goto error; - ret = ec100_write_reg(state, 0x1c, tmp2); - if (ret) - goto error; - - ret = ec100_write_reg(state, 0x0c, 0xbb); /* if freq */ - if (ret) - goto error; - ret = ec100_write_reg(state, 0x0d, 0x31); /* if freq */ - if (ret) - goto error; - - ret = ec100_write_reg(state, 0x08, 0x24); - if (ret) - goto error; - - ret = ec100_write_reg(state, 0x00, 0x00); /* go */ - if (ret) - goto error; - ret = ec100_write_reg(state, 0x00, 0x20); /* go */ - if (ret) - goto error; - - return ret; -error: - deb_info("%s: failed:%d\n", __func__, ret); - return ret; -} - -static int ec100_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *fesettings) -{ - fesettings->min_delay_ms = 300; - fesettings->step_size = 0; - fesettings->max_drift = 0; - - return 0; -} - -static int ec100_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct ec100_state *state = fe->demodulator_priv; - int ret; - u8 tmp; - *status = 0; - - ret = ec100_read_reg(state, 0x42, &tmp); - if (ret) - goto error; - - if (tmp & 0x80) { - /* bit7 set - have lock */ - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | - FE_HAS_SYNC | FE_HAS_LOCK; - } else { - ret = ec100_read_reg(state, 0x01, &tmp); - if (ret) - goto error; - - if (tmp & 0x10) { - /* bit4 set - have signal */ - *status |= FE_HAS_SIGNAL; - if (!(tmp & 0x01)) { - /* bit0 clear - have ~valid signal */ - *status |= FE_HAS_CARRIER | FE_HAS_VITERBI; - } - } - } - - return ret; -error: - deb_info("%s: failed:%d\n", __func__, ret); - return ret; -} - -static int ec100_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct ec100_state *state = fe->demodulator_priv; - int ret; - u8 tmp, tmp2; - u16 ber2; - - *ber = 0; - - ret = ec100_read_reg(state, 0x65, &tmp); - if (ret) - goto error; - ret = ec100_read_reg(state, 0x66, &tmp2); - if (ret) - goto error; - - ber2 = (tmp2 << 8) | tmp; - - /* if counter overflow or clear */ - if (ber2 < state->ber) - *ber = ber2; - else - *ber = ber2 - state->ber; - - state->ber = ber2; - - return ret; -error: - deb_info("%s: failed:%d\n", __func__, ret); - return ret; -} - -static int ec100_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - struct ec100_state *state = fe->demodulator_priv; - int ret; - u8 tmp; - - ret = ec100_read_reg(state, 0x24, &tmp); - if (ret) { - *strength = 0; - goto error; - } - - *strength = ((tmp << 8) | tmp); - - return ret; -error: - deb_info("%s: failed:%d\n", __func__, ret); - return ret; -} - -static int ec100_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - *snr = 0; - return 0; -} - -static int ec100_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - *ucblocks = 0; - return 0; -} - -static void ec100_release(struct dvb_frontend *fe) -{ - struct ec100_state *state = fe->demodulator_priv; - kfree(state); -} - -static struct dvb_frontend_ops ec100_ops; - -struct dvb_frontend *ec100_attach(const struct ec100_config *config, - struct i2c_adapter *i2c) -{ - int ret; - struct ec100_state *state = NULL; - u8 tmp; - - /* allocate memory for the internal state */ - state = kzalloc(sizeof(struct ec100_state), GFP_KERNEL); - if (state == NULL) - goto error; - - /* setup the state */ - state->i2c = i2c; - memcpy(&state->config, config, sizeof(struct ec100_config)); - - /* check if the demod is there */ - ret = ec100_read_reg(state, 0x33, &tmp); - if (ret || tmp != 0x0b) - goto error; - - /* create dvb_frontend */ - memcpy(&state->frontend.ops, &ec100_ops, - sizeof(struct dvb_frontend_ops)); - state->frontend.demodulator_priv = state; - - return &state->frontend; -error: - kfree(state); - return NULL; -} -EXPORT_SYMBOL(ec100_attach); - -static struct dvb_frontend_ops ec100_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "E3C EC100 DVB-T", - .caps = - FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | - FE_CAN_QPSK | FE_CAN_QAM_16 | - FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO | - FE_CAN_MUTE_TS - }, - - .release = ec100_release, - .set_frontend = ec100_set_frontend, - .get_tune_settings = ec100_get_tune_settings, - .read_status = ec100_read_status, - .read_ber = ec100_read_ber, - .read_signal_strength = ec100_read_signal_strength, - .read_snr = ec100_read_snr, - .read_ucblocks = ec100_read_ucblocks, -}; - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("E3C EC100 DVB-T demodulator driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/ec100.h b/drivers/media/dvb/frontends/ec100.h deleted file mode 100644 index ee8e52417958..000000000000 --- a/drivers/media/dvb/frontends/ec100.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * E3C EC100 demodulator driver - * - * Copyright (C) 2009 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef EC100_H -#define EC100_H - -#include - -struct ec100_config { - /* demodulator's I2C address */ - u8 demod_address; -}; - - -#if defined(CONFIG_DVB_EC100) || \ - (defined(CONFIG_DVB_EC100_MODULE) && defined(MODULE)) -extern struct dvb_frontend *ec100_attach(const struct ec100_config *config, - struct i2c_adapter *i2c); -#else -static inline struct dvb_frontend *ec100_attach( - const struct ec100_config *config, struct i2c_adapter *i2c) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif - -#endif /* EC100_H */ diff --git a/drivers/media/dvb/frontends/ec100_priv.h b/drivers/media/dvb/frontends/ec100_priv.h deleted file mode 100644 index 5c990144bc47..000000000000 --- a/drivers/media/dvb/frontends/ec100_priv.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * E3C EC100 demodulator driver - * - * Copyright (C) 2009 Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef EC100_PRIV -#define EC100_PRIV - -#define LOG_PREFIX "ec100" - -#define dprintk(var, level, args...) \ - do { if ((var & level)) printk(args); } while (0) - -#define deb_info(args...) dprintk(ec100_debug, 0x01, args) - -#undef err -#define err(f, arg...) printk(KERN_ERR LOG_PREFIX": " f "\n" , ## arg) -#undef info -#define info(f, arg...) printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef warn -#define warn(f, arg...) printk(KERN_WARNING LOG_PREFIX": " f "\n" , ## arg) - -#endif /* EC100_PRIV */ diff --git a/drivers/media/dvb/frontends/hd29l2.c b/drivers/media/dvb/frontends/hd29l2.c deleted file mode 100644 index a00318190837..000000000000 --- a/drivers/media/dvb/frontends/hd29l2.c +++ /dev/null @@ -1,861 +0,0 @@ -/* - * HDIC HD29L2 DMB-TH demodulator driver - * - * Copyright (C) 2011 Metropolia University of Applied Sciences, Electria R&D - * - * Author: Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include "hd29l2_priv.h" - -int hd29l2_debug; -module_param_named(debug, hd29l2_debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -/* write multiple registers */ -static int hd29l2_wr_regs(struct hd29l2_priv *priv, u8 reg, u8 *val, int len) -{ - int ret; - u8 buf[2 + len]; - struct i2c_msg msg[1] = { - { - .addr = priv->cfg.i2c_addr, - .flags = 0, - .len = sizeof(buf), - .buf = buf, - } - }; - - buf[0] = 0x00; - buf[1] = reg; - memcpy(&buf[2], val, len); - - ret = i2c_transfer(priv->i2c, msg, 1); - if (ret == 1) { - ret = 0; - } else { - warn("i2c wr failed=%d reg=%02x len=%d", ret, reg, len); - ret = -EREMOTEIO; - } - - return ret; -} - -/* read multiple registers */ -static int hd29l2_rd_regs(struct hd29l2_priv *priv, u8 reg, u8 *val, int len) -{ - int ret; - u8 buf[2] = { 0x00, reg }; - struct i2c_msg msg[2] = { - { - .addr = priv->cfg.i2c_addr, - .flags = 0, - .len = 2, - .buf = buf, - }, { - .addr = priv->cfg.i2c_addr, - .flags = I2C_M_RD, - .len = len, - .buf = val, - } - }; - - ret = i2c_transfer(priv->i2c, msg, 2); - if (ret == 2) { - ret = 0; - } else { - warn("i2c rd failed=%d reg=%02x len=%d", ret, reg, len); - ret = -EREMOTEIO; - } - - return ret; -} - -/* write single register */ -static int hd29l2_wr_reg(struct hd29l2_priv *priv, u8 reg, u8 val) -{ - return hd29l2_wr_regs(priv, reg, &val, 1); -} - -/* read single register */ -static int hd29l2_rd_reg(struct hd29l2_priv *priv, u8 reg, u8 *val) -{ - return hd29l2_rd_regs(priv, reg, val, 1); -} - -/* write single register with mask */ -static int hd29l2_wr_reg_mask(struct hd29l2_priv *priv, u8 reg, u8 val, u8 mask) -{ - int ret; - u8 tmp; - - /* no need for read if whole reg is written */ - if (mask != 0xff) { - ret = hd29l2_rd_regs(priv, reg, &tmp, 1); - if (ret) - return ret; - - val &= mask; - tmp &= ~mask; - val |= tmp; - } - - return hd29l2_wr_regs(priv, reg, &val, 1); -} - -/* read single register with mask */ -int hd29l2_rd_reg_mask(struct hd29l2_priv *priv, u8 reg, u8 *val, u8 mask) -{ - int ret, i; - u8 tmp; - - ret = hd29l2_rd_regs(priv, reg, &tmp, 1); - if (ret) - return ret; - - tmp &= mask; - - /* find position of the first bit */ - for (i = 0; i < 8; i++) { - if ((mask >> i) & 0x01) - break; - } - *val = tmp >> i; - - return 0; -} - -static int hd29l2_soft_reset(struct hd29l2_priv *priv) -{ - int ret; - u8 tmp; - - ret = hd29l2_rd_reg(priv, 0x26, &tmp); - if (ret) - goto err; - - ret = hd29l2_wr_reg(priv, 0x26, 0x0d); - if (ret) - goto err; - - usleep_range(10000, 20000); - - ret = hd29l2_wr_reg(priv, 0x26, tmp); - if (ret) - goto err; - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int hd29l2_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - int ret, i; - struct hd29l2_priv *priv = fe->demodulator_priv; - u8 tmp; - - dbg("%s: enable=%d", __func__, enable); - - /* set tuner address for demod */ - if (!priv->tuner_i2c_addr_programmed && enable) { - /* no need to set tuner address every time, once is enough */ - ret = hd29l2_wr_reg(priv, 0x9d, priv->cfg.tuner_i2c_addr << 1); - if (ret) - goto err; - - priv->tuner_i2c_addr_programmed = true; - } - - /* open / close gate */ - ret = hd29l2_wr_reg(priv, 0x9f, enable); - if (ret) - goto err; - - /* wait demod ready */ - for (i = 10; i; i--) { - ret = hd29l2_rd_reg(priv, 0x9e, &tmp); - if (ret) - goto err; - - if (tmp == enable) - break; - - usleep_range(5000, 10000); - } - - dbg("%s: loop=%d", __func__, i); - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int hd29l2_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - int ret; - struct hd29l2_priv *priv = fe->demodulator_priv; - u8 buf[2]; - - *status = 0; - - ret = hd29l2_rd_reg(priv, 0x05, &buf[0]); - if (ret) - goto err; - - if (buf[0] & 0x01) { - /* full lock */ - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | - FE_HAS_SYNC | FE_HAS_LOCK; - } else { - ret = hd29l2_rd_reg(priv, 0x0d, &buf[1]); - if (ret) - goto err; - - if ((buf[1] & 0xfe) == 0x78) - /* partial lock */ - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC; - } - - priv->fe_status = *status; - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int hd29l2_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - int ret; - struct hd29l2_priv *priv = fe->demodulator_priv; - u8 buf[2]; - u16 tmp; - - if (!(priv->fe_status & FE_HAS_LOCK)) { - *snr = 0; - ret = 0; - goto err; - } - - ret = hd29l2_rd_regs(priv, 0x0b, buf, 2); - if (ret) - goto err; - - tmp = (buf[0] << 8) | buf[1]; - - /* report SNR in dB * 10 */ - #define LOG10_20736_24 72422627 /* log10(20736) << 24 */ - if (tmp) - *snr = (LOG10_20736_24 - intlog10(tmp)) / ((1 << 24) / 100); - else - *snr = 0; - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int hd29l2_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - int ret; - struct hd29l2_priv *priv = fe->demodulator_priv; - u8 buf[2]; - u16 tmp; - - *strength = 0; - - ret = hd29l2_rd_regs(priv, 0xd5, buf, 2); - if (ret) - goto err; - - tmp = buf[0] << 8 | buf[1]; - tmp = ~tmp & 0x0fff; - - /* scale value to 0x0000-0xffff from 0x0000-0x0fff */ - *strength = tmp * 0xffff / 0x0fff; - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int hd29l2_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - int ret; - struct hd29l2_priv *priv = fe->demodulator_priv; - u8 buf[2]; - - if (!(priv->fe_status & FE_HAS_SYNC)) { - *ber = 0; - ret = 0; - goto err; - } - - ret = hd29l2_rd_regs(priv, 0xd9, buf, 2); - if (ret) { - *ber = 0; - goto err; - } - - /* LDPC BER */ - *ber = ((buf[0] & 0x0f) << 8) | buf[1]; - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int hd29l2_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - /* no way to read? */ - *ucblocks = 0; - return 0; -} - -static enum dvbfe_search hd29l2_search(struct dvb_frontend *fe) -{ - int ret, i; - struct hd29l2_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u8 tmp, buf[3]; - u8 modulation, carrier, guard_interval, interleave, code_rate; - u64 num64; - u32 if_freq, if_ctl; - bool auto_mode; - - dbg("%s: delivery_system=%d frequency=%d bandwidth_hz=%d " \ - "modulation=%d inversion=%d fec_inner=%d guard_interval=%d", - __func__, - c->delivery_system, c->frequency, c->bandwidth_hz, - c->modulation, c->inversion, c->fec_inner, c->guard_interval); - - /* as for now we detect always params automatically */ - auto_mode = true; - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - - /* get and program IF */ - if (fe->ops.tuner_ops.get_if_frequency) - fe->ops.tuner_ops.get_if_frequency(fe, &if_freq); - else - if_freq = 0; - - if (if_freq) { - /* normal IF */ - - /* calc IF control value */ - num64 = if_freq; - num64 *= 0x800000; - num64 = div_u64(num64, HD29L2_XTAL); - num64 -= 0x800000; - if_ctl = num64; - - tmp = 0xfc; /* tuner type normal */ - } else { - /* zero IF */ - if_ctl = 0; - tmp = 0xfe; /* tuner type Zero-IF */ - } - - buf[0] = ((if_ctl >> 0) & 0xff); - buf[1] = ((if_ctl >> 8) & 0xff); - buf[2] = ((if_ctl >> 16) & 0xff); - - /* program IF control */ - ret = hd29l2_wr_regs(priv, 0x14, buf, 3); - if (ret) - goto err; - - /* program tuner type */ - ret = hd29l2_wr_reg(priv, 0xab, tmp); - if (ret) - goto err; - - dbg("%s: if_freq=%d if_ctl=%x", __func__, if_freq, if_ctl); - - if (auto_mode) { - /* - * use auto mode - */ - - /* disable quick mode */ - ret = hd29l2_wr_reg_mask(priv, 0xac, 0 << 7, 0x80); - if (ret) - goto err; - - ret = hd29l2_wr_reg_mask(priv, 0x82, 1 << 1, 0x02); - if (ret) - goto err; - - /* enable auto mode */ - ret = hd29l2_wr_reg_mask(priv, 0x7d, 1 << 6, 0x40); - if (ret) - goto err; - - ret = hd29l2_wr_reg_mask(priv, 0x81, 1 << 3, 0x08); - if (ret) - goto err; - - /* soft reset */ - ret = hd29l2_soft_reset(priv); - if (ret) - goto err; - - /* detect modulation */ - for (i = 30; i; i--) { - msleep(100); - - ret = hd29l2_rd_reg(priv, 0x0d, &tmp); - if (ret) - goto err; - - if ((((tmp & 0xf0) >= 0x10) && - ((tmp & 0x0f) == 0x08)) || (tmp >= 0x2c)) - break; - } - - dbg("%s: loop=%d", __func__, i); - - if (i == 0) - /* detection failed */ - return DVBFE_ALGO_SEARCH_FAILED; - - /* read modulation */ - ret = hd29l2_rd_reg_mask(priv, 0x7d, &modulation, 0x07); - if (ret) - goto err; - } else { - /* - * use manual mode - */ - - modulation = HD29L2_QAM64; - carrier = HD29L2_CARRIER_MULTI; - guard_interval = HD29L2_PN945; - interleave = HD29L2_INTERLEAVER_420; - code_rate = HD29L2_CODE_RATE_08; - - tmp = (code_rate << 3) | modulation; - ret = hd29l2_wr_reg_mask(priv, 0x7d, tmp, 0x5f); - if (ret) - goto err; - - tmp = (carrier << 2) | guard_interval; - ret = hd29l2_wr_reg_mask(priv, 0x81, tmp, 0x0f); - if (ret) - goto err; - - tmp = interleave; - ret = hd29l2_wr_reg_mask(priv, 0x82, tmp, 0x03); - if (ret) - goto err; - } - - /* ensure modulation validy */ - /* 0=QAM4_NR, 1=QAM4, 2=QAM16, 3=QAM32, 4=QAM64 */ - if (modulation > (ARRAY_SIZE(reg_mod_vals_tab[0].val) - 1)) { - dbg("%s: modulation=%d not valid", __func__, modulation); - goto err; - } - - /* program registers according to modulation */ - for (i = 0; i < ARRAY_SIZE(reg_mod_vals_tab); i++) { - ret = hd29l2_wr_reg(priv, reg_mod_vals_tab[i].reg, - reg_mod_vals_tab[i].val[modulation]); - if (ret) - goto err; - } - - /* read guard interval */ - ret = hd29l2_rd_reg_mask(priv, 0x81, &guard_interval, 0x03); - if (ret) - goto err; - - /* read carrier mode */ - ret = hd29l2_rd_reg_mask(priv, 0x81, &carrier, 0x04); - if (ret) - goto err; - - dbg("%s: modulation=%d guard_interval=%d carrier=%d", - __func__, modulation, guard_interval, carrier); - - if ((carrier == HD29L2_CARRIER_MULTI) && (modulation == HD29L2_QAM64) && - (guard_interval == HD29L2_PN945)) { - dbg("%s: C=3780 && QAM64 && PN945", __func__); - - ret = hd29l2_wr_reg(priv, 0x42, 0x33); - if (ret) - goto err; - - ret = hd29l2_wr_reg(priv, 0xdd, 0x01); - if (ret) - goto err; - } - - usleep_range(10000, 20000); - - /* soft reset */ - ret = hd29l2_soft_reset(priv); - if (ret) - goto err; - - /* wait demod lock */ - for (i = 30; i; i--) { - msleep(100); - - /* read lock bit */ - ret = hd29l2_rd_reg_mask(priv, 0x05, &tmp, 0x01); - if (ret) - goto err; - - if (tmp) - break; - } - - dbg("%s: loop=%d", __func__, i); - - if (i == 0) - return DVBFE_ALGO_SEARCH_AGAIN; - - return DVBFE_ALGO_SEARCH_SUCCESS; -err: - dbg("%s: failed=%d", __func__, ret); - return DVBFE_ALGO_SEARCH_ERROR; -} - -static int hd29l2_get_frontend_algo(struct dvb_frontend *fe) -{ - return DVBFE_ALGO_CUSTOM; -} - -static int hd29l2_get_frontend(struct dvb_frontend *fe) -{ - int ret; - struct hd29l2_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - u8 buf[3]; - u32 if_ctl; - char *str_constellation, *str_code_rate, *str_constellation_code_rate, - *str_guard_interval, *str_carrier, *str_guard_interval_carrier, - *str_interleave, *str_interleave_; - - ret = hd29l2_rd_reg(priv, 0x7d, &buf[0]); - if (ret) - goto err; - - ret = hd29l2_rd_regs(priv, 0x81, &buf[1], 2); - if (ret) - goto err; - - /* constellation, 0x7d[2:0] */ - switch ((buf[0] >> 0) & 0x07) { - case 0: /* QAM4NR */ - str_constellation = "QAM4NR"; - c->modulation = QAM_AUTO; /* FIXME */ - break; - case 1: /* QAM4 */ - str_constellation = "QAM4"; - c->modulation = QPSK; /* FIXME */ - break; - case 2: - str_constellation = "QAM16"; - c->modulation = QAM_16; - break; - case 3: - str_constellation = "QAM32"; - c->modulation = QAM_32; - break; - case 4: - str_constellation = "QAM64"; - c->modulation = QAM_64; - break; - default: - str_constellation = "?"; - } - - /* LDPC code rate, 0x7d[4:3] */ - switch ((buf[0] >> 3) & 0x03) { - case 0: /* 0.4 */ - str_code_rate = "0.4"; - c->fec_inner = FEC_AUTO; /* FIXME */ - break; - case 1: /* 0.6 */ - str_code_rate = "0.6"; - c->fec_inner = FEC_3_5; - break; - case 2: /* 0.8 */ - str_code_rate = "0.8"; - c->fec_inner = FEC_4_5; - break; - default: - str_code_rate = "?"; - } - - /* constellation & code rate set, 0x7d[6] */ - switch ((buf[0] >> 6) & 0x01) { - case 0: - str_constellation_code_rate = "manual"; - break; - case 1: - str_constellation_code_rate = "auto"; - break; - default: - str_constellation_code_rate = "?"; - } - - /* frame header, 0x81[1:0] */ - switch ((buf[1] >> 0) & 0x03) { - case 0: /* PN945 */ - str_guard_interval = "PN945"; - c->guard_interval = GUARD_INTERVAL_AUTO; /* FIXME */ - break; - case 1: /* PN595 */ - str_guard_interval = "PN595"; - c->guard_interval = GUARD_INTERVAL_AUTO; /* FIXME */ - break; - case 2: /* PN420 */ - str_guard_interval = "PN420"; - c->guard_interval = GUARD_INTERVAL_AUTO; /* FIXME */ - break; - default: - str_guard_interval = "?"; - } - - /* carrier, 0x81[2] */ - switch ((buf[1] >> 2) & 0x01) { - case 0: - str_carrier = "C=1"; - break; - case 1: - str_carrier = "C=3780"; - break; - default: - str_carrier = "?"; - } - - /* frame header & carrier set, 0x81[3] */ - switch ((buf[1] >> 3) & 0x01) { - case 0: - str_guard_interval_carrier = "manual"; - break; - case 1: - str_guard_interval_carrier = "auto"; - break; - default: - str_guard_interval_carrier = "?"; - } - - /* interleave, 0x82[0] */ - switch ((buf[2] >> 0) & 0x01) { - case 0: - str_interleave = "M=720"; - break; - case 1: - str_interleave = "M=240"; - break; - default: - str_interleave = "?"; - } - - /* interleave set, 0x82[1] */ - switch ((buf[2] >> 1) & 0x01) { - case 0: - str_interleave_ = "manual"; - break; - case 1: - str_interleave_ = "auto"; - break; - default: - str_interleave_ = "?"; - } - - /* - * We can read out current detected NCO and use that value next - * time instead of calculating new value from targed IF. - * I think it will not effect receiver sensitivity but gaining lock - * after tune could be easier... - */ - ret = hd29l2_rd_regs(priv, 0xb1, &buf[0], 3); - if (ret) - goto err; - - if_ctl = (buf[0] << 16) | ((buf[1] - 7) << 8) | buf[2]; - - dbg("%s: %s %s %s | %s %s %s | %s %s | NCO=%06x", __func__, - str_constellation, str_code_rate, str_constellation_code_rate, - str_guard_interval, str_carrier, str_guard_interval_carrier, - str_interleave, str_interleave_, if_ctl); - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int hd29l2_init(struct dvb_frontend *fe) -{ - int ret, i; - struct hd29l2_priv *priv = fe->demodulator_priv; - u8 tmp; - static const struct reg_val tab[] = { - { 0x3a, 0x06 }, - { 0x3b, 0x03 }, - { 0x3c, 0x04 }, - { 0xaf, 0x06 }, - { 0xb0, 0x1b }, - { 0x80, 0x64 }, - { 0x10, 0x38 }, - }; - - dbg("%s:", __func__); - - /* reset demod */ - /* it is recommended to HW reset chip using RST_N pin */ - if (fe->callback) { - ret = fe->callback(fe, DVB_FRONTEND_COMPONENT_DEMOD, 0, 0); - if (ret) - goto err; - - /* reprogramming needed because HW reset clears registers */ - priv->tuner_i2c_addr_programmed = false; - } - - /* init */ - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = hd29l2_wr_reg(priv, tab[i].reg, tab[i].val); - if (ret) - goto err; - } - - /* TS params */ - ret = hd29l2_rd_reg(priv, 0x36, &tmp); - if (ret) - goto err; - - tmp &= 0x1b; - tmp |= priv->cfg.ts_mode; - ret = hd29l2_wr_reg(priv, 0x36, tmp); - if (ret) - goto err; - - ret = hd29l2_rd_reg(priv, 0x31, &tmp); - tmp &= 0xef; - - if (!(priv->cfg.ts_mode >> 7)) - /* set b4 for serial TS */ - tmp |= 0x10; - - ret = hd29l2_wr_reg(priv, 0x31, tmp); - if (ret) - goto err; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static void hd29l2_release(struct dvb_frontend *fe) -{ - struct hd29l2_priv *priv = fe->demodulator_priv; - kfree(priv); -} - -static struct dvb_frontend_ops hd29l2_ops; - -struct dvb_frontend *hd29l2_attach(const struct hd29l2_config *config, - struct i2c_adapter *i2c) -{ - int ret; - struct hd29l2_priv *priv = NULL; - u8 tmp; - - /* allocate memory for the internal state */ - priv = kzalloc(sizeof(struct hd29l2_priv), GFP_KERNEL); - if (priv == NULL) - goto err; - - /* setup the state */ - priv->i2c = i2c; - memcpy(&priv->cfg, config, sizeof(struct hd29l2_config)); - - - /* check if the demod is there */ - ret = hd29l2_rd_reg(priv, 0x00, &tmp); - if (ret) - goto err; - - /* create dvb_frontend */ - memcpy(&priv->fe.ops, &hd29l2_ops, sizeof(struct dvb_frontend_ops)); - priv->fe.demodulator_priv = priv; - - return &priv->fe; -err: - kfree(priv); - return NULL; -} -EXPORT_SYMBOL(hd29l2_attach); - -static struct dvb_frontend_ops hd29l2_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "HDIC HD29L2 DMB-TH", - .frequency_min = 474000000, - .frequency_max = 858000000, - .frequency_stepsize = 10000, - .caps = FE_CAN_FEC_AUTO | - FE_CAN_QPSK | - FE_CAN_QAM_16 | - FE_CAN_QAM_32 | - FE_CAN_QAM_64 | - FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_BANDWIDTH_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO | - FE_CAN_RECOVER - }, - - .release = hd29l2_release, - - .init = hd29l2_init, - - .get_frontend_algo = hd29l2_get_frontend_algo, - .search = hd29l2_search, - .get_frontend = hd29l2_get_frontend, - - .read_status = hd29l2_read_status, - .read_snr = hd29l2_read_snr, - .read_signal_strength = hd29l2_read_signal_strength, - .read_ber = hd29l2_read_ber, - .read_ucblocks = hd29l2_read_ucblocks, - - .i2c_gate_ctrl = hd29l2_i2c_gate_ctrl, -}; - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("HDIC HD29L2 DMB-TH demodulator driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/hd29l2.h b/drivers/media/dvb/frontends/hd29l2.h deleted file mode 100644 index a7a64431364d..000000000000 --- a/drivers/media/dvb/frontends/hd29l2.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * HDIC HD29L2 DMB-TH demodulator driver - * - * Copyright (C) 2011 Metropolia University of Applied Sciences, Electria R&D - * - * Author: Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#ifndef HD29L2_H -#define HD29L2_H - -#include - -struct hd29l2_config { - /* - * demodulator I2C address - */ - u8 i2c_addr; - - /* - * tuner I2C address - * only needed when tuner is behind demod I2C-gate - */ - u8 tuner_i2c_addr; - - /* - * TS settings - */ -#define HD29L2_TS_SERIAL 0x00 -#define HD29L2_TS_PARALLEL 0x80 -#define HD29L2_TS_CLK_NORMAL 0x40 -#define HD29L2_TS_CLK_INVERTED 0x00 -#define HD29L2_TS_CLK_GATED 0x20 -#define HD29L2_TS_CLK_FREE 0x00 - u8 ts_mode; -}; - - -#if defined(CONFIG_DVB_HD29L2) || \ - (defined(CONFIG_DVB_HD29L2_MODULE) && defined(MODULE)) -extern struct dvb_frontend *hd29l2_attach(const struct hd29l2_config *config, - struct i2c_adapter *i2c); -#else -static inline struct dvb_frontend *hd29l2_attach( -const struct hd29l2_config *config, struct i2c_adapter *i2c) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif - -#endif /* HD29L2_H */ diff --git a/drivers/media/dvb/frontends/hd29l2_priv.h b/drivers/media/dvb/frontends/hd29l2_priv.h deleted file mode 100644 index ba16dc3ec2bd..000000000000 --- a/drivers/media/dvb/frontends/hd29l2_priv.h +++ /dev/null @@ -1,314 +0,0 @@ -/* - * HDIC HD29L2 DMB-TH demodulator driver - * - * Copyright (C) 2011 Metropolia University of Applied Sciences, Electria R&D - * - * Author: Antti Palosaari - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#ifndef HD29L2_PRIV -#define HD29L2_PRIV - -#include -#include "dvb_frontend.h" -#include "dvb_math.h" -#include "hd29l2.h" - -#define LOG_PREFIX "hd29l2" - -#undef dbg -#define dbg(f, arg...) \ - if (hd29l2_debug) \ - printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef err -#define err(f, arg...) printk(KERN_ERR LOG_PREFIX": " f "\n" , ## arg) -#undef info -#define info(f, arg...) printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef warn -#define warn(f, arg...) printk(KERN_WARNING LOG_PREFIX": " f "\n" , ## arg) - -#define HD29L2_XTAL 30400000 /* Hz */ - - -#define HD29L2_QAM4NR 0x00 -#define HD29L2_QAM4 0x01 -#define HD29L2_QAM16 0x02 -#define HD29L2_QAM32 0x03 -#define HD29L2_QAM64 0x04 - -#define HD29L2_CODE_RATE_04 0x00 -#define HD29L2_CODE_RATE_06 0x08 -#define HD29L2_CODE_RATE_08 0x10 - -#define HD29L2_PN945 0x00 -#define HD29L2_PN595 0x01 -#define HD29L2_PN420 0x02 - -#define HD29L2_CARRIER_SINGLE 0x00 -#define HD29L2_CARRIER_MULTI 0x01 - -#define HD29L2_INTERLEAVER_720 0x00 -#define HD29L2_INTERLEAVER_420 0x01 - -struct reg_val { - u8 reg; - u8 val; -}; - -struct reg_mod_vals { - u8 reg; - u8 val[5]; -}; - -struct hd29l2_priv { - struct i2c_adapter *i2c; - struct dvb_frontend fe; - struct hd29l2_config cfg; - u8 tuner_i2c_addr_programmed:1; - - fe_status_t fe_status; -}; - -static const struct reg_mod_vals reg_mod_vals_tab[] = { - /* REG, QAM4NR, QAM4,QAM16,QAM32,QAM64 */ - { 0x01, { 0x10, 0x10, 0x10, 0x10, 0x10 } }, - { 0x02, { 0x07, 0x07, 0x07, 0x07, 0x07 } }, - { 0x03, { 0x10, 0x10, 0x10, 0x10, 0x10 } }, - { 0x04, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x05, { 0x61, 0x60, 0x60, 0x61, 0x60 } }, - { 0x06, { 0xff, 0xff, 0xff, 0xff, 0xff } }, - { 0x07, { 0xff, 0xff, 0xff, 0xff, 0xff } }, - { 0x08, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x09, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x0a, { 0x15, 0x15, 0x03, 0x03, 0x03 } }, - { 0x0d, { 0x78, 0x78, 0x88, 0x78, 0x78 } }, - { 0x0e, { 0xa0, 0x90, 0xa0, 0xa0, 0xa0 } }, - { 0x0f, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x10, { 0xa0, 0xa0, 0x58, 0x38, 0x38 } }, - { 0x11, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x12, { 0x5a, 0x5a, 0x5a, 0x5a, 0x5a } }, - { 0x13, { 0xa2, 0xa2, 0xa2, 0xa2, 0xa2 } }, - { 0x17, { 0x40, 0x40, 0x40, 0x40, 0x40 } }, - { 0x18, { 0x21, 0x21, 0x42, 0x52, 0x42 } }, - { 0x19, { 0x21, 0x21, 0x62, 0x72, 0x62 } }, - { 0x1a, { 0x32, 0x43, 0xa9, 0xb9, 0xa9 } }, - { 0x1b, { 0x32, 0x43, 0xb9, 0xd8, 0xb9 } }, - { 0x1c, { 0x02, 0x02, 0x03, 0x02, 0x03 } }, - { 0x1d, { 0x0c, 0x0c, 0x01, 0x02, 0x02 } }, - { 0x1e, { 0x02, 0x02, 0x02, 0x01, 0x02 } }, - { 0x1f, { 0x02, 0x02, 0x01, 0x02, 0x04 } }, - { 0x20, { 0x01, 0x02, 0x01, 0x01, 0x01 } }, - { 0x21, { 0x08, 0x08, 0x0a, 0x0a, 0x0a } }, - { 0x22, { 0x06, 0x06, 0x04, 0x05, 0x05 } }, - { 0x23, { 0x06, 0x06, 0x05, 0x03, 0x05 } }, - { 0x24, { 0x08, 0x08, 0x05, 0x07, 0x07 } }, - { 0x25, { 0x16, 0x10, 0x10, 0x0a, 0x10 } }, - { 0x26, { 0x14, 0x14, 0x04, 0x04, 0x04 } }, - { 0x27, { 0x58, 0x58, 0x58, 0x5c, 0x58 } }, - { 0x28, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, - { 0x29, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, - { 0x2a, { 0x08, 0x0a, 0x08, 0x08, 0x08 } }, - { 0x2b, { 0x08, 0x08, 0x08, 0x08, 0x08 } }, - { 0x2c, { 0x06, 0x06, 0x06, 0x06, 0x06 } }, - { 0x2d, { 0x05, 0x06, 0x06, 0x06, 0x06 } }, - { 0x2e, { 0x21, 0x21, 0x21, 0x21, 0x21 } }, - { 0x2f, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x30, { 0x14, 0x14, 0x14, 0x14, 0x14 } }, - { 0x33, { 0xb7, 0xb7, 0xb7, 0xb7, 0xb7 } }, - { 0x34, { 0x81, 0x81, 0x81, 0x81, 0x81 } }, - { 0x35, { 0x80, 0x80, 0x80, 0x80, 0x80 } }, - { 0x37, { 0x70, 0x70, 0x70, 0x70, 0x70 } }, - { 0x38, { 0x04, 0x04, 0x02, 0x02, 0x02 } }, - { 0x39, { 0x07, 0x07, 0x05, 0x05, 0x05 } }, - { 0x3a, { 0x06, 0x06, 0x06, 0x06, 0x06 } }, - { 0x3b, { 0x03, 0x03, 0x03, 0x03, 0x03 } }, - { 0x3c, { 0x07, 0x06, 0x04, 0x04, 0x04 } }, - { 0x3d, { 0xf0, 0xf0, 0xf0, 0xf0, 0x80 } }, - { 0x3e, { 0x60, 0x60, 0x60, 0x60, 0xff } }, - { 0x3f, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x40, { 0x5b, 0x5b, 0x5b, 0x57, 0x50 } }, - { 0x41, { 0x30, 0x30, 0x30, 0x30, 0x18 } }, - { 0x42, { 0x20, 0x20, 0x20, 0x00, 0x30 } }, - { 0x43, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x44, { 0x3f, 0x3f, 0x3f, 0x3f, 0x3f } }, - { 0x45, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x46, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, - { 0x47, { 0x00, 0x00, 0x95, 0x00, 0x95 } }, - { 0x48, { 0xc0, 0xc0, 0xc0, 0xc0, 0xc0 } }, - { 0x49, { 0xc0, 0xc0, 0xc0, 0xc0, 0xc0 } }, - { 0x4a, { 0x40, 0x40, 0x33, 0x11, 0x11 } }, - { 0x4b, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, - { 0x4c, { 0x40, 0x40, 0x99, 0x11, 0x11 } }, - { 0x4d, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, - { 0x4e, { 0x40, 0x40, 0x66, 0x77, 0x77 } }, - { 0x4f, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, - { 0x50, { 0x40, 0x40, 0x88, 0x33, 0x11 } }, - { 0x51, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, - { 0x52, { 0x40, 0x40, 0x88, 0x02, 0x02 } }, - { 0x53, { 0x40, 0x40, 0x00, 0x02, 0x02 } }, - { 0x54, { 0x00, 0x00, 0x88, 0x33, 0x33 } }, - { 0x55, { 0x40, 0x40, 0x00, 0x00, 0x00 } }, - { 0x56, { 0x00, 0x00, 0x00, 0x0b, 0x00 } }, - { 0x57, { 0x40, 0x40, 0x0a, 0x0b, 0x0a } }, - { 0x58, { 0xaa, 0x00, 0x00, 0x00, 0x00 } }, - { 0x59, { 0x7a, 0x40, 0x02, 0x02, 0x02 } }, - { 0x5a, { 0x18, 0x18, 0x01, 0x01, 0x01 } }, - { 0x5b, { 0x18, 0x18, 0x01, 0x01, 0x01 } }, - { 0x5c, { 0x18, 0x18, 0x01, 0x01, 0x01 } }, - { 0x5d, { 0x18, 0x18, 0x01, 0x01, 0x01 } }, - { 0x5e, { 0xc0, 0xc0, 0xc0, 0xff, 0xc0 } }, - { 0x5f, { 0xc0, 0xc0, 0xc0, 0xff, 0xc0 } }, - { 0x60, { 0x40, 0x40, 0x00, 0x30, 0x30 } }, - { 0x61, { 0x40, 0x40, 0x10, 0x30, 0x30 } }, - { 0x62, { 0x40, 0x40, 0x00, 0x30, 0x30 } }, - { 0x63, { 0x40, 0x40, 0x05, 0x30, 0x30 } }, - { 0x64, { 0x40, 0x40, 0x06, 0x00, 0x30 } }, - { 0x65, { 0x40, 0x40, 0x06, 0x08, 0x30 } }, - { 0x66, { 0x40, 0x40, 0x00, 0x00, 0x20 } }, - { 0x67, { 0x40, 0x40, 0x01, 0x04, 0x20 } }, - { 0x68, { 0x00, 0x00, 0x30, 0x00, 0x20 } }, - { 0x69, { 0xa0, 0xa0, 0x00, 0x08, 0x20 } }, - { 0x6a, { 0x00, 0x00, 0x30, 0x00, 0x25 } }, - { 0x6b, { 0xa0, 0xa0, 0x00, 0x06, 0x25 } }, - { 0x6c, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x6d, { 0xa0, 0x60, 0x0c, 0x03, 0x0c } }, - { 0x6e, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x6f, { 0xa0, 0x60, 0x04, 0x01, 0x04 } }, - { 0x70, { 0x58, 0x58, 0xaa, 0xaa, 0xaa } }, - { 0x71, { 0x58, 0x58, 0xaa, 0xaa, 0xaa } }, - { 0x72, { 0x58, 0x58, 0xff, 0xff, 0xff } }, - { 0x73, { 0x58, 0x58, 0xff, 0xff, 0xff } }, - { 0x74, { 0x06, 0x06, 0x09, 0x05, 0x05 } }, - { 0x75, { 0x06, 0x06, 0x0a, 0x10, 0x10 } }, - { 0x76, { 0x10, 0x10, 0x06, 0x0a, 0x0a } }, - { 0x77, { 0x12, 0x18, 0x28, 0x10, 0x28 } }, - { 0x78, { 0xf8, 0xf8, 0xf8, 0xf8, 0xf8 } }, - { 0x79, { 0x15, 0x15, 0x03, 0x03, 0x03 } }, - { 0x7a, { 0x02, 0x02, 0x01, 0x04, 0x03 } }, - { 0x7b, { 0x01, 0x02, 0x03, 0x03, 0x03 } }, - { 0x7c, { 0x28, 0x28, 0x28, 0x28, 0x28 } }, - { 0x7f, { 0x25, 0x92, 0x5f, 0x17, 0x2d } }, - { 0x80, { 0x64, 0x64, 0x64, 0x74, 0x64 } }, - { 0x83, { 0x06, 0x03, 0x04, 0x04, 0x04 } }, - { 0x84, { 0xff, 0xff, 0xff, 0xff, 0xff } }, - { 0x85, { 0x05, 0x05, 0x05, 0x05, 0x05 } }, - { 0x86, { 0x00, 0x00, 0x11, 0x11, 0x11 } }, - { 0x87, { 0x03, 0x03, 0x03, 0x03, 0x03 } }, - { 0x88, { 0x09, 0x09, 0x09, 0x09, 0x09 } }, - { 0x89, { 0x20, 0x20, 0x30, 0x20, 0x20 } }, - { 0x8a, { 0x03, 0x03, 0x02, 0x03, 0x02 } }, - { 0x8b, { 0x00, 0x07, 0x09, 0x00, 0x09 } }, - { 0x8c, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x8d, { 0x4f, 0x4f, 0x4f, 0x3f, 0x4f } }, - { 0x8e, { 0xf0, 0xf0, 0x60, 0xf0, 0xa0 } }, - { 0x8f, { 0xe8, 0xe8, 0xe8, 0xe8, 0xe8 } }, - { 0x90, { 0x10, 0x10, 0x10, 0x10, 0x10 } }, - { 0x91, { 0x40, 0x40, 0x70, 0x70, 0x10 } }, - { 0x92, { 0x00, 0x00, 0x00, 0x00, 0x04 } }, - { 0x93, { 0x60, 0x60, 0x60, 0x60, 0x60 } }, - { 0x94, { 0x00, 0x00, 0x00, 0x00, 0x03 } }, - { 0x95, { 0x09, 0x09, 0x47, 0x47, 0x47 } }, - { 0x96, { 0x80, 0xa0, 0xa0, 0x40, 0xa0 } }, - { 0x97, { 0x60, 0x60, 0x60, 0x60, 0x60 } }, - { 0x98, { 0x50, 0x50, 0x50, 0x30, 0x50 } }, - { 0x99, { 0x10, 0x10, 0x10, 0x10, 0x10 } }, - { 0x9a, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0x9b, { 0x40, 0x40, 0x40, 0x30, 0x40 } }, - { 0x9c, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xa0, { 0xf0, 0xf0, 0xf0, 0xf0, 0xf0 } }, - { 0xa1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xa2, { 0x30, 0x30, 0x00, 0x30, 0x00 } }, - { 0xa3, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xa4, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xa5, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xa6, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xa7, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xa8, { 0x77, 0x77, 0x77, 0x77, 0x77 } }, - { 0xa9, { 0x02, 0x02, 0x02, 0x02, 0x02 } }, - { 0xaa, { 0x40, 0x40, 0x40, 0x40, 0x40 } }, - { 0xac, { 0x1f, 0x1f, 0x1f, 0x1f, 0x1f } }, - { 0xad, { 0x14, 0x14, 0x14, 0x14, 0x14 } }, - { 0xae, { 0x78, 0x78, 0x78, 0x78, 0x78 } }, - { 0xaf, { 0x06, 0x06, 0x06, 0x06, 0x07 } }, - { 0xb0, { 0x1b, 0x1b, 0x1b, 0x19, 0x1b } }, - { 0xb1, { 0x18, 0x17, 0x17, 0x18, 0x17 } }, - { 0xb2, { 0x35, 0x82, 0x82, 0x38, 0x82 } }, - { 0xb3, { 0xb6, 0xce, 0xc7, 0x5c, 0xb0 } }, - { 0xb4, { 0x3f, 0x3e, 0x3e, 0x3f, 0x3e } }, - { 0xb5, { 0x70, 0x58, 0x50, 0x68, 0x50 } }, - { 0xb6, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xb7, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xb8, { 0x03, 0x03, 0x01, 0x01, 0x01 } }, - { 0xb9, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xba, { 0x06, 0x06, 0x0a, 0x05, 0x0a } }, - { 0xbb, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xbc, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xbd, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xbe, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xbf, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xc0, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xc1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xc2, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xc3, { 0x00, 0x00, 0x88, 0x66, 0x88 } }, - { 0xc4, { 0x10, 0x10, 0x00, 0x00, 0x00 } }, - { 0xc5, { 0x00, 0x00, 0x44, 0x60, 0x44 } }, - { 0xc6, { 0x10, 0x0a, 0x00, 0x00, 0x00 } }, - { 0xc7, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xc8, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xc9, { 0x90, 0x04, 0x00, 0x00, 0x00 } }, - { 0xca, { 0x90, 0x08, 0x01, 0x01, 0x01 } }, - { 0xcb, { 0xa0, 0x04, 0x00, 0x44, 0x00 } }, - { 0xcc, { 0xa0, 0x10, 0x03, 0x00, 0x03 } }, - { 0xcd, { 0x06, 0x06, 0x06, 0x05, 0x06 } }, - { 0xce, { 0x05, 0x05, 0x01, 0x01, 0x01 } }, - { 0xcf, { 0x40, 0x20, 0x18, 0x18, 0x18 } }, - { 0xd0, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xd1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xd2, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xd3, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xd4, { 0x05, 0x05, 0x05, 0x05, 0x05 } }, - { 0xd5, { 0x05, 0x05, 0x05, 0x03, 0x05 } }, - { 0xd6, { 0xac, 0x22, 0xca, 0x8f, 0xca } }, - { 0xd7, { 0x20, 0x20, 0x20, 0x20, 0x20 } }, - { 0xd8, { 0x01, 0x01, 0x01, 0x01, 0x01 } }, - { 0xd9, { 0x00, 0x00, 0x0f, 0x00, 0x0f } }, - { 0xda, { 0x00, 0xff, 0xff, 0x0e, 0xff } }, - { 0xdb, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, - { 0xdc, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, - { 0xdd, { 0x05, 0x05, 0x05, 0x05, 0x05 } }, - { 0xde, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, - { 0xdf, { 0x42, 0x42, 0x44, 0x44, 0x04 } }, - { 0xe0, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xe1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xe2, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xe3, { 0x00, 0x00, 0x26, 0x06, 0x26 } }, - { 0xe4, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xe5, { 0x01, 0x0a, 0x01, 0x01, 0x01 } }, - { 0xe6, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xe7, { 0x08, 0x08, 0x08, 0x08, 0x08 } }, - { 0xe8, { 0x63, 0x63, 0x63, 0x63, 0x63 } }, - { 0xe9, { 0x59, 0x59, 0x59, 0x59, 0x59 } }, - { 0xea, { 0x80, 0x80, 0x20, 0x80, 0x80 } }, - { 0xeb, { 0x37, 0x37, 0x78, 0x37, 0x77 } }, - { 0xec, { 0x1f, 0x1f, 0x25, 0x25, 0x25 } }, - { 0xed, { 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } }, - { 0xee, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { 0xef, { 0x70, 0x70, 0x58, 0x38, 0x58 } }, - { 0xf0, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, -}; - -#endif /* HD29L2_PRIV */ diff --git a/drivers/media/dvb/frontends/it913x-fe.c b/drivers/media/dvb/frontends/it913x-fe.c deleted file mode 100644 index 708cbf197913..000000000000 --- a/drivers/media/dvb/frontends/it913x-fe.c +++ /dev/null @@ -1,1045 +0,0 @@ -/* - * Driver for it913x-fe Frontend - * - * with support for on chip it9137 integral tuner - * - * Copyright (C) 2011 Malcolm Priestley (tvboxspy@gmail.com) - * IT9137 Copyright (C) ITE Tech Inc. - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA.= - */ - -#include -#include -#include -#include - -#include "dvb_frontend.h" -#include "it913x-fe.h" -#include "it913x-fe-priv.h" - -static int it913x_debug; - -module_param_named(debug, it913x_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); - -#define dprintk(level, args...) do { \ - if (level & it913x_debug) \ - printk(KERN_DEBUG "it913x-fe: " args); \ -} while (0) - -#define deb_info(args...) dprintk(0x01, args) -#define debug_data_snipet(level, name, p) \ - dprintk(level, name" (%02x%02x%02x%02x%02x%02x%02x%02x)", \ - *p, *(p+1), *(p+2), *(p+3), *(p+4), \ - *(p+5), *(p+6), *(p+7)); -#define info(format, arg...) \ - printk(KERN_INFO "it913x-fe: " format "\n" , ## arg) - -struct it913x_fe_state { - struct dvb_frontend frontend; - struct i2c_adapter *i2c_adap; - struct ite_config *config; - u8 i2c_addr; - u32 frequency; - fe_modulation_t constellation; - fe_transmit_mode_t transmission_mode; - u8 priority; - u32 crystalFrequency; - u32 adcFrequency; - u8 tuner_type; - struct adctable *table; - fe_status_t it913x_status; - u16 tun_xtal; - u8 tun_fdiv; - u8 tun_clk_mode; - u32 tun_fn_min; - u32 ucblocks; -}; - -static int it913x_read_reg(struct it913x_fe_state *state, - u32 reg, u8 *data, u8 count) -{ - int ret; - u8 pro = PRO_DMOD; /* All reads from demodulator */ - u8 b[4]; - struct i2c_msg msg[2] = { - { .addr = state->i2c_addr + (pro << 1), .flags = 0, - .buf = b, .len = sizeof(b) }, - { .addr = state->i2c_addr + (pro << 1), .flags = I2C_M_RD, - .buf = data, .len = count } - }; - b[0] = (u8) reg >> 24; - b[1] = (u8)(reg >> 16) & 0xff; - b[2] = (u8)(reg >> 8) & 0xff; - b[3] = (u8) reg & 0xff; - - ret = i2c_transfer(state->i2c_adap, msg, 2); - - return ret; -} - -static int it913x_read_reg_u8(struct it913x_fe_state *state, u32 reg) -{ - int ret; - u8 b[1]; - ret = it913x_read_reg(state, reg, &b[0], sizeof(b)); - return (ret < 0) ? -ENODEV : b[0]; -} - -static int it913x_write(struct it913x_fe_state *state, - u8 pro, u32 reg, u8 buf[], u8 count) -{ - u8 b[256]; - struct i2c_msg msg[1] = { - { .addr = state->i2c_addr + (pro << 1), .flags = 0, - .buf = b, .len = count + 4 } - }; - int ret; - - b[0] = (u8) reg >> 24; - b[1] = (u8)(reg >> 16) & 0xff; - b[2] = (u8)(reg >> 8) & 0xff; - b[3] = (u8) reg & 0xff; - memcpy(&b[4], buf, count); - - ret = i2c_transfer(state->i2c_adap, msg, 1); - - if (ret < 0) - return -EIO; - - return 0; -} - -static int it913x_write_reg(struct it913x_fe_state *state, - u8 pro, u32 reg, u32 data) -{ - int ret; - u8 b[4]; - u8 s; - - b[0] = data >> 24; - b[1] = (data >> 16) & 0xff; - b[2] = (data >> 8) & 0xff; - b[3] = data & 0xff; - /* expand write as needed */ - if (data < 0x100) - s = 3; - else if (data < 0x1000) - s = 2; - else if (data < 0x100000) - s = 1; - else - s = 0; - - ret = it913x_write(state, pro, reg, &b[s], sizeof(b) - s); - - return ret; -} - -static int it913x_fe_script_loader(struct it913x_fe_state *state, - struct it913xset *loadscript) -{ - int ret, i; - if (loadscript == NULL) - return -EINVAL; - - for (i = 0; i < 1000; ++i) { - if (loadscript[i].pro == 0xff) - break; - ret = it913x_write(state, loadscript[i].pro, - loadscript[i].address, - loadscript[i].reg, loadscript[i].count); - if (ret < 0) - return -ENODEV; - } - return 0; -} - -static int it913x_init_tuner(struct it913x_fe_state *state) -{ - int ret, i, reg; - u8 val, nv_val; - u8 nv[] = {48, 32, 24, 16, 12, 8, 6, 4, 2}; - u8 b[2]; - - reg = it913x_read_reg_u8(state, 0xec86); - switch (reg) { - case 0: - state->tun_clk_mode = reg; - state->tun_xtal = 2000; - state->tun_fdiv = 3; - val = 16; - break; - case -ENODEV: - return -ENODEV; - case 1: - default: - state->tun_clk_mode = reg; - state->tun_xtal = 640; - state->tun_fdiv = 1; - val = 6; - break; - } - - reg = it913x_read_reg_u8(state, 0xed03); - - if (reg < 0) - return -ENODEV; - else if (reg < sizeof(nv)) - nv_val = nv[reg]; - else - nv_val = 2; - - for (i = 0; i < 50; i++) { - ret = it913x_read_reg(state, 0xed23, &b[0], sizeof(b)); - reg = (b[1] << 8) + b[0]; - if (reg > 0) - break; - if (ret < 0) - return -ENODEV; - udelay(2000); - } - state->tun_fn_min = state->tun_xtal * reg; - state->tun_fn_min /= (state->tun_fdiv * nv_val); - deb_info("Tuner fn_min %d", state->tun_fn_min); - - if (state->config->chip_ver > 1) - msleep(50); - else { - for (i = 0; i < 50; i++) { - reg = it913x_read_reg_u8(state, 0xec82); - if (reg > 0) - break; - if (reg < 0) - return -ENODEV; - udelay(2000); - } - } - - return it913x_write_reg(state, PRO_DMOD, 0xed81, val); -} - -static int it9137_set_tuner(struct it913x_fe_state *state, - u32 bandwidth, u32 frequency_m) -{ - struct it913xset *set_tuner = set_it9137_template; - int ret, reg; - u32 frequency = frequency_m / 1000; - u32 freq, temp_f, tmp; - u16 iqik_m_cal; - u16 n_div; - u8 n; - u8 l_band; - u8 lna_band; - u8 bw; - - if (state->config->firmware_ver == 1) - set_tuner = set_it9135_template; - else - set_tuner = set_it9137_template; - - deb_info("Tuner Frequency %d Bandwidth %d", frequency, bandwidth); - - if (frequency >= 51000 && frequency <= 440000) { - l_band = 0; - lna_band = 0; - } else if (frequency > 440000 && frequency <= 484000) { - l_band = 1; - lna_band = 1; - } else if (frequency > 484000 && frequency <= 533000) { - l_band = 1; - lna_band = 2; - } else if (frequency > 533000 && frequency <= 587000) { - l_band = 1; - lna_band = 3; - } else if (frequency > 587000 && frequency <= 645000) { - l_band = 1; - lna_band = 4; - } else if (frequency > 645000 && frequency <= 710000) { - l_band = 1; - lna_band = 5; - } else if (frequency > 710000 && frequency <= 782000) { - l_band = 1; - lna_band = 6; - } else if (frequency > 782000 && frequency <= 860000) { - l_band = 1; - lna_band = 7; - } else if (frequency > 1450000 && frequency <= 1492000) { - l_band = 1; - lna_band = 0; - } else if (frequency > 1660000 && frequency <= 1685000) { - l_band = 1; - lna_band = 1; - } else - return -EINVAL; - set_tuner[0].reg[0] = lna_band; - - switch (bandwidth) { - case 5000000: - bw = 0; - break; - case 6000000: - bw = 2; - break; - case 7000000: - bw = 4; - break; - default: - case 8000000: - bw = 6; - break; - } - - set_tuner[1].reg[0] = bw; - set_tuner[2].reg[0] = 0xa0 | (l_band << 3); - - if (frequency > 53000 && frequency <= 74000) { - n_div = 48; - n = 0; - } else if (frequency > 74000 && frequency <= 111000) { - n_div = 32; - n = 1; - } else if (frequency > 111000 && frequency <= 148000) { - n_div = 24; - n = 2; - } else if (frequency > 148000 && frequency <= 222000) { - n_div = 16; - n = 3; - } else if (frequency > 222000 && frequency <= 296000) { - n_div = 12; - n = 4; - } else if (frequency > 296000 && frequency <= 445000) { - n_div = 8; - n = 5; - } else if (frequency > 445000 && frequency <= state->tun_fn_min) { - n_div = 6; - n = 6; - } else if (frequency > state->tun_fn_min && frequency <= 950000) { - n_div = 4; - n = 7; - } else if (frequency > 1450000 && frequency <= 1680000) { - n_div = 2; - n = 0; - } else - return -EINVAL; - - reg = it913x_read_reg_u8(state, 0xed81); - iqik_m_cal = (u16)reg * n_div; - - if (reg < 0x20) { - if (state->tun_clk_mode == 0) - iqik_m_cal = (iqik_m_cal * 9) >> 5; - else - iqik_m_cal >>= 1; - } else { - iqik_m_cal = 0x40 - iqik_m_cal; - if (state->tun_clk_mode == 0) - iqik_m_cal = ~((iqik_m_cal * 9) >> 5); - else - iqik_m_cal = ~(iqik_m_cal >> 1); - } - - temp_f = frequency * (u32)n_div * (u32)state->tun_fdiv; - freq = temp_f / state->tun_xtal; - tmp = freq * state->tun_xtal; - - if ((temp_f - tmp) >= (state->tun_xtal >> 1)) - freq++; - - freq += (u32) n << 13; - /* Frequency OMEGA_IQIK_M_CAL_MID*/ - temp_f = freq + (u32)iqik_m_cal; - - set_tuner[3].reg[0] = temp_f & 0xff; - set_tuner[4].reg[0] = (temp_f >> 8) & 0xff; - - deb_info("High Frequency = %04x", temp_f); - - /* Lower frequency */ - set_tuner[5].reg[0] = freq & 0xff; - set_tuner[6].reg[0] = (freq >> 8) & 0xff; - - deb_info("low Frequency = %04x", freq); - - ret = it913x_fe_script_loader(state, set_tuner); - - return (ret < 0) ? -ENODEV : 0; -} - -static int it913x_fe_select_bw(struct it913x_fe_state *state, - u32 bandwidth, u32 adcFrequency) -{ - int ret, i; - u8 buffer[256]; - u32 coeff[8]; - u16 bfsfcw_fftinx_ratio; - u16 fftinx_bfsfcw_ratio; - u8 count; - u8 bw; - u8 adcmultiplier; - - deb_info("Bandwidth %d Adc %d", bandwidth, adcFrequency); - - switch (bandwidth) { - case 5000000: - bw = 3; - break; - case 6000000: - bw = 0; - break; - case 7000000: - bw = 1; - break; - default: - case 8000000: - bw = 2; - break; - } - ret = it913x_write_reg(state, PRO_DMOD, REG_BW, bw); - - if (state->table == NULL) - return -EINVAL; - - /* In write order */ - coeff[0] = state->table[bw].coeff_1_2048; - coeff[1] = state->table[bw].coeff_2_2k; - coeff[2] = state->table[bw].coeff_1_8191; - coeff[3] = state->table[bw].coeff_1_8192; - coeff[4] = state->table[bw].coeff_1_8193; - coeff[5] = state->table[bw].coeff_2_8k; - coeff[6] = state->table[bw].coeff_1_4096; - coeff[7] = state->table[bw].coeff_2_4k; - bfsfcw_fftinx_ratio = state->table[bw].bfsfcw_fftinx_ratio; - fftinx_bfsfcw_ratio = state->table[bw].fftinx_bfsfcw_ratio; - - /* ADC multiplier */ - ret = it913x_read_reg_u8(state, ADC_X_2); - if (ret < 0) - return -EINVAL; - - adcmultiplier = ret; - - count = 0; - - /* Build Buffer for COEFF Registers */ - for (i = 0; i < 8; i++) { - if (adcmultiplier == 1) - coeff[i] /= 2; - buffer[count++] = (coeff[i] >> 24) & 0x3; - buffer[count++] = (coeff[i] >> 16) & 0xff; - buffer[count++] = (coeff[i] >> 8) & 0xff; - buffer[count++] = coeff[i] & 0xff; - } - - /* bfsfcw_fftinx_ratio register 0x21-0x22 */ - buffer[count++] = bfsfcw_fftinx_ratio & 0xff; - buffer[count++] = (bfsfcw_fftinx_ratio >> 8) & 0xff; - /* fftinx_bfsfcw_ratio register 0x23-0x24 */ - buffer[count++] = fftinx_bfsfcw_ratio & 0xff; - buffer[count++] = (fftinx_bfsfcw_ratio >> 8) & 0xff; - /* start at COEFF_1_2048 and write through to fftinx_bfsfcw_ratio*/ - ret = it913x_write(state, PRO_DMOD, COEFF_1_2048, buffer, count); - - for (i = 0; i < 42; i += 8) - debug_data_snipet(0x1, "Buffer", &buffer[i]); - - return ret; -} - - - -static int it913x_fe_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct it913x_fe_state *state = fe->demodulator_priv; - int ret, i; - fe_status_t old_status = state->it913x_status; - *status = 0; - - if (state->it913x_status == 0) { - ret = it913x_read_reg_u8(state, EMPTY_CHANNEL_STATUS); - if (ret == 0x1) { - *status |= FE_HAS_SIGNAL; - for (i = 0; i < 40; i++) { - ret = it913x_read_reg_u8(state, MP2IF_SYNC_LK); - if (ret == 0x1) - break; - msleep(25); - } - if (ret == 0x1) - *status |= FE_HAS_CARRIER - | FE_HAS_VITERBI - | FE_HAS_SYNC; - state->it913x_status = *status; - } - } - - if (state->it913x_status & FE_HAS_SYNC) { - ret = it913x_read_reg_u8(state, TPSD_LOCK); - if (ret == 0x1) - *status |= FE_HAS_LOCK - | state->it913x_status; - else - state->it913x_status = 0; - if (old_status != state->it913x_status) - ret = it913x_write_reg(state, PRO_LINK, GPIOH3_O, ret); - } - - return 0; -} - -/* FEC values based on fe_code_rate_t non supported values 0*/ -int it913x_qpsk_pval[] = {0, -93, -91, -90, 0, -89, -88}; -int it913x_16qam_pval[] = {0, -87, -85, -84, 0, -83, -82}; -int it913x_64qam_pval[] = {0, -82, -80, -78, 0, -77, -76}; - -static int it913x_get_signal_strength(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *p = &fe->dtv_property_cache; - struct it913x_fe_state *state = fe->demodulator_priv; - u8 code_rate; - int ret, temp; - u8 lna_gain_os; - - ret = it913x_read_reg_u8(state, VAR_P_INBAND); - if (ret < 0) - return ret; - - /* VHF/UHF gain offset */ - if (state->frequency < 300000000) - lna_gain_os = 7; - else - lna_gain_os = 14; - - temp = (ret - 100) - lna_gain_os; - - if (state->priority == PRIORITY_HIGH) - code_rate = p->code_rate_HP; - else - code_rate = p->code_rate_LP; - - if (code_rate >= ARRAY_SIZE(it913x_qpsk_pval)) - return -EINVAL; - - deb_info("Reg VAR_P_INBAND:%d Calc Offset Value:%d", ret, temp); - - /* Apply FEC offset values*/ - switch (p->modulation) { - case QPSK: - temp -= it913x_qpsk_pval[code_rate]; - break; - case QAM_16: - temp -= it913x_16qam_pval[code_rate]; - break; - case QAM_64: - temp -= it913x_64qam_pval[code_rate]; - break; - default: - return -EINVAL; - } - - if (temp < -15) - ret = 0; - else if ((-15 <= temp) && (temp < 0)) - ret = (2 * (temp + 15)) / 3; - else if ((0 <= temp) && (temp < 20)) - ret = 4 * temp + 10; - else if ((20 <= temp) && (temp < 35)) - ret = (2 * (temp - 20)) / 3 + 90; - else if (temp >= 35) - ret = 100; - - deb_info("Signal Strength :%d", ret); - - return ret; -} - -static int it913x_fe_read_signal_strength(struct dvb_frontend *fe, - u16 *strength) -{ - struct it913x_fe_state *state = fe->demodulator_priv; - int ret = 0; - if (state->config->read_slevel) { - if (state->it913x_status & FE_HAS_SIGNAL) - ret = it913x_read_reg_u8(state, SIGNAL_LEVEL); - } else - ret = it913x_get_signal_strength(fe); - - if (ret >= 0) - *strength = (u16)((u32)ret * 0xffff / 0x64); - - return (ret < 0) ? -ENODEV : 0; -} - -static int it913x_fe_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct it913x_fe_state *state = fe->demodulator_priv; - int ret; - u8 reg[3]; - u32 snr_val, snr_min, snr_max; - u32 temp; - - ret = it913x_read_reg(state, 0x2c, reg, sizeof(reg)); - - snr_val = (u32)(reg[2] << 16) | (reg[1] << 8) | reg[0]; - - ret |= it913x_read_reg(state, 0xf78b, reg, 1); - if (reg[0]) - snr_val /= reg[0]; - - if (state->transmission_mode == TRANSMISSION_MODE_2K) - snr_val *= 4; - else if (state->transmission_mode == TRANSMISSION_MODE_4K) - snr_val *= 2; - - if (state->constellation == QPSK) { - snr_min = 0xb4711; - snr_max = 0x191451; - } else if (state->constellation == QAM_16) { - snr_min = 0x4f0d5; - snr_max = 0xc7925; - } else if (state->constellation == QAM_64) { - snr_min = 0x256d0; - snr_max = 0x626be; - } else - return -EINVAL; - - if (snr_val < snr_min) - *snr = 0; - else if (snr_val < snr_max) { - temp = (snr_val - snr_min) >> 5; - temp *= 0xffff; - temp /= (snr_max - snr_min) >> 5; - *snr = (u16)temp; - } else - *snr = 0xffff; - - return (ret < 0) ? -ENODEV : 0; -} - -static int it913x_fe_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct it913x_fe_state *state = fe->demodulator_priv; - u8 reg[5]; - /* Read Aborted Packets and Pre-Viterbi error rate 5 bytes */ - it913x_read_reg(state, RSD_ABORT_PKT_LSB, reg, sizeof(reg)); - state->ucblocks += (u32)(reg[1] << 8) | reg[0]; - *ber = (u32)(reg[4] << 16) | (reg[3] << 8) | reg[2]; - return 0; -} - -static int it913x_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - struct it913x_fe_state *state = fe->demodulator_priv; - int ret; - u8 reg[2]; - /* Aborted Packets */ - ret = it913x_read_reg(state, RSD_ABORT_PKT_LSB, reg, sizeof(reg)); - state->ucblocks += (u32)(reg[1] << 8) | reg[0]; - *ucblocks = state->ucblocks; - return ret; -} - -static int it913x_fe_get_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *p = &fe->dtv_property_cache; - struct it913x_fe_state *state = fe->demodulator_priv; - u8 reg[8]; - - it913x_read_reg(state, REG_TPSD_TX_MODE, reg, sizeof(reg)); - - if (reg[3] < 3) - p->modulation = fe_con[reg[3]]; - - if (reg[0] < 3) - p->transmission_mode = fe_mode[reg[0]]; - - if (reg[1] < 4) - p->guard_interval = fe_gi[reg[1]]; - - if (reg[2] < 4) - p->hierarchy = fe_hi[reg[2]]; - - state->priority = reg[5]; - - p->code_rate_HP = (reg[6] < 6) ? fe_code[reg[6]] : FEC_NONE; - p->code_rate_LP = (reg[7] < 6) ? fe_code[reg[7]] : FEC_NONE; - - /* Update internal state to reflect the autodetected props */ - state->constellation = p->modulation; - state->transmission_mode = p->transmission_mode; - - return 0; -} - -static int it913x_fe_set_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *p = &fe->dtv_property_cache; - struct it913x_fe_state *state = fe->demodulator_priv; - int i; - u8 empty_ch, last_ch; - - state->it913x_status = 0; - - /* Set bw*/ - it913x_fe_select_bw(state, p->bandwidth_hz, - state->adcFrequency); - - /* Training Mode Off */ - it913x_write_reg(state, PRO_LINK, TRAINING_MODE, 0x0); - - /* Clear Empty Channel */ - it913x_write_reg(state, PRO_DMOD, EMPTY_CHANNEL_STATUS, 0x0); - - /* Clear bits */ - it913x_write_reg(state, PRO_DMOD, MP2IF_SYNC_LK, 0x0); - /* LED on */ - it913x_write_reg(state, PRO_LINK, GPIOH3_O, 0x1); - /* Select Band*/ - if ((p->frequency >= 51000000) && (p->frequency <= 230000000)) - i = 0; - else if ((p->frequency >= 350000000) && (p->frequency <= 900000000)) - i = 1; - else if ((p->frequency >= 1450000000) && (p->frequency <= 1680000000)) - i = 2; - else - return -EOPNOTSUPP; - - it913x_write_reg(state, PRO_DMOD, FREE_BAND, i); - - deb_info("Frontend Set Tuner Type %02x", state->tuner_type); - switch (state->tuner_type) { - case IT9135_38: - case IT9135_51: - case IT9135_52: - case IT9135_60: - case IT9135_61: - case IT9135_62: - it9137_set_tuner(state, - p->bandwidth_hz, p->frequency); - break; - default: - if (fe->ops.tuner_ops.set_params) { - fe->ops.tuner_ops.set_params(fe); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } - break; - } - /* LED off */ - it913x_write_reg(state, PRO_LINK, GPIOH3_O, 0x0); - /* Trigger ofsm */ - it913x_write_reg(state, PRO_DMOD, TRIGGER_OFSM, 0x0); - last_ch = 2; - for (i = 0; i < 40; ++i) { - empty_ch = it913x_read_reg_u8(state, EMPTY_CHANNEL_STATUS); - if (last_ch == 1 && empty_ch == 1) - break; - if (last_ch == 2 && empty_ch == 2) - return 0; - last_ch = empty_ch; - msleep(25); - } - for (i = 0; i < 40; ++i) { - if (it913x_read_reg_u8(state, D_TPSD_LOCK) == 1) - break; - msleep(25); - } - - state->frequency = p->frequency; - return 0; -} - -static int it913x_fe_suspend(struct it913x_fe_state *state) -{ - int ret, i; - u8 b; - - ret = it913x_write_reg(state, PRO_DMOD, SUSPEND_FLAG, 0x1); - - ret |= it913x_write_reg(state, PRO_DMOD, TRIGGER_OFSM, 0x0); - - for (i = 0; i < 128; i++) { - ret = it913x_read_reg(state, SUSPEND_FLAG, &b, 1); - if (ret < 0) - return -ENODEV; - if (b == 0) - break; - - } - - ret |= it913x_write_reg(state, PRO_DMOD, AFE_MEM0, 0x8); - /* Turn LED off */ - ret |= it913x_write_reg(state, PRO_LINK, GPIOH3_O, 0x0); - - ret |= it913x_fe_script_loader(state, it9137_tuner_off); - - return (ret < 0) ? -ENODEV : 0; -} - -/* Power sequence */ -/* Power Up Tuner on -> Frontend suspend off -> Tuner clk on */ -/* Power Down Frontend suspend on -> Tuner clk off -> Tuner off */ - -static int it913x_fe_sleep(struct dvb_frontend *fe) -{ - struct it913x_fe_state *state = fe->demodulator_priv; - return it913x_fe_suspend(state); -} - -static u32 compute_div(u32 a, u32 b, u32 x) -{ - u32 res = 0; - u32 c = 0; - u32 i = 0; - - if (a > b) { - c = a / b; - a = a - c * b; - } - - for (i = 0; i < x; i++) { - if (a >= b) { - res += 1; - a -= b; - } - a <<= 1; - res <<= 1; - } - - res = (c << x) + res; - - return res; -} - -static int it913x_fe_start(struct it913x_fe_state *state) -{ - struct it913xset *set_lna; - struct it913xset *set_mode; - int ret; - u8 adf = (state->config->adf & 0xf); - u32 adc, xtal; - u8 b[4]; - - if (state->config->chip_ver == 1) - ret = it913x_init_tuner(state); - - info("ADF table value :%02x", adf); - - if (adf < 10) { - state->crystalFrequency = fe_clockTable[adf].xtal ; - state->table = fe_clockTable[adf].table; - state->adcFrequency = state->table->adcFrequency; - - adc = compute_div(state->adcFrequency, 1000000ul, 19ul); - xtal = compute_div(state->crystalFrequency, 1000000ul, 19ul); - - } else - return -EINVAL; - - /* Set LED indicator on GPIOH3 */ - ret = it913x_write_reg(state, PRO_LINK, GPIOH3_EN, 0x1); - ret |= it913x_write_reg(state, PRO_LINK, GPIOH3_ON, 0x1); - ret |= it913x_write_reg(state, PRO_LINK, GPIOH3_O, 0x1); - - ret |= it913x_write_reg(state, PRO_LINK, 0xf641, state->tuner_type); - ret |= it913x_write_reg(state, PRO_DMOD, 0xf5ca, 0x01); - ret |= it913x_write_reg(state, PRO_DMOD, 0xf715, 0x01); - - b[0] = xtal & 0xff; - b[1] = (xtal >> 8) & 0xff; - b[2] = (xtal >> 16) & 0xff; - b[3] = (xtal >> 24); - ret |= it913x_write(state, PRO_DMOD, XTAL_CLK, b , 4); - - b[0] = adc & 0xff; - b[1] = (adc >> 8) & 0xff; - b[2] = (adc >> 16) & 0xff; - ret |= it913x_write(state, PRO_DMOD, ADC_FREQ, b, 3); - - if (state->config->adc_x2) - ret |= it913x_write_reg(state, PRO_DMOD, ADC_X_2, 0x01); - b[0] = 0; - b[1] = 0; - b[2] = 0; - ret |= it913x_write(state, PRO_DMOD, 0x0029, b, 3); - - info("Crystal Frequency :%d Adc Frequency :%d ADC X2: %02x", - state->crystalFrequency, state->adcFrequency, - state->config->adc_x2); - deb_info("Xtal value :%04x Adc value :%04x", xtal, adc); - - if (ret < 0) - return -ENODEV; - - /* v1 or v2 tuner script */ - if (state->config->chip_ver > 1) - ret = it913x_fe_script_loader(state, it9135_v2); - else - ret = it913x_fe_script_loader(state, it9135_v1); - if (ret < 0) - return ret; - - /* LNA Scripts */ - switch (state->tuner_type) { - case IT9135_51: - set_lna = it9135_51; - break; - case IT9135_52: - set_lna = it9135_52; - break; - case IT9135_60: - set_lna = it9135_60; - break; - case IT9135_61: - set_lna = it9135_61; - break; - case IT9135_62: - set_lna = it9135_62; - break; - case IT9135_38: - default: - set_lna = it9135_38; - } - info("Tuner LNA type :%02x", state->tuner_type); - - ret = it913x_fe_script_loader(state, set_lna); - if (ret < 0) - return ret; - - if (state->config->chip_ver == 2) { - ret = it913x_write_reg(state, PRO_DMOD, TRIGGER_OFSM, 0x1); - ret |= it913x_write_reg(state, PRO_LINK, PADODPU, 0x0); - ret |= it913x_write_reg(state, PRO_LINK, AGC_O_D, 0x0); - ret |= it913x_init_tuner(state); - } - if (ret < 0) - return -ENODEV; - - /* Always solo frontend */ - set_mode = set_solo_fe; - ret |= it913x_fe_script_loader(state, set_mode); - - ret |= it913x_fe_suspend(state); - return (ret < 0) ? -ENODEV : 0; -} - -static int it913x_fe_init(struct dvb_frontend *fe) -{ - struct it913x_fe_state *state = fe->demodulator_priv; - int ret = 0; - /* Power Up Tuner - common all versions */ - ret = it913x_write_reg(state, PRO_DMOD, 0xec40, 0x1); - - ret |= it913x_fe_script_loader(state, init_1); - - ret |= it913x_write_reg(state, PRO_DMOD, AFE_MEM0, 0x0); - - ret |= it913x_write_reg(state, PRO_DMOD, 0xfba8, 0x0); - - return (ret < 0) ? -ENODEV : 0; -} - -static void it913x_fe_release(struct dvb_frontend *fe) -{ - struct it913x_fe_state *state = fe->demodulator_priv; - kfree(state); -} - -static struct dvb_frontend_ops it913x_fe_ofdm_ops; - -struct dvb_frontend *it913x_fe_attach(struct i2c_adapter *i2c_adap, - u8 i2c_addr, struct ite_config *config) -{ - struct it913x_fe_state *state = NULL; - int ret; - - /* allocate memory for the internal state */ - state = kzalloc(sizeof(struct it913x_fe_state), GFP_KERNEL); - if (state == NULL) - return NULL; - if (config == NULL) - goto error; - - state->i2c_adap = i2c_adap; - state->i2c_addr = i2c_addr; - state->config = config; - - switch (state->config->tuner_id_0) { - case IT9135_51: - case IT9135_52: - case IT9135_60: - case IT9135_61: - case IT9135_62: - state->tuner_type = state->config->tuner_id_0; - break; - default: - case IT9135_38: - state->tuner_type = IT9135_38; - } - - ret = it913x_fe_start(state); - if (ret < 0) - goto error; - - - /* create dvb_frontend */ - memcpy(&state->frontend.ops, &it913x_fe_ofdm_ops, - sizeof(struct dvb_frontend_ops)); - state->frontend.demodulator_priv = state; - - return &state->frontend; -error: - kfree(state); - return NULL; -} -EXPORT_SYMBOL(it913x_fe_attach); - -static struct dvb_frontend_ops it913x_fe_ofdm_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "it913x-fe DVB-T", - .frequency_min = 51000000, - .frequency_max = 1680000000, - .frequency_stepsize = 62500, - .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | - FE_CAN_FEC_4_5 | FE_CAN_FEC_5_6 | FE_CAN_FEC_6_7 | - FE_CAN_FEC_7_8 | FE_CAN_FEC_8_9 | FE_CAN_FEC_AUTO | - FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO, - }, - - .release = it913x_fe_release, - - .init = it913x_fe_init, - .sleep = it913x_fe_sleep, - - .set_frontend = it913x_fe_set_frontend, - .get_frontend = it913x_fe_get_frontend, - - .read_status = it913x_fe_read_status, - .read_signal_strength = it913x_fe_read_signal_strength, - .read_snr = it913x_fe_read_snr, - .read_ber = it913x_fe_read_ber, - .read_ucblocks = it913x_fe_read_ucblocks, -}; - -MODULE_DESCRIPTION("it913x Frontend and it9137 tuner"); -MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com"); -MODULE_VERSION("1.15"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/lgs8gl5.c b/drivers/media/dvb/frontends/lgs8gl5.c deleted file mode 100644 index 2cec8041a106..000000000000 --- a/drivers/media/dvb/frontends/lgs8gl5.c +++ /dev/null @@ -1,453 +0,0 @@ -/* - Legend Silicon LGS-8GL5 DMB-TH OFDM demodulator driver - - Copyright (C) 2008 Sirius International (Hong Kong) Limited - Timothy Lee - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#include -#include -#include -#include -#include -#include "dvb_frontend.h" -#include "lgs8gl5.h" - - -#define REG_RESET 0x02 -#define REG_RESET_OFF 0x01 -#define REG_03 0x03 -#define REG_04 0x04 -#define REG_07 0x07 -#define REG_09 0x09 -#define REG_0A 0x0a -#define REG_0B 0x0b -#define REG_0C 0x0c -#define REG_37 0x37 -#define REG_STRENGTH 0x4b -#define REG_STRENGTH_MASK 0x7f -#define REG_STRENGTH_CARRIER 0x80 -#define REG_INVERSION 0x7c -#define REG_INVERSION_ON 0x80 -#define REG_7D 0x7d -#define REG_7E 0x7e -#define REG_A2 0xa2 -#define REG_STATUS 0xa4 -#define REG_STATUS_SYNC 0x04 -#define REG_STATUS_LOCK 0x01 - - -struct lgs8gl5_state { - struct i2c_adapter *i2c; - const struct lgs8gl5_config *config; - struct dvb_frontend frontend; -}; - - -static int debug; -#define dprintk(args...) \ - do { \ - if (debug) \ - printk(KERN_DEBUG "lgs8gl5: " args); \ - } while (0) - - -/* Writes into demod's register */ -static int -lgs8gl5_write_reg(struct lgs8gl5_state *state, u8 reg, u8 data) -{ - int ret; - u8 buf[] = {reg, data}; - struct i2c_msg msg = { - .addr = state->config->demod_address, - .flags = 0, - .buf = buf, - .len = 2 - }; - - ret = i2c_transfer(state->i2c, &msg, 1); - if (ret != 1) - dprintk("%s: error (reg=0x%02x, val=0x%02x, ret=%i)\n", - __func__, reg, data, ret); - return (ret != 1) ? -1 : 0; -} - - -/* Reads from demod's register */ -static int -lgs8gl5_read_reg(struct lgs8gl5_state *state, u8 reg) -{ - int ret; - u8 b0[] = {reg}; - u8 b1[] = {0}; - struct i2c_msg msg[2] = { - { - .addr = state->config->demod_address, - .flags = 0, - .buf = b0, - .len = 1 - }, - { - .addr = state->config->demod_address, - .flags = I2C_M_RD, - .buf = b1, - .len = 1 - } - }; - - ret = i2c_transfer(state->i2c, msg, 2); - if (ret != 2) - return -EIO; - - return b1[0]; -} - - -static int -lgs8gl5_update_reg(struct lgs8gl5_state *state, u8 reg, u8 data) -{ - lgs8gl5_read_reg(state, reg); - lgs8gl5_write_reg(state, reg, data); - return 0; -} - - -/* Writes into alternate device's register */ -/* TODO: Find out what that device is for! */ -static int -lgs8gl5_update_alt_reg(struct lgs8gl5_state *state, u8 reg, u8 data) -{ - int ret; - u8 b0[] = {reg}; - u8 b1[] = {0}; - u8 b2[] = {reg, data}; - struct i2c_msg msg[3] = { - { - .addr = state->config->demod_address + 2, - .flags = 0, - .buf = b0, - .len = 1 - }, - { - .addr = state->config->demod_address + 2, - .flags = I2C_M_RD, - .buf = b1, - .len = 1 - }, - { - .addr = state->config->demod_address + 2, - .flags = 0, - .buf = b2, - .len = 2 - }, - }; - - ret = i2c_transfer(state->i2c, msg, 3); - return (ret != 3) ? -1 : 0; -} - - -static void -lgs8gl5_soft_reset(struct lgs8gl5_state *state) -{ - u8 val; - - dprintk("%s\n", __func__); - - val = lgs8gl5_read_reg(state, REG_RESET); - lgs8gl5_write_reg(state, REG_RESET, val & ~REG_RESET_OFF); - lgs8gl5_write_reg(state, REG_RESET, val | REG_RESET_OFF); - msleep(5); -} - - -/* Starts demodulation */ -static void -lgs8gl5_start_demod(struct lgs8gl5_state *state) -{ - u8 val; - int n; - - dprintk("%s\n", __func__); - - lgs8gl5_update_alt_reg(state, 0xc2, 0x28); - lgs8gl5_soft_reset(state); - lgs8gl5_update_reg(state, REG_07, 0x10); - lgs8gl5_update_reg(state, REG_07, 0x10); - lgs8gl5_write_reg(state, REG_09, 0x0e); - lgs8gl5_write_reg(state, REG_0A, 0xe5); - lgs8gl5_write_reg(state, REG_0B, 0x35); - lgs8gl5_write_reg(state, REG_0C, 0x30); - - lgs8gl5_update_reg(state, REG_03, 0x00); - lgs8gl5_update_reg(state, REG_7E, 0x01); - lgs8gl5_update_alt_reg(state, 0xc5, 0x00); - lgs8gl5_update_reg(state, REG_04, 0x02); - lgs8gl5_update_reg(state, REG_37, 0x01); - lgs8gl5_soft_reset(state); - - /* Wait for carrier */ - for (n = 0; n < 10; n++) { - val = lgs8gl5_read_reg(state, REG_STRENGTH); - dprintk("Wait for carrier[%d] 0x%02X\n", n, val); - if (val & REG_STRENGTH_CARRIER) - break; - msleep(4); - } - if (!(val & REG_STRENGTH_CARRIER)) - return; - - /* Wait for lock */ - for (n = 0; n < 20; n++) { - val = lgs8gl5_read_reg(state, REG_STATUS); - dprintk("Wait for lock[%d] 0x%02X\n", n, val); - if (val & REG_STATUS_LOCK) - break; - msleep(12); - } - if (!(val & REG_STATUS_LOCK)) - return; - - lgs8gl5_write_reg(state, REG_7D, lgs8gl5_read_reg(state, REG_A2)); - lgs8gl5_soft_reset(state); -} - - -static int -lgs8gl5_init(struct dvb_frontend *fe) -{ - struct lgs8gl5_state *state = fe->demodulator_priv; - - dprintk("%s\n", __func__); - - lgs8gl5_update_alt_reg(state, 0xc2, 0x28); - lgs8gl5_soft_reset(state); - lgs8gl5_update_reg(state, REG_07, 0x10); - lgs8gl5_update_reg(state, REG_07, 0x10); - lgs8gl5_write_reg(state, REG_09, 0x0e); - lgs8gl5_write_reg(state, REG_0A, 0xe5); - lgs8gl5_write_reg(state, REG_0B, 0x35); - lgs8gl5_write_reg(state, REG_0C, 0x30); - - return 0; -} - - -static int -lgs8gl5_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct lgs8gl5_state *state = fe->demodulator_priv; - u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); - u8 flags = lgs8gl5_read_reg(state, REG_STATUS); - - *status = 0; - - if ((level & REG_STRENGTH_MASK) > 0) - *status |= FE_HAS_SIGNAL; - if (level & REG_STRENGTH_CARRIER) - *status |= FE_HAS_CARRIER; - if (flags & REG_STATUS_SYNC) - *status |= FE_HAS_SYNC; - if (flags & REG_STATUS_LOCK) - *status |= FE_HAS_LOCK; - - return 0; -} - - -static int -lgs8gl5_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - *ber = 0; - - return 0; -} - - -static int -lgs8gl5_read_signal_strength(struct dvb_frontend *fe, u16 *signal_strength) -{ - struct lgs8gl5_state *state = fe->demodulator_priv; - u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); - *signal_strength = (level & REG_STRENGTH_MASK) << 8; - - return 0; -} - - -static int -lgs8gl5_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct lgs8gl5_state *state = fe->demodulator_priv; - u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); - *snr = (level & REG_STRENGTH_MASK) << 8; - - return 0; -} - - -static int -lgs8gl5_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - *ucblocks = 0; - - return 0; -} - - -static int -lgs8gl5_set_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *p = &fe->dtv_property_cache; - struct lgs8gl5_state *state = fe->demodulator_priv; - - dprintk("%s\n", __func__); - - if (p->bandwidth_hz != 8000000) - return -EINVAL; - - if (fe->ops.tuner_ops.set_params) { - fe->ops.tuner_ops.set_params(fe); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } - - /* lgs8gl5_set_inversion(state, p->inversion); */ - - lgs8gl5_start_demod(state); - - return 0; -} - - -static int -lgs8gl5_get_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *p = &fe->dtv_property_cache; - struct lgs8gl5_state *state = fe->demodulator_priv; - u8 inv = lgs8gl5_read_reg(state, REG_INVERSION); - - p->inversion = (inv & REG_INVERSION_ON) ? INVERSION_ON : INVERSION_OFF; - - p->code_rate_HP = FEC_1_2; - p->code_rate_LP = FEC_7_8; - p->guard_interval = GUARD_INTERVAL_1_32; - p->transmission_mode = TRANSMISSION_MODE_2K; - p->modulation = QAM_64; - p->hierarchy = HIERARCHY_NONE; - p->bandwidth_hz = 8000000; - - return 0; -} - - -static int -lgs8gl5_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *fesettings) -{ - fesettings->min_delay_ms = 240; - fesettings->step_size = 0; - fesettings->max_drift = 0; - return 0; -} - - -static void -lgs8gl5_release(struct dvb_frontend *fe) -{ - struct lgs8gl5_state *state = fe->demodulator_priv; - kfree(state); -} - - -static struct dvb_frontend_ops lgs8gl5_ops; - - -struct dvb_frontend* -lgs8gl5_attach(const struct lgs8gl5_config *config, struct i2c_adapter *i2c) -{ - struct lgs8gl5_state *state = NULL; - - dprintk("%s\n", __func__); - - /* Allocate memory for the internal state */ - state = kzalloc(sizeof(struct lgs8gl5_state), GFP_KERNEL); - if (state == NULL) - goto error; - - /* Setup the state */ - state->config = config; - state->i2c = i2c; - - /* Check if the demod is there */ - if (lgs8gl5_read_reg(state, REG_RESET) < 0) - goto error; - - /* Create dvb_frontend */ - memcpy(&state->frontend.ops, &lgs8gl5_ops, - sizeof(struct dvb_frontend_ops)); - state->frontend.demodulator_priv = state; - return &state->frontend; - -error: - kfree(state); - return NULL; -} -EXPORT_SYMBOL(lgs8gl5_attach); - - -static struct dvb_frontend_ops lgs8gl5_ops = { - .delsys = { SYS_DMBTH }, - .info = { - .name = "Legend Silicon LGS-8GL5 DMB-TH", - .frequency_min = 474000000, - .frequency_max = 858000000, - .frequency_stepsize = 10000, - .frequency_tolerance = 0, - .caps = FE_CAN_FEC_AUTO | - FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_32 | - FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_BANDWIDTH_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO | - FE_CAN_RECOVER - }, - - .release = lgs8gl5_release, - - .init = lgs8gl5_init, - - .set_frontend = lgs8gl5_set_frontend, - .get_frontend = lgs8gl5_get_frontend, - .get_tune_settings = lgs8gl5_get_tune_settings, - - .read_status = lgs8gl5_read_status, - .read_ber = lgs8gl5_read_ber, - .read_signal_strength = lgs8gl5_read_signal_strength, - .read_snr = lgs8gl5_read_snr, - .read_ucblocks = lgs8gl5_read_ucblocks, -}; - - -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -MODULE_DESCRIPTION("Legend Silicon LGS-8GL5 DMB-TH Demodulator driver"); -MODULE_AUTHOR("Timothy Lee"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/lgs8gxx.c b/drivers/media/dvb/frontends/lgs8gxx.c deleted file mode 100644 index c2ea2749ebed..000000000000 --- a/drivers/media/dvb/frontends/lgs8gxx.c +++ /dev/null @@ -1,1075 +0,0 @@ -/* - * Support for Legend Silicon GB20600 (a.k.a DMB-TH) demodulator - * LGS8913, LGS8GL5, LGS8G75 - * experimental support LGS8G42, LGS8G52 - * - * Copyright (C) 2007-2009 David T.L. Wong - * Copyright (C) 2008 Sirius International (Hong Kong) Limited - * Timothy Lee (for initial work on LGS8GL5) - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include -#include - -#include "dvb_frontend.h" - -#include "lgs8gxx.h" -#include "lgs8gxx_priv.h" - -#define dprintk(args...) \ - do { \ - if (debug) \ - printk(KERN_DEBUG "lgs8gxx: " args); \ - } while (0) - -static int debug; -static int fake_signal_str = 1; - -#define LGS8GXX_FIRMWARE "lgs8g75.fw" - -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -module_param(fake_signal_str, int, 0644); -MODULE_PARM_DESC(fake_signal_str, "fake signal strength for LGS8913." -"Signal strength calculation is slow.(default:on)."); - -/* LGS8GXX internal helper functions */ - -static int lgs8gxx_write_reg(struct lgs8gxx_state *priv, u8 reg, u8 data) -{ - int ret; - u8 buf[] = { reg, data }; - struct i2c_msg msg = { .flags = 0, .buf = buf, .len = 2 }; - - msg.addr = priv->config->demod_address; - if (priv->config->prod != LGS8GXX_PROD_LGS8G75 && reg >= 0xC0) - msg.addr += 0x02; - - if (debug >= 2) - dprintk("%s: reg=0x%02X, data=0x%02X\n", __func__, reg, data); - - ret = i2c_transfer(priv->i2c, &msg, 1); - - if (ret != 1) - dprintk("%s: error reg=0x%x, data=0x%x, ret=%i\n", - __func__, reg, data, ret); - - return (ret != 1) ? -1 : 0; -} - -static int lgs8gxx_read_reg(struct lgs8gxx_state *priv, u8 reg, u8 *p_data) -{ - int ret; - u8 dev_addr; - - u8 b0[] = { reg }; - u8 b1[] = { 0 }; - struct i2c_msg msg[] = { - { .flags = 0, .buf = b0, .len = 1 }, - { .flags = I2C_M_RD, .buf = b1, .len = 1 }, - }; - - dev_addr = priv->config->demod_address; - if (priv->config->prod != LGS8GXX_PROD_LGS8G75 && reg >= 0xC0) - dev_addr += 0x02; - msg[1].addr = msg[0].addr = dev_addr; - - ret = i2c_transfer(priv->i2c, msg, 2); - if (ret != 2) { - dprintk("%s: error reg=0x%x, ret=%i\n", __func__, reg, ret); - return -1; - } - - *p_data = b1[0]; - if (debug >= 2) - dprintk("%s: reg=0x%02X, data=0x%02X\n", __func__, reg, b1[0]); - return 0; -} - -static int lgs8gxx_soft_reset(struct lgs8gxx_state *priv) -{ - lgs8gxx_write_reg(priv, 0x02, 0x00); - msleep(1); - lgs8gxx_write_reg(priv, 0x02, 0x01); - msleep(100); - - return 0; -} - -static int wait_reg_mask(struct lgs8gxx_state *priv, u8 reg, u8 mask, - u8 val, u8 delay, u8 tries) -{ - u8 t; - int i; - - for (i = 0; i < tries; i++) { - lgs8gxx_read_reg(priv, reg, &t); - - if ((t & mask) == val) - return 0; - msleep(delay); - } - - return 1; -} - -static int lgs8gxx_set_ad_mode(struct lgs8gxx_state *priv) -{ - const struct lgs8gxx_config *config = priv->config; - u8 if_conf; - - if_conf = 0x10; /* AGC output on, RF_AGC output off; */ - - if_conf |= - ((config->ext_adc) ? 0x80 : 0x00) | - ((config->if_neg_center) ? 0x04 : 0x00) | - ((config->if_freq == 0) ? 0x08 : 0x00) | /* Baseband */ - ((config->adc_signed) ? 0x02 : 0x00) | - ((config->if_neg_edge) ? 0x01 : 0x00); - - if (config->ext_adc && - (config->prod == LGS8GXX_PROD_LGS8G52)) { - lgs8gxx_write_reg(priv, 0xBA, 0x40); - } - - lgs8gxx_write_reg(priv, 0x07, if_conf); - - return 0; -} - -static int lgs8gxx_set_if_freq(struct lgs8gxx_state *priv, u32 freq /*in kHz*/) -{ - u64 val; - u32 v32; - u32 if_clk; - - if_clk = priv->config->if_clk_freq; - - val = freq; - if (freq != 0) { - val <<= 32; - if (if_clk != 0) - do_div(val, if_clk); - v32 = val & 0xFFFFFFFF; - dprintk("Set IF Freq to %dkHz\n", freq); - } else { - v32 = 0; - dprintk("Set IF Freq to baseband\n"); - } - dprintk("AFC_INIT_FREQ = 0x%08X\n", v32); - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - lgs8gxx_write_reg(priv, 0x08, 0xFF & (v32)); - lgs8gxx_write_reg(priv, 0x09, 0xFF & (v32 >> 8)); - lgs8gxx_write_reg(priv, 0x0A, 0xFF & (v32 >> 16)); - lgs8gxx_write_reg(priv, 0x0B, 0xFF & (v32 >> 24)); - } else { - lgs8gxx_write_reg(priv, 0x09, 0xFF & (v32)); - lgs8gxx_write_reg(priv, 0x0A, 0xFF & (v32 >> 8)); - lgs8gxx_write_reg(priv, 0x0B, 0xFF & (v32 >> 16)); - lgs8gxx_write_reg(priv, 0x0C, 0xFF & (v32 >> 24)); - } - - return 0; -} - -static int lgs8gxx_get_afc_phase(struct lgs8gxx_state *priv) -{ - u64 val; - u32 v32 = 0; - u8 reg_addr, t; - int i; - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) - reg_addr = 0x23; - else - reg_addr = 0x48; - - for (i = 0; i < 4; i++) { - lgs8gxx_read_reg(priv, reg_addr, &t); - v32 <<= 8; - v32 |= t; - reg_addr--; - } - - val = v32; - val *= priv->config->if_clk_freq; - val >>= 32; - dprintk("AFC = %u kHz\n", (u32)val); - return 0; -} - -static int lgs8gxx_set_mode_auto(struct lgs8gxx_state *priv) -{ - u8 t; - u8 prod = priv->config->prod; - - if (prod == LGS8GXX_PROD_LGS8913) - lgs8gxx_write_reg(priv, 0xC6, 0x01); - - if (prod == LGS8GXX_PROD_LGS8G75) { - lgs8gxx_read_reg(priv, 0x0C, &t); - t &= (~0x04); - lgs8gxx_write_reg(priv, 0x0C, t | 0x80); - lgs8gxx_write_reg(priv, 0x39, 0x00); - lgs8gxx_write_reg(priv, 0x3D, 0x04); - } else if (prod == LGS8GXX_PROD_LGS8913 || - prod == LGS8GXX_PROD_LGS8GL5 || - prod == LGS8GXX_PROD_LGS8G42 || - prod == LGS8GXX_PROD_LGS8G52 || - prod == LGS8GXX_PROD_LGS8G54) { - lgs8gxx_read_reg(priv, 0x7E, &t); - lgs8gxx_write_reg(priv, 0x7E, t | 0x01); - - /* clear FEC self reset */ - lgs8gxx_read_reg(priv, 0xC5, &t); - lgs8gxx_write_reg(priv, 0xC5, t & 0xE0); - } - - if (prod == LGS8GXX_PROD_LGS8913) { - /* FEC auto detect */ - lgs8gxx_write_reg(priv, 0xC1, 0x03); - - lgs8gxx_read_reg(priv, 0x7C, &t); - t = (t & 0x8C) | 0x03; - lgs8gxx_write_reg(priv, 0x7C, t); - - /* BER test mode */ - lgs8gxx_read_reg(priv, 0xC3, &t); - t = (t & 0xEF) | 0x10; - lgs8gxx_write_reg(priv, 0xC3, t); - } - - if (priv->config->prod == LGS8GXX_PROD_LGS8G52) - lgs8gxx_write_reg(priv, 0xD9, 0x40); - - return 0; -} - -static int lgs8gxx_set_mode_manual(struct lgs8gxx_state *priv) -{ - u8 t; - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - u8 t2; - lgs8gxx_read_reg(priv, 0x0C, &t); - t &= (~0x80); - lgs8gxx_write_reg(priv, 0x0C, t); - - lgs8gxx_read_reg(priv, 0x0C, &t); - lgs8gxx_read_reg(priv, 0x19, &t2); - - if (((t&0x03) == 0x01) && (t2&0x01)) { - lgs8gxx_write_reg(priv, 0x6E, 0x05); - lgs8gxx_write_reg(priv, 0x39, 0x02); - lgs8gxx_write_reg(priv, 0x39, 0x03); - lgs8gxx_write_reg(priv, 0x3D, 0x05); - lgs8gxx_write_reg(priv, 0x3E, 0x28); - lgs8gxx_write_reg(priv, 0x53, 0x80); - } else { - lgs8gxx_write_reg(priv, 0x6E, 0x3F); - lgs8gxx_write_reg(priv, 0x39, 0x00); - lgs8gxx_write_reg(priv, 0x3D, 0x04); - } - - lgs8gxx_soft_reset(priv); - return 0; - } - - /* turn off auto-detect; manual settings */ - lgs8gxx_write_reg(priv, 0x7E, 0); - if (priv->config->prod == LGS8GXX_PROD_LGS8913) - lgs8gxx_write_reg(priv, 0xC1, 0); - - lgs8gxx_read_reg(priv, 0xC5, &t); - t = (t & 0xE0) | 0x06; - lgs8gxx_write_reg(priv, 0xC5, t); - - lgs8gxx_soft_reset(priv); - - return 0; -} - -static int lgs8gxx_is_locked(struct lgs8gxx_state *priv, u8 *locked) -{ - int ret = 0; - u8 t; - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) - ret = lgs8gxx_read_reg(priv, 0x13, &t); - else - ret = lgs8gxx_read_reg(priv, 0x4B, &t); - if (ret != 0) - return ret; - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) - *locked = ((t & 0x80) == 0x80) ? 1 : 0; - else - *locked = ((t & 0xC0) == 0xC0) ? 1 : 0; - return 0; -} - -/* Wait for Code Acquisition Lock */ -static int lgs8gxx_wait_ca_lock(struct lgs8gxx_state *priv, u8 *locked) -{ - int ret = 0; - u8 reg, mask, val; - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - reg = 0x13; - mask = 0x80; - val = 0x80; - } else { - reg = 0x4B; - mask = 0xC0; - val = 0xC0; - } - - ret = wait_reg_mask(priv, reg, mask, val, 50, 40); - *locked = (ret == 0) ? 1 : 0; - - return 0; -} - -static int lgs8gxx_is_autodetect_finished(struct lgs8gxx_state *priv, - u8 *finished) -{ - int ret = 0; - u8 reg, mask, val; - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - reg = 0x1f; - mask = 0xC0; - val = 0x80; - } else { - reg = 0xA4; - mask = 0x03; - val = 0x01; - } - - ret = wait_reg_mask(priv, reg, mask, val, 10, 20); - *finished = (ret == 0) ? 1 : 0; - - return 0; -} - -static int lgs8gxx_autolock_gi(struct lgs8gxx_state *priv, u8 gi, u8 cpn, - u8 *locked) -{ - int err = 0; - u8 ad_fini = 0; - u8 t1, t2; - - if (gi == GI_945) - dprintk("try GI 945\n"); - else if (gi == GI_595) - dprintk("try GI 595\n"); - else if (gi == GI_420) - dprintk("try GI 420\n"); - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - lgs8gxx_read_reg(priv, 0x0C, &t1); - lgs8gxx_read_reg(priv, 0x18, &t2); - t1 &= ~(GI_MASK); - t1 |= gi; - t2 &= 0xFE; - t2 |= cpn ? 0x01 : 0x00; - lgs8gxx_write_reg(priv, 0x0C, t1); - lgs8gxx_write_reg(priv, 0x18, t2); - } else { - lgs8gxx_write_reg(priv, 0x04, gi); - } - lgs8gxx_soft_reset(priv); - err = lgs8gxx_wait_ca_lock(priv, locked); - if (err || !(*locked)) - return err; - err = lgs8gxx_is_autodetect_finished(priv, &ad_fini); - if (err != 0) - return err; - if (ad_fini) { - dprintk("auto detect finished\n"); - } else - *locked = 0; - - return 0; -} - -static int lgs8gxx_auto_detect(struct lgs8gxx_state *priv, - u8 *detected_param, u8 *gi) -{ - int i, j; - int err = 0; - u8 locked = 0, tmp_gi; - - dprintk("%s\n", __func__); - - lgs8gxx_set_mode_auto(priv); - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - lgs8gxx_write_reg(priv, 0x67, 0xAA); - lgs8gxx_write_reg(priv, 0x6E, 0x3F); - } else { - /* Guard Interval */ - lgs8gxx_write_reg(priv, 0x03, 00); - } - - for (i = 0; i < 2; i++) { - for (j = 0; j < 2; j++) { - tmp_gi = GI_945; - err = lgs8gxx_autolock_gi(priv, GI_945, j, &locked); - if (err) - goto out; - if (locked) - goto locked; - } - for (j = 0; j < 2; j++) { - tmp_gi = GI_420; - err = lgs8gxx_autolock_gi(priv, GI_420, j, &locked); - if (err) - goto out; - if (locked) - goto locked; - } - tmp_gi = GI_595; - err = lgs8gxx_autolock_gi(priv, GI_595, 1, &locked); - if (err) - goto out; - if (locked) - goto locked; - } - -locked: - if ((err == 0) && (locked == 1)) { - u8 t; - - if (priv->config->prod != LGS8GXX_PROD_LGS8G75) { - lgs8gxx_read_reg(priv, 0xA2, &t); - *detected_param = t; - } else { - lgs8gxx_read_reg(priv, 0x1F, &t); - *detected_param = t & 0x3F; - } - - if (tmp_gi == GI_945) - dprintk("GI 945 locked\n"); - else if (tmp_gi == GI_595) - dprintk("GI 595 locked\n"); - else if (tmp_gi == GI_420) - dprintk("GI 420 locked\n"); - *gi = tmp_gi; - } - if (!locked) - err = -1; - -out: - return err; -} - -static void lgs8gxx_auto_lock(struct lgs8gxx_state *priv) -{ - s8 err; - u8 gi = 0x2; - u8 detected_param = 0; - - err = lgs8gxx_auto_detect(priv, &detected_param, &gi); - - if (err != 0) { - dprintk("lgs8gxx_auto_detect failed\n"); - } else - dprintk("detected param = 0x%02X\n", detected_param); - - /* Apply detected parameters */ - if (priv->config->prod == LGS8GXX_PROD_LGS8913) { - u8 inter_leave_len = detected_param & TIM_MASK ; - /* Fix 8913 time interleaver detection bug */ - inter_leave_len = (inter_leave_len == TIM_MIDDLE) ? 0x60 : 0x40; - detected_param &= CF_MASK | SC_MASK | LGS_FEC_MASK; - detected_param |= inter_leave_len; - } - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - u8 t; - lgs8gxx_read_reg(priv, 0x19, &t); - t &= 0x81; - t |= detected_param << 1; - lgs8gxx_write_reg(priv, 0x19, t); - } else { - lgs8gxx_write_reg(priv, 0x7D, detected_param); - if (priv->config->prod == LGS8GXX_PROD_LGS8913) - lgs8gxx_write_reg(priv, 0xC0, detected_param); - } - /* lgs8gxx_soft_reset(priv); */ - - /* Enter manual mode */ - lgs8gxx_set_mode_manual(priv); - - switch (gi) { - case GI_945: - priv->curr_gi = 945; break; - case GI_595: - priv->curr_gi = 595; break; - case GI_420: - priv->curr_gi = 420; break; - default: - priv->curr_gi = 945; break; - } -} - -static int lgs8gxx_set_mpeg_mode(struct lgs8gxx_state *priv, - u8 serial, u8 clk_pol, u8 clk_gated) -{ - int ret = 0; - u8 t, reg_addr; - - reg_addr = (priv->config->prod == LGS8GXX_PROD_LGS8G75) ? 0x30 : 0xC2; - ret = lgs8gxx_read_reg(priv, reg_addr, &t); - if (ret != 0) - return ret; - - t &= 0xF8; - t |= serial ? TS_SERIAL : TS_PARALLEL; - t |= clk_pol ? TS_CLK_INVERTED : TS_CLK_NORMAL; - t |= clk_gated ? TS_CLK_GATED : TS_CLK_FREERUN; - - ret = lgs8gxx_write_reg(priv, reg_addr, t); - if (ret != 0) - return ret; - - return 0; -} - -/* A/D input peak-to-peak voltage range */ -static int lgs8g75_set_adc_vpp(struct lgs8gxx_state *priv, - u8 sel) -{ - u8 r26 = 0x73, r27 = 0x90; - - if (priv->config->prod != LGS8GXX_PROD_LGS8G75) - return 0; - - r26 |= (sel & 0x01) << 7; - r27 |= (sel & 0x02) >> 1; - lgs8gxx_write_reg(priv, 0x26, r26); - lgs8gxx_write_reg(priv, 0x27, r27); - - return 0; -} - -/* LGS8913 demod frontend functions */ - -static int lgs8913_init(struct lgs8gxx_state *priv) -{ - u8 t; - - /* LGS8913 specific */ - lgs8gxx_write_reg(priv, 0xc1, 0x3); - - lgs8gxx_read_reg(priv, 0x7c, &t); - lgs8gxx_write_reg(priv, 0x7c, (t&0x8c) | 0x3); - - /* LGS8913 specific */ - lgs8gxx_read_reg(priv, 0xc3, &t); - lgs8gxx_write_reg(priv, 0xc3, t&0x10); - - - return 0; -} - -static int lgs8g75_init_data(struct lgs8gxx_state *priv) -{ - const struct firmware *fw; - int rc; - int i; - - rc = request_firmware(&fw, LGS8GXX_FIRMWARE, &priv->i2c->dev); - if (rc) - return rc; - - lgs8gxx_write_reg(priv, 0xC6, 0x40); - - lgs8gxx_write_reg(priv, 0x3D, 0x04); - lgs8gxx_write_reg(priv, 0x39, 0x00); - - lgs8gxx_write_reg(priv, 0x3A, 0x00); - lgs8gxx_write_reg(priv, 0x38, 0x00); - lgs8gxx_write_reg(priv, 0x3B, 0x00); - lgs8gxx_write_reg(priv, 0x38, 0x00); - - for (i = 0; i < fw->size; i++) { - lgs8gxx_write_reg(priv, 0x38, 0x00); - lgs8gxx_write_reg(priv, 0x3A, (u8)(i&0xff)); - lgs8gxx_write_reg(priv, 0x3B, (u8)(i>>8)); - lgs8gxx_write_reg(priv, 0x3C, fw->data[i]); - } - - lgs8gxx_write_reg(priv, 0x38, 0x00); - - release_firmware(fw); - return 0; -} - -static int lgs8gxx_init(struct dvb_frontend *fe) -{ - struct lgs8gxx_state *priv = - (struct lgs8gxx_state *)fe->demodulator_priv; - const struct lgs8gxx_config *config = priv->config; - u8 data = 0; - s8 err; - dprintk("%s\n", __func__); - - lgs8gxx_read_reg(priv, 0, &data); - dprintk("reg 0 = 0x%02X\n", data); - - if (config->prod == LGS8GXX_PROD_LGS8G75) - lgs8g75_set_adc_vpp(priv, config->adc_vpp); - - /* Setup MPEG output format */ - err = lgs8gxx_set_mpeg_mode(priv, config->serial_ts, - config->ts_clk_pol, - config->ts_clk_gated); - if (err != 0) - return -EIO; - - if (config->prod == LGS8GXX_PROD_LGS8913) - lgs8913_init(priv); - lgs8gxx_set_if_freq(priv, priv->config->if_freq); - lgs8gxx_set_ad_mode(priv); - - return 0; -} - -static void lgs8gxx_release(struct dvb_frontend *fe) -{ - struct lgs8gxx_state *state = fe->demodulator_priv; - dprintk("%s\n", __func__); - - kfree(state); -} - - -static int lgs8gxx_write(struct dvb_frontend *fe, const u8 buf[], int len) -{ - struct lgs8gxx_state *priv = fe->demodulator_priv; - - if (len != 2) - return -EINVAL; - - return lgs8gxx_write_reg(priv, buf[0], buf[1]); -} - -static int lgs8gxx_set_fe(struct dvb_frontend *fe) -{ - - struct lgs8gxx_state *priv = fe->demodulator_priv; - - dprintk("%s\n", __func__); - - /* set frequency */ - if (fe->ops.tuner_ops.set_params) { - fe->ops.tuner_ops.set_params(fe); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } - - /* start auto lock */ - lgs8gxx_auto_lock(priv); - - msleep(10); - - return 0; -} - -static int lgs8gxx_get_fe(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *fe_params = &fe->dtv_property_cache; - dprintk("%s\n", __func__); - - /* TODO: get real readings from device */ - /* inversion status */ - fe_params->inversion = INVERSION_OFF; - - /* bandwidth */ - fe_params->bandwidth_hz = 8000000; - - fe_params->code_rate_HP = FEC_AUTO; - fe_params->code_rate_LP = FEC_AUTO; - - fe_params->modulation = QAM_AUTO; - - /* transmission mode */ - fe_params->transmission_mode = TRANSMISSION_MODE_AUTO; - - /* guard interval */ - fe_params->guard_interval = GUARD_INTERVAL_AUTO; - - /* hierarchy */ - fe_params->hierarchy = HIERARCHY_NONE; - - return 0; -} - -static -int lgs8gxx_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *fesettings) -{ - /* FIXME: copy from tda1004x.c */ - fesettings->min_delay_ms = 800; - fesettings->step_size = 0; - fesettings->max_drift = 0; - return 0; -} - -static int lgs8gxx_read_status(struct dvb_frontend *fe, fe_status_t *fe_status) -{ - struct lgs8gxx_state *priv = fe->demodulator_priv; - s8 ret; - u8 t, locked = 0; - - dprintk("%s\n", __func__); - *fe_status = 0; - - lgs8gxx_get_afc_phase(priv); - lgs8gxx_is_locked(priv, &locked); - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - if (locked) - *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - return 0; - } - - ret = lgs8gxx_read_reg(priv, 0x4B, &t); - if (ret != 0) - return -EIO; - - dprintk("Reg 0x4B: 0x%02X\n", t); - - *fe_status = 0; - if (priv->config->prod == LGS8GXX_PROD_LGS8913) { - if ((t & 0x40) == 0x40) - *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER; - if ((t & 0x80) == 0x80) - *fe_status |= FE_HAS_VITERBI | FE_HAS_SYNC | - FE_HAS_LOCK; - } else { - if ((t & 0x80) == 0x80) - *fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - } - - /* success */ - dprintk("%s: fe_status=0x%x\n", __func__, *fe_status); - return 0; -} - -static int lgs8gxx_read_signal_agc(struct lgs8gxx_state *priv, u16 *signal) -{ - u16 v; - u8 agc_lvl[2], cat; - - dprintk("%s()\n", __func__); - lgs8gxx_read_reg(priv, 0x3F, &agc_lvl[0]); - lgs8gxx_read_reg(priv, 0x3E, &agc_lvl[1]); - - v = agc_lvl[0]; - v <<= 8; - v |= agc_lvl[1]; - - dprintk("agc_lvl: 0x%04X\n", v); - - if (v < 0x100) - cat = 0; - else if (v < 0x190) - cat = 5; - else if (v < 0x2A8) - cat = 4; - else if (v < 0x381) - cat = 3; - else if (v < 0x400) - cat = 2; - else if (v == 0x400) - cat = 1; - else - cat = 0; - - *signal = cat * 65535 / 5; - - return 0; -} - -static int lgs8913_read_signal_strength(struct lgs8gxx_state *priv, u16 *signal) -{ - u8 t; s8 ret; - s16 max_strength = 0; - u8 str; - u16 i, gi = priv->curr_gi; - - dprintk("%s\n", __func__); - - ret = lgs8gxx_read_reg(priv, 0x4B, &t); - if (ret != 0) - return -EIO; - - if (fake_signal_str) { - if ((t & 0xC0) == 0xC0) { - dprintk("Fake signal strength\n"); - *signal = 0x7FFF; - } else - *signal = 0; - return 0; - } - - dprintk("gi = %d\n", gi); - for (i = 0; i < gi; i++) { - - if ((i & 0xFF) == 0) - lgs8gxx_write_reg(priv, 0x84, 0x03 & (i >> 8)); - lgs8gxx_write_reg(priv, 0x83, i & 0xFF); - - lgs8gxx_read_reg(priv, 0x94, &str); - if (max_strength < str) - max_strength = str; - } - - *signal = max_strength; - dprintk("%s: signal=0x%02X\n", __func__, *signal); - - lgs8gxx_read_reg(priv, 0x95, &t); - dprintk("%s: AVG Noise=0x%02X\n", __func__, t); - - return 0; -} - -static int lgs8g75_read_signal_strength(struct lgs8gxx_state *priv, u16 *signal) -{ - u8 t; - s16 v = 0; - - dprintk("%s\n", __func__); - - lgs8gxx_read_reg(priv, 0xB1, &t); - v |= t; - v <<= 8; - lgs8gxx_read_reg(priv, 0xB0, &t); - v |= t; - - *signal = v; - dprintk("%s: signal=0x%02X\n", __func__, *signal); - - return 0; -} - -static int lgs8gxx_read_signal_strength(struct dvb_frontend *fe, u16 *signal) -{ - struct lgs8gxx_state *priv = fe->demodulator_priv; - - if (priv->config->prod == LGS8GXX_PROD_LGS8913) - return lgs8913_read_signal_strength(priv, signal); - else if (priv->config->prod == LGS8GXX_PROD_LGS8G75) - return lgs8g75_read_signal_strength(priv, signal); - else - return lgs8gxx_read_signal_agc(priv, signal); -} - -static int lgs8gxx_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct lgs8gxx_state *priv = fe->demodulator_priv; - u8 t; - *snr = 0; - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) - lgs8gxx_read_reg(priv, 0x34, &t); - else - lgs8gxx_read_reg(priv, 0x95, &t); - dprintk("AVG Noise=0x%02X\n", t); - *snr = 256 - t; - *snr <<= 8; - dprintk("snr=0x%x\n", *snr); - - return 0; -} - -static int lgs8gxx_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - *ucblocks = 0; - dprintk("%s: ucblocks=0x%x\n", __func__, *ucblocks); - return 0; -} - -static void packet_counter_start(struct lgs8gxx_state *priv) -{ - u8 orig, t; - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - lgs8gxx_read_reg(priv, 0x30, &orig); - orig &= 0xE7; - t = orig | 0x10; - lgs8gxx_write_reg(priv, 0x30, t); - t = orig | 0x18; - lgs8gxx_write_reg(priv, 0x30, t); - t = orig | 0x10; - lgs8gxx_write_reg(priv, 0x30, t); - } else { - lgs8gxx_write_reg(priv, 0xC6, 0x01); - lgs8gxx_write_reg(priv, 0xC6, 0x41); - lgs8gxx_write_reg(priv, 0xC6, 0x01); - } -} - -static void packet_counter_stop(struct lgs8gxx_state *priv) -{ - u8 t; - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - lgs8gxx_read_reg(priv, 0x30, &t); - t &= 0xE7; - lgs8gxx_write_reg(priv, 0x30, t); - } else { - lgs8gxx_write_reg(priv, 0xC6, 0x81); - } -} - -static int lgs8gxx_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct lgs8gxx_state *priv = fe->demodulator_priv; - u8 reg_err, reg_total, t; - u32 total_cnt = 0, err_cnt = 0; - int i; - - dprintk("%s\n", __func__); - - packet_counter_start(priv); - msleep(200); - packet_counter_stop(priv); - - if (priv->config->prod == LGS8GXX_PROD_LGS8G75) { - reg_total = 0x28; reg_err = 0x2C; - } else { - reg_total = 0xD0; reg_err = 0xD4; - } - - for (i = 0; i < 4; i++) { - total_cnt <<= 8; - lgs8gxx_read_reg(priv, reg_total+3-i, &t); - total_cnt |= t; - } - for (i = 0; i < 4; i++) { - err_cnt <<= 8; - lgs8gxx_read_reg(priv, reg_err+3-i, &t); - err_cnt |= t; - } - dprintk("error=%d total=%d\n", err_cnt, total_cnt); - - if (total_cnt == 0) - *ber = 0; - else - *ber = err_cnt * 100 / total_cnt; - - dprintk("%s: ber=0x%x\n", __func__, *ber); - return 0; -} - -static int lgs8gxx_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct lgs8gxx_state *priv = fe->demodulator_priv; - - if (priv->config->tuner_address == 0) - return 0; - if (enable) { - u8 v = 0x80 | priv->config->tuner_address; - return lgs8gxx_write_reg(priv, 0x01, v); - } - return lgs8gxx_write_reg(priv, 0x01, 0); -} - -static struct dvb_frontend_ops lgs8gxx_ops = { - .delsys = { SYS_DMBTH }, - .info = { - .name = "Legend Silicon LGS8913/LGS8GXX DMB-TH", - .frequency_min = 474000000, - .frequency_max = 858000000, - .frequency_stepsize = 10000, - .caps = - FE_CAN_FEC_AUTO | - FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO - }, - - .release = lgs8gxx_release, - - .init = lgs8gxx_init, - .write = lgs8gxx_write, - .i2c_gate_ctrl = lgs8gxx_i2c_gate_ctrl, - - .set_frontend = lgs8gxx_set_fe, - .get_frontend = lgs8gxx_get_fe, - .get_tune_settings = lgs8gxx_get_tune_settings, - - .read_status = lgs8gxx_read_status, - .read_ber = lgs8gxx_read_ber, - .read_signal_strength = lgs8gxx_read_signal_strength, - .read_snr = lgs8gxx_read_snr, - .read_ucblocks = lgs8gxx_read_ucblocks, -}; - -struct dvb_frontend *lgs8gxx_attach(const struct lgs8gxx_config *config, - struct i2c_adapter *i2c) -{ - struct lgs8gxx_state *priv = NULL; - u8 data = 0; - - dprintk("%s()\n", __func__); - - if (config == NULL || i2c == NULL) - return NULL; - - priv = kzalloc(sizeof(struct lgs8gxx_state), GFP_KERNEL); - if (priv == NULL) - goto error_out; - - priv->config = config; - priv->i2c = i2c; - - /* check if the demod is there */ - if (lgs8gxx_read_reg(priv, 0, &data) != 0) { - dprintk("%s lgs8gxx not found at i2c addr 0x%02X\n", - __func__, priv->config->demod_address); - goto error_out; - } - - lgs8gxx_read_reg(priv, 1, &data); - - memcpy(&priv->frontend.ops, &lgs8gxx_ops, - sizeof(struct dvb_frontend_ops)); - priv->frontend.demodulator_priv = priv; - - if (config->prod == LGS8GXX_PROD_LGS8G75) - lgs8g75_init_data(priv); - - return &priv->frontend; - -error_out: - dprintk("%s() error_out\n", __func__); - kfree(priv); - return NULL; - -} -EXPORT_SYMBOL(lgs8gxx_attach); - -MODULE_DESCRIPTION("Legend Silicon LGS8913/LGS8GXX DMB-TH demodulator driver"); -MODULE_AUTHOR("David T. L. Wong "); -MODULE_LICENSE("GPL"); -MODULE_FIRMWARE(LGS8GXX_FIRMWARE); diff --git a/drivers/media/dvb/frontends/m88rs2000.c b/drivers/media/dvb/frontends/m88rs2000.c deleted file mode 100644 index 312588e84dae..000000000000 --- a/drivers/media/dvb/frontends/m88rs2000.c +++ /dev/null @@ -1,919 +0,0 @@ -/* - Driver for M88RS2000 demodulator and tuner - - Copyright (C) 2012 Malcolm Priestley (tvboxspy@gmail.com) - Beta Driver - - Include various calculation code from DS3000 driver. - Copyright (C) 2009 Konstantin Dimitrov. - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ -#include -#include -#include -#include -#include -#include -#include - - -#include "dvb_frontend.h" -#include "m88rs2000.h" - -struct m88rs2000_state { - struct i2c_adapter *i2c; - const struct m88rs2000_config *config; - struct dvb_frontend frontend; - u8 no_lock_count; - u32 tuner_frequency; - u32 symbol_rate; - fe_code_rate_t fec_inner; - u8 tuner_level; - int errmode; -}; - -static int m88rs2000_debug; - -module_param_named(debug, m88rs2000_debug, int, 0644); -MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); - -#define dprintk(level, args...) do { \ - if (level & m88rs2000_debug) \ - printk(KERN_DEBUG "m88rs2000-fe: " args); \ -} while (0) - -#define deb_info(args...) dprintk(0x01, args) -#define info(format, arg...) \ - printk(KERN_INFO "m88rs2000-fe: " format "\n" , ## arg) - -static int m88rs2000_writereg(struct m88rs2000_state *state, u8 tuner, - u8 reg, u8 data) -{ - int ret; - u8 addr = (tuner == 0) ? state->config->tuner_addr : - state->config->demod_addr; - u8 buf[] = { reg, data }; - struct i2c_msg msg = { - .addr = addr, - .flags = 0, - .buf = buf, - .len = 2 - }; - - ret = i2c_transfer(state->i2c, &msg, 1); - - if (ret != 1) - deb_info("%s: writereg error (reg == 0x%02x, val == 0x%02x, " - "ret == %i)\n", __func__, reg, data, ret); - - return (ret != 1) ? -EREMOTEIO : 0; -} - -static int m88rs2000_demod_write(struct m88rs2000_state *state, u8 reg, u8 data) -{ - return m88rs2000_writereg(state, 1, reg, data); -} - -static int m88rs2000_tuner_write(struct m88rs2000_state *state, u8 reg, u8 data) -{ - m88rs2000_demod_write(state, 0x81, 0x84); - udelay(10); - return m88rs2000_writereg(state, 0, reg, data); - -} - -static int m88rs2000_write(struct dvb_frontend *fe, const u8 buf[], int len) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - - if (len != 2) - return -EINVAL; - - return m88rs2000_writereg(state, 1, buf[0], buf[1]); -} - -static u8 m88rs2000_readreg(struct m88rs2000_state *state, u8 tuner, u8 reg) -{ - int ret; - u8 b0[] = { reg }; - u8 b1[] = { 0 }; - u8 addr = (tuner == 0) ? state->config->tuner_addr : - state->config->demod_addr; - struct i2c_msg msg[] = { - { - .addr = addr, - .flags = 0, - .buf = b0, - .len = 1 - }, { - .addr = addr, - .flags = I2C_M_RD, - .buf = b1, - .len = 1 - } - }; - - ret = i2c_transfer(state->i2c, msg, 2); - - if (ret != 2) - deb_info("%s: readreg error (reg == 0x%02x, ret == %i)\n", - __func__, reg, ret); - - return b1[0]; -} - -static u8 m88rs2000_demod_read(struct m88rs2000_state *state, u8 reg) -{ - return m88rs2000_readreg(state, 1, reg); -} - -static u8 m88rs2000_tuner_read(struct m88rs2000_state *state, u8 reg) -{ - m88rs2000_demod_write(state, 0x81, 0x85); - udelay(10); - return m88rs2000_readreg(state, 0, reg); -} - -static int m88rs2000_set_symbolrate(struct dvb_frontend *fe, u32 srate) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - int ret; - u32 temp; - u8 b[3]; - - if ((srate < 1000000) || (srate > 45000000)) - return -EINVAL; - - temp = srate / 1000; - temp *= 11831; - temp /= 68; - temp -= 3; - - b[0] = (u8) (temp >> 16) & 0xff; - b[1] = (u8) (temp >> 8) & 0xff; - b[2] = (u8) temp & 0xff; - ret = m88rs2000_demod_write(state, 0x93, b[2]); - ret |= m88rs2000_demod_write(state, 0x94, b[1]); - ret |= m88rs2000_demod_write(state, 0x95, b[0]); - - deb_info("m88rs2000: m88rs2000_set_symbolrate\n"); - return ret; -} - -static int m88rs2000_send_diseqc_msg(struct dvb_frontend *fe, - struct dvb_diseqc_master_cmd *m) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - - int i; - u8 reg; - deb_info("%s\n", __func__); - m88rs2000_demod_write(state, 0x9a, 0x30); - reg = m88rs2000_demod_read(state, 0xb2); - reg &= 0x3f; - m88rs2000_demod_write(state, 0xb2, reg); - for (i = 0; i < m->msg_len; i++) - m88rs2000_demod_write(state, 0xb3 + i, m->msg[i]); - - reg = m88rs2000_demod_read(state, 0xb1); - reg &= 0x87; - reg |= ((m->msg_len - 1) << 3) | 0x07; - reg &= 0x7f; - m88rs2000_demod_write(state, 0xb1, reg); - - for (i = 0; i < 15; i++) { - if ((m88rs2000_demod_read(state, 0xb1) & 0x40) == 0x0) - break; - msleep(20); - } - - reg = m88rs2000_demod_read(state, 0xb1); - if ((reg & 0x40) > 0x0) { - reg &= 0x7f; - reg |= 0x40; - m88rs2000_demod_write(state, 0xb1, reg); - } - - reg = m88rs2000_demod_read(state, 0xb2); - reg &= 0x3f; - reg |= 0x80; - m88rs2000_demod_write(state, 0xb2, reg); - m88rs2000_demod_write(state, 0x9a, 0xb0); - - - return 0; -} - -static int m88rs2000_send_diseqc_burst(struct dvb_frontend *fe, - fe_sec_mini_cmd_t burst) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - u8 reg0, reg1; - deb_info("%s\n", __func__); - m88rs2000_demod_write(state, 0x9a, 0x30); - msleep(50); - reg0 = m88rs2000_demod_read(state, 0xb1); - reg1 = m88rs2000_demod_read(state, 0xb2); - /* TODO complete this section */ - m88rs2000_demod_write(state, 0xb2, reg1); - m88rs2000_demod_write(state, 0xb1, reg0); - m88rs2000_demod_write(state, 0x9a, 0xb0); - - return 0; -} - -static int m88rs2000_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - u8 reg0, reg1; - m88rs2000_demod_write(state, 0x9a, 0x30); - reg0 = m88rs2000_demod_read(state, 0xb1); - reg1 = m88rs2000_demod_read(state, 0xb2); - - reg1 &= 0x3f; - - switch (tone) { - case SEC_TONE_ON: - reg0 |= 0x4; - reg0 &= 0xbc; - break; - case SEC_TONE_OFF: - reg1 |= 0x80; - break; - default: - break; - } - m88rs2000_demod_write(state, 0xb2, reg1); - m88rs2000_demod_write(state, 0xb1, reg0); - m88rs2000_demod_write(state, 0x9a, 0xb0); - return 0; -} - -struct inittab { - u8 cmd; - u8 reg; - u8 val; -}; - -struct inittab m88rs2000_setup[] = { - {DEMOD_WRITE, 0x9a, 0x30}, - {DEMOD_WRITE, 0x00, 0x01}, - {WRITE_DELAY, 0x19, 0x00}, - {DEMOD_WRITE, 0x00, 0x00}, - {DEMOD_WRITE, 0x9a, 0xb0}, - {DEMOD_WRITE, 0x81, 0xc1}, - {TUNER_WRITE, 0x42, 0x73}, - {TUNER_WRITE, 0x05, 0x07}, - {TUNER_WRITE, 0x20, 0x27}, - {TUNER_WRITE, 0x07, 0x02}, - {TUNER_WRITE, 0x11, 0xff}, - {TUNER_WRITE, 0x60, 0xf9}, - {TUNER_WRITE, 0x08, 0x01}, - {TUNER_WRITE, 0x00, 0x41}, - {DEMOD_WRITE, 0x81, 0x81}, - {DEMOD_WRITE, 0x86, 0xc6}, - {DEMOD_WRITE, 0x9a, 0x30}, - {DEMOD_WRITE, 0xf0, 0x22}, - {DEMOD_WRITE, 0xf1, 0xbf}, - {DEMOD_WRITE, 0xb0, 0x45}, - {DEMOD_WRITE, 0xb2, 0x01}, /* set voltage pin always set 1*/ - {DEMOD_WRITE, 0x9a, 0xb0}, - {0xff, 0xaa, 0xff} -}; - -struct inittab m88rs2000_shutdown[] = { - {DEMOD_WRITE, 0x9a, 0x30}, - {DEMOD_WRITE, 0xb0, 0x00}, - {DEMOD_WRITE, 0xf1, 0x89}, - {DEMOD_WRITE, 0x00, 0x01}, - {DEMOD_WRITE, 0x9a, 0xb0}, - {TUNER_WRITE, 0x00, 0x40}, - {DEMOD_WRITE, 0x81, 0x81}, - {0xff, 0xaa, 0xff} -}; - -struct inittab tuner_reset[] = { - {TUNER_WRITE, 0x42, 0x73}, - {TUNER_WRITE, 0x05, 0x07}, - {TUNER_WRITE, 0x20, 0x27}, - {TUNER_WRITE, 0x07, 0x02}, - {TUNER_WRITE, 0x11, 0xff}, - {TUNER_WRITE, 0x60, 0xf9}, - {TUNER_WRITE, 0x08, 0x01}, - {TUNER_WRITE, 0x00, 0x41}, - {0xff, 0xaa, 0xff} -}; - -struct inittab fe_reset[] = { - {DEMOD_WRITE, 0x00, 0x01}, - {DEMOD_WRITE, 0xf1, 0xbf}, - {DEMOD_WRITE, 0x00, 0x01}, - {DEMOD_WRITE, 0x20, 0x81}, - {DEMOD_WRITE, 0x21, 0x80}, - {DEMOD_WRITE, 0x10, 0x33}, - {DEMOD_WRITE, 0x11, 0x44}, - {DEMOD_WRITE, 0x12, 0x07}, - {DEMOD_WRITE, 0x18, 0x20}, - {DEMOD_WRITE, 0x28, 0x04}, - {DEMOD_WRITE, 0x29, 0x8e}, - {DEMOD_WRITE, 0x3b, 0xff}, - {DEMOD_WRITE, 0x32, 0x10}, - {DEMOD_WRITE, 0x33, 0x02}, - {DEMOD_WRITE, 0x34, 0x30}, - {DEMOD_WRITE, 0x35, 0xff}, - {DEMOD_WRITE, 0x38, 0x50}, - {DEMOD_WRITE, 0x39, 0x68}, - {DEMOD_WRITE, 0x3c, 0x7f}, - {DEMOD_WRITE, 0x3d, 0x0f}, - {DEMOD_WRITE, 0x45, 0x20}, - {DEMOD_WRITE, 0x46, 0x24}, - {DEMOD_WRITE, 0x47, 0x7c}, - {DEMOD_WRITE, 0x48, 0x16}, - {DEMOD_WRITE, 0x49, 0x04}, - {DEMOD_WRITE, 0x4a, 0x01}, - {DEMOD_WRITE, 0x4b, 0x78}, - {DEMOD_WRITE, 0X4d, 0xd2}, - {DEMOD_WRITE, 0x4e, 0x6d}, - {DEMOD_WRITE, 0x50, 0x30}, - {DEMOD_WRITE, 0x51, 0x30}, - {DEMOD_WRITE, 0x54, 0x7b}, - {DEMOD_WRITE, 0x56, 0x09}, - {DEMOD_WRITE, 0x58, 0x59}, - {DEMOD_WRITE, 0x59, 0x37}, - {DEMOD_WRITE, 0x63, 0xfa}, - {0xff, 0xaa, 0xff} -}; - -struct inittab fe_trigger[] = { - {DEMOD_WRITE, 0x97, 0x04}, - {DEMOD_WRITE, 0x99, 0x77}, - {DEMOD_WRITE, 0x9b, 0x64}, - {DEMOD_WRITE, 0x9e, 0x00}, - {DEMOD_WRITE, 0x9f, 0xf8}, - {DEMOD_WRITE, 0xa0, 0x20}, - {DEMOD_WRITE, 0xa1, 0xe0}, - {DEMOD_WRITE, 0xa3, 0x38}, - {DEMOD_WRITE, 0x98, 0xff}, - {DEMOD_WRITE, 0xc0, 0x0f}, - {DEMOD_WRITE, 0x89, 0x01}, - {DEMOD_WRITE, 0x00, 0x00}, - {WRITE_DELAY, 0x0a, 0x00}, - {DEMOD_WRITE, 0x00, 0x01}, - {DEMOD_WRITE, 0x00, 0x00}, - {DEMOD_WRITE, 0x9a, 0xb0}, - {0xff, 0xaa, 0xff} -}; - -static int m88rs2000_tab_set(struct m88rs2000_state *state, - struct inittab *tab) -{ - int ret = 0; - u8 i; - if (tab == NULL) - return -EINVAL; - - for (i = 0; i < 255; i++) { - switch (tab[i].cmd) { - case 0x01: - ret = m88rs2000_demod_write(state, tab[i].reg, - tab[i].val); - break; - case 0x02: - ret = m88rs2000_tuner_write(state, tab[i].reg, - tab[i].val); - break; - case 0x10: - if (tab[i].reg > 0) - mdelay(tab[i].reg); - break; - case 0xff: - if (tab[i].reg == 0xaa && tab[i].val == 0xff) - return 0; - case 0x00: - break; - default: - return -EINVAL; - } - if (ret < 0) - return -ENODEV; - } - return 0; -} - -static int m88rs2000_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t volt) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - u8 data; - - data = m88rs2000_demod_read(state, 0xb2); - data |= 0x03; /* bit0 V/H, bit1 off/on */ - - switch (volt) { - case SEC_VOLTAGE_18: - data &= ~0x03; - break; - case SEC_VOLTAGE_13: - data &= ~0x03; - data |= 0x01; - break; - case SEC_VOLTAGE_OFF: - break; - } - - m88rs2000_demod_write(state, 0xb2, data); - - return 0; -} - -static int m88rs2000_startup(struct m88rs2000_state *state) -{ - int ret = 0; - u8 reg; - - reg = m88rs2000_tuner_read(state, 0x00); - if ((reg & 0x40) == 0) - ret = -ENODEV; - - return ret; -} - -static int m88rs2000_init(struct dvb_frontend *fe) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - int ret; - - deb_info("m88rs2000: init chip\n"); - /* Setup frontend from shutdown/cold */ - ret = m88rs2000_tab_set(state, m88rs2000_setup); - - return ret; -} - -static int m88rs2000_sleep(struct dvb_frontend *fe) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - int ret; - /* Shutdown the frondend */ - ret = m88rs2000_tab_set(state, m88rs2000_shutdown); - return ret; -} - -static int m88rs2000_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - u8 reg = m88rs2000_demod_read(state, 0x8c); - - *status = 0; - - if ((reg & 0x7) == 0x7) { - *status = FE_HAS_CARRIER | FE_HAS_SIGNAL | FE_HAS_VITERBI - | FE_HAS_LOCK; - if (state->config->set_ts_params) - state->config->set_ts_params(fe, CALL_IS_READ); - } - return 0; -} - -/* Extact code for these unknown but lmedm04 driver uses interupt callbacks */ - -static int m88rs2000_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - deb_info("m88rs2000_read_ber %d\n", *ber); - *ber = 0; - return 0; -} - -static int m88rs2000_read_signal_strength(struct dvb_frontend *fe, - u16 *strength) -{ - *strength = 0; - return 0; -} - -static int m88rs2000_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - deb_info("m88rs2000_read_snr %d\n", *snr); - *snr = 0; - return 0; -} - -static int m88rs2000_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - deb_info("m88rs2000_read_ber %d\n", *ucblocks); - *ucblocks = 0; - return 0; -} - -static int m88rs2000_tuner_gate_ctrl(struct m88rs2000_state *state, u8 offset) -{ - int ret; - ret = m88rs2000_tuner_write(state, 0x51, 0x1f - offset); - ret |= m88rs2000_tuner_write(state, 0x51, 0x1f); - ret |= m88rs2000_tuner_write(state, 0x50, offset); - ret |= m88rs2000_tuner_write(state, 0x50, 0x00); - msleep(20); - return ret; -} - -static int m88rs2000_set_tuner_rf(struct dvb_frontend *fe) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - int reg; - reg = m88rs2000_tuner_read(state, 0x3d); - reg &= 0x7f; - if (reg < 0x16) - reg = 0xa1; - else if (reg == 0x16) - reg = 0x99; - else - reg = 0xf9; - - m88rs2000_tuner_write(state, 0x60, reg); - reg = m88rs2000_tuner_gate_ctrl(state, 0x08); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - return reg; -} - -static int m88rs2000_set_tuner(struct dvb_frontend *fe, u16 *offset) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct m88rs2000_state *state = fe->demodulator_priv; - int ret; - u32 frequency = c->frequency; - s32 offset_khz; - s32 tmp; - u32 symbol_rate = (c->symbol_rate / 1000); - u32 f3db, gdiv28; - u16 value, ndiv, lpf_coeff; - u8 lpf_mxdiv, mlpf_max, mlpf_min, nlpf; - u8 lo = 0x01, div4 = 0x0; - - /* Reset Tuner */ - ret = m88rs2000_tab_set(state, tuner_reset); - - /* Calculate frequency divider */ - if (frequency < 1060000) { - lo |= 0x10; - div4 = 0x1; - ndiv = (frequency * 14 * 4) / FE_CRYSTAL_KHZ; - } else - ndiv = (frequency * 14 * 2) / FE_CRYSTAL_KHZ; - ndiv = ndiv + ndiv % 2; - ndiv = ndiv - 1024; - - ret = m88rs2000_tuner_write(state, 0x10, 0x80 | lo); - - /* Set frequency divider */ - ret |= m88rs2000_tuner_write(state, 0x01, (ndiv >> 8) & 0xf); - ret |= m88rs2000_tuner_write(state, 0x02, ndiv & 0xff); - - ret |= m88rs2000_tuner_write(state, 0x03, 0x06); - ret |= m88rs2000_tuner_gate_ctrl(state, 0x10); - if (ret < 0) - return -ENODEV; - - /* Tuner Frequency Range */ - ret = m88rs2000_tuner_write(state, 0x10, lo); - - ret |= m88rs2000_tuner_gate_ctrl(state, 0x08); - - /* Tuner RF */ - ret |= m88rs2000_set_tuner_rf(fe); - - gdiv28 = (FE_CRYSTAL_KHZ / 1000 * 1694 + 500) / 1000; - ret |= m88rs2000_tuner_write(state, 0x04, gdiv28 & 0xff); - ret |= m88rs2000_tuner_gate_ctrl(state, 0x04); - if (ret < 0) - return -ENODEV; - - value = m88rs2000_tuner_read(state, 0x26); - - f3db = (symbol_rate * 135) / 200 + 2000; - f3db += FREQ_OFFSET_LOW_SYM_RATE; - if (f3db < 7000) - f3db = 7000; - if (f3db > 40000) - f3db = 40000; - - gdiv28 = gdiv28 * 207 / (value * 2 + 151); - mlpf_max = gdiv28 * 135 / 100; - mlpf_min = gdiv28 * 78 / 100; - if (mlpf_max > 63) - mlpf_max = 63; - - lpf_coeff = 2766; - - nlpf = (f3db * gdiv28 * 2 / lpf_coeff / - (FE_CRYSTAL_KHZ / 1000) + 1) / 2; - if (nlpf > 23) - nlpf = 23; - if (nlpf < 1) - nlpf = 1; - - lpf_mxdiv = (nlpf * (FE_CRYSTAL_KHZ / 1000) - * lpf_coeff * 2 / f3db + 1) / 2; - - if (lpf_mxdiv < mlpf_min) { - nlpf++; - lpf_mxdiv = (nlpf * (FE_CRYSTAL_KHZ / 1000) - * lpf_coeff * 2 / f3db + 1) / 2; - } - - if (lpf_mxdiv > mlpf_max) - lpf_mxdiv = mlpf_max; - - ret = m88rs2000_tuner_write(state, 0x04, lpf_mxdiv); - ret |= m88rs2000_tuner_write(state, 0x06, nlpf); - - ret |= m88rs2000_tuner_gate_ctrl(state, 0x04); - - ret |= m88rs2000_tuner_gate_ctrl(state, 0x01); - - msleep(80); - /* calculate offset assuming 96000kHz*/ - offset_khz = (ndiv - ndiv % 2 + 1024) * FE_CRYSTAL_KHZ - / 14 / (div4 + 1) / 2; - - offset_khz -= frequency; - - tmp = offset_khz; - tmp *= 65536; - - tmp = (2 * tmp + 96000) / (2 * 96000); - if (tmp < 0) - tmp += 65536; - - *offset = tmp & 0xffff; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - - return (ret < 0) ? -EINVAL : 0; -} - -static int m88rs2000_set_fec(struct m88rs2000_state *state, - fe_code_rate_t fec) -{ - u16 fec_set; - switch (fec) { - /* This is not confirmed kept for reference */ -/* case FEC_1_2: - fec_set = 0x88; - break; - case FEC_2_3: - fec_set = 0x68; - break; - case FEC_3_4: - fec_set = 0x48; - break; - case FEC_5_6: - fec_set = 0x28; - break; - case FEC_7_8: - fec_set = 0x18; - break; */ - case FEC_AUTO: - default: - fec_set = 0x08; - } - m88rs2000_demod_write(state, 0x76, fec_set); - - return 0; -} - - -static fe_code_rate_t m88rs2000_get_fec(struct m88rs2000_state *state) -{ - u8 reg; - m88rs2000_demod_write(state, 0x9a, 0x30); - reg = m88rs2000_demod_read(state, 0x76); - m88rs2000_demod_write(state, 0x9a, 0xb0); - - switch (reg) { - case 0x88: - return FEC_1_2; - case 0x68: - return FEC_2_3; - case 0x48: - return FEC_3_4; - case 0x28: - return FEC_5_6; - case 0x18: - return FEC_7_8; - case 0x08: - default: - break; - } - - return FEC_AUTO; -} - -static int m88rs2000_set_frontend(struct dvb_frontend *fe) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - fe_status_t status; - int i, ret; - u16 offset = 0; - u8 reg; - - state->no_lock_count = 0; - - if (c->delivery_system != SYS_DVBS) { - deb_info("%s: unsupported delivery " - "system selected (%d)\n", - __func__, c->delivery_system); - return -EOPNOTSUPP; - } - - /* Set Tuner */ - ret = m88rs2000_set_tuner(fe, &offset); - if (ret < 0) - return -ENODEV; - - ret = m88rs2000_demod_write(state, 0x9a, 0x30); - /* Unknown usually 0xc6 sometimes 0xc1 */ - reg = m88rs2000_demod_read(state, 0x86); - ret |= m88rs2000_demod_write(state, 0x86, reg); - /* Offset lower nibble always 0 */ - ret |= m88rs2000_demod_write(state, 0x9c, (offset >> 8)); - ret |= m88rs2000_demod_write(state, 0x9d, offset & 0xf0); - - - /* Reset Demod */ - ret = m88rs2000_tab_set(state, fe_reset); - if (ret < 0) - return -ENODEV; - - /* Unknown */ - reg = m88rs2000_demod_read(state, 0x70); - ret = m88rs2000_demod_write(state, 0x70, reg); - - /* Set FEC */ - ret |= m88rs2000_set_fec(state, c->fec_inner); - ret |= m88rs2000_demod_write(state, 0x85, 0x1); - ret |= m88rs2000_demod_write(state, 0x8a, 0xbf); - ret |= m88rs2000_demod_write(state, 0x8d, 0x1e); - ret |= m88rs2000_demod_write(state, 0x90, 0xf1); - ret |= m88rs2000_demod_write(state, 0x91, 0x08); - - if (ret < 0) - return -ENODEV; - - /* Set Symbol Rate */ - ret = m88rs2000_set_symbolrate(fe, c->symbol_rate); - if (ret < 0) - return -ENODEV; - - /* Set up Demod */ - ret = m88rs2000_tab_set(state, fe_trigger); - if (ret < 0) - return -ENODEV; - - for (i = 0; i < 25; i++) { - reg = m88rs2000_demod_read(state, 0x8c); - if ((reg & 0x7) == 0x7) { - status = FE_HAS_LOCK; - break; - } - state->no_lock_count++; - if (state->no_lock_count == 15) { - reg = m88rs2000_demod_read(state, 0x70); - reg ^= 0x4; - m88rs2000_demod_write(state, 0x70, reg); - state->no_lock_count = 0; - } - if (state->no_lock_count == 20) - m88rs2000_set_tuner_rf(fe); - msleep(20); - } - - if (status & FE_HAS_LOCK) { - state->fec_inner = m88rs2000_get_fec(state); - /* Uknown suspect SNR level */ - reg = m88rs2000_demod_read(state, 0x65); - } - - state->tuner_frequency = c->frequency; - state->symbol_rate = c->symbol_rate; - return 0; -} - -static int m88rs2000_get_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct m88rs2000_state *state = fe->demodulator_priv; - c->fec_inner = state->fec_inner; - c->frequency = state->tuner_frequency; - c->symbol_rate = state->symbol_rate; - return 0; -} - -static int m88rs2000_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - - if (enable) - m88rs2000_demod_write(state, 0x81, 0x84); - else - m88rs2000_demod_write(state, 0x81, 0x81); - udelay(10); - return 0; -} - -static void m88rs2000_release(struct dvb_frontend *fe) -{ - struct m88rs2000_state *state = fe->demodulator_priv; - kfree(state); -} - -static struct dvb_frontend_ops m88rs2000_ops = { - .delsys = { SYS_DVBS }, - .info = { - .name = "M88RS2000 DVB-S", - .frequency_min = 950000, - .frequency_max = 2150000, - .frequency_stepsize = 1000, /* kHz for QPSK frontends */ - .frequency_tolerance = 5000, - .symbol_rate_min = 1000000, - .symbol_rate_max = 45000000, - .symbol_rate_tolerance = 500, /* ppm */ - .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | - FE_CAN_QPSK | - FE_CAN_FEC_AUTO - }, - - .release = m88rs2000_release, - .init = m88rs2000_init, - .sleep = m88rs2000_sleep, - .write = m88rs2000_write, - .i2c_gate_ctrl = m88rs2000_i2c_gate_ctrl, - .read_status = m88rs2000_read_status, - .read_ber = m88rs2000_read_ber, - .read_signal_strength = m88rs2000_read_signal_strength, - .read_snr = m88rs2000_read_snr, - .read_ucblocks = m88rs2000_read_ucblocks, - .diseqc_send_master_cmd = m88rs2000_send_diseqc_msg, - .diseqc_send_burst = m88rs2000_send_diseqc_burst, - .set_tone = m88rs2000_set_tone, - .set_voltage = m88rs2000_set_voltage, - - .set_frontend = m88rs2000_set_frontend, - .get_frontend = m88rs2000_get_frontend, -}; - -struct dvb_frontend *m88rs2000_attach(const struct m88rs2000_config *config, - struct i2c_adapter *i2c) -{ - struct m88rs2000_state *state = NULL; - - /* allocate memory for the internal state */ - state = kzalloc(sizeof(struct m88rs2000_state), GFP_KERNEL); - if (state == NULL) - goto error; - - /* setup the state */ - state->config = config; - state->i2c = i2c; - state->tuner_frequency = 0; - state->symbol_rate = 0; - state->fec_inner = 0; - - if (m88rs2000_startup(state) < 0) - goto error; - - /* create dvb_frontend */ - memcpy(&state->frontend.ops, &m88rs2000_ops, - sizeof(struct dvb_frontend_ops)); - state->frontend.demodulator_priv = state; - return &state->frontend; - -error: - kfree(state); - - return NULL; -} -EXPORT_SYMBOL(m88rs2000_attach); - -MODULE_DESCRIPTION("M88RS2000 DVB-S Demodulator driver"); -MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com"); -MODULE_LICENSE("GPL"); -MODULE_VERSION("1.13"); - diff --git a/drivers/media/dvb/frontends/nxt200x.c b/drivers/media/dvb/frontends/nxt200x.c deleted file mode 100644 index 49ca78d883b1..000000000000 --- a/drivers/media/dvb/frontends/nxt200x.c +++ /dev/null @@ -1,1240 +0,0 @@ -/* - * Support for NXT2002 and NXT2004 - VSB/QAM - * - * Copyright (C) 2005 Kirk Lapray - * Copyright (C) 2006 Michael Krufky - * based on nxt2002 by Taylor Jacob - * and nxt2004 by Jean-Francois Thibert - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * -*/ - -/* - * NOTES ABOUT THIS DRIVER - * - * This Linux driver supports: - * B2C2/BBTI Technisat Air2PC - ATSC (NXT2002) - * AverTVHD MCE A180 (NXT2004) - * ATI HDTV Wonder (NXT2004) - * - * This driver needs external firmware. Please use the command - * "/Documentation/dvb/get_dvb_firmware nxt2002" or - * "/Documentation/dvb/get_dvb_firmware nxt2004" to - * download/extract the appropriate firmware, and then copy it to - * /usr/lib/hotplug/firmware/ or /lib/firmware/ - * (depending on configuration of firmware hotplug). - */ -#define NXT2002_DEFAULT_FIRMWARE "dvb-fe-nxt2002.fw" -#define NXT2004_DEFAULT_FIRMWARE "dvb-fe-nxt2004.fw" -#define CRC_CCIT_MASK 0x1021 - -#include -#include -#include -#include -#include - -#include "dvb_frontend.h" -#include "nxt200x.h" - -struct nxt200x_state { - - struct i2c_adapter* i2c; - const struct nxt200x_config* config; - struct dvb_frontend frontend; - - /* demodulator private data */ - nxt_chip_type demod_chip; - u8 initialised:1; -}; - -static int debug; -#define dprintk(args...) \ - do { \ - if (debug) printk(KERN_DEBUG "nxt200x: " args); \ - } while (0) - -static int i2c_writebytes (struct nxt200x_state* state, u8 addr, u8 *buf, u8 len) -{ - int err; - struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = buf, .len = len }; - - if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { - printk (KERN_WARNING "nxt200x: %s: i2c write error (addr 0x%02x, err == %i)\n", - __func__, addr, err); - return -EREMOTEIO; - } - return 0; -} - -static int i2c_readbytes(struct nxt200x_state *state, u8 addr, u8 *buf, u8 len) -{ - int err; - struct i2c_msg msg = { .addr = addr, .flags = I2C_M_RD, .buf = buf, .len = len }; - - if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { - printk (KERN_WARNING "nxt200x: %s: i2c read error (addr 0x%02x, err == %i)\n", - __func__, addr, err); - return -EREMOTEIO; - } - return 0; -} - -static int nxt200x_writebytes (struct nxt200x_state* state, u8 reg, - const u8 *buf, u8 len) -{ - u8 buf2 [len+1]; - int err; - struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = buf2, .len = len + 1 }; - - buf2[0] = reg; - memcpy(&buf2[1], buf, len); - - if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { - printk (KERN_WARNING "nxt200x: %s: i2c write error (addr 0x%02x, err == %i)\n", - __func__, state->config->demod_address, err); - return -EREMOTEIO; - } - return 0; -} - -static int nxt200x_readbytes(struct nxt200x_state *state, u8 reg, u8 *buf, u8 len) -{ - u8 reg2 [] = { reg }; - - struct i2c_msg msg [] = { { .addr = state->config->demod_address, .flags = 0, .buf = reg2, .len = 1 }, - { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = buf, .len = len } }; - - int err; - - if ((err = i2c_transfer (state->i2c, msg, 2)) != 2) { - printk (KERN_WARNING "nxt200x: %s: i2c read error (addr 0x%02x, err == %i)\n", - __func__, state->config->demod_address, err); - return -EREMOTEIO; - } - return 0; -} - -static u16 nxt200x_crc(u16 crc, u8 c) -{ - u8 i; - u16 input = (u16) c & 0xFF; - - input<<=8; - for(i=0; i<8; i++) { - if((crc^input) & 0x8000) - crc=(crc<<1)^CRC_CCIT_MASK; - else - crc<<=1; - input<<=1; - } - return crc; -} - -static int nxt200x_writereg_multibyte (struct nxt200x_state* state, u8 reg, u8* data, u8 len) -{ - u8 attr, len2, buf; - dprintk("%s\n", __func__); - - /* set mutli register register */ - nxt200x_writebytes(state, 0x35, ®, 1); - - /* send the actual data */ - nxt200x_writebytes(state, 0x36, data, len); - - switch (state->demod_chip) { - case NXT2002: - len2 = len; - buf = 0x02; - break; - case NXT2004: - /* probably not right, but gives correct values */ - attr = 0x02; - if (reg & 0x80) { - attr = attr << 1; - if (reg & 0x04) - attr = attr >> 1; - } - /* set write bit */ - len2 = ((attr << 4) | 0x10) | len; - buf = 0x80; - break; - default: - return -EINVAL; - break; - } - - /* set multi register length */ - nxt200x_writebytes(state, 0x34, &len2, 1); - - /* toggle the multireg write bit */ - nxt200x_writebytes(state, 0x21, &buf, 1); - - nxt200x_readbytes(state, 0x21, &buf, 1); - - switch (state->demod_chip) { - case NXT2002: - if ((buf & 0x02) == 0) - return 0; - break; - case NXT2004: - if (buf == 0) - return 0; - break; - default: - return -EINVAL; - break; - } - - printk(KERN_WARNING "nxt200x: Error writing multireg register 0x%02X\n",reg); - - return 0; -} - -static int nxt200x_readreg_multibyte (struct nxt200x_state* state, u8 reg, u8* data, u8 len) -{ - int i; - u8 buf, len2, attr; - dprintk("%s\n", __func__); - - /* set mutli register register */ - nxt200x_writebytes(state, 0x35, ®, 1); - - switch (state->demod_chip) { - case NXT2002: - /* set multi register length */ - len2 = len & 0x80; - nxt200x_writebytes(state, 0x34, &len2, 1); - - /* read the actual data */ - nxt200x_readbytes(state, reg, data, len); - return 0; - break; - case NXT2004: - /* probably not right, but gives correct values */ - attr = 0x02; - if (reg & 0x80) { - attr = attr << 1; - if (reg & 0x04) - attr = attr >> 1; - } - - /* set multi register length */ - len2 = (attr << 4) | len; - nxt200x_writebytes(state, 0x34, &len2, 1); - - /* toggle the multireg bit*/ - buf = 0x80; - nxt200x_writebytes(state, 0x21, &buf, 1); - - /* read the actual data */ - for(i = 0; i < len; i++) { - nxt200x_readbytes(state, 0x36 + i, &data[i], 1); - } - return 0; - break; - default: - return -EINVAL; - break; - } -} - -static void nxt200x_microcontroller_stop (struct nxt200x_state* state) -{ - u8 buf, stopval, counter = 0; - dprintk("%s\n", __func__); - - /* set correct stop value */ - switch (state->demod_chip) { - case NXT2002: - stopval = 0x40; - break; - case NXT2004: - stopval = 0x10; - break; - default: - stopval = 0; - break; - } - - buf = 0x80; - nxt200x_writebytes(state, 0x22, &buf, 1); - - while (counter < 20) { - nxt200x_readbytes(state, 0x31, &buf, 1); - if (buf & stopval) - return; - msleep(10); - counter++; - } - - printk(KERN_WARNING "nxt200x: Timeout waiting for nxt200x to stop. This is ok after firmware upload.\n"); - return; -} - -static void nxt200x_microcontroller_start (struct nxt200x_state* state) -{ - u8 buf; - dprintk("%s\n", __func__); - - buf = 0x00; - nxt200x_writebytes(state, 0x22, &buf, 1); -} - -static void nxt2004_microcontroller_init (struct nxt200x_state* state) -{ - u8 buf[9]; - u8 counter = 0; - dprintk("%s\n", __func__); - - buf[0] = 0x00; - nxt200x_writebytes(state, 0x2b, buf, 1); - buf[0] = 0x70; - nxt200x_writebytes(state, 0x34, buf, 1); - buf[0] = 0x04; - nxt200x_writebytes(state, 0x35, buf, 1); - buf[0] = 0x01; buf[1] = 0x23; buf[2] = 0x45; buf[3] = 0x67; buf[4] = 0x89; - buf[5] = 0xAB; buf[6] = 0xCD; buf[7] = 0xEF; buf[8] = 0xC0; - nxt200x_writebytes(state, 0x36, buf, 9); - buf[0] = 0x80; - nxt200x_writebytes(state, 0x21, buf, 1); - - while (counter < 20) { - nxt200x_readbytes(state, 0x21, buf, 1); - if (buf[0] == 0) - return; - msleep(10); - counter++; - } - - printk(KERN_WARNING "nxt200x: Timeout waiting for nxt2004 to init.\n"); - - return; -} - -static int nxt200x_writetuner (struct nxt200x_state* state, u8* data) -{ - u8 buf, count = 0; - - dprintk("%s\n", __func__); - - dprintk("Tuner Bytes: %02X %02X %02X %02X\n", data[1], data[2], data[3], data[4]); - - /* if NXT2004, write directly to tuner. if NXT2002, write through NXT chip. - * direct write is required for Philips TUV1236D and ALPS TDHU2 */ - switch (state->demod_chip) { - case NXT2004: - if (i2c_writebytes(state, data[0], data+1, 4)) - printk(KERN_WARNING "nxt200x: error writing to tuner\n"); - /* wait until we have a lock */ - while (count < 20) { - i2c_readbytes(state, data[0], &buf, 1); - if (buf & 0x40) - return 0; - msleep(100); - count++; - } - printk("nxt2004: timeout waiting for tuner lock\n"); - break; - case NXT2002: - /* set the i2c transfer speed to the tuner */ - buf = 0x03; - nxt200x_writebytes(state, 0x20, &buf, 1); - - /* setup to transfer 4 bytes via i2c */ - buf = 0x04; - nxt200x_writebytes(state, 0x34, &buf, 1); - - /* write actual tuner bytes */ - nxt200x_writebytes(state, 0x36, data+1, 4); - - /* set tuner i2c address */ - buf = data[0] << 1; - nxt200x_writebytes(state, 0x35, &buf, 1); - - /* write UC Opmode to begin transfer */ - buf = 0x80; - nxt200x_writebytes(state, 0x21, &buf, 1); - - while (count < 20) { - nxt200x_readbytes(state, 0x21, &buf, 1); - if ((buf & 0x80)== 0x00) - return 0; - msleep(100); - count++; - } - printk("nxt2002: timeout error writing tuner\n"); - break; - default: - return -EINVAL; - break; - } - return 0; -} - -static void nxt200x_agc_reset(struct nxt200x_state* state) -{ - u8 buf; - dprintk("%s\n", __func__); - - switch (state->demod_chip) { - case NXT2002: - buf = 0x08; - nxt200x_writebytes(state, 0x08, &buf, 1); - buf = 0x00; - nxt200x_writebytes(state, 0x08, &buf, 1); - break; - case NXT2004: - nxt200x_readreg_multibyte(state, 0x08, &buf, 1); - buf = 0x08; - nxt200x_writereg_multibyte(state, 0x08, &buf, 1); - buf = 0x00; - nxt200x_writereg_multibyte(state, 0x08, &buf, 1); - break; - default: - break; - } - return; -} - -static int nxt2002_load_firmware (struct dvb_frontend* fe, const struct firmware *fw) -{ - - struct nxt200x_state* state = fe->demodulator_priv; - u8 buf[3], written = 0, chunkpos = 0; - u16 rambase, position, crc = 0; - - dprintk("%s\n", __func__); - dprintk("Firmware is %zu bytes\n", fw->size); - - /* Get the RAM base for this nxt2002 */ - nxt200x_readbytes(state, 0x10, buf, 1); - - if (buf[0] & 0x10) - rambase = 0x1000; - else - rambase = 0x0000; - - dprintk("rambase on this nxt2002 is %04X\n", rambase); - - /* Hold the micro in reset while loading firmware */ - buf[0] = 0x80; - nxt200x_writebytes(state, 0x2B, buf, 1); - - for (position = 0; position < fw->size; position++) { - if (written == 0) { - crc = 0; - chunkpos = 0x28; - buf[0] = ((rambase + position) >> 8); - buf[1] = (rambase + position) & 0xFF; - buf[2] = 0x81; - /* write starting address */ - nxt200x_writebytes(state, 0x29, buf, 3); - } - written++; - chunkpos++; - - if ((written % 4) == 0) - nxt200x_writebytes(state, chunkpos, &fw->data[position-3], 4); - - crc = nxt200x_crc(crc, fw->data[position]); - - if ((written == 255) || (position+1 == fw->size)) { - /* write remaining bytes of firmware */ - nxt200x_writebytes(state, chunkpos+4-(written %4), - &fw->data[position-(written %4) + 1], - written %4); - buf[0] = crc << 8; - buf[1] = crc & 0xFF; - - /* write crc */ - nxt200x_writebytes(state, 0x2C, buf, 2); - - /* do a read to stop things */ - nxt200x_readbytes(state, 0x2A, buf, 1); - - /* set transfer mode to complete */ - buf[0] = 0x80; - nxt200x_writebytes(state, 0x2B, buf, 1); - - written = 0; - } - } - - return 0; -}; - -static int nxt2004_load_firmware (struct dvb_frontend* fe, const struct firmware *fw) -{ - - struct nxt200x_state* state = fe->demodulator_priv; - u8 buf[3]; - u16 rambase, position, crc=0; - - dprintk("%s\n", __func__); - dprintk("Firmware is %zu bytes\n", fw->size); - - /* set rambase */ - rambase = 0x1000; - - /* hold the micro in reset while loading firmware */ - buf[0] = 0x80; - nxt200x_writebytes(state, 0x2B, buf,1); - - /* calculate firmware CRC */ - for (position = 0; position < fw->size; position++) { - crc = nxt200x_crc(crc, fw->data[position]); - } - - buf[0] = rambase >> 8; - buf[1] = rambase & 0xFF; - buf[2] = 0x81; - /* write starting address */ - nxt200x_writebytes(state,0x29,buf,3); - - for (position = 0; position < fw->size;) { - nxt200x_writebytes(state, 0x2C, &fw->data[position], - fw->size-position > 255 ? 255 : fw->size-position); - position += (fw->size-position > 255 ? 255 : fw->size-position); - } - buf[0] = crc >> 8; - buf[1] = crc & 0xFF; - - dprintk("firmware crc is 0x%02X 0x%02X\n", buf[0], buf[1]); - - /* write crc */ - nxt200x_writebytes(state, 0x2C, buf,2); - - /* do a read to stop things */ - nxt200x_readbytes(state, 0x2C, buf, 1); - - /* set transfer mode to complete */ - buf[0] = 0x80; - nxt200x_writebytes(state, 0x2B, buf,1); - - return 0; -}; - -static int nxt200x_setup_frontend_parameters(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *p = &fe->dtv_property_cache; - struct nxt200x_state* state = fe->demodulator_priv; - u8 buf[5]; - - /* stop the micro first */ - nxt200x_microcontroller_stop(state); - - if (state->demod_chip == NXT2004) { - /* make sure demod is set to digital */ - buf[0] = 0x04; - nxt200x_writebytes(state, 0x14, buf, 1); - buf[0] = 0x00; - nxt200x_writebytes(state, 0x17, buf, 1); - } - - /* set additional params */ - switch (p->modulation) { - case QAM_64: - case QAM_256: - /* Set punctured clock for QAM */ - /* This is just a guess since I am unable to test it */ - if (state->config->set_ts_params) - state->config->set_ts_params(fe, 1); - break; - case VSB_8: - /* Set non-punctured clock for VSB */ - if (state->config->set_ts_params) - state->config->set_ts_params(fe, 0); - break; - default: - return -EINVAL; - break; - } - - if (fe->ops.tuner_ops.calc_regs) { - /* get tuning information */ - fe->ops.tuner_ops.calc_regs(fe, buf, 5); - - /* write frequency information */ - nxt200x_writetuner(state, buf); - } - - /* reset the agc now that tuning has been completed */ - nxt200x_agc_reset(state); - - /* set target power level */ - switch (p->modulation) { - case QAM_64: - case QAM_256: - buf[0] = 0x74; - break; - case VSB_8: - buf[0] = 0x70; - break; - default: - return -EINVAL; - break; - } - nxt200x_writebytes(state, 0x42, buf, 1); - - /* configure sdm */ - switch (state->demod_chip) { - case NXT2002: - buf[0] = 0x87; - break; - case NXT2004: - buf[0] = 0x07; - break; - default: - return -EINVAL; - break; - } - nxt200x_writebytes(state, 0x57, buf, 1); - - /* write sdm1 input */ - buf[0] = 0x10; - buf[1] = 0x00; - switch (state->demod_chip) { - case NXT2002: - nxt200x_writereg_multibyte(state, 0x58, buf, 2); - break; - case NXT2004: - nxt200x_writebytes(state, 0x58, buf, 2); - break; - default: - return -EINVAL; - break; - } - - /* write sdmx input */ - switch (p->modulation) { - case QAM_64: - buf[0] = 0x68; - break; - case QAM_256: - buf[0] = 0x64; - break; - case VSB_8: - buf[0] = 0x60; - break; - default: - return -EINVAL; - break; - } - buf[1] = 0x00; - switch (state->demod_chip) { - case NXT2002: - nxt200x_writereg_multibyte(state, 0x5C, buf, 2); - break; - case NXT2004: - nxt200x_writebytes(state, 0x5C, buf, 2); - break; - default: - return -EINVAL; - break; - } - - /* write adc power lpf fc */ - buf[0] = 0x05; - nxt200x_writebytes(state, 0x43, buf, 1); - - if (state->demod_chip == NXT2004) { - /* write ??? */ - buf[0] = 0x00; - buf[1] = 0x00; - nxt200x_writebytes(state, 0x46, buf, 2); - } - - /* write accumulator2 input */ - buf[0] = 0x80; - buf[1] = 0x00; - switch (state->demod_chip) { - case NXT2002: - nxt200x_writereg_multibyte(state, 0x4B, buf, 2); - break; - case NXT2004: - nxt200x_writebytes(state, 0x4B, buf, 2); - break; - default: - return -EINVAL; - break; - } - - /* write kg1 */ - buf[0] = 0x00; - nxt200x_writebytes(state, 0x4D, buf, 1); - - /* write sdm12 lpf fc */ - buf[0] = 0x44; - nxt200x_writebytes(state, 0x55, buf, 1); - - /* write agc control reg */ - buf[0] = 0x04; - nxt200x_writebytes(state, 0x41, buf, 1); - - if (state->demod_chip == NXT2004) { - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x24; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - - /* soft reset? */ - nxt200x_readreg_multibyte(state, 0x08, buf, 1); - buf[0] = 0x10; - nxt200x_writereg_multibyte(state, 0x08, buf, 1); - nxt200x_readreg_multibyte(state, 0x08, buf, 1); - buf[0] = 0x00; - nxt200x_writereg_multibyte(state, 0x08, buf, 1); - - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x04; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x00; - nxt200x_writereg_multibyte(state, 0x81, buf, 1); - buf[0] = 0x80; buf[1] = 0x00; buf[2] = 0x00; - nxt200x_writereg_multibyte(state, 0x82, buf, 3); - nxt200x_readreg_multibyte(state, 0x88, buf, 1); - buf[0] = 0x11; - nxt200x_writereg_multibyte(state, 0x88, buf, 1); - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x44; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - } - - /* write agc ucgp0 */ - switch (p->modulation) { - case QAM_64: - buf[0] = 0x02; - break; - case QAM_256: - buf[0] = 0x03; - break; - case VSB_8: - buf[0] = 0x00; - break; - default: - return -EINVAL; - break; - } - nxt200x_writebytes(state, 0x30, buf, 1); - - /* write agc control reg */ - buf[0] = 0x00; - nxt200x_writebytes(state, 0x41, buf, 1); - - /* write accumulator2 input */ - buf[0] = 0x80; - buf[1] = 0x00; - switch (state->demod_chip) { - case NXT2002: - nxt200x_writereg_multibyte(state, 0x49, buf, 2); - nxt200x_writereg_multibyte(state, 0x4B, buf, 2); - break; - case NXT2004: - nxt200x_writebytes(state, 0x49, buf, 2); - nxt200x_writebytes(state, 0x4B, buf, 2); - break; - default: - return -EINVAL; - break; - } - - /* write agc control reg */ - buf[0] = 0x04; - nxt200x_writebytes(state, 0x41, buf, 1); - - nxt200x_microcontroller_start(state); - - if (state->demod_chip == NXT2004) { - nxt2004_microcontroller_init(state); - - /* ???? */ - buf[0] = 0xF0; - buf[1] = 0x00; - nxt200x_writebytes(state, 0x5C, buf, 2); - } - - /* adjacent channel detection should be done here, but I don't - have any stations with this need so I cannot test it */ - - return 0; -} - -static int nxt200x_read_status(struct dvb_frontend* fe, fe_status_t* status) -{ - struct nxt200x_state* state = fe->demodulator_priv; - u8 lock; - nxt200x_readbytes(state, 0x31, &lock, 1); - - *status = 0; - if (lock & 0x20) { - *status |= FE_HAS_SIGNAL; - *status |= FE_HAS_CARRIER; - *status |= FE_HAS_VITERBI; - *status |= FE_HAS_SYNC; - *status |= FE_HAS_LOCK; - } - return 0; -} - -static int nxt200x_read_ber(struct dvb_frontend* fe, u32* ber) -{ - struct nxt200x_state* state = fe->demodulator_priv; - u8 b[3]; - - nxt200x_readreg_multibyte(state, 0xE6, b, 3); - - *ber = ((b[0] << 8) + b[1]) * 8; - - return 0; -} - -static int nxt200x_read_signal_strength(struct dvb_frontend* fe, u16* strength) -{ - struct nxt200x_state* state = fe->demodulator_priv; - u8 b[2]; - u16 temp = 0; - - /* setup to read cluster variance */ - b[0] = 0x00; - nxt200x_writebytes(state, 0xA1, b, 1); - - /* get multreg val */ - nxt200x_readreg_multibyte(state, 0xA6, b, 2); - - temp = (b[0] << 8) | b[1]; - *strength = ((0x7FFF - temp) & 0x0FFF) * 16; - - return 0; -} - -static int nxt200x_read_snr(struct dvb_frontend* fe, u16* snr) -{ - - struct nxt200x_state* state = fe->demodulator_priv; - u8 b[2]; - u16 temp = 0, temp2; - u32 snrdb = 0; - - /* setup to read cluster variance */ - b[0] = 0x00; - nxt200x_writebytes(state, 0xA1, b, 1); - - /* get multreg val from 0xA6 */ - nxt200x_readreg_multibyte(state, 0xA6, b, 2); - - temp = (b[0] << 8) | b[1]; - temp2 = 0x7FFF - temp; - - /* snr will be in db */ - if (temp2 > 0x7F00) - snrdb = 1000*24 + ( 1000*(30-24) * ( temp2 - 0x7F00 ) / ( 0x7FFF - 0x7F00 ) ); - else if (temp2 > 0x7EC0) - snrdb = 1000*18 + ( 1000*(24-18) * ( temp2 - 0x7EC0 ) / ( 0x7F00 - 0x7EC0 ) ); - else if (temp2 > 0x7C00) - snrdb = 1000*12 + ( 1000*(18-12) * ( temp2 - 0x7C00 ) / ( 0x7EC0 - 0x7C00 ) ); - else - snrdb = 1000*0 + ( 1000*(12-0) * ( temp2 - 0 ) / ( 0x7C00 - 0 ) ); - - /* the value reported back from the frontend will be FFFF=32db 0000=0db */ - *snr = snrdb * (0xFFFF/32000); - - return 0; -} - -static int nxt200x_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) -{ - struct nxt200x_state* state = fe->demodulator_priv; - u8 b[3]; - - nxt200x_readreg_multibyte(state, 0xE6, b, 3); - *ucblocks = b[2]; - - return 0; -} - -static int nxt200x_sleep(struct dvb_frontend* fe) -{ - return 0; -} - -static int nxt2002_init(struct dvb_frontend* fe) -{ - struct nxt200x_state* state = fe->demodulator_priv; - const struct firmware *fw; - int ret; - u8 buf[2]; - - /* request the firmware, this will block until someone uploads it */ - printk("nxt2002: Waiting for firmware upload (%s)...\n", NXT2002_DEFAULT_FIRMWARE); - ret = request_firmware(&fw, NXT2002_DEFAULT_FIRMWARE, - state->i2c->dev.parent); - printk("nxt2002: Waiting for firmware upload(2)...\n"); - if (ret) { - printk("nxt2002: No firmware uploaded (timeout or file not found?)\n"); - return ret; - } - - ret = nxt2002_load_firmware(fe, fw); - release_firmware(fw); - if (ret) { - printk("nxt2002: Writing firmware to device failed\n"); - return ret; - } - printk("nxt2002: Firmware upload complete\n"); - - /* Put the micro into reset */ - nxt200x_microcontroller_stop(state); - - /* ensure transfer is complete */ - buf[0]=0x00; - nxt200x_writebytes(state, 0x2B, buf, 1); - - /* Put the micro into reset for real this time */ - nxt200x_microcontroller_stop(state); - - /* soft reset everything (agc,frontend,eq,fec)*/ - buf[0] = 0x0F; - nxt200x_writebytes(state, 0x08, buf, 1); - buf[0] = 0x00; - nxt200x_writebytes(state, 0x08, buf, 1); - - /* write agc sdm configure */ - buf[0] = 0xF1; - nxt200x_writebytes(state, 0x57, buf, 1); - - /* write mod output format */ - buf[0] = 0x20; - nxt200x_writebytes(state, 0x09, buf, 1); - - /* write fec mpeg mode */ - buf[0] = 0x7E; - buf[1] = 0x00; - nxt200x_writebytes(state, 0xE9, buf, 2); - - /* write mux selection */ - buf[0] = 0x00; - nxt200x_writebytes(state, 0xCC, buf, 1); - - return 0; -} - -static int nxt2004_init(struct dvb_frontend* fe) -{ - struct nxt200x_state* state = fe->demodulator_priv; - const struct firmware *fw; - int ret; - u8 buf[3]; - - /* ??? */ - buf[0]=0x00; - nxt200x_writebytes(state, 0x1E, buf, 1); - - /* request the firmware, this will block until someone uploads it */ - printk("nxt2004: Waiting for firmware upload (%s)...\n", NXT2004_DEFAULT_FIRMWARE); - ret = request_firmware(&fw, NXT2004_DEFAULT_FIRMWARE, - state->i2c->dev.parent); - printk("nxt2004: Waiting for firmware upload(2)...\n"); - if (ret) { - printk("nxt2004: No firmware uploaded (timeout or file not found?)\n"); - return ret; - } - - ret = nxt2004_load_firmware(fe, fw); - release_firmware(fw); - if (ret) { - printk("nxt2004: Writing firmware to device failed\n"); - return ret; - } - printk("nxt2004: Firmware upload complete\n"); - - /* ensure transfer is complete */ - buf[0] = 0x01; - nxt200x_writebytes(state, 0x19, buf, 1); - - nxt2004_microcontroller_init(state); - nxt200x_microcontroller_stop(state); - nxt200x_microcontroller_stop(state); - nxt2004_microcontroller_init(state); - nxt200x_microcontroller_stop(state); - - /* soft reset everything (agc,frontend,eq,fec)*/ - buf[0] = 0xFF; - nxt200x_writereg_multibyte(state, 0x08, buf, 1); - buf[0] = 0x00; - nxt200x_writereg_multibyte(state, 0x08, buf, 1); - - /* write agc sdm configure */ - buf[0] = 0xD7; - nxt200x_writebytes(state, 0x57, buf, 1); - - /* ???*/ - buf[0] = 0x07; - buf[1] = 0xfe; - nxt200x_writebytes(state, 0x35, buf, 2); - buf[0] = 0x12; - nxt200x_writebytes(state, 0x34, buf, 1); - buf[0] = 0x80; - nxt200x_writebytes(state, 0x21, buf, 1); - - /* ???*/ - buf[0] = 0x21; - nxt200x_writebytes(state, 0x0A, buf, 1); - - /* ???*/ - buf[0] = 0x01; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - - /* write fec mpeg mode */ - buf[0] = 0x7E; - buf[1] = 0x00; - nxt200x_writebytes(state, 0xE9, buf, 2); - - /* write mux selection */ - buf[0] = 0x00; - nxt200x_writebytes(state, 0xCC, buf, 1); - - /* ???*/ - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x00; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - - /* soft reset? */ - nxt200x_readreg_multibyte(state, 0x08, buf, 1); - buf[0] = 0x10; - nxt200x_writereg_multibyte(state, 0x08, buf, 1); - nxt200x_readreg_multibyte(state, 0x08, buf, 1); - buf[0] = 0x00; - nxt200x_writereg_multibyte(state, 0x08, buf, 1); - - /* ???*/ - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x01; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x70; - nxt200x_writereg_multibyte(state, 0x81, buf, 1); - buf[0] = 0x31; buf[1] = 0x5E; buf[2] = 0x66; - nxt200x_writereg_multibyte(state, 0x82, buf, 3); - - nxt200x_readreg_multibyte(state, 0x88, buf, 1); - buf[0] = 0x11; - nxt200x_writereg_multibyte(state, 0x88, buf, 1); - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x40; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - - nxt200x_readbytes(state, 0x10, buf, 1); - buf[0] = 0x10; - nxt200x_writebytes(state, 0x10, buf, 1); - nxt200x_readbytes(state, 0x0A, buf, 1); - buf[0] = 0x21; - nxt200x_writebytes(state, 0x0A, buf, 1); - - nxt2004_microcontroller_init(state); - - buf[0] = 0x21; - nxt200x_writebytes(state, 0x0A, buf, 1); - buf[0] = 0x7E; - nxt200x_writebytes(state, 0xE9, buf, 1); - buf[0] = 0x00; - nxt200x_writebytes(state, 0xEA, buf, 1); - - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x00; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x00; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - - /* soft reset? */ - nxt200x_readreg_multibyte(state, 0x08, buf, 1); - buf[0] = 0x10; - nxt200x_writereg_multibyte(state, 0x08, buf, 1); - nxt200x_readreg_multibyte(state, 0x08, buf, 1); - buf[0] = 0x00; - nxt200x_writereg_multibyte(state, 0x08, buf, 1); - - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x04; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x00; - nxt200x_writereg_multibyte(state, 0x81, buf, 1); - buf[0] = 0x80; buf[1] = 0x00; buf[2] = 0x00; - nxt200x_writereg_multibyte(state, 0x82, buf, 3); - - nxt200x_readreg_multibyte(state, 0x88, buf, 1); - buf[0] = 0x11; - nxt200x_writereg_multibyte(state, 0x88, buf, 1); - - nxt200x_readreg_multibyte(state, 0x80, buf, 1); - buf[0] = 0x44; - nxt200x_writereg_multibyte(state, 0x80, buf, 1); - - /* initialize tuner */ - nxt200x_readbytes(state, 0x10, buf, 1); - buf[0] = 0x12; - nxt200x_writebytes(state, 0x10, buf, 1); - buf[0] = 0x04; - nxt200x_writebytes(state, 0x13, buf, 1); - buf[0] = 0x00; - nxt200x_writebytes(state, 0x16, buf, 1); - buf[0] = 0x04; - nxt200x_writebytes(state, 0x14, buf, 1); - buf[0] = 0x00; - nxt200x_writebytes(state, 0x14, buf, 1); - nxt200x_writebytes(state, 0x17, buf, 1); - nxt200x_writebytes(state, 0x14, buf, 1); - nxt200x_writebytes(state, 0x17, buf, 1); - - return 0; -} - -static int nxt200x_init(struct dvb_frontend* fe) -{ - struct nxt200x_state* state = fe->demodulator_priv; - int ret = 0; - - if (!state->initialised) { - switch (state->demod_chip) { - case NXT2002: - ret = nxt2002_init(fe); - break; - case NXT2004: - ret = nxt2004_init(fe); - break; - default: - return -EINVAL; - break; - } - state->initialised = 1; - } - return ret; -} - -static int nxt200x_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) -{ - fesettings->min_delay_ms = 500; - fesettings->step_size = 0; - fesettings->max_drift = 0; - return 0; -} - -static void nxt200x_release(struct dvb_frontend* fe) -{ - struct nxt200x_state* state = fe->demodulator_priv; - kfree(state); -} - -static struct dvb_frontend_ops nxt200x_ops; - -struct dvb_frontend* nxt200x_attach(const struct nxt200x_config* config, - struct i2c_adapter* i2c) -{ - struct nxt200x_state* state = NULL; - u8 buf [] = {0,0,0,0,0}; - - /* allocate memory for the internal state */ - state = kzalloc(sizeof(struct nxt200x_state), GFP_KERNEL); - if (state == NULL) - goto error; - - /* setup the state */ - state->config = config; - state->i2c = i2c; - state->initialised = 0; - - /* read card id */ - nxt200x_readbytes(state, 0x00, buf, 5); - dprintk("NXT info: %02X %02X %02X %02X %02X\n", - buf[0], buf[1], buf[2], buf[3], buf[4]); - - /* set demod chip */ - switch (buf[0]) { - case 0x04: - state->demod_chip = NXT2002; - printk("nxt200x: NXT2002 Detected\n"); - break; - case 0x05: - state->demod_chip = NXT2004; - printk("nxt200x: NXT2004 Detected\n"); - break; - default: - goto error; - } - - /* make sure demod chip is supported */ - switch (state->demod_chip) { - case NXT2002: - if (buf[0] != 0x04) goto error; /* device id */ - if (buf[1] != 0x02) goto error; /* fab id */ - if (buf[2] != 0x11) goto error; /* month */ - if (buf[3] != 0x20) goto error; /* year msb */ - if (buf[4] != 0x00) goto error; /* year lsb */ - break; - case NXT2004: - if (buf[0] != 0x05) goto error; /* device id */ - break; - default: - goto error; - } - - /* create dvb_frontend */ - memcpy(&state->frontend.ops, &nxt200x_ops, sizeof(struct dvb_frontend_ops)); - state->frontend.demodulator_priv = state; - return &state->frontend; - -error: - kfree(state); - printk("Unknown/Unsupported NXT chip: %02X %02X %02X %02X %02X\n", - buf[0], buf[1], buf[2], buf[3], buf[4]); - return NULL; -} - -static struct dvb_frontend_ops nxt200x_ops = { - .delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B }, - .info = { - .name = "Nextwave NXT200X VSB/QAM frontend", - .frequency_min = 54000000, - .frequency_max = 860000000, - .frequency_stepsize = 166666, /* stepsize is just a guess */ - .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | - FE_CAN_8VSB | FE_CAN_QAM_64 | FE_CAN_QAM_256 - }, - - .release = nxt200x_release, - - .init = nxt200x_init, - .sleep = nxt200x_sleep, - - .set_frontend = nxt200x_setup_frontend_parameters, - .get_tune_settings = nxt200x_get_tune_settings, - - .read_status = nxt200x_read_status, - .read_ber = nxt200x_read_ber, - .read_signal_strength = nxt200x_read_signal_strength, - .read_snr = nxt200x_read_snr, - .read_ucblocks = nxt200x_read_ucblocks, -}; - -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -MODULE_DESCRIPTION("NXT200X (ATSC 8VSB & ITU-T J.83 AnnexB 64/256 QAM) Demodulator Driver"); -MODULE_AUTHOR("Kirk Lapray, Michael Krufky, Jean-Francois Thibert, and Taylor Jacob"); -MODULE_LICENSE("GPL"); - -EXPORT_SYMBOL(nxt200x_attach); - diff --git a/drivers/media/dvb/frontends/rtl2830.c b/drivers/media/dvb/frontends/rtl2830.c deleted file mode 100644 index 93612ebac519..000000000000 --- a/drivers/media/dvb/frontends/rtl2830.c +++ /dev/null @@ -1,757 +0,0 @@ -/* - * Realtek RTL2830 DVB-T demodulator driver - * - * Copyright (C) 2011 Antti Palosaari - * - * 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. - */ - - -/* - * Driver implements own I2C-adapter for tuner I2C access. That's since chip - * have unusual I2C-gate control which closes gate automatically after each - * I2C transfer. Using own I2C adapter we can workaround that. - */ - -#include "rtl2830_priv.h" - -int rtl2830_debug; -module_param_named(debug, rtl2830_debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -/* write multiple hardware registers */ -static int rtl2830_wr(struct rtl2830_priv *priv, u8 reg, u8 *val, int len) -{ - int ret; - u8 buf[1+len]; - struct i2c_msg msg[1] = { - { - .addr = priv->cfg.i2c_addr, - .flags = 0, - .len = 1+len, - .buf = buf, - } - }; - - buf[0] = reg; - memcpy(&buf[1], val, len); - - ret = i2c_transfer(priv->i2c, msg, 1); - if (ret == 1) { - ret = 0; - } else { - warn("i2c wr failed=%d reg=%02x len=%d", ret, reg, len); - ret = -EREMOTEIO; - } - return ret; -} - -/* read multiple hardware registers */ -static int rtl2830_rd(struct rtl2830_priv *priv, u8 reg, u8 *val, int len) -{ - int ret; - struct i2c_msg msg[2] = { - { - .addr = priv->cfg.i2c_addr, - .flags = 0, - .len = 1, - .buf = ®, - }, { - .addr = priv->cfg.i2c_addr, - .flags = I2C_M_RD, - .len = len, - .buf = val, - } - }; - - ret = i2c_transfer(priv->i2c, msg, 2); - if (ret == 2) { - ret = 0; - } else { - warn("i2c rd failed=%d reg=%02x len=%d", ret, reg, len); - ret = -EREMOTEIO; - } - return ret; -} - -/* write multiple registers */ -static int rtl2830_wr_regs(struct rtl2830_priv *priv, u16 reg, u8 *val, int len) -{ - int ret; - u8 reg2 = (reg >> 0) & 0xff; - u8 page = (reg >> 8) & 0xff; - - /* switch bank if needed */ - if (page != priv->page) { - ret = rtl2830_wr(priv, 0x00, &page, 1); - if (ret) - return ret; - - priv->page = page; - } - - return rtl2830_wr(priv, reg2, val, len); -} - -/* read multiple registers */ -static int rtl2830_rd_regs(struct rtl2830_priv *priv, u16 reg, u8 *val, int len) -{ - int ret; - u8 reg2 = (reg >> 0) & 0xff; - u8 page = (reg >> 8) & 0xff; - - /* switch bank if needed */ - if (page != priv->page) { - ret = rtl2830_wr(priv, 0x00, &page, 1); - if (ret) - return ret; - - priv->page = page; - } - - return rtl2830_rd(priv, reg2, val, len); -} - -#if 0 /* currently not used */ -/* write single register */ -static int rtl2830_wr_reg(struct rtl2830_priv *priv, u16 reg, u8 val) -{ - return rtl2830_wr_regs(priv, reg, &val, 1); -} -#endif - -/* read single register */ -static int rtl2830_rd_reg(struct rtl2830_priv *priv, u16 reg, u8 *val) -{ - return rtl2830_rd_regs(priv, reg, val, 1); -} - -/* write single register with mask */ -int rtl2830_wr_reg_mask(struct rtl2830_priv *priv, u16 reg, u8 val, u8 mask) -{ - int ret; - u8 tmp; - - /* no need for read if whole reg is written */ - if (mask != 0xff) { - ret = rtl2830_rd_regs(priv, reg, &tmp, 1); - if (ret) - return ret; - - val &= mask; - tmp &= ~mask; - val |= tmp; - } - - return rtl2830_wr_regs(priv, reg, &val, 1); -} - -/* read single register with mask */ -int rtl2830_rd_reg_mask(struct rtl2830_priv *priv, u16 reg, u8 *val, u8 mask) -{ - int ret, i; - u8 tmp; - - ret = rtl2830_rd_regs(priv, reg, &tmp, 1); - if (ret) - return ret; - - tmp &= mask; - - /* find position of the first bit */ - for (i = 0; i < 8; i++) { - if ((mask >> i) & 0x01) - break; - } - *val = tmp >> i; - - return 0; -} - -static int rtl2830_init(struct dvb_frontend *fe) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - int ret, i; - u64 num; - u8 buf[3], tmp; - u32 if_ctl; - struct rtl2830_reg_val_mask tab[] = { - { 0x00d, 0x01, 0x03 }, - { 0x00d, 0x10, 0x10 }, - { 0x104, 0x00, 0x1e }, - { 0x105, 0x80, 0x80 }, - { 0x110, 0x02, 0x03 }, - { 0x110, 0x08, 0x0c }, - { 0x17b, 0x00, 0x40 }, - { 0x17d, 0x05, 0x0f }, - { 0x17d, 0x50, 0xf0 }, - { 0x18c, 0x08, 0x0f }, - { 0x18d, 0x00, 0xc0 }, - { 0x188, 0x05, 0x0f }, - { 0x189, 0x00, 0xfc }, - { 0x2d5, 0x02, 0x02 }, - { 0x2f1, 0x02, 0x06 }, - { 0x2f1, 0x20, 0xf8 }, - { 0x16d, 0x00, 0x01 }, - { 0x1a6, 0x00, 0x80 }, - { 0x106, priv->cfg.vtop, 0x3f }, - { 0x107, priv->cfg.krf, 0x3f }, - { 0x112, 0x28, 0xff }, - { 0x103, priv->cfg.agc_targ_val, 0xff }, - { 0x00a, 0x02, 0x07 }, - { 0x140, 0x0c, 0x3c }, - { 0x140, 0x40, 0xc0 }, - { 0x15b, 0x05, 0x07 }, - { 0x15b, 0x28, 0x38 }, - { 0x15c, 0x05, 0x07 }, - { 0x15c, 0x28, 0x38 }, - { 0x115, priv->cfg.spec_inv, 0x01 }, - { 0x16f, 0x01, 0x07 }, - { 0x170, 0x18, 0x38 }, - { 0x172, 0x0f, 0x0f }, - { 0x173, 0x08, 0x38 }, - { 0x175, 0x01, 0x07 }, - { 0x176, 0x00, 0xc0 }, - }; - - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = rtl2830_wr_reg_mask(priv, tab[i].reg, tab[i].val, - tab[i].mask); - if (ret) - goto err; - } - - ret = rtl2830_wr_regs(priv, 0x18f, "\x28\x00", 2); - if (ret) - goto err; - - ret = rtl2830_wr_regs(priv, 0x195, - "\x04\x06\x0a\x12\x0a\x12\x1e\x28", 8); - if (ret) - goto err; - - num = priv->cfg.if_dvbt % priv->cfg.xtal; - num *= 0x400000; - num = div_u64(num, priv->cfg.xtal); - num = -num; - if_ctl = num & 0x3fffff; - dbg("%s: if_ctl=%08x", __func__, if_ctl); - - ret = rtl2830_rd_reg_mask(priv, 0x119, &tmp, 0xc0); /* b[7:6] */ - if (ret) - goto err; - - buf[0] = tmp << 6; - buf[0] = (if_ctl >> 16) & 0x3f; - buf[1] = (if_ctl >> 8) & 0xff; - buf[2] = (if_ctl >> 0) & 0xff; - - ret = rtl2830_wr_regs(priv, 0x119, buf, 3); - if (ret) - goto err; - - /* TODO: spec init */ - - /* soft reset */ - ret = rtl2830_wr_reg_mask(priv, 0x101, 0x04, 0x04); - if (ret) - goto err; - - ret = rtl2830_wr_reg_mask(priv, 0x101, 0x00, 0x04); - if (ret) - goto err; - - priv->sleeping = false; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int rtl2830_sleep(struct dvb_frontend *fe) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - priv->sleeping = true; - return 0; -} - -int rtl2830_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s) -{ - s->min_delay_ms = 500; - s->step_size = fe->ops.info.frequency_stepsize * 2; - s->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; - - return 0; -} - -static int rtl2830_set_frontend(struct dvb_frontend *fe) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i; - static u8 bw_params1[3][34] = { - { - 0x1f, 0xf0, 0x1f, 0xf0, 0x1f, 0xfa, 0x00, 0x17, 0x00, 0x41, - 0x00, 0x64, 0x00, 0x67, 0x00, 0x38, 0x1f, 0xde, 0x1f, 0x7a, - 0x1f, 0x47, 0x1f, 0x7c, 0x00, 0x30, 0x01, 0x4b, 0x02, 0x82, - 0x03, 0x73, 0x03, 0xcf, /* 6 MHz */ - }, { - 0x1f, 0xfa, 0x1f, 0xda, 0x1f, 0xc1, 0x1f, 0xb3, 0x1f, 0xca, - 0x00, 0x07, 0x00, 0x4d, 0x00, 0x6d, 0x00, 0x40, 0x1f, 0xca, - 0x1f, 0x4d, 0x1f, 0x2a, 0x1f, 0xb2, 0x00, 0xec, 0x02, 0x7e, - 0x03, 0xd0, 0x04, 0x53, /* 7 MHz */ - }, { - 0x00, 0x10, 0x00, 0x0e, 0x1f, 0xf7, 0x1f, 0xc9, 0x1f, 0xa0, - 0x1f, 0xa6, 0x1f, 0xec, 0x00, 0x4e, 0x00, 0x7d, 0x00, 0x3a, - 0x1f, 0x98, 0x1f, 0x10, 0x1f, 0x40, 0x00, 0x75, 0x02, 0x5f, - 0x04, 0x24, 0x04, 0xdb, /* 8 MHz */ - }, - }; - static u8 bw_params2[3][6] = { - {0xc3, 0x0c, 0x44, 0x33, 0x33, 0x30,}, /* 6 MHz */ - {0xb8, 0xe3, 0x93, 0x99, 0x99, 0x98,}, /* 7 MHz */ - {0xae, 0xba, 0xf3, 0x26, 0x66, 0x64,}, /* 8 MHz */ - }; - - - dbg("%s: frequency=%d bandwidth_hz=%d inversion=%d", __func__, - c->frequency, c->bandwidth_hz, c->inversion); - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - - switch (c->bandwidth_hz) { - case 6000000: - i = 0; - break; - case 7000000: - i = 1; - break; - case 8000000: - i = 2; - break; - default: - dbg("invalid bandwidth"); - return -EINVAL; - } - - ret = rtl2830_wr_reg_mask(priv, 0x008, i << 1, 0x06); - if (ret) - goto err; - - /* 1/2 split I2C write */ - ret = rtl2830_wr_regs(priv, 0x11c, &bw_params1[i][0], 17); - if (ret) - goto err; - - /* 2/2 split I2C write */ - ret = rtl2830_wr_regs(priv, 0x12d, &bw_params1[i][17], 17); - if (ret) - goto err; - - ret = rtl2830_wr_regs(priv, 0x19d, bw_params2[i], 6); - if (ret) - goto err; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int rtl2830_get_frontend(struct dvb_frontend *fe) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret; - u8 buf[3]; - - if (priv->sleeping) - return 0; - - ret = rtl2830_rd_regs(priv, 0x33c, buf, 2); - if (ret) - goto err; - - ret = rtl2830_rd_reg(priv, 0x351, &buf[2]); - if (ret) - goto err; - - dbg("%s: TPS=%02x %02x %02x", __func__, buf[0], buf[1], buf[2]); - - switch ((buf[0] >> 2) & 3) { - case 0: - c->modulation = QPSK; - break; - case 1: - c->modulation = QAM_16; - break; - case 2: - c->modulation = QAM_64; - break; - } - - switch ((buf[2] >> 2) & 1) { - case 0: - c->transmission_mode = TRANSMISSION_MODE_2K; - break; - case 1: - c->transmission_mode = TRANSMISSION_MODE_8K; - } - - switch ((buf[2] >> 0) & 3) { - case 0: - c->guard_interval = GUARD_INTERVAL_1_32; - break; - case 1: - c->guard_interval = GUARD_INTERVAL_1_16; - break; - case 2: - c->guard_interval = GUARD_INTERVAL_1_8; - break; - case 3: - c->guard_interval = GUARD_INTERVAL_1_4; - break; - } - - switch ((buf[0] >> 4) & 7) { - case 0: - c->hierarchy = HIERARCHY_NONE; - break; - case 1: - c->hierarchy = HIERARCHY_1; - break; - case 2: - c->hierarchy = HIERARCHY_2; - break; - case 3: - c->hierarchy = HIERARCHY_4; - break; - } - - switch ((buf[1] >> 3) & 7) { - case 0: - c->code_rate_HP = FEC_1_2; - break; - case 1: - c->code_rate_HP = FEC_2_3; - break; - case 2: - c->code_rate_HP = FEC_3_4; - break; - case 3: - c->code_rate_HP = FEC_5_6; - break; - case 4: - c->code_rate_HP = FEC_7_8; - break; - } - - switch ((buf[1] >> 0) & 7) { - case 0: - c->code_rate_LP = FEC_1_2; - break; - case 1: - c->code_rate_LP = FEC_2_3; - break; - case 2: - c->code_rate_LP = FEC_3_4; - break; - case 3: - c->code_rate_LP = FEC_5_6; - break; - case 4: - c->code_rate_LP = FEC_7_8; - break; - } - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int rtl2830_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - int ret; - u8 tmp; - *status = 0; - - if (priv->sleeping) - return 0; - - ret = rtl2830_rd_reg_mask(priv, 0x351, &tmp, 0x78); /* [6:3] */ - if (ret) - goto err; - - if (tmp == 11) { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - } else if (tmp == 10) { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI; - } - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int rtl2830_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - int ret, hierarchy, constellation; - u8 buf[2], tmp; - u16 tmp16; -#define CONSTELLATION_NUM 3 -#define HIERARCHY_NUM 4 - static const u32 snr_constant[CONSTELLATION_NUM][HIERARCHY_NUM] = { - { 70705899, 70705899, 70705899, 70705899 }, - { 82433173, 82433173, 87483115, 94445660 }, - { 92888734, 92888734, 95487525, 99770748 }, - }; - - if (priv->sleeping) - return 0; - - /* reports SNR in resolution of 0.1 dB */ - - ret = rtl2830_rd_reg(priv, 0x33c, &tmp); - if (ret) - goto err; - - constellation = (tmp >> 2) & 0x03; /* [3:2] */ - if (constellation > CONSTELLATION_NUM - 1) - goto err; - - hierarchy = (tmp >> 4) & 0x07; /* [6:4] */ - if (hierarchy > HIERARCHY_NUM - 1) - goto err; - - ret = rtl2830_rd_regs(priv, 0x40c, buf, 2); - if (ret) - goto err; - - tmp16 = buf[0] << 8 | buf[1]; - - if (tmp16) - *snr = (snr_constant[constellation][hierarchy] - - intlog10(tmp16)) / ((1 << 24) / 100); - else - *snr = 0; - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int rtl2830_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[2]; - - if (priv->sleeping) - return 0; - - ret = rtl2830_rd_regs(priv, 0x34e, buf, 2); - if (ret) - goto err; - - *ber = buf[0] << 8 | buf[1]; - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int rtl2830_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - *ucblocks = 0; - return 0; -} - -static int rtl2830_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[2]; - u16 if_agc_raw, if_agc; - - if (priv->sleeping) - return 0; - - ret = rtl2830_rd_regs(priv, 0x359, buf, 2); - if (ret) - goto err; - - if_agc_raw = (buf[0] << 8 | buf[1]) & 0x3fff; - - if (if_agc_raw & (1 << 9)) - if_agc = -(~(if_agc_raw - 1) & 0x1ff); - else - if_agc = if_agc_raw; - - *strength = (u8) (55 - if_agc / 182); - *strength |= *strength << 8; - - return 0; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static struct dvb_frontend_ops rtl2830_ops; - -static u32 rtl2830_tuner_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C; -} - -static int rtl2830_tuner_i2c_xfer(struct i2c_adapter *i2c_adap, - struct i2c_msg msg[], int num) -{ - struct rtl2830_priv *priv = i2c_get_adapdata(i2c_adap); - int ret; - - /* open i2c-gate */ - ret = rtl2830_wr_reg_mask(priv, 0x101, 0x08, 0x08); - if (ret) - goto err; - - ret = i2c_transfer(priv->i2c, msg, num); - if (ret < 0) - warn("tuner i2c failed=%d", ret); - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static struct i2c_algorithm rtl2830_tuner_i2c_algo = { - .master_xfer = rtl2830_tuner_i2c_xfer, - .functionality = rtl2830_tuner_i2c_func, -}; - -struct i2c_adapter *rtl2830_get_tuner_i2c_adapter(struct dvb_frontend *fe) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - return &priv->tuner_i2c_adapter; -} -EXPORT_SYMBOL(rtl2830_get_tuner_i2c_adapter); - -static void rtl2830_release(struct dvb_frontend *fe) -{ - struct rtl2830_priv *priv = fe->demodulator_priv; - - i2c_del_adapter(&priv->tuner_i2c_adapter); - kfree(priv); -} - -struct dvb_frontend *rtl2830_attach(const struct rtl2830_config *cfg, - struct i2c_adapter *i2c) -{ - struct rtl2830_priv *priv = NULL; - int ret = 0; - u8 tmp; - - /* allocate memory for the internal state */ - priv = kzalloc(sizeof(struct rtl2830_priv), GFP_KERNEL); - if (priv == NULL) - goto err; - - /* setup the priv */ - priv->i2c = i2c; - memcpy(&priv->cfg, cfg, sizeof(struct rtl2830_config)); - - /* check if the demod is there */ - ret = rtl2830_rd_reg(priv, 0x000, &tmp); - if (ret) - goto err; - - /* create dvb_frontend */ - memcpy(&priv->fe.ops, &rtl2830_ops, sizeof(struct dvb_frontend_ops)); - priv->fe.demodulator_priv = priv; - - /* create tuner i2c adapter */ - strlcpy(priv->tuner_i2c_adapter.name, "RTL2830 tuner I2C adapter", - sizeof(priv->tuner_i2c_adapter.name)); - priv->tuner_i2c_adapter.algo = &rtl2830_tuner_i2c_algo; - priv->tuner_i2c_adapter.algo_data = NULL; - i2c_set_adapdata(&priv->tuner_i2c_adapter, priv); - if (i2c_add_adapter(&priv->tuner_i2c_adapter) < 0) { - err("tuner I2C bus could not be initialized"); - goto err; - } - - priv->sleeping = true; - - return &priv->fe; -err: - dbg("%s: failed=%d", __func__, ret); - kfree(priv); - return NULL; -} -EXPORT_SYMBOL(rtl2830_attach); - -static struct dvb_frontend_ops rtl2830_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "Realtek RTL2830 (DVB-T)", - .caps = FE_CAN_FEC_1_2 | - FE_CAN_FEC_2_3 | - FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | - FE_CAN_FEC_7_8 | - FE_CAN_FEC_AUTO | - FE_CAN_QPSK | - FE_CAN_QAM_16 | - FE_CAN_QAM_64 | - FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO | - FE_CAN_RECOVER | - FE_CAN_MUTE_TS - }, - - .release = rtl2830_release, - - .init = rtl2830_init, - .sleep = rtl2830_sleep, - - .get_tune_settings = rtl2830_get_tune_settings, - - .set_frontend = rtl2830_set_frontend, - .get_frontend = rtl2830_get_frontend, - - .read_status = rtl2830_read_status, - .read_snr = rtl2830_read_snr, - .read_ber = rtl2830_read_ber, - .read_ucblocks = rtl2830_read_ucblocks, - .read_signal_strength = rtl2830_read_signal_strength, -}; - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Realtek RTL2830 DVB-T demodulator driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/rtl2830.h b/drivers/media/dvb/frontends/rtl2830.h deleted file mode 100644 index 1c6ee91749c2..000000000000 --- a/drivers/media/dvb/frontends/rtl2830.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Realtek RTL2830 DVB-T demodulator driver - * - * Copyright (C) 2011 Antti Palosaari - * - * 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. - */ - -#ifndef RTL2830_H -#define RTL2830_H - -#include - -struct rtl2830_config { - /* - * Demodulator I2C address. - */ - u8 i2c_addr; - - /* - * Xtal frequency. - * Hz - * 4000000, 16000000, 25000000, 28800000 - */ - u32 xtal; - - /* - * TS output mode. - */ - u8 ts_mode; - - /* - * Spectrum inversion. - */ - bool spec_inv; - - /* - * IFs for all used modes. - * Hz - * 4570000, 4571429, 36000000, 36125000, 36166667, 44000000 - */ - u32 if_dvbt; - - /* - */ - u8 vtop; - - /* - */ - u8 krf; - - /* - */ - u8 agc_targ_val; -}; - -#if defined(CONFIG_DVB_RTL2830) || \ - (defined(CONFIG_DVB_RTL2830_MODULE) && defined(MODULE)) -extern struct dvb_frontend *rtl2830_attach( - const struct rtl2830_config *config, - struct i2c_adapter *i2c -); - -extern struct i2c_adapter *rtl2830_get_tuner_i2c_adapter( - struct dvb_frontend *fe -); -#else -static inline struct dvb_frontend *rtl2830_attach( - const struct rtl2830_config *config, - struct i2c_adapter *i2c -) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} - -static inline struct i2c_adapter *rtl2830_get_tuner_i2c_adapter( - struct dvb_frontend *fe -) -{ - return NULL; -} -#endif - -#endif /* RTL2830_H */ diff --git a/drivers/media/dvb/frontends/rtl2830_priv.h b/drivers/media/dvb/frontends/rtl2830_priv.h deleted file mode 100644 index 9b20557ccf6c..000000000000 --- a/drivers/media/dvb/frontends/rtl2830_priv.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Realtek RTL2830 DVB-T demodulator driver - * - * Copyright (C) 2011 Antti Palosaari - * - * 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. - */ - -#ifndef RTL2830_PRIV_H -#define RTL2830_PRIV_H - -#include "dvb_frontend.h" -#include "dvb_math.h" -#include "rtl2830.h" - -#define LOG_PREFIX "rtl2830" - -#undef dbg -#define dbg(f, arg...) \ - if (rtl2830_debug) \ - printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef err -#define err(f, arg...) printk(KERN_ERR LOG_PREFIX": " f "\n" , ## arg) -#undef info -#define info(f, arg...) printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef warn -#define warn(f, arg...) printk(KERN_WARNING LOG_PREFIX": " f "\n" , ## arg) - -struct rtl2830_priv { - struct i2c_adapter *i2c; - struct dvb_frontend fe; - struct rtl2830_config cfg; - struct i2c_adapter tuner_i2c_adapter; - - bool sleeping; - - u8 page; /* active register page */ -}; - -struct rtl2830_reg_val_mask { - u16 reg; - u8 val; - u8 mask; -}; - -#endif /* RTL2830_PRIV_H */ diff --git a/drivers/media/dvb/frontends/rtl2832.c b/drivers/media/dvb/frontends/rtl2832.c deleted file mode 100644 index 28269ccaeab7..000000000000 --- a/drivers/media/dvb/frontends/rtl2832.c +++ /dev/null @@ -1,789 +0,0 @@ -/* - * Realtek RTL2832 DVB-T demodulator driver - * - * Copyright (C) 2012 Thomas Mair - * - * 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. - */ - -#include "rtl2832_priv.h" -#include - -int rtl2832_debug; -module_param_named(debug, rtl2832_debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -#define REG_MASK(b) (BIT(b + 1) - 1) - -static const struct rtl2832_reg_entry registers[] = { - [DVBT_SOFT_RST] = {0x1, 0x1, 2, 2}, - [DVBT_IIC_REPEAT] = {0x1, 0x1, 3, 3}, - [DVBT_TR_WAIT_MIN_8K] = {0x1, 0x88, 11, 2}, - [DVBT_RSD_BER_FAIL_VAL] = {0x1, 0x8f, 15, 0}, - [DVBT_EN_BK_TRK] = {0x1, 0xa6, 7, 7}, - [DVBT_AD_EN_REG] = {0x0, 0x8, 7, 7}, - [DVBT_AD_EN_REG1] = {0x0, 0x8, 6, 6}, - [DVBT_EN_BBIN] = {0x1, 0xb1, 0, 0}, - [DVBT_MGD_THD0] = {0x1, 0x95, 7, 0}, - [DVBT_MGD_THD1] = {0x1, 0x96, 7, 0}, - [DVBT_MGD_THD2] = {0x1, 0x97, 7, 0}, - [DVBT_MGD_THD3] = {0x1, 0x98, 7, 0}, - [DVBT_MGD_THD4] = {0x1, 0x99, 7, 0}, - [DVBT_MGD_THD5] = {0x1, 0x9a, 7, 0}, - [DVBT_MGD_THD6] = {0x1, 0x9b, 7, 0}, - [DVBT_MGD_THD7] = {0x1, 0x9c, 7, 0}, - [DVBT_EN_CACQ_NOTCH] = {0x1, 0x61, 4, 4}, - [DVBT_AD_AV_REF] = {0x0, 0x9, 6, 0}, - [DVBT_REG_PI] = {0x0, 0xa, 2, 0}, - [DVBT_PIP_ON] = {0x0, 0x21, 3, 3}, - [DVBT_SCALE1_B92] = {0x2, 0x92, 7, 0}, - [DVBT_SCALE1_B93] = {0x2, 0x93, 7, 0}, - [DVBT_SCALE1_BA7] = {0x2, 0xa7, 7, 0}, - [DVBT_SCALE1_BA9] = {0x2, 0xa9, 7, 0}, - [DVBT_SCALE1_BAA] = {0x2, 0xaa, 7, 0}, - [DVBT_SCALE1_BAB] = {0x2, 0xab, 7, 0}, - [DVBT_SCALE1_BAC] = {0x2, 0xac, 7, 0}, - [DVBT_SCALE1_BB0] = {0x2, 0xb0, 7, 0}, - [DVBT_SCALE1_BB1] = {0x2, 0xb1, 7, 0}, - [DVBT_KB_P1] = {0x1, 0x64, 3, 1}, - [DVBT_KB_P2] = {0x1, 0x64, 6, 4}, - [DVBT_KB_P3] = {0x1, 0x65, 2, 0}, - [DVBT_OPT_ADC_IQ] = {0x0, 0x6, 5, 4}, - [DVBT_AD_AVI] = {0x0, 0x9, 1, 0}, - [DVBT_AD_AVQ] = {0x0, 0x9, 3, 2}, - [DVBT_K1_CR_STEP12] = {0x2, 0xad, 9, 4}, - [DVBT_TRK_KS_P2] = {0x1, 0x6f, 2, 0}, - [DVBT_TRK_KS_I2] = {0x1, 0x70, 5, 3}, - [DVBT_TR_THD_SET2] = {0x1, 0x72, 3, 0}, - [DVBT_TRK_KC_P2] = {0x1, 0x73, 5, 3}, - [DVBT_TRK_KC_I2] = {0x1, 0x75, 2, 0}, - [DVBT_CR_THD_SET2] = {0x1, 0x76, 7, 6}, - [DVBT_PSET_IFFREQ] = {0x1, 0x19, 21, 0}, - [DVBT_SPEC_INV] = {0x1, 0x15, 0, 0}, - [DVBT_RSAMP_RATIO] = {0x1, 0x9f, 27, 2}, - [DVBT_CFREQ_OFF_RATIO] = {0x1, 0x9d, 23, 4}, - [DVBT_FSM_STAGE] = {0x3, 0x51, 6, 3}, - [DVBT_RX_CONSTEL] = {0x3, 0x3c, 3, 2}, - [DVBT_RX_HIER] = {0x3, 0x3c, 6, 4}, - [DVBT_RX_C_RATE_LP] = {0x3, 0x3d, 2, 0}, - [DVBT_RX_C_RATE_HP] = {0x3, 0x3d, 5, 3}, - [DVBT_GI_IDX] = {0x3, 0x51, 1, 0}, - [DVBT_FFT_MODE_IDX] = {0x3, 0x51, 2, 2}, - [DVBT_RSD_BER_EST] = {0x3, 0x4e, 15, 0}, - [DVBT_CE_EST_EVM] = {0x4, 0xc, 15, 0}, - [DVBT_RF_AGC_VAL] = {0x3, 0x5b, 13, 0}, - [DVBT_IF_AGC_VAL] = {0x3, 0x59, 13, 0}, - [DVBT_DAGC_VAL] = {0x3, 0x5, 7, 0}, - [DVBT_SFREQ_OFF] = {0x3, 0x18, 13, 0}, - [DVBT_CFREQ_OFF] = {0x3, 0x5f, 17, 0}, - [DVBT_POLAR_RF_AGC] = {0x0, 0xe, 1, 1}, - [DVBT_POLAR_IF_AGC] = {0x0, 0xe, 0, 0}, - [DVBT_AAGC_HOLD] = {0x1, 0x4, 5, 5}, - [DVBT_EN_RF_AGC] = {0x1, 0x4, 6, 6}, - [DVBT_EN_IF_AGC] = {0x1, 0x4, 7, 7}, - [DVBT_IF_AGC_MIN] = {0x1, 0x8, 7, 0}, - [DVBT_IF_AGC_MAX] = {0x1, 0x9, 7, 0}, - [DVBT_RF_AGC_MIN] = {0x1, 0xa, 7, 0}, - [DVBT_RF_AGC_MAX] = {0x1, 0xb, 7, 0}, - [DVBT_IF_AGC_MAN] = {0x1, 0xc, 6, 6}, - [DVBT_IF_AGC_MAN_VAL] = {0x1, 0xc, 13, 0}, - [DVBT_RF_AGC_MAN] = {0x1, 0xe, 6, 6}, - [DVBT_RF_AGC_MAN_VAL] = {0x1, 0xe, 13, 0}, - [DVBT_DAGC_TRG_VAL] = {0x1, 0x12, 7, 0}, - [DVBT_AGC_TARG_VAL_0] = {0x1, 0x2, 0, 0}, - [DVBT_AGC_TARG_VAL_8_1] = {0x1, 0x3, 7, 0}, - [DVBT_AAGC_LOOP_GAIN] = {0x1, 0xc7, 5, 1}, - [DVBT_LOOP_GAIN2_3_0] = {0x1, 0x4, 4, 1}, - [DVBT_LOOP_GAIN2_4] = {0x1, 0x5, 7, 7}, - [DVBT_LOOP_GAIN3] = {0x1, 0xc8, 4, 0}, - [DVBT_VTOP1] = {0x1, 0x6, 5, 0}, - [DVBT_VTOP2] = {0x1, 0xc9, 5, 0}, - [DVBT_VTOP3] = {0x1, 0xca, 5, 0}, - [DVBT_KRF1] = {0x1, 0xcb, 7, 0}, - [DVBT_KRF2] = {0x1, 0x7, 7, 0}, - [DVBT_KRF3] = {0x1, 0xcd, 7, 0}, - [DVBT_KRF4] = {0x1, 0xce, 7, 0}, - [DVBT_EN_GI_PGA] = {0x1, 0xe5, 0, 0}, - [DVBT_THD_LOCK_UP] = {0x1, 0xd9, 8, 0}, - [DVBT_THD_LOCK_DW] = {0x1, 0xdb, 8, 0}, - [DVBT_THD_UP1] = {0x1, 0xdd, 7, 0}, - [DVBT_THD_DW1] = {0x1, 0xde, 7, 0}, - [DVBT_INTER_CNT_LEN] = {0x1, 0xd8, 3, 0}, - [DVBT_GI_PGA_STATE] = {0x1, 0xe6, 3, 3}, - [DVBT_EN_AGC_PGA] = {0x1, 0xd7, 0, 0}, - [DVBT_CKOUTPAR] = {0x1, 0x7b, 5, 5}, - [DVBT_CKOUT_PWR] = {0x1, 0x7b, 6, 6}, - [DVBT_SYNC_DUR] = {0x1, 0x7b, 7, 7}, - [DVBT_ERR_DUR] = {0x1, 0x7c, 0, 0}, - [DVBT_SYNC_LVL] = {0x1, 0x7c, 1, 1}, - [DVBT_ERR_LVL] = {0x1, 0x7c, 2, 2}, - [DVBT_VAL_LVL] = {0x1, 0x7c, 3, 3}, - [DVBT_SERIAL] = {0x1, 0x7c, 4, 4}, - [DVBT_SER_LSB] = {0x1, 0x7c, 5, 5}, - [DVBT_CDIV_PH0] = {0x1, 0x7d, 3, 0}, - [DVBT_CDIV_PH1] = {0x1, 0x7d, 7, 4}, - [DVBT_MPEG_IO_OPT_2_2] = {0x0, 0x6, 7, 7}, - [DVBT_MPEG_IO_OPT_1_0] = {0x0, 0x7, 7, 6}, - [DVBT_CKOUTPAR_PIP] = {0x0, 0xb7, 4, 4}, - [DVBT_CKOUT_PWR_PIP] = {0x0, 0xb7, 3, 3}, - [DVBT_SYNC_LVL_PIP] = {0x0, 0xb7, 2, 2}, - [DVBT_ERR_LVL_PIP] = {0x0, 0xb7, 1, 1}, - [DVBT_VAL_LVL_PIP] = {0x0, 0xb7, 0, 0}, - [DVBT_CKOUTPAR_PID] = {0x0, 0xb9, 4, 4}, - [DVBT_CKOUT_PWR_PID] = {0x0, 0xb9, 3, 3}, - [DVBT_SYNC_LVL_PID] = {0x0, 0xb9, 2, 2}, - [DVBT_ERR_LVL_PID] = {0x0, 0xb9, 1, 1}, - [DVBT_VAL_LVL_PID] = {0x0, 0xb9, 0, 0}, - [DVBT_SM_PASS] = {0x1, 0x93, 11, 0}, - [DVBT_AD7_SETTING] = {0x0, 0x11, 15, 0}, - [DVBT_RSSI_R] = {0x3, 0x1, 6, 0}, - [DVBT_ACI_DET_IND] = {0x3, 0x12, 0, 0}, - [DVBT_REG_MON] = {0x0, 0xd, 1, 0}, - [DVBT_REG_MONSEL] = {0x0, 0xd, 2, 2}, - [DVBT_REG_GPE] = {0x0, 0xd, 7, 7}, - [DVBT_REG_GPO] = {0x0, 0x10, 0, 0}, - [DVBT_REG_4MSEL] = {0x0, 0x13, 0, 0}, -}; - -/* write multiple hardware registers */ -static int rtl2832_wr(struct rtl2832_priv *priv, u8 reg, u8 *val, int len) -{ - int ret; - u8 buf[1+len]; - struct i2c_msg msg[1] = { - { - .addr = priv->cfg.i2c_addr, - .flags = 0, - .len = 1+len, - .buf = buf, - } - }; - - buf[0] = reg; - memcpy(&buf[1], val, len); - - ret = i2c_transfer(priv->i2c, msg, 1); - if (ret == 1) { - ret = 0; - } else { - warn("i2c wr failed=%d reg=%02x len=%d", ret, reg, len); - ret = -EREMOTEIO; - } - return ret; -} - -/* read multiple hardware registers */ -static int rtl2832_rd(struct rtl2832_priv *priv, u8 reg, u8 *val, int len) -{ - int ret; - struct i2c_msg msg[2] = { - { - .addr = priv->cfg.i2c_addr, - .flags = 0, - .len = 1, - .buf = ®, - }, { - .addr = priv->cfg.i2c_addr, - .flags = I2C_M_RD, - .len = len, - .buf = val, - } - }; - - ret = i2c_transfer(priv->i2c, msg, 2); - if (ret == 2) { - ret = 0; - } else { - warn("i2c rd failed=%d reg=%02x len=%d", ret, reg, len); - ret = -EREMOTEIO; -} -return ret; -} - -/* write multiple registers */ -static int rtl2832_wr_regs(struct rtl2832_priv *priv, u8 reg, u8 page, u8 *val, - int len) -{ - int ret; - - - /* switch bank if needed */ - if (page != priv->page) { - ret = rtl2832_wr(priv, 0x00, &page, 1); - if (ret) - return ret; - - priv->page = page; -} - -return rtl2832_wr(priv, reg, val, len); -} - -/* read multiple registers */ -static int rtl2832_rd_regs(struct rtl2832_priv *priv, u8 reg, u8 page, u8 *val, - int len) -{ - int ret; - - /* switch bank if needed */ - if (page != priv->page) { - ret = rtl2832_wr(priv, 0x00, &page, 1); - if (ret) - return ret; - - priv->page = page; - } - - return rtl2832_rd(priv, reg, val, len); -} - -#if 0 /* currently not used */ -/* write single register */ -static int rtl2832_wr_reg(struct rtl2832_priv *priv, u8 reg, u8 page, u8 val) -{ - return rtl2832_wr_regs(priv, reg, page, &val, 1); -} -#endif - -/* read single register */ -static int rtl2832_rd_reg(struct rtl2832_priv *priv, u8 reg, u8 page, u8 *val) -{ - return rtl2832_rd_regs(priv, reg, page, val, 1); -} - -int rtl2832_rd_demod_reg(struct rtl2832_priv *priv, int reg, u32 *val) -{ - int ret; - - u8 reg_start_addr; - u8 msb, lsb; - u8 page; - u8 reading[4]; - u32 reading_tmp; - int i; - - u8 len; - u32 mask; - - reg_start_addr = registers[reg].start_address; - msb = registers[reg].msb; - lsb = registers[reg].lsb; - page = registers[reg].page; - - len = (msb >> 3) + 1; - mask = REG_MASK(msb - lsb); - - ret = rtl2832_rd_regs(priv, reg_start_addr, page, &reading[0], len); - if (ret) - goto err; - - reading_tmp = 0; - for (i = 0; i < len; i++) - reading_tmp |= reading[i] << ((len - 1 - i) * 8); - - *val = (reading_tmp >> lsb) & mask; - - return ret; - -err: - dbg("%s: failed=%d", __func__, ret); - return ret; - -} - -int rtl2832_wr_demod_reg(struct rtl2832_priv *priv, int reg, u32 val) -{ - int ret, i; - u8 len; - u8 reg_start_addr; - u8 msb, lsb; - u8 page; - u32 mask; - - - u8 reading[4]; - u8 writing[4]; - u32 reading_tmp; - u32 writing_tmp; - - - reg_start_addr = registers[reg].start_address; - msb = registers[reg].msb; - lsb = registers[reg].lsb; - page = registers[reg].page; - - len = (msb >> 3) + 1; - mask = REG_MASK(msb - lsb); - - - ret = rtl2832_rd_regs(priv, reg_start_addr, page, &reading[0], len); - if (ret) - goto err; - - reading_tmp = 0; - for (i = 0; i < len; i++) - reading_tmp |= reading[i] << ((len - 1 - i) * 8); - - writing_tmp = reading_tmp & ~(mask << lsb); - writing_tmp |= ((val & mask) << lsb); - - - for (i = 0; i < len; i++) - writing[i] = (writing_tmp >> ((len - 1 - i) * 8)) & 0xff; - - ret = rtl2832_wr_regs(priv, reg_start_addr, page, &writing[0], len); - if (ret) - goto err; - - return ret; - -err: - dbg("%s: failed=%d", __func__, ret); - return ret; - -} - - -static int rtl2832_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - int ret; - struct rtl2832_priv *priv = fe->demodulator_priv; - - dbg("%s: enable=%d", __func__, enable); - - /* gate already open or close */ - if (priv->i2c_gate_state == enable) - return 0; - - ret = rtl2832_wr_demod_reg(priv, DVBT_IIC_REPEAT, (enable ? 0x1 : 0x0)); - if (ret) - goto err; - - priv->i2c_gate_state = enable; - - return ret; -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - - - -static int rtl2832_init(struct dvb_frontend *fe) -{ - struct rtl2832_priv *priv = fe->demodulator_priv; - int i, ret; - - u8 en_bbin; - u64 pset_iffreq; - - /* initialization values for the demodulator registers */ - struct rtl2832_reg_value rtl2832_initial_regs[] = { - {DVBT_AD_EN_REG, 0x1}, - {DVBT_AD_EN_REG1, 0x1}, - {DVBT_RSD_BER_FAIL_VAL, 0x2800}, - {DVBT_MGD_THD0, 0x10}, - {DVBT_MGD_THD1, 0x20}, - {DVBT_MGD_THD2, 0x20}, - {DVBT_MGD_THD3, 0x40}, - {DVBT_MGD_THD4, 0x22}, - {DVBT_MGD_THD5, 0x32}, - {DVBT_MGD_THD6, 0x37}, - {DVBT_MGD_THD7, 0x39}, - {DVBT_EN_BK_TRK, 0x0}, - {DVBT_EN_CACQ_NOTCH, 0x0}, - {DVBT_AD_AV_REF, 0x2a}, - {DVBT_REG_PI, 0x6}, - {DVBT_PIP_ON, 0x0}, - {DVBT_CDIV_PH0, 0x8}, - {DVBT_CDIV_PH1, 0x8}, - {DVBT_SCALE1_B92, 0x4}, - {DVBT_SCALE1_B93, 0xb0}, - {DVBT_SCALE1_BA7, 0x78}, - {DVBT_SCALE1_BA9, 0x28}, - {DVBT_SCALE1_BAA, 0x59}, - {DVBT_SCALE1_BAB, 0x83}, - {DVBT_SCALE1_BAC, 0xd4}, - {DVBT_SCALE1_BB0, 0x65}, - {DVBT_SCALE1_BB1, 0x43}, - {DVBT_KB_P1, 0x1}, - {DVBT_KB_P2, 0x4}, - {DVBT_KB_P3, 0x7}, - {DVBT_K1_CR_STEP12, 0xa}, - {DVBT_REG_GPE, 0x1}, - {DVBT_SERIAL, 0x0}, - {DVBT_CDIV_PH0, 0x9}, - {DVBT_CDIV_PH1, 0x9}, - {DVBT_MPEG_IO_OPT_2_2, 0x0}, - {DVBT_MPEG_IO_OPT_1_0, 0x0}, - {DVBT_TRK_KS_P2, 0x4}, - {DVBT_TRK_KS_I2, 0x7}, - {DVBT_TR_THD_SET2, 0x6}, - {DVBT_TRK_KC_I2, 0x5}, - {DVBT_CR_THD_SET2, 0x1}, - {DVBT_SPEC_INV, 0x0}, - {DVBT_DAGC_TRG_VAL, 0x5a}, - {DVBT_AGC_TARG_VAL_0, 0x0}, - {DVBT_AGC_TARG_VAL_8_1, 0x5a}, - {DVBT_AAGC_LOOP_GAIN, 0x16}, - {DVBT_LOOP_GAIN2_3_0, 0x6}, - {DVBT_LOOP_GAIN2_4, 0x1}, - {DVBT_LOOP_GAIN3, 0x16}, - {DVBT_VTOP1, 0x35}, - {DVBT_VTOP2, 0x21}, - {DVBT_VTOP3, 0x21}, - {DVBT_KRF1, 0x0}, - {DVBT_KRF2, 0x40}, - {DVBT_KRF3, 0x10}, - {DVBT_KRF4, 0x10}, - {DVBT_IF_AGC_MIN, 0x80}, - {DVBT_IF_AGC_MAX, 0x7f}, - {DVBT_RF_AGC_MIN, 0x80}, - {DVBT_RF_AGC_MAX, 0x7f}, - {DVBT_POLAR_RF_AGC, 0x0}, - {DVBT_POLAR_IF_AGC, 0x0}, - {DVBT_AD7_SETTING, 0xe9bf}, - {DVBT_EN_GI_PGA, 0x0}, - {DVBT_THD_LOCK_UP, 0x0}, - {DVBT_THD_LOCK_DW, 0x0}, - {DVBT_THD_UP1, 0x11}, - {DVBT_THD_DW1, 0xef}, - {DVBT_INTER_CNT_LEN, 0xc}, - {DVBT_GI_PGA_STATE, 0x0}, - {DVBT_EN_AGC_PGA, 0x1}, - {DVBT_IF_AGC_MAN, 0x0}, - }; - - - dbg("%s", __func__); - - en_bbin = (priv->cfg.if_dvbt == 0 ? 0x1 : 0x0); - - /* - * PSET_IFFREQ = - floor((IfFreqHz % CrystalFreqHz) * pow(2, 22) - * / CrystalFreqHz) - */ - pset_iffreq = priv->cfg.if_dvbt % priv->cfg.xtal; - pset_iffreq *= 0x400000; - pset_iffreq = div_u64(pset_iffreq, priv->cfg.xtal); - pset_iffreq = pset_iffreq & 0x3fffff; - - - - for (i = 0; i < ARRAY_SIZE(rtl2832_initial_regs); i++) { - ret = rtl2832_wr_demod_reg(priv, rtl2832_initial_regs[i].reg, - rtl2832_initial_regs[i].value); - if (ret) - goto err; - } - - /* if frequency settings */ - ret = rtl2832_wr_demod_reg(priv, DVBT_EN_BBIN, en_bbin); - if (ret) - goto err; - - ret = rtl2832_wr_demod_reg(priv, DVBT_PSET_IFFREQ, pset_iffreq); - if (ret) - goto err; - - priv->sleeping = false; - - return ret; - -err: - dbg("%s: failed=%d", __func__, ret); - return ret; -} - -static int rtl2832_sleep(struct dvb_frontend *fe) -{ - struct rtl2832_priv *priv = fe->demodulator_priv; - - dbg("%s", __func__); - priv->sleeping = true; - return 0; -} - -int rtl2832_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s) -{ - dbg("%s", __func__); - s->min_delay_ms = 1000; - s->step_size = fe->ops.info.frequency_stepsize * 2; - s->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; - return 0; -} - -static int rtl2832_set_frontend(struct dvb_frontend *fe) -{ - struct rtl2832_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i, j; - u64 bw_mode, num, num2; - u32 resamp_ratio, cfreq_off_ratio; - - - static u8 bw_params[3][32] = { - /* 6 MHz bandwidth */ - { - 0xf5, 0xff, 0x15, 0x38, 0x5d, 0x6d, 0x52, 0x07, 0xfa, 0x2f, - 0x53, 0xf5, 0x3f, 0xca, 0x0b, 0x91, 0xea, 0x30, 0x63, 0xb2, - 0x13, 0xda, 0x0b, 0xc4, 0x18, 0x7e, 0x16, 0x66, 0x08, 0x67, - 0x19, 0xe0, - }, - - /* 7 MHz bandwidth */ - { - 0xe7, 0xcc, 0xb5, 0xba, 0xe8, 0x2f, 0x67, 0x61, 0x00, 0xaf, - 0x86, 0xf2, 0xbf, 0x59, 0x04, 0x11, 0xb6, 0x33, 0xa4, 0x30, - 0x15, 0x10, 0x0a, 0x42, 0x18, 0xf8, 0x17, 0xd9, 0x07, 0x22, - 0x19, 0x10, - }, - - /* 8 MHz bandwidth */ - { - 0x09, 0xf6, 0xd2, 0xa7, 0x9a, 0xc9, 0x27, 0x77, 0x06, 0xbf, - 0xec, 0xf4, 0x4f, 0x0b, 0xfc, 0x01, 0x63, 0x35, 0x54, 0xa7, - 0x16, 0x66, 0x08, 0xb4, 0x19, 0x6e, 0x19, 0x65, 0x05, 0xc8, - 0x19, 0xe0, - }, - }; - - - dbg("%s: frequency=%d bandwidth_hz=%d inversion=%d", __func__, - c->frequency, c->bandwidth_hz, c->inversion); - - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe); - - - switch (c->bandwidth_hz) { - case 6000000: - i = 0; - bw_mode = 48000000; - break; - case 7000000: - i = 1; - bw_mode = 56000000; - break; - case 8000000: - i = 2; - bw_mode = 64000000; - break; - default: - dbg("invalid bandwidth"); - return -EINVAL; - } - - for (j = 0; j < sizeof(bw_params[0]); j++) { - ret = rtl2832_wr_regs(priv, 0x1c+j, 1, &bw_params[i][j], 1); - if (ret) - goto err; - } - - /* calculate and set resample ratio - * RSAMP_RATIO = floor(CrystalFreqHz * 7 * pow(2, 22) - * / ConstWithBandwidthMode) - */ - num = priv->cfg.xtal * 7; - num *= 0x400000; - num = div_u64(num, bw_mode); - resamp_ratio = num & 0x3ffffff; - ret = rtl2832_wr_demod_reg(priv, DVBT_RSAMP_RATIO, resamp_ratio); - if (ret) - goto err; - - /* calculate and set cfreq off ratio - * CFREQ_OFF_RATIO = - floor(ConstWithBandwidthMode * pow(2, 20) - * / (CrystalFreqHz * 7)) - */ - num = bw_mode << 20; - num2 = priv->cfg.xtal * 7; - num = div_u64(num, num2); - num = -num; - cfreq_off_ratio = num & 0xfffff; - ret = rtl2832_wr_demod_reg(priv, DVBT_CFREQ_OFF_RATIO, cfreq_off_ratio); - if (ret) - goto err; - - - /* soft reset */ - ret = rtl2832_wr_demod_reg(priv, DVBT_SOFT_RST, 0x1); - if (ret) - goto err; - - ret = rtl2832_wr_demod_reg(priv, DVBT_SOFT_RST, 0x0); - if (ret) - goto err; - - return ret; -err: - info("%s: failed=%d", __func__, ret); - return ret; -} - -static int rtl2832_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct rtl2832_priv *priv = fe->demodulator_priv; - int ret; - u32 tmp; - *status = 0; - - - dbg("%s", __func__); - if (priv->sleeping) - return 0; - - ret = rtl2832_rd_demod_reg(priv, DVBT_FSM_STAGE, &tmp); - if (ret) - goto err; - - if (tmp == 11) { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - } - /* TODO find out if this is also true for rtl2832? */ - /*else if (tmp == 10) { - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | - FE_HAS_VITERBI; - }*/ - - return ret; -err: - info("%s: failed=%d", __func__, ret); - return ret; -} - -static int rtl2832_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - *snr = 0; - return 0; -} - -static int rtl2832_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - *ber = 0; - return 0; -} - -static int rtl2832_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - *ucblocks = 0; - return 0; -} - - -static int rtl2832_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - *strength = 0; - return 0; -} - -static struct dvb_frontend_ops rtl2832_ops; - -static void rtl2832_release(struct dvb_frontend *fe) -{ - struct rtl2832_priv *priv = fe->demodulator_priv; - - dbg("%s", __func__); - kfree(priv); -} - -struct dvb_frontend *rtl2832_attach(const struct rtl2832_config *cfg, - struct i2c_adapter *i2c) -{ - struct rtl2832_priv *priv = NULL; - int ret = 0; - u8 tmp; - - dbg("%s", __func__); - - /* allocate memory for the internal state */ - priv = kzalloc(sizeof(struct rtl2832_priv), GFP_KERNEL); - if (priv == NULL) - goto err; - - /* setup the priv */ - priv->i2c = i2c; - priv->tuner = cfg->tuner; - memcpy(&priv->cfg, cfg, sizeof(struct rtl2832_config)); - - /* check if the demod is there */ - ret = rtl2832_rd_reg(priv, 0x00, 0x0, &tmp); - if (ret) - goto err; - - /* create dvb_frontend */ - memcpy(&priv->fe.ops, &rtl2832_ops, sizeof(struct dvb_frontend_ops)); - priv->fe.demodulator_priv = priv; - - /* TODO implement sleep mode */ - priv->sleeping = true; - - return &priv->fe; -err: - dbg("%s: failed=%d", __func__, ret); - kfree(priv); - return NULL; -} -EXPORT_SYMBOL(rtl2832_attach); - -static struct dvb_frontend_ops rtl2832_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "Realtek RTL2832 (DVB-T)", - .frequency_min = 174000000, - .frequency_max = 862000000, - .frequency_stepsize = 166667, - .caps = FE_CAN_FEC_1_2 | - FE_CAN_FEC_2_3 | - FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | - FE_CAN_FEC_7_8 | - FE_CAN_FEC_AUTO | - FE_CAN_QPSK | - FE_CAN_QAM_16 | - FE_CAN_QAM_64 | - FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO | - FE_CAN_RECOVER | - FE_CAN_MUTE_TS - }, - - .release = rtl2832_release, - - .init = rtl2832_init, - .sleep = rtl2832_sleep, - - .get_tune_settings = rtl2832_get_tune_settings, - - .set_frontend = rtl2832_set_frontend, - - .read_status = rtl2832_read_status, - .read_snr = rtl2832_read_snr, - .read_ber = rtl2832_read_ber, - .read_ucblocks = rtl2832_read_ucblocks, - .read_signal_strength = rtl2832_read_signal_strength, - .i2c_gate_ctrl = rtl2832_i2c_gate_ctrl, -}; - -MODULE_AUTHOR("Thomas Mair "); -MODULE_DESCRIPTION("Realtek RTL2832 DVB-T demodulator driver"); -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.5"); diff --git a/drivers/media/dvb/frontends/rtl2832.h b/drivers/media/dvb/frontends/rtl2832.h deleted file mode 100644 index d94dc9a3fa62..000000000000 --- a/drivers/media/dvb/frontends/rtl2832.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Realtek RTL2832 DVB-T demodulator driver - * - * Copyright (C) 2012 Thomas Mair - * - * 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. - */ - -#ifndef RTL2832_H -#define RTL2832_H - -#include - -struct rtl2832_config { - /* - * Demodulator I2C address. - */ - u8 i2c_addr; - - /* - * Xtal frequency. - * Hz - * 4000000, 16000000, 25000000, 28800000 - */ - u32 xtal; - - /* - * IFs for all used modes. - * Hz - * 4570000, 4571429, 36000000, 36125000, 36166667, 44000000 - */ - u32 if_dvbt; - - /* - */ - u8 tuner; -}; - - -#if defined(CONFIG_DVB_RTL2832) || \ - (defined(CONFIG_DVB_RTL2832_MODULE) && defined(MODULE)) -extern struct dvb_frontend *rtl2832_attach( - const struct rtl2832_config *cfg, - struct i2c_adapter *i2c -); - -extern struct i2c_adapter *rtl2832_get_tuner_i2c_adapter( - struct dvb_frontend *fe -); -#else -static inline struct dvb_frontend *rtl2832_attach( - const struct rtl2832_config *config, - struct i2c_adapter *i2c -) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif - - -#endif /* RTL2832_H */ diff --git a/drivers/media/dvb/frontends/rtl2832_priv.h b/drivers/media/dvb/frontends/rtl2832_priv.h deleted file mode 100644 index 0ce9502da8ba..000000000000 --- a/drivers/media/dvb/frontends/rtl2832_priv.h +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Realtek RTL2832 DVB-T demodulator driver - * - * Copyright (C) 2012 Thomas Mair - * - * 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. - */ - -#ifndef RTL2832_PRIV_H -#define RTL2832_PRIV_H - -#include "dvb_frontend.h" -#include "rtl2832.h" - -#define LOG_PREFIX "rtl2832" - -#undef dbg -#define dbg(f, arg...) \ -do { \ - if (rtl2832_debug) \ - printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg); \ -} while (0) -#undef err -#define err(f, arg...) printk(KERN_ERR LOG_PREFIX": " f "\n" , ## arg) -#undef info -#define info(f, arg...) printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) -#undef warn -#define warn(f, arg...) printk(KERN_WARNING LOG_PREFIX": " f "\n" , ## arg) - -struct rtl2832_priv { - struct i2c_adapter *i2c; - struct dvb_frontend fe; - struct rtl2832_config cfg; - - bool i2c_gate_state; - bool sleeping; - - u8 tuner; - u8 page; /* active register page */ -}; - -struct rtl2832_reg_entry { - u8 page; - u8 start_address; - u8 msb; - u8 lsb; -}; - -struct rtl2832_reg_value { - int reg; - u32 value; -}; - - -/* Demod register bit names */ -enum DVBT_REG_BIT_NAME { - DVBT_SOFT_RST, - DVBT_IIC_REPEAT, - DVBT_TR_WAIT_MIN_8K, - DVBT_RSD_BER_FAIL_VAL, - DVBT_EN_BK_TRK, - DVBT_REG_PI, - DVBT_REG_PFREQ_1_0, - DVBT_PD_DA8, - DVBT_LOCK_TH, - DVBT_BER_PASS_SCAL, - DVBT_CE_FFSM_BYPASS, - DVBT_ALPHAIIR_N, - DVBT_ALPHAIIR_DIF, - DVBT_EN_TRK_SPAN, - DVBT_LOCK_TH_LEN, - DVBT_CCI_THRE, - DVBT_CCI_MON_SCAL, - DVBT_CCI_M0, - DVBT_CCI_M1, - DVBT_CCI_M2, - DVBT_CCI_M3, - DVBT_SPEC_INIT_0, - DVBT_SPEC_INIT_1, - DVBT_SPEC_INIT_2, - DVBT_AD_EN_REG, - DVBT_AD_EN_REG1, - DVBT_EN_BBIN, - DVBT_MGD_THD0, - DVBT_MGD_THD1, - DVBT_MGD_THD2, - DVBT_MGD_THD3, - DVBT_MGD_THD4, - DVBT_MGD_THD5, - DVBT_MGD_THD6, - DVBT_MGD_THD7, - DVBT_EN_CACQ_NOTCH, - DVBT_AD_AV_REF, - DVBT_PIP_ON, - DVBT_SCALE1_B92, - DVBT_SCALE1_B93, - DVBT_SCALE1_BA7, - DVBT_SCALE1_BA9, - DVBT_SCALE1_BAA, - DVBT_SCALE1_BAB, - DVBT_SCALE1_BAC, - DVBT_SCALE1_BB0, - DVBT_SCALE1_BB1, - DVBT_KB_P1, - DVBT_KB_P2, - DVBT_KB_P3, - DVBT_OPT_ADC_IQ, - DVBT_AD_AVI, - DVBT_AD_AVQ, - DVBT_K1_CR_STEP12, - DVBT_TRK_KS_P2, - DVBT_TRK_KS_I2, - DVBT_TR_THD_SET2, - DVBT_TRK_KC_P2, - DVBT_TRK_KC_I2, - DVBT_CR_THD_SET2, - DVBT_PSET_IFFREQ, - DVBT_SPEC_INV, - DVBT_BW_INDEX, - DVBT_RSAMP_RATIO, - DVBT_CFREQ_OFF_RATIO, - DVBT_FSM_STAGE, - DVBT_RX_CONSTEL, - DVBT_RX_HIER, - DVBT_RX_C_RATE_LP, - DVBT_RX_C_RATE_HP, - DVBT_GI_IDX, - DVBT_FFT_MODE_IDX, - DVBT_RSD_BER_EST, - DVBT_CE_EST_EVM, - DVBT_RF_AGC_VAL, - DVBT_IF_AGC_VAL, - DVBT_DAGC_VAL, - DVBT_SFREQ_OFF, - DVBT_CFREQ_OFF, - DVBT_POLAR_RF_AGC, - DVBT_POLAR_IF_AGC, - DVBT_AAGC_HOLD, - DVBT_EN_RF_AGC, - DVBT_EN_IF_AGC, - DVBT_IF_AGC_MIN, - DVBT_IF_AGC_MAX, - DVBT_RF_AGC_MIN, - DVBT_RF_AGC_MAX, - DVBT_IF_AGC_MAN, - DVBT_IF_AGC_MAN_VAL, - DVBT_RF_AGC_MAN, - DVBT_RF_AGC_MAN_VAL, - DVBT_DAGC_TRG_VAL, - DVBT_AGC_TARG_VAL, - DVBT_LOOP_GAIN_3_0, - DVBT_LOOP_GAIN_4, - DVBT_VTOP, - DVBT_KRF, - DVBT_AGC_TARG_VAL_0, - DVBT_AGC_TARG_VAL_8_1, - DVBT_AAGC_LOOP_GAIN, - DVBT_LOOP_GAIN2_3_0, - DVBT_LOOP_GAIN2_4, - DVBT_LOOP_GAIN3, - DVBT_VTOP1, - DVBT_VTOP2, - DVBT_VTOP3, - DVBT_KRF1, - DVBT_KRF2, - DVBT_KRF3, - DVBT_KRF4, - DVBT_EN_GI_PGA, - DVBT_THD_LOCK_UP, - DVBT_THD_LOCK_DW, - DVBT_THD_UP1, - DVBT_THD_DW1, - DVBT_INTER_CNT_LEN, - DVBT_GI_PGA_STATE, - DVBT_EN_AGC_PGA, - DVBT_CKOUTPAR, - DVBT_CKOUT_PWR, - DVBT_SYNC_DUR, - DVBT_ERR_DUR, - DVBT_SYNC_LVL, - DVBT_ERR_LVL, - DVBT_VAL_LVL, - DVBT_SERIAL, - DVBT_SER_LSB, - DVBT_CDIV_PH0, - DVBT_CDIV_PH1, - DVBT_MPEG_IO_OPT_2_2, - DVBT_MPEG_IO_OPT_1_0, - DVBT_CKOUTPAR_PIP, - DVBT_CKOUT_PWR_PIP, - DVBT_SYNC_LVL_PIP, - DVBT_ERR_LVL_PIP, - DVBT_VAL_LVL_PIP, - DVBT_CKOUTPAR_PID, - DVBT_CKOUT_PWR_PID, - DVBT_SYNC_LVL_PID, - DVBT_ERR_LVL_PID, - DVBT_VAL_LVL_PID, - DVBT_SM_PASS, - DVBT_UPDATE_REG_2, - DVBT_BTHD_P3, - DVBT_BTHD_D3, - DVBT_FUNC4_REG0, - DVBT_FUNC4_REG1, - DVBT_FUNC4_REG2, - DVBT_FUNC4_REG3, - DVBT_FUNC4_REG4, - DVBT_FUNC4_REG5, - DVBT_FUNC4_REG6, - DVBT_FUNC4_REG7, - DVBT_FUNC4_REG8, - DVBT_FUNC4_REG9, - DVBT_FUNC4_REG10, - DVBT_FUNC5_REG0, - DVBT_FUNC5_REG1, - DVBT_FUNC5_REG2, - DVBT_FUNC5_REG3, - DVBT_FUNC5_REG4, - DVBT_FUNC5_REG5, - DVBT_FUNC5_REG6, - DVBT_FUNC5_REG7, - DVBT_FUNC5_REG8, - DVBT_FUNC5_REG9, - DVBT_FUNC5_REG10, - DVBT_FUNC5_REG11, - DVBT_FUNC5_REG12, - DVBT_FUNC5_REG13, - DVBT_FUNC5_REG14, - DVBT_FUNC5_REG15, - DVBT_FUNC5_REG16, - DVBT_FUNC5_REG17, - DVBT_FUNC5_REG18, - DVBT_AD7_SETTING, - DVBT_RSSI_R, - DVBT_ACI_DET_IND, - DVBT_REG_MON, - DVBT_REG_MONSEL, - DVBT_REG_GPE, - DVBT_REG_GPO, - DVBT_REG_4MSEL, - DVBT_TEST_REG_1, - DVBT_TEST_REG_2, - DVBT_TEST_REG_3, - DVBT_TEST_REG_4, - DVBT_REG_BIT_NAME_ITEM_TERMINATOR, -}; - -#endif /* RTL2832_PRIV_H */ diff --git a/drivers/media/dvb/frontends/stb0899_drv.c b/drivers/media/dvb/frontends/stb0899_drv.c deleted file mode 100644 index 5d7f8a9b451b..000000000000 --- a/drivers/media/dvb/frontends/stb0899_drv.c +++ /dev/null @@ -1,1651 +0,0 @@ -/* - STB0899 Multistandard Frontend driver - Copyright (C) Manu Abraham (abraham.manu@gmail.com) - - Copyright (C) ST Microelectronics - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include -#include -#include -#include -#include - -#include -#include "dvb_frontend.h" - -#include "stb0899_drv.h" -#include "stb0899_priv.h" -#include "stb0899_reg.h" - -static unsigned int verbose = 0;//1; -module_param(verbose, int, 0644); - -/* C/N in dB/10, NIRM/NIRL */ -static const struct stb0899_tab stb0899_cn_tab[] = { - { 200, 2600 }, - { 190, 2700 }, - { 180, 2860 }, - { 170, 3020 }, - { 160, 3210 }, - { 150, 3440 }, - { 140, 3710 }, - { 130, 4010 }, - { 120, 4360 }, - { 110, 4740 }, - { 100, 5190 }, - { 90, 5670 }, - { 80, 6200 }, - { 70, 6770 }, - { 60, 7360 }, - { 50, 7970 }, - { 40, 8250 }, - { 30, 9000 }, - { 20, 9450 }, - { 15, 9600 }, -}; - -/* DVB-S AGCIQ_VALUE vs. signal level in dBm/10. - * As measured, connected to a modulator. - * -8.0 to -50.0 dBm directly connected, - * -52.0 to -74.8 with extra attenuation. - * Cut-off to AGCIQ_VALUE = 0x80 below -74.8dBm. - * Crude linear extrapolation below -84.8dBm and above -8.0dBm. - */ -static const struct stb0899_tab stb0899_dvbsrf_tab[] = { - { -750, -128 }, - { -748, -94 }, - { -745, -92 }, - { -735, -90 }, - { -720, -87 }, - { -670, -77 }, - { -640, -70 }, - { -610, -62 }, - { -600, -60 }, - { -590, -56 }, - { -560, -41 }, - { -540, -25 }, - { -530, -17 }, - { -520, -11 }, - { -500, 1 }, - { -490, 6 }, - { -480, 10 }, - { -440, 22 }, - { -420, 27 }, - { -400, 31 }, - { -380, 34 }, - { -340, 40 }, - { -320, 43 }, - { -280, 48 }, - { -250, 52 }, - { -230, 55 }, - { -180, 61 }, - { -140, 66 }, - { -90, 73 }, - { -80, 74 }, - { 500, 127 } -}; - -/* DVB-S2 IF_AGC_GAIN vs. signal level in dBm/10. - * As measured, connected to a modulator. - * -8.0 to -50.1 dBm directly connected, - * -53.0 to -76.6 with extra attenuation. - * Cut-off to IF_AGC_GAIN = 0x3fff below -76.6dBm. - * Crude linear extrapolation below -76.6dBm and above -8.0dBm. - */ -static const struct stb0899_tab stb0899_dvbs2rf_tab[] = { - { 700, 0 }, - { -80, 3217 }, - { -150, 3893 }, - { -190, 4217 }, - { -240, 4621 }, - { -280, 4945 }, - { -320, 5273 }, - { -350, 5545 }, - { -370, 5741 }, - { -410, 6147 }, - { -450, 6671 }, - { -490, 7413 }, - { -501, 7665 }, - { -530, 8767 }, - { -560, 10219 }, - { -580, 10939 }, - { -590, 11518 }, - { -600, 11723 }, - { -650, 12659 }, - { -690, 13219 }, - { -730, 13645 }, - { -750, 13909 }, - { -766, 14153 }, - { -950, 16383 } -}; - -/* DVB-S2 Es/N0 quant in dB/100 vs read value * 100*/ -static struct stb0899_tab stb0899_quant_tab[] = { - { 0, 0 }, - { 0, 100 }, - { 600, 200 }, - { 950, 299 }, - { 1200, 398 }, - { 1400, 501 }, - { 1560, 603 }, - { 1690, 700 }, - { 1810, 804 }, - { 1910, 902 }, - { 2000, 1000 }, - { 2080, 1096 }, - { 2160, 1202 }, - { 2230, 1303 }, - { 2350, 1496 }, - { 2410, 1603 }, - { 2460, 1698 }, - { 2510, 1799 }, - { 2600, 1995 }, - { 2650, 2113 }, - { 2690, 2213 }, - { 2720, 2291 }, - { 2760, 2399 }, - { 2800, 2512 }, - { 2860, 2692 }, - { 2930, 2917 }, - { 2960, 3020 }, - { 3010, 3199 }, - { 3040, 3311 }, - { 3060, 3388 }, - { 3120, 3631 }, - { 3190, 3936 }, - { 3400, 5012 }, - { 3610, 6383 }, - { 3800, 7943 }, - { 4210, 12735 }, - { 4500, 17783 }, - { 4690, 22131 }, - { 4810, 25410 } -}; - -/* DVB-S2 Es/N0 estimate in dB/100 vs read value */ -static struct stb0899_tab stb0899_est_tab[] = { - { 0, 0 }, - { 0, 1 }, - { 301, 2 }, - { 1204, 16 }, - { 1806, 64 }, - { 2408, 256 }, - { 2709, 512 }, - { 3010, 1023 }, - { 3311, 2046 }, - { 3612, 4093 }, - { 3823, 6653 }, - { 3913, 8185 }, - { 4010, 10233 }, - { 4107, 12794 }, - { 4214, 16368 }, - { 4266, 18450 }, - { 4311, 20464 }, - { 4353, 22542 }, - { 4391, 24604 }, - { 4425, 26607 }, - { 4457, 28642 }, - { 4487, 30690 }, - { 4515, 32734 }, - { 4612, 40926 }, - { 4692, 49204 }, - { 4816, 65464 }, - { 4913, 81846 }, - { 4993, 98401 }, - { 5060, 114815 }, - { 5118, 131220 }, - { 5200, 158489 }, - { 5300, 199526 }, - { 5400, 251189 }, - { 5500, 316228 }, - { 5600, 398107 }, - { 5720, 524807 }, - { 5721, 526017 }, -}; - -static int _stb0899_read_reg(struct stb0899_state *state, unsigned int reg) -{ - int ret; - - u8 b0[] = { reg >> 8, reg & 0xff }; - u8 buf; - - struct i2c_msg msg[] = { - { - .addr = state->config->demod_address, - .flags = 0, - .buf = b0, - .len = 2 - },{ - .addr = state->config->demod_address, - .flags = I2C_M_RD, - .buf = &buf, - .len = 1 - } - }; - - ret = i2c_transfer(state->i2c, msg, 2); - if (ret != 2) { - if (ret != -ERESTARTSYS) - dprintk(state->verbose, FE_ERROR, 1, - "Read error, Reg=[0x%02x], Status=%d", - reg, ret); - - return ret < 0 ? ret : -EREMOTEIO; - } - if (unlikely(*state->verbose >= FE_DEBUGREG)) - dprintk(state->verbose, FE_ERROR, 1, "Reg=[0x%02x], data=%02x", - reg, buf); - - return (unsigned int)buf; -} - -int stb0899_read_reg(struct stb0899_state *state, unsigned int reg) -{ - int result; - - result = _stb0899_read_reg(state, reg); - /* - * Bug ID 9: - * access to 0xf2xx/0xf6xx - * must be followed by read from 0xf2ff/0xf6ff. - */ - if ((reg != 0xf2ff) && (reg != 0xf6ff) && - (((reg & 0xff00) == 0xf200) || ((reg & 0xff00) == 0xf600))) - _stb0899_read_reg(state, (reg | 0x00ff)); - - return result; -} - -u32 _stb0899_read_s2reg(struct stb0899_state *state, - u32 stb0899_i2cdev, - u32 stb0899_base_addr, - u16 stb0899_reg_offset) -{ - int status; - u32 data; - u8 buf[7] = { 0 }; - u16 tmpaddr; - - u8 buf_0[] = { - GETBYTE(stb0899_i2cdev, BYTE1), /* 0xf3 S2 Base Address (MSB) */ - GETBYTE(stb0899_i2cdev, BYTE0), /* 0xfc S2 Base Address (LSB) */ - GETBYTE(stb0899_base_addr, BYTE0), /* 0x00 Base Address (LSB) */ - GETBYTE(stb0899_base_addr, BYTE1), /* 0x04 Base Address (LSB) */ - GETBYTE(stb0899_base_addr, BYTE2), /* 0x00 Base Address (MSB) */ - GETBYTE(stb0899_base_addr, BYTE3), /* 0x00 Base Address (MSB) */ - }; - u8 buf_1[] = { - 0x00, /* 0xf3 Reg Offset */ - 0x00, /* 0x44 Reg Offset */ - }; - - struct i2c_msg msg_0 = { - .addr = state->config->demod_address, - .flags = 0, - .buf = buf_0, - .len = 6 - }; - - struct i2c_msg msg_1 = { - .addr = state->config->demod_address, - .flags = 0, - .buf = buf_1, - .len = 2 - }; - - struct i2c_msg msg_r = { - .addr = state->config->demod_address, - .flags = I2C_M_RD, - .buf = buf, - .len = 4 - }; - - tmpaddr = stb0899_reg_offset & 0xff00; - if (!(stb0899_reg_offset & 0x8)) - tmpaddr = stb0899_reg_offset | 0x20; - - buf_1[0] = GETBYTE(tmpaddr, BYTE1); - buf_1[1] = GETBYTE(tmpaddr, BYTE0); - - status = i2c_transfer(state->i2c, &msg_0, 1); - if (status < 1) { - if (status != -ERESTARTSYS) - printk(KERN_ERR "%s ERR(1), Device=[0x%04x], Base address=[0x%08x], Offset=[0x%04x], Status=%d\n", - __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, status); - - goto err; - } - - /* Dummy */ - status = i2c_transfer(state->i2c, &msg_1, 1); - if (status < 1) - goto err; - - status = i2c_transfer(state->i2c, &msg_r, 1); - if (status < 1) - goto err; - - buf_1[0] = GETBYTE(stb0899_reg_offset, BYTE1); - buf_1[1] = GETBYTE(stb0899_reg_offset, BYTE0); - - /* Actual */ - status = i2c_transfer(state->i2c, &msg_1, 1); - if (status < 1) { - if (status != -ERESTARTSYS) - printk(KERN_ERR "%s ERR(2), Device=[0x%04x], Base address=[0x%08x], Offset=[0x%04x], Status=%d\n", - __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, status); - goto err; - } - - status = i2c_transfer(state->i2c, &msg_r, 1); - if (status < 1) { - if (status != -ERESTARTSYS) - printk(KERN_ERR "%s ERR(3), Device=[0x%04x], Base address=[0x%08x], Offset=[0x%04x], Status=%d\n", - __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, status); - return status < 0 ? status : -EREMOTEIO; - } - - data = MAKEWORD32(buf[3], buf[2], buf[1], buf[0]); - if (unlikely(*state->verbose >= FE_DEBUGREG)) - printk(KERN_DEBUG "%s Device=[0x%04x], Base address=[0x%08x], Offset=[0x%04x], Data=[0x%08x]\n", - __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, data); - - return data; - -err: - return status < 0 ? status : -EREMOTEIO; -} - -int stb0899_write_s2reg(struct stb0899_state *state, - u32 stb0899_i2cdev, - u32 stb0899_base_addr, - u16 stb0899_reg_offset, - u32 stb0899_data) -{ - int status; - - /* Base Address Setup */ - u8 buf_0[] = { - GETBYTE(stb0899_i2cdev, BYTE1), /* 0xf3 S2 Base Address (MSB) */ - GETBYTE(stb0899_i2cdev, BYTE0), /* 0xfc S2 Base Address (LSB) */ - GETBYTE(stb0899_base_addr, BYTE0), /* 0x00 Base Address (LSB) */ - GETBYTE(stb0899_base_addr, BYTE1), /* 0x04 Base Address (LSB) */ - GETBYTE(stb0899_base_addr, BYTE2), /* 0x00 Base Address (MSB) */ - GETBYTE(stb0899_base_addr, BYTE3), /* 0x00 Base Address (MSB) */ - }; - u8 buf_1[] = { - 0x00, /* 0xf3 Reg Offset */ - 0x00, /* 0x44 Reg Offset */ - 0x00, /* data */ - 0x00, /* data */ - 0x00, /* data */ - 0x00, /* data */ - }; - - struct i2c_msg msg_0 = { - .addr = state->config->demod_address, - .flags = 0, - .buf = buf_0, - .len = 6 - }; - - struct i2c_msg msg_1 = { - .addr = state->config->demod_address, - .flags = 0, - .buf = buf_1, - .len = 6 - }; - - buf_1[0] = GETBYTE(stb0899_reg_offset, BYTE1); - buf_1[1] = GETBYTE(stb0899_reg_offset, BYTE0); - buf_1[2] = GETBYTE(stb0899_data, BYTE0); - buf_1[3] = GETBYTE(stb0899_data, BYTE1); - buf_1[4] = GETBYTE(stb0899_data, BYTE2); - buf_1[5] = GETBYTE(stb0899_data, BYTE3); - - if (unlikely(*state->verbose >= FE_DEBUGREG)) - printk(KERN_DEBUG "%s Device=[0x%04x], Base Address=[0x%08x], Offset=[0x%04x], Data=[0x%08x]\n", - __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, stb0899_data); - - status = i2c_transfer(state->i2c, &msg_0, 1); - if (unlikely(status < 1)) { - if (status != -ERESTARTSYS) - printk(KERN_ERR "%s ERR (1), Device=[0x%04x], Base Address=[0x%08x], Offset=[0x%04x], Data=[0x%08x], status=%d\n", - __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, stb0899_data, status); - goto err; - } - status = i2c_transfer(state->i2c, &msg_1, 1); - if (unlikely(status < 1)) { - if (status != -ERESTARTSYS) - printk(KERN_ERR "%s ERR (2), Device=[0x%04x], Base Address=[0x%08x], Offset=[0x%04x], Data=[0x%08x], status=%d\n", - __func__, stb0899_i2cdev, stb0899_base_addr, stb0899_reg_offset, stb0899_data, status); - - return status < 0 ? status : -EREMOTEIO; - } - - return 0; - -err: - return status < 0 ? status : -EREMOTEIO; -} - -int stb0899_read_regs(struct stb0899_state *state, unsigned int reg, u8 *buf, u32 count) -{ - int status; - - u8 b0[] = { reg >> 8, reg & 0xff }; - - struct i2c_msg msg[] = { - { - .addr = state->config->demod_address, - .flags = 0, - .buf = b0, - .len = 2 - },{ - .addr = state->config->demod_address, - .flags = I2C_M_RD, - .buf = buf, - .len = count - } - }; - - status = i2c_transfer(state->i2c, msg, 2); - if (status != 2) { - if (status != -ERESTARTSYS) - printk(KERN_ERR "%s Read error, Reg=[0x%04x], Count=%u, Status=%d\n", - __func__, reg, count, status); - goto err; - } - /* - * Bug ID 9: - * access to 0xf2xx/0xf6xx - * must be followed by read from 0xf2ff/0xf6ff. - */ - if ((reg != 0xf2ff) && (reg != 0xf6ff) && - (((reg & 0xff00) == 0xf200) || ((reg & 0xff00) == 0xf600))) - _stb0899_read_reg(state, (reg | 0x00ff)); - - if (unlikely(*state->verbose >= FE_DEBUGREG)) { - int i; - - printk(KERN_DEBUG "%s [0x%04x]:", __func__, reg); - for (i = 0; i < count; i++) { - printk(" %02x", buf[i]); - } - printk("\n"); - } - - return 0; -err: - return status < 0 ? status : -EREMOTEIO; -} - -int stb0899_write_regs(struct stb0899_state *state, unsigned int reg, u8 *data, u32 count) -{ - int ret; - u8 buf[2 + count]; - struct i2c_msg i2c_msg = { - .addr = state->config->demod_address, - .flags = 0, - .buf = buf, - .len = 2 + count - }; - - buf[0] = reg >> 8; - buf[1] = reg & 0xff; - memcpy(&buf[2], data, count); - - if (unlikely(*state->verbose >= FE_DEBUGREG)) { - int i; - - printk(KERN_DEBUG "%s [0x%04x]:", __func__, reg); - for (i = 0; i < count; i++) - printk(" %02x", data[i]); - printk("\n"); - } - ret = i2c_transfer(state->i2c, &i2c_msg, 1); - - /* - * Bug ID 9: - * access to 0xf2xx/0xf6xx - * must be followed by read from 0xf2ff/0xf6ff. - */ - if ((((reg & 0xff00) == 0xf200) || ((reg & 0xff00) == 0xf600))) - stb0899_read_reg(state, (reg | 0x00ff)); - - if (ret != 1) { - if (ret != -ERESTARTSYS) - dprintk(state->verbose, FE_ERROR, 1, "Reg=[0x%04x], Data=[0x%02x ...], Count=%u, Status=%d", - reg, data[0], count, ret); - return ret < 0 ? ret : -EREMOTEIO; - } - - return 0; -} - -int stb0899_write_reg(struct stb0899_state *state, unsigned int reg, u8 data) -{ - return stb0899_write_regs(state, reg, &data, 1); -} - -/* - * stb0899_get_mclk - * Get STB0899 master clock frequency - * ExtClk: external clock frequency (Hz) - */ -static u32 stb0899_get_mclk(struct stb0899_state *state) -{ - u32 mclk = 0, div = 0; - - div = stb0899_read_reg(state, STB0899_NCOARSE); - mclk = (div + 1) * state->config->xtal_freq / 6; - dprintk(state->verbose, FE_DEBUG, 1, "div=%d, mclk=%d", div, mclk); - - return mclk; -} - -/* - * stb0899_set_mclk - * Set STB0899 master Clock frequency - * Mclk: demodulator master clock - * ExtClk: external clock frequency (Hz) - */ -static void stb0899_set_mclk(struct stb0899_state *state, u32 Mclk) -{ - struct stb0899_internal *internal = &state->internal; - u8 mdiv = 0; - - dprintk(state->verbose, FE_DEBUG, 1, "state->config=%p", state->config); - mdiv = ((6 * Mclk) / state->config->xtal_freq) - 1; - dprintk(state->verbose, FE_DEBUG, 1, "mdiv=%d", mdiv); - - stb0899_write_reg(state, STB0899_NCOARSE, mdiv); - internal->master_clk = stb0899_get_mclk(state); - - dprintk(state->verbose, FE_DEBUG, 1, "MasterCLOCK=%d", internal->master_clk); -} - -static int stb0899_postproc(struct stb0899_state *state, u8 ctl, int enable) -{ - struct stb0899_config *config = state->config; - const struct stb0899_postproc *postproc = config->postproc; - - /* post process event */ - if (postproc) { - if (enable) { - if (postproc[ctl].level == STB0899_GPIOPULLUP) - stb0899_write_reg(state, postproc[ctl].gpio, 0x02); - else - stb0899_write_reg(state, postproc[ctl].gpio, 0x82); - } else { - if (postproc[ctl].level == STB0899_GPIOPULLUP) - stb0899_write_reg(state, postproc[ctl].gpio, 0x82); - else - stb0899_write_reg(state, postproc[ctl].gpio, 0x02); - } - } - return 0; -} - -static void stb0899_release(struct dvb_frontend *fe) -{ - struct stb0899_state *state = fe->demodulator_priv; - - dprintk(state->verbose, FE_DEBUG, 1, "Release Frontend"); - /* post process event */ - stb0899_postproc(state, STB0899_POSTPROC_GPIO_POWER, 0); - kfree(state); -} - -/* - * stb0899_get_alpha - * return: rolloff - */ -static int stb0899_get_alpha(struct stb0899_state *state) -{ - u8 mode_coeff; - - mode_coeff = stb0899_read_reg(state, STB0899_DEMOD); - - if (STB0899_GETFIELD(MODECOEFF, mode_coeff) == 1) - return 20; - else - return 35; -} - -/* - * stb0899_init_calc - */ -static void stb0899_init_calc(struct stb0899_state *state) -{ - struct stb0899_internal *internal = &state->internal; - int master_clk; - u8 agc[2]; - u32 reg; - - /* Read registers (in burst mode) */ - stb0899_read_regs(state, STB0899_AGC1REF, agc, 2); /* AGC1R and AGC2O */ - - /* Initial calculations */ - master_clk = stb0899_get_mclk(state); - internal->t_agc1 = 0; - internal->t_agc2 = 0; - internal->master_clk = master_clk; - internal->mclk = master_clk / 65536L; - internal->rolloff = stb0899_get_alpha(state); - - /* DVBS2 Initial calculations */ - /* Set AGC value to the middle */ - internal->agc_gain = 8154; - reg = STB0899_READ_S2REG(STB0899_S2DEMOD, IF_AGC_CNTRL); - STB0899_SETFIELD_VAL(IF_GAIN_INIT, reg, internal->agc_gain); - stb0899_write_s2reg(state, STB0899_S2DEMOD, STB0899_BASE_IF_AGC_CNTRL, STB0899_OFF0_IF_AGC_CNTRL, reg); - - reg = STB0899_READ_S2REG(STB0899_S2DEMOD, RRC_ALPHA); - internal->rrc_alpha = STB0899_GETFIELD(RRC_ALPHA, reg); - - internal->center_freq = 0; - internal->av_frame_coarse = 10; - internal->av_frame_fine = 20; - internal->step_size = 2; -/* - if ((pParams->SpectralInv == FE_IQ_NORMAL) || (pParams->SpectralInv == FE_IQ_AUTO)) - pParams->IQLocked = 0; - else - pParams->IQLocked = 1; -*/ -} - -static int stb0899_wait_diseqc_fifo_empty(struct stb0899_state *state, int timeout) -{ - u8 reg = 0; - unsigned long start = jiffies; - - while (1) { - reg = stb0899_read_reg(state, STB0899_DISSTATUS); - if (!STB0899_GETFIELD(FIFOFULL, reg)) - break; - if ((jiffies - start) > timeout) { - dprintk(state->verbose, FE_ERROR, 1, "timed out !!"); - return -ETIMEDOUT; - } - } - - return 0; -} - -static int stb0899_send_diseqc_msg(struct dvb_frontend *fe, struct dvb_diseqc_master_cmd *cmd) -{ - struct stb0899_state *state = fe->demodulator_priv; - u8 reg, i; - - if (cmd->msg_len > 8) - return -EINVAL; - - /* enable FIFO precharge */ - reg = stb0899_read_reg(state, STB0899_DISCNTRL1); - STB0899_SETFIELD_VAL(DISPRECHARGE, reg, 1); - stb0899_write_reg(state, STB0899_DISCNTRL1, reg); - for (i = 0; i < cmd->msg_len; i++) { - /* wait for FIFO empty */ - if (stb0899_wait_diseqc_fifo_empty(state, 100) < 0) - return -ETIMEDOUT; - - stb0899_write_reg(state, STB0899_DISFIFO, cmd->msg[i]); - } - reg = stb0899_read_reg(state, STB0899_DISCNTRL1); - STB0899_SETFIELD_VAL(DISPRECHARGE, reg, 0); - stb0899_write_reg(state, STB0899_DISCNTRL1, reg); - msleep(100); - return 0; -} - -static int stb0899_wait_diseqc_rxidle(struct stb0899_state *state, int timeout) -{ - u8 reg = 0; - unsigned long start = jiffies; - - while (!STB0899_GETFIELD(RXEND, reg)) { - reg = stb0899_read_reg(state, STB0899_DISRX_ST0); - if (jiffies - start > timeout) { - dprintk(state->verbose, FE_ERROR, 1, "timed out!!"); - return -ETIMEDOUT; - } - msleep(10); - } - - return 0; -} - -static int stb0899_recv_slave_reply(struct dvb_frontend *fe, struct dvb_diseqc_slave_reply *reply) -{ - struct stb0899_state *state = fe->demodulator_priv; - u8 reg, length = 0, i; - int result; - - if (stb0899_wait_diseqc_rxidle(state, 100) < 0) - return -ETIMEDOUT; - - reg = stb0899_read_reg(state, STB0899_DISRX_ST0); - if (STB0899_GETFIELD(RXEND, reg)) { - - reg = stb0899_read_reg(state, STB0899_DISRX_ST1); - length = STB0899_GETFIELD(FIFOBYTENBR, reg); - - if (length > sizeof (reply->msg)) { - result = -EOVERFLOW; - goto exit; - } - reply->msg_len = length; - - /* extract data */ - for (i = 0; i < length; i++) - reply->msg[i] = stb0899_read_reg(state, STB0899_DISFIFO); - } - - return 0; -exit: - - return result; -} - -static int stb0899_wait_diseqc_txidle(struct stb0899_state *state, int timeout) -{ - u8 reg = 0; - unsigned long start = jiffies; - - while (!STB0899_GETFIELD(TXIDLE, reg)) { - reg = stb0899_read_reg(state, STB0899_DISSTATUS); - if (jiffies - start > timeout) { - dprintk(state->verbose, FE_ERROR, 1, "timed out!!"); - return -ETIMEDOUT; - } - msleep(10); - } - return 0; -} - -static int stb0899_send_diseqc_burst(struct dvb_frontend *fe, fe_sec_mini_cmd_t burst) -{ - struct stb0899_state *state = fe->demodulator_priv; - u8 reg, old_state; - - /* wait for diseqc idle */ - if (stb0899_wait_diseqc_txidle(state, 100) < 0) - return -ETIMEDOUT; - - reg = stb0899_read_reg(state, STB0899_DISCNTRL1); - old_state = reg; - /* set to burst mode */ - STB0899_SETFIELD_VAL(DISEQCMODE, reg, 0x03); - STB0899_SETFIELD_VAL(DISPRECHARGE, reg, 0x01); - stb0899_write_reg(state, STB0899_DISCNTRL1, reg); - switch (burst) { - case SEC_MINI_A: - /* unmodulated */ - stb0899_write_reg(state, STB0899_DISFIFO, 0x00); - break; - case SEC_MINI_B: - /* modulated */ - stb0899_write_reg(state, STB0899_DISFIFO, 0xff); - break; - } - reg = stb0899_read_reg(state, STB0899_DISCNTRL1); - STB0899_SETFIELD_VAL(DISPRECHARGE, reg, 0x00); - stb0899_write_reg(state, STB0899_DISCNTRL1, reg); - /* wait for diseqc idle */ - if (stb0899_wait_diseqc_txidle(state, 100) < 0) - return -ETIMEDOUT; - - /* restore state */ - stb0899_write_reg(state, STB0899_DISCNTRL1, old_state); - - return 0; -} - -static int stb0899_diseqc_init(struct stb0899_state *state) -{ -/* - struct dvb_diseqc_slave_reply rx_data; -*/ - u8 f22_tx, reg; - - u32 mclk, tx_freq = 22000;/* count = 0, i; */ - reg = stb0899_read_reg(state, STB0899_DISCNTRL2); - STB0899_SETFIELD_VAL(ONECHIP_TRX, reg, 0); - stb0899_write_reg(state, STB0899_DISCNTRL2, reg); - - /* disable Tx spy */ - reg = stb0899_read_reg(state, STB0899_DISCNTRL1); - STB0899_SETFIELD_VAL(DISEQCRESET, reg, 1); - stb0899_write_reg(state, STB0899_DISCNTRL1, reg); - - reg = stb0899_read_reg(state, STB0899_DISCNTRL1); - STB0899_SETFIELD_VAL(DISEQCRESET, reg, 0); - stb0899_write_reg(state, STB0899_DISCNTRL1, reg); - - mclk = stb0899_get_mclk(state); - f22_tx = mclk / (tx_freq * 32); - stb0899_write_reg(state, STB0899_DISF22, f22_tx); /* DiSEqC Tx freq */ - state->rx_freq = 20000; - - return 0; -} - -static int stb0899_sleep(struct dvb_frontend *fe) -{ - struct stb0899_state *state = fe->demodulator_priv; -/* - u8 reg; -*/ - dprintk(state->verbose, FE_DEBUG, 1, "Going to Sleep .. (Really tired .. :-))"); - /* post process event */ - stb0899_postproc(state, STB0899_POSTPROC_GPIO_POWER, 0); - - return 0; -} - -static int stb0899_wakeup(struct dvb_frontend *fe) -{ - int rc; - struct stb0899_state *state = fe->demodulator_priv; - - if ((rc = stb0899_write_reg(state, STB0899_SYNTCTRL, STB0899_SELOSCI))) - return rc; - /* Activate all clocks; DVB-S2 registers are inaccessible otherwise. */ - if ((rc = stb0899_write_reg(state, STB0899_STOPCLK1, 0x00))) - return rc; - if ((rc = stb0899_write_reg(state, STB0899_STOPCLK2, 0x00))) - return rc; - - /* post process event */ - stb0899_postproc(state, STB0899_POSTPROC_GPIO_POWER, 1); - - return 0; -} - -static int stb0899_init(struct dvb_frontend *fe) -{ - int i; - struct stb0899_state *state = fe->demodulator_priv; - struct stb0899_config *config = state->config; - - dprintk(state->verbose, FE_DEBUG, 1, "Initializing STB0899 ... "); - - /* init device */ - dprintk(state->verbose, FE_DEBUG, 1, "init device"); - for (i = 0; config->init_dev[i].address != 0xffff; i++) - stb0899_write_reg(state, config->init_dev[i].address, config->init_dev[i].data); - - dprintk(state->verbose, FE_DEBUG, 1, "init S2 demod"); - /* init S2 demod */ - for (i = 0; config->init_s2_demod[i].offset != 0xffff; i++) - stb0899_write_s2reg(state, STB0899_S2DEMOD, - config->init_s2_demod[i].base_address, - config->init_s2_demod[i].offset, - config->init_s2_demod[i].data); - - dprintk(state->verbose, FE_DEBUG, 1, "init S1 demod"); - /* init S1 demod */ - for (i = 0; config->init_s1_demod[i].address != 0xffff; i++) - stb0899_write_reg(state, config->init_s1_demod[i].address, config->init_s1_demod[i].data); - - dprintk(state->verbose, FE_DEBUG, 1, "init S2 FEC"); - /* init S2 fec */ - for (i = 0; config->init_s2_fec[i].offset != 0xffff; i++) - stb0899_write_s2reg(state, STB0899_S2FEC, - config->init_s2_fec[i].base_address, - config->init_s2_fec[i].offset, - config->init_s2_fec[i].data); - - dprintk(state->verbose, FE_DEBUG, 1, "init TST"); - /* init test */ - for (i = 0; config->init_tst[i].address != 0xffff; i++) - stb0899_write_reg(state, config->init_tst[i].address, config->init_tst[i].data); - - stb0899_init_calc(state); - stb0899_diseqc_init(state); - - return 0; -} - -static int stb0899_table_lookup(const struct stb0899_tab *tab, int max, int val) -{ - int res = 0; - int min = 0, med; - - if (val < tab[min].read) - res = tab[min].real; - else if (val >= tab[max].read) - res = tab[max].real; - else { - while ((max - min) > 1) { - med = (max + min) / 2; - if (val >= tab[min].read && val < tab[med].read) - max = med; - else - min = med; - } - res = ((val - tab[min].read) * - (tab[max].real - tab[min].real) / - (tab[max].read - tab[min].read)) + - tab[min].real; - } - - return res; -} - -static int stb0899_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - struct stb0899_state *state = fe->demodulator_priv; - struct stb0899_internal *internal = &state->internal; - - int val; - u32 reg; - *strength = 0; - switch (state->delsys) { - case SYS_DVBS: - case SYS_DSS: - if (internal->lock) { - reg = stb0899_read_reg(state, STB0899_VSTATUS); - if (STB0899_GETFIELD(VSTATUS_LOCKEDVIT, reg)) { - - reg = stb0899_read_reg(state, STB0899_AGCIQIN); - val = (s32)(s8)STB0899_GETFIELD(AGCIQVALUE, reg); - - *strength = stb0899_table_lookup(stb0899_dvbsrf_tab, ARRAY_SIZE(stb0899_dvbsrf_tab) - 1, val); - *strength += 750; - dprintk(state->verbose, FE_DEBUG, 1, "AGCIQVALUE = 0x%02x, C = %d * 0.1 dBm", - val & 0xff, *strength); - } - } - break; - case SYS_DVBS2: - if (internal->lock) { - reg = STB0899_READ_S2REG(STB0899_S2DEMOD, IF_AGC_GAIN); - val = STB0899_GETFIELD(IF_AGC_GAIN, reg); - - *strength = stb0899_table_lookup(stb0899_dvbs2rf_tab, ARRAY_SIZE(stb0899_dvbs2rf_tab) - 1, val); - *strength += 950; - dprintk(state->verbose, FE_DEBUG, 1, "IF_AGC_GAIN = 0x%04x, C = %d * 0.1 dBm", - val & 0x3fff, *strength); - } - break; - default: - dprintk(state->verbose, FE_DEBUG, 1, "Unsupported delivery system"); - return -EINVAL; - } - - return 0; -} - -static int stb0899_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct stb0899_state *state = fe->demodulator_priv; - struct stb0899_internal *internal = &state->internal; - - unsigned int val, quant, quantn = -1, est, estn = -1; - u8 buf[2]; - u32 reg; - - *snr = 0; - reg = stb0899_read_reg(state, STB0899_VSTATUS); - switch (state->delsys) { - case SYS_DVBS: - case SYS_DSS: - if (internal->lock) { - if (STB0899_GETFIELD(VSTATUS_LOCKEDVIT, reg)) { - - stb0899_read_regs(state, STB0899_NIRM, buf, 2); - val = MAKEWORD16(buf[0], buf[1]); - - *snr = stb0899_table_lookup(stb0899_cn_tab, ARRAY_SIZE(stb0899_cn_tab) - 1, val); - dprintk(state->verbose, FE_DEBUG, 1, "NIR = 0x%02x%02x = %u, C/N = %d * 0.1 dBm\n", - buf[0], buf[1], val, *snr); - } - } - break; - case SYS_DVBS2: - if (internal->lock) { - reg = STB0899_READ_S2REG(STB0899_S2DEMOD, UWP_CNTRL1); - quant = STB0899_GETFIELD(UWP_ESN0_QUANT, reg); - reg = STB0899_READ_S2REG(STB0899_S2DEMOD, UWP_STAT2); - est = STB0899_GETFIELD(ESN0_EST, reg); - if (est == 1) - val = 301; /* C/N = 30.1 dB */ - else if (est == 2) - val = 270; /* C/N = 27.0 dB */ - else { - /* quantn = 100 * log(quant^2) */ - quantn = stb0899_table_lookup(stb0899_quant_tab, ARRAY_SIZE(stb0899_quant_tab) - 1, quant * 100); - /* estn = 100 * log(est) */ - estn = stb0899_table_lookup(stb0899_est_tab, ARRAY_SIZE(stb0899_est_tab) - 1, est); - /* snr(dBm/10) = -10*(log(est)-log(quant^2)) => snr(dBm/10) = (100*log(quant^2)-100*log(est))/10 */ - val = (quantn - estn) / 10; - } - *snr = val; - dprintk(state->verbose, FE_DEBUG, 1, "Es/N0 quant = %d (%d) estimate = %u (%d), C/N = %d * 0.1 dBm", - quant, quantn, est, estn, val); - } - break; - default: - dprintk(state->verbose, FE_DEBUG, 1, "Unsupported delivery system"); - return -EINVAL; - } - - return 0; -} - -static int stb0899_read_status(struct dvb_frontend *fe, enum fe_status *status) -{ - struct stb0899_state *state = fe->demodulator_priv; - struct stb0899_internal *internal = &state->internal; - u8 reg; - *status = 0; - - switch (state->delsys) { - case SYS_DVBS: - case SYS_DSS: - dprintk(state->verbose, FE_DEBUG, 1, "Delivery system DVB-S/DSS"); - if (internal->lock) { - reg = stb0899_read_reg(state, STB0899_VSTATUS); - if (STB0899_GETFIELD(VSTATUS_LOCKEDVIT, reg)) { - dprintk(state->verbose, FE_DEBUG, 1, "--------> FE_HAS_CARRIER | FE_HAS_LOCK"); - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK; - - reg = stb0899_read_reg(state, STB0899_PLPARM); - if (STB0899_GETFIELD(VITCURPUN, reg)) { - dprintk(state->verbose, FE_DEBUG, 1, "--------> FE_HAS_VITERBI | FE_HAS_SYNC"); - *status |= FE_HAS_VITERBI | FE_HAS_SYNC; - /* post process event */ - stb0899_postproc(state, STB0899_POSTPROC_GPIO_LOCK, 1); - } - } - } - break; - case SYS_DVBS2: - dprintk(state->verbose, FE_DEBUG, 1, "Delivery system DVB-S2"); - if (internal->lock) { - reg = STB0899_READ_S2REG(STB0899_S2DEMOD, DMD_STAT2); - if (STB0899_GETFIELD(UWP_LOCK, reg) && STB0899_GETFIELD(CSM_LOCK, reg)) { - *status |= FE_HAS_CARRIER; - dprintk(state->verbose, FE_DEBUG, 1, - "UWP & CSM Lock ! ---> DVB-S2 FE_HAS_CARRIER"); - - reg = stb0899_read_reg(state, STB0899_CFGPDELSTATUS1); - if (STB0899_GETFIELD(CFGPDELSTATUS_LOCK, reg)) { - *status |= FE_HAS_LOCK; - dprintk(state->verbose, FE_DEBUG, 1, - "Packet Delineator Locked ! -----> DVB-S2 FE_HAS_LOCK"); - - } - if (STB0899_GETFIELD(CONTINUOUS_STREAM, reg)) { - *status |= FE_HAS_VITERBI; - dprintk(state->verbose, FE_DEBUG, 1, - "Packet Delineator found VITERBI ! -----> DVB-S2 FE_HAS_VITERBI"); - } - if (STB0899_GETFIELD(ACCEPTED_STREAM, reg)) { - *status |= FE_HAS_SYNC; - dprintk(state->verbose, FE_DEBUG, 1, - "Packet Delineator found SYNC ! -----> DVB-S2 FE_HAS_SYNC"); - /* post process event */ - stb0899_postproc(state, STB0899_POSTPROC_GPIO_LOCK, 1); - } - } - } - break; - default: - dprintk(state->verbose, FE_DEBUG, 1, "Unsupported delivery system"); - return -EINVAL; - } - return 0; -} - -/* - * stb0899_get_error - * viterbi error for DVB-S/DSS - * packet error for DVB-S2 - * Bit Error Rate or Packet Error Rate * 10 ^ 7 - */ -static int stb0899_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct stb0899_state *state = fe->demodulator_priv; - struct stb0899_internal *internal = &state->internal; - - u8 lsb, msb; - - *ber = 0; - - switch (state->delsys) { - case SYS_DVBS: - case SYS_DSS: - if (internal->lock) { - lsb = stb0899_read_reg(state, STB0899_ECNT1L); - msb = stb0899_read_reg(state, STB0899_ECNT1M); - *ber = MAKEWORD16(msb, lsb); - /* Viterbi Check */ - if (STB0899_GETFIELD(VSTATUS_PRFVIT, internal->v_status)) { - /* Error Rate */ - *ber *= 9766; - /* ber = ber * 10 ^ 7 */ - *ber /= (-1 + (1 << (2 * STB0899_GETFIELD(NOE, internal->err_ctrl)))); - *ber /= 8; - } - } - break; - case SYS_DVBS2: - if (internal->lock) { - lsb = stb0899_read_reg(state, STB0899_ECNT1L); - msb = stb0899_read_reg(state, STB0899_ECNT1M); - *ber = MAKEWORD16(msb, lsb); - /* ber = ber * 10 ^ 7 */ - *ber *= 10000000; - *ber /= (-1 + (1 << (4 + 2 * STB0899_GETFIELD(NOE, internal->err_ctrl)))); - } - break; - default: - dprintk(state->verbose, FE_DEBUG, 1, "Unsupported delivery system"); - return -EINVAL; - } - - return 0; -} - -static int stb0899_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) -{ - struct stb0899_state *state = fe->demodulator_priv; - - switch (voltage) { - case SEC_VOLTAGE_13: - stb0899_write_reg(state, STB0899_GPIO00CFG, 0x82); - stb0899_write_reg(state, STB0899_GPIO01CFG, 0x02); - stb0899_write_reg(state, STB0899_GPIO02CFG, 0x00); - break; - case SEC_VOLTAGE_18: - stb0899_write_reg(state, STB0899_GPIO00CFG, 0x02); - stb0899_write_reg(state, STB0899_GPIO01CFG, 0x02); - stb0899_write_reg(state, STB0899_GPIO02CFG, 0x82); - break; - case SEC_VOLTAGE_OFF: - stb0899_write_reg(state, STB0899_GPIO00CFG, 0x82); - stb0899_write_reg(state, STB0899_GPIO01CFG, 0x82); - stb0899_write_reg(state, STB0899_GPIO02CFG, 0x82); - break; - default: - return -EINVAL; - } - - return 0; -} - -static int stb0899_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) -{ - struct stb0899_state *state = fe->demodulator_priv; - struct stb0899_internal *internal = &state->internal; - - u8 div, reg; - - /* wait for diseqc idle */ - if (stb0899_wait_diseqc_txidle(state, 100) < 0) - return -ETIMEDOUT; - - switch (tone) { - case SEC_TONE_ON: - div = (internal->master_clk / 100) / 5632; - div = (div + 5) / 10; - stb0899_write_reg(state, STB0899_DISEQCOCFG, 0x66); - reg = stb0899_read_reg(state, STB0899_ACRPRESC); - STB0899_SETFIELD_VAL(ACRPRESC, reg, 0x03); - stb0899_write_reg(state, STB0899_ACRPRESC, reg); - stb0899_write_reg(state, STB0899_ACRDIV1, div); - break; - case SEC_TONE_OFF: - stb0899_write_reg(state, STB0899_DISEQCOCFG, 0x20); - break; - default: - return -EINVAL; - } - return 0; -} - -int stb0899_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - int i2c_stat; - struct stb0899_state *state = fe->demodulator_priv; - - i2c_stat = stb0899_read_reg(state, STB0899_I2CRPT); - if (i2c_stat < 0) - goto err; - - if (enable) { - dprintk(state->verbose, FE_DEBUG, 1, "Enabling I2C Repeater ..."); - i2c_stat |= STB0899_I2CTON; - if (stb0899_write_reg(state, STB0899_I2CRPT, i2c_stat) < 0) - goto err; - } else { - dprintk(state->verbose, FE_DEBUG, 1, "Disabling I2C Repeater ..."); - i2c_stat &= ~STB0899_I2CTON; - if (stb0899_write_reg(state, STB0899_I2CRPT, i2c_stat) < 0) - goto err; - } - return 0; -err: - dprintk(state->verbose, FE_ERROR, 1, "I2C Repeater control failed"); - return -EREMOTEIO; -} - - -static inline void CONVERT32(u32 x, char *str) -{ - *str++ = (x >> 24) & 0xff; - *str++ = (x >> 16) & 0xff; - *str++ = (x >> 8) & 0xff; - *str++ = (x >> 0) & 0xff; - *str = '\0'; -} - -int stb0899_get_dev_id(struct stb0899_state *state) -{ - u8 chip_id, release; - u16 id; - u32 demod_ver = 0, fec_ver = 0; - char demod_str[5] = { 0 }; - char fec_str[5] = { 0 }; - - id = stb0899_read_reg(state, STB0899_DEV_ID); - dprintk(state->verbose, FE_DEBUG, 1, "ID reg=[0x%02x]", id); - chip_id = STB0899_GETFIELD(CHIP_ID, id); - release = STB0899_GETFIELD(CHIP_REL, id); - - dprintk(state->verbose, FE_ERROR, 1, "Device ID=[%d], Release=[%d]", - chip_id, release); - - CONVERT32(STB0899_READ_S2REG(STB0899_S2DEMOD, DMD_CORE_ID), (char *)&demod_str); - - demod_ver = STB0899_READ_S2REG(STB0899_S2DEMOD, DMD_VERSION_ID); - dprintk(state->verbose, FE_ERROR, 1, "Demodulator Core ID=[%s], Version=[%d]", (char *) &demod_str, demod_ver); - CONVERT32(STB0899_READ_S2REG(STB0899_S2FEC, FEC_CORE_ID_REG), (char *)&fec_str); - fec_ver = STB0899_READ_S2REG(STB0899_S2FEC, FEC_VER_ID_REG); - if (! (chip_id > 0)) { - dprintk(state->verbose, FE_ERROR, 1, "couldn't find a STB 0899"); - - return -ENODEV; - } - dprintk(state->verbose, FE_ERROR, 1, "FEC Core ID=[%s], Version=[%d]", (char*) &fec_str, fec_ver); - - return 0; -} - -static void stb0899_set_delivery(struct stb0899_state *state) -{ - u8 reg; - u8 stop_clk[2]; - - stop_clk[0] = stb0899_read_reg(state, STB0899_STOPCLK1); - stop_clk[1] = stb0899_read_reg(state, STB0899_STOPCLK2); - - switch (state->delsys) { - case SYS_DVBS: - dprintk(state->verbose, FE_DEBUG, 1, "Delivery System -- DVB-S"); - /* FECM/Viterbi ON */ - reg = stb0899_read_reg(state, STB0899_FECM); - STB0899_SETFIELD_VAL(FECM_RSVD0, reg, 0); - STB0899_SETFIELD_VAL(FECM_VITERBI_ON, reg, 1); - stb0899_write_reg(state, STB0899_FECM, reg); - - stb0899_write_reg(state, STB0899_RSULC, 0xb1); - stb0899_write_reg(state, STB0899_TSULC, 0x40); - stb0899_write_reg(state, STB0899_RSLLC, 0x42); - stb0899_write_reg(state, STB0899_TSLPL, 0x12); - - reg = stb0899_read_reg(state, STB0899_TSTRES); - STB0899_SETFIELD_VAL(FRESLDPC, reg, 1); - stb0899_write_reg(state, STB0899_TSTRES, reg); - - STB0899_SETFIELD_VAL(STOP_CHK8PSK, stop_clk[0], 1); - STB0899_SETFIELD_VAL(STOP_CKFEC108, stop_clk[0], 1); - STB0899_SETFIELD_VAL(STOP_CKFEC216, stop_clk[0], 1); - - STB0899_SETFIELD_VAL(STOP_CKPKDLIN108, stop_clk[1], 1); - STB0899_SETFIELD_VAL(STOP_CKPKDLIN216, stop_clk[1], 1); - - STB0899_SETFIELD_VAL(STOP_CKINTBUF216, stop_clk[0], 1); - STB0899_SETFIELD_VAL(STOP_CKCORE216, stop_clk[0], 0); - - STB0899_SETFIELD_VAL(STOP_CKS2DMD108, stop_clk[1], 1); - break; - case SYS_DVBS2: - /* FECM/Viterbi OFF */ - reg = stb0899_read_reg(state, STB0899_FECM); - STB0899_SETFIELD_VAL(FECM_RSVD0, reg, 0); - STB0899_SETFIELD_VAL(FECM_VITERBI_ON, reg, 0); - stb0899_write_reg(state, STB0899_FECM, reg); - - stb0899_write_reg(state, STB0899_RSULC, 0xb1); - stb0899_write_reg(state, STB0899_TSULC, 0x42); - stb0899_write_reg(state, STB0899_RSLLC, 0x40); - stb0899_write_reg(state, STB0899_TSLPL, 0x02); - - reg = stb0899_read_reg(state, STB0899_TSTRES); - STB0899_SETFIELD_VAL(FRESLDPC, reg, 0); - stb0899_write_reg(state, STB0899_TSTRES, reg); - - STB0899_SETFIELD_VAL(STOP_CHK8PSK, stop_clk[0], 1); - STB0899_SETFIELD_VAL(STOP_CKFEC108, stop_clk[0], 0); - STB0899_SETFIELD_VAL(STOP_CKFEC216, stop_clk[0], 0); - - STB0899_SETFIELD_VAL(STOP_CKPKDLIN108, stop_clk[1], 0); - STB0899_SETFIELD_VAL(STOP_CKPKDLIN216, stop_clk[1], 0); - - STB0899_SETFIELD_VAL(STOP_CKINTBUF216, stop_clk[0], 0); - STB0899_SETFIELD_VAL(STOP_CKCORE216, stop_clk[0], 0); - - STB0899_SETFIELD_VAL(STOP_CKS2DMD108, stop_clk[1], 0); - break; - case SYS_DSS: - /* FECM/Viterbi ON */ - reg = stb0899_read_reg(state, STB0899_FECM); - STB0899_SETFIELD_VAL(FECM_RSVD0, reg, 1); - STB0899_SETFIELD_VAL(FECM_VITERBI_ON, reg, 1); - stb0899_write_reg(state, STB0899_FECM, reg); - - stb0899_write_reg(state, STB0899_RSULC, 0xa1); - stb0899_write_reg(state, STB0899_TSULC, 0x61); - stb0899_write_reg(state, STB0899_RSLLC, 0x42); - - reg = stb0899_read_reg(state, STB0899_TSTRES); - STB0899_SETFIELD_VAL(FRESLDPC, reg, 1); - stb0899_write_reg(state, STB0899_TSTRES, reg); - - STB0899_SETFIELD_VAL(STOP_CHK8PSK, stop_clk[0], 1); - STB0899_SETFIELD_VAL(STOP_CKFEC108, stop_clk[0], 1); - STB0899_SETFIELD_VAL(STOP_CKFEC216, stop_clk[0], 1); - - STB0899_SETFIELD_VAL(STOP_CKPKDLIN108, stop_clk[1], 1); - STB0899_SETFIELD_VAL(STOP_CKPKDLIN216, stop_clk[1], 1); - - STB0899_SETFIELD_VAL(STOP_CKCORE216, stop_clk[0], 0); - - STB0899_SETFIELD_VAL(STOP_CKS2DMD108, stop_clk[1], 1); - break; - default: - dprintk(state->verbose, FE_ERROR, 1, "Unsupported delivery system"); - break; - } - STB0899_SETFIELD_VAL(STOP_CKADCI108, stop_clk[0], 0); - stb0899_write_regs(state, STB0899_STOPCLK1, stop_clk, 2); -} - -/* - * stb0899_set_iterations - * set the LDPC iteration scale function - */ -static void stb0899_set_iterations(struct stb0899_state *state) -{ - struct stb0899_internal *internal = &state->internal; - struct stb0899_config *config = state->config; - - s32 iter_scale; - u32 reg; - - iter_scale = 17 * (internal->master_clk / 1000); - iter_scale += 410000; - iter_scale /= (internal->srate / 1000000); - iter_scale /= 1000; - - if (iter_scale > config->ldpc_max_iter) - iter_scale = config->ldpc_max_iter; - - reg = STB0899_READ_S2REG(STB0899_S2FEC, MAX_ITER); - STB0899_SETFIELD_VAL(MAX_ITERATIONS, reg, iter_scale); - stb0899_write_s2reg(state, STB0899_S2FEC, STB0899_BASE_MAX_ITER, STB0899_OFF0_MAX_ITER, reg); -} - -static enum dvbfe_search stb0899_search(struct dvb_frontend *fe) -{ - struct stb0899_state *state = fe->demodulator_priv; - struct stb0899_params *i_params = &state->params; - struct stb0899_internal *internal = &state->internal; - struct stb0899_config *config = state->config; - struct dtv_frontend_properties *props = &fe->dtv_property_cache; - - u32 SearchRange, gain; - - i_params->freq = props->frequency; - i_params->srate = props->symbol_rate; - state->delsys = props->delivery_system; - dprintk(state->verbose, FE_DEBUG, 1, "delivery system=%d", state->delsys); - - SearchRange = 10000000; - dprintk(state->verbose, FE_DEBUG, 1, "Frequency=%d, Srate=%d", i_params->freq, i_params->srate); - /* checking Search Range is meaningless for a fixed 3 Mhz */ - if (INRANGE(i_params->srate, 1000000, 45000000)) { - dprintk(state->verbose, FE_DEBUG, 1, "Parameters IN RANGE"); - stb0899_set_delivery(state); - - if (state->config->tuner_set_rfsiggain) { - if (internal->srate > 15000000) - gain = 8; /* 15Mb < srate < 45Mb, gain = 8dB */ - else if (internal->srate > 5000000) - gain = 12; /* 5Mb < srate < 15Mb, gain = 12dB */ - else - gain = 14; /* 1Mb < srate < 5Mb, gain = 14db */ - state->config->tuner_set_rfsiggain(fe, gain); - } - - if (i_params->srate <= 5000000) - stb0899_set_mclk(state, config->lo_clk); - else - stb0899_set_mclk(state, config->hi_clk); - - switch (state->delsys) { - case SYS_DVBS: - case SYS_DSS: - dprintk(state->verbose, FE_DEBUG, 1, "DVB-S delivery system"); - internal->freq = i_params->freq; - internal->srate = i_params->srate; - /* - * search = user search range + - * 500Khz + - * 2 * Tuner_step_size + - * 10% of the symbol rate - */ - internal->srch_range = SearchRange + 1500000 + (i_params->srate / 5); - internal->derot_percent = 30; - - /* What to do for tuners having no bandwidth setup ? */ - /* enable tuner I/O */ - stb0899_i2c_gate_ctrl(&state->frontend, 1); - - if (state->config->tuner_set_bandwidth) - state->config->tuner_set_bandwidth(fe, (13 * (stb0899_carr_width(state) + SearchRange)) / 10); - if (state->config->tuner_get_bandwidth) - state->config->tuner_get_bandwidth(fe, &internal->tuner_bw); - - /* disable tuner I/O */ - stb0899_i2c_gate_ctrl(&state->frontend, 0); - - /* Set DVB-S1 AGC */ - stb0899_write_reg(state, STB0899_AGCRFCFG, 0x11); - - /* Run the search algorithm */ - dprintk(state->verbose, FE_DEBUG, 1, "running DVB-S search algo .."); - if (stb0899_dvbs_algo(state) == RANGEOK) { - internal->lock = 1; - dprintk(state->verbose, FE_DEBUG, 1, - "-------------------------------------> DVB-S LOCK !"); - -// stb0899_write_reg(state, STB0899_ERRCTRL1, 0x3d); /* Viterbi Errors */ -// internal->v_status = stb0899_read_reg(state, STB0899_VSTATUS); -// internal->err_ctrl = stb0899_read_reg(state, STB0899_ERRCTRL1); -// dprintk(state->verbose, FE_DEBUG, 1, "VSTATUS=0x%02x", internal->v_status); -// dprintk(state->verbose, FE_DEBUG, 1, "ERR_CTRL=0x%02x", internal->err_ctrl); - - return DVBFE_ALGO_SEARCH_SUCCESS; - } else { - internal->lock = 0; - - return DVBFE_ALGO_SEARCH_FAILED; - } - break; - case SYS_DVBS2: - internal->freq = i_params->freq; - internal->srate = i_params->srate; - internal->srch_range = SearchRange; - - /* enable tuner I/O */ - stb0899_i2c_gate_ctrl(&state->frontend, 1); - - if (state->config->tuner_set_bandwidth) - state->config->tuner_set_bandwidth(fe, (stb0899_carr_width(state) + SearchRange)); - if (state->config->tuner_get_bandwidth) - state->config->tuner_get_bandwidth(fe, &internal->tuner_bw); - - /* disable tuner I/O */ - stb0899_i2c_gate_ctrl(&state->frontend, 0); - -// pParams->SpectralInv = pSearch->IQ_Inversion; - - /* Set DVB-S2 AGC */ - stb0899_write_reg(state, STB0899_AGCRFCFG, 0x1c); - - /* Set IterScale =f(MCLK,SYMB) */ - stb0899_set_iterations(state); - - /* Run the search algorithm */ - dprintk(state->verbose, FE_DEBUG, 1, "running DVB-S2 search algo .."); - if (stb0899_dvbs2_algo(state) == DVBS2_FEC_LOCK) { - internal->lock = 1; - dprintk(state->verbose, FE_DEBUG, 1, - "-------------------------------------> DVB-S2 LOCK !"); - -// stb0899_write_reg(state, STB0899_ERRCTRL1, 0xb6); /* Packet Errors */ -// internal->v_status = stb0899_read_reg(state, STB0899_VSTATUS); -// internal->err_ctrl = stb0899_read_reg(state, STB0899_ERRCTRL1); - - return DVBFE_ALGO_SEARCH_SUCCESS; - } else { - internal->lock = 0; - - return DVBFE_ALGO_SEARCH_FAILED; - } - break; - default: - dprintk(state->verbose, FE_ERROR, 1, "Unsupported delivery system"); - return DVBFE_ALGO_SEARCH_INVALID; - } - } - - return DVBFE_ALGO_SEARCH_ERROR; -} - -static int stb0899_get_frontend(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *p = &fe->dtv_property_cache; - struct stb0899_state *state = fe->demodulator_priv; - struct stb0899_internal *internal = &state->internal; - - dprintk(state->verbose, FE_DEBUG, 1, "Get params"); - p->symbol_rate = internal->srate; - - return 0; -} - -static enum dvbfe_algo stb0899_frontend_algo(struct dvb_frontend *fe) -{ - return DVBFE_ALGO_CUSTOM; -} - -static struct dvb_frontend_ops stb0899_ops = { - .delsys = { SYS_DVBS, SYS_DVBS2, SYS_DSS }, - .info = { - .name = "STB0899 Multistandard", - .frequency_min = 950000, - .frequency_max = 2150000, - .frequency_stepsize = 0, - .frequency_tolerance = 0, - .symbol_rate_min = 5000000, - .symbol_rate_max = 45000000, - - .caps = FE_CAN_INVERSION_AUTO | - FE_CAN_FEC_AUTO | - FE_CAN_2G_MODULATION | - FE_CAN_QPSK - }, - - .release = stb0899_release, - .init = stb0899_init, - .sleep = stb0899_sleep, -// .wakeup = stb0899_wakeup, - - .i2c_gate_ctrl = stb0899_i2c_gate_ctrl, - - .get_frontend_algo = stb0899_frontend_algo, - .search = stb0899_search, - .get_frontend = stb0899_get_frontend, - - - .read_status = stb0899_read_status, - .read_snr = stb0899_read_snr, - .read_signal_strength = stb0899_read_signal_strength, - .read_ber = stb0899_read_ber, - - .set_voltage = stb0899_set_voltage, - .set_tone = stb0899_set_tone, - - .diseqc_send_master_cmd = stb0899_send_diseqc_msg, - .diseqc_recv_slave_reply = stb0899_recv_slave_reply, - .diseqc_send_burst = stb0899_send_diseqc_burst, -}; - -struct dvb_frontend *stb0899_attach(struct stb0899_config *config, struct i2c_adapter *i2c) -{ - struct stb0899_state *state = NULL; - enum stb0899_inversion inversion; - - state = kzalloc(sizeof (struct stb0899_state), GFP_KERNEL); - if (state == NULL) - goto error; - - inversion = config->inversion; - state->verbose = &verbose; - state->config = config; - state->i2c = i2c; - state->frontend.ops = stb0899_ops; - state->frontend.demodulator_priv = state; - state->internal.inversion = inversion; - - stb0899_wakeup(&state->frontend); - if (stb0899_get_dev_id(state) == -ENODEV) { - printk("%s: Exiting .. !\n", __func__); - goto error; - } - - printk("%s: Attaching STB0899 \n", __func__); - return &state->frontend; - -error: - kfree(state); - return NULL; -} -EXPORT_SYMBOL(stb0899_attach); -MODULE_PARM_DESC(verbose, "Set Verbosity level"); -MODULE_AUTHOR("Manu Abraham"); -MODULE_DESCRIPTION("STB0899 Multi-Std frontend"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/stv090x.c b/drivers/media/dvb/frontends/stv090x.c deleted file mode 100644 index ea86a5603e57..000000000000 --- a/drivers/media/dvb/frontends/stv090x.c +++ /dev/null @@ -1,4823 +0,0 @@ -/* - STV0900/0903 Multistandard Broadcast Frontend driver - Copyright (C) Manu Abraham - - Copyright (C) ST Microelectronics - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include -#include -#include -#include -#include -#include - -#include -#include "dvb_frontend.h" - -#include "stv6110x.h" /* for demodulator internal modes */ - -#include "stv090x_reg.h" -#include "stv090x.h" -#include "stv090x_priv.h" - -static unsigned int verbose; -module_param(verbose, int, 0644); - -/* internal params node */ -struct stv090x_dev { - /* pointer for internal params, one for each pair of demods */ - struct stv090x_internal *internal; - struct stv090x_dev *next_dev; -}; - -/* first internal params */ -static struct stv090x_dev *stv090x_first_dev; - -/* find chip by i2c adapter and i2c address */ -static struct stv090x_dev *find_dev(struct i2c_adapter *i2c_adap, - u8 i2c_addr) -{ - struct stv090x_dev *temp_dev = stv090x_first_dev; - - /* - Search of the last stv0900 chip or - find it by i2c adapter and i2c address */ - while ((temp_dev != NULL) && - ((temp_dev->internal->i2c_adap != i2c_adap) || - (temp_dev->internal->i2c_addr != i2c_addr))) { - - temp_dev = temp_dev->next_dev; - } - - return temp_dev; -} - -/* deallocating chip */ -static void remove_dev(struct stv090x_internal *internal) -{ - struct stv090x_dev *prev_dev = stv090x_first_dev; - struct stv090x_dev *del_dev = find_dev(internal->i2c_adap, - internal->i2c_addr); - - if (del_dev != NULL) { - if (del_dev == stv090x_first_dev) { - stv090x_first_dev = del_dev->next_dev; - } else { - while (prev_dev->next_dev != del_dev) - prev_dev = prev_dev->next_dev; - - prev_dev->next_dev = del_dev->next_dev; - } - - kfree(del_dev); - } -} - -/* allocating new chip */ -static struct stv090x_dev *append_internal(struct stv090x_internal *internal) -{ - struct stv090x_dev *new_dev; - struct stv090x_dev *temp_dev; - - new_dev = kmalloc(sizeof(struct stv090x_dev), GFP_KERNEL); - if (new_dev != NULL) { - new_dev->internal = internal; - new_dev->next_dev = NULL; - - /* append to list */ - if (stv090x_first_dev == NULL) { - stv090x_first_dev = new_dev; - } else { - temp_dev = stv090x_first_dev; - while (temp_dev->next_dev != NULL) - temp_dev = temp_dev->next_dev; - - temp_dev->next_dev = new_dev; - } - } - - return new_dev; -} - - -/* DVBS1 and DSS C/N Lookup table */ -static const struct stv090x_tab stv090x_s1cn_tab[] = { - { 0, 8917 }, /* 0.0dB */ - { 5, 8801 }, /* 0.5dB */ - { 10, 8667 }, /* 1.0dB */ - { 15, 8522 }, /* 1.5dB */ - { 20, 8355 }, /* 2.0dB */ - { 25, 8175 }, /* 2.5dB */ - { 30, 7979 }, /* 3.0dB */ - { 35, 7763 }, /* 3.5dB */ - { 40, 7530 }, /* 4.0dB */ - { 45, 7282 }, /* 4.5dB */ - { 50, 7026 }, /* 5.0dB */ - { 55, 6781 }, /* 5.5dB */ - { 60, 6514 }, /* 6.0dB */ - { 65, 6241 }, /* 6.5dB */ - { 70, 5965 }, /* 7.0dB */ - { 75, 5690 }, /* 7.5dB */ - { 80, 5424 }, /* 8.0dB */ - { 85, 5161 }, /* 8.5dB */ - { 90, 4902 }, /* 9.0dB */ - { 95, 4654 }, /* 9.5dB */ - { 100, 4417 }, /* 10.0dB */ - { 105, 4186 }, /* 10.5dB */ - { 110, 3968 }, /* 11.0dB */ - { 115, 3757 }, /* 11.5dB */ - { 120, 3558 }, /* 12.0dB */ - { 125, 3366 }, /* 12.5dB */ - { 130, 3185 }, /* 13.0dB */ - { 135, 3012 }, /* 13.5dB */ - { 140, 2850 }, /* 14.0dB */ - { 145, 2698 }, /* 14.5dB */ - { 150, 2550 }, /* 15.0dB */ - { 160, 2283 }, /* 16.0dB */ - { 170, 2042 }, /* 17.0dB */ - { 180, 1827 }, /* 18.0dB */ - { 190, 1636 }, /* 19.0dB */ - { 200, 1466 }, /* 20.0dB */ - { 210, 1315 }, /* 21.0dB */ - { 220, 1181 }, /* 22.0dB */ - { 230, 1064 }, /* 23.0dB */ - { 240, 960 }, /* 24.0dB */ - { 250, 869 }, /* 25.0dB */ - { 260, 792 }, /* 26.0dB */ - { 270, 724 }, /* 27.0dB */ - { 280, 665 }, /* 28.0dB */ - { 290, 616 }, /* 29.0dB */ - { 300, 573 }, /* 30.0dB */ - { 310, 537 }, /* 31.0dB */ - { 320, 507 }, /* 32.0dB */ - { 330, 483 }, /* 33.0dB */ - { 400, 398 }, /* 40.0dB */ - { 450, 381 }, /* 45.0dB */ - { 500, 377 } /* 50.0dB */ -}; - -/* DVBS2 C/N Lookup table */ -static const struct stv090x_tab stv090x_s2cn_tab[] = { - { -30, 13348 }, /* -3.0dB */ - { -20, 12640 }, /* -2d.0B */ - { -10, 11883 }, /* -1.0dB */ - { 0, 11101 }, /* -0.0dB */ - { 5, 10718 }, /* 0.5dB */ - { 10, 10339 }, /* 1.0dB */ - { 15, 9947 }, /* 1.5dB */ - { 20, 9552 }, /* 2.0dB */ - { 25, 9183 }, /* 2.5dB */ - { 30, 8799 }, /* 3.0dB */ - { 35, 8422 }, /* 3.5dB */ - { 40, 8062 }, /* 4.0dB */ - { 45, 7707 }, /* 4.5dB */ - { 50, 7353 }, /* 5.0dB */ - { 55, 7025 }, /* 5.5dB */ - { 60, 6684 }, /* 6.0dB */ - { 65, 6331 }, /* 6.5dB */ - { 70, 6036 }, /* 7.0dB */ - { 75, 5727 }, /* 7.5dB */ - { 80, 5437 }, /* 8.0dB */ - { 85, 5164 }, /* 8.5dB */ - { 90, 4902 }, /* 9.0dB */ - { 95, 4653 }, /* 9.5dB */ - { 100, 4408 }, /* 10.0dB */ - { 105, 4187 }, /* 10.5dB */ - { 110, 3961 }, /* 11.0dB */ - { 115, 3751 }, /* 11.5dB */ - { 120, 3558 }, /* 12.0dB */ - { 125, 3368 }, /* 12.5dB */ - { 130, 3191 }, /* 13.0dB */ - { 135, 3017 }, /* 13.5dB */ - { 140, 2862 }, /* 14.0dB */ - { 145, 2710 }, /* 14.5dB */ - { 150, 2565 }, /* 15.0dB */ - { 160, 2300 }, /* 16.0dB */ - { 170, 2058 }, /* 17.0dB */ - { 180, 1849 }, /* 18.0dB */ - { 190, 1663 }, /* 19.0dB */ - { 200, 1495 }, /* 20.0dB */ - { 210, 1349 }, /* 21.0dB */ - { 220, 1222 }, /* 22.0dB */ - { 230, 1110 }, /* 23.0dB */ - { 240, 1011 }, /* 24.0dB */ - { 250, 925 }, /* 25.0dB */ - { 260, 853 }, /* 26.0dB */ - { 270, 789 }, /* 27.0dB */ - { 280, 734 }, /* 28.0dB */ - { 290, 690 }, /* 29.0dB */ - { 300, 650 }, /* 30.0dB */ - { 310, 619 }, /* 31.0dB */ - { 320, 593 }, /* 32.0dB */ - { 330, 571 }, /* 33.0dB */ - { 400, 498 }, /* 40.0dB */ - { 450, 484 }, /* 45.0dB */ - { 500, 481 } /* 50.0dB */ -}; - -/* RF level C/N lookup table */ -static const struct stv090x_tab stv090x_rf_tab[] = { - { -5, 0xcaa1 }, /* -5dBm */ - { -10, 0xc229 }, /* -10dBm */ - { -15, 0xbb08 }, /* -15dBm */ - { -20, 0xb4bc }, /* -20dBm */ - { -25, 0xad5a }, /* -25dBm */ - { -30, 0xa298 }, /* -30dBm */ - { -35, 0x98a8 }, /* -35dBm */ - { -40, 0x8389 }, /* -40dBm */ - { -45, 0x59be }, /* -45dBm */ - { -50, 0x3a14 }, /* -50dBm */ - { -55, 0x2d11 }, /* -55dBm */ - { -60, 0x210d }, /* -60dBm */ - { -65, 0xa14f }, /* -65dBm */ - { -70, 0x07aa } /* -70dBm */ -}; - - -static struct stv090x_reg stv0900_initval[] = { - - { STV090x_OUTCFG, 0x00 }, - { STV090x_MODECFG, 0xff }, - { STV090x_AGCRF1CFG, 0x11 }, - { STV090x_AGCRF2CFG, 0x13 }, - { STV090x_TSGENERAL1X, 0x14 }, - { STV090x_TSTTNR2, 0x21 }, - { STV090x_TSTTNR4, 0x21 }, - { STV090x_P2_DISTXCTL, 0x22 }, - { STV090x_P2_F22TX, 0xc0 }, - { STV090x_P2_F22RX, 0xc0 }, - { STV090x_P2_DISRXCTL, 0x00 }, - { STV090x_P2_DMDCFGMD, 0xF9 }, - { STV090x_P2_DEMOD, 0x08 }, - { STV090x_P2_DMDCFG3, 0xc4 }, - { STV090x_P2_CARFREQ, 0xed }, - { STV090x_P2_LDT, 0xd0 }, - { STV090x_P2_LDT2, 0xb8 }, - { STV090x_P2_TMGCFG, 0xd2 }, - { STV090x_P2_TMGTHRISE, 0x20 }, - { STV090x_P1_TMGCFG, 0xd2 }, - - { STV090x_P2_TMGTHFALL, 0x00 }, - { STV090x_P2_FECSPY, 0x88 }, - { STV090x_P2_FSPYDATA, 0x3a }, - { STV090x_P2_FBERCPT4, 0x00 }, - { STV090x_P2_FSPYBER, 0x10 }, - { STV090x_P2_ERRCTRL1, 0x35 }, - { STV090x_P2_ERRCTRL2, 0xc1 }, - { STV090x_P2_CFRICFG, 0xf8 }, - { STV090x_P2_NOSCFG, 0x1c }, - { STV090x_P2_DMDTOM, 0x20 }, - { STV090x_P2_CORRELMANT, 0x70 }, - { STV090x_P2_CORRELABS, 0x88 }, - { STV090x_P2_AGC2O, 0x5b }, - { STV090x_P2_AGC2REF, 0x38 }, - { STV090x_P2_CARCFG, 0xe4 }, - { STV090x_P2_ACLC, 0x1A }, - { STV090x_P2_BCLC, 0x09 }, - { STV090x_P2_CARHDR, 0x08 }, - { STV090x_P2_KREFTMG, 0xc1 }, - { STV090x_P2_SFRUPRATIO, 0xf0 }, - { STV090x_P2_SFRLOWRATIO, 0x70 }, - { STV090x_P2_SFRSTEP, 0x58 }, - { STV090x_P2_TMGCFG2, 0x01 }, - { STV090x_P2_CAR2CFG, 0x26 }, - { STV090x_P2_BCLC2S2Q, 0x86 }, - { STV090x_P2_BCLC2S28, 0x86 }, - { STV090x_P2_SMAPCOEF7, 0x77 }, - { STV090x_P2_SMAPCOEF6, 0x85 }, - { STV090x_P2_SMAPCOEF5, 0x77 }, - { STV090x_P2_TSCFGL, 0x20 }, - { STV090x_P2_DMDCFG2, 0x3b }, - { STV090x_P2_MODCODLST0, 0xff }, - { STV090x_P2_MODCODLST1, 0xff }, - { STV090x_P2_MODCODLST2, 0xff }, - { STV090x_P2_MODCODLST3, 0xff }, - { STV090x_P2_MODCODLST4, 0xff }, - { STV090x_P2_MODCODLST5, 0xff }, - { STV090x_P2_MODCODLST6, 0xff }, - { STV090x_P2_MODCODLST7, 0xcc }, - { STV090x_P2_MODCODLST8, 0xcc }, - { STV090x_P2_MODCODLST9, 0xcc }, - { STV090x_P2_MODCODLSTA, 0xcc }, - { STV090x_P2_MODCODLSTB, 0xcc }, - { STV090x_P2_MODCODLSTC, 0xcc }, - { STV090x_P2_MODCODLSTD, 0xcc }, - { STV090x_P2_MODCODLSTE, 0xcc }, - { STV090x_P2_MODCODLSTF, 0xcf }, - { STV090x_P1_DISTXCTL, 0x22 }, - { STV090x_P1_F22TX, 0xc0 }, - { STV090x_P1_F22RX, 0xc0 }, - { STV090x_P1_DISRXCTL, 0x00 }, - { STV090x_P1_DMDCFGMD, 0xf9 }, - { STV090x_P1_DEMOD, 0x08 }, - { STV090x_P1_DMDCFG3, 0xc4 }, - { STV090x_P1_DMDTOM, 0x20 }, - { STV090x_P1_CARFREQ, 0xed }, - { STV090x_P1_LDT, 0xd0 }, - { STV090x_P1_LDT2, 0xb8 }, - { STV090x_P1_TMGCFG, 0xd2 }, - { STV090x_P1_TMGTHRISE, 0x20 }, - { STV090x_P1_TMGTHFALL, 0x00 }, - { STV090x_P1_SFRUPRATIO, 0xf0 }, - { STV090x_P1_SFRLOWRATIO, 0x70 }, - { STV090x_P1_TSCFGL, 0x20 }, - { STV090x_P1_FECSPY, 0x88 }, - { STV090x_P1_FSPYDATA, 0x3a }, - { STV090x_P1_FBERCPT4, 0x00 }, - { STV090x_P1_FSPYBER, 0x10 }, - { STV090x_P1_ERRCTRL1, 0x35 }, - { STV090x_P1_ERRCTRL2, 0xc1 }, - { STV090x_P1_CFRICFG, 0xf8 }, - { STV090x_P1_NOSCFG, 0x1c }, - { STV090x_P1_CORRELMANT, 0x70 }, - { STV090x_P1_CORRELABS, 0x88 }, - { STV090x_P1_AGC2O, 0x5b }, - { STV090x_P1_AGC2REF, 0x38 }, - { STV090x_P1_CARCFG, 0xe4 }, - { STV090x_P1_ACLC, 0x1A }, - { STV090x_P1_BCLC, 0x09 }, - { STV090x_P1_CARHDR, 0x08 }, - { STV090x_P1_KREFTMG, 0xc1 }, - { STV090x_P1_SFRSTEP, 0x58 }, - { STV090x_P1_TMGCFG2, 0x01 }, - { STV090x_P1_CAR2CFG, 0x26 }, - { STV090x_P1_BCLC2S2Q, 0x86 }, - { STV090x_P1_BCLC2S28, 0x86 }, - { STV090x_P1_SMAPCOEF7, 0x77 }, - { STV090x_P1_SMAPCOEF6, 0x85 }, - { STV090x_P1_SMAPCOEF5, 0x77 }, - { STV090x_P1_DMDCFG2, 0x3b }, - { STV090x_P1_MODCODLST0, 0xff }, - { STV090x_P1_MODCODLST1, 0xff }, - { STV090x_P1_MODCODLST2, 0xff }, - { STV090x_P1_MODCODLST3, 0xff }, - { STV090x_P1_MODCODLST4, 0xff }, - { STV090x_P1_MODCODLST5, 0xff }, - { STV090x_P1_MODCODLST6, 0xff }, - { STV090x_P1_MODCODLST7, 0xcc }, - { STV090x_P1_MODCODLST8, 0xcc }, - { STV090x_P1_MODCODLST9, 0xcc }, - { STV090x_P1_MODCODLSTA, 0xcc }, - { STV090x_P1_MODCODLSTB, 0xcc }, - { STV090x_P1_MODCODLSTC, 0xcc }, - { STV090x_P1_MODCODLSTD, 0xcc }, - { STV090x_P1_MODCODLSTE, 0xcc }, - { STV090x_P1_MODCODLSTF, 0xcf }, - { STV090x_GENCFG, 0x1d }, - { STV090x_NBITER_NF4, 0x37 }, - { STV090x_NBITER_NF5, 0x29 }, - { STV090x_NBITER_NF6, 0x37 }, - { STV090x_NBITER_NF7, 0x33 }, - { STV090x_NBITER_NF8, 0x31 }, - { STV090x_NBITER_NF9, 0x2f }, - { STV090x_NBITER_NF10, 0x39 }, - { STV090x_NBITER_NF11, 0x3a }, - { STV090x_NBITER_NF12, 0x29 }, - { STV090x_NBITER_NF13, 0x37 }, - { STV090x_NBITER_NF14, 0x33 }, - { STV090x_NBITER_NF15, 0x2f }, - { STV090x_NBITER_NF16, 0x39 }, - { STV090x_NBITER_NF17, 0x3a }, - { STV090x_NBITERNOERR, 0x04 }, - { STV090x_GAINLLR_NF4, 0x0C }, - { STV090x_GAINLLR_NF5, 0x0F }, - { STV090x_GAINLLR_NF6, 0x11 }, - { STV090x_GAINLLR_NF7, 0x14 }, - { STV090x_GAINLLR_NF8, 0x17 }, - { STV090x_GAINLLR_NF9, 0x19 }, - { STV090x_GAINLLR_NF10, 0x20 }, - { STV090x_GAINLLR_NF11, 0x21 }, - { STV090x_GAINLLR_NF12, 0x0D }, - { STV090x_GAINLLR_NF13, 0x0F }, - { STV090x_GAINLLR_NF14, 0x13 }, - { STV090x_GAINLLR_NF15, 0x1A }, - { STV090x_GAINLLR_NF16, 0x1F }, - { STV090x_GAINLLR_NF17, 0x21 }, - { STV090x_RCCFGH, 0x20 }, - { STV090x_P1_FECM, 0x01 }, /* disable DSS modes */ - { STV090x_P2_FECM, 0x01 }, /* disable DSS modes */ - { STV090x_P1_PRVIT, 0x2F }, /* disable PR 6/7 */ - { STV090x_P2_PRVIT, 0x2F }, /* disable PR 6/7 */ -}; - -static struct stv090x_reg stv0903_initval[] = { - { STV090x_OUTCFG, 0x00 }, - { STV090x_AGCRF1CFG, 0x11 }, - { STV090x_STOPCLK1, 0x48 }, - { STV090x_STOPCLK2, 0x14 }, - { STV090x_TSTTNR1, 0x27 }, - { STV090x_TSTTNR2, 0x21 }, - { STV090x_P1_DISTXCTL, 0x22 }, - { STV090x_P1_F22TX, 0xc0 }, - { STV090x_P1_F22RX, 0xc0 }, - { STV090x_P1_DISRXCTL, 0x00 }, - { STV090x_P1_DMDCFGMD, 0xF9 }, - { STV090x_P1_DEMOD, 0x08 }, - { STV090x_P1_DMDCFG3, 0xc4 }, - { STV090x_P1_CARFREQ, 0xed }, - { STV090x_P1_TNRCFG2, 0x82 }, - { STV090x_P1_LDT, 0xd0 }, - { STV090x_P1_LDT2, 0xb8 }, - { STV090x_P1_TMGCFG, 0xd2 }, - { STV090x_P1_TMGTHRISE, 0x20 }, - { STV090x_P1_TMGTHFALL, 0x00 }, - { STV090x_P1_SFRUPRATIO, 0xf0 }, - { STV090x_P1_SFRLOWRATIO, 0x70 }, - { STV090x_P1_TSCFGL, 0x20 }, - { STV090x_P1_FECSPY, 0x88 }, - { STV090x_P1_FSPYDATA, 0x3a }, - { STV090x_P1_FBERCPT4, 0x00 }, - { STV090x_P1_FSPYBER, 0x10 }, - { STV090x_P1_ERRCTRL1, 0x35 }, - { STV090x_P1_ERRCTRL2, 0xc1 }, - { STV090x_P1_CFRICFG, 0xf8 }, - { STV090x_P1_NOSCFG, 0x1c }, - { STV090x_P1_DMDTOM, 0x20 }, - { STV090x_P1_CORRELMANT, 0x70 }, - { STV090x_P1_CORRELABS, 0x88 }, - { STV090x_P1_AGC2O, 0x5b }, - { STV090x_P1_AGC2REF, 0x38 }, - { STV090x_P1_CARCFG, 0xe4 }, - { STV090x_P1_ACLC, 0x1A }, - { STV090x_P1_BCLC, 0x09 }, - { STV090x_P1_CARHDR, 0x08 }, - { STV090x_P1_KREFTMG, 0xc1 }, - { STV090x_P1_SFRSTEP, 0x58 }, - { STV090x_P1_TMGCFG2, 0x01 }, - { STV090x_P1_CAR2CFG, 0x26 }, - { STV090x_P1_BCLC2S2Q, 0x86 }, - { STV090x_P1_BCLC2S28, 0x86 }, - { STV090x_P1_SMAPCOEF7, 0x77 }, - { STV090x_P1_SMAPCOEF6, 0x85 }, - { STV090x_P1_SMAPCOEF5, 0x77 }, - { STV090x_P1_DMDCFG2, 0x3b }, - { STV090x_P1_MODCODLST0, 0xff }, - { STV090x_P1_MODCODLST1, 0xff }, - { STV090x_P1_MODCODLST2, 0xff }, - { STV090x_P1_MODCODLST3, 0xff }, - { STV090x_P1_MODCODLST4, 0xff }, - { STV090x_P1_MODCODLST5, 0xff }, - { STV090x_P1_MODCODLST6, 0xff }, - { STV090x_P1_MODCODLST7, 0xcc }, - { STV090x_P1_MODCODLST8, 0xcc }, - { STV090x_P1_MODCODLST9, 0xcc }, - { STV090x_P1_MODCODLSTA, 0xcc }, - { STV090x_P1_MODCODLSTB, 0xcc }, - { STV090x_P1_MODCODLSTC, 0xcc }, - { STV090x_P1_MODCODLSTD, 0xcc }, - { STV090x_P1_MODCODLSTE, 0xcc }, - { STV090x_P1_MODCODLSTF, 0xcf }, - { STV090x_GENCFG, 0x1c }, - { STV090x_NBITER_NF4, 0x37 }, - { STV090x_NBITER_NF5, 0x29 }, - { STV090x_NBITER_NF6, 0x37 }, - { STV090x_NBITER_NF7, 0x33 }, - { STV090x_NBITER_NF8, 0x31 }, - { STV090x_NBITER_NF9, 0x2f }, - { STV090x_NBITER_NF10, 0x39 }, - { STV090x_NBITER_NF11, 0x3a }, - { STV090x_NBITER_NF12, 0x29 }, - { STV090x_NBITER_NF13, 0x37 }, - { STV090x_NBITER_NF14, 0x33 }, - { STV090x_NBITER_NF15, 0x2f }, - { STV090x_NBITER_NF16, 0x39 }, - { STV090x_NBITER_NF17, 0x3a }, - { STV090x_NBITERNOERR, 0x04 }, - { STV090x_GAINLLR_NF4, 0x0C }, - { STV090x_GAINLLR_NF5, 0x0F }, - { STV090x_GAINLLR_NF6, 0x11 }, - { STV090x_GAINLLR_NF7, 0x14 }, - { STV090x_GAINLLR_NF8, 0x17 }, - { STV090x_GAINLLR_NF9, 0x19 }, - { STV090x_GAINLLR_NF10, 0x20 }, - { STV090x_GAINLLR_NF11, 0x21 }, - { STV090x_GAINLLR_NF12, 0x0D }, - { STV090x_GAINLLR_NF13, 0x0F }, - { STV090x_GAINLLR_NF14, 0x13 }, - { STV090x_GAINLLR_NF15, 0x1A }, - { STV090x_GAINLLR_NF16, 0x1F }, - { STV090x_GAINLLR_NF17, 0x21 }, - { STV090x_RCCFGH, 0x20 }, - { STV090x_P1_FECM, 0x01 }, /*disable the DSS mode */ - { STV090x_P1_PRVIT, 0x2f } /*disable puncture rate 6/7*/ -}; - -static struct stv090x_reg stv0900_cut20_val[] = { - - { STV090x_P2_DMDCFG3, 0xe8 }, - { STV090x_P2_DMDCFG4, 0x10 }, - { STV090x_P2_CARFREQ, 0x38 }, - { STV090x_P2_CARHDR, 0x20 }, - { STV090x_P2_KREFTMG, 0x5a }, - { STV090x_P2_SMAPCOEF7, 0x06 }, - { STV090x_P2_SMAPCOEF6, 0x00 }, - { STV090x_P2_SMAPCOEF5, 0x04 }, - { STV090x_P2_NOSCFG, 0x0c }, - { STV090x_P1_DMDCFG3, 0xe8 }, - { STV090x_P1_DMDCFG4, 0x10 }, - { STV090x_P1_CARFREQ, 0x38 }, - { STV090x_P1_CARHDR, 0x20 }, - { STV090x_P1_KREFTMG, 0x5a }, - { STV090x_P1_SMAPCOEF7, 0x06 }, - { STV090x_P1_SMAPCOEF6, 0x00 }, - { STV090x_P1_SMAPCOEF5, 0x04 }, - { STV090x_P1_NOSCFG, 0x0c }, - { STV090x_GAINLLR_NF4, 0x21 }, - { STV090x_GAINLLR_NF5, 0x21 }, - { STV090x_GAINLLR_NF6, 0x20 }, - { STV090x_GAINLLR_NF7, 0x1F }, - { STV090x_GAINLLR_NF8, 0x1E }, - { STV090x_GAINLLR_NF9, 0x1E }, - { STV090x_GAINLLR_NF10, 0x1D }, - { STV090x_GAINLLR_NF11, 0x1B }, - { STV090x_GAINLLR_NF12, 0x20 }, - { STV090x_GAINLLR_NF13, 0x20 }, - { STV090x_GAINLLR_NF14, 0x20 }, - { STV090x_GAINLLR_NF15, 0x20 }, - { STV090x_GAINLLR_NF16, 0x20 }, - { STV090x_GAINLLR_NF17, 0x21 }, -}; - -static struct stv090x_reg stv0903_cut20_val[] = { - { STV090x_P1_DMDCFG3, 0xe8 }, - { STV090x_P1_DMDCFG4, 0x10 }, - { STV090x_P1_CARFREQ, 0x38 }, - { STV090x_P1_CARHDR, 0x20 }, - { STV090x_P1_KREFTMG, 0x5a }, - { STV090x_P1_SMAPCOEF7, 0x06 }, - { STV090x_P1_SMAPCOEF6, 0x00 }, - { STV090x_P1_SMAPCOEF5, 0x04 }, - { STV090x_P1_NOSCFG, 0x0c }, - { STV090x_GAINLLR_NF4, 0x21 }, - { STV090x_GAINLLR_NF5, 0x21 }, - { STV090x_GAINLLR_NF6, 0x20 }, - { STV090x_GAINLLR_NF7, 0x1F }, - { STV090x_GAINLLR_NF8, 0x1E }, - { STV090x_GAINLLR_NF9, 0x1E }, - { STV090x_GAINLLR_NF10, 0x1D }, - { STV090x_GAINLLR_NF11, 0x1B }, - { STV090x_GAINLLR_NF12, 0x20 }, - { STV090x_GAINLLR_NF13, 0x20 }, - { STV090x_GAINLLR_NF14, 0x20 }, - { STV090x_GAINLLR_NF15, 0x20 }, - { STV090x_GAINLLR_NF16, 0x20 }, - { STV090x_GAINLLR_NF17, 0x21 } -}; - -/* Cut 2.0 Long Frame Tracking CR loop */ -static struct stv090x_long_frame_crloop stv090x_s2_crl_cut20[] = { - /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ - { STV090x_QPSK_12, 0x1f, 0x3f, 0x1e, 0x3f, 0x3d, 0x1f, 0x3d, 0x3e, 0x3d, 0x1e }, - { STV090x_QPSK_35, 0x2f, 0x3f, 0x2e, 0x2f, 0x3d, 0x0f, 0x0e, 0x2e, 0x3d, 0x0e }, - { STV090x_QPSK_23, 0x2f, 0x3f, 0x2e, 0x2f, 0x0e, 0x0f, 0x0e, 0x1e, 0x3d, 0x3d }, - { STV090x_QPSK_34, 0x3f, 0x3f, 0x3e, 0x1f, 0x0e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, - { STV090x_QPSK_45, 0x3f, 0x3f, 0x3e, 0x1f, 0x0e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, - { STV090x_QPSK_56, 0x3f, 0x3f, 0x3e, 0x1f, 0x0e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, - { STV090x_QPSK_89, 0x3f, 0x3f, 0x3e, 0x1f, 0x1e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, - { STV090x_QPSK_910, 0x3f, 0x3f, 0x3e, 0x1f, 0x1e, 0x3e, 0x0e, 0x1e, 0x3d, 0x3d }, - { STV090x_8PSK_35, 0x3c, 0x3e, 0x1c, 0x2e, 0x0c, 0x1e, 0x2b, 0x2d, 0x1b, 0x1d }, - { STV090x_8PSK_23, 0x1d, 0x3e, 0x3c, 0x2e, 0x2c, 0x1e, 0x0c, 0x2d, 0x2b, 0x1d }, - { STV090x_8PSK_34, 0x0e, 0x3e, 0x3d, 0x2e, 0x0d, 0x1e, 0x2c, 0x2d, 0x0c, 0x1d }, - { STV090x_8PSK_56, 0x2e, 0x3e, 0x1e, 0x2e, 0x2d, 0x1e, 0x3c, 0x2d, 0x2c, 0x1d }, - { STV090x_8PSK_89, 0x3e, 0x3e, 0x1e, 0x2e, 0x3d, 0x1e, 0x0d, 0x2d, 0x3c, 0x1d }, - { STV090x_8PSK_910, 0x3e, 0x3e, 0x1e, 0x2e, 0x3d, 0x1e, 0x1d, 0x2d, 0x0d, 0x1d } -}; - -/* Cut 3.0 Long Frame Tracking CR loop */ -static struct stv090x_long_frame_crloop stv090x_s2_crl_cut30[] = { - /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ - { STV090x_QPSK_12, 0x3c, 0x2c, 0x0c, 0x2c, 0x1b, 0x2c, 0x1b, 0x1c, 0x0b, 0x3b }, - { STV090x_QPSK_35, 0x0d, 0x0d, 0x0c, 0x0d, 0x1b, 0x3c, 0x1b, 0x1c, 0x0b, 0x3b }, - { STV090x_QPSK_23, 0x1d, 0x0d, 0x0c, 0x1d, 0x2b, 0x3c, 0x1b, 0x1c, 0x0b, 0x3b }, - { STV090x_QPSK_34, 0x1d, 0x1d, 0x0c, 0x1d, 0x2b, 0x3c, 0x1b, 0x1c, 0x0b, 0x3b }, - { STV090x_QPSK_45, 0x2d, 0x1d, 0x1c, 0x1d, 0x2b, 0x3c, 0x2b, 0x0c, 0x1b, 0x3b }, - { STV090x_QPSK_56, 0x2d, 0x1d, 0x1c, 0x1d, 0x2b, 0x3c, 0x2b, 0x0c, 0x1b, 0x3b }, - { STV090x_QPSK_89, 0x3d, 0x2d, 0x1c, 0x1d, 0x3b, 0x3c, 0x2b, 0x0c, 0x1b, 0x3b }, - { STV090x_QPSK_910, 0x3d, 0x2d, 0x1c, 0x1d, 0x3b, 0x3c, 0x2b, 0x0c, 0x1b, 0x3b }, - { STV090x_8PSK_35, 0x39, 0x29, 0x39, 0x19, 0x19, 0x19, 0x19, 0x19, 0x09, 0x19 }, - { STV090x_8PSK_23, 0x2a, 0x39, 0x1a, 0x0a, 0x39, 0x0a, 0x29, 0x39, 0x29, 0x0a }, - { STV090x_8PSK_34, 0x2b, 0x3a, 0x1b, 0x1b, 0x3a, 0x1b, 0x1a, 0x0b, 0x1a, 0x3a }, - { STV090x_8PSK_56, 0x0c, 0x1b, 0x3b, 0x3b, 0x1b, 0x3b, 0x3a, 0x3b, 0x3a, 0x1b }, - { STV090x_8PSK_89, 0x0d, 0x3c, 0x2c, 0x2c, 0x2b, 0x0c, 0x0b, 0x3b, 0x0b, 0x1b }, - { STV090x_8PSK_910, 0x0d, 0x0d, 0x2c, 0x3c, 0x3b, 0x1c, 0x0b, 0x3b, 0x0b, 0x1b } -}; - -/* Cut 2.0 Long Frame Tracking CR Loop */ -static struct stv090x_long_frame_crloop stv090x_s2_apsk_crl_cut20[] = { - /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ - { STV090x_16APSK_23, 0x0c, 0x0c, 0x0c, 0x0c, 0x1d, 0x0c, 0x3c, 0x0c, 0x2c, 0x0c }, - { STV090x_16APSK_34, 0x0c, 0x0c, 0x0c, 0x0c, 0x0e, 0x0c, 0x2d, 0x0c, 0x1d, 0x0c }, - { STV090x_16APSK_45, 0x0c, 0x0c, 0x0c, 0x0c, 0x1e, 0x0c, 0x3d, 0x0c, 0x2d, 0x0c }, - { STV090x_16APSK_56, 0x0c, 0x0c, 0x0c, 0x0c, 0x1e, 0x0c, 0x3d, 0x0c, 0x2d, 0x0c }, - { STV090x_16APSK_89, 0x0c, 0x0c, 0x0c, 0x0c, 0x2e, 0x0c, 0x0e, 0x0c, 0x3d, 0x0c }, - { STV090x_16APSK_910, 0x0c, 0x0c, 0x0c, 0x0c, 0x2e, 0x0c, 0x0e, 0x0c, 0x3d, 0x0c }, - { STV090x_32APSK_34, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c }, - { STV090x_32APSK_45, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c }, - { STV090x_32APSK_56, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c }, - { STV090x_32APSK_89, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c }, - { STV090x_32APSK_910, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c } -}; - -/* Cut 3.0 Long Frame Tracking CR Loop */ -static struct stv090x_long_frame_crloop stv090x_s2_apsk_crl_cut30[] = { - /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ - { STV090x_16APSK_23, 0x0a, 0x0a, 0x0a, 0x0a, 0x1a, 0x0a, 0x3a, 0x0a, 0x2a, 0x0a }, - { STV090x_16APSK_34, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0a, 0x3b, 0x0a, 0x1b, 0x0a }, - { STV090x_16APSK_45, 0x0a, 0x0a, 0x0a, 0x0a, 0x1b, 0x0a, 0x3b, 0x0a, 0x2b, 0x0a }, - { STV090x_16APSK_56, 0x0a, 0x0a, 0x0a, 0x0a, 0x1b, 0x0a, 0x3b, 0x0a, 0x2b, 0x0a }, - { STV090x_16APSK_89, 0x0a, 0x0a, 0x0a, 0x0a, 0x2b, 0x0a, 0x0c, 0x0a, 0x3b, 0x0a }, - { STV090x_16APSK_910, 0x0a, 0x0a, 0x0a, 0x0a, 0x2b, 0x0a, 0x0c, 0x0a, 0x3b, 0x0a }, - { STV090x_32APSK_34, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a }, - { STV090x_32APSK_45, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a }, - { STV090x_32APSK_56, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a }, - { STV090x_32APSK_89, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a }, - { STV090x_32APSK_910, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a } -}; - -static struct stv090x_long_frame_crloop stv090x_s2_lowqpsk_crl_cut20[] = { - /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ - { STV090x_QPSK_14, 0x0f, 0x3f, 0x0e, 0x3f, 0x2d, 0x2f, 0x2d, 0x1f, 0x3d, 0x3e }, - { STV090x_QPSK_13, 0x0f, 0x3f, 0x0e, 0x3f, 0x2d, 0x2f, 0x3d, 0x0f, 0x3d, 0x2e }, - { STV090x_QPSK_25, 0x1f, 0x3f, 0x1e, 0x3f, 0x3d, 0x1f, 0x3d, 0x3e, 0x3d, 0x2e } -}; - -static struct stv090x_long_frame_crloop stv090x_s2_lowqpsk_crl_cut30[] = { - /* MODCOD 2MPon 2MPoff 5MPon 5MPoff 10MPon 10MPoff 20MPon 20MPoff 30MPon 30MPoff */ - { STV090x_QPSK_14, 0x0c, 0x3c, 0x0b, 0x3c, 0x2a, 0x2c, 0x2a, 0x1c, 0x3a, 0x3b }, - { STV090x_QPSK_13, 0x0c, 0x3c, 0x0b, 0x3c, 0x2a, 0x2c, 0x3a, 0x0c, 0x3a, 0x2b }, - { STV090x_QPSK_25, 0x1c, 0x3c, 0x1b, 0x3c, 0x3a, 0x1c, 0x3a, 0x3b, 0x3a, 0x2b } -}; - -/* Cut 2.0 Short Frame Tracking CR Loop */ -static struct stv090x_short_frame_crloop stv090x_s2_short_crl_cut20[] = { - /* MODCOD 2M 5M 10M 20M 30M */ - { STV090x_QPSK, 0x2f, 0x2e, 0x0e, 0x0e, 0x3d }, - { STV090x_8PSK, 0x3e, 0x0e, 0x2d, 0x0d, 0x3c }, - { STV090x_16APSK, 0x1e, 0x1e, 0x1e, 0x3d, 0x2d }, - { STV090x_32APSK, 0x1e, 0x1e, 0x1e, 0x3d, 0x2d } -}; - -/* Cut 3.0 Short Frame Tracking CR Loop */ -static struct stv090x_short_frame_crloop stv090x_s2_short_crl_cut30[] = { - /* MODCOD 2M 5M 10M 20M 30M */ - { STV090x_QPSK, 0x2C, 0x2B, 0x0B, 0x0B, 0x3A }, - { STV090x_8PSK, 0x3B, 0x0B, 0x2A, 0x0A, 0x39 }, - { STV090x_16APSK, 0x1B, 0x1B, 0x1B, 0x3A, 0x2A }, - { STV090x_32APSK, 0x1B, 0x1B, 0x1B, 0x3A, 0x2A } -}; - -static inline s32 comp2(s32 __x, s32 __width) -{ - if (__width == 32) - return __x; - else - return (__x >= (1 << (__width - 1))) ? (__x - (1 << __width)) : __x; -} - -static int stv090x_read_reg(struct stv090x_state *state, unsigned int reg) -{ - const struct stv090x_config *config = state->config; - int ret; - - u8 b0[] = { reg >> 8, reg & 0xff }; - u8 buf; - - struct i2c_msg msg[] = { - { .addr = config->address, .flags = 0, .buf = b0, .len = 2 }, - { .addr = config->address, .flags = I2C_M_RD, .buf = &buf, .len = 1 } - }; - - ret = i2c_transfer(state->i2c, msg, 2); - if (ret != 2) { - if (ret != -ERESTARTSYS) - dprintk(FE_ERROR, 1, - "Read error, Reg=[0x%02x], Status=%d", - reg, ret); - - return ret < 0 ? ret : -EREMOTEIO; - } - if (unlikely(*state->verbose >= FE_DEBUGREG)) - dprintk(FE_ERROR, 1, "Reg=[0x%02x], data=%02x", - reg, buf); - - return (unsigned int) buf; -} - -static int stv090x_write_regs(struct stv090x_state *state, unsigned int reg, u8 *data, u32 count) -{ - const struct stv090x_config *config = state->config; - int ret; - u8 buf[2 + count]; - struct i2c_msg i2c_msg = { .addr = config->address, .flags = 0, .buf = buf, .len = 2 + count }; - - buf[0] = reg >> 8; - buf[1] = reg & 0xff; - memcpy(&buf[2], data, count); - - if (unlikely(*state->verbose >= FE_DEBUGREG)) { - int i; - - printk(KERN_DEBUG "%s [0x%04x]:", __func__, reg); - for (i = 0; i < count; i++) - printk(" %02x", data[i]); - printk("\n"); - } - - ret = i2c_transfer(state->i2c, &i2c_msg, 1); - if (ret != 1) { - if (ret != -ERESTARTSYS) - dprintk(FE_ERROR, 1, "Reg=[0x%04x], Data=[0x%02x ...], Count=%u, Status=%d", - reg, data[0], count, ret); - return ret < 0 ? ret : -EREMOTEIO; - } - - return 0; -} - -static int stv090x_write_reg(struct stv090x_state *state, unsigned int reg, u8 data) -{ - return stv090x_write_regs(state, reg, &data, 1); -} - -static int stv090x_i2c_gate_ctrl(struct stv090x_state *state, int enable) -{ - u32 reg; - - /* - * NOTE! A lock is used as a FSM to control the state in which - * access is serialized between two tuners on the same demod. - * This has nothing to do with a lock to protect a critical section - * which may in some other cases be confused with protecting I/O - * access to the demodulator gate. - * In case of any error, the lock is unlocked and exit within the - * relevant operations themselves. - */ - if (enable) { - if (state->config->tuner_i2c_lock) - state->config->tuner_i2c_lock(&state->frontend, 1); - else - mutex_lock(&state->internal->tuner_lock); - } - - reg = STV090x_READ_DEMOD(state, I2CRPT); - if (enable) { - dprintk(FE_DEBUG, 1, "Enable Gate"); - STV090x_SETFIELD_Px(reg, I2CT_ON_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, I2CRPT, reg) < 0) - goto err; - - } else { - dprintk(FE_DEBUG, 1, "Disable Gate"); - STV090x_SETFIELD_Px(reg, I2CT_ON_FIELD, 0); - if ((STV090x_WRITE_DEMOD(state, I2CRPT, reg)) < 0) - goto err; - } - - if (!enable) { - if (state->config->tuner_i2c_lock) - state->config->tuner_i2c_lock(&state->frontend, 0); - else - mutex_unlock(&state->internal->tuner_lock); - } - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - if (state->config->tuner_i2c_lock) - state->config->tuner_i2c_lock(&state->frontend, 0); - else - mutex_unlock(&state->internal->tuner_lock); - return -1; -} - -static void stv090x_get_lock_tmg(struct stv090x_state *state) -{ - switch (state->algo) { - case STV090x_BLIND_SEARCH: - dprintk(FE_DEBUG, 1, "Blind Search"); - if (state->srate <= 1500000) { /*10Msps< SR <=15Msps*/ - state->DemodTimeout = 1500; - state->FecTimeout = 400; - } else if (state->srate <= 5000000) { /*10Msps< SR <=15Msps*/ - state->DemodTimeout = 1000; - state->FecTimeout = 300; - } else { /*SR >20Msps*/ - state->DemodTimeout = 700; - state->FecTimeout = 100; - } - break; - - case STV090x_COLD_SEARCH: - case STV090x_WARM_SEARCH: - default: - dprintk(FE_DEBUG, 1, "Normal Search"); - if (state->srate <= 1000000) { /*SR <=1Msps*/ - state->DemodTimeout = 4500; - state->FecTimeout = 1700; - } else if (state->srate <= 2000000) { /*1Msps < SR <= 2Msps */ - state->DemodTimeout = 2500; - state->FecTimeout = 1100; - } else if (state->srate <= 5000000) { /*2Msps < SR <= 5Msps */ - state->DemodTimeout = 1000; - state->FecTimeout = 550; - } else if (state->srate <= 10000000) { /*5Msps < SR <= 10Msps */ - state->DemodTimeout = 700; - state->FecTimeout = 250; - } else if (state->srate <= 20000000) { /*10Msps < SR <= 20Msps */ - state->DemodTimeout = 400; - state->FecTimeout = 130; - } else { /*SR >20Msps*/ - state->DemodTimeout = 300; - state->FecTimeout = 100; - } - break; - } - - if (state->algo == STV090x_WARM_SEARCH) - state->DemodTimeout /= 2; -} - -static int stv090x_set_srate(struct stv090x_state *state, u32 srate) -{ - u32 sym; - - if (srate > 60000000) { - sym = (srate << 4); /* SR * 2^16 / master_clk */ - sym /= (state->internal->mclk >> 12); - } else if (srate > 6000000) { - sym = (srate << 6); - sym /= (state->internal->mclk >> 10); - } else { - sym = (srate << 9); - sym /= (state->internal->mclk >> 7); - } - - if (STV090x_WRITE_DEMOD(state, SFRINIT1, (sym >> 8) & 0x7f) < 0) /* MSB */ - goto err; - if (STV090x_WRITE_DEMOD(state, SFRINIT0, (sym & 0xff)) < 0) /* LSB */ - goto err; - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_set_max_srate(struct stv090x_state *state, u32 clk, u32 srate) -{ - u32 sym; - - srate = 105 * (srate / 100); - if (srate > 60000000) { - sym = (srate << 4); /* SR * 2^16 / master_clk */ - sym /= (state->internal->mclk >> 12); - } else if (srate > 6000000) { - sym = (srate << 6); - sym /= (state->internal->mclk >> 10); - } else { - sym = (srate << 9); - sym /= (state->internal->mclk >> 7); - } - - if (sym < 0x7fff) { - if (STV090x_WRITE_DEMOD(state, SFRUP1, (sym >> 8) & 0x7f) < 0) /* MSB */ - goto err; - if (STV090x_WRITE_DEMOD(state, SFRUP0, sym & 0xff) < 0) /* LSB */ - goto err; - } else { - if (STV090x_WRITE_DEMOD(state, SFRUP1, 0x7f) < 0) /* MSB */ - goto err; - if (STV090x_WRITE_DEMOD(state, SFRUP0, 0xff) < 0) /* LSB */ - goto err; - } - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_set_min_srate(struct stv090x_state *state, u32 clk, u32 srate) -{ - u32 sym; - - srate = 95 * (srate / 100); - if (srate > 60000000) { - sym = (srate << 4); /* SR * 2^16 / master_clk */ - sym /= (state->internal->mclk >> 12); - } else if (srate > 6000000) { - sym = (srate << 6); - sym /= (state->internal->mclk >> 10); - } else { - sym = (srate << 9); - sym /= (state->internal->mclk >> 7); - } - - if (STV090x_WRITE_DEMOD(state, SFRLOW1, ((sym >> 8) & 0x7f)) < 0) /* MSB */ - goto err; - if (STV090x_WRITE_DEMOD(state, SFRLOW0, (sym & 0xff)) < 0) /* LSB */ - goto err; - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static u32 stv090x_car_width(u32 srate, enum stv090x_rolloff rolloff) -{ - u32 ro; - - switch (rolloff) { - case STV090x_RO_20: - ro = 20; - break; - case STV090x_RO_25: - ro = 25; - break; - case STV090x_RO_35: - default: - ro = 35; - break; - } - - return srate + (srate * ro) / 100; -} - -static int stv090x_set_vit_thacq(struct stv090x_state *state) -{ - if (STV090x_WRITE_DEMOD(state, VTH12, 0x96) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH23, 0x64) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH34, 0x36) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH56, 0x23) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH67, 0x1e) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH78, 0x19) < 0) - goto err; - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_set_vit_thtracq(struct stv090x_state *state) -{ - if (STV090x_WRITE_DEMOD(state, VTH12, 0xd0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH23, 0x7d) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH34, 0x53) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH56, 0x2f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH67, 0x24) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VTH78, 0x1f) < 0) - goto err; - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_set_viterbi(struct stv090x_state *state) -{ - switch (state->search_mode) { - case STV090x_SEARCH_AUTO: - if (STV090x_WRITE_DEMOD(state, FECM, 0x10) < 0) /* DVB-S and DVB-S2 */ - goto err; - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x3f) < 0) /* all puncture rate */ - goto err; - break; - case STV090x_SEARCH_DVBS1: - if (STV090x_WRITE_DEMOD(state, FECM, 0x00) < 0) /* disable DSS */ - goto err; - switch (state->fec) { - case STV090x_PR12: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x01) < 0) - goto err; - break; - - case STV090x_PR23: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x02) < 0) - goto err; - break; - - case STV090x_PR34: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x04) < 0) - goto err; - break; - - case STV090x_PR56: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x08) < 0) - goto err; - break; - - case STV090x_PR78: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x20) < 0) - goto err; - break; - - default: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x2f) < 0) /* all */ - goto err; - break; - } - break; - case STV090x_SEARCH_DSS: - if (STV090x_WRITE_DEMOD(state, FECM, 0x80) < 0) - goto err; - switch (state->fec) { - case STV090x_PR12: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x01) < 0) - goto err; - break; - - case STV090x_PR23: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x02) < 0) - goto err; - break; - - case STV090x_PR67: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x10) < 0) - goto err; - break; - - default: - if (STV090x_WRITE_DEMOD(state, PRVIT, 0x13) < 0) /* 1/2, 2/3, 6/7 */ - goto err; - break; - } - break; - default: - break; - } - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_stop_modcod(struct stv090x_state *state) -{ - if (STV090x_WRITE_DEMOD(state, MODCODLST0, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST1, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST2, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST3, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST4, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST5, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST6, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST7, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST8, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST9, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTA, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTB, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTC, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTD, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTE, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTF, 0xff) < 0) - goto err; - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_activate_modcod(struct stv090x_state *state) -{ - if (STV090x_WRITE_DEMOD(state, MODCODLST0, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST1, 0xfc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST2, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST3, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST4, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST5, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST6, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST7, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST8, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST9, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTA, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTB, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTC, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTD, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTE, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTF, 0xcf) < 0) - goto err; - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_activate_modcod_single(struct stv090x_state *state) -{ - - if (STV090x_WRITE_DEMOD(state, MODCODLST0, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST1, 0xf0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST2, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST3, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST4, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST5, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST6, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST7, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST8, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST9, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTA, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTB, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTC, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTD, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTE, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTF, 0x0f) < 0) - goto err; - - return 0; - -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_vitclk_ctl(struct stv090x_state *state, int enable) -{ - u32 reg; - - switch (state->demod) { - case STV090x_DEMODULATOR_0: - mutex_lock(&state->internal->demod_lock); - reg = stv090x_read_reg(state, STV090x_STOPCLK2); - STV090x_SETFIELD(reg, STOP_CLKVIT1_FIELD, enable); - if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) - goto err; - mutex_unlock(&state->internal->demod_lock); - break; - - case STV090x_DEMODULATOR_1: - mutex_lock(&state->internal->demod_lock); - reg = stv090x_read_reg(state, STV090x_STOPCLK2); - STV090x_SETFIELD(reg, STOP_CLKVIT2_FIELD, enable); - if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) - goto err; - mutex_unlock(&state->internal->demod_lock); - break; - - default: - dprintk(FE_ERROR, 1, "Wrong demodulator!"); - break; - } - return 0; -err: - mutex_unlock(&state->internal->demod_lock); - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_dvbs_track_crl(struct stv090x_state *state) -{ - if (state->internal->dev_ver >= 0x30) { - /* Set ACLC BCLC optimised value vs SR */ - if (state->srate >= 15000000) { - if (STV090x_WRITE_DEMOD(state, ACLC, 0x2b) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, BCLC, 0x1a) < 0) - goto err; - } else if ((state->srate >= 7000000) && (15000000 > state->srate)) { - if (STV090x_WRITE_DEMOD(state, ACLC, 0x0c) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, BCLC, 0x1b) < 0) - goto err; - } else if (state->srate < 7000000) { - if (STV090x_WRITE_DEMOD(state, ACLC, 0x2c) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, BCLC, 0x1c) < 0) - goto err; - } - - } else { - /* Cut 2.0 */ - if (STV090x_WRITE_DEMOD(state, ACLC, 0x1a) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, BCLC, 0x09) < 0) - goto err; - } - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_delivery_search(struct stv090x_state *state) -{ - u32 reg; - - switch (state->search_mode) { - case STV090x_SEARCH_DVBS1: - case STV090x_SEARCH_DSS: - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); - STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - - /* Activate Viterbi decoder in legacy search, - * do not use FRESVIT1, might impact VITERBI2 - */ - if (stv090x_vitclk_ctl(state, 0) < 0) - goto err; - - if (stv090x_dvbs_track_crl(state) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x22) < 0) /* disable DVB-S2 */ - goto err; - - if (stv090x_set_vit_thacq(state) < 0) - goto err; - if (stv090x_set_viterbi(state) < 0) - goto err; - break; - - case STV090x_SEARCH_DVBS2: - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 0); - STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); - STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - - if (stv090x_vitclk_ctl(state, 1) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, ACLC, 0x1a) < 0) /* stop DVB-S CR loop */ - goto err; - if (STV090x_WRITE_DEMOD(state, BCLC, 0x09) < 0) - goto err; - - if (state->internal->dev_ver <= 0x20) { - /* enable S2 carrier loop */ - if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x26) < 0) - goto err; - } else { - /* > Cut 3: Stop carrier 3 */ - if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x66) < 0) - goto err; - } - - if (state->demod_mode != STV090x_SINGLE) { - /* Cut 2: enable link during search */ - if (stv090x_activate_modcod(state) < 0) - goto err; - } else { - /* Single demodulator - * Authorize SHORT and LONG frames, - * QPSK, 8PSK, 16APSK and 32APSK - */ - if (stv090x_activate_modcod_single(state) < 0) - goto err; - } - - if (stv090x_set_vit_thtracq(state) < 0) - goto err; - break; - - case STV090x_SEARCH_AUTO: - default: - /* enable DVB-S2 and DVB-S2 in Auto MODE */ - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 0); - STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); - STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - - if (stv090x_vitclk_ctl(state, 0) < 0) - goto err; - - if (stv090x_dvbs_track_crl(state) < 0) - goto err; - - if (state->internal->dev_ver <= 0x20) { - /* enable S2 carrier loop */ - if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x26) < 0) - goto err; - } else { - /* > Cut 3: Stop carrier 3 */ - if (STV090x_WRITE_DEMOD(state, CAR2CFG, 0x66) < 0) - goto err; - } - - if (state->demod_mode != STV090x_SINGLE) { - /* Cut 2: enable link during search */ - if (stv090x_activate_modcod(state) < 0) - goto err; - } else { - /* Single demodulator - * Authorize SHORT and LONG frames, - * QPSK, 8PSK, 16APSK and 32APSK - */ - if (stv090x_activate_modcod_single(state) < 0) - goto err; - } - - if (stv090x_set_vit_thacq(state) < 0) - goto err; - - if (stv090x_set_viterbi(state) < 0) - goto err; - break; - } - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_start_search(struct stv090x_state *state) -{ - u32 reg, freq_abs; - s16 freq; - - /* Reset demodulator */ - reg = STV090x_READ_DEMOD(state, DMDISTATE); - STV090x_SETFIELD_Px(reg, I2C_DEMOD_MODE_FIELD, 0x1f); - if (STV090x_WRITE_DEMOD(state, DMDISTATE, reg) < 0) - goto err; - - if (state->internal->dev_ver <= 0x20) { - if (state->srate <= 5000000) { - if (STV090x_WRITE_DEMOD(state, CARCFG, 0x44) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRUP1, 0x0f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRUP0, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRLOW1, 0xf0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRLOW0, 0x00) < 0) - goto err; - - /*enlarge the timing bandwidth for Low SR*/ - if (STV090x_WRITE_DEMOD(state, RTCS2, 0x68) < 0) - goto err; - } else { - /* If the symbol rate is >5 Msps - Set The carrier search up and low to auto mode */ - if (STV090x_WRITE_DEMOD(state, CARCFG, 0xc4) < 0) - goto err; - /*reduce the timing bandwidth for high SR*/ - if (STV090x_WRITE_DEMOD(state, RTCS2, 0x44) < 0) - goto err; - } - } else { - /* >= Cut 3 */ - if (state->srate <= 5000000) { - /* enlarge the timing bandwidth for Low SR */ - STV090x_WRITE_DEMOD(state, RTCS2, 0x68); - } else { - /* reduce timing bandwidth for high SR */ - STV090x_WRITE_DEMOD(state, RTCS2, 0x44); - } - - /* Set CFR min and max to manual mode */ - STV090x_WRITE_DEMOD(state, CARCFG, 0x46); - - if (state->algo == STV090x_WARM_SEARCH) { - /* WARM Start - * CFR min = -1MHz, - * CFR max = +1MHz - */ - freq_abs = 1000 << 16; - freq_abs /= (state->internal->mclk / 1000); - freq = (s16) freq_abs; - } else { - /* COLD Start - * CFR min =- (SearchRange / 2 + 600KHz) - * CFR max = +(SearchRange / 2 + 600KHz) - * (600KHz for the tuner step size) - */ - freq_abs = (state->search_range / 2000) + 600; - freq_abs = freq_abs << 16; - freq_abs /= (state->internal->mclk / 1000); - freq = (s16) freq_abs; - } - - if (STV090x_WRITE_DEMOD(state, CFRUP1, MSB(freq)) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRUP0, LSB(freq)) < 0) - goto err; - - freq *= -1; - - if (STV090x_WRITE_DEMOD(state, CFRLOW1, MSB(freq)) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRLOW0, LSB(freq)) < 0) - goto err; - - } - - if (STV090x_WRITE_DEMOD(state, CFRINIT1, 0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, 0) < 0) - goto err; - - if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, EQUALCFG, 0x41) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, FFECFG, 0x41) < 0) - goto err; - - if ((state->search_mode == STV090x_SEARCH_DVBS1) || - (state->search_mode == STV090x_SEARCH_DSS) || - (state->search_mode == STV090x_SEARCH_AUTO)) { - - if (STV090x_WRITE_DEMOD(state, VITSCALE, 0x82) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VAVSRVIT, 0x00) < 0) - goto err; - } - } - - if (STV090x_WRITE_DEMOD(state, SFRSTEP, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGTHRISE, 0xe0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGTHFALL, 0xc0) < 0) - goto err; - - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, SCAN_ENABLE_FIELD, 0); - STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - reg = STV090x_READ_DEMOD(state, DMDCFG2); - STV090x_SETFIELD_Px(reg, S1S2_SEQUENTIAL_FIELD, 0x0); - if (STV090x_WRITE_DEMOD(state, DMDCFG2, reg) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, RTC, 0x88) < 0) - goto err; - - if (state->internal->dev_ver >= 0x20) { - /*Frequency offset detector setting*/ - if (state->srate < 2000000) { - if (state->internal->dev_ver <= 0x20) { - /* Cut 2 */ - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x39) < 0) - goto err; - } else { - /* Cut 3 */ - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x89) < 0) - goto err; - } - if (STV090x_WRITE_DEMOD(state, CARHDR, 0x40) < 0) - goto err; - } else if (state->srate < 10000000) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x4c) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CARHDR, 0x20) < 0) - goto err; - } else { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x4b) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CARHDR, 0x20) < 0) - goto err; - } - } else { - if (state->srate < 10000000) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0xef) < 0) - goto err; - } else { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0xed) < 0) - goto err; - } - } - - switch (state->algo) { - case STV090x_WARM_SEARCH: - /* The symbol rate and the exact - * carrier Frequency are known - */ - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) - goto err; - break; - - case STV090x_COLD_SEARCH: - /* The symbol rate is known */ - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x15) < 0) - goto err; - break; - - default: - break; - } - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_get_agc2_min_level(struct stv090x_state *state) -{ - u32 agc2_min = 0xffff, agc2 = 0, freq_init, freq_step, reg; - s32 i, j, steps, dir; - - if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) - goto err; - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, SCAN_ENABLE_FIELD, 0); - STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, SFRUP1, 0x83) < 0) /* SR = 65 Msps Max */ - goto err; - if (STV090x_WRITE_DEMOD(state, SFRUP0, 0xc0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRLOW1, 0x82) < 0) /* SR= 400 ksps Min */ - goto err; - if (STV090x_WRITE_DEMOD(state, SFRLOW0, 0xa0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDTOM, 0x00) < 0) /* stop acq @ coarse carrier state */ - goto err; - if (stv090x_set_srate(state, 1000000) < 0) - goto err; - - steps = state->search_range / 1000000; - if (steps <= 0) - steps = 1; - - dir = 1; - freq_step = (1000000 * 256) / (state->internal->mclk / 256); - freq_init = 0; - - for (i = 0; i < steps; i++) { - if (dir > 0) - freq_init = freq_init + (freq_step * i); - else - freq_init = freq_init - (freq_step * i); - - dir *= -1; - - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x5c) < 0) /* Demod RESET */ - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT1, (freq_init >> 8) & 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, freq_init & 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x58) < 0) /* Demod RESET */ - goto err; - msleep(10); - - agc2 = 0; - for (j = 0; j < 10; j++) { - agc2 += (STV090x_READ_DEMOD(state, AGC2I1) << 8) | - STV090x_READ_DEMOD(state, AGC2I0); - } - agc2 /= 10; - if (agc2 < agc2_min) - agc2_min = agc2; - } - - return agc2_min; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static u32 stv090x_get_srate(struct stv090x_state *state, u32 clk) -{ - u8 r3, r2, r1, r0; - s32 srate, int_1, int_2, tmp_1, tmp_2; - - r3 = STV090x_READ_DEMOD(state, SFR3); - r2 = STV090x_READ_DEMOD(state, SFR2); - r1 = STV090x_READ_DEMOD(state, SFR1); - r0 = STV090x_READ_DEMOD(state, SFR0); - - srate = ((r3 << 24) | (r2 << 16) | (r1 << 8) | r0); - - int_1 = clk >> 16; - int_2 = srate >> 16; - - tmp_1 = clk % 0x10000; - tmp_2 = srate % 0x10000; - - srate = (int_1 * int_2) + - ((int_1 * tmp_2) >> 16) + - ((int_2 * tmp_1) >> 16); - - return srate; -} - -static u32 stv090x_srate_srch_coarse(struct stv090x_state *state) -{ - struct dvb_frontend *fe = &state->frontend; - - int tmg_lock = 0, i; - s32 tmg_cpt = 0, dir = 1, steps, cur_step = 0, freq; - u32 srate_coarse = 0, agc2 = 0, car_step = 1200, reg; - u32 agc2th; - - if (state->internal->dev_ver >= 0x30) - agc2th = 0x2e00; - else - agc2th = 0x1f00; - - reg = STV090x_READ_DEMOD(state, DMDISTATE); - STV090x_SETFIELD_Px(reg, I2C_DEMOD_MODE_FIELD, 0x1f); /* Demod RESET */ - if (STV090x_WRITE_DEMOD(state, DMDISTATE, reg) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGCFG, 0x12) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGTHRISE, 0xf0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGTHFALL, 0xe0) < 0) - goto err; - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, SCAN_ENABLE_FIELD, 1); - STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, SFRUP1, 0x83) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRUP0, 0xc0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRLOW1, 0x82) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRLOW0, 0xa0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDTOM, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x50) < 0) - goto err; - - if (state->internal->dev_ver >= 0x30) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x99) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRSTEP, 0x98) < 0) - goto err; - - } else if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x6a) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRSTEP, 0x95) < 0) - goto err; - } - - if (state->srate <= 2000000) - car_step = 1000; - else if (state->srate <= 5000000) - car_step = 2000; - else if (state->srate <= 12000000) - car_step = 3000; - else - car_step = 5000; - - steps = -1 + ((state->search_range / 1000) / car_step); - steps /= 2; - steps = (2 * steps) + 1; - if (steps < 0) - steps = 1; - else if (steps > 10) { - steps = 11; - car_step = (state->search_range / 1000) / 10; - } - cur_step = 0; - dir = 1; - freq = state->frequency; - - while ((!tmg_lock) && (cur_step < steps)) { - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x5f) < 0) /* Demod RESET */ - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT1, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRINIT1, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRINIT0, 0x00) < 0) - goto err; - /* trigger acquisition */ - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x40) < 0) - goto err; - msleep(50); - for (i = 0; i < 10; i++) { - reg = STV090x_READ_DEMOD(state, DSTATUS); - if (STV090x_GETFIELD_Px(reg, TMGLOCK_QUALITY_FIELD) >= 2) - tmg_cpt++; - agc2 += (STV090x_READ_DEMOD(state, AGC2I1) << 8) | - STV090x_READ_DEMOD(state, AGC2I0); - } - agc2 /= 10; - srate_coarse = stv090x_get_srate(state, state->internal->mclk); - cur_step++; - dir *= -1; - if ((tmg_cpt >= 5) && (agc2 < agc2th) && - (srate_coarse < 50000000) && (srate_coarse > 850000)) - tmg_lock = 1; - else if (cur_step < steps) { - if (dir > 0) - freq += cur_step * car_step; - else - freq -= cur_step * car_step; - - /* Setup tuner */ - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (state->config->tuner_set_frequency) { - if (state->config->tuner_set_frequency(fe, freq) < 0) - goto err_gateoff; - } - - if (state->config->tuner_set_bandwidth) { - if (state->config->tuner_set_bandwidth(fe, state->tuner_bw) < 0) - goto err_gateoff; - } - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - msleep(50); - - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (state->config->tuner_get_status) { - if (state->config->tuner_get_status(fe, ®) < 0) - goto err_gateoff; - } - - if (reg) - dprintk(FE_DEBUG, 1, "Tuner phase locked"); - else - dprintk(FE_DEBUG, 1, "Tuner unlocked"); - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - } - } - if (!tmg_lock) - srate_coarse = 0; - else - srate_coarse = stv090x_get_srate(state, state->internal->mclk); - - return srate_coarse; - -err_gateoff: - stv090x_i2c_gate_ctrl(state, 0); -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static u32 stv090x_srate_srch_fine(struct stv090x_state *state) -{ - u32 srate_coarse, freq_coarse, sym, reg; - - srate_coarse = stv090x_get_srate(state, state->internal->mclk); - freq_coarse = STV090x_READ_DEMOD(state, CFR2) << 8; - freq_coarse |= STV090x_READ_DEMOD(state, CFR1); - sym = 13 * (srate_coarse / 10); /* SFRUP = SFR + 30% */ - - if (sym < state->srate) - srate_coarse = 0; - else { - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) /* Demod RESET */ - goto err; - if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc1) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGTHRISE, 0x20) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGTHFALL, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGCFG, 0xd2) < 0) - goto err; - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0x00); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) - goto err; - - if (state->internal->dev_ver >= 0x30) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x79) < 0) - goto err; - } else if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x49) < 0) - goto err; - } - - if (srate_coarse > 3000000) { - sym = 13 * (srate_coarse / 10); /* SFRUP = SFR + 30% */ - sym = (sym / 1000) * 65536; - sym /= (state->internal->mclk / 1000); - if (STV090x_WRITE_DEMOD(state, SFRUP1, (sym >> 8) & 0x7f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRUP0, sym & 0xff) < 0) - goto err; - sym = 10 * (srate_coarse / 13); /* SFRLOW = SFR - 30% */ - sym = (sym / 1000) * 65536; - sym /= (state->internal->mclk / 1000); - if (STV090x_WRITE_DEMOD(state, SFRLOW1, (sym >> 8) & 0x7f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRLOW0, sym & 0xff) < 0) - goto err; - sym = (srate_coarse / 1000) * 65536; - sym /= (state->internal->mclk / 1000); - if (STV090x_WRITE_DEMOD(state, SFRINIT1, (sym >> 8) & 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRINIT0, sym & 0xff) < 0) - goto err; - } else { - sym = 13 * (srate_coarse / 10); /* SFRUP = SFR + 30% */ - sym = (sym / 100) * 65536; - sym /= (state->internal->mclk / 100); - if (STV090x_WRITE_DEMOD(state, SFRUP1, (sym >> 8) & 0x7f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRUP0, sym & 0xff) < 0) - goto err; - sym = 10 * (srate_coarse / 14); /* SFRLOW = SFR - 30% */ - sym = (sym / 100) * 65536; - sym /= (state->internal->mclk / 100); - if (STV090x_WRITE_DEMOD(state, SFRLOW1, (sym >> 8) & 0x7f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRLOW0, sym & 0xff) < 0) - goto err; - sym = (srate_coarse / 100) * 65536; - sym /= (state->internal->mclk / 100); - if (STV090x_WRITE_DEMOD(state, SFRINIT1, (sym >> 8) & 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, SFRINIT0, sym & 0xff) < 0) - goto err; - } - if (STV090x_WRITE_DEMOD(state, DMDTOM, 0x20) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT1, (freq_coarse >> 8) & 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, freq_coarse & 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x15) < 0) /* trigger acquisition */ - goto err; - } - - return srate_coarse; - -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_get_dmdlock(struct stv090x_state *state, s32 timeout) -{ - s32 timer = 0, lock = 0; - u32 reg; - u8 stat; - - while ((timer < timeout) && (!lock)) { - reg = STV090x_READ_DEMOD(state, DMDSTATE); - stat = STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD); - - switch (stat) { - case 0: /* searching */ - case 1: /* first PLH detected */ - default: - dprintk(FE_DEBUG, 1, "Demodulator searching .."); - lock = 0; - break; - case 2: /* DVB-S2 mode */ - case 3: /* DVB-S1/legacy mode */ - reg = STV090x_READ_DEMOD(state, DSTATUS); - lock = STV090x_GETFIELD_Px(reg, LOCK_DEFINITIF_FIELD); - break; - } - - if (!lock) - msleep(10); - else - dprintk(FE_DEBUG, 1, "Demodulator acquired LOCK"); - - timer += 10; - } - return lock; -} - -static int stv090x_blind_search(struct stv090x_state *state) -{ - u32 agc2, reg, srate_coarse; - s32 cpt_fail, agc2_ovflw, i; - u8 k_ref, k_max, k_min; - int coarse_fail = 0; - int lock; - - k_max = 110; - k_min = 10; - - agc2 = stv090x_get_agc2_min_level(state); - - if (agc2 > STV090x_SEARCH_AGC2_TH(state->internal->dev_ver)) { - lock = 0; - } else { - - if (state->internal->dev_ver <= 0x20) { - if (STV090x_WRITE_DEMOD(state, CARCFG, 0xc4) < 0) - goto err; - } else { - /* > Cut 3 */ - if (STV090x_WRITE_DEMOD(state, CARCFG, 0x06) < 0) - goto err; - } - - if (STV090x_WRITE_DEMOD(state, RTCS2, 0x44) < 0) - goto err; - - if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, EQUALCFG, 0x41) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, FFECFG, 0x41) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VITSCALE, 0x82) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VAVSRVIT, 0x00) < 0) /* set viterbi hysteresis */ - goto err; - } - - k_ref = k_max; - do { - if (STV090x_WRITE_DEMOD(state, KREFTMG, k_ref) < 0) - goto err; - if (stv090x_srate_srch_coarse(state) != 0) { - srate_coarse = stv090x_srate_srch_fine(state); - if (srate_coarse != 0) { - stv090x_get_lock_tmg(state); - lock = stv090x_get_dmdlock(state, - state->DemodTimeout); - } else { - lock = 0; - } - } else { - cpt_fail = 0; - agc2_ovflw = 0; - for (i = 0; i < 10; i++) { - agc2 += (STV090x_READ_DEMOD(state, AGC2I1) << 8) | - STV090x_READ_DEMOD(state, AGC2I0); - if (agc2 >= 0xff00) - agc2_ovflw++; - reg = STV090x_READ_DEMOD(state, DSTATUS2); - if ((STV090x_GETFIELD_Px(reg, CFR_OVERFLOW_FIELD) == 0x01) && - (STV090x_GETFIELD_Px(reg, DEMOD_DELOCK_FIELD) == 0x01)) - - cpt_fail++; - } - if ((cpt_fail > 7) || (agc2_ovflw > 7)) - coarse_fail = 1; - - lock = 0; - } - k_ref -= 20; - } while ((k_ref >= k_min) && (!lock) && (!coarse_fail)); - } - - return lock; - -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_chk_tmg(struct stv090x_state *state) -{ - u32 reg; - s32 tmg_cpt = 0, i; - u8 freq, tmg_thh, tmg_thl; - int tmg_lock = 0; - - freq = STV090x_READ_DEMOD(state, CARFREQ); - tmg_thh = STV090x_READ_DEMOD(state, TMGTHRISE); - tmg_thl = STV090x_READ_DEMOD(state, TMGTHFALL); - if (STV090x_WRITE_DEMOD(state, TMGTHRISE, 0x20) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGTHFALL, 0x00) < 0) - goto err; - - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0x00); /* stop carrier offset search */ - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, RTC, 0x80) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, RTCS2, 0x40) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x00) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, CFRINIT1, 0x00) < 0) /* set car ofset to 0 */ - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x65) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) /* trigger acquisition */ - goto err; - msleep(10); - - for (i = 0; i < 10; i++) { - reg = STV090x_READ_DEMOD(state, DSTATUS); - if (STV090x_GETFIELD_Px(reg, TMGLOCK_QUALITY_FIELD) >= 2) - tmg_cpt++; - msleep(1); - } - if (tmg_cpt >= 3) - tmg_lock = 1; - - if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, RTC, 0x88) < 0) /* DVB-S1 timing */ - goto err; - if (STV090x_WRITE_DEMOD(state, RTCS2, 0x68) < 0) /* DVB-S2 timing */ - goto err; - - if (STV090x_WRITE_DEMOD(state, CARFREQ, freq) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGTHRISE, tmg_thh) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGTHFALL, tmg_thl) < 0) - goto err; - - return tmg_lock; - -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_get_coldlock(struct stv090x_state *state, s32 timeout_dmd) -{ - struct dvb_frontend *fe = &state->frontend; - - u32 reg; - s32 car_step, steps, cur_step, dir, freq, timeout_lock; - int lock = 0; - - if (state->srate >= 10000000) - timeout_lock = timeout_dmd / 3; - else - timeout_lock = timeout_dmd / 2; - - lock = stv090x_get_dmdlock(state, timeout_lock); /* cold start wait */ - if (!lock) { - if (state->srate >= 10000000) { - if (stv090x_chk_tmg(state)) { - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x15) < 0) - goto err; - lock = stv090x_get_dmdlock(state, timeout_dmd); - } else { - lock = 0; - } - } else { - if (state->srate <= 4000000) - car_step = 1000; - else if (state->srate <= 7000000) - car_step = 2000; - else if (state->srate <= 10000000) - car_step = 3000; - else - car_step = 5000; - - steps = (state->search_range / 1000) / car_step; - steps /= 2; - steps = 2 * (steps + 1); - if (steps < 0) - steps = 2; - else if (steps > 12) - steps = 12; - - cur_step = 1; - dir = 1; - - if (!lock) { - freq = state->frequency; - state->tuner_bw = stv090x_car_width(state->srate, state->rolloff) + state->srate; - while ((cur_step <= steps) && (!lock)) { - if (dir > 0) - freq += cur_step * car_step; - else - freq -= cur_step * car_step; - - /* Setup tuner */ - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (state->config->tuner_set_frequency) { - if (state->config->tuner_set_frequency(fe, freq) < 0) - goto err_gateoff; - } - - if (state->config->tuner_set_bandwidth) { - if (state->config->tuner_set_bandwidth(fe, state->tuner_bw) < 0) - goto err_gateoff; - } - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - msleep(50); - - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (state->config->tuner_get_status) { - if (state->config->tuner_get_status(fe, ®) < 0) - goto err_gateoff; - } - - if (reg) - dprintk(FE_DEBUG, 1, "Tuner phase locked"); - else - dprintk(FE_DEBUG, 1, "Tuner unlocked"); - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1c); - if (STV090x_WRITE_DEMOD(state, CFRINIT1, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, 0x00) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x15) < 0) - goto err; - lock = stv090x_get_dmdlock(state, (timeout_dmd / 3)); - - dir *= -1; - cur_step++; - } - } - } - } - - return lock; - -err_gateoff: - stv090x_i2c_gate_ctrl(state, 0); -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_get_loop_params(struct stv090x_state *state, s32 *freq_inc, s32 *timeout_sw, s32 *steps) -{ - s32 timeout, inc, steps_max, srate, car_max; - - srate = state->srate; - car_max = state->search_range / 1000; - car_max += car_max / 10; - car_max = 65536 * (car_max / 2); - car_max /= (state->internal->mclk / 1000); - - if (car_max > 0x4000) - car_max = 0x4000 ; /* maxcarrier should be<= +-1/4 Mclk */ - - inc = srate; - inc /= state->internal->mclk / 1000; - inc *= 256; - inc *= 256; - inc /= 1000; - - switch (state->search_mode) { - case STV090x_SEARCH_DVBS1: - case STV090x_SEARCH_DSS: - inc *= 3; /* freq step = 3% of srate */ - timeout = 20; - break; - - case STV090x_SEARCH_DVBS2: - inc *= 4; - timeout = 25; - break; - - case STV090x_SEARCH_AUTO: - default: - inc *= 3; - timeout = 25; - break; - } - inc /= 100; - if ((inc > car_max) || (inc < 0)) - inc = car_max / 2; /* increment <= 1/8 Mclk */ - - timeout *= 27500; /* 27.5 Msps reference */ - if (srate > 0) - timeout /= (srate / 1000); - - if ((timeout > 100) || (timeout < 0)) - timeout = 100; - - steps_max = (car_max / inc) + 1; /* min steps = 3 */ - if ((steps_max > 100) || (steps_max < 0)) { - steps_max = 100; /* max steps <= 100 */ - inc = car_max / steps_max; - } - *freq_inc = inc; - *timeout_sw = timeout; - *steps = steps_max; - - return 0; -} - -static int stv090x_chk_signal(struct stv090x_state *state) -{ - s32 offst_car, agc2, car_max; - int no_signal; - - offst_car = STV090x_READ_DEMOD(state, CFR2) << 8; - offst_car |= STV090x_READ_DEMOD(state, CFR1); - offst_car = comp2(offst_car, 16); - - agc2 = STV090x_READ_DEMOD(state, AGC2I1) << 8; - agc2 |= STV090x_READ_DEMOD(state, AGC2I0); - car_max = state->search_range / 1000; - - car_max += (car_max / 10); /* 10% margin */ - car_max = (65536 * car_max / 2); - car_max /= state->internal->mclk / 1000; - - if (car_max > 0x4000) - car_max = 0x4000; - - if ((agc2 > 0x2000) || (offst_car > 2 * car_max) || (offst_car < -2 * car_max)) { - no_signal = 1; - dprintk(FE_DEBUG, 1, "No Signal"); - } else { - no_signal = 0; - dprintk(FE_DEBUG, 1, "Found Signal"); - } - - return no_signal; -} - -static int stv090x_search_car_loop(struct stv090x_state *state, s32 inc, s32 timeout, int zigzag, s32 steps_max) -{ - int no_signal, lock = 0; - s32 cpt_step = 0, offst_freq, car_max; - u32 reg; - - car_max = state->search_range / 1000; - car_max += (car_max / 10); - car_max = (65536 * car_max / 2); - car_max /= (state->internal->mclk / 1000); - if (car_max > 0x4000) - car_max = 0x4000; - - if (zigzag) - offst_freq = 0; - else - offst_freq = -car_max + inc; - - do { - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1c) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT1, ((offst_freq / 256) & 0xff)) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, offst_freq & 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) - goto err; - - reg = STV090x_READ_DEMOD(state, PDELCTRL1); - STV090x_SETFIELD_Px(reg, ALGOSWRST_FIELD, 0x1); /* stop DVB-S2 packet delin */ - if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) - goto err; - - if (zigzag) { - if (offst_freq >= 0) - offst_freq = -offst_freq - 2 * inc; - else - offst_freq = -offst_freq; - } else { - offst_freq += 2 * inc; - } - - cpt_step++; - - lock = stv090x_get_dmdlock(state, timeout); - no_signal = stv090x_chk_signal(state); - - } while ((!lock) && - (!no_signal) && - ((offst_freq - inc) < car_max) && - ((offst_freq + inc) > -car_max) && - (cpt_step < steps_max)); - - reg = STV090x_READ_DEMOD(state, PDELCTRL1); - STV090x_SETFIELD_Px(reg, ALGOSWRST_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) - goto err; - - return lock; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_sw_algo(struct stv090x_state *state) -{ - int no_signal, zigzag, lock = 0; - u32 reg; - - s32 dvbs2_fly_wheel; - s32 inc, timeout_step, trials, steps_max; - - /* get params */ - stv090x_get_loop_params(state, &inc, &timeout_step, &steps_max); - - switch (state->search_mode) { - case STV090x_SEARCH_DVBS1: - case STV090x_SEARCH_DSS: - /* accelerate the frequency detector */ - if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x3B) < 0) - goto err; - } - - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, 0x49) < 0) - goto err; - zigzag = 0; - break; - - case STV090x_SEARCH_DVBS2: - if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x79) < 0) - goto err; - } - - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, 0x89) < 0) - goto err; - zigzag = 1; - break; - - case STV090x_SEARCH_AUTO: - default: - /* accelerate the frequency detector */ - if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x3b) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x79) < 0) - goto err; - } - - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, 0xc9) < 0) - goto err; - zigzag = 0; - break; - } - - trials = 0; - do { - lock = stv090x_search_car_loop(state, inc, timeout_step, zigzag, steps_max); - no_signal = stv090x_chk_signal(state); - trials++; - - /*run the SW search 2 times maximum*/ - if (lock || no_signal || (trials == 2)) { - /*Check if the demod is not losing lock in DVBS2*/ - if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x49) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x9e) < 0) - goto err; - } - - reg = STV090x_READ_DEMOD(state, DMDSTATE); - if ((lock) && (STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD) == STV090x_DVBS2)) { - /*Check if the demod is not losing lock in DVBS2*/ - msleep(timeout_step); - reg = STV090x_READ_DEMOD(state, DMDFLYW); - dvbs2_fly_wheel = STV090x_GETFIELD_Px(reg, FLYWHEEL_CPT_FIELD); - if (dvbs2_fly_wheel < 0xd) { /*if correct frames is decrementing */ - msleep(timeout_step); - reg = STV090x_READ_DEMOD(state, DMDFLYW); - dvbs2_fly_wheel = STV090x_GETFIELD_Px(reg, FLYWHEEL_CPT_FIELD); - } - if (dvbs2_fly_wheel < 0xd) { - /*FALSE lock, The demod is losing lock */ - lock = 0; - if (trials < 2) { - if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x79) < 0) - goto err; - } - - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, 0x89) < 0) - goto err; - } - } - } - } - } while ((!lock) && (trials < 2) && (!no_signal)); - - return lock; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static enum stv090x_delsys stv090x_get_std(struct stv090x_state *state) -{ - u32 reg; - enum stv090x_delsys delsys; - - reg = STV090x_READ_DEMOD(state, DMDSTATE); - if (STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD) == 2) - delsys = STV090x_DVBS2; - else if (STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD) == 3) { - reg = STV090x_READ_DEMOD(state, FECM); - if (STV090x_GETFIELD_Px(reg, DSS_DVB_FIELD) == 1) - delsys = STV090x_DSS; - else - delsys = STV090x_DVBS1; - } else { - delsys = STV090x_ERROR; - } - - return delsys; -} - -/* in Hz */ -static s32 stv090x_get_car_freq(struct stv090x_state *state, u32 mclk) -{ - s32 derot, int_1, int_2, tmp_1, tmp_2; - - derot = STV090x_READ_DEMOD(state, CFR2) << 16; - derot |= STV090x_READ_DEMOD(state, CFR1) << 8; - derot |= STV090x_READ_DEMOD(state, CFR0); - - derot = comp2(derot, 24); - int_1 = mclk >> 12; - int_2 = derot >> 12; - - /* carrier_frequency = MasterClock * Reg / 2^24 */ - tmp_1 = mclk % 0x1000; - tmp_2 = derot % 0x1000; - - derot = (int_1 * int_2) + - ((int_1 * tmp_2) >> 12) + - ((int_2 * tmp_1) >> 12); - - return derot; -} - -static int stv090x_get_viterbi(struct stv090x_state *state) -{ - u32 reg, rate; - - reg = STV090x_READ_DEMOD(state, VITCURPUN); - rate = STV090x_GETFIELD_Px(reg, VIT_CURPUN_FIELD); - - switch (rate) { - case 13: - state->fec = STV090x_PR12; - break; - - case 18: - state->fec = STV090x_PR23; - break; - - case 21: - state->fec = STV090x_PR34; - break; - - case 24: - state->fec = STV090x_PR56; - break; - - case 25: - state->fec = STV090x_PR67; - break; - - case 26: - state->fec = STV090x_PR78; - break; - - default: - state->fec = STV090x_PRERR; - break; - } - - return 0; -} - -static enum stv090x_signal_state stv090x_get_sig_params(struct stv090x_state *state) -{ - struct dvb_frontend *fe = &state->frontend; - - u8 tmg; - u32 reg; - s32 i = 0, offst_freq; - - msleep(5); - - if (state->algo == STV090x_BLIND_SEARCH) { - tmg = STV090x_READ_DEMOD(state, TMGREG2); - STV090x_WRITE_DEMOD(state, SFRSTEP, 0x5c); - while ((i <= 50) && (tmg != 0) && (tmg != 0xff)) { - tmg = STV090x_READ_DEMOD(state, TMGREG2); - msleep(5); - i += 5; - } - } - state->delsys = stv090x_get_std(state); - - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (state->config->tuner_get_frequency) { - if (state->config->tuner_get_frequency(fe, &state->frequency) < 0) - goto err_gateoff; - } - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - offst_freq = stv090x_get_car_freq(state, state->internal->mclk) / 1000; - state->frequency += offst_freq; - - if (stv090x_get_viterbi(state) < 0) - goto err; - - reg = STV090x_READ_DEMOD(state, DMDMODCOD); - state->modcod = STV090x_GETFIELD_Px(reg, DEMOD_MODCOD_FIELD); - state->pilots = STV090x_GETFIELD_Px(reg, DEMOD_TYPE_FIELD) & 0x01; - state->frame_len = STV090x_GETFIELD_Px(reg, DEMOD_TYPE_FIELD) >> 1; - reg = STV090x_READ_DEMOD(state, TMGOBS); - state->rolloff = STV090x_GETFIELD_Px(reg, ROLLOFF_STATUS_FIELD); - reg = STV090x_READ_DEMOD(state, FECM); - state->inversion = STV090x_GETFIELD_Px(reg, IQINV_FIELD); - - if ((state->algo == STV090x_BLIND_SEARCH) || (state->srate < 10000000)) { - - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (state->config->tuner_get_frequency) { - if (state->config->tuner_get_frequency(fe, &state->frequency) < 0) - goto err_gateoff; - } - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - if (abs(offst_freq) <= ((state->search_range / 2000) + 500)) - return STV090x_RANGEOK; - else if (abs(offst_freq) <= (stv090x_car_width(state->srate, state->rolloff) / 2000)) - return STV090x_RANGEOK; - else - return STV090x_OUTOFRANGE; /* Out of Range */ - } else { - if (abs(offst_freq) <= ((state->search_range / 2000) + 500)) - return STV090x_RANGEOK; - else - return STV090x_OUTOFRANGE; - } - - return STV090x_OUTOFRANGE; - -err_gateoff: - stv090x_i2c_gate_ctrl(state, 0); -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static u32 stv090x_get_tmgoffst(struct stv090x_state *state, u32 srate) -{ - s32 offst_tmg; - - offst_tmg = STV090x_READ_DEMOD(state, TMGREG2) << 16; - offst_tmg |= STV090x_READ_DEMOD(state, TMGREG1) << 8; - offst_tmg |= STV090x_READ_DEMOD(state, TMGREG0); - - offst_tmg = comp2(offst_tmg, 24); /* 2's complement */ - if (!offst_tmg) - offst_tmg = 1; - - offst_tmg = ((s32) srate * 10) / ((s32) 0x1000000 / offst_tmg); - offst_tmg /= 320; - - return offst_tmg; -} - -static u8 stv090x_optimize_carloop(struct stv090x_state *state, enum stv090x_modcod modcod, s32 pilots) -{ - u8 aclc = 0x29; - s32 i; - struct stv090x_long_frame_crloop *car_loop, *car_loop_qpsk_low, *car_loop_apsk_low; - - if (state->internal->dev_ver == 0x20) { - car_loop = stv090x_s2_crl_cut20; - car_loop_qpsk_low = stv090x_s2_lowqpsk_crl_cut20; - car_loop_apsk_low = stv090x_s2_apsk_crl_cut20; - } else { - /* >= Cut 3 */ - car_loop = stv090x_s2_crl_cut30; - car_loop_qpsk_low = stv090x_s2_lowqpsk_crl_cut30; - car_loop_apsk_low = stv090x_s2_apsk_crl_cut30; - } - - if (modcod < STV090x_QPSK_12) { - i = 0; - while ((i < 3) && (modcod != car_loop_qpsk_low[i].modcod)) - i++; - - if (i >= 3) - i = 2; - - } else { - i = 0; - while ((i < 14) && (modcod != car_loop[i].modcod)) - i++; - - if (i >= 14) { - i = 0; - while ((i < 11) && (modcod != car_loop_apsk_low[i].modcod)) - i++; - - if (i >= 11) - i = 10; - } - } - - if (modcod <= STV090x_QPSK_25) { - if (pilots) { - if (state->srate <= 3000000) - aclc = car_loop_qpsk_low[i].crl_pilots_on_2; - else if (state->srate <= 7000000) - aclc = car_loop_qpsk_low[i].crl_pilots_on_5; - else if (state->srate <= 15000000) - aclc = car_loop_qpsk_low[i].crl_pilots_on_10; - else if (state->srate <= 25000000) - aclc = car_loop_qpsk_low[i].crl_pilots_on_20; - else - aclc = car_loop_qpsk_low[i].crl_pilots_on_30; - } else { - if (state->srate <= 3000000) - aclc = car_loop_qpsk_low[i].crl_pilots_off_2; - else if (state->srate <= 7000000) - aclc = car_loop_qpsk_low[i].crl_pilots_off_5; - else if (state->srate <= 15000000) - aclc = car_loop_qpsk_low[i].crl_pilots_off_10; - else if (state->srate <= 25000000) - aclc = car_loop_qpsk_low[i].crl_pilots_off_20; - else - aclc = car_loop_qpsk_low[i].crl_pilots_off_30; - } - - } else if (modcod <= STV090x_8PSK_910) { - if (pilots) { - if (state->srate <= 3000000) - aclc = car_loop[i].crl_pilots_on_2; - else if (state->srate <= 7000000) - aclc = car_loop[i].crl_pilots_on_5; - else if (state->srate <= 15000000) - aclc = car_loop[i].crl_pilots_on_10; - else if (state->srate <= 25000000) - aclc = car_loop[i].crl_pilots_on_20; - else - aclc = car_loop[i].crl_pilots_on_30; - } else { - if (state->srate <= 3000000) - aclc = car_loop[i].crl_pilots_off_2; - else if (state->srate <= 7000000) - aclc = car_loop[i].crl_pilots_off_5; - else if (state->srate <= 15000000) - aclc = car_loop[i].crl_pilots_off_10; - else if (state->srate <= 25000000) - aclc = car_loop[i].crl_pilots_off_20; - else - aclc = car_loop[i].crl_pilots_off_30; - } - } else { /* 16APSK and 32APSK */ - if (state->srate <= 3000000) - aclc = car_loop_apsk_low[i].crl_pilots_on_2; - else if (state->srate <= 7000000) - aclc = car_loop_apsk_low[i].crl_pilots_on_5; - else if (state->srate <= 15000000) - aclc = car_loop_apsk_low[i].crl_pilots_on_10; - else if (state->srate <= 25000000) - aclc = car_loop_apsk_low[i].crl_pilots_on_20; - else - aclc = car_loop_apsk_low[i].crl_pilots_on_30; - } - - return aclc; -} - -static u8 stv090x_optimize_carloop_short(struct stv090x_state *state) -{ - struct stv090x_short_frame_crloop *short_crl = NULL; - s32 index = 0; - u8 aclc = 0x0b; - - switch (state->modulation) { - case STV090x_QPSK: - default: - index = 0; - break; - case STV090x_8PSK: - index = 1; - break; - case STV090x_16APSK: - index = 2; - break; - case STV090x_32APSK: - index = 3; - break; - } - - if (state->internal->dev_ver >= 0x30) { - /* Cut 3.0 and up */ - short_crl = stv090x_s2_short_crl_cut30; - } else { - /* Cut 2.0 and up: we don't support cuts older than 2.0 */ - short_crl = stv090x_s2_short_crl_cut20; - } - - if (state->srate <= 3000000) - aclc = short_crl[index].crl_2; - else if (state->srate <= 7000000) - aclc = short_crl[index].crl_5; - else if (state->srate <= 15000000) - aclc = short_crl[index].crl_10; - else if (state->srate <= 25000000) - aclc = short_crl[index].crl_20; - else - aclc = short_crl[index].crl_30; - - return aclc; -} - -static int stv090x_optimize_track(struct stv090x_state *state) -{ - struct dvb_frontend *fe = &state->frontend; - - enum stv090x_modcod modcod; - - s32 srate, pilots, aclc, f_1, f_0, i = 0, blind_tune = 0; - u32 reg; - - srate = stv090x_get_srate(state, state->internal->mclk); - srate += stv090x_get_tmgoffst(state, srate); - - switch (state->delsys) { - case STV090x_DVBS1: - case STV090x_DSS: - if (state->search_mode == STV090x_SEARCH_AUTO) { - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); - STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - } - reg = STV090x_READ_DEMOD(state, DEMOD); - STV090x_SETFIELD_Px(reg, ROLLOFF_CONTROL_FIELD, state->rolloff); - STV090x_SETFIELD_Px(reg, MANUAL_SXROLLOFF_FIELD, 0x01); - if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) - goto err; - - if (state->internal->dev_ver >= 0x30) { - if (stv090x_get_viterbi(state) < 0) - goto err; - - if (state->fec == STV090x_PR12) { - if (STV090x_WRITE_DEMOD(state, GAUSSR0, 0x98) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CCIR0, 0x18) < 0) - goto err; - } else { - if (STV090x_WRITE_DEMOD(state, GAUSSR0, 0x18) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CCIR0, 0x18) < 0) - goto err; - } - } - - if (STV090x_WRITE_DEMOD(state, ERRCTRL1, 0x75) < 0) - goto err; - break; - - case STV090x_DVBS2: - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 0); - STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - if (state->internal->dev_ver >= 0x30) { - if (STV090x_WRITE_DEMOD(state, ACLC, 0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, BCLC, 0) < 0) - goto err; - } - if (state->frame_len == STV090x_LONG_FRAME) { - reg = STV090x_READ_DEMOD(state, DMDMODCOD); - modcod = STV090x_GETFIELD_Px(reg, DEMOD_MODCOD_FIELD); - pilots = STV090x_GETFIELD_Px(reg, DEMOD_TYPE_FIELD) & 0x01; - aclc = stv090x_optimize_carloop(state, modcod, pilots); - if (modcod <= STV090x_QPSK_910) { - STV090x_WRITE_DEMOD(state, ACLC2S2Q, aclc); - } else if (modcod <= STV090x_8PSK_910) { - if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, ACLC2S28, aclc) < 0) - goto err; - } - if ((state->demod_mode == STV090x_SINGLE) && (modcod > STV090x_8PSK_910)) { - if (modcod <= STV090x_16APSK_910) { - if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, ACLC2S216A, aclc) < 0) - goto err; - } else { - if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, ACLC2S232A, aclc) < 0) - goto err; - } - } - } else { - /*Carrier loop setting for short frame*/ - aclc = stv090x_optimize_carloop_short(state); - if (state->modulation == STV090x_QPSK) { - if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, aclc) < 0) - goto err; - } else if (state->modulation == STV090x_8PSK) { - if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, ACLC2S28, aclc) < 0) - goto err; - } else if (state->modulation == STV090x_16APSK) { - if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, ACLC2S216A, aclc) < 0) - goto err; - } else if (state->modulation == STV090x_32APSK) { - if (STV090x_WRITE_DEMOD(state, ACLC2S2Q, 0x2a) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, ACLC2S232A, aclc) < 0) - goto err; - } - } - - STV090x_WRITE_DEMOD(state, ERRCTRL1, 0x67); /* PER */ - break; - - case STV090x_ERROR: - default: - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, DVBS1_ENABLE_FIELD, 1); - STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - break; - } - - f_1 = STV090x_READ_DEMOD(state, CFR2); - f_0 = STV090x_READ_DEMOD(state, CFR1); - reg = STV090x_READ_DEMOD(state, TMGOBS); - - if (state->algo == STV090x_BLIND_SEARCH) { - STV090x_WRITE_DEMOD(state, SFRSTEP, 0x00); - reg = STV090x_READ_DEMOD(state, DMDCFGMD); - STV090x_SETFIELD_Px(reg, SCAN_ENABLE_FIELD, 0x00); - STV090x_SETFIELD_Px(reg, CFR_AUTOSCAN_FIELD, 0x00); - if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc1) < 0) - goto err; - - if (stv090x_set_srate(state, srate) < 0) - goto err; - blind_tune = 1; - - if (stv090x_dvbs_track_crl(state) < 0) - goto err; - } - - if (state->internal->dev_ver >= 0x20) { - if ((state->search_mode == STV090x_SEARCH_DVBS1) || - (state->search_mode == STV090x_SEARCH_DSS) || - (state->search_mode == STV090x_SEARCH_AUTO)) { - - if (STV090x_WRITE_DEMOD(state, VAVSRVIT, 0x0a) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, VITSCALE, 0x00) < 0) - goto err; - } - } - - if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) - goto err; - - /* AUTO tracking MODE */ - if (STV090x_WRITE_DEMOD(state, SFRUP1, 0x80) < 0) - goto err; - /* AUTO tracking MODE */ - if (STV090x_WRITE_DEMOD(state, SFRLOW1, 0x80) < 0) - goto err; - - if ((state->internal->dev_ver >= 0x20) || (blind_tune == 1) || - (state->srate < 10000000)) { - /* update initial carrier freq with the found freq offset */ - if (STV090x_WRITE_DEMOD(state, CFRINIT1, f_1) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, f_0) < 0) - goto err; - state->tuner_bw = stv090x_car_width(srate, state->rolloff) + 10000000; - - if ((state->internal->dev_ver >= 0x20) || (blind_tune == 1)) { - - if (state->algo != STV090x_WARM_SEARCH) { - - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (state->config->tuner_set_bandwidth) { - if (state->config->tuner_set_bandwidth(fe, state->tuner_bw) < 0) - goto err_gateoff; - } - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - } - } - if ((state->algo == STV090x_BLIND_SEARCH) || (state->srate < 10000000)) - msleep(50); /* blind search: wait 50ms for SR stabilization */ - else - msleep(5); - - stv090x_get_lock_tmg(state); - - if (!(stv090x_get_dmdlock(state, (state->DemodTimeout / 2)))) { - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT1, f_1) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, f_0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) - goto err; - - i = 0; - - while ((!(stv090x_get_dmdlock(state, (state->DemodTimeout / 2)))) && (i <= 2)) { - - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x1f) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT1, f_1) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, CFRINIT0, f_0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x18) < 0) - goto err; - i++; - } - } - - } - - if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, CARFREQ, 0x49) < 0) - goto err; - } - - if ((state->delsys == STV090x_DVBS1) || (state->delsys == STV090x_DSS)) - stv090x_set_vit_thtracq(state); - - return 0; - -err_gateoff: - stv090x_i2c_gate_ctrl(state, 0); -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_get_feclock(struct stv090x_state *state, s32 timeout) -{ - s32 timer = 0, lock = 0, stat; - u32 reg; - - while ((timer < timeout) && (!lock)) { - reg = STV090x_READ_DEMOD(state, DMDSTATE); - stat = STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD); - - switch (stat) { - case 0: /* searching */ - case 1: /* first PLH detected */ - default: - lock = 0; - break; - - case 2: /* DVB-S2 mode */ - reg = STV090x_READ_DEMOD(state, PDELSTATUS1); - lock = STV090x_GETFIELD_Px(reg, PKTDELIN_LOCK_FIELD); - break; - - case 3: /* DVB-S1/legacy mode */ - reg = STV090x_READ_DEMOD(state, VSTATUSVIT); - lock = STV090x_GETFIELD_Px(reg, LOCKEDVIT_FIELD); - break; - } - if (!lock) { - msleep(10); - timer += 10; - } - } - return lock; -} - -static int stv090x_get_lock(struct stv090x_state *state, s32 timeout_dmd, s32 timeout_fec) -{ - u32 reg; - s32 timer = 0; - int lock; - - lock = stv090x_get_dmdlock(state, timeout_dmd); - if (lock) - lock = stv090x_get_feclock(state, timeout_fec); - - if (lock) { - lock = 0; - - while ((timer < timeout_fec) && (!lock)) { - reg = STV090x_READ_DEMOD(state, TSSTATUS); - lock = STV090x_GETFIELD_Px(reg, TSFIFO_LINEOK_FIELD); - msleep(1); - timer++; - } - } - - return lock; -} - -static int stv090x_set_s2rolloff(struct stv090x_state *state) -{ - u32 reg; - - if (state->internal->dev_ver <= 0x20) { - /* rolloff to auto mode if DVBS2 */ - reg = STV090x_READ_DEMOD(state, DEMOD); - STV090x_SETFIELD_Px(reg, MANUAL_SXROLLOFF_FIELD, 0x00); - if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) - goto err; - } else { - /* DVB-S2 rolloff to auto mode if DVBS2 */ - reg = STV090x_READ_DEMOD(state, DEMOD); - STV090x_SETFIELD_Px(reg, MANUAL_S2ROLLOFF_FIELD, 0x00); - if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) - goto err; - } - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - - -static enum stv090x_signal_state stv090x_algo(struct stv090x_state *state) -{ - struct dvb_frontend *fe = &state->frontend; - enum stv090x_signal_state signal_state = STV090x_NOCARRIER; - u32 reg; - s32 agc1_power, power_iq = 0, i; - int lock = 0, low_sr = 0; - - reg = STV090x_READ_DEMOD(state, TSCFGH); - STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 1); /* Stop path 1 stream merger */ - if (STV090x_WRITE_DEMOD(state, TSCFGH, reg) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, DMDISTATE, 0x5c) < 0) /* Demod stop */ - goto err; - - if (state->internal->dev_ver >= 0x20) { - if (state->srate > 5000000) { - if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x9e) < 0) - goto err; - } else { - if (STV090x_WRITE_DEMOD(state, CORRELABS, 0x82) < 0) - goto err; - } - } - - stv090x_get_lock_tmg(state); - - if (state->algo == STV090x_BLIND_SEARCH) { - state->tuner_bw = 2 * 36000000; /* wide bw for unknown srate */ - if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc0) < 0) /* wider srate scan */ - goto err; - if (STV090x_WRITE_DEMOD(state, CORRELMANT, 0x70) < 0) - goto err; - if (stv090x_set_srate(state, 1000000) < 0) /* initial srate = 1Msps */ - goto err; - } else { - /* known srate */ - if (STV090x_WRITE_DEMOD(state, DMDTOM, 0x20) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, TMGCFG, 0xd2) < 0) - goto err; - - if (state->srate < 2000000) { - /* SR < 2MSPS */ - if (STV090x_WRITE_DEMOD(state, CORRELMANT, 0x63) < 0) - goto err; - } else { - /* SR >= 2Msps */ - if (STV090x_WRITE_DEMOD(state, CORRELMANT, 0x70) < 0) - goto err; - } - - if (STV090x_WRITE_DEMOD(state, AGC2REF, 0x38) < 0) - goto err; - - if (state->internal->dev_ver >= 0x20) { - if (STV090x_WRITE_DEMOD(state, KREFTMG, 0x5a) < 0) - goto err; - if (state->algo == STV090x_COLD_SEARCH) - state->tuner_bw = (15 * (stv090x_car_width(state->srate, state->rolloff) + 10000000)) / 10; - else if (state->algo == STV090x_WARM_SEARCH) - state->tuner_bw = stv090x_car_width(state->srate, state->rolloff) + 10000000; - } - - /* if cold start or warm (Symbolrate is known) - * use a Narrow symbol rate scan range - */ - if (STV090x_WRITE_DEMOD(state, TMGCFG2, 0xc1) < 0) /* narrow srate scan */ - goto err; - - if (stv090x_set_srate(state, state->srate) < 0) - goto err; - - if (stv090x_set_max_srate(state, state->internal->mclk, - state->srate) < 0) - goto err; - if (stv090x_set_min_srate(state, state->internal->mclk, - state->srate) < 0) - goto err; - - if (state->srate >= 10000000) - low_sr = 0; - else - low_sr = 1; - } - - /* Setup tuner */ - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (state->config->tuner_set_bbgain) { - reg = state->config->tuner_bbgain; - if (reg == 0) - reg = 10; /* default: 10dB */ - if (state->config->tuner_set_bbgain(fe, reg) < 0) - goto err_gateoff; - } - - if (state->config->tuner_set_frequency) { - if (state->config->tuner_set_frequency(fe, state->frequency) < 0) - goto err_gateoff; - } - - if (state->config->tuner_set_bandwidth) { - if (state->config->tuner_set_bandwidth(fe, state->tuner_bw) < 0) - goto err_gateoff; - } - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - msleep(50); - - if (state->config->tuner_get_status) { - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - if (state->config->tuner_get_status(fe, ®) < 0) - goto err_gateoff; - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - if (reg) - dprintk(FE_DEBUG, 1, "Tuner phase locked"); - else { - dprintk(FE_DEBUG, 1, "Tuner unlocked"); - return STV090x_NOCARRIER; - } - } - - msleep(10); - agc1_power = MAKEWORD16(STV090x_READ_DEMOD(state, AGCIQIN1), - STV090x_READ_DEMOD(state, AGCIQIN0)); - - if (agc1_power == 0) { - /* If AGC1 integrator value is 0 - * then read POWERI, POWERQ - */ - for (i = 0; i < 5; i++) { - power_iq += (STV090x_READ_DEMOD(state, POWERI) + - STV090x_READ_DEMOD(state, POWERQ)) >> 1; - } - power_iq /= 5; - } - - if ((agc1_power == 0) && (power_iq < STV090x_IQPOWER_THRESHOLD)) { - dprintk(FE_ERROR, 1, "No Signal: POWER_IQ=0x%02x", power_iq); - lock = 0; - signal_state = STV090x_NOAGC1; - } else { - reg = STV090x_READ_DEMOD(state, DEMOD); - STV090x_SETFIELD_Px(reg, SPECINV_CONTROL_FIELD, state->inversion); - - if (state->internal->dev_ver <= 0x20) { - /* rolloff to auto mode if DVBS2 */ - STV090x_SETFIELD_Px(reg, MANUAL_SXROLLOFF_FIELD, 1); - } else { - /* DVB-S2 rolloff to auto mode if DVBS2 */ - STV090x_SETFIELD_Px(reg, MANUAL_S2ROLLOFF_FIELD, 1); - } - if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) - goto err; - - if (stv090x_delivery_search(state) < 0) - goto err; - - if (state->algo != STV090x_BLIND_SEARCH) { - if (stv090x_start_search(state) < 0) - goto err; - } - } - - if (signal_state == STV090x_NOAGC1) - return signal_state; - - if (state->algo == STV090x_BLIND_SEARCH) - lock = stv090x_blind_search(state); - - else if (state->algo == STV090x_COLD_SEARCH) - lock = stv090x_get_coldlock(state, state->DemodTimeout); - - else if (state->algo == STV090x_WARM_SEARCH) - lock = stv090x_get_dmdlock(state, state->DemodTimeout); - - if ((!lock) && (state->algo == STV090x_COLD_SEARCH)) { - if (!low_sr) { - if (stv090x_chk_tmg(state)) - lock = stv090x_sw_algo(state); - } - } - - if (lock) - signal_state = stv090x_get_sig_params(state); - - if ((lock) && (signal_state == STV090x_RANGEOK)) { /* signal within Range */ - stv090x_optimize_track(state); - - if (state->internal->dev_ver >= 0x20) { - /* >= Cut 2.0 :release TS reset after - * demod lock and optimized Tracking - */ - reg = STV090x_READ_DEMOD(state, TSCFGH); - STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0); /* release merger reset */ - if (STV090x_WRITE_DEMOD(state, TSCFGH, reg) < 0) - goto err; - - msleep(3); - - STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 1); /* merger reset */ - if (STV090x_WRITE_DEMOD(state, TSCFGH, reg) < 0) - goto err; - - STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0); /* release merger reset */ - if (STV090x_WRITE_DEMOD(state, TSCFGH, reg) < 0) - goto err; - } - - lock = stv090x_get_lock(state, state->FecTimeout, - state->FecTimeout); - if (lock) { - if (state->delsys == STV090x_DVBS2) { - stv090x_set_s2rolloff(state); - - reg = STV090x_READ_DEMOD(state, PDELCTRL2); - STV090x_SETFIELD_Px(reg, RESET_UPKO_COUNT, 1); - if (STV090x_WRITE_DEMOD(state, PDELCTRL2, reg) < 0) - goto err; - /* Reset DVBS2 packet delinator error counter */ - reg = STV090x_READ_DEMOD(state, PDELCTRL2); - STV090x_SETFIELD_Px(reg, RESET_UPKO_COUNT, 0); - if (STV090x_WRITE_DEMOD(state, PDELCTRL2, reg) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, ERRCTRL1, 0x67) < 0) /* PER */ - goto err; - } else { - if (STV090x_WRITE_DEMOD(state, ERRCTRL1, 0x75) < 0) - goto err; - } - /* Reset the Total packet counter */ - if (STV090x_WRITE_DEMOD(state, FBERCPT4, 0x00) < 0) - goto err; - /* Reset the packet Error counter2 */ - if (STV090x_WRITE_DEMOD(state, ERRCTRL2, 0xc1) < 0) - goto err; - } else { - signal_state = STV090x_NODATA; - stv090x_chk_signal(state); - } - } - return signal_state; - -err_gateoff: - stv090x_i2c_gate_ctrl(state, 0); -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static enum dvbfe_search stv090x_search(struct dvb_frontend *fe) -{ - struct stv090x_state *state = fe->demodulator_priv; - struct dtv_frontend_properties *props = &fe->dtv_property_cache; - - if (props->frequency == 0) - return DVBFE_ALGO_SEARCH_INVALID; - - state->delsys = props->delivery_system; - state->frequency = props->frequency; - state->srate = props->symbol_rate; - state->search_mode = STV090x_SEARCH_AUTO; - state->algo = STV090x_COLD_SEARCH; - state->fec = STV090x_PRERR; - if (state->srate > 10000000) { - dprintk(FE_DEBUG, 1, "Search range: 10 MHz"); - state->search_range = 10000000; - } else { - dprintk(FE_DEBUG, 1, "Search range: 5 MHz"); - state->search_range = 5000000; - } - - if (stv090x_algo(state) == STV090x_RANGEOK) { - dprintk(FE_DEBUG, 1, "Search success!"); - return DVBFE_ALGO_SEARCH_SUCCESS; - } else { - dprintk(FE_DEBUG, 1, "Search failed!"); - return DVBFE_ALGO_SEARCH_FAILED; - } - - return DVBFE_ALGO_SEARCH_ERROR; -} - -static int stv090x_read_status(struct dvb_frontend *fe, enum fe_status *status) -{ - struct stv090x_state *state = fe->demodulator_priv; - u32 reg, dstatus; - u8 search_state; - - *status = 0; - - dstatus = STV090x_READ_DEMOD(state, DSTATUS); - if (STV090x_GETFIELD_Px(dstatus, CAR_LOCK_FIELD)) - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER; - - reg = STV090x_READ_DEMOD(state, DMDSTATE); - search_state = STV090x_GETFIELD_Px(reg, HEADER_MODE_FIELD); - - switch (search_state) { - case 0: /* searching */ - case 1: /* first PLH detected */ - default: - dprintk(FE_DEBUG, 1, "Status: Unlocked (Searching ..)"); - break; - - case 2: /* DVB-S2 mode */ - dprintk(FE_DEBUG, 1, "Delivery system: DVB-S2"); - if (STV090x_GETFIELD_Px(dstatus, LOCK_DEFINITIF_FIELD)) { - reg = STV090x_READ_DEMOD(state, PDELSTATUS1); - if (STV090x_GETFIELD_Px(reg, PKTDELIN_LOCK_FIELD)) { - *status |= FE_HAS_VITERBI; - reg = STV090x_READ_DEMOD(state, TSSTATUS); - if (STV090x_GETFIELD_Px(reg, TSFIFO_LINEOK_FIELD)) - *status |= FE_HAS_SYNC | FE_HAS_LOCK; - } - } - break; - - case 3: /* DVB-S1/legacy mode */ - dprintk(FE_DEBUG, 1, "Delivery system: DVB-S"); - if (STV090x_GETFIELD_Px(dstatus, LOCK_DEFINITIF_FIELD)) { - reg = STV090x_READ_DEMOD(state, VSTATUSVIT); - if (STV090x_GETFIELD_Px(reg, LOCKEDVIT_FIELD)) { - *status |= FE_HAS_VITERBI; - reg = STV090x_READ_DEMOD(state, TSSTATUS); - if (STV090x_GETFIELD_Px(reg, TSFIFO_LINEOK_FIELD)) - *status |= FE_HAS_SYNC | FE_HAS_LOCK; - } - } - break; - } - - return 0; -} - -static int stv090x_read_per(struct dvb_frontend *fe, u32 *per) -{ - struct stv090x_state *state = fe->demodulator_priv; - - s32 count_4, count_3, count_2, count_1, count_0, count; - u32 reg, h, m, l; - enum fe_status status; - - stv090x_read_status(fe, &status); - if (!(status & FE_HAS_LOCK)) { - *per = 1 << 23; /* Max PER */ - } else { - /* Counter 2 */ - reg = STV090x_READ_DEMOD(state, ERRCNT22); - h = STV090x_GETFIELD_Px(reg, ERR_CNT2_FIELD); - - reg = STV090x_READ_DEMOD(state, ERRCNT21); - m = STV090x_GETFIELD_Px(reg, ERR_CNT21_FIELD); - - reg = STV090x_READ_DEMOD(state, ERRCNT20); - l = STV090x_GETFIELD_Px(reg, ERR_CNT20_FIELD); - - *per = ((h << 16) | (m << 8) | l); - - count_4 = STV090x_READ_DEMOD(state, FBERCPT4); - count_3 = STV090x_READ_DEMOD(state, FBERCPT3); - count_2 = STV090x_READ_DEMOD(state, FBERCPT2); - count_1 = STV090x_READ_DEMOD(state, FBERCPT1); - count_0 = STV090x_READ_DEMOD(state, FBERCPT0); - - if ((!count_4) && (!count_3)) { - count = (count_2 & 0xff) << 16; - count |= (count_1 & 0xff) << 8; - count |= count_0 & 0xff; - } else { - count = 1 << 24; - } - if (count == 0) - *per = 1; - } - if (STV090x_WRITE_DEMOD(state, FBERCPT4, 0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, ERRCTRL2, 0xc1) < 0) - goto err; - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_table_lookup(const struct stv090x_tab *tab, int max, int val) -{ - int res = 0; - int min = 0, med; - - if ((val >= tab[min].read && val < tab[max].read) || - (val >= tab[max].read && val < tab[min].read)) { - while ((max - min) > 1) { - med = (max + min) / 2; - if ((val >= tab[min].read && val < tab[med].read) || - (val >= tab[med].read && val < tab[min].read)) - max = med; - else - min = med; - } - res = ((val - tab[min].read) * - (tab[max].real - tab[min].real) / - (tab[max].read - tab[min].read)) + - tab[min].real; - } else { - if (tab[min].read < tab[max].read) { - if (val < tab[min].read) - res = tab[min].real; - else if (val >= tab[max].read) - res = tab[max].real; - } else { - if (val >= tab[min].read) - res = tab[min].real; - else if (val < tab[max].read) - res = tab[max].real; - } - } - - return res; -} - -static int stv090x_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - struct stv090x_state *state = fe->demodulator_priv; - u32 reg; - s32 agc_0, agc_1, agc; - s32 str; - - reg = STV090x_READ_DEMOD(state, AGCIQIN1); - agc_1 = STV090x_GETFIELD_Px(reg, AGCIQ_VALUE_FIELD); - reg = STV090x_READ_DEMOD(state, AGCIQIN0); - agc_0 = STV090x_GETFIELD_Px(reg, AGCIQ_VALUE_FIELD); - agc = MAKEWORD16(agc_1, agc_0); - - str = stv090x_table_lookup(stv090x_rf_tab, - ARRAY_SIZE(stv090x_rf_tab) - 1, agc); - if (agc > stv090x_rf_tab[0].read) - str = 0; - else if (agc < stv090x_rf_tab[ARRAY_SIZE(stv090x_rf_tab) - 1].read) - str = -100; - *strength = (str + 100) * 0xFFFF / 100; - - return 0; -} - -static int stv090x_read_cnr(struct dvb_frontend *fe, u16 *cnr) -{ - struct stv090x_state *state = fe->demodulator_priv; - u32 reg_0, reg_1, reg, i; - s32 val_0, val_1, val = 0; - u8 lock_f; - s32 div; - u32 last; - - switch (state->delsys) { - case STV090x_DVBS2: - reg = STV090x_READ_DEMOD(state, DSTATUS); - lock_f = STV090x_GETFIELD_Px(reg, LOCK_DEFINITIF_FIELD); - if (lock_f) { - msleep(5); - for (i = 0; i < 16; i++) { - reg_1 = STV090x_READ_DEMOD(state, NNOSPLHT1); - val_1 = STV090x_GETFIELD_Px(reg_1, NOSPLHT_NORMED_FIELD); - reg_0 = STV090x_READ_DEMOD(state, NNOSPLHT0); - val_0 = STV090x_GETFIELD_Px(reg_0, NOSPLHT_NORMED_FIELD); - val += MAKEWORD16(val_1, val_0); - msleep(1); - } - val /= 16; - last = ARRAY_SIZE(stv090x_s2cn_tab) - 1; - div = stv090x_s2cn_tab[0].read - - stv090x_s2cn_tab[last].read; - *cnr = 0xFFFF - ((val * 0xFFFF) / div); - } - break; - - case STV090x_DVBS1: - case STV090x_DSS: - reg = STV090x_READ_DEMOD(state, DSTATUS); - lock_f = STV090x_GETFIELD_Px(reg, LOCK_DEFINITIF_FIELD); - if (lock_f) { - msleep(5); - for (i = 0; i < 16; i++) { - reg_1 = STV090x_READ_DEMOD(state, NOSDATAT1); - val_1 = STV090x_GETFIELD_Px(reg_1, NOSDATAT_UNNORMED_FIELD); - reg_0 = STV090x_READ_DEMOD(state, NOSDATAT0); - val_0 = STV090x_GETFIELD_Px(reg_0, NOSDATAT_UNNORMED_FIELD); - val += MAKEWORD16(val_1, val_0); - msleep(1); - } - val /= 16; - last = ARRAY_SIZE(stv090x_s1cn_tab) - 1; - div = stv090x_s1cn_tab[0].read - - stv090x_s1cn_tab[last].read; - *cnr = 0xFFFF - ((val * 0xFFFF) / div); - } - break; - default: - break; - } - - return 0; -} - -static int stv090x_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) -{ - struct stv090x_state *state = fe->demodulator_priv; - u32 reg; - - reg = STV090x_READ_DEMOD(state, DISTXCTL); - switch (tone) { - case SEC_TONE_ON: - STV090x_SETFIELD_Px(reg, DISTX_MODE_FIELD, 0); - STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - break; - - case SEC_TONE_OFF: - STV090x_SETFIELD_Px(reg, DISTX_MODE_FIELD, 0); - STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - break; - default: - return -EINVAL; - } - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - - -static enum dvbfe_algo stv090x_frontend_algo(struct dvb_frontend *fe) -{ - return DVBFE_ALGO_CUSTOM; -} - -static int stv090x_send_diseqc_msg(struct dvb_frontend *fe, struct dvb_diseqc_master_cmd *cmd) -{ - struct stv090x_state *state = fe->demodulator_priv; - u32 reg, idle = 0, fifo_full = 1; - int i; - - reg = STV090x_READ_DEMOD(state, DISTXCTL); - - STV090x_SETFIELD_Px(reg, DISTX_MODE_FIELD, - (state->config->diseqc_envelope_mode) ? 4 : 2); - STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - - STV090x_SETFIELD_Px(reg, DIS_PRECHARGE_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - - for (i = 0; i < cmd->msg_len; i++) { - - while (fifo_full) { - reg = STV090x_READ_DEMOD(state, DISTXSTATUS); - fifo_full = STV090x_GETFIELD_Px(reg, FIFO_FULL_FIELD); - } - - if (STV090x_WRITE_DEMOD(state, DISTXDATA, cmd->msg[i]) < 0) - goto err; - } - reg = STV090x_READ_DEMOD(state, DISTXCTL); - STV090x_SETFIELD_Px(reg, DIS_PRECHARGE_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - - i = 0; - - while ((!idle) && (i < 10)) { - reg = STV090x_READ_DEMOD(state, DISTXSTATUS); - idle = STV090x_GETFIELD_Px(reg, TX_IDLE_FIELD); - msleep(10); - i++; - } - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_send_diseqc_burst(struct dvb_frontend *fe, fe_sec_mini_cmd_t burst) -{ - struct stv090x_state *state = fe->demodulator_priv; - u32 reg, idle = 0, fifo_full = 1; - u8 mode, value; - int i; - - reg = STV090x_READ_DEMOD(state, DISTXCTL); - - if (burst == SEC_MINI_A) { - mode = (state->config->diseqc_envelope_mode) ? 5 : 3; - value = 0x00; - } else { - mode = (state->config->diseqc_envelope_mode) ? 4 : 2; - value = 0xFF; - } - - STV090x_SETFIELD_Px(reg, DISTX_MODE_FIELD, mode); - STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - STV090x_SETFIELD_Px(reg, DISEQC_RESET_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - - STV090x_SETFIELD_Px(reg, DIS_PRECHARGE_FIELD, 1); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - - while (fifo_full) { - reg = STV090x_READ_DEMOD(state, DISTXSTATUS); - fifo_full = STV090x_GETFIELD_Px(reg, FIFO_FULL_FIELD); - } - - if (STV090x_WRITE_DEMOD(state, DISTXDATA, value) < 0) - goto err; - - reg = STV090x_READ_DEMOD(state, DISTXCTL); - STV090x_SETFIELD_Px(reg, DIS_PRECHARGE_FIELD, 0); - if (STV090x_WRITE_DEMOD(state, DISTXCTL, reg) < 0) - goto err; - - i = 0; - - while ((!idle) && (i < 10)) { - reg = STV090x_READ_DEMOD(state, DISTXSTATUS); - idle = STV090x_GETFIELD_Px(reg, TX_IDLE_FIELD); - msleep(10); - i++; - } - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_recv_slave_reply(struct dvb_frontend *fe, struct dvb_diseqc_slave_reply *reply) -{ - struct stv090x_state *state = fe->demodulator_priv; - u32 reg = 0, i = 0, rx_end = 0; - - while ((rx_end != 1) && (i < 10)) { - msleep(10); - i++; - reg = STV090x_READ_DEMOD(state, DISRX_ST0); - rx_end = STV090x_GETFIELD_Px(reg, RX_END_FIELD); - } - - if (rx_end) { - reply->msg_len = STV090x_GETFIELD_Px(reg, FIFO_BYTENBR_FIELD); - for (i = 0; i < reply->msg_len; i++) - reply->msg[i] = STV090x_READ_DEMOD(state, DISRXDATA); - } - - return 0; -} - -static int stv090x_sleep(struct dvb_frontend *fe) -{ - struct stv090x_state *state = fe->demodulator_priv; - u32 reg; - u8 full_standby = 0; - - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (state->config->tuner_sleep) { - if (state->config->tuner_sleep(fe) < 0) - goto err_gateoff; - } - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - dprintk(FE_DEBUG, 1, "Set %s(%d) to sleep", - state->device == STV0900 ? "STV0900" : "STV0903", - state->demod); - - mutex_lock(&state->internal->demod_lock); - - switch (state->demod) { - case STV090x_DEMODULATOR_0: - /* power off ADC 1 */ - reg = stv090x_read_reg(state, STV090x_TSTTNR1); - STV090x_SETFIELD(reg, ADC1_PON_FIELD, 0); - if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) - goto err; - /* power off DiSEqC 1 */ - reg = stv090x_read_reg(state, STV090x_TSTTNR2); - STV090x_SETFIELD(reg, DISEQC1_PON_FIELD, 0); - if (stv090x_write_reg(state, STV090x_TSTTNR2, reg) < 0) - goto err; - - /* check whether path 2 is already sleeping, that is when - ADC2 is off */ - reg = stv090x_read_reg(state, STV090x_TSTTNR3); - if (STV090x_GETFIELD(reg, ADC2_PON_FIELD) == 0) - full_standby = 1; - - /* stop clocks */ - reg = stv090x_read_reg(state, STV090x_STOPCLK1); - /* packet delineator 1 clock */ - STV090x_SETFIELD(reg, STOP_CLKPKDT1_FIELD, 1); - /* ADC 1 clock */ - STV090x_SETFIELD(reg, STOP_CLKADCI1_FIELD, 1); - /* FEC clock is shared between the two paths, only stop it - when full standby is possible */ - if (full_standby) - STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 1); - if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) - goto err; - reg = stv090x_read_reg(state, STV090x_STOPCLK2); - /* sampling 1 clock */ - STV090x_SETFIELD(reg, STOP_CLKSAMP1_FIELD, 1); - /* viterbi 1 clock */ - STV090x_SETFIELD(reg, STOP_CLKVIT1_FIELD, 1); - /* TS clock is shared between the two paths, only stop it - when full standby is possible */ - if (full_standby) - STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 1); - if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) - goto err; - break; - - case STV090x_DEMODULATOR_1: - /* power off ADC 2 */ - reg = stv090x_read_reg(state, STV090x_TSTTNR3); - STV090x_SETFIELD(reg, ADC2_PON_FIELD, 0); - if (stv090x_write_reg(state, STV090x_TSTTNR3, reg) < 0) - goto err; - /* power off DiSEqC 2 */ - reg = stv090x_read_reg(state, STV090x_TSTTNR4); - STV090x_SETFIELD(reg, DISEQC2_PON_FIELD, 0); - if (stv090x_write_reg(state, STV090x_TSTTNR4, reg) < 0) - goto err; - - /* check whether path 1 is already sleeping, that is when - ADC1 is off */ - reg = stv090x_read_reg(state, STV090x_TSTTNR1); - if (STV090x_GETFIELD(reg, ADC1_PON_FIELD) == 0) - full_standby = 1; - - /* stop clocks */ - reg = stv090x_read_reg(state, STV090x_STOPCLK1); - /* packet delineator 2 clock */ - STV090x_SETFIELD(reg, STOP_CLKPKDT2_FIELD, 1); - /* ADC 2 clock */ - STV090x_SETFIELD(reg, STOP_CLKADCI2_FIELD, 1); - /* FEC clock is shared between the two paths, only stop it - when full standby is possible */ - if (full_standby) - STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 1); - if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) - goto err; - reg = stv090x_read_reg(state, STV090x_STOPCLK2); - /* sampling 2 clock */ - STV090x_SETFIELD(reg, STOP_CLKSAMP2_FIELD, 1); - /* viterbi 2 clock */ - STV090x_SETFIELD(reg, STOP_CLKVIT2_FIELD, 1); - /* TS clock is shared between the two paths, only stop it - when full standby is possible */ - if (full_standby) - STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 1); - if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) - goto err; - break; - - default: - dprintk(FE_ERROR, 1, "Wrong demodulator!"); - break; - } - - if (full_standby) { - /* general power off */ - reg = stv090x_read_reg(state, STV090x_SYNTCTRL); - STV090x_SETFIELD(reg, STANDBY_FIELD, 0x01); - if (stv090x_write_reg(state, STV090x_SYNTCTRL, reg) < 0) - goto err; - } - - mutex_unlock(&state->internal->demod_lock); - return 0; - -err_gateoff: - stv090x_i2c_gate_ctrl(state, 0); -err: - mutex_unlock(&state->internal->demod_lock); - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_wakeup(struct dvb_frontend *fe) -{ - struct stv090x_state *state = fe->demodulator_priv; - u32 reg; - - dprintk(FE_DEBUG, 1, "Wake %s(%d) from standby", - state->device == STV0900 ? "STV0900" : "STV0903", - state->demod); - - mutex_lock(&state->internal->demod_lock); - - /* general power on */ - reg = stv090x_read_reg(state, STV090x_SYNTCTRL); - STV090x_SETFIELD(reg, STANDBY_FIELD, 0x00); - if (stv090x_write_reg(state, STV090x_SYNTCTRL, reg) < 0) - goto err; - - switch (state->demod) { - case STV090x_DEMODULATOR_0: - /* power on ADC 1 */ - reg = stv090x_read_reg(state, STV090x_TSTTNR1); - STV090x_SETFIELD(reg, ADC1_PON_FIELD, 1); - if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) - goto err; - /* power on DiSEqC 1 */ - reg = stv090x_read_reg(state, STV090x_TSTTNR2); - STV090x_SETFIELD(reg, DISEQC1_PON_FIELD, 1); - if (stv090x_write_reg(state, STV090x_TSTTNR2, reg) < 0) - goto err; - - /* activate clocks */ - reg = stv090x_read_reg(state, STV090x_STOPCLK1); - /* packet delineator 1 clock */ - STV090x_SETFIELD(reg, STOP_CLKPKDT1_FIELD, 0); - /* ADC 1 clock */ - STV090x_SETFIELD(reg, STOP_CLKADCI1_FIELD, 0); - /* FEC clock */ - STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 0); - if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) - goto err; - reg = stv090x_read_reg(state, STV090x_STOPCLK2); - /* sampling 1 clock */ - STV090x_SETFIELD(reg, STOP_CLKSAMP1_FIELD, 0); - /* viterbi 1 clock */ - STV090x_SETFIELD(reg, STOP_CLKVIT1_FIELD, 0); - /* TS clock */ - STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 0); - if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) - goto err; - break; - - case STV090x_DEMODULATOR_1: - /* power on ADC 2 */ - reg = stv090x_read_reg(state, STV090x_TSTTNR3); - STV090x_SETFIELD(reg, ADC2_PON_FIELD, 1); - if (stv090x_write_reg(state, STV090x_TSTTNR3, reg) < 0) - goto err; - /* power on DiSEqC 2 */ - reg = stv090x_read_reg(state, STV090x_TSTTNR4); - STV090x_SETFIELD(reg, DISEQC2_PON_FIELD, 1); - if (stv090x_write_reg(state, STV090x_TSTTNR4, reg) < 0) - goto err; - - /* activate clocks */ - reg = stv090x_read_reg(state, STV090x_STOPCLK1); - /* packet delineator 2 clock */ - STV090x_SETFIELD(reg, STOP_CLKPKDT2_FIELD, 0); - /* ADC 2 clock */ - STV090x_SETFIELD(reg, STOP_CLKADCI2_FIELD, 0); - /* FEC clock */ - STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 0); - if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) - goto err; - reg = stv090x_read_reg(state, STV090x_STOPCLK2); - /* sampling 2 clock */ - STV090x_SETFIELD(reg, STOP_CLKSAMP2_FIELD, 0); - /* viterbi 2 clock */ - STV090x_SETFIELD(reg, STOP_CLKVIT2_FIELD, 0); - /* TS clock */ - STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 0); - if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) - goto err; - break; - - default: - dprintk(FE_ERROR, 1, "Wrong demodulator!"); - break; - } - - mutex_unlock(&state->internal->demod_lock); - return 0; -err: - mutex_unlock(&state->internal->demod_lock); - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static void stv090x_release(struct dvb_frontend *fe) -{ - struct stv090x_state *state = fe->demodulator_priv; - - state->internal->num_used--; - if (state->internal->num_used <= 0) { - - dprintk(FE_ERROR, 1, "Actually removing"); - - remove_dev(state->internal); - kfree(state->internal); - } - - kfree(state); -} - -static int stv090x_ldpc_mode(struct stv090x_state *state, enum stv090x_mode ldpc_mode) -{ - u32 reg = 0; - - reg = stv090x_read_reg(state, STV090x_GENCFG); - - switch (ldpc_mode) { - case STV090x_DUAL: - default: - if ((state->demod_mode != STV090x_DUAL) || (STV090x_GETFIELD(reg, DDEMOD_FIELD) != 1)) { - /* set LDPC to dual mode */ - if (stv090x_write_reg(state, STV090x_GENCFG, 0x1d) < 0) - goto err; - - state->demod_mode = STV090x_DUAL; - - reg = stv090x_read_reg(state, STV090x_TSTRES0); - STV090x_SETFIELD(reg, FRESFEC_FIELD, 0x1); - if (stv090x_write_reg(state, STV090x_TSTRES0, reg) < 0) - goto err; - STV090x_SETFIELD(reg, FRESFEC_FIELD, 0x0); - if (stv090x_write_reg(state, STV090x_TSTRES0, reg) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, MODCODLST0, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST1, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST2, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST3, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST4, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST5, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST6, 0xff) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, MODCODLST7, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST8, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLST9, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTA, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTB, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTC, 0xcc) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTD, 0xcc) < 0) - goto err; - - if (STV090x_WRITE_DEMOD(state, MODCODLSTE, 0xff) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, MODCODLSTF, 0xcf) < 0) - goto err; - } - break; - - case STV090x_SINGLE: - if (stv090x_stop_modcod(state) < 0) - goto err; - if (stv090x_activate_modcod_single(state) < 0) - goto err; - - if (state->demod == STV090x_DEMODULATOR_1) { - if (stv090x_write_reg(state, STV090x_GENCFG, 0x06) < 0) /* path 2 */ - goto err; - } else { - if (stv090x_write_reg(state, STV090x_GENCFG, 0x04) < 0) /* path 1 */ - goto err; - } - - reg = stv090x_read_reg(state, STV090x_TSTRES0); - STV090x_SETFIELD(reg, FRESFEC_FIELD, 0x1); - if (stv090x_write_reg(state, STV090x_TSTRES0, reg) < 0) - goto err; - STV090x_SETFIELD(reg, FRESFEC_FIELD, 0x0); - if (stv090x_write_reg(state, STV090x_TSTRES0, reg) < 0) - goto err; - - reg = STV090x_READ_DEMOD(state, PDELCTRL1); - STV090x_SETFIELD_Px(reg, ALGOSWRST_FIELD, 0x01); - if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) - goto err; - STV090x_SETFIELD_Px(reg, ALGOSWRST_FIELD, 0x00); - if (STV090x_WRITE_DEMOD(state, PDELCTRL1, reg) < 0) - goto err; - break; - } - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -/* return (Hz), clk in Hz*/ -static u32 stv090x_get_mclk(struct stv090x_state *state) -{ - const struct stv090x_config *config = state->config; - u32 div, reg; - u8 ratio; - - div = stv090x_read_reg(state, STV090x_NCOARSE); - reg = stv090x_read_reg(state, STV090x_SYNTCTRL); - ratio = STV090x_GETFIELD(reg, SELX1RATIO_FIELD) ? 4 : 6; - - return (div + 1) * config->xtal / ratio; /* kHz */ -} - -static int stv090x_set_mclk(struct stv090x_state *state, u32 mclk, u32 clk) -{ - const struct stv090x_config *config = state->config; - u32 reg, div, clk_sel; - - reg = stv090x_read_reg(state, STV090x_SYNTCTRL); - clk_sel = ((STV090x_GETFIELD(reg, SELX1RATIO_FIELD) == 1) ? 4 : 6); - - div = ((clk_sel * mclk) / config->xtal) - 1; - - reg = stv090x_read_reg(state, STV090x_NCOARSE); - STV090x_SETFIELD(reg, M_DIV_FIELD, div); - if (stv090x_write_reg(state, STV090x_NCOARSE, reg) < 0) - goto err; - - state->internal->mclk = stv090x_get_mclk(state); - - /*Set the DiseqC frequency to 22KHz */ - div = state->internal->mclk / 704000; - if (STV090x_WRITE_DEMOD(state, F22TX, div) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, F22RX, div) < 0) - goto err; - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_set_tspath(struct stv090x_state *state) -{ - u32 reg; - - if (state->internal->dev_ver >= 0x20) { - switch (state->config->ts1_mode) { - case STV090x_TSMODE_PARALLEL_PUNCTURED: - case STV090x_TSMODE_DVBCI: - switch (state->config->ts2_mode) { - case STV090x_TSMODE_SERIAL_PUNCTURED: - case STV090x_TSMODE_SERIAL_CONTINUOUS: - default: - stv090x_write_reg(state, STV090x_TSGENERAL, 0x00); - break; - - case STV090x_TSMODE_PARALLEL_PUNCTURED: - case STV090x_TSMODE_DVBCI: - if (stv090x_write_reg(state, STV090x_TSGENERAL, 0x06) < 0) /* Mux'd stream mode */ - goto err; - reg = stv090x_read_reg(state, STV090x_P1_TSCFGM); - STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); - if (stv090x_write_reg(state, STV090x_P1_TSCFGM, reg) < 0) - goto err; - reg = stv090x_read_reg(state, STV090x_P2_TSCFGM); - STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); - if (stv090x_write_reg(state, STV090x_P2_TSCFGM, reg) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_P1_TSSPEED, 0x14) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_P2_TSSPEED, 0x28) < 0) - goto err; - break; - } - break; - - case STV090x_TSMODE_SERIAL_PUNCTURED: - case STV090x_TSMODE_SERIAL_CONTINUOUS: - default: - switch (state->config->ts2_mode) { - case STV090x_TSMODE_SERIAL_PUNCTURED: - case STV090x_TSMODE_SERIAL_CONTINUOUS: - default: - if (stv090x_write_reg(state, STV090x_TSGENERAL, 0x0c) < 0) - goto err; - break; - - case STV090x_TSMODE_PARALLEL_PUNCTURED: - case STV090x_TSMODE_DVBCI: - if (stv090x_write_reg(state, STV090x_TSGENERAL, 0x0a) < 0) - goto err; - break; - } - break; - } - } else { - switch (state->config->ts1_mode) { - case STV090x_TSMODE_PARALLEL_PUNCTURED: - case STV090x_TSMODE_DVBCI: - switch (state->config->ts2_mode) { - case STV090x_TSMODE_SERIAL_PUNCTURED: - case STV090x_TSMODE_SERIAL_CONTINUOUS: - default: - stv090x_write_reg(state, STV090x_TSGENERAL1X, 0x10); - break; - - case STV090x_TSMODE_PARALLEL_PUNCTURED: - case STV090x_TSMODE_DVBCI: - stv090x_write_reg(state, STV090x_TSGENERAL1X, 0x16); - reg = stv090x_read_reg(state, STV090x_P1_TSCFGM); - STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); - if (stv090x_write_reg(state, STV090x_P1_TSCFGM, reg) < 0) - goto err; - reg = stv090x_read_reg(state, STV090x_P1_TSCFGM); - STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 0); - if (stv090x_write_reg(state, STV090x_P1_TSCFGM, reg) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_P1_TSSPEED, 0x14) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_P2_TSSPEED, 0x28) < 0) - goto err; - break; - } - break; - - case STV090x_TSMODE_SERIAL_PUNCTURED: - case STV090x_TSMODE_SERIAL_CONTINUOUS: - default: - switch (state->config->ts2_mode) { - case STV090x_TSMODE_SERIAL_PUNCTURED: - case STV090x_TSMODE_SERIAL_CONTINUOUS: - default: - stv090x_write_reg(state, STV090x_TSGENERAL1X, 0x14); - break; - - case STV090x_TSMODE_PARALLEL_PUNCTURED: - case STV090x_TSMODE_DVBCI: - stv090x_write_reg(state, STV090x_TSGENERAL1X, 0x12); - break; - } - break; - } - } - - switch (state->config->ts1_mode) { - case STV090x_TSMODE_PARALLEL_PUNCTURED: - reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); - STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); - STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); - STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); - if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) - goto err; - break; - - case STV090x_TSMODE_DVBCI: - reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); - STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); - STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); - STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); - if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) - goto err; - break; - - case STV090x_TSMODE_SERIAL_PUNCTURED: - reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); - STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); - STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); - STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); - if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) - goto err; - break; - - case STV090x_TSMODE_SERIAL_CONTINUOUS: - reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); - STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); - STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); - STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); - if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) - goto err; - break; - - default: - break; - } - - switch (state->config->ts2_mode) { - case STV090x_TSMODE_PARALLEL_PUNCTURED: - reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); - STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); - STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); - STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); - if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) - goto err; - break; - - case STV090x_TSMODE_DVBCI: - reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); - STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); - STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); - STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); - if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) - goto err; - break; - - case STV090x_TSMODE_SERIAL_PUNCTURED: - reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); - STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); - STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); - STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); - if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) - goto err; - break; - - case STV090x_TSMODE_SERIAL_CONTINUOUS: - reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); - STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); - STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); - STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); - if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) - goto err; - break; - - default: - break; - } - - if (state->config->ts1_clk > 0) { - u32 speed; - - switch (state->config->ts1_mode) { - case STV090x_TSMODE_PARALLEL_PUNCTURED: - case STV090x_TSMODE_DVBCI: - default: - speed = state->internal->mclk / - (state->config->ts1_clk / 4); - if (speed < 0x08) - speed = 0x08; - if (speed > 0xFF) - speed = 0xFF; - break; - case STV090x_TSMODE_SERIAL_PUNCTURED: - case STV090x_TSMODE_SERIAL_CONTINUOUS: - speed = state->internal->mclk / - (state->config->ts1_clk / 32); - if (speed < 0x20) - speed = 0x20; - if (speed > 0xFF) - speed = 0xFF; - break; - } - reg = stv090x_read_reg(state, STV090x_P1_TSCFGM); - STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); - if (stv090x_write_reg(state, STV090x_P1_TSCFGM, reg) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_P1_TSSPEED, speed) < 0) - goto err; - } - - if (state->config->ts2_clk > 0) { - u32 speed; - - switch (state->config->ts2_mode) { - case STV090x_TSMODE_PARALLEL_PUNCTURED: - case STV090x_TSMODE_DVBCI: - default: - speed = state->internal->mclk / - (state->config->ts2_clk / 4); - if (speed < 0x08) - speed = 0x08; - if (speed > 0xFF) - speed = 0xFF; - break; - case STV090x_TSMODE_SERIAL_PUNCTURED: - case STV090x_TSMODE_SERIAL_CONTINUOUS: - speed = state->internal->mclk / - (state->config->ts2_clk / 32); - if (speed < 0x20) - speed = 0x20; - if (speed > 0xFF) - speed = 0xFF; - break; - } - reg = stv090x_read_reg(state, STV090x_P2_TSCFGM); - STV090x_SETFIELD_Px(reg, TSFIFO_MANSPEED_FIELD, 3); - if (stv090x_write_reg(state, STV090x_P2_TSCFGM, reg) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_P2_TSSPEED, speed) < 0) - goto err; - } - - reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); - STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0x01); - if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) - goto err; - STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0x00); - if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) - goto err; - - reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); - STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0x01); - if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) - goto err; - STV090x_SETFIELD_Px(reg, RST_HWARE_FIELD, 0x00); - if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) - goto err; - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_init(struct dvb_frontend *fe) -{ - struct stv090x_state *state = fe->demodulator_priv; - const struct stv090x_config *config = state->config; - u32 reg; - - if (state->internal->mclk == 0) { - /* call tuner init to configure the tuner's clock output - divider directly before setting up the master clock of - the stv090x. */ - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (config->tuner_init) { - if (config->tuner_init(fe) < 0) - goto err_gateoff; - } - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - stv090x_set_mclk(state, 135000000, config->xtal); /* 135 Mhz */ - msleep(5); - if (stv090x_write_reg(state, STV090x_SYNTCTRL, - 0x20 | config->clk_mode) < 0) - goto err; - stv090x_get_mclk(state); - } - - if (stv090x_wakeup(fe) < 0) { - dprintk(FE_ERROR, 1, "Error waking device"); - goto err; - } - - if (stv090x_ldpc_mode(state, state->demod_mode) < 0) - goto err; - - reg = STV090x_READ_DEMOD(state, TNRCFG2); - STV090x_SETFIELD_Px(reg, TUN_IQSWAP_FIELD, state->inversion); - if (STV090x_WRITE_DEMOD(state, TNRCFG2, reg) < 0) - goto err; - reg = STV090x_READ_DEMOD(state, DEMOD); - STV090x_SETFIELD_Px(reg, ROLLOFF_CONTROL_FIELD, state->rolloff); - if (STV090x_WRITE_DEMOD(state, DEMOD, reg) < 0) - goto err; - - if (stv090x_i2c_gate_ctrl(state, 1) < 0) - goto err; - - if (config->tuner_set_mode) { - if (config->tuner_set_mode(fe, TUNER_WAKE) < 0) - goto err_gateoff; - } - - if (config->tuner_init) { - if (config->tuner_init(fe) < 0) - goto err_gateoff; - } - - if (stv090x_i2c_gate_ctrl(state, 0) < 0) - goto err; - - if (stv090x_set_tspath(state) < 0) - goto err; - - return 0; - -err_gateoff: - stv090x_i2c_gate_ctrl(state, 0); -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -static int stv090x_setup(struct dvb_frontend *fe) -{ - struct stv090x_state *state = fe->demodulator_priv; - const struct stv090x_config *config = state->config; - const struct stv090x_reg *stv090x_initval = NULL; - const struct stv090x_reg *stv090x_cut20_val = NULL; - unsigned long t1_size = 0, t2_size = 0; - u32 reg = 0; - - int i; - - if (state->device == STV0900) { - dprintk(FE_DEBUG, 1, "Initializing STV0900"); - stv090x_initval = stv0900_initval; - t1_size = ARRAY_SIZE(stv0900_initval); - stv090x_cut20_val = stv0900_cut20_val; - t2_size = ARRAY_SIZE(stv0900_cut20_val); - } else if (state->device == STV0903) { - dprintk(FE_DEBUG, 1, "Initializing STV0903"); - stv090x_initval = stv0903_initval; - t1_size = ARRAY_SIZE(stv0903_initval); - stv090x_cut20_val = stv0903_cut20_val; - t2_size = ARRAY_SIZE(stv0903_cut20_val); - } - - /* STV090x init */ - - /* Stop Demod */ - if (stv090x_write_reg(state, STV090x_P1_DMDISTATE, 0x5c) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_P2_DMDISTATE, 0x5c) < 0) - goto err; - - msleep(5); - - /* Set No Tuner Mode */ - if (stv090x_write_reg(state, STV090x_P1_TNRCFG, 0x6c) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_P2_TNRCFG, 0x6c) < 0) - goto err; - - /* I2C repeater OFF */ - STV090x_SETFIELD_Px(reg, ENARPT_LEVEL_FIELD, config->repeater_level); - if (stv090x_write_reg(state, STV090x_P1_I2CRPT, reg) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_P2_I2CRPT, reg) < 0) - goto err; - - if (stv090x_write_reg(state, STV090x_NCOARSE, 0x13) < 0) /* set PLL divider */ - goto err; - msleep(5); - if (stv090x_write_reg(state, STV090x_I2CCFG, 0x08) < 0) /* 1/41 oversampling */ - goto err; - if (stv090x_write_reg(state, STV090x_SYNTCTRL, 0x20 | config->clk_mode) < 0) /* enable PLL */ - goto err; - msleep(5); - - /* write initval */ - dprintk(FE_DEBUG, 1, "Setting up initial values"); - for (i = 0; i < t1_size; i++) { - if (stv090x_write_reg(state, stv090x_initval[i].addr, stv090x_initval[i].data) < 0) - goto err; - } - - state->internal->dev_ver = stv090x_read_reg(state, STV090x_MID); - if (state->internal->dev_ver >= 0x20) { - if (stv090x_write_reg(state, STV090x_TSGENERAL, 0x0c) < 0) - goto err; - - /* write cut20_val*/ - dprintk(FE_DEBUG, 1, "Setting up Cut 2.0 initial values"); - for (i = 0; i < t2_size; i++) { - if (stv090x_write_reg(state, stv090x_cut20_val[i].addr, stv090x_cut20_val[i].data) < 0) - goto err; - } - - } else if (state->internal->dev_ver < 0x20) { - dprintk(FE_ERROR, 1, "ERROR: Unsupported Cut: 0x%02x!", - state->internal->dev_ver); - - goto err; - } else if (state->internal->dev_ver > 0x30) { - /* we shouldn't bail out from here */ - dprintk(FE_ERROR, 1, "INFO: Cut: 0x%02x probably incomplete support!", - state->internal->dev_ver); - } - - /* ADC1 range */ - reg = stv090x_read_reg(state, STV090x_TSTTNR1); - STV090x_SETFIELD(reg, ADC1_INMODE_FIELD, - (config->adc1_range == STV090x_ADC_1Vpp) ? 0 : 1); - if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) - goto err; - - /* ADC2 range */ - reg = stv090x_read_reg(state, STV090x_TSTTNR3); - STV090x_SETFIELD(reg, ADC2_INMODE_FIELD, - (config->adc2_range == STV090x_ADC_1Vpp) ? 0 : 1); - if (stv090x_write_reg(state, STV090x_TSTTNR3, reg) < 0) - goto err; - - if (stv090x_write_reg(state, STV090x_TSTRES0, 0x80) < 0) - goto err; - if (stv090x_write_reg(state, STV090x_TSTRES0, 0x00) < 0) - goto err; - - return 0; -err: - dprintk(FE_ERROR, 1, "I/O error"); - return -1; -} - -int stv090x_set_gpio(struct dvb_frontend *fe, u8 gpio, u8 dir, u8 value, - u8 xor_value) -{ - struct stv090x_state *state = fe->demodulator_priv; - u8 reg = 0; - - STV090x_SETFIELD(reg, GPIOx_OPD_FIELD, dir); - STV090x_SETFIELD(reg, GPIOx_CONFIG_FIELD, value); - STV090x_SETFIELD(reg, GPIOx_XOR_FIELD, xor_value); - - return stv090x_write_reg(state, STV090x_GPIOxCFG(gpio), reg); -} -EXPORT_SYMBOL(stv090x_set_gpio); - -static struct dvb_frontend_ops stv090x_ops = { - .delsys = { SYS_DVBS, SYS_DVBS2, SYS_DSS }, - .info = { - .name = "STV090x Multistandard", - .frequency_min = 950000, - .frequency_max = 2150000, - .frequency_stepsize = 0, - .frequency_tolerance = 0, - .symbol_rate_min = 1000000, - .symbol_rate_max = 45000000, - .caps = FE_CAN_INVERSION_AUTO | - FE_CAN_FEC_AUTO | - FE_CAN_QPSK | - FE_CAN_2G_MODULATION - }, - - .release = stv090x_release, - .init = stv090x_init, - - .sleep = stv090x_sleep, - .get_frontend_algo = stv090x_frontend_algo, - - .diseqc_send_master_cmd = stv090x_send_diseqc_msg, - .diseqc_send_burst = stv090x_send_diseqc_burst, - .diseqc_recv_slave_reply = stv090x_recv_slave_reply, - .set_tone = stv090x_set_tone, - - .search = stv090x_search, - .read_status = stv090x_read_status, - .read_ber = stv090x_read_per, - .read_signal_strength = stv090x_read_signal_strength, - .read_snr = stv090x_read_cnr, -}; - - -struct dvb_frontend *stv090x_attach(const struct stv090x_config *config, - struct i2c_adapter *i2c, - enum stv090x_demodulator demod) -{ - struct stv090x_state *state = NULL; - struct stv090x_dev *temp_int; - - state = kzalloc(sizeof (struct stv090x_state), GFP_KERNEL); - if (state == NULL) - goto error; - - state->verbose = &verbose; - state->config = config; - state->i2c = i2c; - state->frontend.ops = stv090x_ops; - state->frontend.demodulator_priv = state; - state->demod = demod; - state->demod_mode = config->demod_mode; /* Single or Dual mode */ - state->device = config->device; - state->rolloff = STV090x_RO_35; /* default */ - - temp_int = find_dev(state->i2c, - state->config->address); - - if ((temp_int != NULL) && (state->demod_mode == STV090x_DUAL)) { - state->internal = temp_int->internal; - state->internal->num_used++; - dprintk(FE_INFO, 1, "Found Internal Structure!"); - } else { - state->internal = kmalloc(sizeof(struct stv090x_internal), - GFP_KERNEL); - if (!state->internal) - goto error; - temp_int = append_internal(state->internal); - if (!temp_int) { - kfree(state->internal); - goto error; - } - state->internal->num_used = 1; - state->internal->mclk = 0; - state->internal->dev_ver = 0; - state->internal->i2c_adap = state->i2c; - state->internal->i2c_addr = state->config->address; - dprintk(FE_INFO, 1, "Create New Internal Structure!"); - - mutex_init(&state->internal->demod_lock); - mutex_init(&state->internal->tuner_lock); - - if (stv090x_setup(&state->frontend) < 0) { - dprintk(FE_ERROR, 1, "Error setting up device"); - goto err_remove; - } - } - - /* workaround for stuck DiSEqC output */ - if (config->diseqc_envelope_mode) - stv090x_send_diseqc_burst(&state->frontend, SEC_MINI_A); - - dprintk(FE_ERROR, 1, "Attaching %s demodulator(%d) Cut=0x%02x", - state->device == STV0900 ? "STV0900" : "STV0903", - demod, - state->internal->dev_ver); - - return &state->frontend; - -err_remove: - remove_dev(state->internal); - kfree(state->internal); -error: - kfree(state); - return NULL; -} -EXPORT_SYMBOL(stv090x_attach); -MODULE_PARM_DESC(verbose, "Set Verbosity level"); -MODULE_AUTHOR("Manu Abraham"); -MODULE_DESCRIPTION("STV090x Multi-Std Broadcast frontend"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/tda1004x.c b/drivers/media/dvb/frontends/tda1004x.c deleted file mode 100644 index 35d72b46aa1e..000000000000 --- a/drivers/media/dvb/frontends/tda1004x.c +++ /dev/null @@ -1,1381 +0,0 @@ - /* - Driver for Philips tda1004xh OFDM Demodulator - - (c) 2003, 2004 Andrew de Quincey & Robert Schlabbach - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - - */ -/* - * This driver needs external firmware. Please use the commands - * "/Documentation/dvb/get_dvb_firmware tda10045", - * "/Documentation/dvb/get_dvb_firmware tda10046" to - * download/extract them, and then copy them to /usr/lib/hotplug/firmware - * or /lib/firmware (depending on configuration of firmware hotplug). - */ -#define TDA10045_DEFAULT_FIRMWARE "dvb-fe-tda10045.fw" -#define TDA10046_DEFAULT_FIRMWARE "dvb-fe-tda10046.fw" - -#include -#include -#include -#include -#include -#include - -#include "dvb_frontend.h" -#include "tda1004x.h" - -static int debug; -#define dprintk(args...) \ - do { \ - if (debug) printk(KERN_DEBUG "tda1004x: " args); \ - } while (0) - -#define TDA1004X_CHIPID 0x00 -#define TDA1004X_AUTO 0x01 -#define TDA1004X_IN_CONF1 0x02 -#define TDA1004X_IN_CONF2 0x03 -#define TDA1004X_OUT_CONF1 0x04 -#define TDA1004X_OUT_CONF2 0x05 -#define TDA1004X_STATUS_CD 0x06 -#define TDA1004X_CONFC4 0x07 -#define TDA1004X_DSSPARE2 0x0C -#define TDA10045H_CODE_IN 0x0D -#define TDA10045H_FWPAGE 0x0E -#define TDA1004X_SCAN_CPT 0x10 -#define TDA1004X_DSP_CMD 0x11 -#define TDA1004X_DSP_ARG 0x12 -#define TDA1004X_DSP_DATA1 0x13 -#define TDA1004X_DSP_DATA2 0x14 -#define TDA1004X_CONFADC1 0x15 -#define TDA1004X_CONFC1 0x16 -#define TDA10045H_S_AGC 0x1a -#define TDA10046H_AGC_TUN_LEVEL 0x1a -#define TDA1004X_SNR 0x1c -#define TDA1004X_CONF_TS1 0x1e -#define TDA1004X_CONF_TS2 0x1f -#define TDA1004X_CBER_RESET 0x20 -#define TDA1004X_CBER_MSB 0x21 -#define TDA1004X_CBER_LSB 0x22 -#define TDA1004X_CVBER_LUT 0x23 -#define TDA1004X_VBER_MSB 0x24 -#define TDA1004X_VBER_MID 0x25 -#define TDA1004X_VBER_LSB 0x26 -#define TDA1004X_UNCOR 0x27 - -#define TDA10045H_CONFPLL_P 0x2D -#define TDA10045H_CONFPLL_M_MSB 0x2E -#define TDA10045H_CONFPLL_M_LSB 0x2F -#define TDA10045H_CONFPLL_N 0x30 - -#define TDA10046H_CONFPLL1 0x2D -#define TDA10046H_CONFPLL2 0x2F -#define TDA10046H_CONFPLL3 0x30 -#define TDA10046H_TIME_WREF1 0x31 -#define TDA10046H_TIME_WREF2 0x32 -#define TDA10046H_TIME_WREF3 0x33 -#define TDA10046H_TIME_WREF4 0x34 -#define TDA10046H_TIME_WREF5 0x35 - -#define TDA10045H_UNSURW_MSB 0x31 -#define TDA10045H_UNSURW_LSB 0x32 -#define TDA10045H_WREF_MSB 0x33 -#define TDA10045H_WREF_MID 0x34 -#define TDA10045H_WREF_LSB 0x35 -#define TDA10045H_MUXOUT 0x36 -#define TDA1004X_CONFADC2 0x37 - -#define TDA10045H_IOFFSET 0x38 - -#define TDA10046H_CONF_TRISTATE1 0x3B -#define TDA10046H_CONF_TRISTATE2 0x3C -#define TDA10046H_CONF_POLARITY 0x3D -#define TDA10046H_FREQ_OFFSET 0x3E -#define TDA10046H_GPIO_OUT_SEL 0x41 -#define TDA10046H_GPIO_SELECT 0x42 -#define TDA10046H_AGC_CONF 0x43 -#define TDA10046H_AGC_THR 0x44 -#define TDA10046H_AGC_RENORM 0x45 -#define TDA10046H_AGC_GAINS 0x46 -#define TDA10046H_AGC_TUN_MIN 0x47 -#define TDA10046H_AGC_TUN_MAX 0x48 -#define TDA10046H_AGC_IF_MIN 0x49 -#define TDA10046H_AGC_IF_MAX 0x4A - -#define TDA10046H_FREQ_PHY2_MSB 0x4D -#define TDA10046H_FREQ_PHY2_LSB 0x4E - -#define TDA10046H_CVBER_CTRL 0x4F -#define TDA10046H_AGC_IF_LEVEL 0x52 -#define TDA10046H_CODE_CPT 0x57 -#define TDA10046H_CODE_IN 0x58 - - -static int tda1004x_write_byteI(struct tda1004x_state *state, int reg, int data) -{ - int ret; - u8 buf[] = { reg, data }; - struct i2c_msg msg = { .flags = 0, .buf = buf, .len = 2 }; - - dprintk("%s: reg=0x%x, data=0x%x\n", __func__, reg, data); - - msg.addr = state->config->demod_address; - ret = i2c_transfer(state->i2c, &msg, 1); - - if (ret != 1) - dprintk("%s: error reg=0x%x, data=0x%x, ret=%i\n", - __func__, reg, data, ret); - - dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __func__, - reg, data, ret); - return (ret != 1) ? -1 : 0; -} - -static int tda1004x_read_byte(struct tda1004x_state *state, int reg) -{ - int ret; - u8 b0[] = { reg }; - u8 b1[] = { 0 }; - struct i2c_msg msg[] = {{ .flags = 0, .buf = b0, .len = 1 }, - { .flags = I2C_M_RD, .buf = b1, .len = 1 }}; - - dprintk("%s: reg=0x%x\n", __func__, reg); - - msg[0].addr = state->config->demod_address; - msg[1].addr = state->config->demod_address; - ret = i2c_transfer(state->i2c, msg, 2); - - if (ret != 2) { - dprintk("%s: error reg=0x%x, ret=%i\n", __func__, reg, - ret); - return -EINVAL; - } - - dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __func__, - reg, b1[0], ret); - return b1[0]; -} - -static int tda1004x_write_mask(struct tda1004x_state *state, int reg, int mask, int data) -{ - int val; - dprintk("%s: reg=0x%x, mask=0x%x, data=0x%x\n", __func__, reg, - mask, data); - - // read a byte and check - val = tda1004x_read_byte(state, reg); - if (val < 0) - return val; - - // mask if off - val = val & ~mask; - val |= data & 0xff; - - // write it out again - return tda1004x_write_byteI(state, reg, val); -} - -static int tda1004x_write_buf(struct tda1004x_state *state, int reg, unsigned char *buf, int len) -{ - int i; - int result; - - dprintk("%s: reg=0x%x, len=0x%x\n", __func__, reg, len); - - result = 0; - for (i = 0; i < len; i++) { - result = tda1004x_write_byteI(state, reg + i, buf[i]); - if (result != 0) - break; - } - - return result; -} - -static int tda1004x_enable_tuner_i2c(struct tda1004x_state *state) -{ - int result; - dprintk("%s\n", __func__); - - result = tda1004x_write_mask(state, TDA1004X_CONFC4, 2, 2); - msleep(20); - return result; -} - -static int tda1004x_disable_tuner_i2c(struct tda1004x_state *state) -{ - dprintk("%s\n", __func__); - - return tda1004x_write_mask(state, TDA1004X_CONFC4, 2, 0); -} - -static int tda10045h_set_bandwidth(struct tda1004x_state *state, - u32 bandwidth) -{ - static u8 bandwidth_6mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x60, 0x1e, 0xa7, 0x45, 0x4f }; - static u8 bandwidth_7mhz[] = { 0x02, 0x00, 0x37, 0x00, 0x4a, 0x2f, 0x6d, 0x76, 0xdb }; - static u8 bandwidth_8mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x48, 0x17, 0x89, 0xc7, 0x14 }; - - switch (bandwidth) { - case 6000000: - tda1004x_write_buf(state, TDA10045H_CONFPLL_P, bandwidth_6mhz, sizeof(bandwidth_6mhz)); - break; - - case 7000000: - tda1004x_write_buf(state, TDA10045H_CONFPLL_P, bandwidth_7mhz, sizeof(bandwidth_7mhz)); - break; - - case 8000000: - tda1004x_write_buf(state, TDA10045H_CONFPLL_P, bandwidth_8mhz, sizeof(bandwidth_8mhz)); - break; - - default: - return -EINVAL; - } - - tda1004x_write_byteI(state, TDA10045H_IOFFSET, 0); - - return 0; -} - -static int tda10046h_set_bandwidth(struct tda1004x_state *state, - u32 bandwidth) -{ - static u8 bandwidth_6mhz_53M[] = { 0x7b, 0x2e, 0x11, 0xf0, 0xd2 }; - static u8 bandwidth_7mhz_53M[] = { 0x6a, 0x02, 0x6a, 0x43, 0x9f }; - static u8 bandwidth_8mhz_53M[] = { 0x5c, 0x32, 0xc2, 0x96, 0x6d }; - - static u8 bandwidth_6mhz_48M[] = { 0x70, 0x02, 0x49, 0x24, 0x92 }; - static u8 bandwidth_7mhz_48M[] = { 0x60, 0x02, 0xaa, 0xaa, 0xab }; - static u8 bandwidth_8mhz_48M[] = { 0x54, 0x03, 0x0c, 0x30, 0xc3 }; - int tda10046_clk53m; - - if ((state->config->if_freq == TDA10046_FREQ_045) || - (state->config->if_freq == TDA10046_FREQ_052)) - tda10046_clk53m = 0; - else - tda10046_clk53m = 1; - switch (bandwidth) { - case 6000000: - if (tda10046_clk53m) - tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_6mhz_53M, - sizeof(bandwidth_6mhz_53M)); - else - tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_6mhz_48M, - sizeof(bandwidth_6mhz_48M)); - if (state->config->if_freq == TDA10046_FREQ_045) { - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0a); - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0xab); - } - break; - - case 7000000: - if (tda10046_clk53m) - tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_7mhz_53M, - sizeof(bandwidth_7mhz_53M)); - else - tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_7mhz_48M, - sizeof(bandwidth_7mhz_48M)); - if (state->config->if_freq == TDA10046_FREQ_045) { - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0c); - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x00); - } - break; - - case 8000000: - if (tda10046_clk53m) - tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_8mhz_53M, - sizeof(bandwidth_8mhz_53M)); - else - tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_8mhz_48M, - sizeof(bandwidth_8mhz_48M)); - if (state->config->if_freq == TDA10046_FREQ_045) { - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0d); - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x55); - } - break; - - default: - return -EINVAL; - } - - return 0; -} - -static int tda1004x_do_upload(struct tda1004x_state *state, - const unsigned char *mem, unsigned int len, - u8 dspCodeCounterReg, u8 dspCodeInReg) -{ - u8 buf[65]; - struct i2c_msg fw_msg = { .flags = 0, .buf = buf, .len = 0 }; - int tx_size; - int pos = 0; - - /* clear code counter */ - tda1004x_write_byteI(state, dspCodeCounterReg, 0); - fw_msg.addr = state->config->demod_address; - - buf[0] = dspCodeInReg; - while (pos != len) { - // work out how much to send this time - tx_size = len - pos; - if (tx_size > 0x10) - tx_size = 0x10; - - // send the chunk - memcpy(buf + 1, mem + pos, tx_size); - fw_msg.len = tx_size + 1; - if (i2c_transfer(state->i2c, &fw_msg, 1) != 1) { - printk(KERN_ERR "tda1004x: Error during firmware upload\n"); - return -EIO; - } - pos += tx_size; - - dprintk("%s: fw_pos=0x%x\n", __func__, pos); - } - // give the DSP a chance to settle 03/10/05 Hac - msleep(100); - - return 0; -} - -static int tda1004x_check_upload_ok(struct tda1004x_state *state) -{ - u8 data1, data2; - unsigned long timeout; - - if (state->demod_type == TDA1004X_DEMOD_TDA10046) { - timeout = jiffies + 2 * HZ; - while(!(tda1004x_read_byte(state, TDA1004X_STATUS_CD) & 0x20)) { - if (time_after(jiffies, timeout)) { - printk(KERN_ERR "tda1004x: timeout waiting for DSP ready\n"); - break; - } - msleep(1); - } - } else - msleep(100); - - // check upload was OK - tda1004x_write_mask(state, TDA1004X_CONFC4, 0x10, 0); // we want to read from the DSP - tda1004x_write_byteI(state, TDA1004X_DSP_CMD, 0x67); - - data1 = tda1004x_read_byte(state, TDA1004X_DSP_DATA1); - data2 = tda1004x_read_byte(state, TDA1004X_DSP_DATA2); - if (data1 != 0x67 || data2 < 0x20 || data2 > 0x2e) { - printk(KERN_INFO "tda1004x: found firmware revision %x -- invalid\n", data2); - return -EIO; - } - printk(KERN_INFO "tda1004x: found firmware revision %x -- ok\n", data2); - return 0; -} - -static int tda10045_fwupload(struct dvb_frontend* fe) -{ - struct tda1004x_state* state = fe->demodulator_priv; - int ret; - const struct firmware *fw; - - /* don't re-upload unless necessary */ - if (tda1004x_check_upload_ok(state) == 0) - return 0; - - /* request the firmware, this will block until someone uploads it */ - printk(KERN_INFO "tda1004x: waiting for firmware upload (%s)...\n", TDA10045_DEFAULT_FIRMWARE); - ret = state->config->request_firmware(fe, &fw, TDA10045_DEFAULT_FIRMWARE); - if (ret) { - printk(KERN_ERR "tda1004x: no firmware upload (timeout or file not found?)\n"); - return ret; - } - - /* reset chip */ - tda1004x_write_mask(state, TDA1004X_CONFC4, 0x10, 0); - tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 8); - tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 0); - msleep(10); - - /* set parameters */ - tda10045h_set_bandwidth(state, 8000000); - - ret = tda1004x_do_upload(state, fw->data, fw->size, TDA10045H_FWPAGE, TDA10045H_CODE_IN); - release_firmware(fw); - if (ret) - return ret; - printk(KERN_INFO "tda1004x: firmware upload complete\n"); - - /* wait for DSP to initialise */ - /* DSPREADY doesn't seem to work on the TDA10045H */ - msleep(100); - - return tda1004x_check_upload_ok(state); -} - -static void tda10046_init_plls(struct dvb_frontend* fe) -{ - struct tda1004x_state* state = fe->demodulator_priv; - int tda10046_clk53m; - - if ((state->config->if_freq == TDA10046_FREQ_045) || - (state->config->if_freq == TDA10046_FREQ_052)) - tda10046_clk53m = 0; - else - tda10046_clk53m = 1; - - tda1004x_write_byteI(state, TDA10046H_CONFPLL1, 0xf0); - if(tda10046_clk53m) { - printk(KERN_INFO "tda1004x: setting up plls for 53MHz sampling clock\n"); - tda1004x_write_byteI(state, TDA10046H_CONFPLL2, 0x08); // PLL M = 8 - } else { - printk(KERN_INFO "tda1004x: setting up plls for 48MHz sampling clock\n"); - tda1004x_write_byteI(state, TDA10046H_CONFPLL2, 0x03); // PLL M = 3 - } - if (state->config->xtal_freq == TDA10046_XTAL_4M ) { - dprintk("%s: setting up PLLs for a 4 MHz Xtal\n", __func__); - tda1004x_write_byteI(state, TDA10046H_CONFPLL3, 0); // PLL P = N = 0 - } else { - dprintk("%s: setting up PLLs for a 16 MHz Xtal\n", __func__); - tda1004x_write_byteI(state, TDA10046H_CONFPLL3, 3); // PLL P = 0, N = 3 - } - if(tda10046_clk53m) - tda1004x_write_byteI(state, TDA10046H_FREQ_OFFSET, 0x67); - else - tda1004x_write_byteI(state, TDA10046H_FREQ_OFFSET, 0x72); - /* Note clock frequency is handled implicitly */ - switch (state->config->if_freq) { - case TDA10046_FREQ_045: - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0c); - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x00); - break; - case TDA10046_FREQ_052: - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0d); - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0xc7); - break; - case TDA10046_FREQ_3617: - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0xd7); - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x59); - break; - case TDA10046_FREQ_3613: - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0xd7); - tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x3f); - break; - } - tda10046h_set_bandwidth(state, 8000000); /* default bandwidth 8 MHz */ - /* let the PLLs settle */ - msleep(120); -} - -static int tda10046_fwupload(struct dvb_frontend* fe) -{ - struct tda1004x_state* state = fe->demodulator_priv; - int ret, confc4; - const struct firmware *fw; - - /* reset + wake up chip */ - if (state->config->xtal_freq == TDA10046_XTAL_4M) { - confc4 = 0; - } else { - dprintk("%s: 16MHz Xtal, reducing I2C speed\n", __func__); - confc4 = 0x80; - } - tda1004x_write_byteI(state, TDA1004X_CONFC4, confc4); - - tda1004x_write_mask(state, TDA10046H_CONF_TRISTATE1, 1, 0); - /* set GPIO 1 and 3 */ - if (state->config->gpio_config != TDA10046_GPTRI) { - tda1004x_write_byteI(state, TDA10046H_CONF_TRISTATE2, 0x33); - tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0x0f, state->config->gpio_config &0x0f); - } - /* let the clocks recover from sleep */ - msleep(10); - - /* The PLLs need to be reprogrammed after sleep */ - tda10046_init_plls(fe); - tda1004x_write_mask(state, TDA1004X_CONFADC2, 0xc0, 0); - - /* don't re-upload unless necessary */ - if (tda1004x_check_upload_ok(state) == 0) - return 0; - - /* - For i2c normal work, we need to slow down the bus speed. - However, the slow down breaks the eeprom firmware load. - So, use normal speed for eeprom booting and then restore the - i2c speed after that. Tested with MSI TV @nyware A/D board, - that comes with firmware version 29 inside their eeprom. - - It should also be noticed that no other I2C transfer should - be in course while booting from eeprom, otherwise, tda10046 - goes into an instable state. So, proper locking are needed - at the i2c bus master. - */ - printk(KERN_INFO "tda1004x: trying to boot from eeprom\n"); - tda1004x_write_byteI(state, TDA1004X_CONFC4, 4); - msleep(300); - tda1004x_write_byteI(state, TDA1004X_CONFC4, confc4); - - /* Checks if eeprom firmware went without troubles */ - if (tda1004x_check_upload_ok(state) == 0) - return 0; - - /* eeprom firmware didn't work. Load one manually. */ - - if (state->config->request_firmware != NULL) { - /* request the firmware, this will block until someone uploads it */ - printk(KERN_INFO "tda1004x: waiting for firmware upload...\n"); - ret = state->config->request_firmware(fe, &fw, TDA10046_DEFAULT_FIRMWARE); - if (ret) { - /* remain compatible to old bug: try to load with tda10045 image name */ - ret = state->config->request_firmware(fe, &fw, TDA10045_DEFAULT_FIRMWARE); - if (ret) { - printk(KERN_ERR "tda1004x: no firmware upload (timeout or file not found?)\n"); - return ret; - } else { - printk(KERN_INFO "tda1004x: please rename the firmware file to %s\n", - TDA10046_DEFAULT_FIRMWARE); - } - } - } else { - printk(KERN_ERR "tda1004x: no request function defined, can't upload from file\n"); - return -EIO; - } - tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 8); // going to boot from HOST - ret = tda1004x_do_upload(state, fw->data, fw->size, TDA10046H_CODE_CPT, TDA10046H_CODE_IN); - release_firmware(fw); - return tda1004x_check_upload_ok(state); -} - -static int tda1004x_encode_fec(int fec) -{ - // convert known FEC values - switch (fec) { - case FEC_1_2: - return 0; - case FEC_2_3: - return 1; - case FEC_3_4: - return 2; - case FEC_5_6: - return 3; - case FEC_7_8: - return 4; - } - - // unsupported - return -EINVAL; -} - -static int tda1004x_decode_fec(int tdafec) -{ - // convert known FEC values - switch (tdafec) { - case 0: - return FEC_1_2; - case 1: - return FEC_2_3; - case 2: - return FEC_3_4; - case 3: - return FEC_5_6; - case 4: - return FEC_7_8; - } - - // unsupported - return -1; -} - -static int tda1004x_write(struct dvb_frontend* fe, const u8 buf[], int len) -{ - struct tda1004x_state* state = fe->demodulator_priv; - - if (len != 2) - return -EINVAL; - - return tda1004x_write_byteI(state, buf[0], buf[1]); -} - -static int tda10045_init(struct dvb_frontend* fe) -{ - struct tda1004x_state* state = fe->demodulator_priv; - - dprintk("%s\n", __func__); - - if (tda10045_fwupload(fe)) { - printk("tda1004x: firmware upload failed\n"); - return -EIO; - } - - tda1004x_write_mask(state, TDA1004X_CONFADC1, 0x10, 0); // wake up the ADC - - // tda setup - tda1004x_write_mask(state, TDA1004X_CONFC4, 0x20, 0); // disable DSP watchdog timer - tda1004x_write_mask(state, TDA1004X_AUTO, 8, 0); // select HP stream - tda1004x_write_mask(state, TDA1004X_CONFC1, 0x40, 0); // set polarity of VAGC signal - tda1004x_write_mask(state, TDA1004X_CONFC1, 0x80, 0x80); // enable pulse killer - tda1004x_write_mask(state, TDA1004X_AUTO, 0x10, 0x10); // enable auto offset - tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0xC0, 0x0); // no frequency offset - tda1004x_write_byteI(state, TDA1004X_CONF_TS1, 0); // setup MPEG2 TS interface - tda1004x_write_byteI(state, TDA1004X_CONF_TS2, 0); // setup MPEG2 TS interface - tda1004x_write_mask(state, TDA1004X_VBER_MSB, 0xe0, 0xa0); // 10^6 VBER measurement bits - tda1004x_write_mask(state, TDA1004X_CONFC1, 0x10, 0); // VAGC polarity - tda1004x_write_byteI(state, TDA1004X_CONFADC1, 0x2e); - - tda1004x_write_mask(state, 0x1f, 0x01, state->config->invert_oclk); - - return 0; -} - -static int tda10046_init(struct dvb_frontend* fe) -{ - struct tda1004x_state* state = fe->demodulator_priv; - dprintk("%s\n", __func__); - - if (tda10046_fwupload(fe)) { - printk("tda1004x: firmware upload failed\n"); - return -EIO; - } - - // tda setup - tda1004x_write_mask(state, TDA1004X_CONFC4, 0x20, 0); // disable DSP watchdog timer - tda1004x_write_byteI(state, TDA1004X_AUTO, 0x87); // 100 ppm crystal, select HP stream - tda1004x_write_byteI(state, TDA1004X_CONFC1, 0x88); // enable pulse killer - - switch (state->config->agc_config) { - case TDA10046_AGC_DEFAULT: - tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x00); // AGC setup - tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0xf0, 0x60); // set AGC polarities - break; - case TDA10046_AGC_IFO_AUTO_NEG: - tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x0a); // AGC setup - tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0xf0, 0x60); // set AGC polarities - break; - case TDA10046_AGC_IFO_AUTO_POS: - tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x0a); // AGC setup - tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0xf0, 0x00); // set AGC polarities - break; - case TDA10046_AGC_TDA827X: - tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x02); // AGC setup - tda1004x_write_byteI(state, TDA10046H_AGC_THR, 0x70); // AGC Threshold - tda1004x_write_byteI(state, TDA10046H_AGC_RENORM, 0x08); // Gain Renormalize - tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0xf0, 0x60); // set AGC polarities - break; - } - if (state->config->ts_mode == 0) { - tda1004x_write_mask(state, TDA10046H_CONF_TRISTATE1, 0xc0, 0x40); - tda1004x_write_mask(state, 0x3a, 0x80, state->config->invert_oclk << 7); - } else { - tda1004x_write_mask(state, TDA10046H_CONF_TRISTATE1, 0xc0, 0x80); - tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0x10, - state->config->invert_oclk << 4); - } - tda1004x_write_byteI(state, TDA1004X_CONFADC2, 0x38); - tda1004x_write_mask (state, TDA10046H_CONF_TRISTATE1, 0x3e, 0x38); // Turn IF AGC output on - tda1004x_write_byteI(state, TDA10046H_AGC_TUN_MIN, 0); // } - tda1004x_write_byteI(state, TDA10046H_AGC_TUN_MAX, 0xff); // } AGC min/max values - tda1004x_write_byteI(state, TDA10046H_AGC_IF_MIN, 0); // } - tda1004x_write_byteI(state, TDA10046H_AGC_IF_MAX, 0xff); // } - tda1004x_write_byteI(state, TDA10046H_AGC_GAINS, 0x12); // IF gain 2, TUN gain 1 - tda1004x_write_byteI(state, TDA10046H_CVBER_CTRL, 0x1a); // 10^6 VBER measurement bits - tda1004x_write_byteI(state, TDA1004X_CONF_TS1, 7); // MPEG2 interface config - tda1004x_write_byteI(state, TDA1004X_CONF_TS2, 0xc0); // MPEG2 interface config - // tda1004x_write_mask(state, 0x50, 0x80, 0x80); // handle out of guard echoes - - return 0; -} - -static int tda1004x_set_fe(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *fe_params = &fe->dtv_property_cache; - struct tda1004x_state* state = fe->demodulator_priv; - int tmp; - int inversion; - - dprintk("%s\n", __func__); - - if (state->demod_type == TDA1004X_DEMOD_TDA10046) { - // setup auto offset - tda1004x_write_mask(state, TDA1004X_AUTO, 0x10, 0x10); - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x80, 0); - tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0xC0, 0); - - // disable agc_conf[2] - tda1004x_write_mask(state, TDA10046H_AGC_CONF, 4, 0); - } - - // set frequency - if (fe->ops.tuner_ops.set_params) { - fe->ops.tuner_ops.set_params(fe); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - } - - // Hardcoded to use auto as much as possible on the TDA10045 as it - // is very unreliable if AUTO mode is _not_ used. - if (state->demod_type == TDA1004X_DEMOD_TDA10045) { - fe_params->code_rate_HP = FEC_AUTO; - fe_params->guard_interval = GUARD_INTERVAL_AUTO; - fe_params->transmission_mode = TRANSMISSION_MODE_AUTO; - } - - // Set standard params.. or put them to auto - if ((fe_params->code_rate_HP == FEC_AUTO) || - (fe_params->code_rate_LP == FEC_AUTO) || - (fe_params->modulation == QAM_AUTO) || - (fe_params->hierarchy == HIERARCHY_AUTO)) { - tda1004x_write_mask(state, TDA1004X_AUTO, 1, 1); // enable auto - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x03, 0); /* turn off modulation bits */ - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 0); // turn off hierarchy bits - tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0x3f, 0); // turn off FEC bits - } else { - tda1004x_write_mask(state, TDA1004X_AUTO, 1, 0); // disable auto - - // set HP FEC - tmp = tda1004x_encode_fec(fe_params->code_rate_HP); - if (tmp < 0) - return tmp; - tda1004x_write_mask(state, TDA1004X_IN_CONF2, 7, tmp); - - // set LP FEC - tmp = tda1004x_encode_fec(fe_params->code_rate_LP); - if (tmp < 0) - return tmp; - tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0x38, tmp << 3); - - /* set modulation */ - switch (fe_params->modulation) { - case QPSK: - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 3, 0); - break; - - case QAM_16: - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 3, 1); - break; - - case QAM_64: - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 3, 2); - break; - - default: - return -EINVAL; - } - - // set hierarchy - switch (fe_params->hierarchy) { - case HIERARCHY_NONE: - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 0 << 5); - break; - - case HIERARCHY_1: - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 1 << 5); - break; - - case HIERARCHY_2: - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 2 << 5); - break; - - case HIERARCHY_4: - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 3 << 5); - break; - - default: - return -EINVAL; - } - } - - // set bandwidth - switch (state->demod_type) { - case TDA1004X_DEMOD_TDA10045: - tda10045h_set_bandwidth(state, fe_params->bandwidth_hz); - break; - - case TDA1004X_DEMOD_TDA10046: - tda10046h_set_bandwidth(state, fe_params->bandwidth_hz); - break; - } - - // set inversion - inversion = fe_params->inversion; - if (state->config->invert) - inversion = inversion ? INVERSION_OFF : INVERSION_ON; - switch (inversion) { - case INVERSION_OFF: - tda1004x_write_mask(state, TDA1004X_CONFC1, 0x20, 0); - break; - - case INVERSION_ON: - tda1004x_write_mask(state, TDA1004X_CONFC1, 0x20, 0x20); - break; - - default: - return -EINVAL; - } - - // set guard interval - switch (fe_params->guard_interval) { - case GUARD_INTERVAL_1_32: - tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 0 << 2); - break; - - case GUARD_INTERVAL_1_16: - tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 1 << 2); - break; - - case GUARD_INTERVAL_1_8: - tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 2 << 2); - break; - - case GUARD_INTERVAL_1_4: - tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 3 << 2); - break; - - case GUARD_INTERVAL_AUTO: - tda1004x_write_mask(state, TDA1004X_AUTO, 2, 2); - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 0 << 2); - break; - - default: - return -EINVAL; - } - - // set transmission mode - switch (fe_params->transmission_mode) { - case TRANSMISSION_MODE_2K: - tda1004x_write_mask(state, TDA1004X_AUTO, 4, 0); - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x10, 0 << 4); - break; - - case TRANSMISSION_MODE_8K: - tda1004x_write_mask(state, TDA1004X_AUTO, 4, 0); - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x10, 1 << 4); - break; - - case TRANSMISSION_MODE_AUTO: - tda1004x_write_mask(state, TDA1004X_AUTO, 4, 4); - tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x10, 0); - break; - - default: - return -EINVAL; - } - - // start the lock - switch (state->demod_type) { - case TDA1004X_DEMOD_TDA10045: - tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 8); - tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 0); - break; - - case TDA1004X_DEMOD_TDA10046: - tda1004x_write_mask(state, TDA1004X_AUTO, 0x40, 0x40); - msleep(1); - tda1004x_write_mask(state, TDA10046H_AGC_CONF, 4, 1); - break; - } - - msleep(10); - - return 0; -} - -static int tda1004x_get_fe(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *fe_params = &fe->dtv_property_cache; - struct tda1004x_state* state = fe->demodulator_priv; - - dprintk("%s\n", __func__); - - // inversion status - fe_params->inversion = INVERSION_OFF; - if (tda1004x_read_byte(state, TDA1004X_CONFC1) & 0x20) - fe_params->inversion = INVERSION_ON; - if (state->config->invert) - fe_params->inversion = fe_params->inversion ? INVERSION_OFF : INVERSION_ON; - - // bandwidth - switch (state->demod_type) { - case TDA1004X_DEMOD_TDA10045: - switch (tda1004x_read_byte(state, TDA10045H_WREF_LSB)) { - case 0x14: - fe_params->bandwidth_hz = 8000000; - break; - case 0xdb: - fe_params->bandwidth_hz = 7000000; - break; - case 0x4f: - fe_params->bandwidth_hz = 6000000; - break; - } - break; - case TDA1004X_DEMOD_TDA10046: - switch (tda1004x_read_byte(state, TDA10046H_TIME_WREF1)) { - case 0x5c: - case 0x54: - fe_params->bandwidth_hz = 8000000; - break; - case 0x6a: - case 0x60: - fe_params->bandwidth_hz = 7000000; - break; - case 0x7b: - case 0x70: - fe_params->bandwidth_hz = 6000000; - break; - } - break; - } - - // FEC - fe_params->code_rate_HP = - tda1004x_decode_fec(tda1004x_read_byte(state, TDA1004X_OUT_CONF2) & 7); - fe_params->code_rate_LP = - tda1004x_decode_fec((tda1004x_read_byte(state, TDA1004X_OUT_CONF2) >> 3) & 7); - - /* modulation */ - switch (tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 3) { - case 0: - fe_params->modulation = QPSK; - break; - case 1: - fe_params->modulation = QAM_16; - break; - case 2: - fe_params->modulation = QAM_64; - break; - } - - // transmission mode - fe_params->transmission_mode = TRANSMISSION_MODE_2K; - if (tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 0x10) - fe_params->transmission_mode = TRANSMISSION_MODE_8K; - - // guard interval - switch ((tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 0x0c) >> 2) { - case 0: - fe_params->guard_interval = GUARD_INTERVAL_1_32; - break; - case 1: - fe_params->guard_interval = GUARD_INTERVAL_1_16; - break; - case 2: - fe_params->guard_interval = GUARD_INTERVAL_1_8; - break; - case 3: - fe_params->guard_interval = GUARD_INTERVAL_1_4; - break; - } - - // hierarchy - switch ((tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 0x60) >> 5) { - case 0: - fe_params->hierarchy = HIERARCHY_NONE; - break; - case 1: - fe_params->hierarchy = HIERARCHY_1; - break; - case 2: - fe_params->hierarchy = HIERARCHY_2; - break; - case 3: - fe_params->hierarchy = HIERARCHY_4; - break; - } - - return 0; -} - -static int tda1004x_read_status(struct dvb_frontend* fe, fe_status_t * fe_status) -{ - struct tda1004x_state* state = fe->demodulator_priv; - int status; - int cber; - int vber; - - dprintk("%s\n", __func__); - - // read status - status = tda1004x_read_byte(state, TDA1004X_STATUS_CD); - if (status == -1) - return -EIO; - - // decode - *fe_status = 0; - if (status & 4) - *fe_status |= FE_HAS_SIGNAL; - if (status & 2) - *fe_status |= FE_HAS_CARRIER; - if (status & 8) - *fe_status |= FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - - // if we don't already have VITERBI (i.e. not LOCKED), see if the viterbi - // is getting anything valid - if (!(*fe_status & FE_HAS_VITERBI)) { - // read the CBER - cber = tda1004x_read_byte(state, TDA1004X_CBER_LSB); - if (cber == -1) - return -EIO; - status = tda1004x_read_byte(state, TDA1004X_CBER_MSB); - if (status == -1) - return -EIO; - cber |= (status << 8); - // The address 0x20 should be read to cope with a TDA10046 bug - tda1004x_read_byte(state, TDA1004X_CBER_RESET); - - if (cber != 65535) - *fe_status |= FE_HAS_VITERBI; - } - - // if we DO have some valid VITERBI output, but don't already have SYNC - // bytes (i.e. not LOCKED), see if the RS decoder is getting anything valid. - if ((*fe_status & FE_HAS_VITERBI) && (!(*fe_status & FE_HAS_SYNC))) { - // read the VBER - vber = tda1004x_read_byte(state, TDA1004X_VBER_LSB); - if (vber == -1) - return -EIO; - status = tda1004x_read_byte(state, TDA1004X_VBER_MID); - if (status == -1) - return -EIO; - vber |= (status << 8); - status = tda1004x_read_byte(state, TDA1004X_VBER_MSB); - if (status == -1) - return -EIO; - vber |= (status & 0x0f) << 16; - // The CVBER_LUT should be read to cope with TDA10046 hardware bug - tda1004x_read_byte(state, TDA1004X_CVBER_LUT); - - // if RS has passed some valid TS packets, then we must be - // getting some SYNC bytes - if (vber < 16632) - *fe_status |= FE_HAS_SYNC; - } - - // success - dprintk("%s: fe_status=0x%x\n", __func__, *fe_status); - return 0; -} - -static int tda1004x_read_signal_strength(struct dvb_frontend* fe, u16 * signal) -{ - struct tda1004x_state* state = fe->demodulator_priv; - int tmp; - int reg = 0; - - dprintk("%s\n", __func__); - - // determine the register to use - switch (state->demod_type) { - case TDA1004X_DEMOD_TDA10045: - reg = TDA10045H_S_AGC; - break; - - case TDA1004X_DEMOD_TDA10046: - reg = TDA10046H_AGC_IF_LEVEL; - break; - } - - // read it - tmp = tda1004x_read_byte(state, reg); - if (tmp < 0) - return -EIO; - - *signal = (tmp << 8) | tmp; - dprintk("%s: signal=0x%x\n", __func__, *signal); - return 0; -} - -static int tda1004x_read_snr(struct dvb_frontend* fe, u16 * snr) -{ - struct tda1004x_state* state = fe->demodulator_priv; - int tmp; - - dprintk("%s\n", __func__); - - // read it - tmp = tda1004x_read_byte(state, TDA1004X_SNR); - if (tmp < 0) - return -EIO; - tmp = 255 - tmp; - - *snr = ((tmp << 8) | tmp); - dprintk("%s: snr=0x%x\n", __func__, *snr); - return 0; -} - -static int tda1004x_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) -{ - struct tda1004x_state* state = fe->demodulator_priv; - int tmp; - int tmp2; - int counter; - - dprintk("%s\n", __func__); - - // read the UCBLOCKS and reset - counter = 0; - tmp = tda1004x_read_byte(state, TDA1004X_UNCOR); - if (tmp < 0) - return -EIO; - tmp &= 0x7f; - while (counter++ < 5) { - tda1004x_write_mask(state, TDA1004X_UNCOR, 0x80, 0); - tda1004x_write_mask(state, TDA1004X_UNCOR, 0x80, 0); - tda1004x_write_mask(state, TDA1004X_UNCOR, 0x80, 0); - - tmp2 = tda1004x_read_byte(state, TDA1004X_UNCOR); - if (tmp2 < 0) - return -EIO; - tmp2 &= 0x7f; - if ((tmp2 < tmp) || (tmp2 == 0)) - break; - } - - if (tmp != 0x7f) - *ucblocks = tmp; - else - *ucblocks = 0xffffffff; - - dprintk("%s: ucblocks=0x%x\n", __func__, *ucblocks); - return 0; -} - -static int tda1004x_read_ber(struct dvb_frontend* fe, u32* ber) -{ - struct tda1004x_state* state = fe->demodulator_priv; - int tmp; - - dprintk("%s\n", __func__); - - // read it in - tmp = tda1004x_read_byte(state, TDA1004X_CBER_LSB); - if (tmp < 0) - return -EIO; - *ber = tmp << 1; - tmp = tda1004x_read_byte(state, TDA1004X_CBER_MSB); - if (tmp < 0) - return -EIO; - *ber |= (tmp << 9); - // The address 0x20 should be read to cope with a TDA10046 bug - tda1004x_read_byte(state, TDA1004X_CBER_RESET); - - dprintk("%s: ber=0x%x\n", __func__, *ber); - return 0; -} - -static int tda1004x_sleep(struct dvb_frontend* fe) -{ - struct tda1004x_state* state = fe->demodulator_priv; - int gpio_conf; - - switch (state->demod_type) { - case TDA1004X_DEMOD_TDA10045: - tda1004x_write_mask(state, TDA1004X_CONFADC1, 0x10, 0x10); - break; - - case TDA1004X_DEMOD_TDA10046: - /* set outputs to tristate */ - tda1004x_write_byteI(state, TDA10046H_CONF_TRISTATE1, 0xff); - /* invert GPIO 1 and 3 if desired*/ - gpio_conf = state->config->gpio_config; - if (gpio_conf >= TDA10046_GP00_I) - tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0x0f, - (gpio_conf & 0x0f) ^ 0x0a); - - tda1004x_write_mask(state, TDA1004X_CONFADC2, 0xc0, 0xc0); - tda1004x_write_mask(state, TDA1004X_CONFC4, 1, 1); - break; - } - - return 0; -} - -static int tda1004x_i2c_gate_ctrl(struct dvb_frontend* fe, int enable) -{ - struct tda1004x_state* state = fe->demodulator_priv; - - if (enable) { - return tda1004x_enable_tuner_i2c(state); - } else { - return tda1004x_disable_tuner_i2c(state); - } -} - -static int tda1004x_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) -{ - fesettings->min_delay_ms = 800; - /* Drift compensation makes no sense for DVB-T */ - fesettings->step_size = 0; - fesettings->max_drift = 0; - return 0; -} - -static void tda1004x_release(struct dvb_frontend* fe) -{ - struct tda1004x_state *state = fe->demodulator_priv; - kfree(state); -} - -static struct dvb_frontend_ops tda10045_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "Philips TDA10045H DVB-T", - .frequency_min = 51000000, - .frequency_max = 858000000, - .frequency_stepsize = 166667, - .caps = - FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | - FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO - }, - - .release = tda1004x_release, - - .init = tda10045_init, - .sleep = tda1004x_sleep, - .write = tda1004x_write, - .i2c_gate_ctrl = tda1004x_i2c_gate_ctrl, - - .set_frontend = tda1004x_set_fe, - .get_frontend = tda1004x_get_fe, - .get_tune_settings = tda1004x_get_tune_settings, - - .read_status = tda1004x_read_status, - .read_ber = tda1004x_read_ber, - .read_signal_strength = tda1004x_read_signal_strength, - .read_snr = tda1004x_read_snr, - .read_ucblocks = tda1004x_read_ucblocks, -}; - -struct dvb_frontend* tda10045_attach(const struct tda1004x_config* config, - struct i2c_adapter* i2c) -{ - struct tda1004x_state *state; - int id; - - /* allocate memory for the internal state */ - state = kzalloc(sizeof(struct tda1004x_state), GFP_KERNEL); - if (!state) { - printk(KERN_ERR "Can't allocate memory for tda10045 state\n"); - return NULL; - } - - /* setup the state */ - state->config = config; - state->i2c = i2c; - state->demod_type = TDA1004X_DEMOD_TDA10045; - - /* check if the demod is there */ - id = tda1004x_read_byte(state, TDA1004X_CHIPID); - if (id < 0) { - printk(KERN_ERR "tda10045: chip is not answering. Giving up.\n"); - kfree(state); - return NULL; - } - - if (id != 0x25) { - printk(KERN_ERR "Invalid tda1004x ID = 0x%02x. Can't proceed\n", id); - kfree(state); - return NULL; - } - - /* create dvb_frontend */ - memcpy(&state->frontend.ops, &tda10045_ops, sizeof(struct dvb_frontend_ops)); - state->frontend.demodulator_priv = state; - return &state->frontend; -} - -static struct dvb_frontend_ops tda10046_ops = { - .delsys = { SYS_DVBT }, - .info = { - .name = "Philips TDA10046H DVB-T", - .frequency_min = 51000000, - .frequency_max = 858000000, - .frequency_stepsize = 166667, - .caps = - FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | - FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO - }, - - .release = tda1004x_release, - - .init = tda10046_init, - .sleep = tda1004x_sleep, - .write = tda1004x_write, - .i2c_gate_ctrl = tda1004x_i2c_gate_ctrl, - - .set_frontend = tda1004x_set_fe, - .get_frontend = tda1004x_get_fe, - .get_tune_settings = tda1004x_get_tune_settings, - - .read_status = tda1004x_read_status, - .read_ber = tda1004x_read_ber, - .read_signal_strength = tda1004x_read_signal_strength, - .read_snr = tda1004x_read_snr, - .read_ucblocks = tda1004x_read_ucblocks, -}; - -struct dvb_frontend* tda10046_attach(const struct tda1004x_config* config, - struct i2c_adapter* i2c) -{ - struct tda1004x_state *state; - int id; - - /* allocate memory for the internal state */ - state = kzalloc(sizeof(struct tda1004x_state), GFP_KERNEL); - if (!state) { - printk(KERN_ERR "Can't allocate memory for tda10046 state\n"); - return NULL; - } - - /* setup the state */ - state->config = config; - state->i2c = i2c; - state->demod_type = TDA1004X_DEMOD_TDA10046; - - /* check if the demod is there */ - id = tda1004x_read_byte(state, TDA1004X_CHIPID); - if (id < 0) { - printk(KERN_ERR "tda10046: chip is not answering. Giving up.\n"); - kfree(state); - return NULL; - } - if (id != 0x46) { - printk(KERN_ERR "Invalid tda1004x ID = 0x%02x. Can't proceed\n", id); - kfree(state); - return NULL; - } - - /* create dvb_frontend */ - memcpy(&state->frontend.ops, &tda10046_ops, sizeof(struct dvb_frontend_ops)); - state->frontend.demodulator_priv = state; - return &state->frontend; -} - -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); - -MODULE_DESCRIPTION("Philips TDA10045H & TDA10046H DVB-T Demodulator"); -MODULE_AUTHOR("Andrew de Quincey & Robert Schlabbach"); -MODULE_LICENSE("GPL"); - -EXPORT_SYMBOL(tda10045_attach); -EXPORT_SYMBOL(tda10046_attach); diff --git a/drivers/media/dvb/frontends/tda10071.c b/drivers/media/dvb/frontends/tda10071.c deleted file mode 100644 index 703c3d05f9f4..000000000000 --- a/drivers/media/dvb/frontends/tda10071.c +++ /dev/null @@ -1,1284 +0,0 @@ -/* - * NXP TDA10071 + Conexant CX24118A DVB-S/S2 demodulator + tuner driver - * - * Copyright (C) 2011 Antti Palosaari - * - * 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. - */ - -#include "tda10071_priv.h" - -static struct dvb_frontend_ops tda10071_ops; - -/* write multiple registers */ -static int tda10071_wr_regs(struct tda10071_priv *priv, u8 reg, u8 *val, - int len) -{ - int ret; - u8 buf[len+1]; - struct i2c_msg msg[1] = { - { - .addr = priv->cfg.i2c_address, - .flags = 0, - .len = sizeof(buf), - .buf = buf, - } - }; - - buf[0] = reg; - memcpy(&buf[1], val, len); - - ret = i2c_transfer(priv->i2c, msg, 1); - if (ret == 1) { - ret = 0; - } else { - dev_warn(&priv->i2c->dev, "%s: i2c wr failed=%d reg=%02x " \ - "len=%d\n", KBUILD_MODNAME, ret, reg, len); - ret = -EREMOTEIO; - } - return ret; -} - -/* read multiple registers */ -static int tda10071_rd_regs(struct tda10071_priv *priv, u8 reg, u8 *val, - int len) -{ - int ret; - u8 buf[len]; - struct i2c_msg msg[2] = { - { - .addr = priv->cfg.i2c_address, - .flags = 0, - .len = 1, - .buf = ®, - }, { - .addr = priv->cfg.i2c_address, - .flags = I2C_M_RD, - .len = sizeof(buf), - .buf = buf, - } - }; - - ret = i2c_transfer(priv->i2c, msg, 2); - if (ret == 2) { - memcpy(val, buf, len); - ret = 0; - } else { - dev_warn(&priv->i2c->dev, "%s: i2c rd failed=%d reg=%02x " \ - "len=%d\n", KBUILD_MODNAME, ret, reg, len); - ret = -EREMOTEIO; - } - return ret; -} - -/* write single register */ -static int tda10071_wr_reg(struct tda10071_priv *priv, u8 reg, u8 val) -{ - return tda10071_wr_regs(priv, reg, &val, 1); -} - -/* read single register */ -static int tda10071_rd_reg(struct tda10071_priv *priv, u8 reg, u8 *val) -{ - return tda10071_rd_regs(priv, reg, val, 1); -} - -/* write single register with mask */ -int tda10071_wr_reg_mask(struct tda10071_priv *priv, u8 reg, u8 val, u8 mask) -{ - int ret; - u8 tmp; - - /* no need for read if whole reg is written */ - if (mask != 0xff) { - ret = tda10071_rd_regs(priv, reg, &tmp, 1); - if (ret) - return ret; - - val &= mask; - tmp &= ~mask; - val |= tmp; - } - - return tda10071_wr_regs(priv, reg, &val, 1); -} - -/* read single register with mask */ -int tda10071_rd_reg_mask(struct tda10071_priv *priv, u8 reg, u8 *val, u8 mask) -{ - int ret, i; - u8 tmp; - - ret = tda10071_rd_regs(priv, reg, &tmp, 1); - if (ret) - return ret; - - tmp &= mask; - - /* find position of the first bit */ - for (i = 0; i < 8; i++) { - if ((mask >> i) & 0x01) - break; - } - *val = tmp >> i; - - return 0; -} - -/* execute firmware command */ -static int tda10071_cmd_execute(struct tda10071_priv *priv, - struct tda10071_cmd *cmd) -{ - int ret, i; - u8 tmp; - - if (!priv->warm) { - ret = -EFAULT; - goto error; - } - - /* write cmd and args for firmware */ - ret = tda10071_wr_regs(priv, 0x00, cmd->args, cmd->len); - if (ret) - goto error; - - /* start cmd execution */ - ret = tda10071_wr_reg(priv, 0x1f, 1); - if (ret) - goto error; - - /* wait cmd execution terminate */ - for (i = 1000, tmp = 1; i && tmp; i--) { - ret = tda10071_rd_reg(priv, 0x1f, &tmp); - if (ret) - goto error; - - usleep_range(200, 5000); - } - - dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); - - if (i == 0) { - ret = -ETIMEDOUT; - goto error; - } - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_set_tone(struct dvb_frontend *fe, - fe_sec_tone_mode_t fe_sec_tone_mode) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - int ret; - u8 tone; - - if (!priv->warm) { - ret = -EFAULT; - goto error; - } - - dev_dbg(&priv->i2c->dev, "%s: tone_mode=%d\n", __func__, - fe_sec_tone_mode); - - switch (fe_sec_tone_mode) { - case SEC_TONE_ON: - tone = 1; - break; - case SEC_TONE_OFF: - tone = 0; - break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_tone_mode\n", - __func__); - ret = -EINVAL; - goto error; - } - - cmd.args[0] = CMD_LNB_PCB_CONFIG; - cmd.args[1] = 0; - cmd.args[2] = 0x00; - cmd.args[3] = 0x00; - cmd.args[4] = tone; - cmd.len = 5; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_set_voltage(struct dvb_frontend *fe, - fe_sec_voltage_t fe_sec_voltage) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - int ret; - u8 voltage; - - if (!priv->warm) { - ret = -EFAULT; - goto error; - } - - dev_dbg(&priv->i2c->dev, "%s: voltage=%d\n", __func__, fe_sec_voltage); - - switch (fe_sec_voltage) { - case SEC_VOLTAGE_13: - voltage = 0; - break; - case SEC_VOLTAGE_18: - voltage = 1; - break; - case SEC_VOLTAGE_OFF: - voltage = 0; - break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_voltage\n", - __func__); - ret = -EINVAL; - goto error; - }; - - cmd.args[0] = CMD_LNB_SET_DC_LEVEL; - cmd.args[1] = 0; - cmd.args[2] = voltage; - cmd.len = 3; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_diseqc_send_master_cmd(struct dvb_frontend *fe, - struct dvb_diseqc_master_cmd *diseqc_cmd) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - int ret, i; - u8 tmp; - - if (!priv->warm) { - ret = -EFAULT; - goto error; - } - - dev_dbg(&priv->i2c->dev, "%s: msg_len=%d\n", __func__, - diseqc_cmd->msg_len); - - if (diseqc_cmd->msg_len < 3 || diseqc_cmd->msg_len > 6) { - ret = -EINVAL; - goto error; - } - - /* wait LNB TX */ - for (i = 500, tmp = 0; i && !tmp; i--) { - ret = tda10071_rd_reg_mask(priv, 0x47, &tmp, 0x01); - if (ret) - goto error; - - usleep_range(10000, 20000); - } - - dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); - - if (i == 0) { - ret = -ETIMEDOUT; - goto error; - } - - ret = tda10071_wr_reg_mask(priv, 0x47, 0x00, 0x01); - if (ret) - goto error; - - cmd.args[0] = CMD_LNB_SEND_DISEQC; - cmd.args[1] = 0; - cmd.args[2] = 0; - cmd.args[3] = 0; - cmd.args[4] = 2; - cmd.args[5] = 0; - cmd.args[6] = diseqc_cmd->msg_len; - memcpy(&cmd.args[7], diseqc_cmd->msg, diseqc_cmd->msg_len); - cmd.len = 7 + diseqc_cmd->msg_len; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_diseqc_recv_slave_reply(struct dvb_frontend *fe, - struct dvb_diseqc_slave_reply *reply) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - int ret, i; - u8 tmp; - - if (!priv->warm) { - ret = -EFAULT; - goto error; - } - - dev_dbg(&priv->i2c->dev, "%s:\n", __func__); - - /* wait LNB RX */ - for (i = 500, tmp = 0; i && !tmp; i--) { - ret = tda10071_rd_reg_mask(priv, 0x47, &tmp, 0x02); - if (ret) - goto error; - - usleep_range(10000, 20000); - } - - dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); - - if (i == 0) { - ret = -ETIMEDOUT; - goto error; - } - - /* reply len */ - ret = tda10071_rd_reg(priv, 0x46, &tmp); - if (ret) - goto error; - - reply->msg_len = tmp & 0x1f; /* [4:0] */; - if (reply->msg_len > sizeof(reply->msg)) - reply->msg_len = sizeof(reply->msg); /* truncate API max */ - - /* read reply */ - cmd.args[0] = CMD_LNB_UPDATE_REPLY; - cmd.args[1] = 0; - cmd.len = 2; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - ret = tda10071_rd_regs(priv, cmd.len, reply->msg, reply->msg_len); - if (ret) - goto error; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_diseqc_send_burst(struct dvb_frontend *fe, - fe_sec_mini_cmd_t fe_sec_mini_cmd) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - int ret, i; - u8 tmp, burst; - - if (!priv->warm) { - ret = -EFAULT; - goto error; - } - - dev_dbg(&priv->i2c->dev, "%s: fe_sec_mini_cmd=%d\n", __func__, - fe_sec_mini_cmd); - - switch (fe_sec_mini_cmd) { - case SEC_MINI_A: - burst = 0; - break; - case SEC_MINI_B: - burst = 1; - break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_mini_cmd\n", - __func__); - ret = -EINVAL; - goto error; - } - - /* wait LNB TX */ - for (i = 500, tmp = 0; i && !tmp; i--) { - ret = tda10071_rd_reg_mask(priv, 0x47, &tmp, 0x01); - if (ret) - goto error; - - usleep_range(10000, 20000); - } - - dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i); - - if (i == 0) { - ret = -ETIMEDOUT; - goto error; - } - - ret = tda10071_wr_reg_mask(priv, 0x47, 0x00, 0x01); - if (ret) - goto error; - - cmd.args[0] = CMD_LNB_SEND_TONEBURST; - cmd.args[1] = 0; - cmd.args[2] = burst; - cmd.len = 3; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - int ret; - u8 tmp; - - *status = 0; - - if (!priv->warm) { - ret = 0; - goto error; - } - - ret = tda10071_rd_reg(priv, 0x39, &tmp); - if (ret) - goto error; - - if (tmp & 0x01) /* tuner PLL */ - *status |= FE_HAS_SIGNAL; - if (tmp & 0x02) /* demod PLL */ - *status |= FE_HAS_CARRIER; - if (tmp & 0x04) /* viterbi or LDPC*/ - *status |= FE_HAS_VITERBI; - if (tmp & 0x08) /* RS or BCH */ - *status |= FE_HAS_SYNC | FE_HAS_LOCK; - - priv->fe_status = *status; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - int ret; - u8 buf[2]; - - if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { - *snr = 0; - ret = 0; - goto error; - } - - ret = tda10071_rd_regs(priv, 0x3a, buf, 2); - if (ret) - goto error; - - /* Es/No dBx10 */ - *snr = buf[0] << 8 | buf[1]; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_read_signal_strength(struct dvb_frontend *fe, u16 *strength) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - int ret; - u8 tmp; - - if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { - *strength = 0; - ret = 0; - goto error; - } - - cmd.args[0] = CMD_GET_AGCACC; - cmd.args[1] = 0; - cmd.len = 2; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - /* input power estimate dBm */ - ret = tda10071_rd_reg(priv, 0x50, &tmp); - if (ret) - goto error; - - if (tmp < 181) - tmp = 181; /* -75 dBm */ - else if (tmp > 236) - tmp = 236; /* -20 dBm */ - - /* scale value to 0x0000-0xffff */ - *strength = (tmp-181) * 0xffff / (236-181); - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_read_ber(struct dvb_frontend *fe, u32 *ber) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - int ret, i, len; - u8 tmp, reg, buf[8]; - - if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { - *ber = priv->ber = 0; - ret = 0; - goto error; - } - - switch (priv->delivery_system) { - case SYS_DVBS: - reg = 0x4c; - len = 8; - i = 1; - break; - case SYS_DVBS2: - reg = 0x4d; - len = 4; - i = 0; - break; - default: - *ber = priv->ber = 0; - return 0; - } - - ret = tda10071_rd_reg(priv, reg, &tmp); - if (ret) - goto error; - - if (priv->meas_count[i] == tmp) { - dev_dbg(&priv->i2c->dev, "%s: meas not ready=%02x\n", __func__, - tmp); - *ber = priv->ber; - return 0; - } else { - priv->meas_count[i] = tmp; - } - - cmd.args[0] = CMD_BER_UPDATE_COUNTERS; - cmd.args[1] = 0; - cmd.args[2] = i; - cmd.len = 3; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - ret = tda10071_rd_regs(priv, cmd.len, buf, len); - if (ret) - goto error; - - if (priv->delivery_system == SYS_DVBS) { - *ber = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; - priv->ucb += (buf[4] << 8) | buf[5]; - } else { - *ber = (buf[0] << 8) | buf[1]; - } - priv->ber = *ber; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - int ret = 0; - - if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { - *ucblocks = 0; - goto error; - } - - /* UCB is updated when BER is read. Assume BER is read anyway. */ - - *ucblocks = priv->ucb; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_set_frontend(struct dvb_frontend *fe) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i; - u8 mode, rolloff, pilot, inversion, div; - - dev_dbg(&priv->i2c->dev, "%s: delivery_system=%d modulation=%d " \ - "frequency=%d symbol_rate=%d inversion=%d pilot=%d " \ - "rolloff=%d\n", __func__, c->delivery_system, c->modulation, - c->frequency, c->symbol_rate, c->inversion, c->pilot, - c->rolloff); - - priv->delivery_system = SYS_UNDEFINED; - - if (!priv->warm) { - ret = -EFAULT; - goto error; - } - - switch (c->inversion) { - case INVERSION_OFF: - inversion = 1; - break; - case INVERSION_ON: - inversion = 0; - break; - case INVERSION_AUTO: - /* 2 = auto; try first on then off - * 3 = auto; try first off then on */ - inversion = 3; - break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n", __func__); - ret = -EINVAL; - goto error; - } - - switch (c->delivery_system) { - case SYS_DVBS: - rolloff = 0; - pilot = 2; - break; - case SYS_DVBS2: - switch (c->rolloff) { - case ROLLOFF_20: - rolloff = 2; - break; - case ROLLOFF_25: - rolloff = 1; - break; - case ROLLOFF_35: - rolloff = 0; - break; - case ROLLOFF_AUTO: - default: - dev_dbg(&priv->i2c->dev, "%s: invalid rolloff\n", - __func__); - ret = -EINVAL; - goto error; - } - - switch (c->pilot) { - case PILOT_OFF: - pilot = 0; - break; - case PILOT_ON: - pilot = 1; - break; - case PILOT_AUTO: - pilot = 2; - break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid pilot\n", - __func__); - ret = -EINVAL; - goto error; - } - break; - default: - dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n", - __func__); - ret = -EINVAL; - goto error; - } - - for (i = 0, mode = 0xff; i < ARRAY_SIZE(TDA10071_MODCOD); i++) { - if (c->delivery_system == TDA10071_MODCOD[i].delivery_system && - c->modulation == TDA10071_MODCOD[i].modulation && - c->fec_inner == TDA10071_MODCOD[i].fec) { - mode = TDA10071_MODCOD[i].val; - dev_dbg(&priv->i2c->dev, "%s: mode found=%02x\n", - __func__, mode); - break; - } - } - - if (mode == 0xff) { - dev_dbg(&priv->i2c->dev, "%s: invalid parameter combination\n", - __func__); - ret = -EINVAL; - goto error; - } - - if (c->symbol_rate <= 5000000) - div = 14; - else - div = 4; - - ret = tda10071_wr_reg(priv, 0x81, div); - if (ret) - goto error; - - ret = tda10071_wr_reg(priv, 0xe3, div); - if (ret) - goto error; - - cmd.args[0] = CMD_CHANGE_CHANNEL; - cmd.args[1] = 0; - cmd.args[2] = mode; - cmd.args[3] = (c->frequency >> 16) & 0xff; - cmd.args[4] = (c->frequency >> 8) & 0xff; - cmd.args[5] = (c->frequency >> 0) & 0xff; - cmd.args[6] = ((c->symbol_rate / 1000) >> 8) & 0xff; - cmd.args[7] = ((c->symbol_rate / 1000) >> 0) & 0xff; - cmd.args[8] = (tda10071_ops.info.frequency_tolerance >> 8) & 0xff; - cmd.args[9] = (tda10071_ops.info.frequency_tolerance >> 0) & 0xff; - cmd.args[10] = rolloff; - cmd.args[11] = inversion; - cmd.args[12] = pilot; - cmd.args[13] = 0x00; - cmd.args[14] = 0x00; - cmd.len = 15; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - priv->delivery_system = c->delivery_system; - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_get_frontend(struct dvb_frontend *fe) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i; - u8 buf[5], tmp; - - if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) { - ret = -EFAULT; - goto error; - } - - ret = tda10071_rd_regs(priv, 0x30, buf, 5); - if (ret) - goto error; - - tmp = buf[0] & 0x3f; - for (i = 0; i < ARRAY_SIZE(TDA10071_MODCOD); i++) { - if (tmp == TDA10071_MODCOD[i].val) { - c->modulation = TDA10071_MODCOD[i].modulation; - c->fec_inner = TDA10071_MODCOD[i].fec; - c->delivery_system = TDA10071_MODCOD[i].delivery_system; - } - } - - switch ((buf[1] >> 0) & 0x01) { - case 0: - c->inversion = INVERSION_OFF; - break; - case 1: - c->inversion = INVERSION_ON; - break; - } - - switch ((buf[1] >> 7) & 0x01) { - case 0: - c->pilot = PILOT_OFF; - break; - case 1: - c->pilot = PILOT_ON; - break; - } - - c->frequency = (buf[2] << 16) | (buf[3] << 8) | (buf[4] << 0); - - ret = tda10071_rd_regs(priv, 0x52, buf, 3); - if (ret) - goto error; - - c->symbol_rate = (buf[0] << 16) | (buf[1] << 8) | (buf[2] << 0); - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_init(struct dvb_frontend *fe) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - int ret, i, len, remaining, fw_size; - const struct firmware *fw; - u8 *fw_file = TDA10071_DEFAULT_FIRMWARE; - u8 tmp, buf[4]; - struct tda10071_reg_val_mask tab[] = { - { 0xcd, 0x00, 0x07 }, - { 0x80, 0x00, 0x02 }, - { 0xcd, 0x00, 0xc0 }, - { 0xce, 0x00, 0x1b }, - { 0x9d, 0x00, 0x01 }, - { 0x9d, 0x00, 0x02 }, - { 0x9e, 0x00, 0x01 }, - { 0x87, 0x00, 0x80 }, - { 0xce, 0x00, 0x08 }, - { 0xce, 0x00, 0x10 }, - }; - struct tda10071_reg_val_mask tab2[] = { - { 0xf1, 0x70, 0xff }, - { 0x88, priv->cfg.pll_multiplier, 0x3f }, - { 0x89, 0x00, 0x10 }, - { 0x89, 0x10, 0x10 }, - { 0xc0, 0x01, 0x01 }, - { 0xc0, 0x00, 0x01 }, - { 0xe0, 0xff, 0xff }, - { 0xe0, 0x00, 0xff }, - { 0x96, 0x1e, 0x7e }, - { 0x8b, 0x08, 0x08 }, - { 0x8b, 0x00, 0x08 }, - { 0x8f, 0x1a, 0x7e }, - { 0x8c, 0x68, 0xff }, - { 0x8d, 0x08, 0xff }, - { 0x8e, 0x4c, 0xff }, - { 0x8f, 0x01, 0x01 }, - { 0x8b, 0x04, 0x04 }, - { 0x8b, 0x00, 0x04 }, - { 0x87, 0x05, 0x07 }, - { 0x80, 0x00, 0x20 }, - { 0xc8, 0x01, 0xff }, - { 0xb4, 0x47, 0xff }, - { 0xb5, 0x9c, 0xff }, - { 0xb6, 0x7d, 0xff }, - { 0xba, 0x00, 0x03 }, - { 0xb7, 0x47, 0xff }, - { 0xb8, 0x9c, 0xff }, - { 0xb9, 0x7d, 0xff }, - { 0xba, 0x00, 0x0c }, - { 0xc8, 0x00, 0xff }, - { 0xcd, 0x00, 0x04 }, - { 0xcd, 0x00, 0x20 }, - { 0xe8, 0x02, 0xff }, - { 0xcf, 0x20, 0xff }, - { 0x9b, 0xd7, 0xff }, - { 0x9a, 0x01, 0x03 }, - { 0xa8, 0x05, 0x0f }, - { 0xa8, 0x65, 0xf0 }, - { 0xa6, 0xa0, 0xf0 }, - { 0x9d, 0x50, 0xfc }, - { 0x9e, 0x20, 0xe0 }, - { 0xa3, 0x1c, 0x7c }, - { 0xd5, 0x03, 0x03 }, - }; - - /* firmware status */ - ret = tda10071_rd_reg(priv, 0x51, &tmp); - if (ret) - goto error; - - if (!tmp) { - /* warm state - wake up device from sleep */ - priv->warm = 1; - - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = tda10071_wr_reg_mask(priv, tab[i].reg, - tab[i].val, tab[i].mask); - if (ret) - goto error; - } - - cmd.args[0] = CMD_SET_SLEEP_MODE; - cmd.args[1] = 0; - cmd.args[2] = 0; - cmd.len = 3; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - } else { - /* cold state - try to download firmware */ - priv->warm = 0; - - /* request the firmware, this will block and timeout */ - ret = request_firmware(&fw, fw_file, priv->i2c->dev.parent); - if (ret) { - dev_err(&priv->i2c->dev, "%s: did not find the " \ - "firmware file. (%s) Please see " \ - "linux/Documentation/dvb/ for more " \ - "details on firmware-problems. (%d)\n", - KBUILD_MODNAME, fw_file, ret); - goto error; - } - - /* init */ - for (i = 0; i < ARRAY_SIZE(tab2); i++) { - ret = tda10071_wr_reg_mask(priv, tab2[i].reg, - tab2[i].val, tab2[i].mask); - if (ret) - goto error_release_firmware; - } - - /* download firmware */ - ret = tda10071_wr_reg(priv, 0xe0, 0x7f); - if (ret) - goto error_release_firmware; - - ret = tda10071_wr_reg(priv, 0xf7, 0x81); - if (ret) - goto error_release_firmware; - - ret = tda10071_wr_reg(priv, 0xf8, 0x00); - if (ret) - goto error_release_firmware; - - ret = tda10071_wr_reg(priv, 0xf9, 0x00); - if (ret) - goto error_release_firmware; - - dev_info(&priv->i2c->dev, "%s: found a '%s' in cold state, " \ - "will try to load a firmware\n", KBUILD_MODNAME, - tda10071_ops.info.name); - dev_info(&priv->i2c->dev, "%s: downloading firmware from " \ - "file '%s'\n", KBUILD_MODNAME, fw_file); - - /* do not download last byte */ - fw_size = fw->size - 1; - - for (remaining = fw_size; remaining > 0; - remaining -= (priv->cfg.i2c_wr_max - 1)) { - len = remaining; - if (len > (priv->cfg.i2c_wr_max - 1)) - len = (priv->cfg.i2c_wr_max - 1); - - ret = tda10071_wr_regs(priv, 0xfa, - (u8 *) &fw->data[fw_size - remaining], len); - if (ret) { - dev_err(&priv->i2c->dev, "%s: firmware " \ - "download failed=%d\n", - KBUILD_MODNAME, ret); - if (ret) - goto error_release_firmware; - } - } - release_firmware(fw); - - ret = tda10071_wr_reg(priv, 0xf7, 0x0c); - if (ret) - goto error; - - ret = tda10071_wr_reg(priv, 0xe0, 0x00); - if (ret) - goto error; - - /* wait firmware start */ - msleep(250); - - /* firmware status */ - ret = tda10071_rd_reg(priv, 0x51, &tmp); - if (ret) - goto error; - - if (tmp) { - dev_info(&priv->i2c->dev, "%s: firmware did not run\n", - KBUILD_MODNAME); - ret = -EFAULT; - goto error; - } else { - priv->warm = 1; - } - - cmd.args[0] = CMD_GET_FW_VERSION; - cmd.len = 1; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - ret = tda10071_rd_regs(priv, cmd.len, buf, 4); - if (ret) - goto error; - - dev_info(&priv->i2c->dev, "%s: firmware version %d.%d.%d.%d\n", - KBUILD_MODNAME, buf[0], buf[1], buf[2], buf[3]); - dev_info(&priv->i2c->dev, "%s: found a '%s' in warm state\n", - KBUILD_MODNAME, tda10071_ops.info.name); - - ret = tda10071_rd_regs(priv, 0x81, buf, 2); - if (ret) - goto error; - - cmd.args[0] = CMD_DEMOD_INIT; - cmd.args[1] = ((priv->cfg.xtal / 1000) >> 8) & 0xff; - cmd.args[2] = ((priv->cfg.xtal / 1000) >> 0) & 0xff; - cmd.args[3] = buf[0]; - cmd.args[4] = buf[1]; - cmd.args[5] = priv->cfg.pll_multiplier; - cmd.args[6] = priv->cfg.spec_inv; - cmd.args[7] = 0x00; - cmd.len = 8; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - cmd.args[0] = CMD_TUNER_INIT; - cmd.args[1] = 0x00; - cmd.args[2] = 0x00; - cmd.args[3] = 0x00; - cmd.args[4] = 0x00; - cmd.args[5] = 0x14; - cmd.args[6] = 0x00; - cmd.args[7] = 0x03; - cmd.args[8] = 0x02; - cmd.args[9] = 0x02; - cmd.args[10] = 0x00; - cmd.args[11] = 0x00; - cmd.args[12] = 0x00; - cmd.args[13] = 0x00; - cmd.args[14] = 0x00; - cmd.len = 15; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - cmd.args[0] = CMD_MPEG_CONFIG; - cmd.args[1] = 0; - cmd.args[2] = priv->cfg.ts_mode; - cmd.args[3] = 0x00; - cmd.args[4] = 0x04; - cmd.args[5] = 0x00; - cmd.len = 6; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - ret = tda10071_wr_reg_mask(priv, 0xf0, 0x01, 0x01); - if (ret) - goto error; - - cmd.args[0] = CMD_LNB_CONFIG; - cmd.args[1] = 0; - cmd.args[2] = 150; - cmd.args[3] = 3; - cmd.args[4] = 22; - cmd.args[5] = 1; - cmd.args[6] = 1; - cmd.args[7] = 30; - cmd.args[8] = 30; - cmd.args[9] = 30; - cmd.args[10] = 30; - cmd.len = 11; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - cmd.args[0] = CMD_BER_CONTROL; - cmd.args[1] = 0; - cmd.args[2] = 14; - cmd.args[3] = 14; - cmd.len = 4; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - } - - return ret; -error_release_firmware: - release_firmware(fw); -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_sleep(struct dvb_frontend *fe) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - struct tda10071_cmd cmd; - int ret, i; - struct tda10071_reg_val_mask tab[] = { - { 0xcd, 0x07, 0x07 }, - { 0x80, 0x02, 0x02 }, - { 0xcd, 0xc0, 0xc0 }, - { 0xce, 0x1b, 0x1b }, - { 0x9d, 0x01, 0x01 }, - { 0x9d, 0x02, 0x02 }, - { 0x9e, 0x01, 0x01 }, - { 0x87, 0x80, 0x80 }, - { 0xce, 0x08, 0x08 }, - { 0xce, 0x10, 0x10 }, - }; - - if (!priv->warm) { - ret = -EFAULT; - goto error; - } - - cmd.args[0] = CMD_SET_SLEEP_MODE; - cmd.args[1] = 0; - cmd.args[2] = 1; - cmd.len = 3; - ret = tda10071_cmd_execute(priv, &cmd); - if (ret) - goto error; - - for (i = 0; i < ARRAY_SIZE(tab); i++) { - ret = tda10071_wr_reg_mask(priv, tab[i].reg, tab[i].val, - tab[i].mask); - if (ret) - goto error; - } - - return ret; -error: - dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret); - return ret; -} - -static int tda10071_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s) -{ - s->min_delay_ms = 8000; - s->step_size = 0; - s->max_drift = 0; - - return 0; -} - -static void tda10071_release(struct dvb_frontend *fe) -{ - struct tda10071_priv *priv = fe->demodulator_priv; - kfree(priv); -} - -struct dvb_frontend *tda10071_attach(const struct tda10071_config *config, - struct i2c_adapter *i2c) -{ - int ret; - struct tda10071_priv *priv = NULL; - u8 tmp; - - /* allocate memory for the internal priv */ - priv = kzalloc(sizeof(struct tda10071_priv), GFP_KERNEL); - if (priv == NULL) { - ret = -ENOMEM; - goto error; - } - - /* setup the priv */ - priv->i2c = i2c; - memcpy(&priv->cfg, config, sizeof(struct tda10071_config)); - - /* chip ID */ - ret = tda10071_rd_reg(priv, 0xff, &tmp); - if (ret || tmp != 0x0f) - goto error; - - /* chip type */ - ret = tda10071_rd_reg(priv, 0xdd, &tmp); - if (ret || tmp != 0x00) - goto error; - - /* chip version */ - ret = tda10071_rd_reg(priv, 0xfe, &tmp); - if (ret || tmp != 0x01) - goto error; - - /* create dvb_frontend */ - memcpy(&priv->fe.ops, &tda10071_ops, sizeof(struct dvb_frontend_ops)); - priv->fe.demodulator_priv = priv; - - return &priv->fe; -error: - dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret); - kfree(priv); - return NULL; -} -EXPORT_SYMBOL(tda10071_attach); - -static struct dvb_frontend_ops tda10071_ops = { - .delsys = { SYS_DVBS, SYS_DVBS2 }, - .info = { - .name = "NXP TDA10071", - .frequency_min = 950000, - .frequency_max = 2150000, - .frequency_tolerance = 5000, - .symbol_rate_min = 1000000, - .symbol_rate_max = 45000000, - .caps = FE_CAN_INVERSION_AUTO | - FE_CAN_FEC_1_2 | - FE_CAN_FEC_2_3 | - FE_CAN_FEC_3_4 | - FE_CAN_FEC_4_5 | - FE_CAN_FEC_5_6 | - FE_CAN_FEC_6_7 | - FE_CAN_FEC_7_8 | - FE_CAN_FEC_8_9 | - FE_CAN_FEC_AUTO | - FE_CAN_QPSK | - FE_CAN_RECOVER | - FE_CAN_2G_MODULATION - }, - - .release = tda10071_release, - - .get_tune_settings = tda10071_get_tune_settings, - - .init = tda10071_init, - .sleep = tda10071_sleep, - - .set_frontend = tda10071_set_frontend, - .get_frontend = tda10071_get_frontend, - - .read_status = tda10071_read_status, - .read_snr = tda10071_read_snr, - .read_signal_strength = tda10071_read_signal_strength, - .read_ber = tda10071_read_ber, - .read_ucblocks = tda10071_read_ucblocks, - - .diseqc_send_master_cmd = tda10071_diseqc_send_master_cmd, - .diseqc_recv_slave_reply = tda10071_diseqc_recv_slave_reply, - .diseqc_send_burst = tda10071_diseqc_send_burst, - - .set_tone = tda10071_set_tone, - .set_voltage = tda10071_set_voltage, -}; - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("NXP TDA10071 DVB-S/S2 demodulator driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/tda10071_priv.h b/drivers/media/dvb/frontends/tda10071_priv.h deleted file mode 100644 index 0fa85cfa70c2..000000000000 --- a/drivers/media/dvb/frontends/tda10071_priv.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * NXP TDA10071 + Conexant CX24118A DVB-S/S2 demodulator + tuner driver - * - * Copyright (C) 2011 Antti Palosaari - * - * 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. - */ - -#ifndef TDA10071_PRIV -#define TDA10071_PRIV - -#include "dvb_frontend.h" -#include "tda10071.h" -#include - -struct tda10071_priv { - struct i2c_adapter *i2c; - struct dvb_frontend fe; - struct tda10071_config cfg; - - u8 meas_count[2]; - u32 ber; - u32 ucb; - fe_status_t fe_status; - fe_delivery_system_t delivery_system; - bool warm; /* FW running */ -}; - -static struct tda10071_modcod { - fe_delivery_system_t delivery_system; - fe_modulation_t modulation; - fe_code_rate_t fec; - u8 val; -} TDA10071_MODCOD[] = { - /* NBC-QPSK */ - { SYS_DVBS2, QPSK, FEC_AUTO, 0x00 }, - { SYS_DVBS2, QPSK, FEC_1_2, 0x04 }, - { SYS_DVBS2, QPSK, FEC_3_5, 0x05 }, - { SYS_DVBS2, QPSK, FEC_2_3, 0x06 }, - { SYS_DVBS2, QPSK, FEC_3_4, 0x07 }, - { SYS_DVBS2, QPSK, FEC_4_5, 0x08 }, - { SYS_DVBS2, QPSK, FEC_5_6, 0x09 }, - { SYS_DVBS2, QPSK, FEC_8_9, 0x0a }, - { SYS_DVBS2, QPSK, FEC_9_10, 0x0b }, - /* 8PSK */ - { SYS_DVBS2, PSK_8, FEC_3_5, 0x0c }, - { SYS_DVBS2, PSK_8, FEC_2_3, 0x0d }, - { SYS_DVBS2, PSK_8, FEC_3_4, 0x0e }, - { SYS_DVBS2, PSK_8, FEC_5_6, 0x0f }, - { SYS_DVBS2, PSK_8, FEC_8_9, 0x10 }, - { SYS_DVBS2, PSK_8, FEC_9_10, 0x11 }, - /* QPSK */ - { SYS_DVBS, QPSK, FEC_AUTO, 0x2d }, - { SYS_DVBS, QPSK, FEC_1_2, 0x2e }, - { SYS_DVBS, QPSK, FEC_2_3, 0x2f }, - { SYS_DVBS, QPSK, FEC_3_4, 0x30 }, - { SYS_DVBS, QPSK, FEC_5_6, 0x31 }, - { SYS_DVBS, QPSK, FEC_7_8, 0x32 }, -}; - -struct tda10071_reg_val_mask { - u8 reg; - u8 val; - u8 mask; -}; - -/* firmware filename */ -#define TDA10071_DEFAULT_FIRMWARE "dvb-fe-tda10071.fw" - -/* firmware commands */ -#define CMD_DEMOD_INIT 0x10 -#define CMD_CHANGE_CHANNEL 0x11 -#define CMD_MPEG_CONFIG 0x13 -#define CMD_TUNER_INIT 0x15 -#define CMD_GET_AGCACC 0x1a - -#define CMD_LNB_CONFIG 0x20 -#define CMD_LNB_SEND_DISEQC 0x21 -#define CMD_LNB_SET_DC_LEVEL 0x22 -#define CMD_LNB_PCB_CONFIG 0x23 -#define CMD_LNB_SEND_TONEBURST 0x24 -#define CMD_LNB_UPDATE_REPLY 0x25 - -#define CMD_GET_FW_VERSION 0x35 -#define CMD_SET_SLEEP_MODE 0x36 -#define CMD_BER_CONTROL 0x3e -#define CMD_BER_UPDATE_COUNTERS 0x3f - -/* firmare command struct */ -#define TDA10071_ARGLEN 30 -struct tda10071_cmd { - u8 args[TDA10071_ARGLEN]; - u8 len; -}; - - -#endif /* TDA10071_PRIV */ diff --git a/drivers/media/dvb/frontends/tda8261.c b/drivers/media/dvb/frontends/tda8261.c deleted file mode 100644 index 53c7d8f1df28..000000000000 --- a/drivers/media/dvb/frontends/tda8261.c +++ /dev/null @@ -1,230 +0,0 @@ -/* - TDA8261 8PSK/QPSK tuner driver - Copyright (C) Manu Abraham (abraham.manu@gmail.com) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - - -#include -#include -#include -#include - -#include "dvb_frontend.h" -#include "tda8261.h" - -struct tda8261_state { - struct dvb_frontend *fe; - struct i2c_adapter *i2c; - const struct tda8261_config *config; - - /* state cache */ - u32 frequency; - u32 bandwidth; -}; - -static int tda8261_read(struct tda8261_state *state, u8 *buf) -{ - const struct tda8261_config *config = state->config; - int err = 0; - struct i2c_msg msg = { .addr = config->addr, .flags = I2C_M_RD,.buf = buf, .len = 1 }; - - if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) - printk("%s: read error, err=%d\n", __func__, err); - - return err; -} - -static int tda8261_write(struct tda8261_state *state, u8 *buf) -{ - const struct tda8261_config *config = state->config; - int err = 0; - struct i2c_msg msg = { .addr = config->addr, .flags = 0, .buf = buf, .len = 4 }; - - if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) - printk("%s: write error, err=%d\n", __func__, err); - - return err; -} - -static int tda8261_get_status(struct dvb_frontend *fe, u32 *status) -{ - struct tda8261_state *state = fe->tuner_priv; - u8 result = 0; - int err = 0; - - *status = 0; - - if ((err = tda8261_read(state, &result)) < 0) { - printk("%s: I/O Error\n", __func__); - return err; - } - if ((result >> 6) & 0x01) { - printk("%s: Tuner Phase Locked\n", __func__); - *status = 1; - } - - return err; -} - -static const u32 div_tab[] = { 2000, 1000, 500, 250, 125 }; /* kHz */ -static const u8 ref_div[] = { 0x00, 0x01, 0x02, 0x05, 0x07 }; - -static int tda8261_get_state(struct dvb_frontend *fe, - enum tuner_param param, - struct tuner_state *tstate) -{ - struct tda8261_state *state = fe->tuner_priv; - int err = 0; - - switch (param) { - case DVBFE_TUNER_FREQUENCY: - tstate->frequency = state->frequency; - break; - case DVBFE_TUNER_BANDWIDTH: - tstate->bandwidth = 40000000; /* FIXME! need to calculate Bandwidth */ - break; - default: - printk("%s: Unknown parameter (param=%d)\n", __func__, param); - err = -EINVAL; - break; - } - - return err; -} - -static int tda8261_set_state(struct dvb_frontend *fe, - enum tuner_param param, - struct tuner_state *tstate) -{ - struct tda8261_state *state = fe->tuner_priv; - const struct tda8261_config *config = state->config; - u32 frequency, N, status = 0; - u8 buf[4]; - int err = 0; - - if (param & DVBFE_TUNER_FREQUENCY) { - /** - * N = Max VCO Frequency / Channel Spacing - * Max VCO Frequency = VCO frequency + (channel spacing - 1) - * (to account for half channel spacing on either side) - */ - frequency = tstate->frequency; - if ((frequency < 950000) || (frequency > 2150000)) { - printk("%s: Frequency beyond limits, frequency=%d\n", __func__, frequency); - return -EINVAL; - } - N = (frequency + (div_tab[config->step_size] - 1)) / div_tab[config->step_size]; - printk("%s: Step size=%d, Divider=%d, PG=0x%02x (%d)\n", - __func__, config->step_size, div_tab[config->step_size], N, N); - - buf[0] = (N >> 8) & 0xff; - buf[1] = N & 0xff; - buf[2] = (0x01 << 7) | ((ref_div[config->step_size] & 0x07) << 1); - - if (frequency < 1450000) - buf[3] = 0x00; - else if (frequency < 2000000) - buf[3] = 0x40; - else if (frequency < 2150000) - buf[3] = 0x80; - - /* Set params */ - if ((err = tda8261_write(state, buf)) < 0) { - printk("%s: I/O Error\n", __func__); - return err; - } - /* sleep for some time */ - printk("%s: Waiting to Phase LOCK\n", __func__); - msleep(20); - /* check status */ - if ((err = tda8261_get_status(fe, &status)) < 0) { - printk("%s: I/O Error\n", __func__); - return err; - } - if (status == 1) { - printk("%s: Tuner Phase locked: status=%d\n", __func__, status); - state->frequency = frequency; /* cache successful state */ - } else { - printk("%s: No Phase lock: status=%d\n", __func__, status); - } - } else { - printk("%s: Unknown parameter (param=%d)\n", __func__, param); - return -EINVAL; - } - - return 0; -} - -static int tda8261_release(struct dvb_frontend *fe) -{ - struct tda8261_state *state = fe->tuner_priv; - - fe->tuner_priv = NULL; - kfree(state); - return 0; -} - -static struct dvb_tuner_ops tda8261_ops = { - - .info = { - .name = "TDA8261", -// .tuner_name = NULL, - .frequency_min = 950000, - .frequency_max = 2150000, - .frequency_step = 0 - }, - - .set_state = tda8261_set_state, - .get_state = tda8261_get_state, - .get_status = tda8261_get_status, - .release = tda8261_release -}; - -struct dvb_frontend *tda8261_attach(struct dvb_frontend *fe, - const struct tda8261_config *config, - struct i2c_adapter *i2c) -{ - struct tda8261_state *state = NULL; - - if ((state = kzalloc(sizeof (struct tda8261_state), GFP_KERNEL)) == NULL) - goto exit; - - state->config = config; - state->i2c = i2c; - state->fe = fe; - fe->tuner_priv = state; - fe->ops.tuner_ops = tda8261_ops; - - fe->ops.tuner_ops.info.frequency_step = div_tab[config->step_size]; -// fe->ops.tuner_ops.tuner_name = &config->buf; - -// printk("%s: Attaching %s TDA8261 8PSK/QPSK tuner\n", -// __func__, fe->ops.tuner_ops.tuner_name); - printk("%s: Attaching TDA8261 8PSK/QPSK tuner\n", __func__); - - return fe; - -exit: - kfree(state); - return NULL; -} - -EXPORT_SYMBOL(tda8261_attach); - -MODULE_AUTHOR("Manu Abraham"); -MODULE_DESCRIPTION("TDA8261 8PSK/QPSK Tuner"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/mantis/Kconfig b/drivers/media/dvb/mantis/Kconfig deleted file mode 100644 index a13a50503134..000000000000 --- a/drivers/media/dvb/mantis/Kconfig +++ /dev/null @@ -1,38 +0,0 @@ -config MANTIS_CORE - tristate "Mantis/Hopper PCI bridge based devices" - depends on PCI && I2C && INPUT && RC_CORE - - help - Support for PCI cards based on the Mantis and Hopper PCi bridge. - - Say Y if you own such a device and want to use it. - -config DVB_MANTIS - tristate "MANTIS based cards" - depends on MANTIS_CORE && DVB_CORE && PCI && I2C - select DVB_MB86A16 if !DVB_FE_CUSTOMISE - select DVB_ZL10353 if !DVB_FE_CUSTOMISE - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_LNBP21 if !DVB_FE_CUSTOMISE - select DVB_STB0899 if !DVB_FE_CUSTOMISE - select DVB_STB6100 if !DVB_FE_CUSTOMISE - select DVB_TDA665x if !DVB_FE_CUSTOMISE - select DVB_TDA10021 if !DVB_FE_CUSTOMISE - select DVB_TDA10023 if !DVB_FE_CUSTOMISE - select DVB_PLL - help - Support for PCI cards based on the Mantis PCI bridge. - Say Y when you have a Mantis based DVB card and want to use it. - - If unsure say N. - -config DVB_HOPPER - tristate "HOPPER based cards" - depends on MANTIS_CORE && DVB_CORE && PCI && I2C - select DVB_ZL10353 if !DVB_FE_CUSTOMISE - select DVB_PLL - help - Support for PCI cards based on the Hopper PCI bridge. - Say Y when you have a Hopper based DVB card and want to use it. - - If unsure say N diff --git a/drivers/media/dvb/mantis/Makefile b/drivers/media/dvb/mantis/Makefile deleted file mode 100644 index ec8116dcb368..000000000000 --- a/drivers/media/dvb/mantis/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -mantis_core-objs := mantis_ioc.o \ - mantis_uart.o \ - mantis_dma.o \ - mantis_pci.o \ - mantis_i2c.o \ - mantis_dvb.o \ - mantis_evm.o \ - mantis_hif.o \ - mantis_ca.o \ - mantis_pcmcia.o \ - mantis_input.o - -mantis-objs := mantis_cards.o \ - mantis_vp1033.o \ - mantis_vp1034.o \ - mantis_vp1041.o \ - mantis_vp2033.o \ - mantis_vp2040.o \ - mantis_vp3030.o - -hopper-objs := hopper_cards.o \ - hopper_vp3028.o - -obj-$(CONFIG_MANTIS_CORE) += mantis_core.o -obj-$(CONFIG_DVB_MANTIS) += mantis.o -obj-$(CONFIG_DVB_HOPPER) += hopper.o - -ccflags-y += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/ diff --git a/drivers/media/dvb/mantis/mantis_cards.c b/drivers/media/dvb/mantis/mantis_cards.c deleted file mode 100644 index 095cf3a994e2..000000000000 --- a/drivers/media/dvb/mantis/mantis_cards.c +++ /dev/null @@ -1,307 +0,0 @@ -/* - Mantis PCI bridge driver - - Copyright (C) Manu Abraham (abraham.manu@gmail.com) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include -#include -#include -#include -#include -#include -#include - -#include "dmxdev.h" -#include "dvbdev.h" -#include "dvb_demux.h" -#include "dvb_frontend.h" -#include "dvb_net.h" - -#include "mantis_common.h" - -#include "mantis_vp1033.h" -#include "mantis_vp1034.h" -#include "mantis_vp1041.h" -#include "mantis_vp2033.h" -#include "mantis_vp2040.h" -#include "mantis_vp3030.h" - -#include "mantis_dma.h" -#include "mantis_ca.h" -#include "mantis_dvb.h" -#include "mantis_uart.h" -#include "mantis_ioc.h" -#include "mantis_pci.h" -#include "mantis_i2c.h" -#include "mantis_reg.h" - -static unsigned int verbose; -module_param(verbose, int, 0644); -MODULE_PARM_DESC(verbose, "verbose startup messages, default is 0 (no)"); - -static int devs; - -#define DRIVER_NAME "Mantis" - -static char *label[10] = { - "DMA", - "IRQ-0", - "IRQ-1", - "OCERR", - "PABRT", - "RIPRR", - "PPERR", - "FTRGT", - "RISCI", - "RACK" -}; - -static irqreturn_t mantis_irq_handler(int irq, void *dev_id) -{ - u32 stat = 0, mask = 0; - u32 rst_stat = 0, rst_mask = 0; - - struct mantis_pci *mantis; - struct mantis_ca *ca; - - mantis = (struct mantis_pci *) dev_id; - if (unlikely(mantis == NULL)) { - dprintk(MANTIS_ERROR, 1, "Mantis == NULL"); - return IRQ_NONE; - } - ca = mantis->mantis_ca; - - stat = mmread(MANTIS_INT_STAT); - mask = mmread(MANTIS_INT_MASK); - if (!(stat & mask)) - return IRQ_NONE; - - rst_mask = MANTIS_GPIF_WRACK | - MANTIS_GPIF_OTHERR | - MANTIS_SBUF_WSTO | - MANTIS_GPIF_EXTIRQ; - - rst_stat = mmread(MANTIS_GPIF_STATUS); - rst_stat &= rst_mask; - mmwrite(rst_stat, MANTIS_GPIF_STATUS); - - mantis->mantis_int_stat = stat; - mantis->mantis_int_mask = mask; - dprintk(MANTIS_DEBUG, 0, "\n-- Stat=<%02x> Mask=<%02x> --", stat, mask); - if (stat & MANTIS_INT_RISCEN) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[0]); - } - if (stat & MANTIS_INT_IRQ0) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[1]); - mantis->gpif_status = rst_stat; - wake_up(&ca->hif_write_wq); - schedule_work(&ca->hif_evm_work); - } - if (stat & MANTIS_INT_IRQ1) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[2]); - schedule_work(&mantis->uart_work); - } - if (stat & MANTIS_INT_OCERR) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[3]); - } - if (stat & MANTIS_INT_PABORT) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[4]); - } - if (stat & MANTIS_INT_RIPERR) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[5]); - } - if (stat & MANTIS_INT_PPERR) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[6]); - } - if (stat & MANTIS_INT_FTRGT) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[7]); - } - if (stat & MANTIS_INT_RISCI) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[8]); - mantis->busy_block = (stat & MANTIS_INT_RISCSTAT) >> 28; - tasklet_schedule(&mantis->tasklet); - } - if (stat & MANTIS_INT_I2CDONE) { - dprintk(MANTIS_DEBUG, 0, "<%s>", label[9]); - wake_up(&mantis->i2c_wq); - } - mmwrite(stat, MANTIS_INT_STAT); - stat &= ~(MANTIS_INT_RISCEN | MANTIS_INT_I2CDONE | - MANTIS_INT_I2CRACK | MANTIS_INT_PCMCIA7 | - MANTIS_INT_PCMCIA6 | MANTIS_INT_PCMCIA5 | - MANTIS_INT_PCMCIA4 | MANTIS_INT_PCMCIA3 | - MANTIS_INT_PCMCIA2 | MANTIS_INT_PCMCIA1 | - MANTIS_INT_PCMCIA0 | MANTIS_INT_IRQ1 | - MANTIS_INT_IRQ0 | MANTIS_INT_OCERR | - MANTIS_INT_PABORT | MANTIS_INT_RIPERR | - MANTIS_INT_PPERR | MANTIS_INT_FTRGT | - MANTIS_INT_RISCI); - - if (stat) - dprintk(MANTIS_DEBUG, 0, " Stat=<%02x> Mask=<%02x>", stat, mask); - - dprintk(MANTIS_DEBUG, 0, "\n"); - return IRQ_HANDLED; -} - -static int __devinit mantis_pci_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) -{ - struct mantis_pci *mantis; - struct mantis_hwconfig *config; - int err = 0; - - mantis = kzalloc(sizeof(struct mantis_pci), GFP_KERNEL); - if (mantis == NULL) { - printk(KERN_ERR "%s ERROR: Out of memory\n", __func__); - err = -ENOMEM; - goto fail0; - } - - mantis->num = devs; - mantis->verbose = verbose; - mantis->pdev = pdev; - config = (struct mantis_hwconfig *) pci_id->driver_data; - config->irq_handler = &mantis_irq_handler; - mantis->hwconfig = config; - - err = mantis_pci_init(mantis); - if (err) { - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis PCI initialization failed <%d>", err); - goto fail1; - } - - err = mantis_stream_control(mantis, STREAM_TO_HIF); - if (err < 0) { - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis stream control failed <%d>", err); - goto fail1; - } - - err = mantis_i2c_init(mantis); - if (err < 0) { - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis I2C initialization failed <%d>", err); - goto fail2; - } - - err = mantis_get_mac(mantis); - if (err < 0) { - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis MAC address read failed <%d>", err); - goto fail2; - } - - err = mantis_dma_init(mantis); - if (err < 0) { - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DMA initialization failed <%d>", err); - goto fail3; - } - - err = mantis_dvb_init(mantis); - if (err < 0) { - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DVB initialization failed <%d>", err); - goto fail4; - } - err = mantis_uart_init(mantis); - if (err < 0) { - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis UART initialization failed <%d>", err); - goto fail6; - } - - devs++; - - return err; - - - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis UART exit! <%d>", err); - mantis_uart_exit(mantis); - -fail6: -fail4: - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DMA exit! <%d>", err); - mantis_dma_exit(mantis); - -fail3: - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis I2C exit! <%d>", err); - mantis_i2c_exit(mantis); - -fail2: - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis PCI exit! <%d>", err); - mantis_pci_exit(mantis); - -fail1: - dprintk(MANTIS_ERROR, 1, "ERROR: Mantis free! <%d>", err); - kfree(mantis); - -fail0: - return err; -} - -static void __devexit mantis_pci_remove(struct pci_dev *pdev) -{ - struct mantis_pci *mantis = pci_get_drvdata(pdev); - - if (mantis) { - - mantis_uart_exit(mantis); - mantis_dvb_exit(mantis); - mantis_dma_exit(mantis); - mantis_i2c_exit(mantis); - mantis_pci_exit(mantis); - kfree(mantis); - } - return; -} - -static struct pci_device_id mantis_pci_table[] = { - MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_1033_DVB_S, &vp1033_config), - MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_1034_DVB_S, &vp1034_config), - MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_1041_DVB_S2, &vp1041_config), - MAKE_ENTRY(TECHNISAT, SKYSTAR_HD2_10, &vp1041_config), - MAKE_ENTRY(TECHNISAT, SKYSTAR_HD2_20, &vp1041_config), - MAKE_ENTRY(TERRATEC, CINERGY_S2_PCI_HD, &vp1041_config), - MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_2033_DVB_C, &vp2033_config), - MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_2040_DVB_C, &vp2040_config), - MAKE_ENTRY(TECHNISAT, CABLESTAR_HD2, &vp2040_config), - MAKE_ENTRY(TERRATEC, CINERGY_C, &vp2033_config), - MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_3030_DVB_T, &vp3030_config), - { } -}; - -MODULE_DEVICE_TABLE(pci, mantis_pci_table); - -static struct pci_driver mantis_pci_driver = { - .name = DRIVER_NAME, - .id_table = mantis_pci_table, - .probe = mantis_pci_probe, - .remove = mantis_pci_remove, -}; - -static int __devinit mantis_init(void) -{ - return pci_register_driver(&mantis_pci_driver); -} - -static void __devexit mantis_exit(void) -{ - return pci_unregister_driver(&mantis_pci_driver); -} - -module_init(mantis_init); -module_exit(mantis_exit); - -MODULE_DESCRIPTION("MANTIS driver"); -MODULE_AUTHOR("Manu Abraham"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/mantis/mantis_core.c b/drivers/media/dvb/mantis/mantis_core.c deleted file mode 100644 index 22524a8e6f61..000000000000 --- a/drivers/media/dvb/mantis/mantis_core.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - Mantis PCI bridge driver - - Copyright (C) Manu Abraham (abraham.manu@gmail.com) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include "mantis_common.h" -#include "mantis_core.h" -#include "mantis_vp1033.h" -#include "mantis_vp1034.h" -#include "mantis_vp1041.h" -#include "mantis_vp2033.h" -#include "mantis_vp2040.h" -#include "mantis_vp3030.h" - -static int read_eeprom_byte(struct mantis_pci *mantis, u8 *data, u8 length) -{ - int err; - struct i2c_msg msg[] = { - { - .addr = 0x50, - .flags = 0, - .buf = data, - .len = 1 - }, { - .addr = 0x50, - .flags = I2C_M_RD, - .buf = data, - .len = length - }, - }; - - err = i2c_transfer(&mantis->adapter, msg, 2); - if (err < 0) { - dprintk(verbose, MANTIS_ERROR, 1, - "ERROR: i2c read: < err=%i d0=0x%02x d1=0x%02x >", - err, data[0], data[1]); - - return err; - } - - return 0; -} - -static int write_eeprom_byte(struct mantis_pci *mantis, u8 *data, u8 length) -{ - int err; - - struct i2c_msg msg = { - .addr = 0x50, - .flags = 0, - .buf = data, - .len = length - }; - - err = i2c_transfer(&mantis->adapter, &msg, 1); - if (err < 0) { - dprintk(verbose, MANTIS_ERROR, 1, - "ERROR: i2c write: < err=%i length=0x%02x d0=0x%02x, d1=0x%02x >", - err, length, data[0], data[1]); - - return err; - } - - return 0; -} - -static int get_mac_address(struct mantis_pci *mantis) -{ - int err; - - mantis->mac_address[0] = 0x08; - err = read_eeprom_byte(mantis, &mantis->mac_address[0], 6); - if (err < 0) { - dprintk(verbose, MANTIS_ERROR, 1, "Mantis EEPROM read error"); - - return err; - } - dprintk(verbose, MANTIS_ERROR, 0, - " MAC Address=[%pM]\n", mantis->mac_address); - - return 0; -} - -#define MANTIS_MODEL_UNKNOWN "UNKNOWN" -#define MANTIS_DEV_UNKNOWN "UNKNOWN" - -struct mantis_hwconfig unknown_device = { - .model_name = MANTIS_MODEL_UNKNOWN, - .dev_type = MANTIS_DEV_UNKNOWN, -}; - -static void mantis_load_config(struct mantis_pci *mantis) -{ - switch (mantis->subsystem_device) { - case MANTIS_VP_1033_DVB_S: /* VP-1033 */ - mantis->hwconfig = &vp1033_mantis_config; - break; - case MANTIS_VP_1034_DVB_S: /* VP-1034 */ - mantis->hwconfig = &vp1034_mantis_config; - break; - case MANTIS_VP_1041_DVB_S2: /* VP-1041 */ - case TECHNISAT_SKYSTAR_HD2: - mantis->hwconfig = &vp1041_mantis_config; - break; - case MANTIS_VP_2033_DVB_C: /* VP-2033 */ - mantis->hwconfig = &vp2033_mantis_config; - break; - case MANTIS_VP_2040_DVB_C: /* VP-2040 */ - case TERRATEC_CINERGY_C_PCI: /* VP-2040 clone */ - case TECHNISAT_CABLESTAR_HD2: - mantis->hwconfig = &vp2040_mantis_config; - break; - case MANTIS_VP_3030_DVB_T: /* VP-3030 */ - mantis->hwconfig = &vp3030_mantis_config; - break; - default: - mantis->hwconfig = &unknown_device; - break; - } -} - -int mantis_core_init(struct mantis_pci *mantis) -{ - int err = 0; - - mantis_load_config(mantis); - dprintk(verbose, MANTIS_ERROR, 0, "found a %s PCI %s device on (%02x:%02x.%x),\n", - mantis->hwconfig->model_name, mantis->hwconfig->dev_type, - mantis->pdev->bus->number, PCI_SLOT(mantis->pdev->devfn), PCI_FUNC(mantis->pdev->devfn)); - dprintk(verbose, MANTIS_ERROR, 0, " Mantis Rev %d [%04x:%04x], ", - mantis->revision, - mantis->subsystem_vendor, mantis->subsystem_device); - dprintk(verbose, MANTIS_ERROR, 0, - "irq: %d, latency: %d\n memory: 0x%lx, mmio: 0x%p\n", - mantis->pdev->irq, mantis->latency, - mantis->mantis_addr, mantis->mantis_mmio); - - err = mantis_i2c_init(mantis); - if (err < 0) { - dprintk(verbose, MANTIS_ERROR, 1, "Mantis I2C init failed"); - return err; - } - err = get_mac_address(mantis); - if (err < 0) { - dprintk(verbose, MANTIS_ERROR, 1, "get MAC address failed"); - return err; - } - err = mantis_dma_init(mantis); - if (err < 0) { - dprintk(verbose, MANTIS_ERROR, 1, "Mantis DMA init failed"); - return err; - } - err = mantis_dvb_init(mantis); - if (err < 0) { - dprintk(verbose, MANTIS_DEBUG, 1, "Mantis DVB init failed"); - return err; - } - err = mantis_uart_init(mantis); - if (err < 0) { - dprintk(verbose, MANTIS_DEBUG, 1, "Mantis UART init failed"); - return err; - } - - return 0; -} - -int mantis_core_exit(struct mantis_pci *mantis) -{ - mantis_dma_stop(mantis); - dprintk(verbose, MANTIS_ERROR, 1, "DMA engine stopping"); - - mantis_uart_exit(mantis); - dprintk(verbose, MANTIS_ERROR, 1, "UART exit failed"); - - if (mantis_dma_exit(mantis) < 0) - dprintk(verbose, MANTIS_ERROR, 1, "DMA exit failed"); - if (mantis_dvb_exit(mantis) < 0) - dprintk(verbose, MANTIS_ERROR, 1, "DVB exit failed"); - if (mantis_i2c_exit(mantis) < 0) - dprintk(verbose, MANTIS_ERROR, 1, "I2C adapter delete.. failed"); - - return 0; -} - -/* Turn the given bit on or off. */ -void gpio_set_bits(struct mantis_pci *mantis, u32 bitpos, u8 value) -{ - u32 cur; - - cur = mmread(MANTIS_GPIF_ADDR); - if (value) - mantis->gpio_status = cur | (1 << bitpos); - else - mantis->gpio_status = cur & (~(1 << bitpos)); - - mmwrite(mantis->gpio_status, MANTIS_GPIF_ADDR); - mmwrite(0x00, MANTIS_GPIF_DOUT); - udelay(100); -} - -/* direction = 0 , no CI passthrough ; 1 , CI passthrough */ -void mantis_set_direction(struct mantis_pci *mantis, int direction) -{ - u32 reg; - - reg = mmread(0x28); - dprintk(verbose, MANTIS_DEBUG, 1, "TS direction setup"); - if (direction == 0x01) { - /* to CI */ - reg |= 0x04; - mmwrite(reg, 0x28); - reg &= 0xff - 0x04; - mmwrite(reg, 0x28); - } else { - reg &= 0xff - 0x04; - mmwrite(reg, 0x28); - reg |= 0x04; - mmwrite(reg, 0x28); - } -} diff --git a/drivers/media/dvb/mantis/mantis_dvb.c b/drivers/media/dvb/mantis/mantis_dvb.c deleted file mode 100644 index e5180e45d310..000000000000 --- a/drivers/media/dvb/mantis/mantis_dvb.c +++ /dev/null @@ -1,299 +0,0 @@ -/* - Mantis PCI bridge driver - Copyright (C) Manu Abraham (abraham.manu@gmail.com) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#include -#include - -#include -#include -#include -#include -#include - -#include "dmxdev.h" -#include "dvbdev.h" -#include "dvb_demux.h" -#include "dvb_frontend.h" -#include "dvb_net.h" - -#include "mantis_common.h" -#include "mantis_dma.h" -#include "mantis_ca.h" -#include "mantis_ioc.h" -#include "mantis_dvb.h" - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -int mantis_frontend_power(struct mantis_pci *mantis, enum mantis_power power) -{ - struct mantis_hwconfig *config = mantis->hwconfig; - - switch (power) { - case POWER_ON: - dprintk(MANTIS_DEBUG, 1, "Power ON"); - mantis_gpio_set_bits(mantis, config->power, POWER_ON); - msleep(100); - mantis_gpio_set_bits(mantis, config->power, POWER_ON); - msleep(100); - break; - - case POWER_OFF: - dprintk(MANTIS_DEBUG, 1, "Power OFF"); - mantis_gpio_set_bits(mantis, config->power, POWER_OFF); - msleep(100); - break; - - default: - dprintk(MANTIS_DEBUG, 1, "Unknown state <%02x>", power); - return -1; - } - - return 0; -} -EXPORT_SYMBOL_GPL(mantis_frontend_power); - -void mantis_frontend_soft_reset(struct mantis_pci *mantis) -{ - struct mantis_hwconfig *config = mantis->hwconfig; - - dprintk(MANTIS_DEBUG, 1, "Frontend RESET"); - mantis_gpio_set_bits(mantis, config->reset, 0); - msleep(100); - mantis_gpio_set_bits(mantis, config->reset, 0); - msleep(100); - mantis_gpio_set_bits(mantis, config->reset, 1); - msleep(100); - mantis_gpio_set_bits(mantis, config->reset, 1); - msleep(100); - - return; -} -EXPORT_SYMBOL_GPL(mantis_frontend_soft_reset); - -static int mantis_frontend_shutdown(struct mantis_pci *mantis) -{ - int err; - - mantis_frontend_soft_reset(mantis); - err = mantis_frontend_power(mantis, POWER_OFF); - if (err != 0) { - dprintk(MANTIS_ERROR, 1, "Frontend POWER OFF failed! <%d>", err); - return 1; - } - - return 0; -} - -static int mantis_dvb_start_feed(struct dvb_demux_feed *dvbdmxfeed) -{ - struct dvb_demux *dvbdmx = dvbdmxfeed->demux; - struct mantis_pci *mantis = dvbdmx->priv; - - dprintk(MANTIS_DEBUG, 1, "Mantis DVB Start feed"); - if (!dvbdmx->dmx.frontend) { - dprintk(MANTIS_DEBUG, 1, "no frontend ?"); - return -EINVAL; - } - - mantis->feeds++; - dprintk(MANTIS_DEBUG, 1, "mantis start feed, feeds=%d", mantis->feeds); - - if (mantis->feeds == 1) { - dprintk(MANTIS_DEBUG, 1, "mantis start feed & dma"); - mantis_dma_start(mantis); - tasklet_enable(&mantis->tasklet); - } - - return mantis->feeds; -} - -static int mantis_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) -{ - struct dvb_demux *dvbdmx = dvbdmxfeed->demux; - struct mantis_pci *mantis = dvbdmx->priv; - - dprintk(MANTIS_DEBUG, 1, "Mantis DVB Stop feed"); - if (!dvbdmx->dmx.frontend) { - dprintk(MANTIS_DEBUG, 1, "no frontend ?"); - return -EINVAL; - } - - mantis->feeds--; - if (mantis->feeds == 0) { - dprintk(MANTIS_DEBUG, 1, "mantis stop feed and dma"); - tasklet_disable(&mantis->tasklet); - mantis_dma_stop(mantis); - } - - return 0; -} - -int __devinit mantis_dvb_init(struct mantis_pci *mantis) -{ - struct mantis_hwconfig *config = mantis->hwconfig; - int result = -1; - - dprintk(MANTIS_DEBUG, 1, "dvb_register_adapter"); - - result = dvb_register_adapter(&mantis->dvb_adapter, - "Mantis DVB adapter", - THIS_MODULE, - &mantis->pdev->dev, - adapter_nr); - - if (result < 0) { - - dprintk(MANTIS_ERROR, 1, "Error registering adapter"); - return -ENODEV; - } - - mantis->dvb_adapter.priv = mantis; - mantis->demux.dmx.capabilities = DMX_TS_FILTERING | - DMX_SECTION_FILTERING | - DMX_MEMORY_BASED_FILTERING; - - mantis->demux.priv = mantis; - mantis->demux.filternum = 256; - mantis->demux.feednum = 256; - mantis->demux.start_feed = mantis_dvb_start_feed; - mantis->demux.stop_feed = mantis_dvb_stop_feed; - mantis->demux.write_to_decoder = NULL; - - dprintk(MANTIS_DEBUG, 1, "dvb_dmx_init"); - result = dvb_dmx_init(&mantis->demux); - if (result < 0) { - dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); - - goto err0; - } - - mantis->dmxdev.filternum = 256; - mantis->dmxdev.demux = &mantis->demux.dmx; - mantis->dmxdev.capabilities = 0; - dprintk(MANTIS_DEBUG, 1, "dvb_dmxdev_init"); - - result = dvb_dmxdev_init(&mantis->dmxdev, &mantis->dvb_adapter); - if (result < 0) { - - dprintk(MANTIS_ERROR, 1, "dvb_dmxdev_init failed, ERROR=%d", result); - goto err1; - } - - mantis->fe_hw.source = DMX_FRONTEND_0; - result = mantis->demux.dmx.add_frontend(&mantis->demux.dmx, &mantis->fe_hw); - if (result < 0) { - - dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); - goto err2; - } - - mantis->fe_mem.source = DMX_MEMORY_FE; - result = mantis->demux.dmx.add_frontend(&mantis->demux.dmx, &mantis->fe_mem); - if (result < 0) { - dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); - goto err3; - } - - result = mantis->demux.dmx.connect_frontend(&mantis->demux.dmx, &mantis->fe_hw); - if (result < 0) { - dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); - goto err4; - } - - dvb_net_init(&mantis->dvb_adapter, &mantis->dvbnet, &mantis->demux.dmx); - tasklet_init(&mantis->tasklet, mantis_dma_xfer, (unsigned long) mantis); - tasklet_disable(&mantis->tasklet); - if (mantis->hwconfig) { - result = config->frontend_init(mantis, mantis->fe); - if (result < 0) { - dprintk(MANTIS_ERROR, 1, "!!! NO Frontends found !!!"); - goto err5; - } else { - if (mantis->fe == NULL) { - dprintk(MANTIS_ERROR, 1, "FE "); - goto err5; - } - - if (dvb_register_frontend(&mantis->dvb_adapter, mantis->fe)) { - dprintk(MANTIS_ERROR, 1, "ERROR: Frontend registration failed"); - - if (mantis->fe->ops.release) - mantis->fe->ops.release(mantis->fe); - - mantis->fe = NULL; - goto err5; - } - } - } - - return 0; - - /* Error conditions .. */ -err5: - tasklet_kill(&mantis->tasklet); - dvb_net_release(&mantis->dvbnet); - dvb_unregister_frontend(mantis->fe); - dvb_frontend_detach(mantis->fe); -err4: - mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_mem); - -err3: - mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_hw); - -err2: - dvb_dmxdev_release(&mantis->dmxdev); - -err1: - dvb_dmx_release(&mantis->demux); - -err0: - dvb_unregister_adapter(&mantis->dvb_adapter); - - return result; -} -EXPORT_SYMBOL_GPL(mantis_dvb_init); - -int __devexit mantis_dvb_exit(struct mantis_pci *mantis) -{ - int err; - - if (mantis->fe) { - /* mantis_ca_exit(mantis); */ - err = mantis_frontend_shutdown(mantis); - if (err != 0) - dprintk(MANTIS_ERROR, 1, "Frontend exit while POWER ON! <%d>", err); - dvb_unregister_frontend(mantis->fe); - dvb_frontend_detach(mantis->fe); - } - - tasklet_kill(&mantis->tasklet); - dvb_net_release(&mantis->dvbnet); - - mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_mem); - mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_hw); - - dvb_dmxdev_release(&mantis->dmxdev); - dvb_dmx_release(&mantis->demux); - - dprintk(MANTIS_DEBUG, 1, "dvb_unregister_adapter"); - dvb_unregister_adapter(&mantis->dvb_adapter); - - return 0; -} -EXPORT_SYMBOL_GPL(mantis_dvb_exit); diff --git a/drivers/media/dvb/ngene/Kconfig b/drivers/media/dvb/ngene/Kconfig deleted file mode 100644 index 64c84702ba5c..000000000000 --- a/drivers/media/dvb/ngene/Kconfig +++ /dev/null @@ -1,13 +0,0 @@ -config DVB_NGENE - tristate "Micronas nGene support" - depends on DVB_CORE && PCI && I2C - select DVB_LNBP21 if !DVB_FE_CUSTOMISE - select DVB_STV6110x if !DVB_FE_CUSTOMISE - select DVB_STV090x if !DVB_FE_CUSTOMISE - select DVB_LGDT330X if !DVB_FE_CUSTOMISE - select DVB_DRXK if !DVB_FE_CUSTOMISE - select DVB_TDA18271C2DD if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_MT2131 if !MEDIA_TUNER_CUSTOMISE - ---help--- - Support for Micronas PCI express cards with nGene bridge. - diff --git a/drivers/media/dvb/ngene/Makefile b/drivers/media/dvb/ngene/Makefile deleted file mode 100644 index 13ebeffb705f..000000000000 --- a/drivers/media/dvb/ngene/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# Makefile for the nGene device driver -# - -ngene-objs := ngene-core.o ngene-i2c.o ngene-cards.o ngene-dvb.o - -obj-$(CONFIG_DVB_NGENE) += ngene.o - -ccflags-y += -Idrivers/media/dvb/dvb-core/ -ccflags-y += -Idrivers/media/dvb/frontends/ -ccflags-y += -Idrivers/media/common/tuners/ - -# For the staging CI driver cxd2099 -ccflags-y += -Idrivers/staging/media/cxd2099/ diff --git a/drivers/media/dvb/ngene/ngene-cards.c b/drivers/media/dvb/ngene/ngene-cards.c deleted file mode 100644 index 0a497be97af8..000000000000 --- a/drivers/media/dvb/ngene/ngene-cards.c +++ /dev/null @@ -1,560 +0,0 @@ -/* - * ngene-cards.c: nGene PCIe bridge driver - card specific info - * - * Copyright (C) 2005-2007 Micronas - * - * Copyright (C) 2008-2009 Ralph Metzler - * Modifications for new nGene firmware, - * support for EEPROM-copying, - * support for new dual DVB-S2 card prototype - * - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 only, as published by the Free Software Foundation. - * - * - * 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 - * Or, point your browser to http://www.gnu.org/copyleft/gpl.html - */ - -#include -#include -#include -#include - -#include "ngene.h" - -/* demods/tuners */ -#include "stv6110x.h" -#include "stv090x.h" -#include "lnbh24.h" -#include "lgdt330x.h" -#include "mt2131.h" -#include "tda18271c2dd.h" -#include "drxk.h" - - -/****************************************************************************/ -/* Demod/tuner attachment ***************************************************/ -/****************************************************************************/ - -static int tuner_attach_stv6110(struct ngene_channel *chan) -{ - struct i2c_adapter *i2c; - struct stv090x_config *feconf = (struct stv090x_config *) - chan->dev->card_info->fe_config[chan->number]; - struct stv6110x_config *tunerconf = (struct stv6110x_config *) - chan->dev->card_info->tuner_config[chan->number]; - struct stv6110x_devctl *ctl; - - /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ - if (chan->number < 2) - i2c = &chan->dev->channel[0].i2c_adapter; - else - i2c = &chan->dev->channel[1].i2c_adapter; - - ctl = dvb_attach(stv6110x_attach, chan->fe, tunerconf, i2c); - if (ctl == NULL) { - printk(KERN_ERR DEVICE_NAME ": No STV6110X found!\n"); - return -ENODEV; - } - - feconf->tuner_init = ctl->tuner_init; - feconf->tuner_sleep = ctl->tuner_sleep; - feconf->tuner_set_mode = ctl->tuner_set_mode; - feconf->tuner_set_frequency = ctl->tuner_set_frequency; - feconf->tuner_get_frequency = ctl->tuner_get_frequency; - feconf->tuner_set_bandwidth = ctl->tuner_set_bandwidth; - feconf->tuner_get_bandwidth = ctl->tuner_get_bandwidth; - feconf->tuner_set_bbgain = ctl->tuner_set_bbgain; - feconf->tuner_get_bbgain = ctl->tuner_get_bbgain; - feconf->tuner_set_refclk = ctl->tuner_set_refclk; - feconf->tuner_get_status = ctl->tuner_get_status; - - return 0; -} - - -static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) -{ - struct ngene_channel *chan = fe->sec_priv; - int status; - - if (enable) { - down(&chan->dev->pll_mutex); - status = chan->gate_ctrl(fe, 1); - } else { - status = chan->gate_ctrl(fe, 0); - up(&chan->dev->pll_mutex); - } - return status; -} - -static int tuner_attach_tda18271(struct ngene_channel *chan) -{ - struct i2c_adapter *i2c; - struct dvb_frontend *fe; - - i2c = &chan->dev->channel[0].i2c_adapter; - if (chan->fe->ops.i2c_gate_ctrl) - chan->fe->ops.i2c_gate_ctrl(chan->fe, 1); - fe = dvb_attach(tda18271c2dd_attach, chan->fe, i2c, 0x60); - if (chan->fe->ops.i2c_gate_ctrl) - chan->fe->ops.i2c_gate_ctrl(chan->fe, 0); - if (!fe) { - printk(KERN_ERR "No TDA18271 found!\n"); - return -ENODEV; - } - - return 0; -} - -static int tuner_attach_probe(struct ngene_channel *chan) -{ - if (chan->demod_type == 0) - return tuner_attach_stv6110(chan); - if (chan->demod_type == 1) - return tuner_attach_tda18271(chan); - return -EINVAL; -} - -static int demod_attach_stv0900(struct ngene_channel *chan) -{ - struct i2c_adapter *i2c; - struct stv090x_config *feconf = (struct stv090x_config *) - chan->dev->card_info->fe_config[chan->number]; - - /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ - /* Note: Both adapters share the same i2c bus, but the demod */ - /* driver requires that each demod has its own i2c adapter */ - if (chan->number < 2) - i2c = &chan->dev->channel[0].i2c_adapter; - else - i2c = &chan->dev->channel[1].i2c_adapter; - - chan->fe = dvb_attach(stv090x_attach, feconf, i2c, - (chan->number & 1) == 0 ? STV090x_DEMODULATOR_0 - : STV090x_DEMODULATOR_1); - if (chan->fe == NULL) { - printk(KERN_ERR DEVICE_NAME ": No STV0900 found!\n"); - return -ENODEV; - } - - /* store channel info */ - if (feconf->tuner_i2c_lock) - chan->fe->analog_demod_priv = chan; - - if (!dvb_attach(lnbh24_attach, chan->fe, i2c, 0, - 0, chan->dev->card_info->lnb[chan->number])) { - printk(KERN_ERR DEVICE_NAME ": No LNBH24 found!\n"); - dvb_frontend_detach(chan->fe); - chan->fe = NULL; - return -ENODEV; - } - - return 0; -} - -static void cineS2_tuner_i2c_lock(struct dvb_frontend *fe, int lock) -{ - struct ngene_channel *chan = fe->analog_demod_priv; - - if (lock) - down(&chan->dev->pll_mutex); - else - up(&chan->dev->pll_mutex); -} - -static int i2c_read(struct i2c_adapter *adapter, u8 adr, u8 *val) -{ - struct i2c_msg msgs[1] = {{.addr = adr, .flags = I2C_M_RD, - .buf = val, .len = 1 } }; - return (i2c_transfer(adapter, msgs, 1) == 1) ? 0 : -1; -} - -static int i2c_read_reg16(struct i2c_adapter *adapter, u8 adr, - u16 reg, u8 *val) -{ - u8 msg[2] = {reg>>8, reg&0xff}; - struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, - .buf = msg, .len = 2}, - {.addr = adr, .flags = I2C_M_RD, - .buf = val, .len = 1} }; - return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; -} - -static int port_has_stv0900(struct i2c_adapter *i2c, int port) -{ - u8 val; - if (i2c_read_reg16(i2c, 0x68+port/2, 0xf100, &val) < 0) - return 0; - return 1; -} - -static int port_has_drxk(struct i2c_adapter *i2c, int port) -{ - u8 val; - - if (i2c_read(i2c, 0x29+port, &val) < 0) - return 0; - return 1; -} - -static int demod_attach_drxk(struct ngene_channel *chan, - struct i2c_adapter *i2c) -{ - struct drxk_config config; - - memset(&config, 0, sizeof(config)); - config.microcode_name = "drxk_a3.mc"; - config.qam_demod_parameter_count = 4; - config.adr = 0x29 + (chan->number ^ 2); - - chan->fe = dvb_attach(drxk_attach, &config, i2c); - if (!chan->fe) { - printk(KERN_ERR "No DRXK found!\n"); - return -ENODEV; - } - chan->fe->sec_priv = chan; - chan->gate_ctrl = chan->fe->ops.i2c_gate_ctrl; - chan->fe->ops.i2c_gate_ctrl = drxk_gate_ctrl; - return 0; -} - -static int cineS2_probe(struct ngene_channel *chan) -{ - struct i2c_adapter *i2c; - struct stv090x_config *fe_conf; - u8 buf[3]; - struct i2c_msg i2c_msg = { .flags = 0, .buf = buf }; - int rc; - - /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ - if (chan->number < 2) - i2c = &chan->dev->channel[0].i2c_adapter; - else - i2c = &chan->dev->channel[1].i2c_adapter; - - if (port_has_stv0900(i2c, chan->number)) { - chan->demod_type = 0; - fe_conf = chan->dev->card_info->fe_config[chan->number]; - /* demod found, attach it */ - rc = demod_attach_stv0900(chan); - if (rc < 0 || chan->number < 2) - return rc; - - /* demod #2: reprogram outputs DPN1 & DPN2 */ - i2c_msg.addr = fe_conf->address; - i2c_msg.len = 3; - buf[0] = 0xf1; - switch (chan->number) { - case 2: - buf[1] = 0x5c; - buf[2] = 0xc2; - break; - case 3: - buf[1] = 0x61; - buf[2] = 0xcc; - break; - default: - return -ENODEV; - } - rc = i2c_transfer(i2c, &i2c_msg, 1); - if (rc != 1) { - printk(KERN_ERR DEVICE_NAME ": could not setup DPNx\n"); - return -EIO; - } - } else if (port_has_drxk(i2c, chan->number^2)) { - chan->demod_type = 1; - demod_attach_drxk(chan, i2c); - } else { - printk(KERN_ERR "No demod found on chan %d\n", chan->number); - return -ENODEV; - } - return 0; -} - - -static struct lgdt330x_config aver_m780 = { - .demod_address = 0xb2 >> 1, - .demod_chip = LGDT3303, - .serial_mpeg = 0x00, /* PARALLEL */ - .clock_polarity_flip = 1, -}; - -static struct mt2131_config m780_tunerconfig = { - 0xc0 >> 1 -}; - -/* A single func to attach the demo and tuner, rather than - * use two sep funcs like the current design mandates. - */ -static int demod_attach_lg330x(struct ngene_channel *chan) -{ - chan->fe = dvb_attach(lgdt330x_attach, &aver_m780, &chan->i2c_adapter); - if (chan->fe == NULL) { - printk(KERN_ERR DEVICE_NAME ": No LGDT330x found!\n"); - return -ENODEV; - } - - dvb_attach(mt2131_attach, chan->fe, &chan->i2c_adapter, - &m780_tunerconfig, 0); - - return (chan->fe) ? 0 : -ENODEV; -} - -/****************************************************************************/ -/* Switch control (I2C gates, etc.) *****************************************/ -/****************************************************************************/ - - -static struct stv090x_config fe_cineS2 = { - .device = STV0900, - .demod_mode = STV090x_DUAL, - .clk_mode = STV090x_CLK_EXT, - - .xtal = 27000000, - .address = 0x68, - - .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, - .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, - - .repeater_level = STV090x_RPTLEVEL_16, - - .adc1_range = STV090x_ADC_1Vpp, - .adc2_range = STV090x_ADC_1Vpp, - - .diseqc_envelope_mode = true, - - .tuner_i2c_lock = cineS2_tuner_i2c_lock, -}; - -static struct stv090x_config fe_cineS2_2 = { - .device = STV0900, - .demod_mode = STV090x_DUAL, - .clk_mode = STV090x_CLK_EXT, - - .xtal = 27000000, - .address = 0x69, - - .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, - .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, - - .repeater_level = STV090x_RPTLEVEL_16, - - .adc1_range = STV090x_ADC_1Vpp, - .adc2_range = STV090x_ADC_1Vpp, - - .diseqc_envelope_mode = true, - - .tuner_i2c_lock = cineS2_tuner_i2c_lock, -}; - -static struct stv6110x_config tuner_cineS2_0 = { - .addr = 0x60, - .refclk = 27000000, - .clk_div = 1, -}; - -static struct stv6110x_config tuner_cineS2_1 = { - .addr = 0x63, - .refclk = 27000000, - .clk_div = 1, -}; - -static struct ngene_info ngene_info_cineS2 = { - .type = NGENE_SIDEWINDER, - .name = "Linux4Media cineS2 DVB-S2 Twin Tuner", - .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, - .demod_attach = {demod_attach_stv0900, demod_attach_stv0900}, - .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110}, - .fe_config = {&fe_cineS2, &fe_cineS2}, - .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, - .lnb = {0x0b, 0x08}, - .tsf = {3, 3}, - .fw_version = 18, - .msi_supported = true, -}; - -static struct ngene_info ngene_info_satixS2 = { - .type = NGENE_SIDEWINDER, - .name = "Mystique SaTiX-S2 Dual", - .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, - .demod_attach = {demod_attach_stv0900, demod_attach_stv0900}, - .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110}, - .fe_config = {&fe_cineS2, &fe_cineS2}, - .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, - .lnb = {0x0b, 0x08}, - .tsf = {3, 3}, - .fw_version = 18, - .msi_supported = true, -}; - -static struct ngene_info ngene_info_satixS2v2 = { - .type = NGENE_SIDEWINDER, - .name = "Mystique SaTiX-S2 Dual (v2)", - .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, - NGENE_IO_TSOUT}, - .demod_attach = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe}, - .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_probe, tuner_attach_probe}, - .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, - .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, - .lnb = {0x0a, 0x08, 0x0b, 0x09}, - .tsf = {3, 3}, - .fw_version = 18, - .msi_supported = true, -}; - -static struct ngene_info ngene_info_cineS2v5 = { - .type = NGENE_SIDEWINDER, - .name = "Linux4Media cineS2 DVB-S2 Twin Tuner (v5)", - .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, - NGENE_IO_TSOUT}, - .demod_attach = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe}, - .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_probe, tuner_attach_probe}, - .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, - .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, - .lnb = {0x0a, 0x08, 0x0b, 0x09}, - .tsf = {3, 3}, - .fw_version = 18, - .msi_supported = true, -}; - - -static struct ngene_info ngene_info_duoFlex = { - .type = NGENE_SIDEWINDER, - .name = "Digital Devices DuoFlex PCIe or miniPCIe", - .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, - NGENE_IO_TSOUT}, - .demod_attach = {cineS2_probe, cineS2_probe, cineS2_probe, cineS2_probe}, - .tuner_attach = {tuner_attach_probe, tuner_attach_probe, tuner_attach_probe, tuner_attach_probe}, - .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, - .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, - .lnb = {0x0a, 0x08, 0x0b, 0x09}, - .tsf = {3, 3}, - .fw_version = 18, - .msi_supported = true, -}; - -static struct ngene_info ngene_info_m780 = { - .type = NGENE_APP, - .name = "Aver M780 ATSC/QAM-B", - - /* Channel 0 is analog, which is currently unsupported */ - .io_type = { NGENE_IO_NONE, NGENE_IO_TSIN }, - .demod_attach = { NULL, demod_attach_lg330x }, - - /* Ensure these are NULL else the frame will call them (as funcs) */ - .tuner_attach = { 0, 0, 0, 0 }, - .fe_config = { NULL, &aver_m780 }, - .avf = { 0 }, - - /* A custom electrical interface config for the demod to bridge */ - .tsf = { 4, 4 }, - .fw_version = 15, -}; - -/****************************************************************************/ - - - -/****************************************************************************/ -/* PCI Subsystem ID *********************************************************/ -/****************************************************************************/ - -#define NGENE_ID(_subvend, _subdev, _driverdata) { \ - .vendor = NGENE_VID, .device = NGENE_PID, \ - .subvendor = _subvend, .subdevice = _subdev, \ - .driver_data = (unsigned long) &_driverdata } - -/****************************************************************************/ - -static const struct pci_device_id ngene_id_tbl[] __devinitdata = { - NGENE_ID(0x18c3, 0xabc3, ngene_info_cineS2), - NGENE_ID(0x18c3, 0xabc4, ngene_info_cineS2), - NGENE_ID(0x18c3, 0xdb01, ngene_info_satixS2), - NGENE_ID(0x18c3, 0xdb02, ngene_info_satixS2v2), - NGENE_ID(0x18c3, 0xdd00, ngene_info_cineS2v5), - NGENE_ID(0x18c3, 0xdd10, ngene_info_duoFlex), - NGENE_ID(0x18c3, 0xdd20, ngene_info_duoFlex), - NGENE_ID(0x1461, 0x062e, ngene_info_m780), - {0} -}; -MODULE_DEVICE_TABLE(pci, ngene_id_tbl); - -/****************************************************************************/ -/* Init/Exit ****************************************************************/ -/****************************************************************************/ - -static pci_ers_result_t ngene_error_detected(struct pci_dev *dev, - enum pci_channel_state state) -{ - printk(KERN_ERR DEVICE_NAME ": PCI error\n"); - if (state == pci_channel_io_perm_failure) - return PCI_ERS_RESULT_DISCONNECT; - if (state == pci_channel_io_frozen) - return PCI_ERS_RESULT_NEED_RESET; - return PCI_ERS_RESULT_CAN_RECOVER; -} - -static pci_ers_result_t ngene_link_reset(struct pci_dev *dev) -{ - printk(KERN_INFO DEVICE_NAME ": link reset\n"); - return 0; -} - -static pci_ers_result_t ngene_slot_reset(struct pci_dev *dev) -{ - printk(KERN_INFO DEVICE_NAME ": slot reset\n"); - return 0; -} - -static void ngene_resume(struct pci_dev *dev) -{ - printk(KERN_INFO DEVICE_NAME ": resume\n"); -} - -static const struct pci_error_handlers ngene_errors = { - .error_detected = ngene_error_detected, - .link_reset = ngene_link_reset, - .slot_reset = ngene_slot_reset, - .resume = ngene_resume, -}; - -static struct pci_driver ngene_pci_driver = { - .name = "ngene", - .id_table = ngene_id_tbl, - .probe = ngene_probe, - .remove = __devexit_p(ngene_remove), - .err_handler = &ngene_errors, - .shutdown = ngene_shutdown, -}; - -static __init int module_init_ngene(void) -{ - printk(KERN_INFO - "nGene PCIE bridge driver, Copyright (C) 2005-2007 Micronas\n"); - return pci_register_driver(&ngene_pci_driver); -} - -static __exit void module_exit_ngene(void) -{ - pci_unregister_driver(&ngene_pci_driver); -} - -module_init(module_init_ngene); -module_exit(module_exit_ngene); - -MODULE_DESCRIPTION("nGene"); -MODULE_AUTHOR("Micronas, Ralph Metzler, Manfred Voelkel"); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/ngene/ngene-core.c b/drivers/media/dvb/ngene/ngene-core.c deleted file mode 100644 index 39857384af10..000000000000 --- a/drivers/media/dvb/ngene/ngene-core.c +++ /dev/null @@ -1,1713 +0,0 @@ -/* - * ngene.c: nGene PCIe bridge driver - * - * Copyright (C) 2005-2007 Micronas - * - * Copyright (C) 2008-2009 Ralph Metzler - * Modifications for new nGene firmware, - * support for EEPROM-copying, - * support for new dual DVB-S2 card prototype - * - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 only, as published by the Free Software Foundation. - * - * - * 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 - * Or, point your browser to http://www.gnu.org/copyleft/gpl.html - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ngene.h" - -static int one_adapter; -module_param(one_adapter, int, 0444); -MODULE_PARM_DESC(one_adapter, "Use only one adapter."); - -static int shutdown_workaround; -module_param(shutdown_workaround, int, 0644); -MODULE_PARM_DESC(shutdown_workaround, "Activate workaround for shutdown problem with some chipsets."); - -static int debug; -module_param(debug, int, 0444); -MODULE_PARM_DESC(debug, "Print debugging information."); - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -#define dprintk if (debug) printk - -#define ngwriteb(dat, adr) writeb((dat), (char *)(dev->iomem + (adr))) -#define ngwritel(dat, adr) writel((dat), (char *)(dev->iomem + (adr))) -#define ngwriteb(dat, adr) writeb((dat), (char *)(dev->iomem + (adr))) -#define ngreadl(adr) readl(dev->iomem + (adr)) -#define ngreadb(adr) readb(dev->iomem + (adr)) -#define ngcpyto(adr, src, count) memcpy_toio((char *) \ - (dev->iomem + (adr)), (src), (count)) -#define ngcpyfrom(dst, adr, count) memcpy_fromio((dst), (char *) \ - (dev->iomem + (adr)), (count)) - -/****************************************************************************/ -/* nGene interrupt handler **************************************************/ -/****************************************************************************/ - -static void event_tasklet(unsigned long data) -{ - struct ngene *dev = (struct ngene *)data; - - while (dev->EventQueueReadIndex != dev->EventQueueWriteIndex) { - struct EVENT_BUFFER Event = - dev->EventQueue[dev->EventQueueReadIndex]; - dev->EventQueueReadIndex = - (dev->EventQueueReadIndex + 1) & (EVENT_QUEUE_SIZE - 1); - - if ((Event.UARTStatus & 0x01) && (dev->TxEventNotify)) - dev->TxEventNotify(dev, Event.TimeStamp); - if ((Event.UARTStatus & 0x02) && (dev->RxEventNotify)) - dev->RxEventNotify(dev, Event.TimeStamp, - Event.RXCharacter); - } -} - -static void demux_tasklet(unsigned long data) -{ - struct ngene_channel *chan = (struct ngene_channel *)data; - struct SBufferHeader *Cur = chan->nextBuffer; - - spin_lock_irq(&chan->state_lock); - - while (Cur->ngeneBuffer.SR.Flags & 0x80) { - if (chan->mode & NGENE_IO_TSOUT) { - u32 Flags = chan->DataFormatFlags; - if (Cur->ngeneBuffer.SR.Flags & 0x20) - Flags |= BEF_OVERFLOW; - if (chan->pBufferExchange) { - if (!chan->pBufferExchange(chan, - Cur->Buffer1, - chan->Capture1Length, - Cur->ngeneBuffer.SR. - Clock, Flags)) { - /* - We didn't get data - Clear in service flag to make sure we - get called on next interrupt again. - leave fill/empty (0x80) flag alone - to avoid hardware running out of - buffers during startup, we hold only - in run state ( the source may be late - delivering data ) - */ - - if (chan->HWState == HWSTATE_RUN) { - Cur->ngeneBuffer.SR.Flags &= - ~0x40; - break; - /* Stop processing stream */ - } - } else { - /* We got a valid buffer, - so switch to run state */ - chan->HWState = HWSTATE_RUN; - } - } else { - printk(KERN_ERR DEVICE_NAME ": OOPS\n"); - if (chan->HWState == HWSTATE_RUN) { - Cur->ngeneBuffer.SR.Flags &= ~0x40; - break; /* Stop processing stream */ - } - } - if (chan->AudioDTOUpdated) { - printk(KERN_INFO DEVICE_NAME - ": Update AudioDTO = %d\n", - chan->AudioDTOValue); - Cur->ngeneBuffer.SR.DTOUpdate = - chan->AudioDTOValue; - chan->AudioDTOUpdated = 0; - } - } else { - if (chan->HWState == HWSTATE_RUN) { - u32 Flags = chan->DataFormatFlags; - IBufferExchange *exch1 = chan->pBufferExchange; - IBufferExchange *exch2 = chan->pBufferExchange2; - if (Cur->ngeneBuffer.SR.Flags & 0x01) - Flags |= BEF_EVEN_FIELD; - if (Cur->ngeneBuffer.SR.Flags & 0x20) - Flags |= BEF_OVERFLOW; - spin_unlock_irq(&chan->state_lock); - if (exch1) - exch1(chan, Cur->Buffer1, - chan->Capture1Length, - Cur->ngeneBuffer.SR.Clock, - Flags); - if (exch2) - exch2(chan, Cur->Buffer2, - chan->Capture2Length, - Cur->ngeneBuffer.SR.Clock, - Flags); - spin_lock_irq(&chan->state_lock); - } else if (chan->HWState != HWSTATE_STOP) - chan->HWState = HWSTATE_RUN; - } - Cur->ngeneBuffer.SR.Flags = 0x00; - Cur = Cur->Next; - } - chan->nextBuffer = Cur; - - spin_unlock_irq(&chan->state_lock); -} - -static irqreturn_t irq_handler(int irq, void *dev_id) -{ - struct ngene *dev = (struct ngene *)dev_id; - u32 icounts = 0; - irqreturn_t rc = IRQ_NONE; - u32 i = MAX_STREAM; - u8 *tmpCmdDoneByte; - - if (dev->BootFirmware) { - icounts = ngreadl(NGENE_INT_COUNTS); - if (icounts != dev->icounts) { - ngwritel(0, FORCE_NMI); - dev->cmd_done = 1; - wake_up(&dev->cmd_wq); - dev->icounts = icounts; - rc = IRQ_HANDLED; - } - return rc; - } - - ngwritel(0, FORCE_NMI); - - spin_lock(&dev->cmd_lock); - tmpCmdDoneByte = dev->CmdDoneByte; - if (tmpCmdDoneByte && - (*tmpCmdDoneByte || - (dev->ngenetohost[0] == 1 && dev->ngenetohost[1] != 0))) { - dev->CmdDoneByte = NULL; - dev->cmd_done = 1; - wake_up(&dev->cmd_wq); - rc = IRQ_HANDLED; - } - spin_unlock(&dev->cmd_lock); - - if (dev->EventBuffer->EventStatus & 0x80) { - u8 nextWriteIndex = - (dev->EventQueueWriteIndex + 1) & - (EVENT_QUEUE_SIZE - 1); - if (nextWriteIndex != dev->EventQueueReadIndex) { - dev->EventQueue[dev->EventQueueWriteIndex] = - *(dev->EventBuffer); - dev->EventQueueWriteIndex = nextWriteIndex; - } else { - printk(KERN_ERR DEVICE_NAME ": event overflow\n"); - dev->EventQueueOverflowCount += 1; - dev->EventQueueOverflowFlag = 1; - } - dev->EventBuffer->EventStatus &= ~0x80; - tasklet_schedule(&dev->event_tasklet); - rc = IRQ_HANDLED; - } - - while (i > 0) { - i--; - spin_lock(&dev->channel[i].state_lock); - /* if (dev->channel[i].State>=KSSTATE_RUN) { */ - if (dev->channel[i].nextBuffer) { - if ((dev->channel[i].nextBuffer-> - ngeneBuffer.SR.Flags & 0xC0) == 0x80) { - dev->channel[i].nextBuffer-> - ngeneBuffer.SR.Flags |= 0x40; - tasklet_schedule( - &dev->channel[i].demux_tasklet); - rc = IRQ_HANDLED; - } - } - spin_unlock(&dev->channel[i].state_lock); - } - - /* Request might have been processed by a previous call. */ - return IRQ_HANDLED; -} - -/****************************************************************************/ -/* nGene command interface **************************************************/ -/****************************************************************************/ - -static void dump_command_io(struct ngene *dev) -{ - u8 buf[8], *b; - - ngcpyfrom(buf, HOST_TO_NGENE, 8); - printk(KERN_ERR "host_to_ngene (%04x): %02x %02x %02x %02x %02x %02x %02x %02x\n", - HOST_TO_NGENE, buf[0], buf[1], buf[2], buf[3], - buf[4], buf[5], buf[6], buf[7]); - - ngcpyfrom(buf, NGENE_TO_HOST, 8); - printk(KERN_ERR "ngene_to_host (%04x): %02x %02x %02x %02x %02x %02x %02x %02x\n", - NGENE_TO_HOST, buf[0], buf[1], buf[2], buf[3], - buf[4], buf[5], buf[6], buf[7]); - - b = dev->hosttongene; - printk(KERN_ERR "dev->hosttongene (%p): %02x %02x %02x %02x %02x %02x %02x %02x\n", - b, b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); - - b = dev->ngenetohost; - printk(KERN_ERR "dev->ngenetohost (%p): %02x %02x %02x %02x %02x %02x %02x %02x\n", - b, b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); -} - -static int ngene_command_mutex(struct ngene *dev, struct ngene_command *com) -{ - int ret; - u8 *tmpCmdDoneByte; - - dev->cmd_done = 0; - - if (com->cmd.hdr.Opcode == CMD_FWLOAD_PREPARE) { - dev->BootFirmware = 1; - dev->icounts = ngreadl(NGENE_INT_COUNTS); - ngwritel(0, NGENE_COMMAND); - ngwritel(0, NGENE_COMMAND_HI); - ngwritel(0, NGENE_STATUS); - ngwritel(0, NGENE_STATUS_HI); - ngwritel(0, NGENE_EVENT); - ngwritel(0, NGENE_EVENT_HI); - } else if (com->cmd.hdr.Opcode == CMD_FWLOAD_FINISH) { - u64 fwio = dev->PAFWInterfaceBuffer; - - ngwritel(fwio & 0xffffffff, NGENE_COMMAND); - ngwritel(fwio >> 32, NGENE_COMMAND_HI); - ngwritel((fwio + 256) & 0xffffffff, NGENE_STATUS); - ngwritel((fwio + 256) >> 32, NGENE_STATUS_HI); - ngwritel((fwio + 512) & 0xffffffff, NGENE_EVENT); - ngwritel((fwio + 512) >> 32, NGENE_EVENT_HI); - } - - memcpy(dev->FWInterfaceBuffer, com->cmd.raw8, com->in_len + 2); - - if (dev->BootFirmware) - ngcpyto(HOST_TO_NGENE, com->cmd.raw8, com->in_len + 2); - - spin_lock_irq(&dev->cmd_lock); - tmpCmdDoneByte = dev->ngenetohost + com->out_len; - if (!com->out_len) - tmpCmdDoneByte++; - *tmpCmdDoneByte = 0; - dev->ngenetohost[0] = 0; - dev->ngenetohost[1] = 0; - dev->CmdDoneByte = tmpCmdDoneByte; - spin_unlock_irq(&dev->cmd_lock); - - /* Notify 8051. */ - ngwritel(1, FORCE_INT); - - ret = wait_event_timeout(dev->cmd_wq, dev->cmd_done == 1, 2 * HZ); - if (!ret) { - /*ngwritel(0, FORCE_NMI);*/ - - printk(KERN_ERR DEVICE_NAME - ": Command timeout cmd=%02x prev=%02x\n", - com->cmd.hdr.Opcode, dev->prev_cmd); - dump_command_io(dev); - return -1; - } - if (com->cmd.hdr.Opcode == CMD_FWLOAD_FINISH) - dev->BootFirmware = 0; - - dev->prev_cmd = com->cmd.hdr.Opcode; - - if (!com->out_len) - return 0; - - memcpy(com->cmd.raw8, dev->ngenetohost, com->out_len); - - return 0; -} - -int ngene_command(struct ngene *dev, struct ngene_command *com) -{ - int result; - - down(&dev->cmd_mutex); - result = ngene_command_mutex(dev, com); - up(&dev->cmd_mutex); - return result; -} - - -static int ngene_command_load_firmware(struct ngene *dev, - u8 *ngene_fw, u32 size) -{ -#define FIRSTCHUNK (1024) - u32 cleft; - struct ngene_command com; - - com.cmd.hdr.Opcode = CMD_FWLOAD_PREPARE; - com.cmd.hdr.Length = 0; - com.in_len = 0; - com.out_len = 0; - - ngene_command(dev, &com); - - cleft = (size + 3) & ~3; - if (cleft > FIRSTCHUNK) { - ngcpyto(PROGRAM_SRAM + FIRSTCHUNK, ngene_fw + FIRSTCHUNK, - cleft - FIRSTCHUNK); - cleft = FIRSTCHUNK; - } - ngcpyto(DATA_FIFO_AREA, ngene_fw, cleft); - - memset(&com, 0, sizeof(struct ngene_command)); - com.cmd.hdr.Opcode = CMD_FWLOAD_FINISH; - com.cmd.hdr.Length = 4; - com.cmd.FWLoadFinish.Address = DATA_FIFO_AREA; - com.cmd.FWLoadFinish.Length = (unsigned short)cleft; - com.in_len = 4; - com.out_len = 0; - - return ngene_command(dev, &com); -} - - -static int ngene_command_config_buf(struct ngene *dev, u8 config) -{ - struct ngene_command com; - - com.cmd.hdr.Opcode = CMD_CONFIGURE_BUFFER; - com.cmd.hdr.Length = 1; - com.cmd.ConfigureBuffers.config = config; - com.in_len = 1; - com.out_len = 0; - - if (ngene_command(dev, &com) < 0) - return -EIO; - return 0; -} - -static int ngene_command_config_free_buf(struct ngene *dev, u8 *config) -{ - struct ngene_command com; - - com.cmd.hdr.Opcode = CMD_CONFIGURE_FREE_BUFFER; - com.cmd.hdr.Length = 6; - memcpy(&com.cmd.ConfigureBuffers.config, config, 6); - com.in_len = 6; - com.out_len = 0; - - if (ngene_command(dev, &com) < 0) - return -EIO; - - return 0; -} - -int ngene_command_gpio_set(struct ngene *dev, u8 select, u8 level) -{ - struct ngene_command com; - - com.cmd.hdr.Opcode = CMD_SET_GPIO_PIN; - com.cmd.hdr.Length = 1; - com.cmd.SetGpioPin.select = select | (level << 7); - com.in_len = 1; - com.out_len = 0; - - return ngene_command(dev, &com); -} - - -/* - 02000640 is sample on rising edge. - 02000740 is sample on falling edge. - 02000040 is ignore "valid" signal - - 0: FD_CTL1 Bit 7,6 must be 0,1 - 7 disable(fw controlled) - 6 0-AUX,1-TS - 5 0-par,1-ser - 4 0-lsb/1-msb - 3,2 reserved - 1,0 0-no sync, 1-use ext. start, 2-use 0x47, 3-both - 1: FD_CTL2 has 3-valid must be hi, 2-use valid, 1-edge - 2: FD_STA is read-only. 0-sync - 3: FD_INSYNC is number of 47s to trigger "in sync". - 4: FD_OUTSYNC is number of 47s to trigger "out of sync". - 5: FD_MAXBYTE1 is low-order of bytes per packet. - 6: FD_MAXBYTE2 is high-order of bytes per packet. - 7: Top byte is unused. -*/ - -/****************************************************************************/ - -static u8 TSFeatureDecoderSetup[8 * 5] = { - 0x42, 0x00, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, - 0x40, 0x06, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* DRXH */ - 0x71, 0x07, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* DRXHser */ - 0x72, 0x00, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* S2ser */ - 0x40, 0x07, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* LGDT3303 */ -}; - -/* Set NGENE I2S Config to 16 bit packed */ -static u8 I2SConfiguration[] = { - 0x00, 0x10, 0x00, 0x00, - 0x80, 0x10, 0x00, 0x00, -}; - -static u8 SPDIFConfiguration[10] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - -/* Set NGENE I2S Config to transport stream compatible mode */ - -static u8 TS_I2SConfiguration[4] = { 0x3E, 0x18, 0x00, 0x00 }; - -static u8 TS_I2SOutConfiguration[4] = { 0x80, 0x04, 0x00, 0x00 }; - -static u8 ITUDecoderSetup[4][16] = { - {0x1c, 0x13, 0x01, 0x68, 0x3d, 0x90, 0x14, 0x20, /* SDTV */ - 0x00, 0x00, 0x01, 0xb0, 0x9c, 0x00, 0x00, 0x00}, - {0x9c, 0x03, 0x23, 0xC0, 0x60, 0x0E, 0x13, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00}, - {0x9f, 0x00, 0x23, 0xC0, 0x60, 0x0F, 0x13, 0x00, /* HDTV 1080i50 */ - 0x00, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00}, - {0x9c, 0x01, 0x23, 0xC0, 0x60, 0x0E, 0x13, 0x00, /* HDTV 1080i60 */ - 0x00, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00}, -}; - -/* - * 50 48 60 gleich - * 27p50 9f 00 22 80 42 69 18 ... - * 27p60 93 00 22 80 82 69 1c ... - */ - -/* Maxbyte to 1144 (for raw data) */ -static u8 ITUFeatureDecoderSetup[8] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x04, 0x00 -}; - -void FillTSBuffer(void *Buffer, int Length, u32 Flags) -{ - u32 *ptr = Buffer; - - memset(Buffer, TS_FILLER, Length); - while (Length > 0) { - if (Flags & DF_SWAP32) - *ptr = 0x471FFF10; - else - *ptr = 0x10FF1F47; - ptr += (188 / 4); - Length -= 188; - } -} - - -static void flush_buffers(struct ngene_channel *chan) -{ - u8 val; - - do { - msleep(1); - spin_lock_irq(&chan->state_lock); - val = chan->nextBuffer->ngeneBuffer.SR.Flags & 0x80; - spin_unlock_irq(&chan->state_lock); - } while (val); -} - -static void clear_buffers(struct ngene_channel *chan) -{ - struct SBufferHeader *Cur = chan->nextBuffer; - - do { - memset(&Cur->ngeneBuffer.SR, 0, sizeof(Cur->ngeneBuffer.SR)); - if (chan->mode & NGENE_IO_TSOUT) - FillTSBuffer(Cur->Buffer1, - chan->Capture1Length, - chan->DataFormatFlags); - Cur = Cur->Next; - } while (Cur != chan->nextBuffer); - - if (chan->mode & NGENE_IO_TSOUT) { - chan->nextBuffer->ngeneBuffer.SR.DTOUpdate = - chan->AudioDTOValue; - chan->AudioDTOUpdated = 0; - - Cur = chan->TSIdleBuffer.Head; - - do { - memset(&Cur->ngeneBuffer.SR, 0, - sizeof(Cur->ngeneBuffer.SR)); - FillTSBuffer(Cur->Buffer1, - chan->Capture1Length, - chan->DataFormatFlags); - Cur = Cur->Next; - } while (Cur != chan->TSIdleBuffer.Head); - } -} - -static int ngene_command_stream_control(struct ngene *dev, u8 stream, - u8 control, u8 mode, u8 flags) -{ - struct ngene_channel *chan = &dev->channel[stream]; - struct ngene_command com; - u16 BsUVI = ((stream & 1) ? 0x9400 : 0x9300); - u16 BsSDI = ((stream & 1) ? 0x9600 : 0x9500); - u16 BsSPI = ((stream & 1) ? 0x9800 : 0x9700); - u16 BsSDO = 0x9B00; - - down(&dev->stream_mutex); - memset(&com, 0, sizeof(com)); - com.cmd.hdr.Opcode = CMD_CONTROL; - com.cmd.hdr.Length = sizeof(struct FW_STREAM_CONTROL) - 2; - com.cmd.StreamControl.Stream = stream | (control ? 8 : 0); - if (chan->mode & NGENE_IO_TSOUT) - com.cmd.StreamControl.Stream |= 0x07; - com.cmd.StreamControl.Control = control | - (flags & SFLAG_ORDER_LUMA_CHROMA); - com.cmd.StreamControl.Mode = mode; - com.in_len = sizeof(struct FW_STREAM_CONTROL); - com.out_len = 0; - - dprintk(KERN_INFO DEVICE_NAME - ": Stream=%02x, Control=%02x, Mode=%02x\n", - com.cmd.StreamControl.Stream, com.cmd.StreamControl.Control, - com.cmd.StreamControl.Mode); - - chan->Mode = mode; - - if (!(control & 0x80)) { - spin_lock_irq(&chan->state_lock); - if (chan->State == KSSTATE_RUN) { - chan->State = KSSTATE_ACQUIRE; - chan->HWState = HWSTATE_STOP; - spin_unlock_irq(&chan->state_lock); - if (ngene_command(dev, &com) < 0) { - up(&dev->stream_mutex); - return -1; - } - /* clear_buffers(chan); */ - flush_buffers(chan); - up(&dev->stream_mutex); - return 0; - } - spin_unlock_irq(&chan->state_lock); - up(&dev->stream_mutex); - return 0; - } - - if (mode & SMODE_AUDIO_CAPTURE) { - com.cmd.StreamControl.CaptureBlockCount = - chan->Capture1Length / AUDIO_BLOCK_SIZE; - com.cmd.StreamControl.Buffer_Address = chan->RingBuffer.PAHead; - } else if (mode & SMODE_TRANSPORT_STREAM) { - com.cmd.StreamControl.CaptureBlockCount = - chan->Capture1Length / TS_BLOCK_SIZE; - com.cmd.StreamControl.MaxLinesPerField = - chan->Capture1Length / TS_BLOCK_SIZE; - com.cmd.StreamControl.Buffer_Address = - chan->TSRingBuffer.PAHead; - if (chan->mode & NGENE_IO_TSOUT) { - com.cmd.StreamControl.BytesPerVBILine = - chan->Capture1Length / TS_BLOCK_SIZE; - com.cmd.StreamControl.Stream |= 0x07; - } - } else { - com.cmd.StreamControl.BytesPerVideoLine = chan->nBytesPerLine; - com.cmd.StreamControl.MaxLinesPerField = chan->nLines; - com.cmd.StreamControl.MinLinesPerField = 100; - com.cmd.StreamControl.Buffer_Address = chan->RingBuffer.PAHead; - - if (mode & SMODE_VBI_CAPTURE) { - com.cmd.StreamControl.MaxVBILinesPerField = - chan->nVBILines; - com.cmd.StreamControl.MinVBILinesPerField = 0; - com.cmd.StreamControl.BytesPerVBILine = - chan->nBytesPerVBILine; - } - if (flags & SFLAG_COLORBAR) - com.cmd.StreamControl.Stream |= 0x04; - } - - spin_lock_irq(&chan->state_lock); - if (mode & SMODE_AUDIO_CAPTURE) { - chan->nextBuffer = chan->RingBuffer.Head; - if (mode & SMODE_AUDIO_SPDIF) { - com.cmd.StreamControl.SetupDataLen = - sizeof(SPDIFConfiguration); - com.cmd.StreamControl.SetupDataAddr = BsSPI; - memcpy(com.cmd.StreamControl.SetupData, - SPDIFConfiguration, sizeof(SPDIFConfiguration)); - } else { - com.cmd.StreamControl.SetupDataLen = 4; - com.cmd.StreamControl.SetupDataAddr = BsSDI; - memcpy(com.cmd.StreamControl.SetupData, - I2SConfiguration + - 4 * dev->card_info->i2s[stream], 4); - } - } else if (mode & SMODE_TRANSPORT_STREAM) { - chan->nextBuffer = chan->TSRingBuffer.Head; - if (stream >= STREAM_AUDIOIN1) { - if (chan->mode & NGENE_IO_TSOUT) { - com.cmd.StreamControl.SetupDataLen = - sizeof(TS_I2SOutConfiguration); - com.cmd.StreamControl.SetupDataAddr = BsSDO; - memcpy(com.cmd.StreamControl.SetupData, - TS_I2SOutConfiguration, - sizeof(TS_I2SOutConfiguration)); - } else { - com.cmd.StreamControl.SetupDataLen = - sizeof(TS_I2SConfiguration); - com.cmd.StreamControl.SetupDataAddr = BsSDI; - memcpy(com.cmd.StreamControl.SetupData, - TS_I2SConfiguration, - sizeof(TS_I2SConfiguration)); - } - } else { - com.cmd.StreamControl.SetupDataLen = 8; - com.cmd.StreamControl.SetupDataAddr = BsUVI + 0x10; - memcpy(com.cmd.StreamControl.SetupData, - TSFeatureDecoderSetup + - 8 * dev->card_info->tsf[stream], 8); - } - } else { - chan->nextBuffer = chan->RingBuffer.Head; - com.cmd.StreamControl.SetupDataLen = - 16 + sizeof(ITUFeatureDecoderSetup); - com.cmd.StreamControl.SetupDataAddr = BsUVI; - memcpy(com.cmd.StreamControl.SetupData, - ITUDecoderSetup[chan->itumode], 16); - memcpy(com.cmd.StreamControl.SetupData + 16, - ITUFeatureDecoderSetup, sizeof(ITUFeatureDecoderSetup)); - } - clear_buffers(chan); - chan->State = KSSTATE_RUN; - if (mode & SMODE_TRANSPORT_STREAM) - chan->HWState = HWSTATE_RUN; - else - chan->HWState = HWSTATE_STARTUP; - spin_unlock_irq(&chan->state_lock); - - if (ngene_command(dev, &com) < 0) { - up(&dev->stream_mutex); - return -1; - } - up(&dev->stream_mutex); - return 0; -} - -void set_transfer(struct ngene_channel *chan, int state) -{ - u8 control = 0, mode = 0, flags = 0; - struct ngene *dev = chan->dev; - int ret; - - /* - printk(KERN_INFO DEVICE_NAME ": st %d\n", state); - msleep(100); - */ - - if (state) { - if (chan->running) { - printk(KERN_INFO DEVICE_NAME ": already running\n"); - return; - } - } else { - if (!chan->running) { - printk(KERN_INFO DEVICE_NAME ": already stopped\n"); - return; - } - } - - if (dev->card_info->switch_ctrl) - dev->card_info->switch_ctrl(chan, 1, state ^ 1); - - if (state) { - spin_lock_irq(&chan->state_lock); - - /* printk(KERN_INFO DEVICE_NAME ": lock=%08x\n", - ngreadl(0x9310)); */ - dvb_ringbuffer_flush(&dev->tsout_rbuf); - control = 0x80; - if (chan->mode & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { - chan->Capture1Length = 512 * 188; - mode = SMODE_TRANSPORT_STREAM; - } - if (chan->mode & NGENE_IO_TSOUT) { - chan->pBufferExchange = tsout_exchange; - /* 0x66666666 = 50MHz *2^33 /250MHz */ - chan->AudioDTOValue = 0x80000000; - chan->AudioDTOUpdated = 1; - } - if (chan->mode & NGENE_IO_TSIN) - chan->pBufferExchange = tsin_exchange; - spin_unlock_irq(&chan->state_lock); - } else - ;/* printk(KERN_INFO DEVICE_NAME ": lock=%08x\n", - ngreadl(0x9310)); */ - - ret = ngene_command_stream_control(dev, chan->number, - control, mode, flags); - if (!ret) - chan->running = state; - else - printk(KERN_ERR DEVICE_NAME ": set_transfer %d failed\n", - state); - if (!state) { - spin_lock_irq(&chan->state_lock); - chan->pBufferExchange = NULL; - dvb_ringbuffer_flush(&dev->tsout_rbuf); - spin_unlock_irq(&chan->state_lock); - } -} - - -/****************************************************************************/ -/* nGene hardware init and release functions ********************************/ -/****************************************************************************/ - -static void free_ringbuffer(struct ngene *dev, struct SRingBufferDescriptor *rb) -{ - struct SBufferHeader *Cur = rb->Head; - u32 j; - - if (!Cur) - return; - - for (j = 0; j < rb->NumBuffers; j++, Cur = Cur->Next) { - if (Cur->Buffer1) - pci_free_consistent(dev->pci_dev, - rb->Buffer1Length, - Cur->Buffer1, - Cur->scList1->Address); - - if (Cur->Buffer2) - pci_free_consistent(dev->pci_dev, - rb->Buffer2Length, - Cur->Buffer2, - Cur->scList2->Address); - } - - if (rb->SCListMem) - pci_free_consistent(dev->pci_dev, rb->SCListMemSize, - rb->SCListMem, rb->PASCListMem); - - pci_free_consistent(dev->pci_dev, rb->MemSize, rb->Head, rb->PAHead); -} - -static void free_idlebuffer(struct ngene *dev, - struct SRingBufferDescriptor *rb, - struct SRingBufferDescriptor *tb) -{ - int j; - struct SBufferHeader *Cur = tb->Head; - - if (!rb->Head) - return; - free_ringbuffer(dev, rb); - for (j = 0; j < tb->NumBuffers; j++, Cur = Cur->Next) { - Cur->Buffer2 = NULL; - Cur->scList2 = NULL; - Cur->ngeneBuffer.Address_of_first_entry_2 = 0; - Cur->ngeneBuffer.Number_of_entries_2 = 0; - } -} - -static void free_common_buffers(struct ngene *dev) -{ - u32 i; - struct ngene_channel *chan; - - for (i = STREAM_VIDEOIN1; i < MAX_STREAM; i++) { - chan = &dev->channel[i]; - free_idlebuffer(dev, &chan->TSIdleBuffer, &chan->TSRingBuffer); - free_ringbuffer(dev, &chan->RingBuffer); - free_ringbuffer(dev, &chan->TSRingBuffer); - } - - if (dev->OverflowBuffer) - pci_free_consistent(dev->pci_dev, - OVERFLOW_BUFFER_SIZE, - dev->OverflowBuffer, dev->PAOverflowBuffer); - - if (dev->FWInterfaceBuffer) - pci_free_consistent(dev->pci_dev, - 4096, - dev->FWInterfaceBuffer, - dev->PAFWInterfaceBuffer); -} - -/****************************************************************************/ -/* Ring buffer handling *****************************************************/ -/****************************************************************************/ - -static int create_ring_buffer(struct pci_dev *pci_dev, - struct SRingBufferDescriptor *descr, u32 NumBuffers) -{ - dma_addr_t tmp; - struct SBufferHeader *Head; - u32 i; - u32 MemSize = SIZEOF_SBufferHeader * NumBuffers; - u64 PARingBufferHead; - u64 PARingBufferCur; - u64 PARingBufferNext; - struct SBufferHeader *Cur, *Next; - - descr->Head = NULL; - descr->MemSize = 0; - descr->PAHead = 0; - descr->NumBuffers = 0; - - if (MemSize < 4096) - MemSize = 4096; - - Head = pci_alloc_consistent(pci_dev, MemSize, &tmp); - PARingBufferHead = tmp; - - if (!Head) - return -ENOMEM; - - memset(Head, 0, MemSize); - - PARingBufferCur = PARingBufferHead; - Cur = Head; - - for (i = 0; i < NumBuffers - 1; i++) { - Next = (struct SBufferHeader *) - (((u8 *) Cur) + SIZEOF_SBufferHeader); - PARingBufferNext = PARingBufferCur + SIZEOF_SBufferHeader; - Cur->Next = Next; - Cur->ngeneBuffer.Next = PARingBufferNext; - Cur = Next; - PARingBufferCur = PARingBufferNext; - } - /* Last Buffer points back to first one */ - Cur->Next = Head; - Cur->ngeneBuffer.Next = PARingBufferHead; - - descr->Head = Head; - descr->MemSize = MemSize; - descr->PAHead = PARingBufferHead; - descr->NumBuffers = NumBuffers; - - return 0; -} - -static int AllocateRingBuffers(struct pci_dev *pci_dev, - dma_addr_t of, - struct SRingBufferDescriptor *pRingBuffer, - u32 Buffer1Length, u32 Buffer2Length) -{ - dma_addr_t tmp; - u32 i, j; - int status = 0; - u32 SCListMemSize = pRingBuffer->NumBuffers - * ((Buffer2Length != 0) ? (NUM_SCATTER_GATHER_ENTRIES * 2) : - NUM_SCATTER_GATHER_ENTRIES) - * sizeof(struct HW_SCATTER_GATHER_ELEMENT); - - u64 PASCListMem; - struct HW_SCATTER_GATHER_ELEMENT *SCListEntry; - u64 PASCListEntry; - struct SBufferHeader *Cur; - void *SCListMem; - - if (SCListMemSize < 4096) - SCListMemSize = 4096; - - SCListMem = pci_alloc_consistent(pci_dev, SCListMemSize, &tmp); - - PASCListMem = tmp; - if (SCListMem == NULL) - return -ENOMEM; - - memset(SCListMem, 0, SCListMemSize); - - pRingBuffer->SCListMem = SCListMem; - pRingBuffer->PASCListMem = PASCListMem; - pRingBuffer->SCListMemSize = SCListMemSize; - pRingBuffer->Buffer1Length = Buffer1Length; - pRingBuffer->Buffer2Length = Buffer2Length; - - SCListEntry = SCListMem; - PASCListEntry = PASCListMem; - Cur = pRingBuffer->Head; - - for (i = 0; i < pRingBuffer->NumBuffers; i += 1, Cur = Cur->Next) { - u64 PABuffer; - - void *Buffer = pci_alloc_consistent(pci_dev, Buffer1Length, - &tmp); - PABuffer = tmp; - - if (Buffer == NULL) - return -ENOMEM; - - Cur->Buffer1 = Buffer; - - SCListEntry->Address = PABuffer; - SCListEntry->Length = Buffer1Length; - - Cur->scList1 = SCListEntry; - Cur->ngeneBuffer.Address_of_first_entry_1 = PASCListEntry; - Cur->ngeneBuffer.Number_of_entries_1 = - NUM_SCATTER_GATHER_ENTRIES; - - SCListEntry += 1; - PASCListEntry += sizeof(struct HW_SCATTER_GATHER_ELEMENT); - -#if NUM_SCATTER_GATHER_ENTRIES > 1 - for (j = 0; j < NUM_SCATTER_GATHER_ENTRIES - 1; j += 1) { - SCListEntry->Address = of; - SCListEntry->Length = OVERFLOW_BUFFER_SIZE; - SCListEntry += 1; - PASCListEntry += - sizeof(struct HW_SCATTER_GATHER_ELEMENT); - } -#endif - - if (!Buffer2Length) - continue; - - Buffer = pci_alloc_consistent(pci_dev, Buffer2Length, &tmp); - PABuffer = tmp; - - if (Buffer == NULL) - return -ENOMEM; - - Cur->Buffer2 = Buffer; - - SCListEntry->Address = PABuffer; - SCListEntry->Length = Buffer2Length; - - Cur->scList2 = SCListEntry; - Cur->ngeneBuffer.Address_of_first_entry_2 = PASCListEntry; - Cur->ngeneBuffer.Number_of_entries_2 = - NUM_SCATTER_GATHER_ENTRIES; - - SCListEntry += 1; - PASCListEntry += sizeof(struct HW_SCATTER_GATHER_ELEMENT); - -#if NUM_SCATTER_GATHER_ENTRIES > 1 - for (j = 0; j < NUM_SCATTER_GATHER_ENTRIES - 1; j++) { - SCListEntry->Address = of; - SCListEntry->Length = OVERFLOW_BUFFER_SIZE; - SCListEntry += 1; - PASCListEntry += - sizeof(struct HW_SCATTER_GATHER_ELEMENT); - } -#endif - - } - - return status; -} - -static int FillTSIdleBuffer(struct SRingBufferDescriptor *pIdleBuffer, - struct SRingBufferDescriptor *pRingBuffer) -{ - int status = 0; - - /* Copy pointer to scatter gather list in TSRingbuffer - structure for buffer 2 - Load number of buffer - */ - u32 n = pRingBuffer->NumBuffers; - - /* Point to first buffer entry */ - struct SBufferHeader *Cur = pRingBuffer->Head; - int i; - /* Loop thru all buffer and set Buffer 2 pointers to TSIdlebuffer */ - for (i = 0; i < n; i++) { - Cur->Buffer2 = pIdleBuffer->Head->Buffer1; - Cur->scList2 = pIdleBuffer->Head->scList1; - Cur->ngeneBuffer.Address_of_first_entry_2 = - pIdleBuffer->Head->ngeneBuffer. - Address_of_first_entry_1; - Cur->ngeneBuffer.Number_of_entries_2 = - pIdleBuffer->Head->ngeneBuffer.Number_of_entries_1; - Cur = Cur->Next; - } - return status; -} - -static u32 RingBufferSizes[MAX_STREAM] = { - RING_SIZE_VIDEO, - RING_SIZE_VIDEO, - RING_SIZE_AUDIO, - RING_SIZE_AUDIO, - RING_SIZE_AUDIO, -}; - -static u32 Buffer1Sizes[MAX_STREAM] = { - MAX_VIDEO_BUFFER_SIZE, - MAX_VIDEO_BUFFER_SIZE, - MAX_AUDIO_BUFFER_SIZE, - MAX_AUDIO_BUFFER_SIZE, - MAX_AUDIO_BUFFER_SIZE -}; - -static u32 Buffer2Sizes[MAX_STREAM] = { - MAX_VBI_BUFFER_SIZE, - MAX_VBI_BUFFER_SIZE, - 0, - 0, - 0 -}; - - -static int AllocCommonBuffers(struct ngene *dev) -{ - int status = 0, i; - - dev->FWInterfaceBuffer = pci_alloc_consistent(dev->pci_dev, 4096, - &dev->PAFWInterfaceBuffer); - if (!dev->FWInterfaceBuffer) - return -ENOMEM; - dev->hosttongene = dev->FWInterfaceBuffer; - dev->ngenetohost = dev->FWInterfaceBuffer + 256; - dev->EventBuffer = dev->FWInterfaceBuffer + 512; - - dev->OverflowBuffer = pci_alloc_consistent(dev->pci_dev, - OVERFLOW_BUFFER_SIZE, - &dev->PAOverflowBuffer); - if (!dev->OverflowBuffer) - return -ENOMEM; - memset(dev->OverflowBuffer, 0, OVERFLOW_BUFFER_SIZE); - - for (i = STREAM_VIDEOIN1; i < MAX_STREAM; i++) { - int type = dev->card_info->io_type[i]; - - dev->channel[i].State = KSSTATE_STOP; - - if (type & (NGENE_IO_TV | NGENE_IO_HDTV | NGENE_IO_AIN)) { - status = create_ring_buffer(dev->pci_dev, - &dev->channel[i].RingBuffer, - RingBufferSizes[i]); - if (status < 0) - break; - - if (type & (NGENE_IO_TV | NGENE_IO_AIN)) { - status = AllocateRingBuffers(dev->pci_dev, - dev-> - PAOverflowBuffer, - &dev->channel[i]. - RingBuffer, - Buffer1Sizes[i], - Buffer2Sizes[i]); - if (status < 0) - break; - } else if (type & NGENE_IO_HDTV) { - status = AllocateRingBuffers(dev->pci_dev, - dev-> - PAOverflowBuffer, - &dev->channel[i]. - RingBuffer, - MAX_HDTV_BUFFER_SIZE, - 0); - if (status < 0) - break; - } - } - - if (type & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { - - status = create_ring_buffer(dev->pci_dev, - &dev->channel[i]. - TSRingBuffer, RING_SIZE_TS); - if (status < 0) - break; - - status = AllocateRingBuffers(dev->pci_dev, - dev->PAOverflowBuffer, - &dev->channel[i]. - TSRingBuffer, - MAX_TS_BUFFER_SIZE, 0); - if (status) - break; - } - - if (type & NGENE_IO_TSOUT) { - status = create_ring_buffer(dev->pci_dev, - &dev->channel[i]. - TSIdleBuffer, 1); - if (status < 0) - break; - status = AllocateRingBuffers(dev->pci_dev, - dev->PAOverflowBuffer, - &dev->channel[i]. - TSIdleBuffer, - MAX_TS_BUFFER_SIZE, 0); - if (status) - break; - FillTSIdleBuffer(&dev->channel[i].TSIdleBuffer, - &dev->channel[i].TSRingBuffer); - } - } - return status; -} - -static void ngene_release_buffers(struct ngene *dev) -{ - if (dev->iomem) - iounmap(dev->iomem); - free_common_buffers(dev); - vfree(dev->tsout_buf); - vfree(dev->tsin_buf); - vfree(dev->ain_buf); - vfree(dev->vin_buf); - vfree(dev); -} - -static int ngene_get_buffers(struct ngene *dev) -{ - if (AllocCommonBuffers(dev)) - return -ENOMEM; - if (dev->card_info->io_type[4] & NGENE_IO_TSOUT) { - dev->tsout_buf = vmalloc(TSOUT_BUF_SIZE); - if (!dev->tsout_buf) - return -ENOMEM; - dvb_ringbuffer_init(&dev->tsout_rbuf, - dev->tsout_buf, TSOUT_BUF_SIZE); - } - if (dev->card_info->io_type[2]&NGENE_IO_TSIN) { - dev->tsin_buf = vmalloc(TSIN_BUF_SIZE); - if (!dev->tsin_buf) - return -ENOMEM; - dvb_ringbuffer_init(&dev->tsin_rbuf, - dev->tsin_buf, TSIN_BUF_SIZE); - } - if (dev->card_info->io_type[2] & NGENE_IO_AIN) { - dev->ain_buf = vmalloc(AIN_BUF_SIZE); - if (!dev->ain_buf) - return -ENOMEM; - dvb_ringbuffer_init(&dev->ain_rbuf, dev->ain_buf, AIN_BUF_SIZE); - } - if (dev->card_info->io_type[0] & NGENE_IO_HDTV) { - dev->vin_buf = vmalloc(VIN_BUF_SIZE); - if (!dev->vin_buf) - return -ENOMEM; - dvb_ringbuffer_init(&dev->vin_rbuf, dev->vin_buf, VIN_BUF_SIZE); - } - dev->iomem = ioremap(pci_resource_start(dev->pci_dev, 0), - pci_resource_len(dev->pci_dev, 0)); - if (!dev->iomem) - return -ENOMEM; - - return 0; -} - -static void ngene_init(struct ngene *dev) -{ - int i; - - tasklet_init(&dev->event_tasklet, event_tasklet, (unsigned long)dev); - - memset_io(dev->iomem + 0xc000, 0x00, 0x220); - memset_io(dev->iomem + 0xc400, 0x00, 0x100); - - for (i = 0; i < MAX_STREAM; i++) { - dev->channel[i].dev = dev; - dev->channel[i].number = i; - } - - dev->fw_interface_version = 0; - - ngwritel(0, NGENE_INT_ENABLE); - - dev->icounts = ngreadl(NGENE_INT_COUNTS); - - dev->device_version = ngreadl(DEV_VER) & 0x0f; - printk(KERN_INFO DEVICE_NAME ": Device version %d\n", - dev->device_version); -} - -static int ngene_load_firm(struct ngene *dev) -{ - u32 size; - const struct firmware *fw = NULL; - u8 *ngene_fw; - char *fw_name; - int err, version; - - version = dev->card_info->fw_version; - - switch (version) { - default: - case 15: - version = 15; - size = 23466; - fw_name = "ngene_15.fw"; - dev->cmd_timeout_workaround = true; - break; - case 16: - size = 23498; - fw_name = "ngene_16.fw"; - dev->cmd_timeout_workaround = true; - break; - case 17: - size = 24446; - fw_name = "ngene_17.fw"; - dev->cmd_timeout_workaround = true; - break; - case 18: - size = 0; - fw_name = "ngene_18.fw"; - break; - } - - if (request_firmware(&fw, fw_name, &dev->pci_dev->dev) < 0) { - printk(KERN_ERR DEVICE_NAME - ": Could not load firmware file %s.\n", fw_name); - printk(KERN_INFO DEVICE_NAME - ": Copy %s to your hotplug directory!\n", fw_name); - return -1; - } - if (size == 0) - size = fw->size; - if (size != fw->size) { - printk(KERN_ERR DEVICE_NAME - ": Firmware %s has invalid size!", fw_name); - err = -1; - } else { - printk(KERN_INFO DEVICE_NAME - ": Loading firmware file %s.\n", fw_name); - ngene_fw = (u8 *) fw->data; - err = ngene_command_load_firmware(dev, ngene_fw, size); - } - - release_firmware(fw); - - return err; -} - -static void ngene_stop(struct ngene *dev) -{ - down(&dev->cmd_mutex); - i2c_del_adapter(&(dev->channel[0].i2c_adapter)); - i2c_del_adapter(&(dev->channel[1].i2c_adapter)); - ngwritel(0, NGENE_INT_ENABLE); - ngwritel(0, NGENE_COMMAND); - ngwritel(0, NGENE_COMMAND_HI); - ngwritel(0, NGENE_STATUS); - ngwritel(0, NGENE_STATUS_HI); - ngwritel(0, NGENE_EVENT); - ngwritel(0, NGENE_EVENT_HI); - free_irq(dev->pci_dev->irq, dev); -#ifdef CONFIG_PCI_MSI - if (dev->msi_enabled) - pci_disable_msi(dev->pci_dev); -#endif -} - -static int ngene_buffer_config(struct ngene *dev) -{ - int stat; - - if (dev->card_info->fw_version >= 17) { - u8 tsin12_config[6] = { 0x60, 0x60, 0x00, 0x00, 0x00, 0x00 }; - u8 tsin1234_config[6] = { 0x30, 0x30, 0x00, 0x30, 0x30, 0x00 }; - u8 tsio1235_config[6] = { 0x30, 0x30, 0x00, 0x28, 0x00, 0x38 }; - u8 *bconf = tsin12_config; - - if (dev->card_info->io_type[2]&NGENE_IO_TSIN && - dev->card_info->io_type[3]&NGENE_IO_TSIN) { - bconf = tsin1234_config; - if (dev->card_info->io_type[4]&NGENE_IO_TSOUT && - dev->ci.en) - bconf = tsio1235_config; - } - stat = ngene_command_config_free_buf(dev, bconf); - } else { - int bconf = BUFFER_CONFIG_4422; - - if (dev->card_info->io_type[3] == NGENE_IO_TSIN) - bconf = BUFFER_CONFIG_3333; - stat = ngene_command_config_buf(dev, bconf); - } - return stat; -} - - -static int ngene_start(struct ngene *dev) -{ - int stat; - int i; - - pci_set_master(dev->pci_dev); - ngene_init(dev); - - stat = request_irq(dev->pci_dev->irq, irq_handler, - IRQF_SHARED, "nGene", - (void *)dev); - if (stat < 0) - return stat; - - init_waitqueue_head(&dev->cmd_wq); - init_waitqueue_head(&dev->tx_wq); - init_waitqueue_head(&dev->rx_wq); - sema_init(&dev->cmd_mutex, 1); - sema_init(&dev->stream_mutex, 1); - sema_init(&dev->pll_mutex, 1); - sema_init(&dev->i2c_switch_mutex, 1); - spin_lock_init(&dev->cmd_lock); - for (i = 0; i < MAX_STREAM; i++) - spin_lock_init(&dev->channel[i].state_lock); - ngwritel(1, TIMESTAMPS); - - ngwritel(1, NGENE_INT_ENABLE); - - stat = ngene_load_firm(dev); - if (stat < 0) - goto fail; - -#ifdef CONFIG_PCI_MSI - /* enable MSI if kernel and card support it */ - if (pci_msi_enabled() && dev->card_info->msi_supported) { - unsigned long flags; - - ngwritel(0, NGENE_INT_ENABLE); - free_irq(dev->pci_dev->irq, dev); - stat = pci_enable_msi(dev->pci_dev); - if (stat) { - printk(KERN_INFO DEVICE_NAME - ": MSI not available\n"); - flags = IRQF_SHARED; - } else { - flags = 0; - dev->msi_enabled = true; - } - stat = request_irq(dev->pci_dev->irq, irq_handler, - flags, "nGene", dev); - if (stat < 0) - goto fail2; - ngwritel(1, NGENE_INT_ENABLE); - } -#endif - - stat = ngene_i2c_init(dev, 0); - if (stat < 0) - goto fail; - - stat = ngene_i2c_init(dev, 1); - if (stat < 0) - goto fail; - - return 0; - -fail: - ngwritel(0, NGENE_INT_ENABLE); - free_irq(dev->pci_dev->irq, dev); -#ifdef CONFIG_PCI_MSI -fail2: - if (dev->msi_enabled) - pci_disable_msi(dev->pci_dev); -#endif - return stat; -} - -/****************************************************************************/ -/****************************************************************************/ -/****************************************************************************/ - -static void release_channel(struct ngene_channel *chan) -{ - struct dvb_demux *dvbdemux = &chan->demux; - struct ngene *dev = chan->dev; - - if (chan->running) - set_transfer(chan, 0); - - tasklet_kill(&chan->demux_tasklet); - - if (chan->ci_dev) { - dvb_unregister_device(chan->ci_dev); - chan->ci_dev = NULL; - } - - if (chan->fe2) - dvb_unregister_frontend(chan->fe2); - - if (chan->fe) { - dvb_unregister_frontend(chan->fe); - dvb_frontend_detach(chan->fe); - chan->fe = NULL; - } - - if (chan->has_demux) { - dvb_net_release(&chan->dvbnet); - dvbdemux->dmx.close(&dvbdemux->dmx); - dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, - &chan->hw_frontend); - dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, - &chan->mem_frontend); - dvb_dmxdev_release(&chan->dmxdev); - dvb_dmx_release(&chan->demux); - chan->has_demux = false; - } - - if (chan->has_adapter) { - dvb_unregister_adapter(&dev->adapter[chan->number]); - chan->has_adapter = false; - } -} - -static int init_channel(struct ngene_channel *chan) -{ - int ret = 0, nr = chan->number; - struct dvb_adapter *adapter = NULL; - struct dvb_demux *dvbdemux = &chan->demux; - struct ngene *dev = chan->dev; - struct ngene_info *ni = dev->card_info; - int io = ni->io_type[nr]; - - tasklet_init(&chan->demux_tasklet, demux_tasklet, (unsigned long)chan); - chan->users = 0; - chan->type = io; - chan->mode = chan->type; /* for now only one mode */ - - if (io & NGENE_IO_TSIN) { - chan->fe = NULL; - if (ni->demod_attach[nr]) { - ret = ni->demod_attach[nr](chan); - if (ret < 0) - goto err; - } - if (chan->fe && ni->tuner_attach[nr]) { - ret = ni->tuner_attach[nr](chan); - if (ret < 0) - goto err; - } - } - - if (!dev->ci.en && (io & NGENE_IO_TSOUT)) - return 0; - - if (io & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { - if (nr >= STREAM_AUDIOIN1) - chan->DataFormatFlags = DF_SWAP32; - - if (nr == 0 || !one_adapter || dev->first_adapter == NULL) { - adapter = &dev->adapter[nr]; - ret = dvb_register_adapter(adapter, "nGene", - THIS_MODULE, - &chan->dev->pci_dev->dev, - adapter_nr); - if (ret < 0) - goto err; - if (dev->first_adapter == NULL) - dev->first_adapter = adapter; - chan->has_adapter = true; - } else - adapter = dev->first_adapter; - } - - if (dev->ci.en && (io & NGENE_IO_TSOUT)) { - dvb_ca_en50221_init(adapter, dev->ci.en, 0, 1); - set_transfer(chan, 1); - chan->dev->channel[2].DataFormatFlags = DF_SWAP32; - set_transfer(&chan->dev->channel[2], 1); - dvb_register_device(adapter, &chan->ci_dev, - &ngene_dvbdev_ci, (void *) chan, - DVB_DEVICE_SEC); - if (!chan->ci_dev) - goto err; - } - - if (chan->fe) { - if (dvb_register_frontend(adapter, chan->fe) < 0) - goto err; - chan->has_demux = true; - } - if (chan->fe2) { - if (dvb_register_frontend(adapter, chan->fe2) < 0) - goto err; - chan->fe2->tuner_priv = chan->fe->tuner_priv; - memcpy(&chan->fe2->ops.tuner_ops, - &chan->fe->ops.tuner_ops, - sizeof(struct dvb_tuner_ops)); - } - - if (chan->has_demux) { - ret = my_dvb_dmx_ts_card_init(dvbdemux, "SW demux", - ngene_start_feed, - ngene_stop_feed, chan); - ret = my_dvb_dmxdev_ts_card_init(&chan->dmxdev, &chan->demux, - &chan->hw_frontend, - &chan->mem_frontend, adapter); - ret = dvb_net_init(adapter, &chan->dvbnet, &chan->demux.dmx); - } - - return ret; - -err: - if (chan->fe) { - dvb_frontend_detach(chan->fe); - chan->fe = NULL; - } - release_channel(chan); - return 0; -} - -static int init_channels(struct ngene *dev) -{ - int i, j; - - for (i = 0; i < MAX_STREAM; i++) { - dev->channel[i].number = i; - if (init_channel(&dev->channel[i]) < 0) { - for (j = i - 1; j >= 0; j--) - release_channel(&dev->channel[j]); - return -1; - } - } - return 0; -} - -static struct cxd2099_cfg cxd_cfg = { - .bitrate = 62000, - .adr = 0x40, - .polarity = 0, - .clock_mode = 0, -}; - -static void cxd_attach(struct ngene *dev) -{ - struct ngene_ci *ci = &dev->ci; - - ci->en = cxd2099_attach(&cxd_cfg, dev, &dev->channel[0].i2c_adapter); - ci->dev = dev; - return; -} - -static void cxd_detach(struct ngene *dev) -{ - struct ngene_ci *ci = &dev->ci; - - dvb_ca_en50221_release(ci->en); - kfree(ci->en); - ci->en = 0; -} - -/***********************************/ -/* workaround for shutdown failure */ -/***********************************/ - -static void ngene_unlink(struct ngene *dev) -{ - struct ngene_command com; - - com.cmd.hdr.Opcode = CMD_MEM_WRITE; - com.cmd.hdr.Length = 3; - com.cmd.MemoryWrite.address = 0x910c; - com.cmd.MemoryWrite.data = 0xff; - com.in_len = 3; - com.out_len = 1; - - down(&dev->cmd_mutex); - ngwritel(0, NGENE_INT_ENABLE); - ngene_command_mutex(dev, &com); - up(&dev->cmd_mutex); -} - -void ngene_shutdown(struct pci_dev *pdev) -{ - struct ngene *dev = (struct ngene *)pci_get_drvdata(pdev); - - if (!dev || !shutdown_workaround) - return; - - printk(KERN_INFO DEVICE_NAME ": shutdown workaround...\n"); - ngene_unlink(dev); - pci_disable_device(pdev); -} - -/****************************************************************************/ -/* device probe/remove calls ************************************************/ -/****************************************************************************/ - -void __devexit ngene_remove(struct pci_dev *pdev) -{ - struct ngene *dev = pci_get_drvdata(pdev); - int i; - - tasklet_kill(&dev->event_tasklet); - for (i = MAX_STREAM - 1; i >= 0; i--) - release_channel(&dev->channel[i]); - if (dev->ci.en) - cxd_detach(dev); - ngene_stop(dev); - ngene_release_buffers(dev); - pci_set_drvdata(pdev, NULL); - pci_disable_device(pdev); -} - -int __devinit ngene_probe(struct pci_dev *pci_dev, - const struct pci_device_id *id) -{ - struct ngene *dev; - int stat = 0; - - if (pci_enable_device(pci_dev) < 0) - return -ENODEV; - - dev = vzalloc(sizeof(struct ngene)); - if (dev == NULL) { - stat = -ENOMEM; - goto fail0; - } - - dev->pci_dev = pci_dev; - dev->card_info = (struct ngene_info *)id->driver_data; - printk(KERN_INFO DEVICE_NAME ": Found %s\n", dev->card_info->name); - - pci_set_drvdata(pci_dev, dev); - - /* Alloc buffers and start nGene */ - stat = ngene_get_buffers(dev); - if (stat < 0) - goto fail1; - stat = ngene_start(dev); - if (stat < 0) - goto fail1; - - cxd_attach(dev); - - stat = ngene_buffer_config(dev); - if (stat < 0) - goto fail1; - - - dev->i2c_current_bus = -1; - - /* Register DVB adapters and devices for both channels */ - if (init_channels(dev) < 0) - goto fail2; - - return 0; - -fail2: - ngene_stop(dev); -fail1: - ngene_release_buffers(dev); -fail0: - pci_disable_device(pci_dev); - pci_set_drvdata(pci_dev, NULL); - return stat; -} diff --git a/drivers/media/dvb/pluto2/Makefile b/drivers/media/dvb/pluto2/Makefile deleted file mode 100644 index 700822350ec5..000000000000 --- a/drivers/media/dvb/pluto2/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -obj-$(CONFIG_DVB_PLUTO2) += pluto2.o - -ccflags-y += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/ diff --git a/drivers/media/dvb/pt1/Makefile b/drivers/media/dvb/pt1/Makefile deleted file mode 100644 index d80d8e8e7c57..000000000000 --- a/drivers/media/dvb/pt1/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -earth-pt1-objs := pt1.o va1j5jf8007s.o va1j5jf8007t.o - -obj-$(CONFIG_DVB_PT1) += earth-pt1.o - -ccflags-y += -Idrivers/media/dvb/dvb-core -Idrivers/media/dvb/frontends diff --git a/drivers/media/dvb/pt1/va1j5jf8007s.c b/drivers/media/dvb/pt1/va1j5jf8007s.c deleted file mode 100644 index d980dfb21e5e..000000000000 --- a/drivers/media/dvb/pt1/va1j5jf8007s.c +++ /dev/null @@ -1,735 +0,0 @@ -/* - * ISDB-S driver for VA1J5JF8007/VA1J5JF8011 - * - * Copyright (C) 2009 HIRANO Takahito - * - * based on pt1dvr - http://pt1dvr.sourceforge.jp/ - * by Tomoaki Ishikawa - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include "dvb_frontend.h" -#include "va1j5jf8007s.h" - -enum va1j5jf8007s_tune_state { - VA1J5JF8007S_IDLE, - VA1J5JF8007S_SET_FREQUENCY_1, - VA1J5JF8007S_SET_FREQUENCY_2, - VA1J5JF8007S_SET_FREQUENCY_3, - VA1J5JF8007S_CHECK_FREQUENCY, - VA1J5JF8007S_SET_MODULATION, - VA1J5JF8007S_CHECK_MODULATION, - VA1J5JF8007S_SET_TS_ID, - VA1J5JF8007S_CHECK_TS_ID, - VA1J5JF8007S_TRACK, -}; - -struct va1j5jf8007s_state { - const struct va1j5jf8007s_config *config; - struct i2c_adapter *adap; - struct dvb_frontend fe; - enum va1j5jf8007s_tune_state tune_state; -}; - -static int va1j5jf8007s_read_snr(struct dvb_frontend *fe, u16 *snr) -{ - struct va1j5jf8007s_state *state; - u8 addr; - int i; - u8 write_buf[1], read_buf[1]; - struct i2c_msg msgs[2]; - s32 word, x1, x2, x3, x4, x5, y; - - state = fe->demodulator_priv; - addr = state->config->demod_address; - - word = 0; - for (i = 0; i < 2; i++) { - write_buf[0] = 0xbc + i; - - msgs[0].addr = addr; - msgs[0].flags = 0; - msgs[0].len = sizeof(write_buf); - msgs[0].buf = write_buf; - - msgs[1].addr = addr; - msgs[1].flags = I2C_M_RD; - msgs[1].len = sizeof(read_buf); - msgs[1].buf = read_buf; - - if (i2c_transfer(state->adap, msgs, 2) != 2) - return -EREMOTEIO; - - word <<= 8; - word |= read_buf[0]; - } - - word -= 3000; - if (word < 0) - word = 0; - - x1 = int_sqrt(word << 16) * ((15625ll << 21) / 1000000); - x2 = (s64)x1 * x1 >> 31; - x3 = (s64)x2 * x1 >> 31; - x4 = (s64)x2 * x2 >> 31; - x5 = (s64)x4 * x1 >> 31; - - y = (58857ll << 23) / 1000; - y -= (s64)x1 * ((89565ll << 24) / 1000) >> 30; - y += (s64)x2 * ((88977ll << 24) / 1000) >> 28; - y -= (s64)x3 * ((50259ll << 25) / 1000) >> 27; - y += (s64)x4 * ((14341ll << 27) / 1000) >> 27; - y -= (s64)x5 * ((16346ll << 30) / 10000) >> 28; - - *snr = y < 0 ? 0 : y >> 15; - return 0; -} - -static int va1j5jf8007s_get_frontend_algo(struct dvb_frontend *fe) -{ - return DVBFE_ALGO_HW; -} - -static int -va1j5jf8007s_read_status(struct dvb_frontend *fe, fe_status_t *status) -{ - struct va1j5jf8007s_state *state; - - state = fe->demodulator_priv; - - switch (state->tune_state) { - case VA1J5JF8007S_IDLE: - case VA1J5JF8007S_SET_FREQUENCY_1: - case VA1J5JF8007S_SET_FREQUENCY_2: - case VA1J5JF8007S_SET_FREQUENCY_3: - case VA1J5JF8007S_CHECK_FREQUENCY: - *status = 0; - return 0; - - - case VA1J5JF8007S_SET_MODULATION: - case VA1J5JF8007S_CHECK_MODULATION: - *status |= FE_HAS_SIGNAL; - return 0; - - case VA1J5JF8007S_SET_TS_ID: - case VA1J5JF8007S_CHECK_TS_ID: - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER; - return 0; - - case VA1J5JF8007S_TRACK: - *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK; - return 0; - } - - BUG(); -} - -struct va1j5jf8007s_cb_map { - u32 frequency; - u8 cb; -}; - -static const struct va1j5jf8007s_cb_map va1j5jf8007s_cb_maps[] = { - { 986000, 0xb2 }, - { 1072000, 0xd2 }, - { 1154000, 0xe2 }, - { 1291000, 0x20 }, - { 1447000, 0x40 }, - { 1615000, 0x60 }, - { 1791000, 0x80 }, - { 1972000, 0xa0 }, -}; - -static u8 va1j5jf8007s_lookup_cb(u32 frequency) -{ - int i; - const struct va1j5jf8007s_cb_map *map; - - for (i = 0; i < ARRAY_SIZE(va1j5jf8007s_cb_maps); i++) { - map = &va1j5jf8007s_cb_maps[i]; - if (frequency < map->frequency) - return map->cb; - } - return 0xc0; -} - -static int va1j5jf8007s_set_frequency_1(struct va1j5jf8007s_state *state) -{ - u32 frequency; - u16 word; - u8 buf[6]; - struct i2c_msg msg; - - frequency = state->fe.dtv_property_cache.frequency; - - word = (frequency + 500) / 1000; - if (frequency < 1072000) - word = (word << 1 & ~0x1f) | (word & 0x0f); - - buf[0] = 0xfe; - buf[1] = 0xc0; - buf[2] = 0x40 | word >> 8; - buf[3] = word; - buf[4] = 0xe0; - buf[5] = va1j5jf8007s_lookup_cb(frequency); - - msg.addr = state->config->demod_address; - msg.flags = 0; - msg.len = sizeof(buf); - msg.buf = buf; - - if (i2c_transfer(state->adap, &msg, 1) != 1) - return -EREMOTEIO; - - return 0; -} - -static int va1j5jf8007s_set_frequency_2(struct va1j5jf8007s_state *state) -{ - u8 buf[3]; - struct i2c_msg msg; - - buf[0] = 0xfe; - buf[1] = 0xc0; - buf[2] = 0xe4; - - msg.addr = state->config->demod_address; - msg.flags = 0; - msg.len = sizeof(buf); - msg.buf = buf; - - if (i2c_transfer(state->adap, &msg, 1) != 1) - return -EREMOTEIO; - - return 0; -} - -static int va1j5jf8007s_set_frequency_3(struct va1j5jf8007s_state *state) -{ - u32 frequency; - u8 buf[4]; - struct i2c_msg msg; - - frequency = state->fe.dtv_property_cache.frequency; - - buf[0] = 0xfe; - buf[1] = 0xc0; - buf[2] = 0xf4; - buf[3] = va1j5jf8007s_lookup_cb(frequency) | 0x4; - - msg.addr = state->config->demod_address; - msg.flags = 0; - msg.len = sizeof(buf); - msg.buf = buf; - - if (i2c_transfer(state->adap, &msg, 1) != 1) - return -EREMOTEIO; - - return 0; -} - -static int -va1j5jf8007s_check_frequency(struct va1j5jf8007s_state *state, int *lock) -{ - u8 addr; - u8 write_buf[2], read_buf[1]; - struct i2c_msg msgs[2]; - - addr = state->config->demod_address; - - write_buf[0] = 0xfe; - write_buf[1] = 0xc1; - - msgs[0].addr = addr; - msgs[0].flags = 0; - msgs[0].len = sizeof(write_buf); - msgs[0].buf = write_buf; - - msgs[1].addr = addr; - msgs[1].flags = I2C_M_RD; - msgs[1].len = sizeof(read_buf); - msgs[1].buf = read_buf; - - if (i2c_transfer(state->adap, msgs, 2) != 2) - return -EREMOTEIO; - - *lock = read_buf[0] & 0x40; - return 0; -} - -static int va1j5jf8007s_set_modulation(struct va1j5jf8007s_state *state) -{ - u8 buf[2]; - struct i2c_msg msg; - - buf[0] = 0x03; - buf[1] = 0x01; - - msg.addr = state->config->demod_address; - msg.flags = 0; - msg.len = sizeof(buf); - msg.buf = buf; - - if (i2c_transfer(state->adap, &msg, 1) != 1) - return -EREMOTEIO; - - return 0; -} - -static int -va1j5jf8007s_check_modulation(struct va1j5jf8007s_state *state, int *lock) -{ - u8 addr; - u8 write_buf[1], read_buf[1]; - struct i2c_msg msgs[2]; - - addr = state->config->demod_address; - - write_buf[0] = 0xc3; - - msgs[0].addr = addr; - msgs[0].flags = 0; - msgs[0].len = sizeof(write_buf); - msgs[0].buf = write_buf; - - msgs[1].addr = addr; - msgs[1].flags = I2C_M_RD; - msgs[1].len = sizeof(read_buf); - msgs[1].buf = read_buf; - - if (i2c_transfer(state->adap, msgs, 2) != 2) - return -EREMOTEIO; - - *lock = !(read_buf[0] & 0x10); - return 0; -} - -static int -va1j5jf8007s_set_ts_id(struct va1j5jf8007s_state *state) -{ - u32 ts_id; - u8 buf[3]; - struct i2c_msg msg; - - ts_id = state->fe.dtv_property_cache.isdbs_ts_id; - if (!ts_id) - return 0; - - buf[0] = 0x8f; - buf[1] = ts_id >> 8; - buf[2] = ts_id; - - msg.addr = state->config->demod_address; - msg.flags = 0; - msg.len = sizeof(buf); - msg.buf = buf; - - if (i2c_transfer(state->adap, &msg, 1) != 1) - return -EREMOTEIO; - - return 0; -} - -static int -va1j5jf8007s_check_ts_id(struct va1j5jf8007s_state *state, int *lock) -{ - u8 addr; - u8 write_buf[1], read_buf[2]; - struct i2c_msg msgs[2]; - u32 ts_id; - - ts_id = state->fe.dtv_property_cache.isdbs_ts_id; - if (!ts_id) { - *lock = 1; - return 0; - } - - addr = state->config->demod_address; - - write_buf[0] = 0xe6; - - msgs[0].addr = addr; - msgs[0].flags = 0; - msgs[0].len = sizeof(write_buf); - msgs[0].buf = write_buf; - - msgs[1].addr = addr; - msgs[1].flags = I2C_M_RD; - msgs[1].len = sizeof(read_buf); - msgs[1].buf = read_buf; - - if (i2c_transfer(state->adap, msgs, 2) != 2) - return -EREMOTEIO; - - *lock = (read_buf[0] << 8 | read_buf[1]) == ts_id; - return 0; -} - -static int -va1j5jf8007s_tune(struct dvb_frontend *fe, - bool re_tune, - unsigned int mode_flags, unsigned int *delay, - fe_status_t *status) -{ - struct va1j5jf8007s_state *state; - int ret; - int lock = 0; - - state = fe->demodulator_priv; - - if (re_tune) - state->tune_state = VA1J5JF8007S_SET_FREQUENCY_1; - - switch (state->tune_state) { - case VA1J5JF8007S_IDLE: - *delay = 3 * HZ; - *status = 0; - return 0; - - case VA1J5JF8007S_SET_FREQUENCY_1: - ret = va1j5jf8007s_set_frequency_1(state); - if (ret < 0) - return ret; - - state->tune_state = VA1J5JF8007S_SET_FREQUENCY_2; - *delay = 0; - *status = 0; - return 0; - - case VA1J5JF8007S_SET_FREQUENCY_2: - ret = va1j5jf8007s_set_frequency_2(state); - if (ret < 0) - return ret; - - state->tune_state = VA1J5JF8007S_SET_FREQUENCY_3; - *delay = (HZ + 99) / 100; - *status = 0; - return 0; - - case VA1J5JF8007S_SET_FREQUENCY_3: - ret = va1j5jf8007s_set_frequency_3(state); - if (ret < 0) - return ret; - - state->tune_state = VA1J5JF8007S_CHECK_FREQUENCY; - *delay = 0; - *status = 0; - return 0; - - case VA1J5JF8007S_CHECK_FREQUENCY: - ret = va1j5jf8007s_check_frequency(state, &lock); - if (ret < 0) - return ret; - - if (!lock) { - *delay = (HZ + 999) / 1000; - *status = 0; - return 0; - } - - state->tune_state = VA1J5JF8007S_SET_MODULATION; - *delay = 0; - *status = FE_HAS_SIGNAL; - return 0; - - case VA1J5JF8007S_SET_MODULATION: - ret = va1j5jf8007s_set_modulation(state); - if (ret < 0) - return ret; - - state->tune_state = VA1J5JF8007S_CHECK_MODULATION; - *delay = 0; - *status = FE_HAS_SIGNAL; - return 0; - - case VA1J5JF8007S_CHECK_MODULATION: - ret = va1j5jf8007s_check_modulation(state, &lock); - if (ret < 0) - return ret; - - if (!lock) { - *delay = (HZ + 49) / 50; - *status = FE_HAS_SIGNAL; - return 0; - } - - state->tune_state = VA1J5JF8007S_SET_TS_ID; - *delay = 0; - *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; - return 0; - - case VA1J5JF8007S_SET_TS_ID: - ret = va1j5jf8007s_set_ts_id(state); - if (ret < 0) - return ret; - - state->tune_state = VA1J5JF8007S_CHECK_TS_ID; - return 0; - - case VA1J5JF8007S_CHECK_TS_ID: - ret = va1j5jf8007s_check_ts_id(state, &lock); - if (ret < 0) - return ret; - - if (!lock) { - *delay = (HZ + 99) / 100; - *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; - return 0; - } - - state->tune_state = VA1J5JF8007S_TRACK; - /* fall through */ - - case VA1J5JF8007S_TRACK: - *delay = 3 * HZ; - *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK; - return 0; - } - - BUG(); -} - -static int va1j5jf8007s_init_frequency(struct va1j5jf8007s_state *state) -{ - u8 buf[4]; - struct i2c_msg msg; - - buf[0] = 0xfe; - buf[1] = 0xc0; - buf[2] = 0xf0; - buf[3] = 0x04; - - msg.addr = state->config->demod_address; - msg.flags = 0; - msg.len = sizeof(buf); - msg.buf = buf; - - if (i2c_transfer(state->adap, &msg, 1) != 1) - return -EREMOTEIO; - - return 0; -} - -static int va1j5jf8007s_set_sleep(struct va1j5jf8007s_state *state, int sleep) -{ - u8 buf[2]; - struct i2c_msg msg; - - buf[0] = 0x17; - buf[1] = sleep ? 0x01 : 0x00; - - msg.addr = state->config->demod_address; - msg.flags = 0; - msg.len = sizeof(buf); - msg.buf = buf; - - if (i2c_transfer(state->adap, &msg, 1) != 1) - return -EREMOTEIO; - - return 0; -} - -static int va1j5jf8007s_sleep(struct dvb_frontend *fe) -{ - struct va1j5jf8007s_state *state; - int ret; - - state = fe->demodulator_priv; - - ret = va1j5jf8007s_init_frequency(state); - if (ret < 0) - return ret; - - return va1j5jf8007s_set_sleep(state, 1); -} - -static int va1j5jf8007s_init(struct dvb_frontend *fe) -{ - struct va1j5jf8007s_state *state; - - state = fe->demodulator_priv; - state->tune_state = VA1J5JF8007S_IDLE; - - return va1j5jf8007s_set_sleep(state, 0); -} - -static void va1j5jf8007s_release(struct dvb_frontend *fe) -{ - struct va1j5jf8007s_state *state; - state = fe->demodulator_priv; - kfree(state); -} - -static struct dvb_frontend_ops va1j5jf8007s_ops = { - .delsys = { SYS_ISDBS }, - .info = { - .name = "VA1J5JF8007/VA1J5JF8011 ISDB-S", - .frequency_min = 950000, - .frequency_max = 2150000, - .frequency_stepsize = 1000, - .caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | - FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO, - }, - - .read_snr = va1j5jf8007s_read_snr, - .get_frontend_algo = va1j5jf8007s_get_frontend_algo, - .read_status = va1j5jf8007s_read_status, - .tune = va1j5jf8007s_tune, - .sleep = va1j5jf8007s_sleep, - .init = va1j5jf8007s_init, - .release = va1j5jf8007s_release, -}; - -static int va1j5jf8007s_prepare_1(struct va1j5jf8007s_state *state) -{ - u8 addr; - u8 write_buf[1], read_buf[1]; - struct i2c_msg msgs[2]; - - addr = state->config->demod_address; - - write_buf[0] = 0x07; - - msgs[0].addr = addr; - msgs[0].flags = 0; - msgs[0].len = sizeof(write_buf); - msgs[0].buf = write_buf; - - msgs[1].addr = addr; - msgs[1].flags = I2C_M_RD; - msgs[1].len = sizeof(read_buf); - msgs[1].buf = read_buf; - - if (i2c_transfer(state->adap, msgs, 2) != 2) - return -EREMOTEIO; - - if (read_buf[0] != 0x41) - return -EIO; - - return 0; -} - -static const u8 va1j5jf8007s_20mhz_prepare_bufs[][2] = { - {0x04, 0x02}, {0x0d, 0x55}, {0x11, 0x40}, {0x13, 0x80}, {0x17, 0x01}, - {0x1c, 0x0a}, {0x1d, 0xaa}, {0x1e, 0x20}, {0x1f, 0x88}, {0x51, 0xb0}, - {0x52, 0x89}, {0x53, 0xb3}, {0x5a, 0x2d}, {0x5b, 0xd3}, {0x85, 0x69}, - {0x87, 0x04}, {0x8e, 0x02}, {0xa3, 0xf7}, {0xa5, 0xc0}, -}; - -static const u8 va1j5jf8007s_25mhz_prepare_bufs[][2] = { - {0x04, 0x02}, {0x11, 0x40}, {0x13, 0x80}, {0x17, 0x01}, {0x1c, 0x0a}, - {0x1d, 0xaa}, {0x1e, 0x20}, {0x1f, 0x88}, {0x51, 0xb0}, {0x52, 0x89}, - {0x53, 0xb3}, {0x5a, 0x2d}, {0x5b, 0xd3}, {0x85, 0x69}, {0x87, 0x04}, - {0x8e, 0x26}, {0xa3, 0xf7}, {0xa5, 0xc0}, -}; - -static int va1j5jf8007s_prepare_2(struct va1j5jf8007s_state *state) -{ - const u8 (*bufs)[2]; - int size; - u8 addr; - u8 buf[2]; - struct i2c_msg msg; - int i; - - switch (state->config->frequency) { - case VA1J5JF8007S_20MHZ: - bufs = va1j5jf8007s_20mhz_prepare_bufs; - size = ARRAY_SIZE(va1j5jf8007s_20mhz_prepare_bufs); - break; - case VA1J5JF8007S_25MHZ: - bufs = va1j5jf8007s_25mhz_prepare_bufs; - size = ARRAY_SIZE(va1j5jf8007s_25mhz_prepare_bufs); - break; - default: - return -EINVAL; - } - - addr = state->config->demod_address; - - msg.addr = addr; - msg.flags = 0; - msg.len = 2; - msg.buf = buf; - for (i = 0; i < size; i++) { - memcpy(buf, bufs[i], sizeof(buf)); - if (i2c_transfer(state->adap, &msg, 1) != 1) - return -EREMOTEIO; - } - - return 0; -} - -/* must be called after va1j5jf8007t_attach */ -int va1j5jf8007s_prepare(struct dvb_frontend *fe) -{ - struct va1j5jf8007s_state *state; - int ret; - - state = fe->demodulator_priv; - - ret = va1j5jf8007s_prepare_1(state); - if (ret < 0) - return ret; - - ret = va1j5jf8007s_prepare_2(state); - if (ret < 0) - return ret; - - return va1j5jf8007s_init_frequency(state); -} - -struct dvb_frontend * -va1j5jf8007s_attach(const struct va1j5jf8007s_config *config, - struct i2c_adapter *adap) -{ - struct va1j5jf8007s_state *state; - struct dvb_frontend *fe; - u8 buf[2]; - struct i2c_msg msg; - - state = kzalloc(sizeof(struct va1j5jf8007s_state), GFP_KERNEL); - if (!state) - return NULL; - - state->config = config; - state->adap = adap; - - fe = &state->fe; - memcpy(&fe->ops, &va1j5jf8007s_ops, sizeof(struct dvb_frontend_ops)); - fe->demodulator_priv = state; - - buf[0] = 0x01; - buf[1] = 0x80; - - msg.addr = state->config->demod_address; - msg.flags = 0; - msg.len = sizeof(buf); - msg.buf = buf; - - if (i2c_transfer(state->adap, &msg, 1) != 1) { - kfree(state); - return NULL; - } - - return fe; -} diff --git a/drivers/media/dvb/siano/Kconfig b/drivers/media/dvb/siano/Kconfig deleted file mode 100644 index bc6456eb2c4f..000000000000 --- a/drivers/media/dvb/siano/Kconfig +++ /dev/null @@ -1,34 +0,0 @@ -# -# Siano Mobile Silicon Digital TV device configuration -# - -config SMS_SIANO_MDTV - tristate "Siano SMS1xxx based MDTV receiver" - depends on DVB_CORE && RC_CORE && HAS_DMA - ---help--- - Choose Y or M here if you have MDTV receiver with a Siano chipset. - - To compile this driver as a module, choose M here - (The module will be called smsmdtv). - - Further documentation on this driver can be found on the WWW - at http://www.siano-ms.com/ - -if SMS_SIANO_MDTV -menu "Siano module components" - -# Hardware interfaces support - -config SMS_USB_DRV - tristate "USB interface support" - depends on DVB_CORE && USB - ---help--- - Choose if you would like to have Siano's support for USB interface - -config SMS_SDIO_DRV - tristate "SDIO interface support" - depends on DVB_CORE && MMC - ---help--- - Choose if you would like to have Siano's support for SDIO interface -endmenu -endif # SMS_SIANO_MDTV diff --git a/drivers/media/dvb/siano/Makefile b/drivers/media/dvb/siano/Makefile deleted file mode 100644 index f233b57c86fb..000000000000 --- a/drivers/media/dvb/siano/Makefile +++ /dev/null @@ -1,11 +0,0 @@ - -smsmdtv-objs := smscoreapi.o sms-cards.o smsendian.o smsir.o - -obj-$(CONFIG_SMS_SIANO_MDTV) += smsmdtv.o smsdvb.o -obj-$(CONFIG_SMS_USB_DRV) += smsusb.o -obj-$(CONFIG_SMS_SDIO_DRV) += smssdio.o - -ccflags-y += -Idrivers/media/dvb/dvb-core - -ccflags-y += $(extra-cflags-y) $(extra-cflags-m) - diff --git a/drivers/media/dvb/ttpci/Kconfig b/drivers/media/dvb/ttpci/Kconfig deleted file mode 100644 index 9d83ced69dd6..000000000000 --- a/drivers/media/dvb/ttpci/Kconfig +++ /dev/null @@ -1,159 +0,0 @@ -config TTPCI_EEPROM - tristate - depends on I2C - default n - -config DVB_AV7110 - tristate "AV7110 cards" - depends on DVB_CORE && PCI && I2C - select TTPCI_EEPROM - select VIDEO_SAA7146_VV - depends on VIDEO_DEV # dependencies of VIDEO_SAA7146_VV - select DVB_VES1820 if !DVB_FE_CUSTOMISE - select DVB_VES1X93 if !DVB_FE_CUSTOMISE - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_TDA8083 if !DVB_FE_CUSTOMISE - select DVB_SP8870 if !DVB_FE_CUSTOMISE - select DVB_STV0297 if !DVB_FE_CUSTOMISE - select DVB_L64781 if !DVB_FE_CUSTOMISE - select DVB_LNBP21 if !DVB_FE_CUSTOMISE - help - Support for SAA7146 and AV7110 based DVB cards as produced - by Fujitsu-Siemens, Technotrend, Hauppauge and others. - - This driver only supports the fullfeatured cards with - onboard MPEG2 decoder. - - This driver needs an external firmware. Please use the script - "/Documentation/dvb/get_dvb_firmware av7110" to - download/extract it, and then copy it to /usr/lib/hotplug/firmware - or /lib/firmware (depending on configuration of firmware hotplug). - - Alternatively, you can download the file and use the kernel's - EXTRA_FIRMWARE configuration option to build it into your - kernel image by adding the filename to the EXTRA_FIRMWARE - configuration option string. - - Say Y if you own such a card and want to use it. - -config DVB_AV7110_OSD - bool "AV7110 OSD support" - depends on DVB_AV7110 - default y if DVB_AV7110=y || DVB_AV7110=m - help - The AV7110 firmware provides some code to generate an OnScreenDisplay - on the video output. This is kind of nonstandard and not guaranteed to - be maintained. - - Anyway, some popular DVB software like VDR uses this OSD to render - its menus, so say Y if you want to use this software. - - All other people say N. - -config DVB_BUDGET_CORE - tristate "SAA7146 DVB cards (aka Budget, Nova-PCI)" - depends on DVB_CORE && PCI && I2C - select VIDEO_SAA7146 - select TTPCI_EEPROM - help - Support for simple SAA7146 based DVB cards - (so called Budget- or Nova-PCI cards) without onboard - MPEG2 decoder. - -config DVB_BUDGET - tristate "Budget cards" - depends on DVB_BUDGET_CORE && I2C - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_VES1X93 if !DVB_FE_CUSTOMISE - select DVB_VES1820 if !DVB_FE_CUSTOMISE - select DVB_L64781 if !DVB_FE_CUSTOMISE - select DVB_TDA8083 if !DVB_FE_CUSTOMISE - select DVB_S5H1420 if !DVB_FE_CUSTOMISE - select DVB_TDA10086 if !DVB_FE_CUSTOMISE - select DVB_TDA826X if !DVB_FE_CUSTOMISE - select DVB_LNBP21 if !DVB_FE_CUSTOMISE - select DVB_TDA1004X if !DVB_FE_CUSTOMISE - select DVB_ISL6423 if !DVB_FE_CUSTOMISE - select DVB_STV090x if !DVB_FE_CUSTOMISE - select DVB_STV6110x if !DVB_FE_CUSTOMISE - help - Support for simple SAA7146 based DVB cards (so called Budget- - or Nova-PCI cards) without onboard MPEG2 decoder, and without - analog inputs or an onboard Common Interface connector. - - Say Y if you own such a card and want to use it. - - To compile this driver as a module, choose M here: the - module will be called budget. - -config DVB_BUDGET_CI - tristate "Budget cards with onboard CI connector" - depends on DVB_BUDGET_CORE && I2C - select DVB_STV0297 if !DVB_FE_CUSTOMISE - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_TDA1004X if !DVB_FE_CUSTOMISE - select DVB_STB0899 if !DVB_FE_CUSTOMISE - select DVB_STB6100 if !DVB_FE_CUSTOMISE - select DVB_LNBP21 if !DVB_FE_CUSTOMISE - select DVB_STV0288 if !DVB_FE_CUSTOMISE - select DVB_STB6000 if !DVB_FE_CUSTOMISE - select DVB_TDA10023 if !DVB_FE_CUSTOMISE - select MEDIA_TUNER_TDA827X if !MEDIA_TUNER_CUSTOMISE - depends on RC_CORE - help - Support for simple SAA7146 based DVB cards - (so called Budget- or Nova-PCI cards) without onboard - MPEG2 decoder, but with onboard Common Interface connector. - - Note: The Common Interface is not yet supported by this driver - due to lack of information from the vendor. - - Say Y if you own such a card and want to use it. - - To compile this driver as a module, choose M here: the - module will be called budget-ci. - -config DVB_BUDGET_AV - tristate "Budget cards with analog video inputs" - depends on DVB_BUDGET_CORE && I2C - select VIDEO_SAA7146_VV - depends on VIDEO_DEV # dependencies of VIDEO_SAA7146_VV - select DVB_PLL if !DVB_FE_CUSTOMISE - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_TDA1004X if !DVB_FE_CUSTOMISE - select DVB_TDA10021 if !DVB_FE_CUSTOMISE - select DVB_TDA10023 if !DVB_FE_CUSTOMISE - select DVB_STB0899 if !DVB_FE_CUSTOMISE - select DVB_TDA8261 if !DVB_FE_CUSTOMISE - select DVB_TUA6100 if !DVB_FE_CUSTOMISE - help - Support for simple SAA7146 based DVB cards - (so called Budget- or Nova-PCI cards) without onboard - MPEG2 decoder, but with one or more analog video inputs. - - Say Y if you own such a card and want to use it. - - To compile this driver as a module, choose M here: the - module will be called budget-av. - -config DVB_BUDGET_PATCH - tristate "AV7110 cards with Budget Patch" - depends on DVB_BUDGET_CORE && I2C - depends on DVB_AV7110 - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_VES1X93 if !DVB_FE_CUSTOMISE - select DVB_TDA8083 if !DVB_FE_CUSTOMISE - help - Support for Budget Patch (full TS) modification on - SAA7146+AV7110 based cards (DVB-S cards). This - driver doesn't use onboard MPEG2 decoder. The - card is driven in Budget-only mode. Card is - required to have loaded firmware to tune properly. - Firmware can be loaded by insertion and removal of - standard AV7110 driver prior to loading this - driver. - - Say Y if you own such a card and want to use it. - - To compile this driver as a module, choose M here: the - module will be called budget-patch. diff --git a/drivers/media/dvb/ttpci/Makefile b/drivers/media/dvb/ttpci/Makefile deleted file mode 100644 index f6e869372e30..000000000000 --- a/drivers/media/dvb/ttpci/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# -# Makefile for the kernel SAA7146 FULL TS DVB device driver -# and the AV7110 DVB device driver -# - -dvb-ttpci-objs := av7110_hw.o av7110_v4l.o av7110_av.o av7110_ca.o av7110.o av7110_ipack.o - -ifdef CONFIG_INPUT_EVDEV -dvb-ttpci-objs += av7110_ir.o -endif - -obj-$(CONFIG_TTPCI_EEPROM) += ttpci-eeprom.o -obj-$(CONFIG_DVB_BUDGET_CORE) += budget-core.o -obj-$(CONFIG_DVB_BUDGET) += budget.o -obj-$(CONFIG_DVB_BUDGET_AV) += budget-av.o -obj-$(CONFIG_DVB_BUDGET_CI) += budget-ci.o -obj-$(CONFIG_DVB_BUDGET_PATCH) += budget-patch.o -obj-$(CONFIG_DVB_AV7110) += dvb-ttpci.o - -ccflags-y += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/ -ccflags-y += -Idrivers/media/common/tuners diff --git a/drivers/media/dvb/ttpci/av7110_v4l.c b/drivers/media/dvb/ttpci/av7110_v4l.c deleted file mode 100644 index 1b2d15140a1d..000000000000 --- a/drivers/media/dvb/ttpci/av7110_v4l.c +++ /dev/null @@ -1,966 +0,0 @@ -/* - * av7110_v4l.c: av7110 video4linux interface for DVB and Siemens DVB-C analog module - * - * Copyright (C) 1999-2002 Ralph Metzler - * & Marcus Metzler for convergence integrated media GmbH - * - * originally based on code by: - * Copyright (C) 1998,1999 Christian Theiss - * - * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * Or, point your browser to http://www.gnu.org/copyleft/gpl.html - * - * the project's page is at http://www.linuxtv.org/ - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include - -#include "av7110.h" -#include "av7110_hw.h" -#include "av7110_av.h" - -int msp_writereg(struct av7110 *av7110, u8 dev, u16 reg, u16 val) -{ - u8 msg[5] = { dev, reg >> 8, reg & 0xff, val >> 8 , val & 0xff }; - struct i2c_msg msgs = { .flags = 0, .len = 5, .buf = msg }; - - switch (av7110->adac_type) { - case DVB_ADAC_MSP34x0: - msgs.addr = 0x40; - break; - case DVB_ADAC_MSP34x5: - msgs.addr = 0x42; - break; - default: - return 0; - } - - if (i2c_transfer(&av7110->i2c_adap, &msgs, 1) != 1) { - dprintk(1, "dvb-ttpci: failed @ card %d, %u = %u\n", - av7110->dvb_adapter.num, reg, val); - return -EIO; - } - return 0; -} - -static int msp_readreg(struct av7110 *av7110, u8 dev, u16 reg, u16 *val) -{ - u8 msg1[3] = { dev, reg >> 8, reg & 0xff }; - u8 msg2[2]; - struct i2c_msg msgs[2] = { - { .flags = 0 , .len = 3, .buf = msg1 }, - { .flags = I2C_M_RD, .len = 2, .buf = msg2 } - }; - - switch (av7110->adac_type) { - case DVB_ADAC_MSP34x0: - msgs[0].addr = 0x40; - msgs[1].addr = 0x40; - break; - case DVB_ADAC_MSP34x5: - msgs[0].addr = 0x42; - msgs[1].addr = 0x42; - break; - default: - return 0; - } - - if (i2c_transfer(&av7110->i2c_adap, &msgs[0], 2) != 2) { - dprintk(1, "dvb-ttpci: failed @ card %d, %u\n", - av7110->dvb_adapter.num, reg); - return -EIO; - } - *val = (msg2[0] << 8) | msg2[1]; - return 0; -} - -static struct v4l2_input inputs[4] = { - { - .index = 0, - .name = "DVB", - .type = V4L2_INPUT_TYPE_CAMERA, - .audioset = 1, - .tuner = 0, /* ignored */ - .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, - .status = 0, - .capabilities = V4L2_IN_CAP_STD, - }, { - .index = 1, - .name = "Television", - .type = V4L2_INPUT_TYPE_TUNER, - .audioset = 1, - .tuner = 0, - .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, - .status = 0, - .capabilities = V4L2_IN_CAP_STD, - }, { - .index = 2, - .name = "Video", - .type = V4L2_INPUT_TYPE_CAMERA, - .audioset = 0, - .tuner = 0, - .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, - .status = 0, - .capabilities = V4L2_IN_CAP_STD, - }, { - .index = 3, - .name = "Y/C", - .type = V4L2_INPUT_TYPE_CAMERA, - .audioset = 0, - .tuner = 0, - .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, - .status = 0, - .capabilities = V4L2_IN_CAP_STD, - } -}; - -static int ves1820_writereg(struct saa7146_dev *dev, u8 addr, u8 reg, u8 data) -{ - struct av7110 *av7110 = dev->ext_priv; - u8 buf[] = { 0x00, reg, data }; - struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = buf, .len = 3 }; - - dprintk(4, "dev: %p\n", dev); - - if (1 != i2c_transfer(&av7110->i2c_adap, &msg, 1)) - return -1; - return 0; -} - -static int tuner_write(struct saa7146_dev *dev, u8 addr, u8 data [4]) -{ - struct av7110 *av7110 = dev->ext_priv; - struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = data, .len = 4 }; - - dprintk(4, "dev: %p\n", dev); - - if (1 != i2c_transfer(&av7110->i2c_adap, &msg, 1)) - return -1; - return 0; -} - -static int ves1820_set_tv_freq(struct saa7146_dev *dev, u32 freq) -{ - u32 div; - u8 config; - u8 buf[4]; - - dprintk(4, "freq: 0x%08x\n", freq); - - /* magic number: 614. tuning with the frequency given by v4l2 - is always off by 614*62.5 = 38375 kHz...*/ - div = freq + 614; - - buf[0] = (div >> 8) & 0x7f; - buf[1] = div & 0xff; - buf[2] = 0x8e; - - if (freq < (u32) (16 * 168.25)) - config = 0xa0; - else if (freq < (u32) (16 * 447.25)) - config = 0x90; - else - config = 0x30; - config &= ~0x02; - - buf[3] = config; - - return tuner_write(dev, 0x61, buf); -} - -static int stv0297_set_tv_freq(struct saa7146_dev *dev, u32 freq) -{ - struct av7110 *av7110 = (struct av7110*)dev->ext_priv; - u32 div; - u8 data[4]; - - div = (freq + 38900000 + 31250) / 62500; - - data[0] = (div >> 8) & 0x7f; - data[1] = div & 0xff; - data[2] = 0xce; - - if (freq < 45000000) - return -EINVAL; - else if (freq < 137000000) - data[3] = 0x01; - else if (freq < 403000000) - data[3] = 0x02; - else if (freq < 860000000) - data[3] = 0x04; - else - return -EINVAL; - - if (av7110->fe->ops.i2c_gate_ctrl) - av7110->fe->ops.i2c_gate_ctrl(av7110->fe, 1); - return tuner_write(dev, 0x63, data); -} - - - -static struct saa7146_standard analog_standard[]; -static struct saa7146_standard dvb_standard[]; -static struct saa7146_standard standard[]; - -static struct v4l2_audio msp3400_v4l2_audio = { - .index = 0, - .name = "Television", - .capability = V4L2_AUDCAP_STEREO -}; - -static int av7110_dvb_c_switch(struct saa7146_fh *fh) -{ - struct saa7146_dev *dev = fh->dev; - struct saa7146_vv *vv = dev->vv_data; - struct av7110 *av7110 = (struct av7110*)dev->ext_priv; - u16 adswitch; - int source, sync, err; - - dprintk(4, "%p\n", av7110); - - if ((vv->video_status & STATUS_OVERLAY) != 0) { - vv->ov_suspend = vv->video_fh; - err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ - if (err != 0) { - dprintk(2, "suspending video failed\n"); - vv->ov_suspend = NULL; - } - } - - if (0 != av7110->current_input) { - dprintk(1, "switching to analog TV:\n"); - adswitch = 1; - source = SAA7146_HPS_SOURCE_PORT_B; - sync = SAA7146_HPS_SYNC_PORT_B; - memcpy(standard, analog_standard, sizeof(struct saa7146_standard) * 2); - - switch (av7110->current_input) { - case 1: - dprintk(1, "switching SAA7113 to Analog Tuner Input\n"); - msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0000); // loudspeaker source - msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0000); // headphone source - msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0000); // SCART 1 source - msp_writereg(av7110, MSP_WR_DSP, 0x000e, 0x3000); // FM matrix, mono - msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x4f00); // loudspeaker + headphone - msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x4f00); // SCART 1 volume - - if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { - if (ves1820_writereg(dev, 0x09, 0x0f, 0x60)) - dprintk(1, "setting band in demodulator failed\n"); - } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { - saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); // TDA9819 pin9(STD) - saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); // TDA9819 pin30(VIF) - } - if (i2c_writereg(av7110, 0x48, 0x02, 0xd0) != 1) - dprintk(1, "saa7113 write failed @ card %d", av7110->dvb_adapter.num); - break; - case 2: - dprintk(1, "switching SAA7113 to Video AV CVBS Input\n"); - if (i2c_writereg(av7110, 0x48, 0x02, 0xd2) != 1) - dprintk(1, "saa7113 write failed @ card %d", av7110->dvb_adapter.num); - break; - case 3: - dprintk(1, "switching SAA7113 to Video AV Y/C Input\n"); - if (i2c_writereg(av7110, 0x48, 0x02, 0xd9) != 1) - dprintk(1, "saa7113 write failed @ card %d", av7110->dvb_adapter.num); - break; - default: - dprintk(1, "switching SAA7113 to Input: AV7110: SAA7113: invalid input\n"); - } - } else { - adswitch = 0; - source = SAA7146_HPS_SOURCE_PORT_A; - sync = SAA7146_HPS_SYNC_PORT_A; - memcpy(standard, dvb_standard, sizeof(struct saa7146_standard) * 2); - dprintk(1, "switching DVB mode\n"); - msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0220); // loudspeaker source - msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0220); // headphone source - msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0220); // SCART 1 source - msp_writereg(av7110, MSP_WR_DSP, 0x000e, 0x3000); // FM matrix, mono - msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x7f00); // loudspeaker + headphone - msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x7f00); // SCART 1 volume - - if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { - if (ves1820_writereg(dev, 0x09, 0x0f, 0x20)) - dprintk(1, "setting band in demodulator failed\n"); - } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { - saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTLO); // TDA9819 pin9(STD) - saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); // TDA9819 pin30(VIF) - } - } - - /* hmm, this does not do anything!? */ - if (av7110_fw_cmd(av7110, COMTYPE_AUDIODAC, ADSwitch, 1, adswitch)) - dprintk(1, "ADSwitch error\n"); - - saa7146_set_hps_source_and_sync(dev, source, sync); - - if (vv->ov_suspend != NULL) { - saa7146_start_preview(vv->ov_suspend); - vv->ov_suspend = NULL; - } - - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *fh, struct v4l2_tuner *t) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - u16 stereo_det; - s8 stereo; - - dprintk(2, "VIDIOC_G_TUNER: %d\n", t->index); - - if (!av7110->analog_tuner_flags || t->index != 0) - return -EINVAL; - - memset(t, 0, sizeof(*t)); - strcpy((char *)t->name, "Television"); - - t->type = V4L2_TUNER_ANALOG_TV; - t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | - V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; - t->rangelow = 772; /* 48.25 MHZ / 62.5 kHz = 772, see fi1216mk2-specs, page 2 */ - t->rangehigh = 13684; /* 855.25 MHz / 62.5 kHz = 13684 */ - /* FIXME: add the real signal strength here */ - t->signal = 0xffff; - t->afc = 0; - - /* FIXME: standard / stereo detection is still broken */ - msp_readreg(av7110, MSP_RD_DEM, 0x007e, &stereo_det); - dprintk(1, "VIDIOC_G_TUNER: msp3400 TV standard detection: 0x%04x\n", stereo_det); - msp_readreg(av7110, MSP_RD_DSP, 0x0018, &stereo_det); - dprintk(1, "VIDIOC_G_TUNER: msp3400 stereo detection: 0x%04x\n", stereo_det); - stereo = (s8)(stereo_det >> 8); - if (stereo > 0x10) { - /* stereo */ - t->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; - t->audmode = V4L2_TUNER_MODE_STEREO; - } else if (stereo < -0x10) { - /* bilingual */ - t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; - t->audmode = V4L2_TUNER_MODE_LANG1; - } else /* mono */ - t->rxsubchans = V4L2_TUNER_SUB_MONO; - - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *fh, struct v4l2_tuner *t) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - u16 fm_matrix, src; - dprintk(2, "VIDIOC_S_TUNER: %d\n", t->index); - - if (!av7110->analog_tuner_flags || av7110->current_input != 1) - return -EINVAL; - - switch (t->audmode) { - case V4L2_TUNER_MODE_STEREO: - dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_STEREO\n"); - fm_matrix = 0x3001; /* stereo */ - src = 0x0020; - break; - case V4L2_TUNER_MODE_LANG1_LANG2: - dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1_LANG2\n"); - fm_matrix = 0x3000; /* bilingual */ - src = 0x0020; - break; - case V4L2_TUNER_MODE_LANG1: - dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1\n"); - fm_matrix = 0x3000; /* mono */ - src = 0x0000; - break; - case V4L2_TUNER_MODE_LANG2: - dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG2\n"); - fm_matrix = 0x3000; /* mono */ - src = 0x0010; - break; - default: /* case V4L2_TUNER_MODE_MONO: */ - dprintk(2, "VIDIOC_S_TUNER: TDA9840_SET_MONO\n"); - fm_matrix = 0x3000; /* mono */ - src = 0x0030; - break; - } - msp_writereg(av7110, MSP_WR_DSP, 0x000e, fm_matrix); - msp_writereg(av7110, MSP_WR_DSP, 0x0008, src); - msp_writereg(av7110, MSP_WR_DSP, 0x0009, src); - msp_writereg(av7110, MSP_WR_DSP, 0x000a, src); - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *fh, struct v4l2_frequency *f) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - dprintk(2, "VIDIOC_G_FREQ: freq:0x%08x\n", f->frequency); - - if (!av7110->analog_tuner_flags || av7110->current_input != 1) - return -EINVAL; - - memset(f, 0, sizeof(*f)); - f->type = V4L2_TUNER_ANALOG_TV; - f->frequency = av7110->current_freq; - return 0; -} - -static int vidioc_s_frequency(struct file *file, void *fh, struct v4l2_frequency *f) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - dprintk(2, "VIDIOC_S_FREQUENCY: freq:0x%08x\n", f->frequency); - - if (!av7110->analog_tuner_flags || av7110->current_input != 1) - return -EINVAL; - - if (V4L2_TUNER_ANALOG_TV != f->type) - return -EINVAL; - - msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0xffe0); /* fast mute */ - msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0xffe0); - - /* tune in desired frequency */ - if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) - ves1820_set_tv_freq(dev, f->frequency); - else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) - stv0297_set_tv_freq(dev, f->frequency); - av7110->current_freq = f->frequency; - - msp_writereg(av7110, MSP_WR_DSP, 0x0015, 0x003f); /* start stereo detection */ - msp_writereg(av7110, MSP_WR_DSP, 0x0015, 0x0000); - msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x4f00); /* loudspeaker + headphone */ - msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x4f00); /* SCART 1 volume */ - return 0; -} - -static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - dprintk(2, "VIDIOC_ENUMINPUT: %d\n", i->index); - - if (av7110->analog_tuner_flags) { - if (i->index >= 4) - return -EINVAL; - } else { - if (i->index != 0) - return -EINVAL; - } - - memcpy(i, &inputs[i->index], sizeof(struct v4l2_input)); - - return 0; -} - -static int vidioc_g_input(struct file *file, void *fh, unsigned int *input) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - *input = av7110->current_input; - dprintk(2, "VIDIOC_G_INPUT: %d\n", *input); - return 0; -} - -static int vidioc_s_input(struct file *file, void *fh, unsigned int input) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - dprintk(2, "VIDIOC_S_INPUT: %d\n", input); - - if (!av7110->analog_tuner_flags) - return input ? -EINVAL : 0; - - if (input >= 4) - return -EINVAL; - - av7110->current_input = input; - return av7110_dvb_c_switch(fh); -} - -static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a) -{ - dprintk(2, "VIDIOC_G_AUDIO: %d\n", a->index); - if (a->index != 0) - return -EINVAL; - *a = msp3400_v4l2_audio; - return 0; -} - -static int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - dprintk(2, "VIDIOC_G_AUDIO: %d\n", a->index); - if (a->index != 0) - return -EINVAL; - if (av7110->current_input >= 2) - return -EINVAL; - *a = msp3400_v4l2_audio; - return 0; -} - -static int vidioc_s_audio(struct file *file, void *fh, struct v4l2_audio *a) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - dprintk(2, "VIDIOC_S_AUDIO: %d\n", a->index); - if (av7110->current_input >= 2) - return -EINVAL; - return a->index ? -EINVAL : 0; -} - -static int vidioc_g_sliced_vbi_cap(struct file *file, void *fh, - struct v4l2_sliced_vbi_cap *cap) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - dprintk(2, "VIDIOC_G_SLICED_VBI_CAP\n"); - if (cap->type != V4L2_BUF_TYPE_SLICED_VBI_OUTPUT) - return -EINVAL; - if (FW_VERSION(av7110->arm_app) >= 0x2623) { - cap->service_set = V4L2_SLICED_WSS_625; - cap->service_lines[0][23] = V4L2_SLICED_WSS_625; - } - return 0; -} - -static int vidioc_g_fmt_sliced_vbi_out(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - dprintk(2, "VIDIOC_G_FMT:\n"); - if (FW_VERSION(av7110->arm_app) < 0x2623) - return -EINVAL; - memset(&f->fmt.sliced, 0, sizeof f->fmt.sliced); - if (av7110->wssMode) { - f->fmt.sliced.service_set = V4L2_SLICED_WSS_625; - f->fmt.sliced.service_lines[0][23] = V4L2_SLICED_WSS_625; - f->fmt.sliced.io_size = sizeof(struct v4l2_sliced_vbi_data); - } - return 0; -} - -static int vidioc_s_fmt_sliced_vbi_out(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; - struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; - - dprintk(2, "VIDIOC_S_FMT\n"); - if (FW_VERSION(av7110->arm_app) < 0x2623) - return -EINVAL; - if (f->fmt.sliced.service_set != V4L2_SLICED_WSS_625 && - f->fmt.sliced.service_lines[0][23] != V4L2_SLICED_WSS_625) { - memset(&f->fmt.sliced, 0, sizeof(f->fmt.sliced)); - /* WSS controlled by firmware */ - av7110->wssMode = 0; - av7110->wssData = 0; - return av7110_fw_cmd(av7110, COMTYPE_ENCODER, - SetWSSConfig, 1, 0); - } else { - memset(&f->fmt.sliced, 0, sizeof(f->fmt.sliced)); - f->fmt.sliced.service_set = V4L2_SLICED_WSS_625; - f->fmt.sliced.service_lines[0][23] = V4L2_SLICED_WSS_625; - f->fmt.sliced.io_size = sizeof(struct v4l2_sliced_vbi_data); - /* WSS controlled by userspace */ - av7110->wssMode = 1; - av7110->wssData = 0; - } - return 0; -} - -static int av7110_vbi_reset(struct file *file) -{ - struct saa7146_fh *fh = file->private_data; - struct saa7146_dev *dev = fh->dev; - struct av7110 *av7110 = (struct av7110*) dev->ext_priv; - - dprintk(2, "%s\n", __func__); - av7110->wssMode = 0; - av7110->wssData = 0; - if (FW_VERSION(av7110->arm_app) < 0x2623) - return 0; - else - return av7110_fw_cmd(av7110, COMTYPE_ENCODER, SetWSSConfig, 1, 0); -} - -static ssize_t av7110_vbi_write(struct file *file, const char __user *data, size_t count, loff_t *ppos) -{ - struct saa7146_fh *fh = file->private_data; - struct saa7146_dev *dev = fh->dev; - struct av7110 *av7110 = (struct av7110*) dev->ext_priv; - struct v4l2_sliced_vbi_data d; - int rc; - - dprintk(2, "%s\n", __func__); - if (FW_VERSION(av7110->arm_app) < 0x2623 || !av7110->wssMode || count != sizeof d) - return -EINVAL; - if (copy_from_user(&d, data, count)) - return -EFAULT; - if ((d.id != 0 && d.id != V4L2_SLICED_WSS_625) || d.field != 0 || d.line != 23) - return -EINVAL; - if (d.id) - av7110->wssData = ((d.data[1] << 8) & 0x3f00) | d.data[0]; - else - av7110->wssData = 0x8000; - rc = av7110_fw_cmd(av7110, COMTYPE_ENCODER, SetWSSConfig, 2, 1, av7110->wssData); - return (rc < 0) ? rc : count; -} - -/**************************************************************************** - * INITIALIZATION - ****************************************************************************/ - -static u8 saa7113_init_regs[] = { - 0x02, 0xd0, - 0x03, 0x23, - 0x04, 0x00, - 0x05, 0x00, - 0x06, 0xe9, - 0x07, 0x0d, - 0x08, 0x98, - 0x09, 0x02, - 0x0a, 0x80, - 0x0b, 0x40, - 0x0c, 0x40, - 0x0d, 0x00, - 0x0e, 0x01, - 0x0f, 0x7c, - 0x10, 0x48, - 0x11, 0x0c, - 0x12, 0x8b, - 0x13, 0x1a, - 0x14, 0x00, - 0x15, 0x00, - 0x16, 0x00, - 0x17, 0x00, - 0x18, 0x00, - 0x19, 0x00, - 0x1a, 0x00, - 0x1b, 0x00, - 0x1c, 0x00, - 0x1d, 0x00, - 0x1e, 0x00, - - 0x41, 0x77, - 0x42, 0x77, - 0x43, 0x77, - 0x44, 0x77, - 0x45, 0x77, - 0x46, 0x77, - 0x47, 0x77, - 0x48, 0x77, - 0x49, 0x77, - 0x4a, 0x77, - 0x4b, 0x77, - 0x4c, 0x77, - 0x4d, 0x77, - 0x4e, 0x77, - 0x4f, 0x77, - 0x50, 0x77, - 0x51, 0x77, - 0x52, 0x77, - 0x53, 0x77, - 0x54, 0x77, - 0x55, 0x77, - 0x56, 0x77, - 0x57, 0xff, - - 0xff -}; - - -static struct saa7146_ext_vv av7110_vv_data_st; -static struct saa7146_ext_vv av7110_vv_data_c; - -int av7110_init_analog_module(struct av7110 *av7110) -{ - u16 version1, version2; - - if (i2c_writereg(av7110, 0x80, 0x0, 0x80) == 1 && - i2c_writereg(av7110, 0x80, 0x0, 0) == 1) { - pr_info("DVB-C analog module @ card %d detected, initializing MSP3400\n", - av7110->dvb_adapter.num); - av7110->adac_type = DVB_ADAC_MSP34x0; - } else if (i2c_writereg(av7110, 0x84, 0x0, 0x80) == 1 && - i2c_writereg(av7110, 0x84, 0x0, 0) == 1) { - pr_info("DVB-C analog module @ card %d detected, initializing MSP3415\n", - av7110->dvb_adapter.num); - av7110->adac_type = DVB_ADAC_MSP34x5; - } else - return -ENODEV; - - msleep(100); // the probing above resets the msp... - msp_readreg(av7110, MSP_RD_DSP, 0x001e, &version1); - msp_readreg(av7110, MSP_RD_DSP, 0x001f, &version2); - dprintk(1, "dvb-ttpci: @ card %d MSP34xx version 0x%04x 0x%04x\n", - av7110->dvb_adapter.num, version1, version2); - msp_writereg(av7110, MSP_WR_DSP, 0x0013, 0x0c00); - msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x7f00); // loudspeaker + headphone - msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0220); // loudspeaker source - msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0220); // headphone source - msp_writereg(av7110, MSP_WR_DSP, 0x0004, 0x7f00); // loudspeaker volume - msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0220); // SCART 1 source - msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x7f00); // SCART 1 volume - msp_writereg(av7110, MSP_WR_DSP, 0x000d, 0x1900); // prescale SCART - - if (i2c_writereg(av7110, 0x48, 0x01, 0x00)!=1) { - pr_info("saa7113 not accessible\n"); - } else { - u8 *i = saa7113_init_regs; - - if ((av7110->dev->pci->subsystem_vendor == 0x110a) && (av7110->dev->pci->subsystem_device == 0x0000)) { - /* Fujitsu/Siemens DVB-Cable */ - av7110->analog_tuner_flags |= ANALOG_TUNER_VES1820; - } else if ((av7110->dev->pci->subsystem_vendor == 0x13c2) && (av7110->dev->pci->subsystem_device == 0x0002)) { - /* Hauppauge/TT DVB-C premium */ - av7110->analog_tuner_flags |= ANALOG_TUNER_VES1820; - } else if ((av7110->dev->pci->subsystem_vendor == 0x13c2) && (av7110->dev->pci->subsystem_device == 0x000A)) { - /* Hauppauge/TT DVB-C premium */ - av7110->analog_tuner_flags |= ANALOG_TUNER_STV0297; - } - - /* setup for DVB by default */ - if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { - if (ves1820_writereg(av7110->dev, 0x09, 0x0f, 0x20)) - dprintk(1, "setting band in demodulator failed\n"); - } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { - saa7146_setgpio(av7110->dev, 1, SAA7146_GPIO_OUTLO); // TDA9819 pin9(STD) - saa7146_setgpio(av7110->dev, 3, SAA7146_GPIO_OUTLO); // TDA9819 pin30(VIF) - } - - /* init the saa7113 */ - while (*i != 0xff) { - if (i2c_writereg(av7110, 0x48, i[0], i[1]) != 1) { - dprintk(1, "saa7113 initialization failed @ card %d", av7110->dvb_adapter.num); - break; - } - i += 2; - } - /* setup msp for analog sound: B/G Dual-FM */ - msp_writereg(av7110, MSP_WR_DEM, 0x00bb, 0x02d0); // AD_CV - msp_writereg(av7110, MSP_WR_DEM, 0x0001, 3); // FIR1 - msp_writereg(av7110, MSP_WR_DEM, 0x0001, 18); // FIR1 - msp_writereg(av7110, MSP_WR_DEM, 0x0001, 27); // FIR1 - msp_writereg(av7110, MSP_WR_DEM, 0x0001, 48); // FIR1 - msp_writereg(av7110, MSP_WR_DEM, 0x0001, 66); // FIR1 - msp_writereg(av7110, MSP_WR_DEM, 0x0001, 72); // FIR1 - msp_writereg(av7110, MSP_WR_DEM, 0x0005, 4); // FIR2 - msp_writereg(av7110, MSP_WR_DEM, 0x0005, 64); // FIR2 - msp_writereg(av7110, MSP_WR_DEM, 0x0005, 0); // FIR2 - msp_writereg(av7110, MSP_WR_DEM, 0x0005, 3); // FIR2 - msp_writereg(av7110, MSP_WR_DEM, 0x0005, 18); // FIR2 - msp_writereg(av7110, MSP_WR_DEM, 0x0005, 27); // FIR2 - msp_writereg(av7110, MSP_WR_DEM, 0x0005, 48); // FIR2 - msp_writereg(av7110, MSP_WR_DEM, 0x0005, 66); // FIR2 - msp_writereg(av7110, MSP_WR_DEM, 0x0005, 72); // FIR2 - msp_writereg(av7110, MSP_WR_DEM, 0x0083, 0xa000); // MODE_REG - msp_writereg(av7110, MSP_WR_DEM, 0x0093, 0x00aa); // DCO1_LO 5.74MHz - msp_writereg(av7110, MSP_WR_DEM, 0x009b, 0x04fc); // DCO1_HI - msp_writereg(av7110, MSP_WR_DEM, 0x00a3, 0x038e); // DCO2_LO 5.5MHz - msp_writereg(av7110, MSP_WR_DEM, 0x00ab, 0x04c6); // DCO2_HI - msp_writereg(av7110, MSP_WR_DEM, 0x0056, 0); // LOAD_REG 1/2 - } - - memcpy(standard, dvb_standard, sizeof(struct saa7146_standard) * 2); - /* set dd1 stream a & b */ - saa7146_write(av7110->dev, DD1_STREAM_B, 0x00000000); - saa7146_write(av7110->dev, DD1_INIT, 0x03000700); - saa7146_write(av7110->dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); - - return 0; -} - -int av7110_init_v4l(struct av7110 *av7110) -{ - struct saa7146_dev* dev = av7110->dev; - struct saa7146_ext_vv *vv_data; - int ret; - - /* special case DVB-C: these cards have an analog tuner - plus need some special handling, so we have separate - saa7146_ext_vv data for these... */ - if (av7110->analog_tuner_flags) - vv_data = &av7110_vv_data_c; - else - vv_data = &av7110_vv_data_st; - ret = saa7146_vv_init(dev, vv_data); - - if (ret) { - ERR("cannot init capture device. skipping\n"); - return -ENODEV; - } - vv_data->vid_ops.vidioc_enum_input = vidioc_enum_input; - vv_data->vid_ops.vidioc_g_input = vidioc_g_input; - vv_data->vid_ops.vidioc_s_input = vidioc_s_input; - vv_data->vid_ops.vidioc_g_tuner = vidioc_g_tuner; - vv_data->vid_ops.vidioc_s_tuner = vidioc_s_tuner; - vv_data->vid_ops.vidioc_g_frequency = vidioc_g_frequency; - vv_data->vid_ops.vidioc_s_frequency = vidioc_s_frequency; - vv_data->vid_ops.vidioc_enumaudio = vidioc_enumaudio; - vv_data->vid_ops.vidioc_g_audio = vidioc_g_audio; - vv_data->vid_ops.vidioc_s_audio = vidioc_s_audio; - vv_data->vid_ops.vidioc_g_fmt_vbi_cap = NULL; - - vv_data->vbi_ops.vidioc_g_tuner = vidioc_g_tuner; - vv_data->vbi_ops.vidioc_s_tuner = vidioc_s_tuner; - vv_data->vbi_ops.vidioc_g_frequency = vidioc_g_frequency; - vv_data->vbi_ops.vidioc_s_frequency = vidioc_s_frequency; - vv_data->vbi_ops.vidioc_g_fmt_vbi_cap = NULL; - vv_data->vbi_ops.vidioc_g_sliced_vbi_cap = vidioc_g_sliced_vbi_cap; - vv_data->vbi_ops.vidioc_g_fmt_sliced_vbi_out = vidioc_g_fmt_sliced_vbi_out; - vv_data->vbi_ops.vidioc_s_fmt_sliced_vbi_out = vidioc_s_fmt_sliced_vbi_out; - - if (FW_VERSION(av7110->arm_app) < 0x2623) - vv_data->capabilities &= ~V4L2_CAP_SLICED_VBI_OUTPUT; - - if (saa7146_register_device(&av7110->v4l_dev, dev, "av7110", VFL_TYPE_GRABBER)) { - ERR("cannot register capture device. skipping\n"); - saa7146_vv_release(dev); - return -ENODEV; - } - if (FW_VERSION(av7110->arm_app) >= 0x2623) { - if (saa7146_register_device(&av7110->vbi_dev, dev, "av7110", VFL_TYPE_VBI)) - ERR("cannot register vbi v4l2 device. skipping\n"); - } - return 0; -} - -int av7110_exit_v4l(struct av7110 *av7110) -{ - struct saa7146_dev* dev = av7110->dev; - - saa7146_unregister_device(&av7110->v4l_dev, av7110->dev); - saa7146_unregister_device(&av7110->vbi_dev, av7110->dev); - - saa7146_vv_release(dev); - - return 0; -} - - - -/* FIXME: these values are experimental values that look better than the - values from the latest "official" driver -- at least for me... (MiHu) */ -static struct saa7146_standard standard[] = { - { - .name = "PAL", .id = V4L2_STD_PAL_BG, - .v_offset = 0x15, .v_field = 288, - .h_offset = 0x48, .h_pixels = 708, - .v_max_out = 576, .h_max_out = 768, - }, { - .name = "NTSC", .id = V4L2_STD_NTSC, - .v_offset = 0x10, .v_field = 244, - .h_offset = 0x40, .h_pixels = 708, - .v_max_out = 480, .h_max_out = 640, - } -}; - -static struct saa7146_standard analog_standard[] = { - { - .name = "PAL", .id = V4L2_STD_PAL_BG, - .v_offset = 0x1b, .v_field = 288, - .h_offset = 0x08, .h_pixels = 708, - .v_max_out = 576, .h_max_out = 768, - }, { - .name = "NTSC", .id = V4L2_STD_NTSC, - .v_offset = 0x10, .v_field = 244, - .h_offset = 0x40, .h_pixels = 708, - .v_max_out = 480, .h_max_out = 640, - } -}; - -static struct saa7146_standard dvb_standard[] = { - { - .name = "PAL", .id = V4L2_STD_PAL_BG, - .v_offset = 0x14, .v_field = 288, - .h_offset = 0x48, .h_pixels = 708, - .v_max_out = 576, .h_max_out = 768, - }, { - .name = "NTSC", .id = V4L2_STD_NTSC, - .v_offset = 0x10, .v_field = 244, - .h_offset = 0x40, .h_pixels = 708, - .v_max_out = 480, .h_max_out = 640, - } -}; - -static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std) -{ - struct av7110 *av7110 = (struct av7110*) dev->ext_priv; - - if (std->id & V4L2_STD_PAL) { - av7110->vidmode = AV7110_VIDEO_MODE_PAL; - av7110_set_vidmode(av7110, av7110->vidmode); - } - else if (std->id & V4L2_STD_NTSC) { - av7110->vidmode = AV7110_VIDEO_MODE_NTSC; - av7110_set_vidmode(av7110, av7110->vidmode); - } - else - return -1; - - return 0; -} - - -static struct saa7146_ext_vv av7110_vv_data_st = { - .inputs = 1, - .audios = 1, - .capabilities = V4L2_CAP_SLICED_VBI_OUTPUT | V4L2_CAP_AUDIO, - .flags = 0, - - .stds = &standard[0], - .num_stds = ARRAY_SIZE(standard), - .std_callback = &std_callback, - - .vbi_fops.open = av7110_vbi_reset, - .vbi_fops.release = av7110_vbi_reset, - .vbi_fops.write = av7110_vbi_write, -}; - -static struct saa7146_ext_vv av7110_vv_data_c = { - .inputs = 1, - .audios = 1, - .capabilities = V4L2_CAP_TUNER | V4L2_CAP_SLICED_VBI_OUTPUT | V4L2_CAP_AUDIO, - .flags = SAA7146_USE_PORT_B_FOR_VBI, - - .stds = &standard[0], - .num_stds = ARRAY_SIZE(standard), - .std_callback = &std_callback, - - .vbi_fops.open = av7110_vbi_reset, - .vbi_fops.release = av7110_vbi_reset, - .vbi_fops.write = av7110_vbi_write, -}; - diff --git a/drivers/media/dvb/ttpci/budget.c b/drivers/media/dvb/ttpci/budget.c deleted file mode 100644 index b21bcce66708..000000000000 --- a/drivers/media/dvb/ttpci/budget.c +++ /dev/null @@ -1,811 +0,0 @@ -/* - * budget.c: driver for the SAA7146 based Budget DVB cards - * - * Compiled from various sources by Michael Hunold - * - * Copyright (C) 2002 Ralph Metzler - * - * Copyright (C) 1999-2002 Ralph Metzler - * & Marcus Metzler for convergence integrated media GmbH - * - * 26feb2004 Support for FS Activy Card (Grundig tuner) by - * Michael Dreher , - * Oliver Endriss and - * Andreas 'randy' Weinberger - * - * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * Or, point your browser to http://www.gnu.org/copyleft/gpl.html - * - * - * the project's page is at http://www.linuxtv.org/ - */ - -#include "budget.h" -#include "stv0299.h" -#include "ves1x93.h" -#include "ves1820.h" -#include "l64781.h" -#include "tda8083.h" -#include "s5h1420.h" -#include "tda10086.h" -#include "tda826x.h" -#include "lnbp21.h" -#include "bsru6.h" -#include "bsbe1.h" -#include "tdhd1.h" -#include "stv6110x.h" -#include "stv090x.h" -#include "isl6423.h" - -static int diseqc_method; -module_param(diseqc_method, int, 0444); -MODULE_PARM_DESC(diseqc_method, "Select DiSEqC method for subsystem id 13c2:1003, 0: default, 1: more reliable (for newer revisions only)"); - -DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); - -static void Set22K (struct budget *budget, int state) -{ - struct saa7146_dev *dev=budget->dev; - dprintk(2, "budget: %p\n", budget); - saa7146_setgpio(dev, 3, (state ? SAA7146_GPIO_OUTHI : SAA7146_GPIO_OUTLO)); -} - -/* Diseqc functions only for TT Budget card */ -/* taken from the Skyvision DVB driver by - Ralph Metzler */ - -static void DiseqcSendBit (struct budget *budget, int data) -{ - struct saa7146_dev *dev=budget->dev; - dprintk(2, "budget: %p\n", budget); - - saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); - udelay(data ? 500 : 1000); - saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); - udelay(data ? 1000 : 500); -} - -static void DiseqcSendByte (struct budget *budget, int data) -{ - int i, par=1, d; - - dprintk(2, "budget: %p\n", budget); - - for (i=7; i>=0; i--) { - d = (data>>i)&1; - par ^= d; - DiseqcSendBit(budget, d); - } - - DiseqcSendBit(budget, par); -} - -static int SendDiSEqCMsg (struct budget *budget, int len, u8 *msg, unsigned long burst) -{ - struct saa7146_dev *dev=budget->dev; - int i; - - dprintk(2, "budget: %p\n", budget); - - saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); - mdelay(16); - - for (i=0; idev; - - dprintk(2, "budget: %p\n", budget); - - switch (voltage) { - case SEC_VOLTAGE_13: - saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); - saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTLO); - break; - case SEC_VOLTAGE_18: - saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); - saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); - break; - case SEC_VOLTAGE_OFF: - saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTLO); - break; - default: - return -EINVAL; - } - - return 0; -} - -static int siemens_budget_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) -{ - struct budget* budget = (struct budget*) fe->dvb->priv; - - return SetVoltage_Activy (budget, voltage); -} - -static int budget_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) -{ - struct budget* budget = (struct budget*) fe->dvb->priv; - - switch (tone) { - case SEC_TONE_ON: - Set22K (budget, 1); - break; - - case SEC_TONE_OFF: - Set22K (budget, 0); - break; - - default: - return -EINVAL; - } - - return 0; -} - -static int budget_diseqc_send_master_cmd(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd) -{ - struct budget* budget = (struct budget*) fe->dvb->priv; - - SendDiSEqCMsg (budget, cmd->msg_len, cmd->msg, 0); - - return 0; -} - -static int budget_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd) -{ - struct budget* budget = (struct budget*) fe->dvb->priv; - - SendDiSEqCMsg (budget, 0, NULL, minicmd); - - return 0; -} - -static int alps_bsrv2_tuner_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct budget* budget = (struct budget*) fe->dvb->priv; - u8 pwr = 0; - u8 buf[4]; - struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; - u32 div = (c->frequency + 479500) / 125; - - if (c->frequency > 2000000) - pwr = 3; - else if (c->frequency > 1800000) - pwr = 2; - else if (c->frequency > 1600000) - pwr = 1; - else if (c->frequency > 1200000) - pwr = 0; - else if (c->frequency >= 1100000) - pwr = 1; - else pwr = 2; - - buf[0] = (div >> 8) & 0x7f; - buf[1] = div & 0xff; - buf[2] = ((div & 0x18000) >> 10) | 0x95; - buf[3] = (pwr << 6) | 0x30; - - // NOTE: since we're using a prescaler of 2, we set the - // divisor frequency to 62.5kHz and divide by 125 above - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; - return 0; -} - -static struct ves1x93_config alps_bsrv2_config = -{ - .demod_address = 0x08, - .xin = 90100000UL, - .invert_pwm = 0, -}; - -static int alps_tdbe2_tuner_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct budget* budget = (struct budget*) fe->dvb->priv; - u32 div; - u8 data[4]; - struct i2c_msg msg = { .addr = 0x62, .flags = 0, .buf = data, .len = sizeof(data) }; - - div = (c->frequency + 35937500 + 31250) / 62500; - - data[0] = (div >> 8) & 0x7f; - data[1] = div & 0xff; - data[2] = 0x85 | ((div >> 10) & 0x60); - data[3] = (c->frequency < 174000000 ? 0x88 : c->frequency < 470000000 ? 0x84 : 0x81); - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; - return 0; -} - -static struct ves1820_config alps_tdbe2_config = { - .demod_address = 0x09, - .xin = 57840000UL, - .invert = 1, - .selagc = VES1820_SELAGC_SIGNAMPERR, -}; - -static int grundig_29504_401_tuner_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct budget *budget = fe->dvb->priv; - u8 *tuner_addr = fe->tuner_priv; - u32 div; - u8 cfg, cpump, band_select; - u8 data[4]; - struct i2c_msg msg = { .flags = 0, .buf = data, .len = sizeof(data) }; - - if (tuner_addr) - msg.addr = *tuner_addr; - else - msg.addr = 0x61; - - div = (36125000 + c->frequency) / 166666; - - cfg = 0x88; - - if (c->frequency < 175000000) - cpump = 2; - else if (c->frequency < 390000000) - cpump = 1; - else if (c->frequency < 470000000) - cpump = 2; - else if (c->frequency < 750000000) - cpump = 1; - else - cpump = 3; - - if (c->frequency < 175000000) - band_select = 0x0e; - else if (c->frequency < 470000000) - band_select = 0x05; - else - band_select = 0x03; - - data[0] = (div >> 8) & 0x7f; - data[1] = div & 0xff; - data[2] = ((div >> 10) & 0x60) | cfg; - data[3] = (cpump << 6) | band_select; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; - return 0; -} - -static struct l64781_config grundig_29504_401_config = { - .demod_address = 0x55, -}; - -static struct l64781_config grundig_29504_401_config_activy = { - .demod_address = 0x54, -}; - -static u8 tuner_address_grundig_29504_401_activy = 0x60; - -static int grundig_29504_451_tuner_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct budget* budget = (struct budget*) fe->dvb->priv; - u32 div; - u8 data[4]; - struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; - - div = c->frequency / 125; - data[0] = (div >> 8) & 0x7f; - data[1] = div & 0xff; - data[2] = 0x8e; - data[3] = 0x00; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; - return 0; -} - -static struct tda8083_config grundig_29504_451_config = { - .demod_address = 0x68, -}; - -static int s5h1420_tuner_set_params(struct dvb_frontend *fe) -{ - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct budget* budget = (struct budget*) fe->dvb->priv; - u32 div; - u8 data[4]; - struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; - - div = c->frequency / 1000; - data[0] = (div >> 8) & 0x7f; - data[1] = div & 0xff; - data[2] = 0xc2; - - if (div < 1450) - data[3] = 0x00; - else if (div < 1850) - data[3] = 0x40; - else if (div < 2000) - data[3] = 0x80; - else - data[3] = 0xc0; - - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; - - return 0; -} - -static struct s5h1420_config s5h1420_config = { - .demod_address = 0x53, - .invert = 1, - .cdclk_polarity = 1, -}; - -static struct tda10086_config tda10086_config = { - .demod_address = 0x0e, - .invert = 0, - .diseqc_tone = 1, - .xtal_freq = TDA10086_XTAL_16M, -}; - -static struct stv0299_config alps_bsru6_config_activy = { - .demod_address = 0x68, - .inittab = alps_bsru6_inittab, - .mclk = 88000000UL, - .invert = 1, - .op0_off = 1, - .min_delay_ms = 100, - .set_symbol_rate = alps_bsru6_set_symbol_rate, -}; - -static struct stv0299_config alps_bsbe1_config_activy = { - .demod_address = 0x68, - .inittab = alps_bsbe1_inittab, - .mclk = 88000000UL, - .invert = 1, - .op0_off = 1, - .min_delay_ms = 100, - .set_symbol_rate = alps_bsbe1_set_symbol_rate, -}; - -static int alps_tdhd1_204_request_firmware(struct dvb_frontend *fe, const struct firmware **fw, char *name) -{ - struct budget *budget = (struct budget *)fe->dvb->priv; - - return request_firmware(fw, name, &budget->dev->pci->dev); -} - - -static int i2c_readreg(struct i2c_adapter *i2c, u8 adr, u8 reg) -{ - u8 val; - struct i2c_msg msg[] = { - { .addr = adr, .flags = 0, .buf = ®, .len = 1 }, - { .addr = adr, .flags = I2C_M_RD, .buf = &val, .len = 1 } - }; - - return (i2c_transfer(i2c, msg, 2) != 2) ? -EIO : val; -} - -static u8 read_pwm(struct budget* budget) -{ - u8 b = 0xff; - u8 pwm; - struct i2c_msg msg[] = { { .addr = 0x50,.flags = 0,.buf = &b,.len = 1 }, - { .addr = 0x50,.flags = I2C_M_RD,.buf = &pwm,.len = 1} }; - - if ((i2c_transfer(&budget->i2c_adap, msg, 2) != 2) || (pwm == 0xff)) - pwm = 0x48; - - return pwm; -} - -static struct stv090x_config tt1600_stv090x_config = { - .device = STV0903, - .demod_mode = STV090x_SINGLE, - .clk_mode = STV090x_CLK_EXT, - - .xtal = 13500000, - .address = 0x68, - - .ts1_mode = STV090x_TSMODE_DVBCI, - .ts2_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, - - .repeater_level = STV090x_RPTLEVEL_16, - - .tuner_init = NULL, - .tuner_sleep = NULL, - .tuner_set_mode = NULL, - .tuner_set_frequency = NULL, - .tuner_get_frequency = NULL, - .tuner_set_bandwidth = NULL, - .tuner_get_bandwidth = NULL, - .tuner_set_bbgain = NULL, - .tuner_get_bbgain = NULL, - .tuner_set_refclk = NULL, - .tuner_get_status = NULL, -}; - -static struct stv6110x_config tt1600_stv6110x_config = { - .addr = 0x60, - .refclk = 27000000, - .clk_div = 2, -}; - -static struct isl6423_config tt1600_isl6423_config = { - .current_max = SEC_CURRENT_515m, - .curlim = SEC_CURRENT_LIM_ON, - .mod_extern = 1, - .addr = 0x08, -}; - -static void frontend_init(struct budget *budget) -{ - (void)alps_bsbe1_config; /* avoid warning */ - - switch(budget->dev->pci->subsystem_device) { - case 0x1003: // Hauppauge/TT Nova budget (stv0299/ALPS BSRU6(tsa5059) OR ves1893/ALPS BSRV2(sp5659)) - case 0x1013: - // try the ALPS BSRV2 first of all - budget->dvb_frontend = dvb_attach(ves1x93_attach, &alps_bsrv2_config, &budget->i2c_adap); - if (budget->dvb_frontend) { - budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsrv2_tuner_set_params; - budget->dvb_frontend->ops.diseqc_send_master_cmd = budget_diseqc_send_master_cmd; - budget->dvb_frontend->ops.diseqc_send_burst = budget_diseqc_send_burst; - budget->dvb_frontend->ops.set_tone = budget_set_tone; - break; - } - - // try the ALPS BSRU6 now - budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config, &budget->i2c_adap); - if (budget->dvb_frontend) { - budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; - budget->dvb_frontend->tuner_priv = &budget->i2c_adap; - if (budget->dev->pci->subsystem_device == 0x1003 && diseqc_method == 0) { - budget->dvb_frontend->ops.diseqc_send_master_cmd = budget_diseqc_send_master_cmd; - budget->dvb_frontend->ops.diseqc_send_burst = budget_diseqc_send_burst; - budget->dvb_frontend->ops.set_tone = budget_set_tone; - } - break; - } - break; - - case 0x1004: // Hauppauge/TT DVB-C budget (ves1820/ALPS TDBE2(sp5659)) - - budget->dvb_frontend = dvb_attach(ves1820_attach, &alps_tdbe2_config, &budget->i2c_adap, read_pwm(budget)); - if (budget->dvb_frontend) { - budget->dvb_frontend->ops.tuner_ops.set_params = alps_tdbe2_tuner_set_params; - break; - } - break; - - case 0x1005: // Hauppauge/TT Nova-T budget (L64781/Grundig 29504-401(tsa5060)) - - budget->dvb_frontend = dvb_attach(l64781_attach, &grundig_29504_401_config, &budget->i2c_adap); - if (budget->dvb_frontend) { - budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_401_tuner_set_params; - budget->dvb_frontend->tuner_priv = NULL; - break; - } - break; - - case 0x4f60: /* Fujitsu Siemens Activy Budget-S PCI rev AL (stv0299/tsa5059) */ - { - int subtype = i2c_readreg(&budget->i2c_adap, 0x50, 0x67); - - if (subtype < 0) - break; - /* fixme: find a better way to identify the card */ - if (subtype < 0x36) { - /* assume ALPS BSRU6 */ - budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config_activy, &budget->i2c_adap); - if (budget->dvb_frontend) { - printk(KERN_INFO "budget: tuner ALPS BSRU6 detected\n"); - budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; - budget->dvb_frontend->tuner_priv = &budget->i2c_adap; - budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; - budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; - break; - } - } else { - /* assume ALPS BSBE1 */ - /* reset tuner */ - saa7146_setgpio(budget->dev, 3, SAA7146_GPIO_OUTLO); - msleep(50); - saa7146_setgpio(budget->dev, 3, SAA7146_GPIO_OUTHI); - msleep(250); - budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsbe1_config_activy, &budget->i2c_adap); - if (budget->dvb_frontend) { - printk(KERN_INFO "budget: tuner ALPS BSBE1 detected\n"); - budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsbe1_tuner_set_params; - budget->dvb_frontend->tuner_priv = &budget->i2c_adap; - budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; - budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; - break; - } - } - break; - } - - case 0x4f61: // Fujitsu Siemens Activy Budget-S PCI rev GR (tda8083/Grundig 29504-451(tsa5522)) - budget->dvb_frontend = dvb_attach(tda8083_attach, &grundig_29504_451_config, &budget->i2c_adap); - if (budget->dvb_frontend) { - budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_451_tuner_set_params; - budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; - budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; - } - break; - - case 0x5f60: /* Fujitsu Siemens Activy Budget-T PCI rev AL (tda10046/ALPS TDHD1-204A) */ - budget->dvb_frontend = dvb_attach(tda10046_attach, &alps_tdhd1_204a_config, &budget->i2c_adap); - if (budget->dvb_frontend) { - budget->dvb_frontend->ops.tuner_ops.set_params = alps_tdhd1_204a_tuner_set_params; - budget->dvb_frontend->tuner_priv = &budget->i2c_adap; - } - break; - - case 0x5f61: /* Fujitsu Siemens Activy Budget-T PCI rev GR (L64781/Grundig 29504-401(tsa5060)) */ - budget->dvb_frontend = dvb_attach(l64781_attach, &grundig_29504_401_config_activy, &budget->i2c_adap); - if (budget->dvb_frontend) { - budget->dvb_frontend->tuner_priv = &tuner_address_grundig_29504_401_activy; - budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_401_tuner_set_params; - } - break; - - case 0x1016: // Hauppauge/TT Nova-S SE (samsung s5h1420/????(tda8260)) - budget->dvb_frontend = dvb_attach(s5h1420_attach, &s5h1420_config, &budget->i2c_adap); - if (budget->dvb_frontend) { - budget->dvb_frontend->ops.tuner_ops.set_params = s5h1420_tuner_set_params; - if (dvb_attach(lnbp21_attach, budget->dvb_frontend, &budget->i2c_adap, 0, 0) == NULL) { - printk("%s: No LNBP21 found!\n", __func__); - goto error_out; - } - break; - } - - case 0x1018: // TT Budget-S-1401 (philips tda10086/philips tda8262) - // gpio2 is connected to CLB - reset it + leave it high - saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); - msleep(1); - saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); - msleep(1); - - budget->dvb_frontend = dvb_attach(tda10086_attach, &tda10086_config, &budget->i2c_adap); - if (budget->dvb_frontend) { - if (dvb_attach(tda826x_attach, budget->dvb_frontend, 0x60, &budget->i2c_adap, 0) == NULL) - printk("%s: No tda826x found!\n", __func__); - if (dvb_attach(lnbp21_attach, budget->dvb_frontend, &budget->i2c_adap, 0, 0) == NULL) { - printk("%s: No LNBP21 found!\n", __func__); - goto error_out; - } - break; - } - - case 0x101c: { /* TT S2-1600 */ - struct stv6110x_devctl *ctl; - saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); - msleep(50); - saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); - msleep(250); - - budget->dvb_frontend = dvb_attach(stv090x_attach, - &tt1600_stv090x_config, - &budget->i2c_adap, - STV090x_DEMODULATOR_0); - - if (budget->dvb_frontend) { - - ctl = dvb_attach(stv6110x_attach, - budget->dvb_frontend, - &tt1600_stv6110x_config, - &budget->i2c_adap); - - if (ctl) { - tt1600_stv090x_config.tuner_init = ctl->tuner_init; - tt1600_stv090x_config.tuner_sleep = ctl->tuner_sleep; - tt1600_stv090x_config.tuner_set_mode = ctl->tuner_set_mode; - tt1600_stv090x_config.tuner_set_frequency = ctl->tuner_set_frequency; - tt1600_stv090x_config.tuner_get_frequency = ctl->tuner_get_frequency; - tt1600_stv090x_config.tuner_set_bandwidth = ctl->tuner_set_bandwidth; - tt1600_stv090x_config.tuner_get_bandwidth = ctl->tuner_get_bandwidth; - tt1600_stv090x_config.tuner_set_bbgain = ctl->tuner_set_bbgain; - tt1600_stv090x_config.tuner_get_bbgain = ctl->tuner_get_bbgain; - tt1600_stv090x_config.tuner_set_refclk = ctl->tuner_set_refclk; - tt1600_stv090x_config.tuner_get_status = ctl->tuner_get_status; - - /* call the init function once to initialize - tuner's clock output divider and demod's - master clock */ - if (budget->dvb_frontend->ops.init) - budget->dvb_frontend->ops.init(budget->dvb_frontend); - - if (dvb_attach(isl6423_attach, - budget->dvb_frontend, - &budget->i2c_adap, - &tt1600_isl6423_config) == NULL) { - printk(KERN_ERR "%s: No Intersil ISL6423 found!\n", __func__); - goto error_out; - } - } else { - printk(KERN_ERR "%s: No STV6110(A) Silicon Tuner found!\n", __func__); - goto error_out; - } - } - } - break; - } - - if (budget->dvb_frontend == NULL) { - printk("budget: A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n", - budget->dev->pci->vendor, - budget->dev->pci->device, - budget->dev->pci->subsystem_vendor, - budget->dev->pci->subsystem_device); - } else { - if (dvb_register_frontend(&budget->dvb_adapter, budget->dvb_frontend)) - goto error_out; - } - return; - -error_out: - printk("budget: Frontend registration failed!\n"); - dvb_frontend_detach(budget->dvb_frontend); - budget->dvb_frontend = NULL; - return; -} - -static int budget_attach (struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) -{ - struct budget *budget = NULL; - int err; - - budget = kmalloc(sizeof(struct budget), GFP_KERNEL); - if( NULL == budget ) { - return -ENOMEM; - } - - dprintk(2, "dev:%p, info:%p, budget:%p\n", dev, info, budget); - - dev->ext_priv = budget; - - err = ttpci_budget_init(budget, dev, info, THIS_MODULE, adapter_nr); - if (err) { - printk("==> failed\n"); - kfree (budget); - return err; - } - - budget->dvb_adapter.priv = budget; - frontend_init(budget); - - ttpci_budget_init_hooks(budget); - - return 0; -} - -static int budget_detach (struct saa7146_dev* dev) -{ - struct budget *budget = (struct budget*) dev->ext_priv; - int err; - - if (budget->dvb_frontend) { - dvb_unregister_frontend(budget->dvb_frontend); - dvb_frontend_detach(budget->dvb_frontend); - } - - err = ttpci_budget_deinit (budget); - - kfree (budget); - dev->ext_priv = NULL; - - return err; -} - -static struct saa7146_extension budget_extension; - -MAKE_BUDGET_INFO(ttbs, "TT-Budget/WinTV-NOVA-S PCI", BUDGET_TT); -MAKE_BUDGET_INFO(ttbc, "TT-Budget/WinTV-NOVA-C PCI", BUDGET_TT); -MAKE_BUDGET_INFO(ttbt, "TT-Budget/WinTV-NOVA-T PCI", BUDGET_TT); -MAKE_BUDGET_INFO(satel, "SATELCO Multimedia PCI", BUDGET_TT_HW_DISEQC); -MAKE_BUDGET_INFO(ttbs1401, "TT-Budget-S-1401 PCI", BUDGET_TT); -MAKE_BUDGET_INFO(tt1600, "TT-Budget S2-1600 PCI", BUDGET_TT); -MAKE_BUDGET_INFO(fsacs0, "Fujitsu Siemens Activy Budget-S PCI (rev GR/grundig frontend)", BUDGET_FS_ACTIVY); -MAKE_BUDGET_INFO(fsacs1, "Fujitsu Siemens Activy Budget-S PCI (rev AL/alps frontend)", BUDGET_FS_ACTIVY); -MAKE_BUDGET_INFO(fsact, "Fujitsu Siemens Activy Budget-T PCI (rev GR/Grundig frontend)", BUDGET_FS_ACTIVY); -MAKE_BUDGET_INFO(fsact1, "Fujitsu Siemens Activy Budget-T PCI (rev AL/ALPS TDHD1-204A)", BUDGET_FS_ACTIVY); - -static struct pci_device_id pci_tbl[] = { - MAKE_EXTENSION_PCI(ttbs, 0x13c2, 0x1003), - MAKE_EXTENSION_PCI(ttbc, 0x13c2, 0x1004), - MAKE_EXTENSION_PCI(ttbt, 0x13c2, 0x1005), - MAKE_EXTENSION_PCI(satel, 0x13c2, 0x1013), - MAKE_EXTENSION_PCI(ttbs, 0x13c2, 0x1016), - MAKE_EXTENSION_PCI(ttbs1401, 0x13c2, 0x1018), - MAKE_EXTENSION_PCI(tt1600, 0x13c2, 0x101c), - MAKE_EXTENSION_PCI(fsacs1,0x1131, 0x4f60), - MAKE_EXTENSION_PCI(fsacs0,0x1131, 0x4f61), - MAKE_EXTENSION_PCI(fsact1, 0x1131, 0x5f60), - MAKE_EXTENSION_PCI(fsact, 0x1131, 0x5f61), - { - .vendor = 0, - } -}; - -MODULE_DEVICE_TABLE(pci, pci_tbl); - -static struct saa7146_extension budget_extension = { - .name = "budget dvb", - .flags = SAA7146_USE_I2C_IRQ, - - .module = THIS_MODULE, - .pci_tbl = pci_tbl, - .attach = budget_attach, - .detach = budget_detach, - - .irq_mask = MASK_10, - .irq_func = ttpci_budget_irq10_handler, -}; - -static int __init budget_init(void) -{ - return saa7146_register_extension(&budget_extension); -} - -static void __exit budget_exit(void) -{ - saa7146_unregister_extension(&budget_extension); -} - -module_init(budget_init); -module_exit(budget_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, Michael Hunold, others"); -MODULE_DESCRIPTION("driver for the SAA7146 based so-called " - "budget PCI DVB cards by Siemens, Technotrend, Hauppauge"); diff --git a/drivers/media/dvb/ttusb-budget/Kconfig b/drivers/media/dvb/ttusb-budget/Kconfig deleted file mode 100644 index 2663ae39b886..000000000000 --- a/drivers/media/dvb/ttusb-budget/Kconfig +++ /dev/null @@ -1,18 +0,0 @@ -config DVB_TTUSB_BUDGET - tristate "Technotrend/Hauppauge Nova-USB devices" - depends on DVB_CORE && USB && I2C && PCI - select DVB_CX22700 if !DVB_FE_CUSTOMISE - select DVB_TDA1004X if !DVB_FE_CUSTOMISE - select DVB_VES1820 if !DVB_FE_CUSTOMISE - select DVB_TDA8083 if !DVB_FE_CUSTOMISE - select DVB_STV0299 if !DVB_FE_CUSTOMISE - select DVB_STV0297 if !DVB_FE_CUSTOMISE - select DVB_LNBP21 if !DVB_FE_CUSTOMISE - help - Support for external USB adapters designed by Technotrend and - produced by Hauppauge, shipped under the brand name 'Nova-USB'. - - These devices don't have a MPEG decoder built in, so you need - an external software decoder to watch TV. - - Say Y if you own such a device and want to use it. diff --git a/drivers/media/dvb/ttusb-budget/Makefile b/drivers/media/dvb/ttusb-budget/Makefile deleted file mode 100644 index 8d6c4acb7f1d..000000000000 --- a/drivers/media/dvb/ttusb-budget/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -obj-$(CONFIG_DVB_TTUSB_BUDGET) += dvb-ttusb-budget.o - -ccflags-y += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends diff --git a/drivers/media/dvb/ttusb-dec/Makefile b/drivers/media/dvb/ttusb-dec/Makefile deleted file mode 100644 index ed28b5384d20..000000000000 --- a/drivers/media/dvb/ttusb-dec/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -obj-$(CONFIG_DVB_TTUSB_DEC) += ttusb_dec.o ttusbdecfe.o - -ccflags-y += -Idrivers/media/dvb/dvb-core/ diff --git a/drivers/media/dvb/firewire/Kconfig b/drivers/media/firewire/Kconfig similarity index 100% rename from drivers/media/dvb/firewire/Kconfig rename to drivers/media/firewire/Kconfig diff --git a/drivers/media/firewire/Makefile b/drivers/media/firewire/Makefile new file mode 100644 index 000000000000..239481344d7c --- /dev/null +++ b/drivers/media/firewire/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_DVB_FIREDTV) += firedtv.o + +firedtv-y += firedtv-avc.o firedtv-ci.o firedtv-dvb.o firedtv-fe.o firedtv-fw.o +firedtv-$(CONFIG_DVB_FIREDTV_INPUT) += firedtv-rc.o + +ccflags-y += -Idrivers/media/dvb-core diff --git a/drivers/media/dvb/firewire/firedtv-avc.c b/drivers/media/firewire/firedtv-avc.c similarity index 100% rename from drivers/media/dvb/firewire/firedtv-avc.c rename to drivers/media/firewire/firedtv-avc.c diff --git a/drivers/media/dvb/firewire/firedtv-ci.c b/drivers/media/firewire/firedtv-ci.c similarity index 100% rename from drivers/media/dvb/firewire/firedtv-ci.c rename to drivers/media/firewire/firedtv-ci.c diff --git a/drivers/media/dvb/firewire/firedtv-dvb.c b/drivers/media/firewire/firedtv-dvb.c similarity index 100% rename from drivers/media/dvb/firewire/firedtv-dvb.c rename to drivers/media/firewire/firedtv-dvb.c diff --git a/drivers/media/dvb/firewire/firedtv-fe.c b/drivers/media/firewire/firedtv-fe.c similarity index 100% rename from drivers/media/dvb/firewire/firedtv-fe.c rename to drivers/media/firewire/firedtv-fe.c diff --git a/drivers/media/dvb/firewire/firedtv-fw.c b/drivers/media/firewire/firedtv-fw.c similarity index 100% rename from drivers/media/dvb/firewire/firedtv-fw.c rename to drivers/media/firewire/firedtv-fw.c diff --git a/drivers/media/dvb/firewire/firedtv-rc.c b/drivers/media/firewire/firedtv-rc.c similarity index 100% rename from drivers/media/dvb/firewire/firedtv-rc.c rename to drivers/media/firewire/firedtv-rc.c diff --git a/drivers/media/dvb/firewire/firedtv.h b/drivers/media/firewire/firedtv.h similarity index 100% rename from drivers/media/dvb/firewire/firedtv.h rename to drivers/media/firewire/firedtv.h diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig new file mode 100644 index 000000000000..24d78e28e493 --- /dev/null +++ b/drivers/media/i2c/Kconfig @@ -0,0 +1,591 @@ +# +# Generic video config states +# + +config VIDEO_BTCX + depends on PCI + tristate + +config VIDEO_TVEEPROM + tristate + depends on I2C + +# +# Multimedia Video device configuration +# + +if VIDEO_V4L2 + +config VIDEO_IR_I2C + tristate "I2C module for IR" if !MEDIA_SUBDRV_AUTOSELECT + depends on I2C && RC_CORE + default y + ---help--- + Most boards have an IR chip directly connected via GPIO. However, + some video boards have the IR connected via I2C bus. + + If your board doesn't have an I2C IR chip, you may disable this + option. + + In doubt, say Y. + +# +# Encoder / Decoder module configuration +# + +menu "Encoders, decoders, sensors and other helper chips" + visible if !MEDIA_SUBDRV_AUTOSELECT + +comment "Audio decoders, processors and mixers" + +config VIDEO_TVAUDIO + tristate "Simple audio decoder chips" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for several audio decoder chips found on some bt8xx boards: + Philips: tda9840, tda9873h, tda9874h/a, tda9850, tda985x, tea6300, + tea6320, tea6420, tda8425, ta8874z. + Microchip: pic16c54 based design on ProVideo PV951 board. + + To compile this driver as a module, choose M here: the + module will be called tvaudio. + +config VIDEO_TDA7432 + tristate "Philips TDA7432 audio processor" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for tda7432 audio decoder chip found on some bt8xx boards. + + To compile this driver as a module, choose M here: the + module will be called tda7432. + +config VIDEO_TDA9840 + tristate "Philips TDA9840 audio processor" + depends on I2C + ---help--- + Support for tda9840 audio decoder chip found on some Zoran boards. + + To compile this driver as a module, choose M here: the + module will be called tda9840. + +config VIDEO_TEA6415C + tristate "Philips TEA6415C audio processor" + depends on I2C + ---help--- + Support for tea6415c audio decoder chip found on some bt8xx boards. + + To compile this driver as a module, choose M here: the + module will be called tea6415c. + +config VIDEO_TEA6420 + tristate "Philips TEA6420 audio processor" + depends on I2C + ---help--- + Support for tea6420 audio decoder chip found on some bt8xx boards. + + To compile this driver as a module, choose M here: the + module will be called tea6420. + +config VIDEO_MSP3400 + tristate "Micronas MSP34xx audio decoders" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Micronas MSP34xx series of audio decoders. + + To compile this driver as a module, choose M here: the + module will be called msp3400. + +config VIDEO_CS5345 + tristate "Cirrus Logic CS5345 audio ADC" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Cirrus Logic CS5345 24-bit, 192 kHz + stereo A/D converter. + + To compile this driver as a module, choose M here: the + module will be called cs5345. + +config VIDEO_CS53L32A + tristate "Cirrus Logic CS53L32A audio ADC" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Cirrus Logic CS53L32A low voltage + stereo A/D converter. + + To compile this driver as a module, choose M here: the + module will be called cs53l32a. + +config VIDEO_TLV320AIC23B + tristate "Texas Instruments TLV320AIC23B audio codec" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Texas Instruments TLV320AIC23B audio codec. + + To compile this driver as a module, choose M here: the + module will be called tlv320aic23b. + +config VIDEO_WM8775 + tristate "Wolfson Microelectronics WM8775 audio ADC with input mixer" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Wolfson Microelectronics WM8775 high + performance stereo A/D Converter with a 4 channel input mixer. + + To compile this driver as a module, choose M here: the + module will be called wm8775. + +config VIDEO_WM8739 + tristate "Wolfson Microelectronics WM8739 stereo audio ADC" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Wolfson Microelectronics WM8739 + stereo A/D Converter. + + To compile this driver as a module, choose M here: the + module will be called wm8739. + +config VIDEO_VP27SMPX + tristate "Panasonic VP27s internal MPX" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the internal MPX of the Panasonic VP27s tuner. + + To compile this driver as a module, choose M here: the + module will be called vp27smpx. + +comment "RDS decoders" + +config VIDEO_SAA6588 + tristate "SAA6588 Radio Chip RDS decoder support" + depends on VIDEO_V4L2 && I2C + + help + Support for this Radio Data System (RDS) decoder. This allows + seeing radio station identification transmitted using this + standard. + + To compile this driver as a module, choose M here: the + module will be called saa6588. + +comment "Video decoders" + +config VIDEO_ADV7180 + tristate "Analog Devices ADV7180 decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Analog Devices ADV7180 video decoder. + + To compile this driver as a module, choose M here: the + module will be called adv7180. + +config VIDEO_ADV7183 + tristate "Analog Devices ADV7183 decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + V4l2 subdevice driver for the Analog Devices + ADV7183 video decoder. + + To compile this driver as a module, choose M here: the + module will be called adv7183. + +config VIDEO_ADV7604 + tristate "Analog Devices ADV7604 decoder" + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API + ---help--- + Support for the Analog Devices ADV7604 video decoder. + + This is a Analog Devices Component/Graphics Digitizer + with 4:1 Multiplexed HDMI Receiver. + + To compile this driver as a module, choose M here: the + module will be called adv7604. + +config VIDEO_BT819 + tristate "BT819A VideoStream decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for BT819A video decoder. + + To compile this driver as a module, choose M here: the + module will be called bt819. + +config VIDEO_BT856 + tristate "BT856 VideoStream decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for BT856 video decoder. + + To compile this driver as a module, choose M here: the + module will be called bt856. + +config VIDEO_BT866 + tristate "BT866 VideoStream decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for BT866 video decoder. + + To compile this driver as a module, choose M here: the + module will be called bt866. + +config VIDEO_KS0127 + tristate "KS0127 video decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for KS0127 video decoder. + + This chip is used on AverMedia AVS6EYES Zoran-based MJPEG + cards. + + To compile this driver as a module, choose M here: the + module will be called ks0127. + +config VIDEO_SAA7110 + tristate "Philips SAA7110 video decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Philips SAA7110 video decoders. + + To compile this driver as a module, choose M here: the + module will be called saa7110. + +config VIDEO_SAA711X + tristate "Philips SAA7111/3/4/5 video decoders" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Philips SAA7111/3/4/5 video decoders. + + To compile this driver as a module, choose M here: the + module will be called saa7115. + +config VIDEO_SAA7191 + tristate "Philips SAA7191 video decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Philips SAA7191 video decoder. + + To compile this driver as a module, choose M here: the + module will be called saa7191. + +config VIDEO_TVP514X + tristate "Texas Instruments TVP514x video decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + This is a Video4Linux2 sensor-level driver for the TI TVP5146/47 + decoder. It is currently working with the TI OMAP3 camera + controller. + + To compile this driver as a module, choose M here: the + module will be called tvp514x. + +config VIDEO_TVP5150 + tristate "Texas Instruments TVP5150 video decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Texas Instruments TVP5150 video decoder. + + To compile this driver as a module, choose M here: the + module will be called tvp5150. + +config VIDEO_TVP7002 + tristate "Texas Instruments TVP7002 video decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Texas Instruments TVP7002 video decoder. + + To compile this driver as a module, choose M here: the + module will be called tvp7002. + +config VIDEO_VPX3220 + tristate "vpx3220a, vpx3216b & vpx3214c video decoders" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for VPX322x video decoders. + + To compile this driver as a module, choose M here: the + module will be called vpx3220. + +comment "Video and audio decoders" + +config VIDEO_SAA717X + tristate "Philips SAA7171/3/4 audio/video decoders" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Philips SAA7171/3/4 audio/video decoders. + + To compile this driver as a module, choose M here: the + module will be called saa717x. + +source "drivers/media/i2c/cx25840/Kconfig" + +comment "MPEG video encoders" + +config VIDEO_CX2341X + tristate "Conexant CX2341x MPEG encoders" + depends on VIDEO_V4L2 + ---help--- + Support for the Conexant CX23416 MPEG encoders + and CX23415 MPEG encoder/decoders. + + This module currently supports the encoding functions only. + + To compile this driver as a module, choose M here: the + module will be called cx2341x. + +comment "Video encoders" + +config VIDEO_SAA7127 + tristate "Philips SAA7127/9 digital video encoders" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Philips SAA7127/9 digital video encoders. + + To compile this driver as a module, choose M here: the + module will be called saa7127. + +config VIDEO_SAA7185 + tristate "Philips SAA7185 video encoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Philips SAA7185 video encoder. + + To compile this driver as a module, choose M here: the + module will be called saa7185. + +config VIDEO_ADV7170 + tristate "Analog Devices ADV7170 video encoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Analog Devices ADV7170 video encoder driver + + To compile this driver as a module, choose M here: the + module will be called adv7170. + +config VIDEO_ADV7175 + tristate "Analog Devices ADV7175 video encoder" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Analog Devices ADV7175 video encoder driver + + To compile this driver as a module, choose M here: the + module will be called adv7175. + +config VIDEO_ADV7343 + tristate "ADV7343 video encoder" + depends on I2C + help + Support for Analog Devices I2C bus based ADV7343 encoder. + + To compile this driver as a module, choose M here: the + module will be called adv7343. + +config VIDEO_ADV7393 + tristate "ADV7393 video encoder" + depends on I2C + help + Support for Analog Devices I2C bus based ADV7393 encoder. + + To compile this driver as a module, choose M here: the + module will be called adv7393. + +config VIDEO_AD9389B + tristate "Analog Devices AD9389B encoder" + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API + ---help--- + Support for the Analog Devices AD9389B video encoder. + + This is a Analog Devices HDMI transmitter. + + To compile this driver as a module, choose M here: the + module will be called ad9389b. + +config VIDEO_AK881X + tristate "AK8813/AK8814 video encoders" + depends on I2C + help + Video output driver for AKM AK8813 and AK8814 TV encoders + +comment "Camera sensor devices" + +config VIDEO_APTINA_PLL + tristate + +config VIDEO_SMIAPP_PLL + tristate + +config VIDEO_OV7670 + tristate "OmniVision OV7670 sensor support" + depends on I2C && VIDEO_V4L2 + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This is a Video4Linux2 sensor-level driver for the OmniVision + OV7670 VGA camera. It currently only works with the M88ALP01 + controller. + +config VIDEO_VS6624 + tristate "ST VS6624 sensor support" + depends on VIDEO_V4L2 && I2C + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This is a Video4Linux2 sensor-level driver for the ST VS6624 + camera. + + To compile this driver as a module, choose M here: the + module will be called vs6624. + +config VIDEO_MT9M032 + tristate "MT9M032 camera sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CAMERA_SUPPORT + select VIDEO_APTINA_PLL + ---help--- + This driver supports MT9M032 camera sensors from Aptina, monochrome + models only. + +config VIDEO_MT9P031 + tristate "Aptina MT9P031 support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CAMERA_SUPPORT + select VIDEO_APTINA_PLL + ---help--- + This is a Video4Linux2 sensor-level driver for the Aptina + (Micron) mt9p031 5 Mpixel camera. + +config VIDEO_MT9T001 + tristate "Aptina MT9T001 support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This is a Video4Linux2 sensor-level driver for the Aptina + (Micron) mt0t001 3 Mpixel camera. + +config VIDEO_MT9V011 + tristate "Micron mt9v011 sensor support" + depends on I2C && VIDEO_V4L2 + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This is a Video4Linux2 sensor-level driver for the Micron + mt0v011 1.3 Mpixel camera. It currently only works with the + em28xx driver. + +config VIDEO_MT9V032 + tristate "Micron MT9V032 sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This is a Video4Linux2 sensor-level driver for the Micron + MT9V032 752x480 CMOS sensor. + +config VIDEO_TCM825X + tristate "TCM825x camera sensor support" + depends on I2C && VIDEO_V4L2 + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This is a driver for the Toshiba TCM825x VGA camera sensor. + It is used for example in Nokia N800. + +config VIDEO_SR030PC30 + tristate "Siliconfile SR030PC30 sensor support" + depends on I2C && VIDEO_V4L2 + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This driver supports SR030PC30 VGA camera from Siliconfile + +config VIDEO_NOON010PC30 + tristate "Siliconfile NOON010PC30 sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This driver supports NOON010PC30 CIF camera from Siliconfile + +source "drivers/media/i2c/m5mols/Kconfig" + +config VIDEO_S5K6AA + tristate "Samsung S5K6AAFX sensor support" + depends on MEDIA_CAMERA_SUPPORT + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + ---help--- + This is a V4L2 sensor-level driver for Samsung S5K6AA(FX) 1.3M + camera sensor with an embedded SoC image signal processor. + +config VIDEO_S5K4ECGX + tristate "Samsung S5K4ECGX sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + ---help--- + This is a V4L2 sensor-level driver for Samsung S5K4ECGX 5M + camera sensor with an embedded SoC image signal processor. + +source "drivers/media/i2c/smiapp/Kconfig" + +comment "Flash devices" + +config VIDEO_ADP1653 + tristate "ADP1653 flash support" + depends on I2C && VIDEO_V4L2 && MEDIA_CONTROLLER + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This is a driver for the ADP1653 flash controller. It is used for + example in Nokia N900. + +config VIDEO_AS3645A + tristate "AS3645A flash driver support" + depends on I2C && VIDEO_V4L2 && MEDIA_CONTROLLER + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This is a driver for the AS3645A and LM3555 flash controllers. It has + build in control for flash, torch and indicator LEDs. + +comment "Video improvement chips" + +config VIDEO_UPD64031A + tristate "NEC Electronics uPD64031A Ghost Reduction" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the NEC Electronics uPD64031A Ghost Reduction + video chip. It is most often found in NTSC TV cards made for + Japan and is used to reduce the 'ghosting' effect that can + be present in analog TV broadcasts. + + To compile this driver as a module, choose M here: the + module will be called upd64031a. + +config VIDEO_UPD64083 + tristate "NEC Electronics uPD64083 3-Dimensional Y/C separation" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the NEC Electronics uPD64083 3-Dimensional Y/C + separation video chip. It is used to improve the quality of + the colors of a composite signal. + + To compile this driver as a module, choose M here: the + module will be called upd64083. + +comment "Miscelaneous helper chips" + +config VIDEO_THS7303 + tristate "THS7303 Video Amplifier" + depends on I2C + help + Support for TI THS7303 video amplifier + + To compile this driver as a module, choose M here: the + module will be called ths7303. + +config VIDEO_M52790 + tristate "Mitsubishi M52790 A/V switch" + depends on VIDEO_V4L2 && I2C + ---help--- + Support for the Mitsubishi M52790 A/V switch. + + To compile this driver as a module, choose M here: the + module will be called m52790. +endmenu + +menu "Sensors used on soc_camera driver" + +if SOC_CAMERA + source "drivers/media/i2c/soc_camera/Kconfig" +endif + +endmenu + +endif diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile new file mode 100644 index 000000000000..b1d62dfd49b8 --- /dev/null +++ b/drivers/media/i2c/Makefile @@ -0,0 +1,67 @@ +msp3400-objs := msp3400-driver.o msp3400-kthreads.o +obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o + +obj-$(CONFIG_VIDEO_SMIAPP) += smiapp/ +obj-$(CONFIG_VIDEO_CX25840) += cx25840/ +obj-$(CONFIG_VIDEO_M5MOLS) += m5mols/ +obj-y += soc_camera/ + +obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o +obj-$(CONFIG_VIDEO_TVAUDIO) += tvaudio.o +obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o +obj-$(CONFIG_VIDEO_SAA6588) += saa6588.o +obj-$(CONFIG_VIDEO_TDA9840) += tda9840.o +obj-$(CONFIG_VIDEO_TEA6415C) += tea6415c.o +obj-$(CONFIG_VIDEO_TEA6420) += tea6420.o +obj-$(CONFIG_VIDEO_SAA7110) += saa7110.o +obj-$(CONFIG_VIDEO_SAA711X) += saa7115.o +obj-$(CONFIG_VIDEO_SAA717X) += saa717x.o +obj-$(CONFIG_VIDEO_SAA7127) += saa7127.o +obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o +obj-$(CONFIG_VIDEO_SAA7191) += saa7191.o +obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o +obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o +obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o +obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o +obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o +obj-$(CONFIG_VIDEO_ADV7393) += adv7393.o +obj-$(CONFIG_VIDEO_ADV7604) += adv7604.o +obj-$(CONFIG_VIDEO_AD9389B) += ad9389b.o +obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o +obj-$(CONFIG_VIDEO_VS6624) += vs6624.o +obj-$(CONFIG_VIDEO_BT819) += bt819.o +obj-$(CONFIG_VIDEO_BT856) += bt856.o +obj-$(CONFIG_VIDEO_BT866) += bt866.o +obj-$(CONFIG_VIDEO_KS0127) += ks0127.o +obj-$(CONFIG_VIDEO_THS7303) += ths7303.o +obj-$(CONFIG_VIDEO_TVP5150) += tvp5150.o +obj-$(CONFIG_VIDEO_TVP514X) += tvp514x.o +obj-$(CONFIG_VIDEO_TVP7002) += tvp7002.o +obj-$(CONFIG_VIDEO_CS5345) += cs5345.o +obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o +obj-$(CONFIG_VIDEO_M52790) += m52790.o +obj-$(CONFIG_VIDEO_TLV320AIC23B) += tlv320aic23b.o +obj-$(CONFIG_VIDEO_WM8775) += wm8775.o +obj-$(CONFIG_VIDEO_WM8739) += wm8739.o +obj-$(CONFIG_VIDEO_VP27SMPX) += vp27smpx.o +obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o +obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o +obj-$(CONFIG_VIDEO_OV7670) += ov7670.o +obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o +obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o +obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o +obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o +obj-$(CONFIG_VIDEO_MT9T001) += mt9t001.o +obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o +obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o +obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o +obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o +obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o +obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o +obj-$(CONFIG_VIDEO_ADP1653) += adp1653.o +obj-$(CONFIG_VIDEO_AS3645A) += as3645a.o +obj-$(CONFIG_VIDEO_SMIAPP_PLL) += smiapp-pll.o +obj-$(CONFIG_VIDEO_BTCX) += btcx-risc.o +obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o +obj-$(CONFIG_VIDEO_AK881X) += ak881x.o +obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o diff --git a/drivers/media/i2c/ad9389b.c b/drivers/media/i2c/ad9389b.c new file mode 100644 index 000000000000..c2886b6a727d --- /dev/null +++ b/drivers/media/i2c/ad9389b.c @@ -0,0 +1,1328 @@ +/* + * Analog Devices AD9389B/AD9889B video encoder driver + * + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Analog Devices, Programming Guide, AD9889B/AD9389B, + * HDMI Transitter, Rev. A, October 2010 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices AD9389B/AD9889B video encoder driver"); +MODULE_AUTHOR("Hans Verkuil "); +MODULE_AUTHOR("Martin Bugge "); +MODULE_LICENSE("GPL"); + +#define MASK_AD9389B_EDID_RDY_INT 0x04 +#define MASK_AD9389B_MSEN_INT 0x40 +#define MASK_AD9389B_HPD_INT 0x80 + +#define MASK_AD9389B_HPD_DETECT 0x40 +#define MASK_AD9389B_MSEN_DETECT 0x20 +#define MASK_AD9389B_EDID_RDY 0x10 + +#define EDID_MAX_RETRIES (8) +#define EDID_DELAY 250 +#define EDID_MAX_SEGM 8 + +/* +********************************************************************** +* +* Arrays with configuration parameters for the AD9389B +* +********************************************************************** +*/ + +struct i2c_reg_value { + u8 reg; + u8 value; +}; + +struct ad9389b_state_edid { + /* total number of blocks */ + u32 blocks; + /* Number of segments read */ + u32 segments; + u8 data[EDID_MAX_SEGM * 256]; + /* Number of EDID read retries left */ + unsigned read_retries; +}; + +struct ad9389b_state { + struct ad9389b_platform_data pdata; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + int chip_revision; + /* Is the ad9389b powered on? */ + bool power_on; + /* Did we receive hotplug and rx-sense signals? */ + bool have_monitor; + /* timings from s_dv_timings */ + struct v4l2_dv_timings dv_timings; + /* controls */ + struct v4l2_ctrl *hdmi_mode_ctrl; + struct v4l2_ctrl *hotplug_ctrl; + struct v4l2_ctrl *rx_sense_ctrl; + struct v4l2_ctrl *have_edid0_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; + struct i2c_client *edid_i2c_client; + struct ad9389b_state_edid edid; + /* Running counter of the number of detected EDIDs (for debugging) */ + unsigned edid_detect_counter; + struct workqueue_struct *work_queue; + struct delayed_work edid_handler; /* work entry */ +}; + +static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd); +static bool ad9389b_check_edid_status(struct v4l2_subdev *sd); +static void ad9389b_setup(struct v4l2_subdev *sd); +static int ad9389b_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq); +static int ad9389b_s_clock_freq(struct v4l2_subdev *sd, u32 freq); + +static inline struct ad9389b_state *get_ad9389b_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ad9389b_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ad9389b_state, hdl)->sd; +} + +/* ------------------------ I2C ----------------------------------------------- */ + +static int ad9389b_rd(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int ad9389b_wr(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "I2C Write Problem\n"); + return ret; +} + +/* To set specific bits in the register, a clear-mask is given (to be AND-ed), + and then the value-mask (to be OR-ed). */ +static inline void ad9389b_wr_and_or(struct v4l2_subdev *sd, u8 reg, + u8 clr_mask, u8 val_mask) +{ + ad9389b_wr(sd, reg, (ad9389b_rd(sd, reg) & clr_mask) | val_mask); +} + +static void ad9389b_edid_rd(struct v4l2_subdev *sd, u16 len, u8 *buf) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + int i; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + for (i = 0; i < len; i++) + buf[i] = i2c_smbus_read_byte_data(state->edid_i2c_client, i); +} + +static inline bool ad9389b_have_hotplug(struct v4l2_subdev *sd) +{ + return ad9389b_rd(sd, 0x42) & MASK_AD9389B_HPD_DETECT; +} + +static inline bool ad9389b_have_rx_sense(struct v4l2_subdev *sd) +{ + return ad9389b_rd(sd, 0x42) & MASK_AD9389B_MSEN_DETECT; +} + +static void ad9389b_csc_conversion_mode(struct v4l2_subdev *sd, u8 mode) +{ + ad9389b_wr_and_or(sd, 0x17, 0xe7, (mode & 0x3)<<3); + ad9389b_wr_and_or(sd, 0x18, 0x9f, (mode & 0x3)<<5); +} + +static void ad9389b_csc_coeff(struct v4l2_subdev *sd, + u16 A1, u16 A2, u16 A3, u16 A4, + u16 B1, u16 B2, u16 B3, u16 B4, + u16 C1, u16 C2, u16 C3, u16 C4) +{ + /* A */ + ad9389b_wr_and_or(sd, 0x18, 0xe0, A1>>8); + ad9389b_wr(sd, 0x19, A1); + ad9389b_wr_and_or(sd, 0x1A, 0xe0, A2>>8); + ad9389b_wr(sd, 0x1B, A2); + ad9389b_wr_and_or(sd, 0x1c, 0xe0, A3>>8); + ad9389b_wr(sd, 0x1d, A3); + ad9389b_wr_and_or(sd, 0x1e, 0xe0, A4>>8); + ad9389b_wr(sd, 0x1f, A4); + + /* B */ + ad9389b_wr_and_or(sd, 0x20, 0xe0, B1>>8); + ad9389b_wr(sd, 0x21, B1); + ad9389b_wr_and_or(sd, 0x22, 0xe0, B2>>8); + ad9389b_wr(sd, 0x23, B2); + ad9389b_wr_and_or(sd, 0x24, 0xe0, B3>>8); + ad9389b_wr(sd, 0x25, B3); + ad9389b_wr_and_or(sd, 0x26, 0xe0, B4>>8); + ad9389b_wr(sd, 0x27, B4); + + /* C */ + ad9389b_wr_and_or(sd, 0x28, 0xe0, C1>>8); + ad9389b_wr(sd, 0x29, C1); + ad9389b_wr_and_or(sd, 0x2A, 0xe0, C2>>8); + ad9389b_wr(sd, 0x2B, C2); + ad9389b_wr_and_or(sd, 0x2C, 0xe0, C3>>8); + ad9389b_wr(sd, 0x2D, C3); + ad9389b_wr_and_or(sd, 0x2E, 0xe0, C4>>8); + ad9389b_wr(sd, 0x2F, C4); +} + +static void ad9389b_csc_rgb_full2limit(struct v4l2_subdev *sd, bool enable) +{ + if (enable) { + u8 csc_mode = 0; + + ad9389b_csc_conversion_mode(sd, csc_mode); + ad9389b_csc_coeff(sd, + 4096-564, 0, 0, 256, + 0, 4096-564, 0, 256, + 0, 0, 4096-564, 256); + /* enable CSC */ + ad9389b_wr_and_or(sd, 0x3b, 0xfe, 0x1); + /* AVI infoframe: Limited range RGB (16-235) */ + ad9389b_wr_and_or(sd, 0xcd, 0xf9, 0x02); + } else { + /* disable CSC */ + ad9389b_wr_and_or(sd, 0x3b, 0xfe, 0x0); + /* AVI infoframe: Full range RGB (0-255) */ + ad9389b_wr_and_or(sd, 0xcd, 0xf9, 0x04); + } +} + +static void ad9389b_set_IT_content_AVI_InfoFrame(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + if (state->dv_timings.bt.standards & V4L2_DV_BT_STD_CEA861) { + /* CEA format, not IT */ + ad9389b_wr_and_or(sd, 0xcd, 0xbf, 0x00); + } else { + /* IT format */ + ad9389b_wr_and_or(sd, 0xcd, 0xbf, 0x40); + } +} + +static int ad9389b_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2_ctrl *ctrl) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + switch (ctrl->val) { + case V4L2_DV_RGB_RANGE_AUTO: + /* automatic */ + if (state->dv_timings.bt.standards & V4L2_DV_BT_STD_CEA861) { + /* cea format, RGB limited range (16-235) */ + ad9389b_csc_rgb_full2limit(sd, true); + } else { + /* not cea format, RGB full range (0-255) */ + ad9389b_csc_rgb_full2limit(sd, false); + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + /* RGB limited range (16-235) */ + ad9389b_csc_rgb_full2limit(sd, true); + break; + case V4L2_DV_RGB_RANGE_FULL: + /* RGB full range (0-255) */ + ad9389b_csc_rgb_full2limit(sd, false); + break; + default: + return -EINVAL; + } + return 0; +} + +static void ad9389b_set_manual_pll_gear(struct v4l2_subdev *sd, u32 pixelclock) +{ + u8 gear; + + /* Workaround for TMDS PLL problem + * The TMDS PLL in AD9389b change gear when the chip is heated above a + * certain temperature. The output is disabled when the PLL change gear + * so the monitor has to lock on the signal again. A workaround for + * this is to use the manual PLL gears. This is a solution from Analog + * Devices that is not documented in the datasheets. + * 0x98 [7] = enable manual gearing. 0x98 [6:4] = gear + * + * The pixel frequency ranges are based on readout of the gear the + * automatic gearing selects for different pixel clocks + * (read from 0x9e [3:1]). + */ + + if (pixelclock > 140000000) + gear = 0xc0; /* 4th gear */ + else if (pixelclock > 117000000) + gear = 0xb0; /* 3rd gear */ + else if (pixelclock > 87000000) + gear = 0xa0; /* 2nd gear */ + else if (pixelclock > 60000000) + gear = 0x90; /* 1st gear */ + else + gear = 0x80; /* 0th gear */ + + ad9389b_wr_and_or(sd, 0x98, 0x0f, gear); +} + +/* ------------------------------ CTRL OPS ------------------------------ */ + +static int ad9389b_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct ad9389b_state *state = get_ad9389b_state(sd); + + v4l2_dbg(1, debug, sd, + "%s: ctrl id: %d, ctrl->val %d\n", __func__, ctrl->id, ctrl->val); + + if (state->hdmi_mode_ctrl == ctrl) { + /* Set HDMI or DVI-D */ + ad9389b_wr_and_or(sd, 0xaf, 0xfd, + ctrl->val == V4L2_DV_TX_MODE_HDMI ? 0x02 : 0x00); + return 0; + } + if (state->rgb_quantization_range_ctrl == ctrl) + return ad9389b_set_rgb_quantization_mode(sd, ctrl); + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ad9389b_ctrl_ops = { + .s_ctrl = ad9389b_s_ctrl, +}; + +/* ---------------------------- CORE OPS ------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ad9389b_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + reg->val = ad9389b_rd(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int ad9389b_s_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ad9389b_wr(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static int ad9389b_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_AD9389B, 0); +} + +static int ad9389b_log_status(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + struct ad9389b_state_edid *edid = &state->edid; + + static const char * const states[] = { + "in reset", + "reading EDID", + "idle", + "initializing HDCP", + "HDCP enabled", + "initializing HDCP repeater", + "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" + }; + static const char * const errors[] = { + "no error", + "bad receiver BKSV", + "Ri mismatch", + "Pj mismatch", + "i2c error", + "timed out", + "max repeater cascade exceeded", + "hash check failed", + "too many devices", + "9", "A", "B", "C", "D", "E", "F" + }; + + u8 manual_gear; + + v4l2_info(sd, "chip revision %d\n", state->chip_revision); + v4l2_info(sd, "power %s\n", state->power_on ? "on" : "off"); + v4l2_info(sd, "%s hotplug, %s Rx Sense, %s EDID (%d block(s))\n", + (ad9389b_rd(sd, 0x42) & MASK_AD9389B_HPD_DETECT) ? + "detected" : "no", + (ad9389b_rd(sd, 0x42) & MASK_AD9389B_MSEN_DETECT) ? + "detected" : "no", + edid->segments ? "found" : "no", edid->blocks); + if (state->have_monitor) { + v4l2_info(sd, "%s output %s\n", + (ad9389b_rd(sd, 0xaf) & 0x02) ? + "HDMI" : "DVI-D", + (ad9389b_rd(sd, 0xa1) & 0x3c) ? + "disabled" : "enabled"); + } + v4l2_info(sd, "ad9389b: %s\n", (ad9389b_rd(sd, 0xb8) & 0x40) ? + "encrypted" : "no encryption"); + v4l2_info(sd, "state: %s, error: %s, detect count: %u, msk/irq: %02x/%02x\n", + states[ad9389b_rd(sd, 0xc8) & 0xf], + errors[ad9389b_rd(sd, 0xc8) >> 4], + state->edid_detect_counter, + ad9389b_rd(sd, 0x94), ad9389b_rd(sd, 0x96)); + manual_gear = ad9389b_rd(sd, 0x98) & 0x80; + v4l2_info(sd, "ad9389b: RGB quantization: %s range\n", + ad9389b_rd(sd, 0x3b) & 0x01 ? "limited" : "full"); + v4l2_info(sd, "ad9389b: %s gear %d\n", + manual_gear ? "manual" : "automatic", + manual_gear ? ((ad9389b_rd(sd, 0x98) & 0x70) >> 4) : + ((ad9389b_rd(sd, 0x9e) & 0x0e) >> 1)); + if (state->have_monitor) { + if (ad9389b_rd(sd, 0xaf) & 0x02) { + /* HDMI only */ + u8 manual_cts = ad9389b_rd(sd, 0x0a) & 0x80; + u32 N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 | + ad9389b_rd(sd, 0x02) << 8 | + ad9389b_rd(sd, 0x03); + u8 vic_detect = ad9389b_rd(sd, 0x3e) >> 2; + u8 vic_sent = ad9389b_rd(sd, 0x3d) & 0x3f; + u32 CTS; + + if (manual_cts) + CTS = (ad9389b_rd(sd, 0x07) & 0xf) << 16 | + ad9389b_rd(sd, 0x08) << 8 | + ad9389b_rd(sd, 0x09); + else + CTS = (ad9389b_rd(sd, 0x04) & 0xf) << 16 | + ad9389b_rd(sd, 0x05) << 8 | + ad9389b_rd(sd, 0x06); + N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 | + ad9389b_rd(sd, 0x02) << 8 | + ad9389b_rd(sd, 0x03); + + v4l2_info(sd, "ad9389b: CTS %s mode: N %d, CTS %d\n", + manual_cts ? "manual" : "automatic", N, CTS); + + v4l2_info(sd, "ad9389b: VIC: detected %d, sent %d\n", + vic_detect, vic_sent); + } + } + if (state->dv_timings.type == V4L2_DV_BT_656_1120) { + struct v4l2_bt_timings *bt = bt = &state->dv_timings.bt; + u32 frame_width = bt->width + bt->hfrontporch + + bt->hsync + bt->hbackporch; + u32 frame_height = bt->height + bt->vfrontporch + + bt->vsync + bt->vbackporch; + u32 frame_size = frame_width * frame_height; + + v4l2_info(sd, "timings: %ux%u%s%u (%ux%u). Pix freq. = %u Hz. Polarities = 0x%x\n", + bt->width, bt->height, bt->interlaced ? "i" : "p", + frame_size > 0 ? (unsigned)bt->pixelclock / frame_size : 0, + frame_width, frame_height, + (unsigned)bt->pixelclock, bt->polarities); + } else { + v4l2_info(sd, "no timings set\n"); + } + return 0; +} + +/* Power up/down ad9389b */ +static int ad9389b_s_power(struct v4l2_subdev *sd, int on) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + struct ad9389b_platform_data *pdata = &state->pdata; + const int retries = 20; + int i; + + v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off"); + + state->power_on = on; + + if (!on) { + /* Power down */ + ad9389b_wr_and_or(sd, 0x41, 0xbf, 0x40); + return true; + } + + /* Power up */ + /* The ad9389b does not always come up immediately. + Retry multiple times. */ + for (i = 0; i < retries; i++) { + ad9389b_wr_and_or(sd, 0x41, 0xbf, 0x0); + if ((ad9389b_rd(sd, 0x41) & 0x40) == 0) + break; + ad9389b_wr_and_or(sd, 0x41, 0xbf, 0x40); + msleep(10); + } + if (i == retries) { + v4l2_dbg(1, debug, sd, "failed to powerup the ad9389b\n"); + ad9389b_s_power(sd, 0); + return false; + } + if (i > 1) + v4l2_dbg(1, debug, sd, + "needed %d retries to powerup the ad9389b\n", i); + + /* Select chip: AD9389B */ + ad9389b_wr_and_or(sd, 0xba, 0xef, 0x10); + + /* Reserved registers that must be set according to REF_01 p. 11*/ + ad9389b_wr_and_or(sd, 0x98, 0xf0, 0x07); + ad9389b_wr(sd, 0x9c, 0x38); + ad9389b_wr_and_or(sd, 0x9d, 0xfc, 0x01); + + /* Differential output drive strength */ + if (pdata->diff_data_drive_strength > 0) + ad9389b_wr(sd, 0xa2, pdata->diff_data_drive_strength); + else + ad9389b_wr(sd, 0xa2, 0x87); + + if (pdata->diff_clk_drive_strength > 0) + ad9389b_wr(sd, 0xa3, pdata->diff_clk_drive_strength); + else + ad9389b_wr(sd, 0xa3, 0x87); + + ad9389b_wr(sd, 0x0a, 0x01); + ad9389b_wr(sd, 0xbb, 0xff); + + /* Set number of attempts to read the EDID */ + ad9389b_wr(sd, 0xc9, 0xf); + return true; +} + +/* Enable interrupts */ +static void ad9389b_set_isr(struct v4l2_subdev *sd, bool enable) +{ + u8 irqs = MASK_AD9389B_HPD_INT | MASK_AD9389B_MSEN_INT; + u8 irqs_rd; + int retries = 100; + + /* The datasheet says that the EDID ready interrupt should be + disabled if there is no hotplug. */ + if (!enable) + irqs = 0; + else if (ad9389b_have_hotplug(sd)) + irqs |= MASK_AD9389B_EDID_RDY_INT; + + /* + * This i2c write can fail (approx. 1 in 1000 writes). But it + * is essential that this register is correct, so retry it + * multiple times. + * + * Note that the i2c write does not report an error, but the readback + * clearly shows the wrong value. + */ + do { + ad9389b_wr(sd, 0x94, irqs); + irqs_rd = ad9389b_rd(sd, 0x94); + } while (retries-- && irqs_rd != irqs); + + if (irqs_rd != irqs) + v4l2_err(sd, "Could not set interrupts: hw failure?\n"); +} + +/* Interrupt handler */ +static int ad9389b_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + u8 irq_status; + + /* disable interrupts to prevent a race condition */ + ad9389b_set_isr(sd, false); + irq_status = ad9389b_rd(sd, 0x96); + /* clear detected interrupts */ + ad9389b_wr(sd, 0x96, irq_status); + + if (irq_status & (MASK_AD9389B_HPD_INT | MASK_AD9389B_MSEN_INT)) + ad9389b_check_monitor_present_status(sd); + if (irq_status & MASK_AD9389B_EDID_RDY_INT) + ad9389b_check_edid_status(sd); + + /* enable interrupts */ + ad9389b_set_isr(sd, true); + *handled = true; + return 0; +} + +static const struct v4l2_subdev_core_ops ad9389b_core_ops = { + .log_status = ad9389b_log_status, + .g_chip_ident = ad9389b_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ad9389b_g_register, + .s_register = ad9389b_s_register, +#endif + .s_power = ad9389b_s_power, + .interrupt_service_routine = ad9389b_isr, +}; + +/* ------------------------------ PAD OPS ------------------------------ */ + +static int ad9389b_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + if (edid->pad != 0) + return -EINVAL; + if (edid->blocks == 0 || edid->blocks > 256) + return -EINVAL; + if (!edid->edid) + return -EINVAL; + if (!state->edid.segments) { + v4l2_dbg(1, debug, sd, "EDID segment 0 not found\n"); + return -ENODATA; + } + if (edid->start_block >= state->edid.segments * 2) + return -E2BIG; + if (edid->blocks + edid->start_block >= state->edid.segments * 2) + edid->blocks = state->edid.segments * 2 - edid->start_block; + memcpy(edid->edid, &state->edid.data[edid->start_block * 128], + 128 * edid->blocks); + return 0; +} + +static const struct v4l2_subdev_pad_ops ad9389b_pad_ops = { + .get_edid = ad9389b_get_edid, +}; + +/* ------------------------------ VIDEO OPS ------------------------------ */ + +/* Enable/disable ad9389b output */ +static int ad9389b_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + + ad9389b_wr_and_or(sd, 0xa1, ~0x3c, (enable ? 0 : 0x3c)); + if (enable) { + ad9389b_check_monitor_present_status(sd); + } else { + ad9389b_s_power(sd, 0); + state->have_monitor = false; + } + return 0; +} + +static const struct v4l2_dv_timings ad9389b_timings[] = { + V4L2_DV_BT_CEA_720X480P59_94, + V4L2_DV_BT_CEA_720X576P50, + V4L2_DV_BT_CEA_1280X720P24, + V4L2_DV_BT_CEA_1280X720P25, + V4L2_DV_BT_CEA_1280X720P30, + V4L2_DV_BT_CEA_1280X720P50, + V4L2_DV_BT_CEA_1280X720P60, + V4L2_DV_BT_CEA_1920X1080P24, + V4L2_DV_BT_CEA_1920X1080P25, + V4L2_DV_BT_CEA_1920X1080P30, + V4L2_DV_BT_CEA_1920X1080P50, + V4L2_DV_BT_CEA_1920X1080P60, + + V4L2_DV_BT_DMT_640X350P85, + V4L2_DV_BT_DMT_640X400P85, + V4L2_DV_BT_DMT_720X400P85, + V4L2_DV_BT_DMT_640X480P60, + V4L2_DV_BT_DMT_640X480P72, + V4L2_DV_BT_DMT_640X480P75, + V4L2_DV_BT_DMT_640X480P85, + V4L2_DV_BT_DMT_800X600P56, + V4L2_DV_BT_DMT_800X600P60, + V4L2_DV_BT_DMT_800X600P72, + V4L2_DV_BT_DMT_800X600P75, + V4L2_DV_BT_DMT_800X600P85, + V4L2_DV_BT_DMT_848X480P60, + V4L2_DV_BT_DMT_1024X768P60, + V4L2_DV_BT_DMT_1024X768P70, + V4L2_DV_BT_DMT_1024X768P75, + V4L2_DV_BT_DMT_1024X768P85, + V4L2_DV_BT_DMT_1152X864P75, + V4L2_DV_BT_DMT_1280X768P60_RB, + V4L2_DV_BT_DMT_1280X768P60, + V4L2_DV_BT_DMT_1280X768P75, + V4L2_DV_BT_DMT_1280X768P85, + V4L2_DV_BT_DMT_1280X800P60_RB, + V4L2_DV_BT_DMT_1280X800P60, + V4L2_DV_BT_DMT_1280X800P75, + V4L2_DV_BT_DMT_1280X800P85, + V4L2_DV_BT_DMT_1280X960P60, + V4L2_DV_BT_DMT_1280X960P85, + V4L2_DV_BT_DMT_1280X1024P60, + V4L2_DV_BT_DMT_1280X1024P75, + V4L2_DV_BT_DMT_1280X1024P85, + V4L2_DV_BT_DMT_1360X768P60, + V4L2_DV_BT_DMT_1400X1050P60_RB, + V4L2_DV_BT_DMT_1400X1050P60, + V4L2_DV_BT_DMT_1400X1050P75, + V4L2_DV_BT_DMT_1400X1050P85, + V4L2_DV_BT_DMT_1440X900P60_RB, + V4L2_DV_BT_DMT_1440X900P60, + V4L2_DV_BT_DMT_1600X1200P60, + V4L2_DV_BT_DMT_1680X1050P60_RB, + V4L2_DV_BT_DMT_1680X1050P60, + V4L2_DV_BT_DMT_1792X1344P60, + V4L2_DV_BT_DMT_1856X1392P60, + V4L2_DV_BT_DMT_1920X1200P60_RB, + V4L2_DV_BT_DMT_1366X768P60, + V4L2_DV_BT_DMT_1920X1080P60, + {}, +}; + +static int ad9389b_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + int i; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + /* quick sanity check */ + if (timings->type != V4L2_DV_BT_656_1120) + return -EINVAL; + + if (timings->bt.interlaced) + return -EINVAL; + if (timings->bt.pixelclock < 27000000 || + timings->bt.pixelclock > 170000000) + return -EINVAL; + + /* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is listed in ad9389b_timings[] */ + for (i = 0; ad9389b_timings[i].bt.width; i++) { + if (v4l_match_dv_timings(timings, &ad9389b_timings[i], 0)) { + *timings = ad9389b_timings[i]; + break; + } + } + + timings->bt.flags &= ~V4L2_DV_FL_REDUCED_FPS; + + /* save timings */ + state->dv_timings = *timings; + + /* update quantization range based on new dv_timings */ + ad9389b_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl); + + /* update PLL gear based on new dv_timings */ + if (state->pdata.tmds_pll_gear == AD9389B_TMDS_PLL_GEAR_SEMI_AUTOMATIC) + ad9389b_set_manual_pll_gear(sd, (u32)timings->bt.pixelclock); + + /* update AVI infoframe */ + ad9389b_set_IT_content_AVI_InfoFrame(sd); + + return 0; +} + +static int ad9389b_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (!timings) + return -EINVAL; + + *timings = state->dv_timings; + + return 0; +} + +static int ad9389b_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->index >= ARRAY_SIZE(ad9389b_timings)) + return -EINVAL; + + memset(timings->reserved, 0, sizeof(timings->reserved)); + timings->timings = ad9389b_timings[timings->index]; + return 0; +} + +static int ad9389b_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + cap->type = V4L2_DV_BT_656_1120; + cap->bt.max_width = 1920; + cap->bt.max_height = 1200; + cap->bt.min_pixelclock = 27000000; + cap->bt.max_pixelclock = 170000000; + cap->bt.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT; + cap->bt.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM; + return 0; +} + +static const struct v4l2_subdev_video_ops ad9389b_video_ops = { + .s_stream = ad9389b_s_stream, + .s_dv_timings = ad9389b_s_dv_timings, + .g_dv_timings = ad9389b_g_dv_timings, + .enum_dv_timings = ad9389b_enum_dv_timings, + .dv_timings_cap = ad9389b_dv_timings_cap, +}; + +static int ad9389b_s_audio_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + + if (enable) + ad9389b_wr_and_or(sd, 0x45, 0x3f, 0x80); + else + ad9389b_wr_and_or(sd, 0x45, 0x3f, 0x40); + + return 0; +} + +static int ad9389b_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 N; + + switch (freq) { + case 32000: N = 4096; break; + case 44100: N = 6272; break; + case 48000: N = 6144; break; + case 88200: N = 12544; break; + case 96000: N = 12288; break; + case 176400: N = 25088; break; + case 192000: N = 24576; break; + default: + return -EINVAL; + } + + /* Set N (used with CTS to regenerate the audio clock) */ + ad9389b_wr(sd, 0x01, (N >> 16) & 0xf); + ad9389b_wr(sd, 0x02, (N >> 8) & 0xff); + ad9389b_wr(sd, 0x03, N & 0xff); + + return 0; +} + +static int ad9389b_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 i2s_sf; + + switch (freq) { + case 32000: i2s_sf = 0x30; break; + case 44100: i2s_sf = 0x00; break; + case 48000: i2s_sf = 0x20; break; + case 88200: i2s_sf = 0x80; break; + case 96000: i2s_sf = 0xa0; break; + case 176400: i2s_sf = 0xc0; break; + case 192000: i2s_sf = 0xe0; break; + default: + return -EINVAL; + } + + /* Set sampling frequency for I2S audio to 48 kHz */ + ad9389b_wr_and_or(sd, 0x15, 0xf, i2s_sf); + + return 0; +} + +static int ad9389b_s_routing(struct v4l2_subdev *sd, u32 input, u32 output, u32 config) +{ + /* TODO based on input/output/config */ + /* TODO See datasheet "Programmers guide" p. 39-40 */ + + /* Only 2 channels in use for application */ + ad9389b_wr_and_or(sd, 0x50, 0x1f, 0x20); + /* Speaker mapping */ + ad9389b_wr(sd, 0x51, 0x00); + + /* TODO Where should this be placed? */ + /* 16 bit audio word length */ + ad9389b_wr_and_or(sd, 0x14, 0xf0, 0x02); + + return 0; +} + +static const struct v4l2_subdev_audio_ops ad9389b_audio_ops = { + .s_stream = ad9389b_s_audio_stream, + .s_clock_freq = ad9389b_s_clock_freq, + .s_i2s_clock_freq = ad9389b_s_i2s_clock_freq, + .s_routing = ad9389b_s_routing, +}; + +/* --------------------- SUBDEV OPS --------------------------------------- */ + +static const struct v4l2_subdev_ops ad9389b_ops = { + .core = &ad9389b_core_ops, + .video = &ad9389b_video_ops, + .audio = &ad9389b_audio_ops, + .pad = &ad9389b_pad_ops, +}; + +/* ----------------------------------------------------------------------- */ +static void ad9389b_dbg_dump_edid(int lvl, int debug, struct v4l2_subdev *sd, + int segment, u8 *buf) +{ + int i, j; + + if (debug < lvl) + return; + + v4l2_dbg(lvl, debug, sd, "edid segment %d\n", segment); + for (i = 0; i < 256; i += 16) { + u8 b[128]; + u8 *bp = b; + + if (i == 128) + v4l2_dbg(lvl, debug, sd, "\n"); + for (j = i; j < i + 16; j++) { + sprintf(bp, "0x%02x, ", buf[j]); + bp += 6; + } + bp[0] = '\0'; + v4l2_dbg(lvl, debug, sd, "%s\n", b); + } +} + +static void ad9389b_edid_handler(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct ad9389b_state *state = container_of(dwork, + struct ad9389b_state, edid_handler); + struct v4l2_subdev *sd = &state->sd; + struct ad9389b_edid_detect ed; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (ad9389b_check_edid_status(sd)) { + /* Return if we received the EDID. */ + return; + } + + if (ad9389b_have_hotplug(sd)) { + /* We must retry reading the EDID several times, it is possible + * that initially the EDID couldn't be read due to i2c errors + * (DVI connectors are particularly prone to this problem). */ + if (state->edid.read_retries) { + state->edid.read_retries--; + /* EDID read failed, trigger a retry */ + ad9389b_wr(sd, 0xc9, 0xf); + queue_delayed_work(state->work_queue, + &state->edid_handler, EDID_DELAY); + return; + } + } + + /* We failed to read the EDID, so send an event for this. */ + ed.present = false; + ed.segment = ad9389b_rd(sd, 0xc4); + v4l2_subdev_notify(sd, AD9389B_EDID_DETECT, (void *)&ed); + v4l2_dbg(1, debug, sd, "%s: no edid found\n", __func__); +} + +static void ad9389b_audio_setup(struct v4l2_subdev *sd) +{ + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + ad9389b_s_i2s_clock_freq(sd, 48000); + ad9389b_s_clock_freq(sd, 48000); + ad9389b_s_routing(sd, 0, 0, 0); +} + +/* Initial setup of AD9389b */ + +/* Configure hdmi transmitter. */ +static void ad9389b_setup(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* Input format: RGB 4:4:4 */ + ad9389b_wr_and_or(sd, 0x15, 0xf1, 0x0); + /* Output format: RGB 4:4:4 */ + ad9389b_wr_and_or(sd, 0x16, 0x3f, 0x0); + /* CSC fixed point: +/-2, 1st order interpolation 4:2:2 -> 4:4:4 up + conversion, Aspect ratio: 16:9 */ + ad9389b_wr_and_or(sd, 0x17, 0xe1, 0x0e); + /* Disable pixel repetition and CSC */ + ad9389b_wr_and_or(sd, 0x3b, 0x9e, 0x0); + /* Output format: RGB 4:4:4, Active Format Information is valid. */ + ad9389b_wr_and_or(sd, 0x45, 0xc7, 0x08); + /* Underscanned */ + ad9389b_wr_and_or(sd, 0x46, 0x3f, 0x80); + /* Setup video format */ + ad9389b_wr(sd, 0x3c, 0x0); + /* Active format aspect ratio: same as picure. */ + ad9389b_wr(sd, 0x47, 0x80); + /* No encryption */ + ad9389b_wr_and_or(sd, 0xaf, 0xef, 0x0); + /* Positive clk edge capture for input video clock */ + ad9389b_wr_and_or(sd, 0xba, 0x1f, 0x60); + + ad9389b_audio_setup(sd); + + v4l2_ctrl_handler_setup(&state->hdl); + + ad9389b_set_IT_content_AVI_InfoFrame(sd); +} + +static void ad9389b_notify_monitor_detect(struct v4l2_subdev *sd) +{ + struct ad9389b_monitor_detect mdt; + struct ad9389b_state *state = get_ad9389b_state(sd); + + mdt.present = state->have_monitor; + v4l2_subdev_notify(sd, AD9389B_MONITOR_DETECT, (void *)&mdt); +} + +static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + /* read hotplug and rx-sense state */ + u8 status = ad9389b_rd(sd, 0x42); + + v4l2_dbg(1, debug, sd, "%s: status: 0x%x%s%s\n", + __func__, + status, + status & MASK_AD9389B_HPD_DETECT ? ", hotplug" : "", + status & MASK_AD9389B_MSEN_DETECT ? ", rx-sense" : ""); + + if ((status & MASK_AD9389B_HPD_DETECT) && + ((status & MASK_AD9389B_MSEN_DETECT) || state->edid.segments)) { + v4l2_dbg(1, debug, sd, + "%s: hotplug and (rx-sense or edid)\n", __func__); + if (!state->have_monitor) { + v4l2_dbg(1, debug, sd, "%s: monitor detected\n", __func__); + state->have_monitor = true; + ad9389b_set_isr(sd, true); + if (!ad9389b_s_power(sd, true)) { + v4l2_dbg(1, debug, sd, + "%s: monitor detected, powerup failed\n", __func__); + return; + } + ad9389b_setup(sd); + ad9389b_notify_monitor_detect(sd); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, + &state->edid_handler, EDID_DELAY); + } + } else if (status & MASK_AD9389B_HPD_DETECT) { + v4l2_dbg(1, debug, sd, "%s: hotplug detected\n", __func__); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, + &state->edid_handler, EDID_DELAY); + } else if (!(status & MASK_AD9389B_HPD_DETECT)) { + v4l2_dbg(1, debug, sd, "%s: hotplug not detected\n", __func__); + if (state->have_monitor) { + v4l2_dbg(1, debug, sd, "%s: monitor not detected\n", __func__); + state->have_monitor = false; + ad9389b_notify_monitor_detect(sd); + } + ad9389b_s_power(sd, false); + memset(&state->edid, 0, sizeof(struct ad9389b_state_edid)); + } + + /* update read only ctrls */ + v4l2_ctrl_s_ctrl(state->hotplug_ctrl, ad9389b_have_hotplug(sd) ? 0x1 : 0x0); + v4l2_ctrl_s_ctrl(state->rx_sense_ctrl, ad9389b_have_rx_sense(sd) ? 0x1 : 0x0); + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, state->edid.segments ? 0x1 : 0x0); +} + +static bool edid_block_verify_crc(u8 *edid_block) +{ + int i; + u8 sum = 0; + + for (i = 0; i < 127; i++) + sum += *(edid_block + i); + return ((255 - sum + 1) == edid_block[127]); +} + +static bool edid_segment_verify_crc(struct v4l2_subdev *sd, u32 segment) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + u32 blocks = state->edid.blocks; + u8 *data = state->edid.data; + + if (edid_block_verify_crc(&data[segment * 256])) { + if ((segment + 1) * 2 <= blocks) + return edid_block_verify_crc(&data[segment * 256 + 128]); + return true; + } + return false; +} + +static bool ad9389b_check_edid_status(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + struct ad9389b_edid_detect ed; + int segment; + u8 edidRdy = ad9389b_rd(sd, 0xc5); + + v4l2_dbg(1, debug, sd, "%s: edid ready (retries: %d)\n", + __func__, EDID_MAX_RETRIES - state->edid.read_retries); + + if (!(edidRdy & MASK_AD9389B_EDID_RDY)) + return false; + + segment = ad9389b_rd(sd, 0xc4); + if (segment >= EDID_MAX_SEGM) { + v4l2_err(sd, "edid segment number too big\n"); + return false; + } + v4l2_dbg(1, debug, sd, "%s: got segment %d\n", __func__, segment); + ad9389b_edid_rd(sd, 256, &state->edid.data[segment * 256]); + ad9389b_dbg_dump_edid(2, debug, sd, segment, + &state->edid.data[segment * 256]); + if (segment == 0) { + state->edid.blocks = state->edid.data[0x7e] + 1; + v4l2_dbg(1, debug, sd, "%s: %d blocks in total\n", + __func__, state->edid.blocks); + } + if (!edid_segment_verify_crc(sd, segment)) { + /* edid crc error, force reread of edid segment */ + ad9389b_s_power(sd, false); + ad9389b_s_power(sd, true); + return false; + } + /* one more segment read ok */ + state->edid.segments = segment + 1; + if (((state->edid.data[0x7e] >> 1) + 1) > state->edid.segments) { + /* Request next EDID segment */ + v4l2_dbg(1, debug, sd, "%s: request segment %d\n", + __func__, state->edid.segments); + ad9389b_wr(sd, 0xc9, 0xf); + ad9389b_wr(sd, 0xc4, state->edid.segments); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, + &state->edid_handler, EDID_DELAY); + return false; + } + + /* report when we have all segments but report only for segment 0 */ + ed.present = true; + ed.segment = 0; + v4l2_subdev_notify(sd, AD9389B_EDID_DETECT, (void *)&ed); + state->edid_detect_counter++; + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, state->edid.segments ? 0x1 : 0x0); + return ed.present; +} + +/* ----------------------------------------------------------------------- */ + +static void ad9389b_init_setup(struct v4l2_subdev *sd) +{ + struct ad9389b_state *state = get_ad9389b_state(sd); + struct ad9389b_state_edid *edid = &state->edid; + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* clear all interrupts */ + ad9389b_wr(sd, 0x96, 0xff); + + memset(edid, 0, sizeof(struct ad9389b_state_edid)); + state->have_monitor = false; + ad9389b_set_isr(sd, false); +} + +static int ad9389b_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + const struct v4l2_dv_timings dv1080p60 = V4L2_DV_BT_CEA_1920X1080P60; + struct ad9389b_state *state; + struct ad9389b_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; + struct v4l2_subdev *sd; + int err = -EIO; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_dbg(1, debug, client, "detecting ad9389b client on address 0x%x\n", + client->addr << 1); + + state = kzalloc(sizeof(struct ad9389b_state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* Platform data */ + if (pdata == NULL) { + v4l_err(client, "No platform data!\n"); + err = -ENODEV; + goto err_free; + } + memcpy(&state->pdata, pdata, sizeof(state->pdata)); + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &ad9389b_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 5); + + /* private controls */ + + state->hdmi_mode_ctrl = v4l2_ctrl_new_std_menu(hdl, &ad9389b_ctrl_ops, + V4L2_CID_DV_TX_MODE, V4L2_DV_TX_MODE_HDMI, + 0, V4L2_DV_TX_MODE_DVI_D); + state->hdmi_mode_ctrl->is_private = true; + state->hotplug_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0); + state->hotplug_ctrl->is_private = true; + state->rx_sense_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0); + state->rx_sense_ctrl->is_private = true; + state->have_edid0_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_EDID_PRESENT, 0, 1, 0, 0); + state->have_edid0_ctrl->is_private = true; + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &ad9389b_ctrl_ops, + V4L2_CID_DV_TX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + state->rgb_quantization_range_ctrl->is_private = true; + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + + goto err_hdl; + } + + state->pad.flags = MEDIA_PAD_FL_SINK; + err = media_entity_init(&sd->entity, 1, &state->pad, 0); + if (err) + goto err_hdl; + + state->chip_revision = ad9389b_rd(sd, 0x0); + if (state->chip_revision != 2) { + v4l2_err(sd, "chip_revision %d != 2\n", state->chip_revision); + err = -EIO; + goto err_entity; + } + v4l2_dbg(1, debug, sd, "reg 0x41 0x%x, chip version (reg 0x00) 0x%x\n", + ad9389b_rd(sd, 0x41), state->chip_revision); + + state->edid_i2c_client = i2c_new_dummy(client->adapter, (0x7e>>1)); + if (state->edid_i2c_client == NULL) { + v4l2_err(sd, "failed to register edid i2c client\n"); + goto err_entity; + } + + state->work_queue = create_singlethread_workqueue(sd->name); + if (state->work_queue == NULL) { + v4l2_err(sd, "could not create workqueue\n"); + goto err_unreg; + } + + INIT_DELAYED_WORK(&state->edid_handler, ad9389b_edid_handler); + state->dv_timings = dv1080p60; + + ad9389b_init_setup(sd); + ad9389b_set_isr(sd, true); + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + return 0; + +err_unreg: + i2c_unregister_device(state->edid_i2c_client); +err_entity: + media_entity_cleanup(&sd->entity); +err_hdl: + v4l2_ctrl_handler_free(&state->hdl); +err_free: + kfree(state); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static int ad9389b_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ad9389b_state *state = get_ad9389b_state(sd); + + state->chip_revision = -1; + + v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + ad9389b_s_stream(sd, false); + ad9389b_s_audio_stream(sd, false); + ad9389b_init_setup(sd); + cancel_delayed_work(&state->edid_handler); + i2c_unregister_device(state->edid_i2c_client); + destroy_workqueue(state->work_queue); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + kfree(get_ad9389b_state(sd)); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_device_id ad9389b_id[] = { + { "ad9389b", V4L2_IDENT_AD9389B }, + { "ad9889b", V4L2_IDENT_AD9389B }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad9389b_id); + +static struct i2c_driver ad9389b_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ad9389b", + }, + .probe = ad9389b_probe, + .remove = ad9389b_remove, + .id_table = ad9389b_id, +}; + +module_i2c_driver(ad9389b_driver); diff --git a/drivers/media/i2c/adp1653.c b/drivers/media/i2c/adp1653.c new file mode 100644 index 000000000000..18a38b38fcb8 --- /dev/null +++ b/drivers/media/i2c/adp1653.c @@ -0,0 +1,487 @@ +/* + * drivers/media/i2c/adp1653.c + * + * Copyright (C) 2008--2011 Nokia Corporation + * + * Contact: Sakari Ailus + * + * Contributors: + * Sakari Ailus + * Tuukka Toivonen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * TODO: + * - fault interrupt handling + * - hardware strobe + * - power doesn't need to be ON if all lights are off + * + */ + +#include +#include +#include +#include +#include +#include + +#define TIMEOUT_MAX 820000 +#define TIMEOUT_STEP 54600 +#define TIMEOUT_MIN (TIMEOUT_MAX - ADP1653_REG_CONFIG_TMR_SET_MAX \ + * TIMEOUT_STEP) +#define TIMEOUT_US_TO_CODE(t) ((TIMEOUT_MAX + (TIMEOUT_STEP / 2) - (t)) \ + / TIMEOUT_STEP) +#define TIMEOUT_CODE_TO_US(c) (TIMEOUT_MAX - (c) * TIMEOUT_STEP) + +/* Write values into ADP1653 registers. */ +static int adp1653_update_hw(struct adp1653_flash *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + u8 out_sel; + u8 config = 0; + int rval; + + out_sel = ADP1653_INDICATOR_INTENSITY_uA_TO_REG( + flash->indicator_intensity->val) + << ADP1653_REG_OUT_SEL_ILED_SHIFT; + + switch (flash->led_mode->val) { + case V4L2_FLASH_LED_MODE_NONE: + break; + case V4L2_FLASH_LED_MODE_FLASH: + /* Flash mode, light on with strobe, duration from timer */ + config = ADP1653_REG_CONFIG_TMR_CFG; + config |= TIMEOUT_US_TO_CODE(flash->flash_timeout->val) + << ADP1653_REG_CONFIG_TMR_SET_SHIFT; + break; + case V4L2_FLASH_LED_MODE_TORCH: + /* Torch mode, light immediately on, duration indefinite */ + out_sel |= ADP1653_FLASH_INTENSITY_mA_TO_REG( + flash->torch_intensity->val) + << ADP1653_REG_OUT_SEL_HPLED_SHIFT; + break; + } + + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, out_sel); + if (rval < 0) + return rval; + + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_CONFIG, config); + if (rval < 0) + return rval; + + return 0; +} + +static int adp1653_get_fault(struct adp1653_flash *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int fault; + int rval; + + fault = i2c_smbus_read_byte_data(client, ADP1653_REG_FAULT); + if (IS_ERR_VALUE(fault)) + return fault; + + flash->fault |= fault; + + if (!flash->fault) + return 0; + + /* Clear faults. */ + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, 0); + if (IS_ERR_VALUE(rval)) + return rval; + + flash->led_mode->val = V4L2_FLASH_LED_MODE_NONE; + + rval = adp1653_update_hw(flash); + if (IS_ERR_VALUE(rval)) + return rval; + + return flash->fault; +} + +static int adp1653_strobe(struct adp1653_flash *flash, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + u8 out_sel = ADP1653_INDICATOR_INTENSITY_uA_TO_REG( + flash->indicator_intensity->val) + << ADP1653_REG_OUT_SEL_ILED_SHIFT; + int rval; + + if (flash->led_mode->val != V4L2_FLASH_LED_MODE_FLASH) + return -EBUSY; + + if (!enable) + return i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, + out_sel); + + out_sel |= ADP1653_FLASH_INTENSITY_mA_TO_REG( + flash->flash_intensity->val) + << ADP1653_REG_OUT_SEL_HPLED_SHIFT; + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, out_sel); + if (rval) + return rval; + + /* Software strobe using i2c */ + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_SW_STROBE, + ADP1653_REG_SW_STROBE_SW_STROBE); + if (rval) + return rval; + return i2c_smbus_write_byte_data(client, ADP1653_REG_SW_STROBE, 0); +} + +/* -------------------------------------------------------------------------- + * V4L2 controls + */ + +static int adp1653_get_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adp1653_flash *flash = + container_of(ctrl->handler, struct adp1653_flash, ctrls); + int rval; + + rval = adp1653_get_fault(flash); + if (IS_ERR_VALUE(rval)) + return rval; + + ctrl->cur.val = 0; + + if (flash->fault & ADP1653_REG_FAULT_FLT_SCP) + ctrl->cur.val |= V4L2_FLASH_FAULT_SHORT_CIRCUIT; + if (flash->fault & ADP1653_REG_FAULT_FLT_OT) + ctrl->cur.val |= V4L2_FLASH_FAULT_OVER_TEMPERATURE; + if (flash->fault & ADP1653_REG_FAULT_FLT_TMR) + ctrl->cur.val |= V4L2_FLASH_FAULT_TIMEOUT; + if (flash->fault & ADP1653_REG_FAULT_FLT_OV) + ctrl->cur.val |= V4L2_FLASH_FAULT_OVER_VOLTAGE; + + flash->fault = 0; + + return 0; +} + +static int adp1653_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adp1653_flash *flash = + container_of(ctrl->handler, struct adp1653_flash, ctrls); + int rval; + + rval = adp1653_get_fault(flash); + if (IS_ERR_VALUE(rval)) + return rval; + if ((rval & (ADP1653_REG_FAULT_FLT_SCP | + ADP1653_REG_FAULT_FLT_OT | + ADP1653_REG_FAULT_FLT_OV)) && + (ctrl->id == V4L2_CID_FLASH_STROBE || + ctrl->id == V4L2_CID_FLASH_TORCH_INTENSITY || + ctrl->id == V4L2_CID_FLASH_LED_MODE)) + return -EBUSY; + + switch (ctrl->id) { + case V4L2_CID_FLASH_STROBE: + return adp1653_strobe(flash, 1); + case V4L2_CID_FLASH_STROBE_STOP: + return adp1653_strobe(flash, 0); + } + + return adp1653_update_hw(flash); +} + +static const struct v4l2_ctrl_ops adp1653_ctrl_ops = { + .g_volatile_ctrl = adp1653_get_ctrl, + .s_ctrl = adp1653_set_ctrl, +}; + +static int adp1653_init_controls(struct adp1653_flash *flash) +{ + struct v4l2_ctrl *fault; + + v4l2_ctrl_handler_init(&flash->ctrls, 9); + + flash->led_mode = + v4l2_ctrl_new_std_menu(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_LED_MODE, + V4L2_FLASH_LED_MODE_TORCH, ~0x7, 0); + v4l2_ctrl_new_std_menu(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_STROBE_SOURCE, + V4L2_FLASH_STROBE_SOURCE_SOFTWARE, ~0x1, 0); + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_STROBE, 0, 0, 0, 0); + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_STROBE_STOP, 0, 0, 0, 0); + flash->flash_timeout = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_TIMEOUT, TIMEOUT_MIN, + flash->platform_data->max_flash_timeout, + TIMEOUT_STEP, + flash->platform_data->max_flash_timeout); + flash->flash_intensity = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_INTENSITY, + ADP1653_FLASH_INTENSITY_MIN, + flash->platform_data->max_flash_intensity, + 1, flash->platform_data->max_flash_intensity); + flash->torch_intensity = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_TORCH_INTENSITY, + ADP1653_TORCH_INTENSITY_MIN, + flash->platform_data->max_torch_intensity, + ADP1653_FLASH_INTENSITY_STEP, + flash->platform_data->max_torch_intensity); + flash->indicator_intensity = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_INDICATOR_INTENSITY, + ADP1653_INDICATOR_INTENSITY_MIN, + flash->platform_data->max_indicator_intensity, + ADP1653_INDICATOR_INTENSITY_STEP, + ADP1653_INDICATOR_INTENSITY_MIN); + fault = v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_FAULT, 0, + V4L2_FLASH_FAULT_OVER_VOLTAGE + | V4L2_FLASH_FAULT_OVER_TEMPERATURE + | V4L2_FLASH_FAULT_SHORT_CIRCUIT, 0, 0); + + if (flash->ctrls.error) + return flash->ctrls.error; + + fault->flags |= V4L2_CTRL_FLAG_VOLATILE; + + flash->subdev.ctrl_handler = &flash->ctrls; + return 0; +} + +/* -------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static int +adp1653_init_device(struct adp1653_flash *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int rval; + + /* Clear FAULT register by writing zero to OUT_SEL */ + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, 0); + if (rval < 0) { + dev_err(&client->dev, "failed writing fault register\n"); + return -EIO; + } + + mutex_lock(flash->ctrls.lock); + /* Reset faults before reading new ones. */ + flash->fault = 0; + rval = adp1653_get_fault(flash); + mutex_unlock(flash->ctrls.lock); + if (rval > 0) { + dev_err(&client->dev, "faults detected: 0x%1.1x\n", rval); + return -EIO; + } + + mutex_lock(flash->ctrls.lock); + rval = adp1653_update_hw(flash); + mutex_unlock(flash->ctrls.lock); + if (rval) { + dev_err(&client->dev, + "adp1653_update_hw failed at %s\n", __func__); + return -EIO; + } + + return 0; +} + +static int +__adp1653_set_power(struct adp1653_flash *flash, int on) +{ + int ret; + + ret = flash->platform_data->power(&flash->subdev, on); + if (ret < 0) + return ret; + + if (!on) + return 0; + + ret = adp1653_init_device(flash); + if (ret < 0) + flash->platform_data->power(&flash->subdev, 0); + + return ret; +} + +static int +adp1653_set_power(struct v4l2_subdev *subdev, int on) +{ + struct adp1653_flash *flash = to_adp1653_flash(subdev); + int ret = 0; + + mutex_lock(&flash->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (flash->power_count == !on) { + ret = __adp1653_set_power(flash, !!on); + if (ret < 0) + goto done; + } + + /* Update the power count. */ + flash->power_count += on ? 1 : -1; + WARN_ON(flash->power_count < 0); + +done: + mutex_unlock(&flash->power_lock); + return ret; +} + +static int adp1653_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return adp1653_set_power(sd, 1); +} + +static int adp1653_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return adp1653_set_power(sd, 0); +} + +static const struct v4l2_subdev_core_ops adp1653_core_ops = { + .s_power = adp1653_set_power, +}; + +static const struct v4l2_subdev_ops adp1653_ops = { + .core = &adp1653_core_ops, +}; + +static const struct v4l2_subdev_internal_ops adp1653_internal_ops = { + .open = adp1653_open, + .close = adp1653_close, +}; + +/* -------------------------------------------------------------------------- + * I2C driver + */ +#ifdef CONFIG_PM + +static int adp1653_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct adp1653_flash *flash = to_adp1653_flash(subdev); + + if (!flash->power_count) + return 0; + + return __adp1653_set_power(flash, 0); +} + +static int adp1653_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct adp1653_flash *flash = to_adp1653_flash(subdev); + + if (!flash->power_count) + return 0; + + return __adp1653_set_power(flash, 1); +} + +#else + +#define adp1653_suspend NULL +#define adp1653_resume NULL + +#endif /* CONFIG_PM */ + +static int adp1653_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct adp1653_flash *flash; + int ret; + + /* we couldn't work without platform data */ + if (client->dev.platform_data == NULL) + return -ENODEV; + + flash = kzalloc(sizeof(*flash), GFP_KERNEL); + if (flash == NULL) + return -ENOMEM; + + flash->platform_data = client->dev.platform_data; + + mutex_init(&flash->power_lock); + + v4l2_i2c_subdev_init(&flash->subdev, client, &adp1653_ops); + flash->subdev.internal_ops = &adp1653_internal_ops; + flash->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + ret = adp1653_init_controls(flash); + if (ret) + goto free_and_quit; + + ret = media_entity_init(&flash->subdev.entity, 0, NULL, 0); + if (ret < 0) + goto free_and_quit; + + flash->subdev.entity.type = MEDIA_ENT_T_V4L2_SUBDEV_FLASH; + + return 0; + +free_and_quit: + v4l2_ctrl_handler_free(&flash->ctrls); + kfree(flash); + return ret; +} + +static int __exit adp1653_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct adp1653_flash *flash = to_adp1653_flash(subdev); + + v4l2_device_unregister_subdev(&flash->subdev); + v4l2_ctrl_handler_free(&flash->ctrls); + media_entity_cleanup(&flash->subdev.entity); + kfree(flash); + return 0; +} + +static const struct i2c_device_id adp1653_id_table[] = { + { ADP1653_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adp1653_id_table); + +static struct dev_pm_ops adp1653_pm_ops = { + .suspend = adp1653_suspend, + .resume = adp1653_resume, +}; + +static struct i2c_driver adp1653_i2c_driver = { + .driver = { + .name = ADP1653_NAME, + .pm = &adp1653_pm_ops, + }, + .probe = adp1653_probe, + .remove = __exit_p(adp1653_remove), + .id_table = adp1653_id_table, +}; + +module_i2c_driver(adp1653_i2c_driver); + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_DESCRIPTION("Analog Devices ADP1653 LED flash driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/adv7170.c b/drivers/media/i2c/adv7170.c similarity index 100% rename from drivers/media/video/adv7170.c rename to drivers/media/i2c/adv7170.c diff --git a/drivers/media/video/adv7175.c b/drivers/media/i2c/adv7175.c similarity index 100% rename from drivers/media/video/adv7175.c rename to drivers/media/i2c/adv7175.c diff --git a/drivers/media/video/adv7180.c b/drivers/media/i2c/adv7180.c similarity index 100% rename from drivers/media/video/adv7180.c rename to drivers/media/i2c/adv7180.c diff --git a/drivers/media/video/adv7183.c b/drivers/media/i2c/adv7183.c similarity index 100% rename from drivers/media/video/adv7183.c rename to drivers/media/i2c/adv7183.c diff --git a/drivers/media/video/adv7183_regs.h b/drivers/media/i2c/adv7183_regs.h similarity index 100% rename from drivers/media/video/adv7183_regs.h rename to drivers/media/i2c/adv7183_regs.h diff --git a/drivers/media/video/adv7343.c b/drivers/media/i2c/adv7343.c similarity index 100% rename from drivers/media/video/adv7343.c rename to drivers/media/i2c/adv7343.c diff --git a/drivers/media/video/adv7343_regs.h b/drivers/media/i2c/adv7343_regs.h similarity index 100% rename from drivers/media/video/adv7343_regs.h rename to drivers/media/i2c/adv7343_regs.h diff --git a/drivers/media/video/adv7393.c b/drivers/media/i2c/adv7393.c similarity index 100% rename from drivers/media/video/adv7393.c rename to drivers/media/i2c/adv7393.c diff --git a/drivers/media/video/adv7393_regs.h b/drivers/media/i2c/adv7393_regs.h similarity index 100% rename from drivers/media/video/adv7393_regs.h rename to drivers/media/i2c/adv7393_regs.h diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c new file mode 100644 index 000000000000..109bc9b12e74 --- /dev/null +++ b/drivers/media/i2c/adv7604.c @@ -0,0 +1,1959 @@ +/* + * adv7604 - Analog Devices ADV7604 video decoder driver + * + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Analog devices, ADV7604, Register Settings Recommendations, + * Revision 2.5, June 2010 + * REF_02 - Analog devices, Register map documentation, Documentation of + * the register maps, Software manual, Rev. F, June 2010 + * REF_03 - Analog devices, ADV7604, Hardware Manual, Rev. F, August 2010 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices ADV7604 video decoder driver"); +MODULE_AUTHOR("Hans Verkuil "); +MODULE_AUTHOR("Mats Randgaard "); +MODULE_LICENSE("GPL"); + +/* ADV7604 system clock frequency */ +#define ADV7604_fsc (28636360) + +#define DIGITAL_INPUT ((state->prim_mode == ADV7604_PRIM_MODE_HDMI_COMP) || \ + (state->prim_mode == ADV7604_PRIM_MODE_HDMI_GR)) + +/* + ********************************************************************** + * + * Arrays with configuration parameters for the ADV7604 + * + ********************************************************************** + */ +struct adv7604_state { + struct adv7604_platform_data pdata; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + enum adv7604_prim_mode prim_mode; + struct v4l2_dv_timings timings; + u8 edid[256]; + unsigned edid_blocks; + struct v4l2_fract aspect_ratio; + u32 rgb_quantization_range; + struct workqueue_struct *work_queues; + struct delayed_work delayed_work_enable_hotplug; + bool connector_hdmi; + + /* i2c clients */ + struct i2c_client *i2c_avlink; + struct i2c_client *i2c_cec; + struct i2c_client *i2c_infoframe; + struct i2c_client *i2c_esdp; + struct i2c_client *i2c_dpp; + struct i2c_client *i2c_afe; + struct i2c_client *i2c_repeater; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_hdmi; + struct i2c_client *i2c_test; + struct i2c_client *i2c_cp; + struct i2c_client *i2c_vdp; + + /* controls */ + struct v4l2_ctrl *detect_tx_5v_ctrl; + struct v4l2_ctrl *analog_sampling_phase_ctrl; + struct v4l2_ctrl *free_run_color_manual_ctrl; + struct v4l2_ctrl *free_run_color_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; +}; + +/* Supported CEA and DMT timings */ +static const struct v4l2_dv_timings adv7604_timings[] = { + V4L2_DV_BT_CEA_720X480P59_94, + V4L2_DV_BT_CEA_720X576P50, + V4L2_DV_BT_CEA_1280X720P24, + V4L2_DV_BT_CEA_1280X720P25, + V4L2_DV_BT_CEA_1280X720P30, + V4L2_DV_BT_CEA_1280X720P50, + V4L2_DV_BT_CEA_1280X720P60, + V4L2_DV_BT_CEA_1920X1080P24, + V4L2_DV_BT_CEA_1920X1080P25, + V4L2_DV_BT_CEA_1920X1080P30, + V4L2_DV_BT_CEA_1920X1080P50, + V4L2_DV_BT_CEA_1920X1080P60, + + V4L2_DV_BT_DMT_640X350P85, + V4L2_DV_BT_DMT_640X400P85, + V4L2_DV_BT_DMT_720X400P85, + V4L2_DV_BT_DMT_640X480P60, + V4L2_DV_BT_DMT_640X480P72, + V4L2_DV_BT_DMT_640X480P75, + V4L2_DV_BT_DMT_640X480P85, + V4L2_DV_BT_DMT_800X600P56, + V4L2_DV_BT_DMT_800X600P60, + V4L2_DV_BT_DMT_800X600P72, + V4L2_DV_BT_DMT_800X600P75, + V4L2_DV_BT_DMT_800X600P85, + V4L2_DV_BT_DMT_848X480P60, + V4L2_DV_BT_DMT_1024X768P60, + V4L2_DV_BT_DMT_1024X768P70, + V4L2_DV_BT_DMT_1024X768P75, + V4L2_DV_BT_DMT_1024X768P85, + V4L2_DV_BT_DMT_1152X864P75, + V4L2_DV_BT_DMT_1280X768P60_RB, + V4L2_DV_BT_DMT_1280X768P60, + V4L2_DV_BT_DMT_1280X768P75, + V4L2_DV_BT_DMT_1280X768P85, + V4L2_DV_BT_DMT_1280X800P60_RB, + V4L2_DV_BT_DMT_1280X800P60, + V4L2_DV_BT_DMT_1280X800P75, + V4L2_DV_BT_DMT_1280X800P85, + V4L2_DV_BT_DMT_1280X960P60, + V4L2_DV_BT_DMT_1280X960P85, + V4L2_DV_BT_DMT_1280X1024P60, + V4L2_DV_BT_DMT_1280X1024P75, + V4L2_DV_BT_DMT_1280X1024P85, + V4L2_DV_BT_DMT_1360X768P60, + V4L2_DV_BT_DMT_1400X1050P60_RB, + V4L2_DV_BT_DMT_1400X1050P60, + V4L2_DV_BT_DMT_1400X1050P75, + V4L2_DV_BT_DMT_1400X1050P85, + V4L2_DV_BT_DMT_1440X900P60_RB, + V4L2_DV_BT_DMT_1440X900P60, + V4L2_DV_BT_DMT_1600X1200P60, + V4L2_DV_BT_DMT_1680X1050P60_RB, + V4L2_DV_BT_DMT_1680X1050P60, + V4L2_DV_BT_DMT_1792X1344P60, + V4L2_DV_BT_DMT_1856X1392P60, + V4L2_DV_BT_DMT_1920X1200P60_RB, + V4L2_DV_BT_DMT_1366X768P60, + V4L2_DV_BT_DMT_1920X1080P60, + { }, +}; + +/* ----------------------------------------------------------------------- */ + +static inline struct adv7604_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7604_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7604_state, hdl)->sd; +} + +static inline unsigned hblanking(const struct v4l2_bt_timings *t) +{ + return t->hfrontporch + t->hsync + t->hbackporch; +} + +static inline unsigned htotal(const struct v4l2_bt_timings *t) +{ + return t->width + t->hfrontporch + t->hsync + t->hbackporch; +} + +static inline unsigned vblanking(const struct v4l2_bt_timings *t) +{ + return t->vfrontporch + t->vsync + t->vbackporch; +} + +static inline unsigned vtotal(const struct v4l2_bt_timings *t) +{ + return t->height + t->vfrontporch + t->vsync + t->vbackporch; +} + +/* ----------------------------------------------------------------------- */ + +static s32 adv_smbus_read_byte_data_check(struct i2c_client *client, + u8 command, bool check) +{ + union i2c_smbus_data data; + + if (!i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BYTE_DATA, &data)) + return data.byte; + if (check) + v4l_err(client, "error reading %02x, %02x\n", + client->addr, command); + return -EIO; +} + +static s32 adv_smbus_read_byte_data(struct i2c_client *client, u8 command) +{ + return adv_smbus_read_byte_data_check(client, command, true); +} + +static s32 adv_smbus_write_byte_data(struct i2c_client *client, + u8 command, u8 value) +{ + union i2c_smbus_data data; + int err; + int i; + + data.byte = value; + for (i = 0; i < 3; i++) { + err = i2c_smbus_xfer(client->adapter, client->addr, + client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_BYTE_DATA, &data); + if (!err) + break; + } + if (err < 0) + v4l_err(client, "error writing %02x, %02x, %02x\n", + client->addr, command, value); + return err; +} + +static s32 adv_smbus_write_i2c_block_data(struct i2c_client *client, + u8 command, unsigned length, const u8 *values) +{ + union i2c_smbus_data data; + + if (length > I2C_SMBUS_BLOCK_MAX) + length = I2C_SMBUS_BLOCK_MAX; + data.block[0] = length; + memcpy(data.block + 1, values, length); + return i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_I2C_BLOCK_DATA, &data); +} + +/* ----------------------------------------------------------------------- */ + +static inline int io_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_read_byte_data(client, reg); +} + +static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_write_byte_data(client, reg, val); +} + +static inline int io_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return io_write(sd, reg, (io_read(sd, reg) & mask) | val); +} + +static inline int avlink_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_avlink, reg); +} + +static inline int avlink_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_avlink, reg, val); +} + +static inline int cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_cec, reg); +} + +static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_cec, reg, val); +} + +static inline int cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cec_write(sd, reg, (cec_read(sd, reg) & mask) | val); +} + +static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_infoframe, reg); +} + +static inline int infoframe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_infoframe, reg, val); +} + +static inline int esdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_esdp, reg); +} + +static inline int esdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_esdp, reg, val); +} + +static inline int dpp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_dpp, reg); +} + +static inline int dpp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_dpp, reg, val); +} + +static inline int afe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_afe, reg); +} + +static inline int afe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_afe, reg, val); +} + +static inline int rep_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_repeater, reg); +} + +static inline int rep_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_repeater, reg, val); +} + +static inline int rep_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return rep_write(sd, reg, (rep_read(sd, reg) & mask) | val); +} + +static inline int edid_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_edid, reg); +} + +static inline int edid_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_edid, reg, val); +} + +static inline int edid_read_block(struct v4l2_subdev *sd, unsigned len, u8 *val) +{ + struct adv7604_state *state = to_state(sd); + struct i2c_client *client = state->i2c_edid; + u8 msgbuf0[1] = { 0 }; + u8 msgbuf1[256]; + struct i2c_msg msg[2] = { { client->addr, 0, 1, msgbuf0 }, + { client->addr, 0 | I2C_M_RD, len, msgbuf1 } + }; + + if (i2c_transfer(client->adapter, msg, 2) < 0) + return -EIO; + memcpy(val, msgbuf1, len); + return 0; +} + +static void adv7604_delayed_work_enable_hotplug(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv7604_state *state = container_of(dwork, struct adv7604_state, + delayed_work_enable_hotplug); + struct v4l2_subdev *sd = &state->sd; + + v4l2_dbg(2, debug, sd, "%s: enable hotplug\n", __func__); + + v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)1); +} + +static inline int edid_write_block(struct v4l2_subdev *sd, + unsigned len, const u8 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7604_state *state = to_state(sd); + int err = 0; + int i; + + v4l2_dbg(2, debug, sd, "%s: write EDID block (%d byte)\n", __func__, len); + + v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)0); + + /* Disables I2C access to internal EDID ram from DDC port */ + rep_write_and_or(sd, 0x77, 0xf0, 0x0); + + for (i = 0; !err && i < len; i += I2C_SMBUS_BLOCK_MAX) + err = adv_smbus_write_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, val + i); + if (err) + return err; + + /* adv7604 calculates the checksums and enables I2C access to internal + EDID ram from DDC port. */ + rep_write_and_or(sd, 0x77, 0xf0, 0x1); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, 0x7d) & 1) + break; + mdelay(1); + } + if (i == 1000) { + v4l_err(client, "error enabling edid\n"); + return -EIO; + } + + /* enable hotplug after 100 ms */ + queue_delayed_work(state->work_queues, + &state->delayed_work_enable_hotplug, HZ / 10); + return 0; +} + +static inline int hdmi_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_hdmi, reg); +} + +static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_hdmi, reg, val); +} + +static inline int test_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_test, reg); +} + +static inline int test_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_test, reg, val); +} + +static inline int cp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_cp, reg); +} + +static inline int cp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_cp, reg, val); +} + +static inline int cp_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cp_write(sd, reg, (cp_read(sd, reg) & mask) | val); +} + +static inline int vdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_vdp, reg); +} + +static inline int vdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7604_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_vdp, reg, val); +} + +/* ----------------------------------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void adv7604_inv_register(struct v4l2_subdev *sd) +{ + v4l2_info(sd, "0x000-0x0ff: IO Map\n"); + v4l2_info(sd, "0x100-0x1ff: AVLink Map\n"); + v4l2_info(sd, "0x200-0x2ff: CEC Map\n"); + v4l2_info(sd, "0x300-0x3ff: InfoFrame Map\n"); + v4l2_info(sd, "0x400-0x4ff: ESDP Map\n"); + v4l2_info(sd, "0x500-0x5ff: DPP Map\n"); + v4l2_info(sd, "0x600-0x6ff: AFE Map\n"); + v4l2_info(sd, "0x700-0x7ff: Repeater Map\n"); + v4l2_info(sd, "0x800-0x8ff: EDID Map\n"); + v4l2_info(sd, "0x900-0x9ff: HDMI Map\n"); + v4l2_info(sd, "0xa00-0xaff: Test Map\n"); + v4l2_info(sd, "0xb00-0xbff: CP Map\n"); + v4l2_info(sd, "0xc00-0xcff: VDP Map\n"); +} + +static int adv7604_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + reg->size = 1; + switch (reg->reg >> 8) { + case 0: + reg->val = io_read(sd, reg->reg & 0xff); + break; + case 1: + reg->val = avlink_read(sd, reg->reg & 0xff); + break; + case 2: + reg->val = cec_read(sd, reg->reg & 0xff); + break; + case 3: + reg->val = infoframe_read(sd, reg->reg & 0xff); + break; + case 4: + reg->val = esdp_read(sd, reg->reg & 0xff); + break; + case 5: + reg->val = dpp_read(sd, reg->reg & 0xff); + break; + case 6: + reg->val = afe_read(sd, reg->reg & 0xff); + break; + case 7: + reg->val = rep_read(sd, reg->reg & 0xff); + break; + case 8: + reg->val = edid_read(sd, reg->reg & 0xff); + break; + case 9: + reg->val = hdmi_read(sd, reg->reg & 0xff); + break; + case 0xa: + reg->val = test_read(sd, reg->reg & 0xff); + break; + case 0xb: + reg->val = cp_read(sd, reg->reg & 0xff); + break; + case 0xc: + reg->val = vdp_read(sd, reg->reg & 0xff); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7604_inv_register(sd); + break; + } + return 0; +} + +static int adv7604_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + switch (reg->reg >> 8) { + case 0: + io_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 1: + avlink_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 2: + cec_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 3: + infoframe_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 4: + esdp_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 5: + dpp_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 6: + afe_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 7: + rep_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 8: + edid_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 9: + hdmi_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 0xa: + test_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 0xb: + cp_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 0xc: + vdp_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7604_inv_register(sd); + break; + } + return 0; +} +#endif + +static int adv7604_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) +{ + struct adv7604_state *state = to_state(sd); + + /* port A only */ + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, + ((io_read(sd, 0x6f) & 0x10) >> 4)); +} + +static void configure_free_run(struct v4l2_subdev *sd, const struct v4l2_bt_timings *timings) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u32 width = htotal(timings); + u32 height = vtotal(timings); + u16 ch1_fr_ll = (((u32)timings->pixelclock / 100) > 0) ? + ((width * (ADV7604_fsc / 100)) / ((u32)timings->pixelclock / 100)) : 0; + + v4l2_dbg(2, debug, sd, "%s\n", __func__); + + cp_write(sd, 0x8f, (ch1_fr_ll >> 8) & 0x7); /* CH1_FR_LL */ + cp_write(sd, 0x90, ch1_fr_ll & 0xff); /* CH1_FR_LL */ + cp_write(sd, 0xab, (height >> 4) & 0xff); /* CP_LCOUNT_MAX */ + cp_write(sd, 0xac, (height & 0x0f) << 4); /* CP_LCOUNT_MAX */ + /* TODO support interlaced */ + cp_write(sd, 0x91, 0x10); /* INTERLACED */ + + /* Should only be set in auto-graphics mode [REF_02 p. 91-92] */ + if ((io_read(sd, 0x00) == 0x07) && (io_read(sd, 0x01) == 0x02)) { + u16 cp_start_sav, cp_start_eav, cp_start_vbi, cp_end_vbi; + const u8 pll[2] = { + (0xc0 | ((width >> 8) & 0x1f)), + (width & 0xff) + }; + + /* setup PLL_DIV_MAN_EN and PLL_DIV_RATIO */ + /* IO-map reg. 0x16 and 0x17 should be written in sequence */ + if (adv_smbus_write_i2c_block_data(client, 0x16, 2, pll)) { + v4l2_err(sd, "writing to reg 0x16 and 0x17 failed\n"); + return; + } + + /* active video - horizontal timing */ + cp_start_sav = timings->hsync + timings->hbackporch - 4; + cp_start_eav = width - timings->hfrontporch; + cp_write(sd, 0xa2, (cp_start_sav >> 4) & 0xff); + cp_write(sd, 0xa3, ((cp_start_sav & 0x0f) << 4) | ((cp_start_eav >> 8) & 0x0f)); + cp_write(sd, 0xa4, cp_start_eav & 0xff); + + /* active video - vertical timing */ + cp_start_vbi = height - timings->vfrontporch; + cp_end_vbi = timings->vsync + timings->vbackporch; + cp_write(sd, 0xa5, (cp_start_vbi >> 4) & 0xff); + cp_write(sd, 0xa6, ((cp_start_vbi & 0xf) << 4) | ((cp_end_vbi >> 8) & 0xf)); + cp_write(sd, 0xa7, cp_end_vbi & 0xff); + } else { + /* reset to default values */ + io_write(sd, 0x16, 0x43); + io_write(sd, 0x17, 0x5a); + cp_write(sd, 0xa2, 0x00); + cp_write(sd, 0xa3, 0x00); + cp_write(sd, 0xa4, 0x00); + cp_write(sd, 0xa5, 0x00); + cp_write(sd, 0xa6, 0x00); + cp_write(sd, 0xa7, 0x00); + } +} + + +static void set_rgb_quantization_range(struct v4l2_subdev *sd) +{ + struct adv7604_state *state = to_state(sd); + + switch (state->rgb_quantization_range) { + case V4L2_DV_RGB_RANGE_AUTO: + /* automatic */ + if ((hdmi_read(sd, 0x05) & 0x80) || + (state->prim_mode == ADV7604_PRIM_MODE_COMP) || + (state->prim_mode == ADV7604_PRIM_MODE_RGB)) { + /* receiving HDMI or analog signal */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + } else { + /* receiving DVI-D signal */ + + /* ADV7604 selects RGB limited range regardless of + input format (CE/IT) in automatic mode */ + if (state->timings.bt.standards & V4L2_DV_BT_STD_CEA861) { + /* RGB limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x00); + + } else { + /* RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + } + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + /* RGB limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x00); + break; + case V4L2_DV_RGB_RANGE_FULL: + /* RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + break; + } +} + + +static int adv7604_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct adv7604_state *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + cp_write(sd, 0x3c, ctrl->val); + return 0; + case V4L2_CID_CONTRAST: + cp_write(sd, 0x3a, ctrl->val); + return 0; + case V4L2_CID_SATURATION: + cp_write(sd, 0x3b, ctrl->val); + return 0; + case V4L2_CID_HUE: + cp_write(sd, 0x3d, ctrl->val); + return 0; + case V4L2_CID_DV_RX_RGB_RANGE: + state->rgb_quantization_range = ctrl->val; + set_rgb_quantization_range(sd); + return 0; + case V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE: + /* Set the analog sampling phase. This is needed to find the + best sampling phase for analog video: an application or + driver has to try a number of phases and analyze the picture + quality before settling on the best performing phase. */ + afe_write(sd, 0xc8, ctrl->val); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL: + /* Use the default blue color for free running mode, + or supply your own. */ + cp_write_and_or(sd, 0xbf, ~0x04, (ctrl->val << 2)); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR: + cp_write(sd, 0xc0, (ctrl->val & 0xff0000) >> 16); + cp_write(sd, 0xc1, (ctrl->val & 0x00ff00) >> 8); + cp_write(sd, 0xc2, (u8)(ctrl->val & 0x0000ff)); + return 0; + } + return -EINVAL; +} + +static int adv7604_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_ADV7604, 0); +} + +/* ----------------------------------------------------------------------- */ + +static inline bool no_power(struct v4l2_subdev *sd) +{ + /* Entire chip or CP powered off */ + return io_read(sd, 0x0c) & 0x24; +} + +static inline bool no_signal_tmds(struct v4l2_subdev *sd) +{ + /* TODO port B, C and D */ + return !(io_read(sd, 0x6a) & 0x10); +} + +static inline bool no_lock_tmds(struct v4l2_subdev *sd) +{ + return (io_read(sd, 0x6a) & 0xe0) != 0xe0; +} + +static inline bool no_lock_sspd(struct v4l2_subdev *sd) +{ + /* TODO channel 2 */ + return ((cp_read(sd, 0xb5) & 0xd0) != 0xd0); +} + +static inline bool no_lock_stdi(struct v4l2_subdev *sd) +{ + /* TODO channel 2 */ + return !(cp_read(sd, 0xb1) & 0x80); +} + +static inline bool no_signal(struct v4l2_subdev *sd) +{ + struct adv7604_state *state = to_state(sd); + bool ret; + + ret = no_power(sd); + + ret |= no_lock_stdi(sd); + ret |= no_lock_sspd(sd); + + if (DIGITAL_INPUT) { + ret |= no_lock_tmds(sd); + ret |= no_signal_tmds(sd); + } + + return ret; +} + +static inline bool no_lock_cp(struct v4l2_subdev *sd) +{ + /* CP has detected a non standard number of lines on the incoming + video compared to what it is configured to receive by s_dv_timings */ + return io_read(sd, 0x12) & 0x01; +} + +static int adv7604_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv7604_state *state = to_state(sd); + + *status = 0; + *status |= no_power(sd) ? V4L2_IN_ST_NO_POWER : 0; + *status |= no_signal(sd) ? V4L2_IN_ST_NO_SIGNAL : 0; + if (no_lock_cp(sd)) + *status |= DIGITAL_INPUT ? V4L2_IN_ST_NO_SYNC : V4L2_IN_ST_NO_H_LOCK; + + v4l2_dbg(1, debug, sd, "%s: status = 0x%x\n", __func__, *status); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static void adv7604_print_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings, const char *txt, bool detailed) +{ + struct v4l2_bt_timings *bt = &timings->bt; + u32 htot, vtot; + + if (timings->type != V4L2_DV_BT_656_1120) + return; + + htot = htotal(bt); + vtot = vtotal(bt); + + v4l2_info(sd, "%s %dx%d%s%d (%dx%d)", + txt, bt->width, bt->height, bt->interlaced ? "i" : "p", + (htot * vtot) > 0 ? ((u32)bt->pixelclock / + (htot * vtot)) : 0, + htot, vtot); + + if (detailed) { + v4l2_info(sd, " horizontal: fp = %d, %ssync = %d, bp = %d\n", + bt->hfrontporch, + (bt->polarities & V4L2_DV_HSYNC_POS_POL) ? "+" : "-", + bt->hsync, bt->hbackporch); + v4l2_info(sd, " vertical: fp = %d, %ssync = %d, bp = %d\n", + bt->vfrontporch, + (bt->polarities & V4L2_DV_VSYNC_POS_POL) ? "+" : "-", + bt->vsync, bt->vbackporch); + v4l2_info(sd, " pixelclock: %lld, flags: 0x%x, standards: 0x%x\n", + bt->pixelclock, bt->flags, bt->standards); + } +} + +struct stdi_readback { + u16 bl, lcf, lcvs; + u8 hs_pol, vs_pol; + bool interlaced; +}; + +static int stdi2dv_timings(struct v4l2_subdev *sd, + struct stdi_readback *stdi, + struct v4l2_dv_timings *timings) +{ + struct adv7604_state *state = to_state(sd); + u32 hfreq = (ADV7604_fsc * 8) / stdi->bl; + u32 pix_clk; + int i; + + for (i = 0; adv7604_timings[i].bt.height; i++) { + if (vtotal(&adv7604_timings[i].bt) != stdi->lcf + 1) + continue; + if (adv7604_timings[i].bt.vsync != stdi->lcvs) + continue; + + pix_clk = hfreq * htotal(&adv7604_timings[i].bt); + + if ((pix_clk < adv7604_timings[i].bt.pixelclock + 1000000) && + (pix_clk > adv7604_timings[i].bt.pixelclock - 1000000)) { + *timings = adv7604_timings[i]; + return 0; + } + } + + if (v4l2_detect_cvt(stdi->lcf + 1, hfreq, stdi->lcvs, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + timings)) + return 0; + if (v4l2_detect_gtf(stdi->lcf + 1, hfreq, stdi->lcvs, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + state->aspect_ratio, timings)) + return 0; + + v4l2_dbg(2, debug, sd, "%s: No format candidate found for lcf=%d, bl = %d\n", + __func__, stdi->lcf, stdi->bl); + return -1; +} + +static int read_stdi(struct v4l2_subdev *sd, struct stdi_readback *stdi) +{ + if (no_lock_stdi(sd) || no_lock_sspd(sd)) { + v4l2_dbg(2, debug, sd, "%s: STDI and/or SSPD not locked\n", __func__); + return -1; + } + + /* read STDI */ + stdi->bl = ((cp_read(sd, 0xb1) & 0x3f) << 8) | cp_read(sd, 0xb2); + stdi->lcf = ((cp_read(sd, 0xb3) & 0x7) << 8) | cp_read(sd, 0xb4); + stdi->lcvs = cp_read(sd, 0xb3) >> 3; + stdi->interlaced = io_read(sd, 0x12) & 0x10; + + /* read SSPD */ + if ((cp_read(sd, 0xb5) & 0x03) == 0x01) { + stdi->hs_pol = ((cp_read(sd, 0xb5) & 0x10) ? + ((cp_read(sd, 0xb5) & 0x08) ? '+' : '-') : 'x'); + stdi->vs_pol = ((cp_read(sd, 0xb5) & 0x40) ? + ((cp_read(sd, 0xb5) & 0x20) ? '+' : '-') : 'x'); + } else { + stdi->hs_pol = 'x'; + stdi->vs_pol = 'x'; + } + + if (no_lock_stdi(sd) || no_lock_sspd(sd)) { + v4l2_dbg(2, debug, sd, + "%s: signal lost during readout of STDI/SSPD\n", __func__); + return -1; + } + + if (stdi->lcf < 239 || stdi->bl < 8 || stdi->bl == 0x3fff) { + v4l2_dbg(2, debug, sd, "%s: invalid signal\n", __func__); + memset(stdi, 0, sizeof(struct stdi_readback)); + return -1; + } + + v4l2_dbg(2, debug, sd, + "%s: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %chsync, %cvsync, %s\n", + __func__, stdi->lcf, stdi->bl, stdi->lcvs, + stdi->hs_pol, stdi->vs_pol, + stdi->interlaced ? "interlaced" : "progressive"); + + return 0; +} + +static int adv7604_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->index >= ARRAY_SIZE(adv7604_timings) - 1) + return -EINVAL; + memset(timings->reserved, 0, sizeof(timings->reserved)); + timings->timings = adv7604_timings[timings->index]; + return 0; +} + +static int adv7604_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + struct adv7604_state *state = to_state(sd); + + cap->type = V4L2_DV_BT_656_1120; + cap->bt.max_width = 1920; + cap->bt.max_height = 1200; + cap->bt.min_pixelclock = 27000000; + if (DIGITAL_INPUT) + cap->bt.max_pixelclock = 225000000; + else + cap->bt.max_pixelclock = 170000000; + cap->bt.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT; + cap->bt.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM; + return 0; +} + +/* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is listed in adv7604_timings[] */ +static void adv7604_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7604_state *state = to_state(sd); + int i; + + for (i = 0; adv7604_timings[i].bt.width; i++) { + if (v4l_match_dv_timings(timings, &adv7604_timings[i], + DIGITAL_INPUT ? 250000 : 1000000)) { + *timings = adv7604_timings[i]; + break; + } + } +} + +static int adv7604_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7604_state *state = to_state(sd); + struct v4l2_bt_timings *bt = &timings->bt; + struct stdi_readback stdi; + + if (!timings) + return -EINVAL; + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + + if (no_signal(sd)) { + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return -ENOLINK; + } + + /* read STDI */ + if (read_stdi(sd, &stdi)) { + v4l2_dbg(1, debug, sd, "%s: STDI/SSPD not locked\n", __func__); + return -ENOLINK; + } + bt->interlaced = stdi.interlaced ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + + if (DIGITAL_INPUT) { + timings->type = V4L2_DV_BT_656_1120; + + bt->width = (hdmi_read(sd, 0x07) & 0x0f) * 256 + hdmi_read(sd, 0x08); + bt->height = (hdmi_read(sd, 0x09) & 0x0f) * 256 + hdmi_read(sd, 0x0a); + bt->pixelclock = (hdmi_read(sd, 0x06) * 1000000) + + ((hdmi_read(sd, 0x3b) & 0x30) >> 4) * 250000; + bt->hfrontporch = (hdmi_read(sd, 0x20) & 0x03) * 256 + + hdmi_read(sd, 0x21); + bt->hsync = (hdmi_read(sd, 0x22) & 0x03) * 256 + + hdmi_read(sd, 0x23); + bt->hbackporch = (hdmi_read(sd, 0x24) & 0x03) * 256 + + hdmi_read(sd, 0x25); + bt->vfrontporch = ((hdmi_read(sd, 0x2a) & 0x1f) * 256 + + hdmi_read(sd, 0x2b)) / 2; + bt->vsync = ((hdmi_read(sd, 0x2e) & 0x1f) * 256 + + hdmi_read(sd, 0x2f)) / 2; + bt->vbackporch = ((hdmi_read(sd, 0x32) & 0x1f) * 256 + + hdmi_read(sd, 0x33)) / 2; + bt->polarities = ((hdmi_read(sd, 0x05) & 0x10) ? V4L2_DV_VSYNC_POS_POL : 0) | + ((hdmi_read(sd, 0x05) & 0x20) ? V4L2_DV_HSYNC_POS_POL : 0); + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height += (hdmi_read(sd, 0x0b) & 0x0f) * 256 + + hdmi_read(sd, 0x0c); + bt->il_vfrontporch = ((hdmi_read(sd, 0x2c) & 0x1f) * 256 + + hdmi_read(sd, 0x2d)) / 2; + bt->il_vsync = ((hdmi_read(sd, 0x30) & 0x1f) * 256 + + hdmi_read(sd, 0x31)) / 2; + bt->vbackporch = ((hdmi_read(sd, 0x34) & 0x1f) * 256 + + hdmi_read(sd, 0x35)) / 2; + } + adv7604_fill_optional_dv_timings_fields(sd, timings); + } else { + /* find format + * Since LCVS values are inaccurate (REF_03, page 275-276), + * stdi2dv_timings() is called with lcvs +-1 if the first attempt fails. + */ + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs += 1; + v4l2_dbg(1, debug, sd, "%s: lcvs + 1 = %d\n", __func__, stdi.lcvs); + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs -= 2; + v4l2_dbg(1, debug, sd, "%s: lcvs - 1 = %d\n", __func__, stdi.lcvs); + if (stdi2dv_timings(sd, &stdi, timings)) { + v4l2_dbg(1, debug, sd, "%s: format not supported\n", __func__); + return -ERANGE; + } + } +found: + + if (no_signal(sd)) { + v4l2_dbg(1, debug, sd, "%s: signal lost during readout\n", __func__); + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + return -ENOLINK; + } + + if ((!DIGITAL_INPUT && bt->pixelclock > 170000000) || + (DIGITAL_INPUT && bt->pixelclock > 225000000)) { + v4l2_dbg(1, debug, sd, "%s: pixelclock out of range %d\n", + __func__, (u32)bt->pixelclock); + return -ERANGE; + } + + if (debug > 1) + adv7604_print_timings(sd, timings, + "adv7604_query_dv_timings:", true); + + return 0; +} + +static int adv7604_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7604_state *state = to_state(sd); + struct v4l2_bt_timings *bt; + + if (!timings) + return -EINVAL; + + bt = &timings->bt; + + if ((!DIGITAL_INPUT && bt->pixelclock > 170000000) || + (DIGITAL_INPUT && bt->pixelclock > 225000000)) { + v4l2_dbg(1, debug, sd, "%s: pixelclock out of range %d\n", + __func__, (u32)bt->pixelclock); + return -ERANGE; + } + adv7604_fill_optional_dv_timings_fields(sd, timings); + + state->timings = *timings; + + /* freerun */ + configure_free_run(sd, bt); + + set_rgb_quantization_range(sd); + + + if (debug > 1) + adv7604_print_timings(sd, timings, + "adv7604_s_dv_timings:", true); + return 0; +} + +static int adv7604_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7604_state *state = to_state(sd); + + *timings = state->timings; + return 0; +} + +static void enable_input(struct v4l2_subdev *sd, enum adv7604_prim_mode prim_mode) +{ + switch (prim_mode) { + case ADV7604_PRIM_MODE_COMP: + case ADV7604_PRIM_MODE_RGB: + /* enable */ + io_write(sd, 0x15, 0xb0); /* Disable Tristate of Pins (no audio) */ + break; + case ADV7604_PRIM_MODE_HDMI_COMP: + case ADV7604_PRIM_MODE_HDMI_GR: + /* enable */ + hdmi_write(sd, 0x1a, 0x0a); /* Unmute audio */ + hdmi_write(sd, 0x01, 0x00); /* Enable HDMI clock terminators */ + io_write(sd, 0x15, 0xa0); /* Disable Tristate of Pins */ + break; + default: + v4l2_err(sd, "%s: reserved primary mode 0x%0x\n", + __func__, prim_mode); + break; + } +} + +static void disable_input(struct v4l2_subdev *sd) +{ + /* disable */ + io_write(sd, 0x15, 0xbe); /* Tristate all outputs from video core */ + hdmi_write(sd, 0x1a, 0x1a); /* Mute audio */ + hdmi_write(sd, 0x01, 0x78); /* Disable HDMI clock terminators */ +} + +static void select_input(struct v4l2_subdev *sd, enum adv7604_prim_mode prim_mode) +{ + switch (prim_mode) { + case ADV7604_PRIM_MODE_COMP: + case ADV7604_PRIM_MODE_RGB: + /* set mode and select free run resolution */ + io_write(sd, 0x00, 0x07); /* video std */ + io_write(sd, 0x01, 0x02); /* prim mode */ + /* enable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); + + /* reset ADI recommended settings for HDMI: */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */ + hdmi_write(sd, 0x0d, 0x04); /* HDMI filter optimization */ + hdmi_write(sd, 0x3d, 0x00); /* DDC bus active pull-up control */ + hdmi_write(sd, 0x3e, 0x74); /* TMDS PLL optimization */ + hdmi_write(sd, 0x4e, 0x3b); /* TMDS PLL optimization */ + hdmi_write(sd, 0x57, 0x74); /* TMDS PLL optimization */ + hdmi_write(sd, 0x58, 0x63); /* TMDS PLL optimization */ + hdmi_write(sd, 0x8d, 0x18); /* equaliser */ + hdmi_write(sd, 0x8e, 0x34); /* equaliser */ + hdmi_write(sd, 0x93, 0x88); /* equaliser */ + hdmi_write(sd, 0x94, 0x2e); /* equaliser */ + hdmi_write(sd, 0x96, 0x00); /* enable automatic EQ changing */ + + afe_write(sd, 0x00, 0x08); /* power up ADC */ + afe_write(sd, 0x01, 0x06); /* power up Analog Front End */ + afe_write(sd, 0xc8, 0x00); /* phase control */ + + /* set ADI recommended settings for digitizer */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 17. */ + afe_write(sd, 0x12, 0x7b); /* ADC noise shaping filter controls */ + afe_write(sd, 0x0c, 0x1f); /* CP core gain controls */ + cp_write(sd, 0x3e, 0x04); /* CP core pre-gain control */ + cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */ + cp_write(sd, 0x40, 0x5c); /* CP core pre-gain control. Graphics mode */ + break; + + case ADV7604_PRIM_MODE_HDMI_COMP: + case ADV7604_PRIM_MODE_HDMI_GR: + /* set mode and select free run resolution */ + /* video std */ + io_write(sd, 0x00, + (prim_mode == ADV7604_PRIM_MODE_HDMI_GR) ? 0x02 : 0x1e); + io_write(sd, 0x01, prim_mode); /* prim mode */ + /* disable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x00); + + /* set ADI recommended settings for HDMI: */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */ + hdmi_write(sd, 0x0d, 0x84); /* HDMI filter optimization */ + hdmi_write(sd, 0x3d, 0x10); /* DDC bus active pull-up control */ + hdmi_write(sd, 0x3e, 0x39); /* TMDS PLL optimization */ + hdmi_write(sd, 0x4e, 0x3b); /* TMDS PLL optimization */ + hdmi_write(sd, 0x57, 0xb6); /* TMDS PLL optimization */ + hdmi_write(sd, 0x58, 0x03); /* TMDS PLL optimization */ + hdmi_write(sd, 0x8d, 0x18); /* equaliser */ + hdmi_write(sd, 0x8e, 0x34); /* equaliser */ + hdmi_write(sd, 0x93, 0x8b); /* equaliser */ + hdmi_write(sd, 0x94, 0x2d); /* equaliser */ + hdmi_write(sd, 0x96, 0x01); /* enable automatic EQ changing */ + + afe_write(sd, 0x00, 0xff); /* power down ADC */ + afe_write(sd, 0x01, 0xfe); /* power down Analog Front End */ + afe_write(sd, 0xc8, 0x40); /* phase control */ + + /* reset ADI recommended settings for digitizer */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 17. */ + afe_write(sd, 0x12, 0xfb); /* ADC noise shaping filter controls */ + afe_write(sd, 0x0c, 0x0d); /* CP core gain controls */ + cp_write(sd, 0x3e, 0x00); /* CP core pre-gain control */ + cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */ + cp_write(sd, 0x40, 0x80); /* CP core pre-gain control. Graphics mode */ + + break; + default: + v4l2_err(sd, "%s: reserved primary mode 0x%0x\n", __func__, prim_mode); + break; + } +} + +static int adv7604_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7604_state *state = to_state(sd); + + v4l2_dbg(2, debug, sd, "%s: input %d", __func__, input); + + switch (input) { + case 0: + /* TODO select HDMI_COMP or HDMI_GR */ + state->prim_mode = ADV7604_PRIM_MODE_HDMI_COMP; + break; + case 1: + state->prim_mode = ADV7604_PRIM_MODE_RGB; + break; + case 2: + state->prim_mode = ADV7604_PRIM_MODE_COMP; + break; + default: + return -EINVAL; + } + + disable_input(sd); + + select_input(sd, state->prim_mode); + + enable_input(sd, state->prim_mode); + + return 0; +} + +static int adv7604_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index) + return -EINVAL; + /* Good enough for now */ + *code = V4L2_MBUS_FMT_FIXED; + return 0; +} + +static int adv7604_g_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct adv7604_state *state = to_state(sd); + + fmt->width = state->timings.bt.width; + fmt->height = state->timings.bt.height; + fmt->code = V4L2_MBUS_FMT_FIXED; + fmt->field = V4L2_FIELD_NONE; + if (state->timings.bt.standards & V4L2_DV_BT_STD_CEA861) { + fmt->colorspace = (state->timings.bt.height <= 576) ? + V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; + } + return 0; +} + +static int adv7604_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct adv7604_state *state = to_state(sd); + u8 fmt_change, fmt_change_digital, tx_5v; + + /* format change */ + fmt_change = io_read(sd, 0x43) & 0x98; + if (fmt_change) + io_write(sd, 0x44, fmt_change); + fmt_change_digital = DIGITAL_INPUT ? (io_read(sd, 0x6b) & 0xc0) : 0; + if (fmt_change_digital) + io_write(sd, 0x6c, fmt_change_digital); + if (fmt_change || fmt_change_digital) { + v4l2_dbg(1, debug, sd, + "%s: ADV7604_FMT_CHANGE, fmt_change = 0x%x, fmt_change_digital = 0x%x\n", + __func__, fmt_change, fmt_change_digital); + v4l2_subdev_notify(sd, ADV7604_FMT_CHANGE, NULL); + if (handled) + *handled = true; + } + /* tx 5v detect */ + tx_5v = io_read(sd, 0x70) & 0x10; + if (tx_5v) { + v4l2_dbg(1, debug, sd, "%s: tx_5v: 0x%x\n", __func__, tx_5v); + io_write(sd, 0x71, tx_5v); + adv7604_s_detect_tx_5v_ctrl(sd); + if (handled) + *handled = true; + } + return 0; +} + +static int adv7604_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) +{ + struct adv7604_state *state = to_state(sd); + + if (edid->pad != 0) + return -EINVAL; + if (edid->blocks == 0) + return -EINVAL; + if (edid->start_block >= state->edid_blocks) + return -EINVAL; + if (edid->start_block + edid->blocks > state->edid_blocks) + edid->blocks = state->edid_blocks - edid->start_block; + if (!edid->edid) + return -EINVAL; + memcpy(edid->edid + edid->start_block * 128, + state->edid + edid->start_block * 128, + edid->blocks * 128); + return 0; +} + +static int adv7604_set_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) +{ + struct adv7604_state *state = to_state(sd); + int err; + + if (edid->pad != 0) + return -EINVAL; + if (edid->start_block != 0) + return -EINVAL; + if (edid->blocks == 0) { + /* Pull down the hotplug pin */ + v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)0); + /* Disables I2C access to internal EDID ram from DDC port */ + rep_write_and_or(sd, 0x77, 0xf0, 0x0); + state->edid_blocks = 0; + /* Fall back to a 16:9 aspect ratio */ + state->aspect_ratio.numerator = 16; + state->aspect_ratio.denominator = 9; + return 0; + } + if (edid->blocks > 2) + return -E2BIG; + if (!edid->edid) + return -EINVAL; + memcpy(state->edid, edid->edid, 128 * edid->blocks); + state->edid_blocks = edid->blocks; + state->aspect_ratio = v4l2_calc_aspect_ratio(edid->edid[0x15], + edid->edid[0x16]); + err = edid_write_block(sd, 128 * edid->blocks, state->edid); + if (err < 0) + v4l2_err(sd, "error %d writing edid\n", err); + return err; +} + +/*********** avi info frame CEA-861-E **************/ + +static void print_avi_infoframe(struct v4l2_subdev *sd) +{ + int i; + u8 buf[14]; + u8 avi_len; + u8 avi_ver; + + if (!(hdmi_read(sd, 0x05) & 0x80)) { + v4l2_info(sd, "receive DVI-D signal (AVI infoframe not supported)\n"); + return; + } + if (!(io_read(sd, 0x60) & 0x01)) { + v4l2_info(sd, "AVI infoframe not received\n"); + return; + } + + if (io_read(sd, 0x83) & 0x01) { + v4l2_info(sd, "AVI infoframe checksum error has occurred earlier\n"); + io_write(sd, 0x85, 0x01); /* clear AVI_INF_CKS_ERR_RAW */ + if (io_read(sd, 0x83) & 0x01) { + v4l2_info(sd, "AVI infoframe checksum error still present\n"); + io_write(sd, 0x85, 0x01); /* clear AVI_INF_CKS_ERR_RAW */ + } + } + + avi_len = infoframe_read(sd, 0xe2); + avi_ver = infoframe_read(sd, 0xe1); + v4l2_info(sd, "AVI infoframe version %d (%d byte)\n", + avi_ver, avi_len); + + if (avi_ver != 0x02) + return; + + for (i = 0; i < 14; i++) + buf[i] = infoframe_read(sd, i); + + v4l2_info(sd, + "\t%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], buf[12], buf[13]); +} + +static int adv7604_log_status(struct v4l2_subdev *sd) +{ + struct adv7604_state *state = to_state(sd); + struct v4l2_dv_timings timings; + struct stdi_readback stdi; + u8 reg_io_0x02 = io_read(sd, 0x02); + + char *csc_coeff_sel_rb[16] = { + "bypassed", "YPbPr601 -> RGB", "reserved", "YPbPr709 -> RGB", + "reserved", "RGB -> YPbPr601", "reserved", "RGB -> YPbPr709", + "reserved", "YPbPr709 -> YPbPr601", "YPbPr601 -> YPbPr709", + "reserved", "reserved", "reserved", "reserved", "manual" + }; + char *input_color_space_txt[16] = { + "RGB limited range (16-235)", "RGB full range (0-255)", + "YCbCr Bt.601 (16-235)", "YCbCr Bt.709 (16-235)", + "XvYCC Bt.601", "XvYCC Bt.709", + "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", + "invalid", "invalid", "invalid", "invalid", "invalid", + "invalid", "invalid", "automatic" + }; + char *rgb_quantization_range_txt[] = { + "Automatic", + "RGB limited range (16-235)", + "RGB full range (0-255)", + }; + + v4l2_info(sd, "-----Chip status-----\n"); + v4l2_info(sd, "Chip power: %s\n", no_power(sd) ? "off" : "on"); + v4l2_info(sd, "Connector type: %s\n", state->connector_hdmi ? + "HDMI" : (DIGITAL_INPUT ? "DVI-D" : "DVI-A")); + v4l2_info(sd, "EDID: %s\n", ((rep_read(sd, 0x7d) & 0x01) && + (rep_read(sd, 0x77) & 0x01)) ? "enabled" : "disabled "); + v4l2_info(sd, "CEC: %s\n", !!(cec_read(sd, 0x2a) & 0x01) ? + "enabled" : "disabled"); + + v4l2_info(sd, "-----Signal status-----\n"); + v4l2_info(sd, "Cable detected (+5V power): %s\n", + (io_read(sd, 0x6f) & 0x10) ? "true" : "false"); + v4l2_info(sd, "TMDS signal detected: %s\n", + no_signal_tmds(sd) ? "false" : "true"); + v4l2_info(sd, "TMDS signal locked: %s\n", + no_lock_tmds(sd) ? "false" : "true"); + v4l2_info(sd, "SSPD locked: %s\n", no_lock_sspd(sd) ? "false" : "true"); + v4l2_info(sd, "STDI locked: %s\n", no_lock_stdi(sd) ? "false" : "true"); + v4l2_info(sd, "CP locked: %s\n", no_lock_cp(sd) ? "false" : "true"); + v4l2_info(sd, "CP free run: %s\n", + (!!(cp_read(sd, 0xff) & 0x10) ? "on" : "off")); + v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x\n", + io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f); + + v4l2_info(sd, "-----Video Timings-----\n"); + if (read_stdi(sd, &stdi)) + v4l2_info(sd, "STDI: not locked\n"); + else + v4l2_info(sd, "STDI: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %s, %chsync, %cvsync\n", + stdi.lcf, stdi.bl, stdi.lcvs, + stdi.interlaced ? "interlaced" : "progressive", + stdi.hs_pol, stdi.vs_pol); + if (adv7604_query_dv_timings(sd, &timings)) + v4l2_info(sd, "No video detected\n"); + else + adv7604_print_timings(sd, &timings, "Detected format:", true); + adv7604_print_timings(sd, &state->timings, "Configured format:", true); + + v4l2_info(sd, "-----Color space-----\n"); + v4l2_info(sd, "RGB quantization range ctrl: %s\n", + rgb_quantization_range_txt[state->rgb_quantization_range]); + v4l2_info(sd, "Input color space: %s\n", + input_color_space_txt[reg_io_0x02 >> 4]); + v4l2_info(sd, "Output color space: %s %s, saturator %s\n", + (reg_io_0x02 & 0x02) ? "RGB" : "YCbCr", + (reg_io_0x02 & 0x04) ? "(16-235)" : "(0-255)", + ((reg_io_0x02 & 0x04) ^ (reg_io_0x02 & 0x01)) ? + "enabled" : "disabled"); + v4l2_info(sd, "Color space conversion: %s\n", + csc_coeff_sel_rb[cp_read(sd, 0xfc) >> 4]); + + /* Digital video */ + if (DIGITAL_INPUT) { + v4l2_info(sd, "-----HDMI status-----\n"); + v4l2_info(sd, "HDCP encrypted content: %s\n", + hdmi_read(sd, 0x05) & 0x40 ? "true" : "false"); + + print_avi_infoframe(sd); + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops adv7604_ctrl_ops = { + .s_ctrl = adv7604_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7604_core_ops = { + .log_status = adv7604_log_status, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, + .g_chip_ident = adv7604_g_chip_ident, + .interrupt_service_routine = adv7604_isr, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7604_g_register, + .s_register = adv7604_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops adv7604_video_ops = { + .s_routing = adv7604_s_routing, + .g_input_status = adv7604_g_input_status, + .s_dv_timings = adv7604_s_dv_timings, + .g_dv_timings = adv7604_g_dv_timings, + .query_dv_timings = adv7604_query_dv_timings, + .enum_dv_timings = adv7604_enum_dv_timings, + .dv_timings_cap = adv7604_dv_timings_cap, + .enum_mbus_fmt = adv7604_enum_mbus_fmt, + .g_mbus_fmt = adv7604_g_mbus_fmt, + .try_mbus_fmt = adv7604_g_mbus_fmt, + .s_mbus_fmt = adv7604_g_mbus_fmt, +}; + +static const struct v4l2_subdev_pad_ops adv7604_pad_ops = { + .get_edid = adv7604_get_edid, + .set_edid = adv7604_set_edid, +}; + +static const struct v4l2_subdev_ops adv7604_ops = { + .core = &adv7604_core_ops, + .video = &adv7604_video_ops, + .pad = &adv7604_pad_ops, +}; + +/* -------------------------- custom ctrls ---------------------------------- */ + +static const struct v4l2_ctrl_config adv7604_ctrl_analog_sampling_phase = { + .ops = &adv7604_ctrl_ops, + .id = V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE, + .name = "Analog Sampling Phase", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 0x1f, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config adv7604_ctrl_free_run_color_manual = { + .ops = &adv7604_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL, + .name = "Free Running Color, Manual", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = false, +}; + +static const struct v4l2_ctrl_config adv7604_ctrl_free_run_color = { + .ops = &adv7604_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR, + .name = "Free Running Color", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0x0, + .max = 0xffffff, + .step = 0x1, + .def = 0x0, +}; + +/* ----------------------------------------------------------------------- */ + +static int adv7604_core_init(struct v4l2_subdev *sd) +{ + struct adv7604_state *state = to_state(sd); + struct adv7604_platform_data *pdata = &state->pdata; + + hdmi_write(sd, 0x48, + (pdata->disable_pwrdnb ? 0x80 : 0) | + (pdata->disable_cable_det_rst ? 0x40 : 0)); + + disable_input(sd); + + /* power */ + io_write(sd, 0x0c, 0x42); /* Power up part and power down VDP */ + io_write(sd, 0x0b, 0x44); /* Power down ESDP block */ + cp_write(sd, 0xcf, 0x01); /* Power down macrovision */ + + /* video format */ + io_write_and_or(sd, 0x02, 0xf0, + pdata->alt_gamma << 3 | + pdata->op_656_range << 2 | + pdata->rgb_out << 1 | + pdata->alt_data_sat << 0); + io_write(sd, 0x03, pdata->op_format_sel); + io_write_and_or(sd, 0x04, 0x1f, pdata->op_ch_sel << 5); + io_write_and_or(sd, 0x05, 0xf0, pdata->blank_data << 3 | + pdata->insert_av_codes << 2 | + pdata->replicate_av_codes << 1 | + pdata->invert_cbcr << 0); + + /* TODO from platform data */ + cp_write(sd, 0x69, 0x30); /* Enable CP CSC */ + io_write(sd, 0x06, 0xa6); /* positive VS and HS */ + io_write(sd, 0x14, 0x7f); /* Drive strength adjusted to max */ + cp_write(sd, 0xba, (pdata->hdmi_free_run_mode << 1) | 0x01); /* HDMI free run */ + cp_write(sd, 0xf3, 0xdc); /* Low threshold to enter/exit free run mode */ + cp_write(sd, 0xf9, 0x23); /* STDI ch. 1 - LCVS change threshold - + ADI recommended setting [REF_01 c. 2.3.3] */ + cp_write(sd, 0x45, 0x23); /* STDI ch. 2 - LCVS change threshold - + ADI recommended setting [REF_01 c. 2.3.3] */ + cp_write(sd, 0xc9, 0x2d); /* use prim_mode and vid_std as free run resolution + for digital formats */ + + /* TODO from platform data */ + afe_write(sd, 0xb5, 0x01); /* Setting MCLK to 256Fs */ + + afe_write(sd, 0x02, pdata->ain_sel); /* Select analog input muxing mode */ + io_write_and_or(sd, 0x30, ~(1 << 4), pdata->output_bus_lsb_to_msb << 4); + + state->prim_mode = pdata->prim_mode; + select_input(sd, pdata->prim_mode); + + enable_input(sd, pdata->prim_mode); + + /* interrupts */ + io_write(sd, 0x40, 0xc2); /* Configure INT1 */ + io_write(sd, 0x41, 0xd7); /* STDI irq for any change, disable INT2 */ + io_write(sd, 0x46, 0x98); /* Enable SSPD, STDI and CP unlocked interrupts */ + io_write(sd, 0x6e, 0xc0); /* Enable V_LOCKED and DE_REGEN_LCK interrupts */ + io_write(sd, 0x73, 0x10); /* Enable CABLE_DET_A_ST (+5v) interrupt */ + + return v4l2_ctrl_handler_setup(sd->ctrl_handler); +} + +static void adv7604_unregister_clients(struct adv7604_state *state) +{ + if (state->i2c_avlink) + i2c_unregister_device(state->i2c_avlink); + if (state->i2c_cec) + i2c_unregister_device(state->i2c_cec); + if (state->i2c_infoframe) + i2c_unregister_device(state->i2c_infoframe); + if (state->i2c_esdp) + i2c_unregister_device(state->i2c_esdp); + if (state->i2c_dpp) + i2c_unregister_device(state->i2c_dpp); + if (state->i2c_afe) + i2c_unregister_device(state->i2c_afe); + if (state->i2c_repeater) + i2c_unregister_device(state->i2c_repeater); + if (state->i2c_edid) + i2c_unregister_device(state->i2c_edid); + if (state->i2c_hdmi) + i2c_unregister_device(state->i2c_hdmi); + if (state->i2c_test) + i2c_unregister_device(state->i2c_test); + if (state->i2c_cp) + i2c_unregister_device(state->i2c_cp); + if (state->i2c_vdp) + i2c_unregister_device(state->i2c_vdp); +} + +static struct i2c_client *adv7604_dummy_client(struct v4l2_subdev *sd, + u8 addr, u8 io_reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (addr) + io_write(sd, io_reg, addr << 1); + return i2c_new_dummy(client->adapter, io_read(sd, io_reg) >> 1); +} + +static int adv7604_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adv7604_state *state; + struct adv7604_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; + struct v4l2_subdev *sd; + int err; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + v4l_dbg(1, debug, client, "detecting adv7604 client on address 0x%x\n", + client->addr << 1); + + state = kzalloc(sizeof(struct adv7604_state), GFP_KERNEL); + if (!state) { + v4l_err(client, "Could not allocate adv7604_state memory!\n"); + return -ENOMEM; + } + + /* platform data */ + if (!pdata) { + v4l_err(client, "No platform data!\n"); + err = -ENODEV; + goto err_state; + } + memcpy(&state->pdata, pdata, sizeof(state->pdata)); + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &adv7604_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + state->connector_hdmi = pdata->connector_hdmi; + + /* i2c access to adv7604? */ + if (adv_smbus_read_byte_data_check(client, 0xfb, false) != 0x68) { + v4l2_info(sd, "not an adv7604 on address 0x%x\n", + client->addr << 1); + err = -ENODEV; + goto err_state; + } + + /* control handlers */ + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 9); + + v4l2_ctrl_new_std(hdl, &adv7604_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &adv7604_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv7604_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv7604_ctrl_ops, + V4L2_CID_HUE, 0, 128, 1, 0); + + /* private controls */ + state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0); + state->detect_tx_5v_ctrl->is_private = true; + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv7604_ctrl_ops, + V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + state->rgb_quantization_range_ctrl->is_private = true; + + /* custom controls */ + state->analog_sampling_phase_ctrl = + v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_analog_sampling_phase, NULL); + state->analog_sampling_phase_ctrl->is_private = true; + state->free_run_color_manual_ctrl = + v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_free_run_color_manual, NULL); + state->free_run_color_manual_ctrl->is_private = true; + state->free_run_color_ctrl = + v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_free_run_color, NULL); + state->free_run_color_ctrl->is_private = true; + + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + if (adv7604_s_detect_tx_5v_ctrl(sd)) { + err = -ENODEV; + goto err_hdl; + } + + state->i2c_avlink = adv7604_dummy_client(sd, pdata->i2c_avlink, 0xf3); + state->i2c_cec = adv7604_dummy_client(sd, pdata->i2c_cec, 0xf4); + state->i2c_infoframe = adv7604_dummy_client(sd, pdata->i2c_infoframe, 0xf5); + state->i2c_esdp = adv7604_dummy_client(sd, pdata->i2c_esdp, 0xf6); + state->i2c_dpp = adv7604_dummy_client(sd, pdata->i2c_dpp, 0xf7); + state->i2c_afe = adv7604_dummy_client(sd, pdata->i2c_afe, 0xf8); + state->i2c_repeater = adv7604_dummy_client(sd, pdata->i2c_repeater, 0xf9); + state->i2c_edid = adv7604_dummy_client(sd, pdata->i2c_edid, 0xfa); + state->i2c_hdmi = adv7604_dummy_client(sd, pdata->i2c_hdmi, 0xfb); + state->i2c_test = adv7604_dummy_client(sd, pdata->i2c_test, 0xfc); + state->i2c_cp = adv7604_dummy_client(sd, pdata->i2c_cp, 0xfd); + state->i2c_vdp = adv7604_dummy_client(sd, pdata->i2c_vdp, 0xfe); + if (!state->i2c_avlink || !state->i2c_cec || !state->i2c_infoframe || + !state->i2c_esdp || !state->i2c_dpp || !state->i2c_afe || + !state->i2c_repeater || !state->i2c_edid || !state->i2c_hdmi || + !state->i2c_test || !state->i2c_cp || !state->i2c_vdp) { + err = -ENOMEM; + v4l2_err(sd, "failed to create all i2c clients\n"); + goto err_i2c; + } + + /* work queues */ + state->work_queues = create_singlethread_workqueue(client->name); + if (!state->work_queues) { + v4l2_err(sd, "Could not create work queue\n"); + err = -ENOMEM; + goto err_i2c; + } + + INIT_DELAYED_WORK(&state->delayed_work_enable_hotplug, + adv7604_delayed_work_enable_hotplug); + + state->pad.flags = MEDIA_PAD_FL_SOURCE; + err = media_entity_init(&sd->entity, 1, &state->pad, 0); + if (err) + goto err_work_queues; + + err = adv7604_core_init(sd); + if (err) + goto err_entity; + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + return 0; + +err_entity: + media_entity_cleanup(&sd->entity); +err_work_queues: + cancel_delayed_work(&state->delayed_work_enable_hotplug); + destroy_workqueue(state->work_queues); +err_i2c: + adv7604_unregister_clients(state); +err_hdl: + v4l2_ctrl_handler_free(hdl); +err_state: + kfree(state); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static int adv7604_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7604_state *state = to_state(sd); + + cancel_delayed_work(&state->delayed_work_enable_hotplug); + destroy_workqueue(state->work_queues); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + adv7604_unregister_clients(to_state(sd)); + v4l2_ctrl_handler_free(sd->ctrl_handler); + kfree(to_state(sd)); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_device_id adv7604_id[] = { + { "adv7604", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7604_id); + +static struct i2c_driver adv7604_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "adv7604", + }, + .probe = adv7604_probe, + .remove = adv7604_remove, + .id_table = adv7604_id, +}; + +module_i2c_driver(adv7604_driver); diff --git a/drivers/media/video/ak881x.c b/drivers/media/i2c/ak881x.c similarity index 100% rename from drivers/media/video/ak881x.c rename to drivers/media/i2c/ak881x.c diff --git a/drivers/media/video/aptina-pll.c b/drivers/media/i2c/aptina-pll.c similarity index 100% rename from drivers/media/video/aptina-pll.c rename to drivers/media/i2c/aptina-pll.c diff --git a/drivers/media/video/aptina-pll.h b/drivers/media/i2c/aptina-pll.h similarity index 100% rename from drivers/media/video/aptina-pll.h rename to drivers/media/i2c/aptina-pll.h diff --git a/drivers/media/i2c/as3645a.c b/drivers/media/i2c/as3645a.c new file mode 100644 index 000000000000..3bfdbf9d9bf1 --- /dev/null +++ b/drivers/media/i2c/as3645a.c @@ -0,0 +1,888 @@ +/* + * drivers/media/i2c/as3645a.c - AS3645A and LM3555 flash controllers driver + * + * Copyright (C) 2008-2011 Nokia Corporation + * Copyright (c) 2011, Intel Corporation. + * + * Contact: Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * TODO: + * - Check hardware FSTROBE control when sensor driver add support for this + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AS_TIMER_MS_TO_CODE(t) (((t) - 100) / 50) +#define AS_TIMER_CODE_TO_MS(c) (50 * (c) + 100) + +/* Register definitions */ + +/* Read-only Design info register: Reset state: xxxx 0001 */ +#define AS_DESIGN_INFO_REG 0x00 +#define AS_DESIGN_INFO_FACTORY(x) (((x) >> 4)) +#define AS_DESIGN_INFO_MODEL(x) ((x) & 0x0f) + +/* Read-only Version control register: Reset state: 0000 0000 + * for first engineering samples + */ +#define AS_VERSION_CONTROL_REG 0x01 +#define AS_VERSION_CONTROL_RFU(x) (((x) >> 4)) +#define AS_VERSION_CONTROL_VERSION(x) ((x) & 0x0f) + +/* Read / Write (Indicator and timer register): Reset state: 0000 1111 */ +#define AS_INDICATOR_AND_TIMER_REG 0x02 +#define AS_INDICATOR_AND_TIMER_TIMEOUT_SHIFT 0 +#define AS_INDICATOR_AND_TIMER_VREF_SHIFT 4 +#define AS_INDICATOR_AND_TIMER_INDICATOR_SHIFT 6 + +/* Read / Write (Current set register): Reset state: 0110 1001 */ +#define AS_CURRENT_SET_REG 0x03 +#define AS_CURRENT_ASSIST_LIGHT_SHIFT 0 +#define AS_CURRENT_LED_DET_ON (1 << 3) +#define AS_CURRENT_FLASH_CURRENT_SHIFT 4 + +/* Read / Write (Control register): Reset state: 1011 0100 */ +#define AS_CONTROL_REG 0x04 +#define AS_CONTROL_MODE_SETTING_SHIFT 0 +#define AS_CONTROL_STROBE_ON (1 << 2) +#define AS_CONTROL_OUT_ON (1 << 3) +#define AS_CONTROL_EXT_TORCH_ON (1 << 4) +#define AS_CONTROL_STROBE_TYPE_EDGE (0 << 5) +#define AS_CONTROL_STROBE_TYPE_LEVEL (1 << 5) +#define AS_CONTROL_COIL_PEAK_SHIFT 6 + +/* Read only (D3 is read / write) (Fault and info): Reset state: 0000 x000 */ +#define AS_FAULT_INFO_REG 0x05 +#define AS_FAULT_INFO_INDUCTOR_PEAK_LIMIT (1 << 1) +#define AS_FAULT_INFO_INDICATOR_LED (1 << 2) +#define AS_FAULT_INFO_LED_AMOUNT (1 << 3) +#define AS_FAULT_INFO_TIMEOUT (1 << 4) +#define AS_FAULT_INFO_OVER_TEMPERATURE (1 << 5) +#define AS_FAULT_INFO_SHORT_CIRCUIT (1 << 6) +#define AS_FAULT_INFO_OVER_VOLTAGE (1 << 7) + +/* Boost register */ +#define AS_BOOST_REG 0x0d +#define AS_BOOST_CURRENT_DISABLE (0 << 0) +#define AS_BOOST_CURRENT_ENABLE (1 << 0) + +/* Password register is used to unlock boost register writing */ +#define AS_PASSWORD_REG 0x0f +#define AS_PASSWORD_UNLOCK_VALUE 0x55 + +enum as_mode { + AS_MODE_EXT_TORCH = 0 << AS_CONTROL_MODE_SETTING_SHIFT, + AS_MODE_INDICATOR = 1 << AS_CONTROL_MODE_SETTING_SHIFT, + AS_MODE_ASSIST = 2 << AS_CONTROL_MODE_SETTING_SHIFT, + AS_MODE_FLASH = 3 << AS_CONTROL_MODE_SETTING_SHIFT, +}; + +/* + * struct as3645a + * + * @subdev: V4L2 subdev + * @pdata: Flash platform data + * @power_lock: Protects power_count + * @power_count: Power reference count + * @led_mode: V4L2 flash LED mode + * @timeout: Flash timeout in microseconds + * @flash_current: Flash current (0=200mA ... 15=500mA). Maximum + * values are 400mA for two LEDs and 500mA for one LED. + * @assist_current: Torch/Assist light current (0=20mA, 1=40mA ... 7=160mA) + * @indicator_current: Indicator LED current (0=0mA, 1=2.5mA ... 4=10mA) + * @strobe_source: Flash strobe source (software or external) + */ +struct as3645a { + struct v4l2_subdev subdev; + const struct as3645a_platform_data *pdata; + + struct mutex power_lock; + int power_count; + + /* Controls */ + struct v4l2_ctrl_handler ctrls; + + enum v4l2_flash_led_mode led_mode; + unsigned int timeout; + u8 flash_current; + u8 assist_current; + u8 indicator_current; + enum v4l2_flash_strobe_source strobe_source; +}; + +#define to_as3645a(sd) container_of(sd, struct as3645a, subdev) + +/* Return negative errno else zero on success */ +static int as3645a_write(struct as3645a *flash, u8 addr, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int rval; + + rval = i2c_smbus_write_byte_data(client, addr, val); + + dev_dbg(&client->dev, "Write Addr:%02X Val:%02X %s\n", addr, val, + rval < 0 ? "fail" : "ok"); + + return rval; +} + +/* Return negative errno else a data byte received from the device. */ +static int as3645a_read(struct as3645a *flash, u8 addr) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int rval; + + rval = i2c_smbus_read_byte_data(client, addr); + + dev_dbg(&client->dev, "Read Addr:%02X Val:%02X %s\n", addr, rval, + rval < 0 ? "fail" : "ok"); + + return rval; +} + +/* ----------------------------------------------------------------------------- + * Hardware configuration and trigger + */ + +/* + * as3645a_set_config - Set flash configuration registers + * @flash: The flash + * + * Configure the hardware with flash, assist and indicator currents, as well as + * flash timeout. + * + * Return 0 on success, or a negative error code if an I2C communication error + * occurred. + */ +static int as3645a_set_config(struct as3645a *flash) +{ + int ret; + u8 val; + + val = (flash->flash_current << AS_CURRENT_FLASH_CURRENT_SHIFT) + | (flash->assist_current << AS_CURRENT_ASSIST_LIGHT_SHIFT) + | AS_CURRENT_LED_DET_ON; + + ret = as3645a_write(flash, AS_CURRENT_SET_REG, val); + if (ret < 0) + return ret; + + val = AS_TIMER_MS_TO_CODE(flash->timeout / 1000) + << AS_INDICATOR_AND_TIMER_TIMEOUT_SHIFT; + + val |= (flash->pdata->vref << AS_INDICATOR_AND_TIMER_VREF_SHIFT) + | ((flash->indicator_current ? flash->indicator_current - 1 : 0) + << AS_INDICATOR_AND_TIMER_INDICATOR_SHIFT); + + return as3645a_write(flash, AS_INDICATOR_AND_TIMER_REG, val); +} + +/* + * as3645a_set_control - Set flash control register + * @flash: The flash + * @mode: Desired output mode + * @on: Desired output state + * + * Configure the hardware with output mode and state. + * + * Return 0 on success, or a negative error code if an I2C communication error + * occurred. + */ +static int +as3645a_set_control(struct as3645a *flash, enum as_mode mode, bool on) +{ + u8 reg; + + /* Configure output parameters and operation mode. */ + reg = (flash->pdata->peak << AS_CONTROL_COIL_PEAK_SHIFT) + | (on ? AS_CONTROL_OUT_ON : 0) + | mode; + + if (flash->led_mode == V4L2_FLASH_LED_MODE_FLASH && + flash->strobe_source == V4L2_FLASH_STROBE_SOURCE_EXTERNAL) { + reg |= AS_CONTROL_STROBE_TYPE_LEVEL + | AS_CONTROL_STROBE_ON; + } + + return as3645a_write(flash, AS_CONTROL_REG, reg); +} + +/* + * as3645a_set_output - Configure output and operation mode + * @flash: Flash controller + * @strobe: Strobe the flash (only valid in flash mode) + * + * Turn the LEDs output on/off and set the operation mode based on the current + * parameters. + * + * The AS3645A can't control the indicator LED independently of the flash/torch + * LED. If the flash controller is in V4L2_FLASH_LED_MODE_NONE mode, set the + * chip to indicator mode. Otherwise set it to assist light (torch) or flash + * mode. + * + * In indicator and assist modes, turn the output on/off based on the indicator + * and torch currents. In software strobe flash mode, turn the output on/off + * based on the strobe parameter. + */ +static int as3645a_set_output(struct as3645a *flash, bool strobe) +{ + enum as_mode mode; + bool on; + + switch (flash->led_mode) { + case V4L2_FLASH_LED_MODE_NONE: + on = flash->indicator_current != 0; + mode = AS_MODE_INDICATOR; + break; + case V4L2_FLASH_LED_MODE_TORCH: + on = true; + mode = AS_MODE_ASSIST; + break; + case V4L2_FLASH_LED_MODE_FLASH: + on = strobe; + mode = AS_MODE_FLASH; + break; + default: + BUG(); + } + + /* Configure output parameters and operation mode. */ + return as3645a_set_control(flash, mode, on); +} + +/* ----------------------------------------------------------------------------- + * V4L2 controls + */ + +static int as3645a_is_active(struct as3645a *flash) +{ + int ret; + + ret = as3645a_read(flash, AS_CONTROL_REG); + return ret < 0 ? ret : !!(ret & AS_CONTROL_OUT_ON); +} + +static int as3645a_read_fault(struct as3645a *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int rval; + + /* NOTE: reading register clear fault status */ + rval = as3645a_read(flash, AS_FAULT_INFO_REG); + if (rval < 0) + return rval; + + if (rval & AS_FAULT_INFO_INDUCTOR_PEAK_LIMIT) + dev_dbg(&client->dev, "Inductor Peak limit fault\n"); + + if (rval & AS_FAULT_INFO_INDICATOR_LED) + dev_dbg(&client->dev, "Indicator LED fault: " + "Short circuit or open loop\n"); + + dev_dbg(&client->dev, "%u connected LEDs\n", + rval & AS_FAULT_INFO_LED_AMOUNT ? 2 : 1); + + if (rval & AS_FAULT_INFO_TIMEOUT) + dev_dbg(&client->dev, "Timeout fault\n"); + + if (rval & AS_FAULT_INFO_OVER_TEMPERATURE) + dev_dbg(&client->dev, "Over temperature fault\n"); + + if (rval & AS_FAULT_INFO_SHORT_CIRCUIT) + dev_dbg(&client->dev, "Short circuit fault\n"); + + if (rval & AS_FAULT_INFO_OVER_VOLTAGE) + dev_dbg(&client->dev, "Over voltage fault: " + "Indicates missing capacitor or open connection\n"); + + return rval; +} + +static int as3645a_get_ctrl(struct v4l2_ctrl *ctrl) +{ + struct as3645a *flash = + container_of(ctrl->handler, struct as3645a, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int value; + + switch (ctrl->id) { + case V4L2_CID_FLASH_FAULT: + value = as3645a_read_fault(flash); + if (value < 0) + return value; + + ctrl->cur.val = 0; + if (value & AS_FAULT_INFO_SHORT_CIRCUIT) + ctrl->cur.val |= V4L2_FLASH_FAULT_SHORT_CIRCUIT; + if (value & AS_FAULT_INFO_OVER_TEMPERATURE) + ctrl->cur.val |= V4L2_FLASH_FAULT_OVER_TEMPERATURE; + if (value & AS_FAULT_INFO_TIMEOUT) + ctrl->cur.val |= V4L2_FLASH_FAULT_TIMEOUT; + if (value & AS_FAULT_INFO_OVER_VOLTAGE) + ctrl->cur.val |= V4L2_FLASH_FAULT_OVER_VOLTAGE; + if (value & AS_FAULT_INFO_INDUCTOR_PEAK_LIMIT) + ctrl->cur.val |= V4L2_FLASH_FAULT_OVER_CURRENT; + if (value & AS_FAULT_INFO_INDICATOR_LED) + ctrl->cur.val |= V4L2_FLASH_FAULT_INDICATOR; + break; + + case V4L2_CID_FLASH_STROBE_STATUS: + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) { + ctrl->cur.val = 0; + break; + } + + value = as3645a_is_active(flash); + if (value < 0) + return value; + + ctrl->cur.val = value; + break; + } + + dev_dbg(&client->dev, "G_CTRL %08x:%d\n", ctrl->id, ctrl->cur.val); + + return 0; +} + +static int as3645a_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct as3645a *flash = + container_of(ctrl->handler, struct as3645a, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int ret; + + dev_dbg(&client->dev, "S_CTRL %08x:%d\n", ctrl->id, ctrl->val); + + /* If a control that doesn't apply to the current mode is modified, + * we store the value and return immediately. The setting will be + * applied when the LED mode is changed. Otherwise we apply the setting + * immediately. + */ + + switch (ctrl->id) { + case V4L2_CID_FLASH_LED_MODE: + if (flash->indicator_current) + return -EBUSY; + + ret = as3645a_set_config(flash); + if (ret < 0) + return ret; + + flash->led_mode = ctrl->val; + return as3645a_set_output(flash, false); + + case V4L2_CID_FLASH_STROBE_SOURCE: + flash->strobe_source = ctrl->val; + + /* Applies to flash mode only. */ + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) + break; + + return as3645a_set_output(flash, false); + + case V4L2_CID_FLASH_STROBE: + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) + return -EBUSY; + + return as3645a_set_output(flash, true); + + case V4L2_CID_FLASH_STROBE_STOP: + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) + return -EBUSY; + + return as3645a_set_output(flash, false); + + case V4L2_CID_FLASH_TIMEOUT: + flash->timeout = ctrl->val; + + /* Applies to flash mode only. */ + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) + break; + + return as3645a_set_config(flash); + + case V4L2_CID_FLASH_INTENSITY: + flash->flash_current = (ctrl->val - AS3645A_FLASH_INTENSITY_MIN) + / AS3645A_FLASH_INTENSITY_STEP; + + /* Applies to flash mode only. */ + if (flash->led_mode != V4L2_FLASH_LED_MODE_FLASH) + break; + + return as3645a_set_config(flash); + + case V4L2_CID_FLASH_TORCH_INTENSITY: + flash->assist_current = + (ctrl->val - AS3645A_TORCH_INTENSITY_MIN) + / AS3645A_TORCH_INTENSITY_STEP; + + /* Applies to torch mode only. */ + if (flash->led_mode != V4L2_FLASH_LED_MODE_TORCH) + break; + + return as3645a_set_config(flash); + + case V4L2_CID_FLASH_INDICATOR_INTENSITY: + if (flash->led_mode != V4L2_FLASH_LED_MODE_NONE) + return -EBUSY; + + flash->indicator_current = + (ctrl->val - AS3645A_INDICATOR_INTENSITY_MIN) + / AS3645A_INDICATOR_INTENSITY_STEP; + + ret = as3645a_set_config(flash); + if (ret < 0) + return ret; + + if ((ctrl->val == 0) == (ctrl->cur.val == 0)) + break; + + return as3645a_set_output(flash, false); + } + + return 0; +} + +static const struct v4l2_ctrl_ops as3645a_ctrl_ops = { + .g_volatile_ctrl = as3645a_get_ctrl, + .s_ctrl = as3645a_set_ctrl, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +/* Put device into know state. */ +static int as3645a_setup(struct as3645a *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int ret; + + /* clear errors */ + ret = as3645a_read(flash, AS_FAULT_INFO_REG); + if (ret < 0) + return ret; + + dev_dbg(&client->dev, "Fault info: %02x\n", ret); + + ret = as3645a_set_config(flash); + if (ret < 0) + return ret; + + ret = as3645a_set_output(flash, false); + if (ret < 0) + return ret; + + /* read status */ + ret = as3645a_read_fault(flash); + if (ret < 0) + return ret; + + dev_dbg(&client->dev, "AS_INDICATOR_AND_TIMER_REG: %02x\n", + as3645a_read(flash, AS_INDICATOR_AND_TIMER_REG)); + dev_dbg(&client->dev, "AS_CURRENT_SET_REG: %02x\n", + as3645a_read(flash, AS_CURRENT_SET_REG)); + dev_dbg(&client->dev, "AS_CONTROL_REG: %02x\n", + as3645a_read(flash, AS_CONTROL_REG)); + + return ret & ~AS_FAULT_INFO_LED_AMOUNT ? -EIO : 0; +} + +static int __as3645a_set_power(struct as3645a *flash, int on) +{ + int ret; + + if (!on) + as3645a_set_control(flash, AS_MODE_EXT_TORCH, false); + + if (flash->pdata->set_power) { + ret = flash->pdata->set_power(&flash->subdev, on); + if (ret < 0) + return ret; + } + + if (!on) + return 0; + + ret = as3645a_setup(flash); + if (ret < 0) { + if (flash->pdata->set_power) + flash->pdata->set_power(&flash->subdev, 0); + } + + return ret; +} + +static int as3645a_set_power(struct v4l2_subdev *sd, int on) +{ + struct as3645a *flash = to_as3645a(sd); + int ret = 0; + + mutex_lock(&flash->power_lock); + + if (flash->power_count == !on) { + ret = __as3645a_set_power(flash, !!on); + if (ret < 0) + goto done; + } + + flash->power_count += on ? 1 : -1; + WARN_ON(flash->power_count < 0); + +done: + mutex_unlock(&flash->power_lock); + return ret; +} + +static int as3645a_registered(struct v4l2_subdev *sd) +{ + struct as3645a *flash = to_as3645a(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int rval, man, model, rfu, version; + const char *vendor; + + /* Power up the flash driver and read manufacturer ID, model ID, RFU + * and version. + */ + rval = as3645a_set_power(&flash->subdev, 1); + if (rval < 0) + return rval; + + rval = as3645a_read(flash, AS_DESIGN_INFO_REG); + if (rval < 0) + goto power_off; + + man = AS_DESIGN_INFO_FACTORY(rval); + model = AS_DESIGN_INFO_MODEL(rval); + + rval = as3645a_read(flash, AS_VERSION_CONTROL_REG); + if (rval < 0) + goto power_off; + + rfu = AS_VERSION_CONTROL_RFU(rval); + version = AS_VERSION_CONTROL_VERSION(rval); + + /* Verify the chip model and version. */ + if (model != 0x01 || rfu != 0x00) { + dev_err(&client->dev, "AS3645A not detected " + "(model %d rfu %d)\n", model, rfu); + rval = -ENODEV; + goto power_off; + } + + switch (man) { + case 1: + vendor = "AMS, Austria Micro Systems"; + break; + case 2: + vendor = "ADI, Analog Devices Inc."; + break; + case 3: + vendor = "NSC, National Semiconductor"; + break; + case 4: + vendor = "NXP"; + break; + case 5: + vendor = "TI, Texas Instrument"; + break; + default: + vendor = "Unknown"; + } + + dev_info(&client->dev, "Chip vendor: %s (%d) Version: %d\n", vendor, + man, version); + + rval = as3645a_write(flash, AS_PASSWORD_REG, AS_PASSWORD_UNLOCK_VALUE); + if (rval < 0) + goto power_off; + + rval = as3645a_write(flash, AS_BOOST_REG, AS_BOOST_CURRENT_DISABLE); + if (rval < 0) + goto power_off; + + /* Setup default values. This makes sure that the chip is in a known + * state, in case the power rail can't be controlled. + */ + rval = as3645a_setup(flash); + +power_off: + as3645a_set_power(&flash->subdev, 0); + + return rval; +} + +static int as3645a_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return as3645a_set_power(sd, 1); +} + +static int as3645a_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return as3645a_set_power(sd, 0); +} + +static const struct v4l2_subdev_core_ops as3645a_core_ops = { + .s_power = as3645a_set_power, +}; + +static const struct v4l2_subdev_ops as3645a_ops = { + .core = &as3645a_core_ops, +}; + +static const struct v4l2_subdev_internal_ops as3645a_internal_ops = { + .registered = as3645a_registered, + .open = as3645a_open, + .close = as3645a_close, +}; + +/* ----------------------------------------------------------------------------- + * I2C driver + */ +#ifdef CONFIG_PM + +static int as3645a_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct as3645a *flash = to_as3645a(subdev); + int rval; + + if (flash->power_count == 0) + return 0; + + rval = __as3645a_set_power(flash, 0); + + dev_dbg(&client->dev, "Suspend %s\n", rval < 0 ? "failed" : "ok"); + + return rval; +} + +static int as3645a_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct as3645a *flash = to_as3645a(subdev); + int rval; + + if (flash->power_count == 0) + return 0; + + rval = __as3645a_set_power(flash, 1); + + dev_dbg(&client->dev, "Resume %s\n", rval < 0 ? "fail" : "ok"); + + return rval; +} + +#else + +#define as3645a_suspend NULL +#define as3645a_resume NULL + +#endif /* CONFIG_PM */ + +/* + * as3645a_init_controls - Create controls + * @flash: The flash + * + * The number of LEDs reported in platform data is used to compute default + * limits. Parameters passed through platform data can override those limits. + */ +static int __devinit as3645a_init_controls(struct as3645a *flash) +{ + const struct as3645a_platform_data *pdata = flash->pdata; + struct v4l2_ctrl *ctrl; + int maximum; + + v4l2_ctrl_handler_init(&flash->ctrls, 10); + + /* V4L2_CID_FLASH_LED_MODE */ + v4l2_ctrl_new_std_menu(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_LED_MODE, 2, ~7, + V4L2_FLASH_LED_MODE_NONE); + + /* V4L2_CID_FLASH_STROBE_SOURCE */ + v4l2_ctrl_new_std_menu(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_STROBE_SOURCE, + pdata->ext_strobe ? 1 : 0, + pdata->ext_strobe ? ~3 : ~1, + V4L2_FLASH_STROBE_SOURCE_SOFTWARE); + + flash->strobe_source = V4L2_FLASH_STROBE_SOURCE_SOFTWARE; + + /* V4L2_CID_FLASH_STROBE */ + v4l2_ctrl_new_std(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_STROBE, 0, 0, 0, 0); + + /* V4L2_CID_FLASH_STROBE_STOP */ + v4l2_ctrl_new_std(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_STROBE_STOP, 0, 0, 0, 0); + + /* V4L2_CID_FLASH_STROBE_STATUS */ + ctrl = v4l2_ctrl_new_std(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_STROBE_STATUS, 0, 1, 1, 1); + if (ctrl != NULL) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + /* V4L2_CID_FLASH_TIMEOUT */ + maximum = pdata->timeout_max; + + v4l2_ctrl_new_std(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_TIMEOUT, AS3645A_FLASH_TIMEOUT_MIN, + maximum, AS3645A_FLASH_TIMEOUT_STEP, maximum); + + flash->timeout = maximum; + + /* V4L2_CID_FLASH_INTENSITY */ + maximum = pdata->flash_max_current; + + v4l2_ctrl_new_std(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_INTENSITY, AS3645A_FLASH_INTENSITY_MIN, + maximum, AS3645A_FLASH_INTENSITY_STEP, maximum); + + flash->flash_current = (maximum - AS3645A_FLASH_INTENSITY_MIN) + / AS3645A_FLASH_INTENSITY_STEP; + + /* V4L2_CID_FLASH_TORCH_INTENSITY */ + maximum = pdata->torch_max_current; + + v4l2_ctrl_new_std(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_TORCH_INTENSITY, + AS3645A_TORCH_INTENSITY_MIN, maximum, + AS3645A_TORCH_INTENSITY_STEP, + AS3645A_TORCH_INTENSITY_MIN); + + flash->assist_current = 0; + + /* V4L2_CID_FLASH_INDICATOR_INTENSITY */ + v4l2_ctrl_new_std(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_INDICATOR_INTENSITY, + AS3645A_INDICATOR_INTENSITY_MIN, + AS3645A_INDICATOR_INTENSITY_MAX, + AS3645A_INDICATOR_INTENSITY_STEP, + AS3645A_INDICATOR_INTENSITY_MIN); + + flash->indicator_current = 0; + + /* V4L2_CID_FLASH_FAULT */ + ctrl = v4l2_ctrl_new_std(&flash->ctrls, &as3645a_ctrl_ops, + V4L2_CID_FLASH_FAULT, 0, + V4L2_FLASH_FAULT_OVER_VOLTAGE | + V4L2_FLASH_FAULT_TIMEOUT | + V4L2_FLASH_FAULT_OVER_TEMPERATURE | + V4L2_FLASH_FAULT_SHORT_CIRCUIT, 0, 0); + if (ctrl != NULL) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + flash->subdev.ctrl_handler = &flash->ctrls; + + return flash->ctrls.error; +} + +static int __devinit as3645a_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct as3645a *flash; + int ret; + + if (client->dev.platform_data == NULL) + return -ENODEV; + + flash = kzalloc(sizeof(*flash), GFP_KERNEL); + if (flash == NULL) + return -ENOMEM; + + flash->pdata = client->dev.platform_data; + + v4l2_i2c_subdev_init(&flash->subdev, client, &as3645a_ops); + flash->subdev.internal_ops = &as3645a_internal_ops; + flash->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + ret = as3645a_init_controls(flash); + if (ret < 0) + goto done; + + ret = media_entity_init(&flash->subdev.entity, 0, NULL, 0); + if (ret < 0) + goto done; + + flash->subdev.entity.type = MEDIA_ENT_T_V4L2_SUBDEV_FLASH; + + mutex_init(&flash->power_lock); + + flash->led_mode = V4L2_FLASH_LED_MODE_NONE; + +done: + if (ret < 0) { + v4l2_ctrl_handler_free(&flash->ctrls); + kfree(flash); + } + + return ret; +} + +static int __devexit as3645a_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct as3645a *flash = to_as3645a(subdev); + + v4l2_device_unregister_subdev(subdev); + v4l2_ctrl_handler_free(&flash->ctrls); + media_entity_cleanup(&flash->subdev.entity); + mutex_destroy(&flash->power_lock); + kfree(flash); + + return 0; +} + +static const struct i2c_device_id as3645a_id_table[] = { + { AS3645A_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, as3645a_id_table); + +static const struct dev_pm_ops as3645a_pm_ops = { + .suspend = as3645a_suspend, + .resume = as3645a_resume, +}; + +static struct i2c_driver as3645a_i2c_driver = { + .driver = { + .name = AS3645A_NAME, + .pm = &as3645a_pm_ops, + }, + .probe = as3645a_probe, + .remove = __devexit_p(as3645a_remove), + .id_table = as3645a_id_table, +}; + +module_i2c_driver(as3645a_i2c_driver); + +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_DESCRIPTION("LED flash driver for AS3645A, LM3555 and their clones"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/bt819.c b/drivers/media/i2c/bt819.c similarity index 100% rename from drivers/media/video/bt819.c rename to drivers/media/i2c/bt819.c diff --git a/drivers/media/video/bt856.c b/drivers/media/i2c/bt856.c similarity index 100% rename from drivers/media/video/bt856.c rename to drivers/media/i2c/bt856.c diff --git a/drivers/media/video/bt866.c b/drivers/media/i2c/bt866.c similarity index 100% rename from drivers/media/video/bt866.c rename to drivers/media/i2c/bt866.c diff --git a/drivers/media/video/btcx-risc.c b/drivers/media/i2c/btcx-risc.c similarity index 100% rename from drivers/media/video/btcx-risc.c rename to drivers/media/i2c/btcx-risc.c diff --git a/drivers/media/video/btcx-risc.h b/drivers/media/i2c/btcx-risc.h similarity index 100% rename from drivers/media/video/btcx-risc.h rename to drivers/media/i2c/btcx-risc.h diff --git a/drivers/media/video/cs5345.c b/drivers/media/i2c/cs5345.c similarity index 100% rename from drivers/media/video/cs5345.c rename to drivers/media/i2c/cs5345.c diff --git a/drivers/media/video/cs53l32a.c b/drivers/media/i2c/cs53l32a.c similarity index 100% rename from drivers/media/video/cs53l32a.c rename to drivers/media/i2c/cs53l32a.c diff --git a/drivers/media/video/cx2341x.c b/drivers/media/i2c/cx2341x.c similarity index 100% rename from drivers/media/video/cx2341x.c rename to drivers/media/i2c/cx2341x.c diff --git a/drivers/media/video/cx25840/Kconfig b/drivers/media/i2c/cx25840/Kconfig similarity index 100% rename from drivers/media/video/cx25840/Kconfig rename to drivers/media/i2c/cx25840/Kconfig diff --git a/drivers/media/i2c/cx25840/Makefile b/drivers/media/i2c/cx25840/Makefile new file mode 100644 index 000000000000..898eb13340ae --- /dev/null +++ b/drivers/media/i2c/cx25840/Makefile @@ -0,0 +1,6 @@ +cx25840-objs := cx25840-core.o cx25840-audio.o cx25840-firmware.o \ + cx25840-vbi.o cx25840-ir.o + +obj-$(CONFIG_VIDEO_CX25840) += cx25840.o + +ccflags-y += -Idrivers/media/i2c diff --git a/drivers/media/video/cx25840/cx25840-audio.c b/drivers/media/i2c/cx25840/cx25840-audio.c similarity index 100% rename from drivers/media/video/cx25840/cx25840-audio.c rename to drivers/media/i2c/cx25840/cx25840-audio.c diff --git a/drivers/media/video/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c similarity index 100% rename from drivers/media/video/cx25840/cx25840-core.c rename to drivers/media/i2c/cx25840/cx25840-core.c diff --git a/drivers/media/video/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h similarity index 100% rename from drivers/media/video/cx25840/cx25840-core.h rename to drivers/media/i2c/cx25840/cx25840-core.h diff --git a/drivers/media/i2c/cx25840/cx25840-firmware.c b/drivers/media/i2c/cx25840/cx25840-firmware.c new file mode 100644 index 000000000000..b3169f94ece8 --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-firmware.c @@ -0,0 +1,175 @@ +/* cx25840 firmware functions + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include "cx25840-core.h" + +/* + * Mike Isely - The FWSEND parameter controls the + * size of the firmware chunks sent down the I2C bus to the chip. + * Previously this had been set to 1024 but unfortunately some I2C + * implementations can't transfer data in such big gulps. + * Specifically, the pvrusb2 driver has a hard limit of around 60 + * bytes, due to the encapsulation there of I2C traffic into USB + * messages. So we have to significantly reduce this parameter. + */ +#define FWSEND 48 + +#define FWDEV(x) &((x)->dev) + +static char *firmware = ""; + +module_param(firmware, charp, 0444); + +MODULE_PARM_DESC(firmware, "Firmware image to load"); + +static void start_fw_load(struct i2c_client *client) +{ + /* DL_ADDR_LB=0 DL_ADDR_HB=0 */ + cx25840_write(client, 0x800, 0x00); + cx25840_write(client, 0x801, 0x00); + // DL_MAP=3 DL_AUTO_INC=0 DL_ENABLE=1 + cx25840_write(client, 0x803, 0x0b); + /* AUTO_INC_DIS=1 */ + cx25840_write(client, 0x000, 0x20); +} + +static void end_fw_load(struct i2c_client *client) +{ + /* AUTO_INC_DIS=0 */ + cx25840_write(client, 0x000, 0x00); + /* DL_ENABLE=0 */ + cx25840_write(client, 0x803, 0x03); +} + +#define CX2388x_FIRMWARE "v4l-cx23885-avcore-01.fw" +#define CX231xx_FIRMWARE "v4l-cx231xx-avcore-01.fw" +#define CX25840_FIRMWARE "v4l-cx25840.fw" + +static const char *get_fw_name(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (firmware[0]) + return firmware; + if (is_cx2388x(state)) + return CX2388x_FIRMWARE; + if (is_cx231xx(state)) + return CX231xx_FIRMWARE; + return CX25840_FIRMWARE; +} + +static int check_fw_load(struct i2c_client *client, int size) +{ + /* DL_ADDR_HB DL_ADDR_LB */ + int s = cx25840_read(client, 0x801) << 8; + s |= cx25840_read(client, 0x800); + + if (size != s) { + v4l_err(client, "firmware %s load failed\n", + get_fw_name(client)); + return -EINVAL; + } + + v4l_info(client, "loaded %s firmware (%d bytes)\n", + get_fw_name(client), size); + return 0; +} + +static int fw_write(struct i2c_client *client, const u8 *data, int size) +{ + if (i2c_master_send(client, data, size) < size) { + v4l_err(client, "firmware load i2c failure\n"); + return -ENOSYS; + } + + return 0; +} + +int cx25840_loadfw(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + const struct firmware *fw = NULL; + u8 buffer[FWSEND]; + const u8 *ptr; + const char *fwname = get_fw_name(client); + int size, retval; + int MAX_BUF_SIZE = FWSEND; + u32 gpio_oe = 0, gpio_da = 0; + + if (is_cx2388x(state)) { + /* Preserve the GPIO OE and output bits */ + gpio_oe = cx25840_read(client, 0x160); + gpio_da = cx25840_read(client, 0x164); + } + + if (is_cx231xx(state) && MAX_BUF_SIZE > 16) { + v4l_err(client, " Firmware download size changed to 16 bytes max length\n"); + MAX_BUF_SIZE = 16; /* cx231xx cannot accept more than 16 bytes at a time */ + } + + if (request_firmware(&fw, fwname, FWDEV(client)) != 0) { + v4l_err(client, "unable to open firmware %s\n", fwname); + return -EINVAL; + } + + start_fw_load(client); + + buffer[0] = 0x08; + buffer[1] = 0x02; + + size = fw->size; + ptr = fw->data; + while (size > 0) { + int len = min(MAX_BUF_SIZE - 2, size); + + memcpy(buffer + 2, ptr, len); + + retval = fw_write(client, buffer, len + 2); + + if (retval < 0) { + release_firmware(fw); + return retval; + } + + size -= len; + ptr += len; + } + + end_fw_load(client); + + size = fw->size; + release_firmware(fw); + + if (is_cx2388x(state)) { + /* Restore GPIO configuration after f/w load */ + cx25840_write(client, 0x160, gpio_oe); + cx25840_write(client, 0x164, gpio_da); + } + + return check_fw_load(client, size); +} + +MODULE_FIRMWARE(CX2388x_FIRMWARE); +MODULE_FIRMWARE(CX231xx_FIRMWARE); +MODULE_FIRMWARE(CX25840_FIRMWARE); + diff --git a/drivers/media/video/cx25840/cx25840-ir.c b/drivers/media/i2c/cx25840/cx25840-ir.c similarity index 100% rename from drivers/media/video/cx25840/cx25840-ir.c rename to drivers/media/i2c/cx25840/cx25840-ir.c diff --git a/drivers/media/i2c/cx25840/cx25840-vbi.c b/drivers/media/i2c/cx25840/cx25840-vbi.c new file mode 100644 index 000000000000..c39e91dc1137 --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-vbi.c @@ -0,0 +1,257 @@ +/* cx25840 VBI functions + * + * 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. + */ + + +#include +#include +#include +#include + +#include "cx25840-core.h" + +static int odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static int decode_vps(u8 * dst, u8 * p) +{ + static const u8 biphase_tbl[] = { + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87, + 0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3, + 0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85, + 0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86, + 0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2, + 0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84, + 0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + }; + + u8 c, err = 0; + int i; + + for (i = 0; i < 2 * 13; i += 2) { + err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]]; + c = (biphase_tbl[p[i + 1]] & 0xf) | + ((biphase_tbl[p[i]] & 0xf) << 4); + dst[i / 2] = c; + } + + return err & 0xf0; +} + +int cx25840_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + static const u16 lcr2vbi[] = { + 0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */ + 0, V4L2_SLICED_WSS_625, 0, /* 4 */ + V4L2_SLICED_CAPTION_525, /* 6 */ + 0, 0, V4L2_SLICED_VPS, 0, 0, /* 9 */ + 0, 0, 0, 0 + }; + int is_pal = !(state->std & V4L2_STD_525_60); + int i; + + memset(svbi->service_lines, 0, sizeof(svbi->service_lines)); + svbi->service_set = 0; + /* we're done if raw VBI is active */ + if ((cx25840_read(client, 0x404) & 0x10) == 0) + return 0; + + if (is_pal) { + for (i = 7; i <= 23; i++) { + u8 v = cx25840_read(client, 0x424 + i - 7); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } else { + for (i = 10; i <= 21; i++) { + u8 v = cx25840_read(client, 0x424 + i - 10); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } + return 0; +} + +int cx25840_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + int is_pal = !(state->std & V4L2_STD_525_60); + int vbi_offset = is_pal ? 1 : 0; + + /* Setup standard */ + cx25840_std_setup(client); + + /* VBI Offset */ + cx25840_write(client, 0x47f, vbi_offset); + cx25840_write(client, 0x404, 0x2e); + return 0; +} + +int cx25840_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + int is_pal = !(state->std & V4L2_STD_525_60); + int vbi_offset = is_pal ? 1 : 0; + int i, x; + u8 lcr[24]; + + for (x = 0; x <= 23; x++) + lcr[x] = 0x00; + + /* Setup standard */ + cx25840_std_setup(client); + + /* Sliced VBI */ + cx25840_write(client, 0x404, 0x32); /* Ancillary data */ + cx25840_write(client, 0x406, 0x13); + cx25840_write(client, 0x47f, vbi_offset); + + if (is_pal) { + for (i = 0; i <= 6; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } else { + for (i = 0; i <= 9; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + + for (i = 22; i <= 23; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } + + for (i = 7; i <= 23; i++) { + for (x = 0; x <= 1; x++) { + switch (svbi->service_lines[1-x][i]) { + case V4L2_SLICED_TELETEXT_B: + lcr[i] |= 1 << (4 * x); + break; + case V4L2_SLICED_WSS_625: + lcr[i] |= 4 << (4 * x); + break; + case V4L2_SLICED_CAPTION_525: + lcr[i] |= 6 << (4 * x); + break; + case V4L2_SLICED_VPS: + lcr[i] |= 9 << (4 * x); + break; + } + } + } + + if (is_pal) { + for (x = 1, i = 0x424; i <= 0x434; i++, x++) + cx25840_write(client, i, lcr[6 + x]); + } else { + for (x = 1, i = 0x424; i <= 0x430; i++, x++) + cx25840_write(client, i, lcr[9 + x]); + for (i = 0x431; i <= 0x434; i++) + cx25840_write(client, i, 0); + } + + cx25840_write(client, 0x43c, 0x16); + cx25840_write(client, 0x474, is_pal ? 0x2a : 0x22); + return 0; +} + +int cx25840_decode_vbi_line(struct v4l2_subdev *sd, struct v4l2_decode_vbi_line *vbi) +{ + struct cx25840_state *state = to_state(sd); + u8 *p = vbi->p; + int id1, id2, l, err = 0; + + if (p[0] || p[1] != 0xff || p[2] != 0xff || + (p[3] != 0x55 && p[3] != 0x91)) { + vbi->line = vbi->type = 0; + return 0; + } + + p += 4; + id1 = p[-1]; + id2 = p[0] & 0xf; + l = p[2] & 0x3f; + l += state->vbi_line_offset; + p += 4; + + switch (id2) { + case 1: + id2 = V4L2_SLICED_TELETEXT_B; + break; + case 4: + id2 = V4L2_SLICED_WSS_625; + break; + case 6: + id2 = V4L2_SLICED_CAPTION_525; + err = !odd_parity(p[0]) || !odd_parity(p[1]); + break; + case 9: + id2 = V4L2_SLICED_VPS; + if (decode_vps(p, p) != 0) + err = 1; + break; + default: + id2 = 0; + err = 1; + break; + } + + vbi->type = err ? 0 : id2; + vbi->line = err ? 0 : l; + vbi->is_second_field = err ? 0 : (id1 == 0x55); + vbi->p = p; + return 0; +} diff --git a/drivers/media/video/ir-kbd-i2c.c b/drivers/media/i2c/ir-kbd-i2c.c similarity index 100% rename from drivers/media/video/ir-kbd-i2c.c rename to drivers/media/i2c/ir-kbd-i2c.c diff --git a/drivers/media/i2c/ks0127.c b/drivers/media/i2c/ks0127.c new file mode 100644 index 000000000000..04a6efa37cc3 --- /dev/null +++ b/drivers/media/i2c/ks0127.c @@ -0,0 +1,733 @@ +/* + * Video Capture Driver (Video for Linux 1/2) + * for the Matrox Marvel G200,G400 and Rainbow Runner-G series + * + * This module is an interface to the KS0127 video decoder chip. + * + * Copyright (C) 1999 Ryan Drake + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ***************************************************************************** + * + * Modified and extended by + * Mike Bernson + * Gerard v.d. Horst + * Leon van Stuivenberg + * Gernot Ziegler + * + * Version History: + * V1.0 Ryan Drake Initial version by Ryan Drake + * V1.1 Gerard v.d. Horst Added some debugoutput, reset the video-standard + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ks0127.h" + +MODULE_DESCRIPTION("KS0127 video decoder driver"); +MODULE_AUTHOR("Ryan Drake"); +MODULE_LICENSE("GPL"); + +/* Addresses */ +#define I2C_KS0127_ADDON 0xD8 +#define I2C_KS0127_ONBOARD 0xDA + + +/* ks0127 control registers */ +#define KS_STAT 0x00 +#define KS_CMDA 0x01 +#define KS_CMDB 0x02 +#define KS_CMDC 0x03 +#define KS_CMDD 0x04 +#define KS_HAVB 0x05 +#define KS_HAVE 0x06 +#define KS_HS1B 0x07 +#define KS_HS1E 0x08 +#define KS_HS2B 0x09 +#define KS_HS2E 0x0a +#define KS_AGC 0x0b +#define KS_HXTRA 0x0c +#define KS_CDEM 0x0d +#define KS_PORTAB 0x0e +#define KS_LUMA 0x0f +#define KS_CON 0x10 +#define KS_BRT 0x11 +#define KS_CHROMA 0x12 +#define KS_CHROMB 0x13 +#define KS_DEMOD 0x14 +#define KS_SAT 0x15 +#define KS_HUE 0x16 +#define KS_VERTIA 0x17 +#define KS_VERTIB 0x18 +#define KS_VERTIC 0x19 +#define KS_HSCLL 0x1a +#define KS_HSCLH 0x1b +#define KS_VSCLL 0x1c +#define KS_VSCLH 0x1d +#define KS_OFMTA 0x1e +#define KS_OFMTB 0x1f +#define KS_VBICTL 0x20 +#define KS_CCDAT2 0x21 +#define KS_CCDAT1 0x22 +#define KS_VBIL30 0x23 +#define KS_VBIL74 0x24 +#define KS_VBIL118 0x25 +#define KS_VBIL1512 0x26 +#define KS_TTFRAM 0x27 +#define KS_TESTA 0x28 +#define KS_UVOFFH 0x29 +#define KS_UVOFFL 0x2a +#define KS_UGAIN 0x2b +#define KS_VGAIN 0x2c +#define KS_VAVB 0x2d +#define KS_VAVE 0x2e +#define KS_CTRACK 0x2f +#define KS_POLCTL 0x30 +#define KS_REFCOD 0x31 +#define KS_INVALY 0x32 +#define KS_INVALU 0x33 +#define KS_INVALV 0x34 +#define KS_UNUSEY 0x35 +#define KS_UNUSEU 0x36 +#define KS_UNUSEV 0x37 +#define KS_USRSAV 0x38 +#define KS_USREAV 0x39 +#define KS_SHS1A 0x3a +#define KS_SHS1B 0x3b +#define KS_SHS1C 0x3c +#define KS_CMDE 0x3d +#define KS_VSDEL 0x3e +#define KS_CMDF 0x3f +#define KS_GAMMA0 0x40 +#define KS_GAMMA1 0x41 +#define KS_GAMMA2 0x42 +#define KS_GAMMA3 0x43 +#define KS_GAMMA4 0x44 +#define KS_GAMMA5 0x45 +#define KS_GAMMA6 0x46 +#define KS_GAMMA7 0x47 +#define KS_GAMMA8 0x48 +#define KS_GAMMA9 0x49 +#define KS_GAMMA10 0x4a +#define KS_GAMMA11 0x4b +#define KS_GAMMA12 0x4c +#define KS_GAMMA13 0x4d +#define KS_GAMMA14 0x4e +#define KS_GAMMA15 0x4f +#define KS_GAMMA16 0x50 +#define KS_GAMMA17 0x51 +#define KS_GAMMA18 0x52 +#define KS_GAMMA19 0x53 +#define KS_GAMMA20 0x54 +#define KS_GAMMA21 0x55 +#define KS_GAMMA22 0x56 +#define KS_GAMMA23 0x57 +#define KS_GAMMA24 0x58 +#define KS_GAMMA25 0x59 +#define KS_GAMMA26 0x5a +#define KS_GAMMA27 0x5b +#define KS_GAMMA28 0x5c +#define KS_GAMMA29 0x5d +#define KS_GAMMA30 0x5e +#define KS_GAMMA31 0x5f +#define KS_GAMMAD0 0x60 +#define KS_GAMMAD1 0x61 +#define KS_GAMMAD2 0x62 +#define KS_GAMMAD3 0x63 +#define KS_GAMMAD4 0x64 +#define KS_GAMMAD5 0x65 +#define KS_GAMMAD6 0x66 +#define KS_GAMMAD7 0x67 +#define KS_GAMMAD8 0x68 +#define KS_GAMMAD9 0x69 +#define KS_GAMMAD10 0x6a +#define KS_GAMMAD11 0x6b +#define KS_GAMMAD12 0x6c +#define KS_GAMMAD13 0x6d +#define KS_GAMMAD14 0x6e +#define KS_GAMMAD15 0x6f +#define KS_GAMMAD16 0x70 +#define KS_GAMMAD17 0x71 +#define KS_GAMMAD18 0x72 +#define KS_GAMMAD19 0x73 +#define KS_GAMMAD20 0x74 +#define KS_GAMMAD21 0x75 +#define KS_GAMMAD22 0x76 +#define KS_GAMMAD23 0x77 +#define KS_GAMMAD24 0x78 +#define KS_GAMMAD25 0x79 +#define KS_GAMMAD26 0x7a +#define KS_GAMMAD27 0x7b +#define KS_GAMMAD28 0x7c +#define KS_GAMMAD29 0x7d +#define KS_GAMMAD30 0x7e +#define KS_GAMMAD31 0x7f + + +/**************************************************************************** +* mga_dev : represents one ks0127 chip. +****************************************************************************/ + +struct adjust { + int contrast; + int bright; + int hue; + int ugain; + int vgain; +}; + +struct ks0127 { + struct v4l2_subdev sd; + v4l2_std_id norm; + int ident; + u8 regs[256]; +}; + +static inline struct ks0127 *to_ks0127(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ks0127, sd); +} + + +static int debug; /* insmod parameter */ + +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug output"); + +static u8 reg_defaults[64]; + +static void init_reg_defaults(void) +{ + static int initialized; + u8 *table = reg_defaults; + + if (initialized) + return; + initialized = 1; + + table[KS_CMDA] = 0x2c; /* VSE=0, CCIR 601, autodetect standard */ + table[KS_CMDB] = 0x12; /* VALIGN=0, AGC control and input */ + table[KS_CMDC] = 0x00; /* Test options */ + /* clock & input select, write 1 to PORTA */ + table[KS_CMDD] = 0x01; + table[KS_HAVB] = 0x00; /* HAV Start Control */ + table[KS_HAVE] = 0x00; /* HAV End Control */ + table[KS_HS1B] = 0x10; /* HS1 Start Control */ + table[KS_HS1E] = 0x00; /* HS1 End Control */ + table[KS_HS2B] = 0x00; /* HS2 Start Control */ + table[KS_HS2E] = 0x00; /* HS2 End Control */ + table[KS_AGC] = 0x53; /* Manual setting for AGC */ + table[KS_HXTRA] = 0x00; /* Extra Bits for HAV and HS1/2 */ + table[KS_CDEM] = 0x00; /* Chroma Demodulation Control */ + table[KS_PORTAB] = 0x0f; /* port B is input, port A output GPPORT */ + table[KS_LUMA] = 0x01; /* Luma control */ + table[KS_CON] = 0x00; /* Contrast Control */ + table[KS_BRT] = 0x00; /* Brightness Control */ + table[KS_CHROMA] = 0x2a; /* Chroma control A */ + table[KS_CHROMB] = 0x90; /* Chroma control B */ + table[KS_DEMOD] = 0x00; /* Chroma Demodulation Control & Status */ + table[KS_SAT] = 0x00; /* Color Saturation Control*/ + table[KS_HUE] = 0x00; /* Hue Control */ + table[KS_VERTIA] = 0x00; /* Vertical Processing Control A */ + /* Vertical Processing Control B, luma 1 line delayed */ + table[KS_VERTIB] = 0x12; + table[KS_VERTIC] = 0x0b; /* Vertical Processing Control C */ + table[KS_HSCLL] = 0x00; /* Horizontal Scaling Ratio Low */ + table[KS_HSCLH] = 0x00; /* Horizontal Scaling Ratio High */ + table[KS_VSCLL] = 0x00; /* Vertical Scaling Ratio Low */ + table[KS_VSCLH] = 0x00; /* Vertical Scaling Ratio High */ + /* 16 bit YCbCr 4:2:2 output; I can't make the bt866 like 8 bit /Sam */ + table[KS_OFMTA] = 0x30; + table[KS_OFMTB] = 0x00; /* Output Control B */ + /* VBI Decoder Control; 4bit fmt: avoid Y overflow */ + table[KS_VBICTL] = 0x5d; + table[KS_CCDAT2] = 0x00; /* Read Only register */ + table[KS_CCDAT1] = 0x00; /* Read Only register */ + table[KS_VBIL30] = 0xa8; /* VBI data decoding options */ + table[KS_VBIL74] = 0xaa; /* VBI data decoding options */ + table[KS_VBIL118] = 0x2a; /* VBI data decoding options */ + table[KS_VBIL1512] = 0x00; /* VBI data decoding options */ + table[KS_TTFRAM] = 0x00; /* Teletext frame alignment pattern */ + table[KS_TESTA] = 0x00; /* test register, shouldn't be written */ + table[KS_UVOFFH] = 0x00; /* UV Offset Adjustment High */ + table[KS_UVOFFL] = 0x00; /* UV Offset Adjustment Low */ + table[KS_UGAIN] = 0x00; /* U Component Gain Adjustment */ + table[KS_VGAIN] = 0x00; /* V Component Gain Adjustment */ + table[KS_VAVB] = 0x07; /* VAV Begin */ + table[KS_VAVE] = 0x00; /* VAV End */ + table[KS_CTRACK] = 0x00; /* Chroma Tracking Control */ + table[KS_POLCTL] = 0x41; /* Timing Signal Polarity Control */ + table[KS_REFCOD] = 0x80; /* Reference Code Insertion Control */ + table[KS_INVALY] = 0x10; /* Invalid Y Code */ + table[KS_INVALU] = 0x80; /* Invalid U Code */ + table[KS_INVALV] = 0x80; /* Invalid V Code */ + table[KS_UNUSEY] = 0x10; /* Unused Y Code */ + table[KS_UNUSEU] = 0x80; /* Unused U Code */ + table[KS_UNUSEV] = 0x80; /* Unused V Code */ + table[KS_USRSAV] = 0x00; /* reserved */ + table[KS_USREAV] = 0x00; /* reserved */ + table[KS_SHS1A] = 0x00; /* User Defined SHS1 A */ + /* User Defined SHS1 B, ALT656=1 on 0127B */ + table[KS_SHS1B] = 0x80; + table[KS_SHS1C] = 0x00; /* User Defined SHS1 C */ + table[KS_CMDE] = 0x00; /* Command Register E */ + table[KS_VSDEL] = 0x00; /* VS Delay Control */ + /* Command Register F, update -immediately- */ + /* (there might come no vsync)*/ + table[KS_CMDF] = 0x02; +} + + +/* We need to manually read because of a bug in the KS0127 chip. + * + * An explanation from kayork@mail.utexas.edu: + * + * During I2C reads, the KS0127 only samples for a stop condition + * during the place where the acknowledge bit should be. Any standard + * I2C implementation (correctly) throws in another clock transition + * at the 9th bit, and the KS0127 will not recognize the stop condition + * and will continue to clock out data. + * + * So we have to do the read ourself. Big deal. + * workaround in i2c-algo-bit + */ + + +static u8 ks0127_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + char val = 0; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .len = sizeof(reg), + .buf = ® + }, + { + .addr = client->addr, + .flags = I2C_M_RD | I2C_M_NO_RD_ACK, + .len = sizeof(val), + .buf = &val + } + }; + int ret; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + v4l2_dbg(1, debug, sd, "read error\n"); + + return val; +} + + +static void ks0127_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ks0127 *ks = to_ks0127(sd); + char msg[] = { reg, val }; + + if (i2c_master_send(client, msg, sizeof(msg)) != sizeof(msg)) + v4l2_dbg(1, debug, sd, "write error\n"); + + ks->regs[reg] = val; +} + + +/* generic bit-twiddling */ +static void ks0127_and_or(struct v4l2_subdev *sd, u8 reg, u8 and_v, u8 or_v) +{ + struct ks0127 *ks = to_ks0127(sd); + + u8 val = ks->regs[reg]; + val = (val & and_v) | or_v; + ks0127_write(sd, reg, val); +} + + + +/**************************************************************************** +* ks0127 private api +****************************************************************************/ +static void ks0127_init(struct v4l2_subdev *sd) +{ + struct ks0127 *ks = to_ks0127(sd); + u8 *table = reg_defaults; + int i; + + ks->ident = V4L2_IDENT_KS0127; + + v4l2_dbg(1, debug, sd, "reset\n"); + msleep(1); + + /* initialize all registers to known values */ + /* (except STAT, 0x21, 0x22, TEST and 0x38,0x39) */ + + for (i = 1; i < 33; i++) + ks0127_write(sd, i, table[i]); + + for (i = 35; i < 40; i++) + ks0127_write(sd, i, table[i]); + + for (i = 41; i < 56; i++) + ks0127_write(sd, i, table[i]); + + for (i = 58; i < 64; i++) + ks0127_write(sd, i, table[i]); + + + if ((ks0127_read(sd, KS_STAT) & 0x80) == 0) { + ks->ident = V4L2_IDENT_KS0122S; + v4l2_dbg(1, debug, sd, "ks0122s found\n"); + return; + } + + switch (ks0127_read(sd, KS_CMDE) & 0x0f) { + case 0: + v4l2_dbg(1, debug, sd, "ks0127 found\n"); + break; + + case 9: + ks->ident = V4L2_IDENT_KS0127B; + v4l2_dbg(1, debug, sd, "ks0127B Revision A found\n"); + break; + + default: + v4l2_dbg(1, debug, sd, "unknown revision\n"); + break; + } +} + +static int ks0127_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct ks0127 *ks = to_ks0127(sd); + + switch (input) { + case KS_INPUT_COMPOSITE_1: + case KS_INPUT_COMPOSITE_2: + case KS_INPUT_COMPOSITE_3: + case KS_INPUT_COMPOSITE_4: + case KS_INPUT_COMPOSITE_5: + case KS_INPUT_COMPOSITE_6: + v4l2_dbg(1, debug, sd, + "s_routing %d: Composite\n", input); + /* autodetect 50/60 Hz */ + ks0127_and_or(sd, KS_CMDA, 0xfc, 0x00); + /* VSE=0 */ + ks0127_and_or(sd, KS_CMDA, ~0x40, 0x00); + /* set input line */ + ks0127_and_or(sd, KS_CMDB, 0xb0, input); + /* non-freerunning mode */ + ks0127_and_or(sd, KS_CMDC, 0x70, 0x0a); + /* analog input */ + ks0127_and_or(sd, KS_CMDD, 0x03, 0x00); + /* enable chroma demodulation */ + ks0127_and_or(sd, KS_CTRACK, 0xcf, 0x00); + /* chroma trap, HYBWR=1 */ + ks0127_and_or(sd, KS_LUMA, 0x00, + (reg_defaults[KS_LUMA])|0x0c); + /* scaler fullbw, luma comb off */ + ks0127_and_or(sd, KS_VERTIA, 0x08, 0x81); + /* manual chroma comb .25 .5 .25 */ + ks0127_and_or(sd, KS_VERTIC, 0x0f, 0x90); + + /* chroma path delay */ + ks0127_and_or(sd, KS_CHROMB, 0x0f, 0x90); + + ks0127_write(sd, KS_UGAIN, reg_defaults[KS_UGAIN]); + ks0127_write(sd, KS_VGAIN, reg_defaults[KS_VGAIN]); + ks0127_write(sd, KS_UVOFFH, reg_defaults[KS_UVOFFH]); + ks0127_write(sd, KS_UVOFFL, reg_defaults[KS_UVOFFL]); + break; + + case KS_INPUT_SVIDEO_1: + case KS_INPUT_SVIDEO_2: + case KS_INPUT_SVIDEO_3: + v4l2_dbg(1, debug, sd, + "s_routing %d: S-Video\n", input); + /* autodetect 50/60 Hz */ + ks0127_and_or(sd, KS_CMDA, 0xfc, 0x00); + /* VSE=0 */ + ks0127_and_or(sd, KS_CMDA, ~0x40, 0x00); + /* set input line */ + ks0127_and_or(sd, KS_CMDB, 0xb0, input); + /* non-freerunning mode */ + ks0127_and_or(sd, KS_CMDC, 0x70, 0x0a); + /* analog input */ + ks0127_and_or(sd, KS_CMDD, 0x03, 0x00); + /* enable chroma demodulation */ + ks0127_and_or(sd, KS_CTRACK, 0xcf, 0x00); + ks0127_and_or(sd, KS_LUMA, 0x00, + reg_defaults[KS_LUMA]); + /* disable luma comb */ + ks0127_and_or(sd, KS_VERTIA, 0x08, + (reg_defaults[KS_VERTIA]&0xf0)|0x01); + ks0127_and_or(sd, KS_VERTIC, 0x0f, + reg_defaults[KS_VERTIC]&0xf0); + + ks0127_and_or(sd, KS_CHROMB, 0x0f, + reg_defaults[KS_CHROMB]&0xf0); + + ks0127_write(sd, KS_UGAIN, reg_defaults[KS_UGAIN]); + ks0127_write(sd, KS_VGAIN, reg_defaults[KS_VGAIN]); + ks0127_write(sd, KS_UVOFFH, reg_defaults[KS_UVOFFH]); + ks0127_write(sd, KS_UVOFFL, reg_defaults[KS_UVOFFL]); + break; + + case KS_INPUT_YUV656: + v4l2_dbg(1, debug, sd, "s_routing 15: YUV656\n"); + if (ks->norm & V4L2_STD_525_60) + /* force 60 Hz */ + ks0127_and_or(sd, KS_CMDA, 0xfc, 0x03); + else + /* force 50 Hz */ + ks0127_and_or(sd, KS_CMDA, 0xfc, 0x02); + + ks0127_and_or(sd, KS_CMDA, 0xff, 0x40); /* VSE=1 */ + /* set input line and VALIGN */ + ks0127_and_or(sd, KS_CMDB, 0xb0, (input | 0x40)); + /* freerunning mode, */ + /* TSTGEN = 1 TSTGFR=11 TSTGPH=0 TSTGPK=0 VMEM=1*/ + ks0127_and_or(sd, KS_CMDC, 0x70, 0x87); + /* digital input, SYNDIR = 0 INPSL=01 CLKDIR=0 EAV=0 */ + ks0127_and_or(sd, KS_CMDD, 0x03, 0x08); + /* disable chroma demodulation */ + ks0127_and_or(sd, KS_CTRACK, 0xcf, 0x30); + /* HYPK =01 CTRAP = 0 HYBWR=0 PED=1 RGBH=1 UNIT=1 */ + ks0127_and_or(sd, KS_LUMA, 0x00, 0x71); + ks0127_and_or(sd, KS_VERTIC, 0x0f, + reg_defaults[KS_VERTIC]&0xf0); + + /* scaler fullbw, luma comb off */ + ks0127_and_or(sd, KS_VERTIA, 0x08, 0x81); + + ks0127_and_or(sd, KS_CHROMB, 0x0f, + reg_defaults[KS_CHROMB]&0xf0); + + ks0127_and_or(sd, KS_CON, 0x00, 0x00); + ks0127_and_or(sd, KS_BRT, 0x00, 32); /* spec: 34 */ + /* spec: 229 (e5) */ + ks0127_and_or(sd, KS_SAT, 0x00, 0xe8); + ks0127_and_or(sd, KS_HUE, 0x00, 0); + + ks0127_and_or(sd, KS_UGAIN, 0x00, 238); + ks0127_and_or(sd, KS_VGAIN, 0x00, 0x00); + + /*UOFF:0x30, VOFF:0x30, TSTCGN=1 */ + ks0127_and_or(sd, KS_UVOFFH, 0x00, 0x4f); + ks0127_and_or(sd, KS_UVOFFL, 0x00, 0x00); + break; + + default: + v4l2_dbg(1, debug, sd, + "s_routing: Unknown input %d\n", input); + break; + } + + /* hack: CDMLPF sometimes spontaneously switches on; */ + /* force back off */ + ks0127_write(sd, KS_DEMOD, reg_defaults[KS_DEMOD]); + return 0; +} + +static int ks0127_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct ks0127 *ks = to_ks0127(sd); + + /* Set to automatic SECAM/Fsc mode */ + ks0127_and_or(sd, KS_DEMOD, 0xf0, 0x00); + + ks->norm = std; + if (std & V4L2_STD_NTSC) { + v4l2_dbg(1, debug, sd, + "s_std: NTSC_M\n"); + ks0127_and_or(sd, KS_CHROMA, 0x9f, 0x20); + } else if (std & V4L2_STD_PAL_N) { + v4l2_dbg(1, debug, sd, + "s_std: NTSC_N (fixme)\n"); + ks0127_and_or(sd, KS_CHROMA, 0x9f, 0x40); + } else if (std & V4L2_STD_PAL) { + v4l2_dbg(1, debug, sd, + "s_std: PAL_N\n"); + ks0127_and_or(sd, KS_CHROMA, 0x9f, 0x20); + } else if (std & V4L2_STD_PAL_M) { + v4l2_dbg(1, debug, sd, + "s_std: PAL_M (fixme)\n"); + ks0127_and_or(sd, KS_CHROMA, 0x9f, 0x40); + } else if (std & V4L2_STD_SECAM) { + v4l2_dbg(1, debug, sd, + "s_std: SECAM\n"); + + /* set to secam autodetection */ + ks0127_and_or(sd, KS_CHROMA, 0xdf, 0x20); + ks0127_and_or(sd, KS_DEMOD, 0xf0, 0x00); + schedule_timeout_interruptible(HZ/10+1); + + /* did it autodetect? */ + if (!(ks0127_read(sd, KS_DEMOD) & 0x40)) + /* force to secam mode */ + ks0127_and_or(sd, KS_DEMOD, 0xf0, 0x0f); + } else { + v4l2_dbg(1, debug, sd, "s_std: Unknown norm %llx\n", + (unsigned long long)std); + } + return 0; +} + +static int ks0127_s_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "s_stream(%d)\n", enable); + if (enable) { + /* All output pins on */ + ks0127_and_or(sd, KS_OFMTA, 0xcf, 0x30); + /* Obey the OEN pin */ + ks0127_and_or(sd, KS_CDEM, 0x7f, 0x00); + } else { + /* Video output pins off */ + ks0127_and_or(sd, KS_OFMTA, 0xcf, 0x00); + /* Ignore the OEN pin */ + ks0127_and_or(sd, KS_CDEM, 0x7f, 0x80); + } + return 0; +} + +static int ks0127_status(struct v4l2_subdev *sd, u32 *pstatus, v4l2_std_id *pstd) +{ + int stat = V4L2_IN_ST_NO_SIGNAL; + u8 status; + v4l2_std_id std = V4L2_STD_ALL; + + status = ks0127_read(sd, KS_STAT); + if (!(status & 0x20)) /* NOVID not set */ + stat = 0; + if (!(status & 0x01)) /* CLOCK set */ + stat |= V4L2_IN_ST_NO_COLOR; + if ((status & 0x08)) /* PALDET set */ + std = V4L2_STD_PAL; + else + std = V4L2_STD_NTSC; + if (pstd) + *pstd = std; + if (pstatus) + *pstatus = stat; + return 0; +} + +static int ks0127_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + v4l2_dbg(1, debug, sd, "querystd\n"); + return ks0127_status(sd, NULL, std); +} + +static int ks0127_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + v4l2_dbg(1, debug, sd, "g_input_status\n"); + return ks0127_status(sd, status, NULL); +} + +static int ks0127_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ks0127 *ks = to_ks0127(sd); + + return v4l2_chip_ident_i2c_client(client, chip, ks->ident, 0); +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops ks0127_core_ops = { + .g_chip_ident = ks0127_g_chip_ident, + .s_std = ks0127_s_std, +}; + +static const struct v4l2_subdev_video_ops ks0127_video_ops = { + .s_routing = ks0127_s_routing, + .s_stream = ks0127_s_stream, + .querystd = ks0127_querystd, + .g_input_status = ks0127_g_input_status, +}; + +static const struct v4l2_subdev_ops ks0127_ops = { + .core = &ks0127_core_ops, + .video = &ks0127_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + + +static int ks0127_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct ks0127 *ks; + struct v4l2_subdev *sd; + + v4l_info(client, "%s chip found @ 0x%x (%s)\n", + client->addr == (I2C_KS0127_ADDON >> 1) ? "addon" : "on-board", + client->addr << 1, client->adapter->name); + + ks = kzalloc(sizeof(*ks), GFP_KERNEL); + if (ks == NULL) + return -ENOMEM; + sd = &ks->sd; + v4l2_i2c_subdev_init(sd, client, &ks0127_ops); + + /* power up */ + init_reg_defaults(); + ks0127_write(sd, KS_CMDA, 0x2c); + mdelay(10); + + /* reset the device */ + ks0127_init(sd); + return 0; +} + +static int ks0127_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + ks0127_write(sd, KS_OFMTA, 0x20); /* tristate */ + ks0127_write(sd, KS_CMDA, 0x2c | 0x80); /* power down */ + kfree(to_ks0127(sd)); + return 0; +} + +static const struct i2c_device_id ks0127_id[] = { + { "ks0127", 0 }, + { "ks0127b", 0 }, + { "ks0122s", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ks0127_id); + +static struct i2c_driver ks0127_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ks0127", + }, + .probe = ks0127_probe, + .remove = ks0127_remove, + .id_table = ks0127_id, +}; + +module_i2c_driver(ks0127_driver); diff --git a/drivers/media/video/ks0127.h b/drivers/media/i2c/ks0127.h similarity index 100% rename from drivers/media/video/ks0127.h rename to drivers/media/i2c/ks0127.h diff --git a/drivers/media/video/m52790.c b/drivers/media/i2c/m52790.c similarity index 100% rename from drivers/media/video/m52790.c rename to drivers/media/i2c/m52790.c diff --git a/drivers/media/video/m5mols/Kconfig b/drivers/media/i2c/m5mols/Kconfig similarity index 100% rename from drivers/media/video/m5mols/Kconfig rename to drivers/media/i2c/m5mols/Kconfig diff --git a/drivers/media/video/m5mols/Makefile b/drivers/media/i2c/m5mols/Makefile similarity index 100% rename from drivers/media/video/m5mols/Makefile rename to drivers/media/i2c/m5mols/Makefile diff --git a/drivers/media/i2c/m5mols/m5mols.h b/drivers/media/i2c/m5mols/m5mols.h new file mode 100644 index 000000000000..86c815be348c --- /dev/null +++ b/drivers/media/i2c/m5mols/m5mols.h @@ -0,0 +1,338 @@ +/* + * Header for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim + * + * 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. + */ + +#ifndef M5MOLS_H +#define M5MOLS_H + +#include +#include "m5mols_reg.h" + +extern int m5mols_debug; + +enum m5mols_restype { + M5MOLS_RESTYPE_MONITOR, + M5MOLS_RESTYPE_CAPTURE, + M5MOLS_RESTYPE_MAX, +}; + +/** + * struct m5mols_resolution - structure for the resolution + * @type: resolution type according to the pixel code + * @width: width of the resolution + * @height: height of the resolution + * @reg: resolution preset register value + */ +struct m5mols_resolution { + u8 reg; + enum m5mols_restype type; + u16 width; + u16 height; +}; + +/** + * struct m5mols_exif - structure for the EXIF information of M-5MOLS + * @exposure_time: exposure time register value + * @shutter_speed: speed of the shutter register value + * @aperture: aperture register value + * @exposure_bias: it calls also EV bias + * @iso_speed: ISO register value + * @flash: status register value of the flash + * @sdr: status register value of the Subject Distance Range + * @qval: not written exact meaning in document + */ +struct m5mols_exif { + u32 exposure_time; + u32 shutter_speed; + u32 aperture; + u32 brightness; + u32 exposure_bias; + u16 iso_speed; + u16 flash; + u16 sdr; + u16 qval; +}; + +/** + * struct m5mols_capture - Structure for the capture capability + * @exif: EXIF information + * @main: size in bytes of the main image + * @thumb: size in bytes of the thumb image, if it was accompanied + * @total: total size in bytes of the produced image + */ +struct m5mols_capture { + struct m5mols_exif exif; + u32 main; + u32 thumb; + u32 total; +}; + +/** + * struct m5mols_scenemode - structure for the scenemode capability + * @metering: metering light register value + * @ev_bias: EV bias register value + * @wb_mode: mode which means the WhiteBalance is Auto or Manual + * @wb_preset: whitebalance preset register value in the Manual mode + * @chroma_en: register value whether the Chroma capability is enabled or not + * @chroma_lvl: chroma's level register value + * @edge_en: register value Whether the Edge capability is enabled or not + * @edge_lvl: edge's level register value + * @af_range: Auto Focus's range + * @fd_mode: Face Detection mode + * @mcc: Multi-axis Color Conversion which means emotion color + * @light: status of the Light + * @flash: status of the Flash + * @tone: Tone color which means Contrast + * @iso: ISO register value + * @capt_mode: Mode of the Image Stabilization while the camera capturing + * @wdr: Wide Dynamic Range register value + * + * The each value according to each scenemode is recommended in the documents. + */ +struct m5mols_scenemode { + u8 metering; + u8 ev_bias; + u8 wb_mode; + u8 wb_preset; + u8 chroma_en; + u8 chroma_lvl; + u8 edge_en; + u8 edge_lvl; + u8 af_range; + u8 fd_mode; + u8 mcc; + u8 light; + u8 flash; + u8 tone; + u8 iso; + u8 capt_mode; + u8 wdr; +}; + +/** + * struct m5mols_version - firmware version information + * @customer: customer information + * @project: version of project information according to customer + * @fw: firmware revision + * @hw: hardware revision + * @param: version of the parameter + * @awb: Auto WhiteBalance algorithm version + * @str: information about manufacturer and packaging vendor + * @af: Auto Focus version + * + * The register offset starts the customer version at 0x0, and it ends + * the awb version at 0x09. The customer, project information occupies 1 bytes + * each. And also the fw, hw, param, awb each requires 2 bytes. The str is + * unique string associated with firmware's version. It includes information + * about manufacturer and the vendor of the sensor's packaging. The least + * significant 2 bytes of the string indicate packaging manufacturer. + */ +#define VERSION_STRING_SIZE 22 +struct m5mols_version { + u8 customer; + u8 project; + u16 fw; + u16 hw; + u16 param; + u16 awb; + u8 str[VERSION_STRING_SIZE]; + u8 af; +}; + +/** + * struct m5mols_info - M-5MOLS driver data structure + * @pdata: platform data + * @sd: v4l-subdev instance + * @pad: media pad + * @irq_waitq: waitqueue for the capture + * @irq_done: set to 1 in the interrupt handler + * @handle: control handler + * @auto_exposure: auto/manual exposure control + * @exposure_bias: exposure compensation control + * @exposure: manual exposure control + * @metering: exposure metering control + * @auto_iso: auto/manual ISO sensitivity control + * @iso: manual ISO sensitivity control + * @auto_wb: auto white balance control + * @lock_3a: 3A lock control + * @colorfx: color effect control + * @saturation: saturation control + * @zoom: zoom control + * @wdr: wide dynamic range control + * @stabilization: image stabilization control + * @jpeg_quality: JPEG compression quality control + * @set_power: optional power callback to the board code + * @lock: mutex protecting the structure fields below + * @ffmt: current fmt according to resolution type + * @res_type: current resolution type + * @ver: information of the version + * @cap: the capture mode attributes + * @isp_ready: 1 when the ISP controller has completed booting + * @power: current sensor's power status + * @ctrl_sync: 1 when the control handler state is restored in H/W + * @resolution: register value for current resolution + * @mode: register value for current operation mode + */ +struct m5mols_info { + const struct m5mols_platform_data *pdata; + struct v4l2_subdev sd; + struct media_pad pad; + + wait_queue_head_t irq_waitq; + atomic_t irq_done; + + struct v4l2_ctrl_handler handle; + struct { + /* exposure/exposure bias/auto exposure cluster */ + struct v4l2_ctrl *auto_exposure; + struct v4l2_ctrl *exposure_bias; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *metering; + }; + struct { + /* iso/auto iso cluster */ + struct v4l2_ctrl *auto_iso; + struct v4l2_ctrl *iso; + }; + struct v4l2_ctrl *auto_wb; + + struct v4l2_ctrl *lock_3a; + struct v4l2_ctrl *colorfx; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *zoom; + struct v4l2_ctrl *wdr; + struct v4l2_ctrl *stabilization; + struct v4l2_ctrl *jpeg_quality; + + int (*set_power)(struct device *dev, int on); + + struct mutex lock; + + struct v4l2_mbus_framefmt ffmt[M5MOLS_RESTYPE_MAX]; + int res_type; + + struct m5mols_version ver; + struct m5mols_capture cap; + + unsigned int isp_ready:1; + unsigned int power:1; + unsigned int ctrl_sync:1; + + u8 resolution; + u8 mode; +}; + +#define is_available_af(__info) (__info->ver.af) +#define is_code(__code, __type) (__code == m5mols_default_ffmt[__type].code) +#define is_manufacturer(__info, __manufacturer) \ + (__info->ver.str[0] == __manufacturer[0] && \ + __info->ver.str[1] == __manufacturer[1]) +/* + * I2C operation of the M-5MOLS + * + * The I2C read operation of the M-5MOLS requires 2 messages. The first + * message sends the information about the command, command category, and total + * message size. The second message is used to retrieve the data specifed in + * the first message + * + * 1st message 2nd message + * +-------+---+----------+-----+-------+ +------+------+------+------+ + * | size1 | R | category | cmd | size2 | | d[0] | d[1] | d[2] | d[3] | + * +-------+---+----------+-----+-------+ +------+------+------+------+ + * - size1: message data size(5 in this case) + * - size2: desired buffer size of the 2nd message + * - d[0..3]: according to size2 + * + * The I2C write operation needs just one message. The message includes + * category, command, total size, and desired data. + * + * 1st message + * +-------+---+----------+-----+------+------+------+------+ + * | size1 | W | category | cmd | d[0] | d[1] | d[2] | d[3] | + * +-------+---+----------+-----+------+------+------+------+ + * - d[0..3]: according to size1 + */ +int m5mols_read_u8(struct v4l2_subdev *sd, u32 reg_comb, u8 *val); +int m5mols_read_u16(struct v4l2_subdev *sd, u32 reg_comb, u16 *val); +int m5mols_read_u32(struct v4l2_subdev *sd, u32 reg_comb, u32 *val); +int m5mols_write(struct v4l2_subdev *sd, u32 reg_comb, u32 val); + +int m5mols_busy_wait(struct v4l2_subdev *sd, u32 reg, u32 value, u32 mask, + int timeout); + +/* Mask value for busy waiting until M-5MOLS I2C interface is initialized */ +#define M5MOLS_I2C_RDY_WAIT_FL (1 << 16) +/* ISP state transition timeout, in ms */ +#define M5MOLS_MODE_CHANGE_TIMEOUT 200 +#define M5MOLS_BUSY_WAIT_DEF_TIMEOUT 250 + +/* + * Mode operation of the M-5MOLS + * + * Changing the mode of the M-5MOLS is needed right executing order. + * There are three modes(PARAMETER, MONITOR, CAPTURE) which can be changed + * by user. There are various categories associated with each mode. + * + * +============================================================+ + * | mode | category | + * +============================================================+ + * | FLASH | FLASH(only after Stand-by or Power-on) | + * | SYSTEM | SYSTEM(only after sensor arm-booting) | + * | PARAMETER | PARAMETER | + * | MONITOR | MONITOR(preview), Auto Focus, Face Detection | + * | CAPTURE | Single CAPTURE, Preview(recording) | + * +============================================================+ + * + * The available executing order between each modes are as follows: + * PARAMETER <---> MONITOR <---> CAPTURE + */ +int m5mols_set_mode(struct m5mols_info *info, u8 mode); + +int m5mols_enable_interrupt(struct v4l2_subdev *sd, u8 reg); +int m5mols_wait_interrupt(struct v4l2_subdev *sd, u8 condition, u32 timeout); +int m5mols_restore_controls(struct m5mols_info *info); +int m5mols_start_capture(struct m5mols_info *info); +int m5mols_do_scenemode(struct m5mols_info *info, u8 mode); +int m5mols_lock_3a(struct m5mols_info *info, bool lock); +int m5mols_set_ctrl(struct v4l2_ctrl *ctrl); +int m5mols_init_controls(struct v4l2_subdev *sd); + +/* The firmware function */ +int m5mols_update_fw(struct v4l2_subdev *sd, + int (*set_power)(struct m5mols_info *, bool)); + +static inline struct m5mols_info *to_m5mols(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct m5mols_info, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + struct m5mols_info *info = container_of(ctrl->handler, + struct m5mols_info, handle); + return &info->sd; +} + +static inline void m5mols_set_ctrl_mode(struct v4l2_ctrl *ctrl, + unsigned int mode) +{ + ctrl->priv = (void *)(uintptr_t)mode; +} + +static inline unsigned int m5mols_get_ctrl_mode(struct v4l2_ctrl *ctrl) +{ + return (unsigned int)(uintptr_t)ctrl->priv; +} + +#endif /* M5MOLS_H */ diff --git a/drivers/media/video/m5mols/m5mols_capture.c b/drivers/media/i2c/m5mols/m5mols_capture.c similarity index 100% rename from drivers/media/video/m5mols/m5mols_capture.c rename to drivers/media/i2c/m5mols/m5mols_capture.c diff --git a/drivers/media/i2c/m5mols/m5mols_controls.c b/drivers/media/i2c/m5mols/m5mols_controls.c new file mode 100644 index 000000000000..f34429e452ab --- /dev/null +++ b/drivers/media/i2c/m5mols/m5mols_controls.c @@ -0,0 +1,628 @@ +/* + * Controls for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim + * + * 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. + */ + +#include +#include +#include +#include + +#include "m5mols.h" +#include "m5mols_reg.h" + +static struct m5mols_scenemode m5mols_default_scenemode[] = { + [REG_SCENE_NORMAL] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_NORMAL, REG_LIGHT_OFF, REG_FLASH_OFF, + 5, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_PORTRAIT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 4, + REG_AF_NORMAL, BIT_FD_EN | BIT_FD_DRAW_FACE_FRAME, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_LANDSCAPE] = { + REG_AE_ALL, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 6, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_SPORTS] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_PARTY_INDOOR] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_200, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_BEACH_SNOW] = { + REG_AE_CENTER, REG_AE_INDEX_10_POS, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_50, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_SUNSET] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_PRESET, + REG_AWB_DAYLIGHT, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_DAWN_DUSK] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_PRESET, + REG_AWB_FLUORESCENT_1, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_FALL] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 5, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_NIGHT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_AGAINST_LIGHT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_FIRE] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_50, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_TEXT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 7, + REG_AF_MACRO, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_ANTI_SHAKE, REG_WDR_ON, + }, + [REG_SCENE_CANDLE] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, +}; + +/** + * m5mols_do_scenemode() - Change current scenemode + * @mode: Desired mode of the scenemode + * + * WARNING: The execution order is important. Do not change the order. + */ +int m5mols_do_scenemode(struct m5mols_info *info, u8 mode) +{ + struct v4l2_subdev *sd = &info->sd; + struct m5mols_scenemode scenemode = m5mols_default_scenemode[mode]; + int ret; + + if (mode > REG_SCENE_CANDLE) + return -EINVAL; + + ret = v4l2_ctrl_s_ctrl(info->lock_3a, 0); + if (!ret) + ret = m5mols_write(sd, AE_EV_PRESET_MONITOR, mode); + if (!ret) + ret = m5mols_write(sd, AE_EV_PRESET_CAPTURE, mode); + if (!ret) + ret = m5mols_write(sd, AE_MODE, scenemode.metering); + if (!ret) + ret = m5mols_write(sd, AE_INDEX, scenemode.ev_bias); + if (!ret) + ret = m5mols_write(sd, AWB_MODE, scenemode.wb_mode); + if (!ret) + ret = m5mols_write(sd, AWB_MANUAL, scenemode.wb_preset); + if (!ret) + ret = m5mols_write(sd, MON_CHROMA_EN, scenemode.chroma_en); + if (!ret) + ret = m5mols_write(sd, MON_CHROMA_LVL, scenemode.chroma_lvl); + if (!ret) + ret = m5mols_write(sd, MON_EDGE_EN, scenemode.edge_en); + if (!ret) + ret = m5mols_write(sd, MON_EDGE_LVL, scenemode.edge_lvl); + if (!ret && is_available_af(info)) + ret = m5mols_write(sd, AF_MODE, scenemode.af_range); + if (!ret && is_available_af(info)) + ret = m5mols_write(sd, FD_CTL, scenemode.fd_mode); + if (!ret) + ret = m5mols_write(sd, MON_TONE_CTL, scenemode.tone); + if (!ret) + ret = m5mols_write(sd, AE_ISO, scenemode.iso); + if (!ret) + ret = m5mols_set_mode(info, REG_CAPTURE); + if (!ret) + ret = m5mols_write(sd, CAPP_WDR_EN, scenemode.wdr); + if (!ret) + ret = m5mols_write(sd, CAPP_MCC_MODE, scenemode.mcc); + if (!ret) + ret = m5mols_write(sd, CAPP_LIGHT_CTRL, scenemode.light); + if (!ret) + ret = m5mols_write(sd, CAPP_FLASH_CTRL, scenemode.flash); + if (!ret) + ret = m5mols_write(sd, CAPC_MODE, scenemode.capt_mode); + if (!ret) + ret = m5mols_set_mode(info, REG_MONITOR); + + return ret; +} + +static int m5mols_3a_lock(struct m5mols_info *info, struct v4l2_ctrl *ctrl) +{ + bool af_lock = ctrl->val & V4L2_LOCK_FOCUS; + int ret = 0; + + if ((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_EXPOSURE) { + bool ae_lock = ctrl->val & V4L2_LOCK_EXPOSURE; + + ret = m5mols_write(&info->sd, AE_LOCK, ae_lock ? + REG_AE_LOCK : REG_AE_UNLOCK); + if (ret) + return ret; + } + + if (((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_WHITE_BALANCE) + && info->auto_wb->val) { + bool awb_lock = ctrl->val & V4L2_LOCK_WHITE_BALANCE; + + ret = m5mols_write(&info->sd, AWB_LOCK, awb_lock ? + REG_AWB_LOCK : REG_AWB_UNLOCK); + if (ret) + return ret; + } + + if (!info->ver.af || !af_lock) + return ret; + + if ((ctrl->val ^ ctrl->cur.val) & V4L2_LOCK_FOCUS) + ret = m5mols_write(&info->sd, AF_EXECUTE, REG_AF_STOP); + + return ret; +} + +static int m5mols_set_metering_mode(struct m5mols_info *info, int mode) +{ + unsigned int metering; + + switch (mode) { + case V4L2_EXPOSURE_METERING_CENTER_WEIGHTED: + metering = REG_AE_CENTER; + break; + case V4L2_EXPOSURE_METERING_SPOT: + metering = REG_AE_SPOT; + break; + default: + metering = REG_AE_ALL; + break; + } + + return m5mols_write(&info->sd, AE_MODE, metering); +} + +static int m5mols_set_exposure(struct m5mols_info *info, int exposure) +{ + struct v4l2_subdev *sd = &info->sd; + int ret = 0; + + if (exposure == V4L2_EXPOSURE_AUTO) { + /* Unlock auto exposure */ + info->lock_3a->val &= ~V4L2_LOCK_EXPOSURE; + m5mols_3a_lock(info, info->lock_3a); + + ret = m5mols_set_metering_mode(info, info->metering->val); + if (ret < 0) + return ret; + + v4l2_dbg(1, m5mols_debug, sd, + "%s: exposure bias: %#x, metering: %#x\n", + __func__, info->exposure_bias->val, + info->metering->val); + + return m5mols_write(sd, AE_INDEX, info->exposure_bias->val); + } + + if (exposure == V4L2_EXPOSURE_MANUAL) { + ret = m5mols_write(sd, AE_MODE, REG_AE_OFF); + if (ret == 0) + ret = m5mols_write(sd, AE_MAN_GAIN_MON, + info->exposure->val); + if (ret == 0) + ret = m5mols_write(sd, AE_MAN_GAIN_CAP, + info->exposure->val); + + v4l2_dbg(1, m5mols_debug, sd, "%s: exposure: %#x\n", + __func__, info->exposure->val); + } + + return ret; +} + +static int m5mols_set_white_balance(struct m5mols_info *info, int val) +{ + static const unsigned short wb[][2] = { + { V4L2_WHITE_BALANCE_INCANDESCENT, REG_AWB_INCANDESCENT }, + { V4L2_WHITE_BALANCE_FLUORESCENT, REG_AWB_FLUORESCENT_1 }, + { V4L2_WHITE_BALANCE_FLUORESCENT_H, REG_AWB_FLUORESCENT_2 }, + { V4L2_WHITE_BALANCE_HORIZON, REG_AWB_HORIZON }, + { V4L2_WHITE_BALANCE_DAYLIGHT, REG_AWB_DAYLIGHT }, + { V4L2_WHITE_BALANCE_FLASH, REG_AWB_LEDLIGHT }, + { V4L2_WHITE_BALANCE_CLOUDY, REG_AWB_CLOUDY }, + { V4L2_WHITE_BALANCE_SHADE, REG_AWB_SHADE }, + { V4L2_WHITE_BALANCE_AUTO, REG_AWB_AUTO }, + }; + int i; + struct v4l2_subdev *sd = &info->sd; + int ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(wb); i++) { + int awb; + if (wb[i][0] != val) + continue; + + v4l2_dbg(1, m5mols_debug, sd, + "Setting white balance to: %#x\n", wb[i][0]); + + awb = wb[i][0] == V4L2_WHITE_BALANCE_AUTO; + ret = m5mols_write(sd, AWB_MODE, awb ? REG_AWB_AUTO : + REG_AWB_PRESET); + if (ret < 0) + return ret; + + if (!awb) + ret = m5mols_write(sd, AWB_MANUAL, wb[i][1]); + } + + return ret; +} + +static int m5mols_set_saturation(struct m5mols_info *info, int val) +{ + int ret = m5mols_write(&info->sd, MON_CHROMA_LVL, val); + if (ret < 0) + return ret; + + return m5mols_write(&info->sd, MON_CHROMA_EN, REG_CHROMA_ON); +} + +static int m5mols_set_color_effect(struct m5mols_info *info, int val) +{ + unsigned int m_effect = REG_COLOR_EFFECT_OFF; + unsigned int p_effect = REG_EFFECT_OFF; + unsigned int cfix_r = 0, cfix_b = 0; + struct v4l2_subdev *sd = &info->sd; + int ret = 0; + + switch (val) { + case V4L2_COLORFX_BW: + m_effect = REG_COLOR_EFFECT_ON; + break; + case V4L2_COLORFX_NEGATIVE: + p_effect = REG_EFFECT_NEGA; + break; + case V4L2_COLORFX_EMBOSS: + p_effect = REG_EFFECT_EMBOSS; + break; + case V4L2_COLORFX_SEPIA: + m_effect = REG_COLOR_EFFECT_ON; + cfix_r = REG_CFIXR_SEPIA; + cfix_b = REG_CFIXB_SEPIA; + break; + } + + ret = m5mols_write(sd, PARM_EFFECT, p_effect); + if (!ret) + ret = m5mols_write(sd, MON_EFFECT, m_effect); + + if (ret == 0 && m_effect == REG_COLOR_EFFECT_ON) { + ret = m5mols_write(sd, MON_CFIXR, cfix_r); + if (!ret) + ret = m5mols_write(sd, MON_CFIXB, cfix_b); + } + + v4l2_dbg(1, m5mols_debug, sd, + "p_effect: %#x, m_effect: %#x, r: %#x, b: %#x (%d)\n", + p_effect, m_effect, cfix_r, cfix_b, ret); + + return ret; +} + +static int m5mols_set_iso(struct m5mols_info *info, int auto_iso) +{ + u32 iso = auto_iso ? 0 : info->iso->val + 1; + + return m5mols_write(&info->sd, AE_ISO, iso); +} + +static int m5mols_set_wdr(struct m5mols_info *info, int wdr) +{ + int ret; + + ret = m5mols_write(&info->sd, MON_TONE_CTL, wdr ? 9 : 5); + if (ret < 0) + return ret; + + ret = m5mols_set_mode(info, REG_CAPTURE); + if (ret < 0) + return ret; + + return m5mols_write(&info->sd, CAPP_WDR_EN, wdr); +} + +static int m5mols_set_stabilization(struct m5mols_info *info, int val) +{ + struct v4l2_subdev *sd = &info->sd; + unsigned int evp = val ? 0xe : 0x0; + int ret; + + ret = m5mols_write(sd, AE_EV_PRESET_MONITOR, evp); + if (ret < 0) + return ret; + + return m5mols_write(sd, AE_EV_PRESET_CAPTURE, evp); +} + +static int m5mols_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct m5mols_info *info = to_m5mols(sd); + int ret = 0; + u8 status; + + v4l2_dbg(1, m5mols_debug, sd, "%s: ctrl: %s (%d)\n", + __func__, ctrl->name, info->isp_ready); + + if (!info->isp_ready) + return -EBUSY; + + switch (ctrl->id) { + case V4L2_CID_ISO_SENSITIVITY_AUTO: + ret = m5mols_read_u8(sd, AE_ISO, &status); + if (ret == 0) + ctrl->val = !status; + if (status != REG_ISO_AUTO) + info->iso->val = status - 1; + break; + + case V4L2_CID_3A_LOCK: + ctrl->val &= ~0x7; + + ret = m5mols_read_u8(sd, AE_LOCK, &status); + if (ret) + return ret; + if (status) + info->lock_3a->val |= V4L2_LOCK_EXPOSURE; + + ret = m5mols_read_u8(sd, AWB_LOCK, &status); + if (ret) + return ret; + if (status) + info->lock_3a->val |= V4L2_LOCK_EXPOSURE; + + ret = m5mols_read_u8(sd, AF_EXECUTE, &status); + if (!status) + info->lock_3a->val |= V4L2_LOCK_EXPOSURE; + break; + } + + return ret; +} + +static int m5mols_s_ctrl(struct v4l2_ctrl *ctrl) +{ + unsigned int ctrl_mode = m5mols_get_ctrl_mode(ctrl); + struct v4l2_subdev *sd = to_sd(ctrl); + struct m5mols_info *info = to_m5mols(sd); + int last_mode = info->mode; + int ret = 0; + + /* + * If needed, defer restoring the controls until + * the device is fully initialized. + */ + if (!info->isp_ready) { + info->ctrl_sync = 0; + return 0; + } + + v4l2_dbg(1, m5mols_debug, sd, "%s: %s, val: %d, priv: %p\n", + __func__, ctrl->name, ctrl->val, ctrl->priv); + + if (ctrl_mode && ctrl_mode != info->mode) { + ret = m5mols_set_mode(info, ctrl_mode); + if (ret < 0) + return ret; + } + + switch (ctrl->id) { + case V4L2_CID_3A_LOCK: + ret = m5mols_3a_lock(info, ctrl); + break; + + case V4L2_CID_ZOOM_ABSOLUTE: + ret = m5mols_write(sd, MON_ZOOM, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + ret = m5mols_set_exposure(info, ctrl->val); + break; + + case V4L2_CID_ISO_SENSITIVITY: + ret = m5mols_set_iso(info, ctrl->val); + break; + + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: + ret = m5mols_set_white_balance(info, ctrl->val); + break; + + case V4L2_CID_SATURATION: + ret = m5mols_set_saturation(info, ctrl->val); + break; + + case V4L2_CID_COLORFX: + ret = m5mols_set_color_effect(info, ctrl->val); + break; + + case V4L2_CID_WIDE_DYNAMIC_RANGE: + ret = m5mols_set_wdr(info, ctrl->val); + break; + + case V4L2_CID_IMAGE_STABILIZATION: + ret = m5mols_set_stabilization(info, ctrl->val); + break; + + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + ret = m5mols_write(sd, CAPP_JPEG_RATIO, ctrl->val); + break; + } + + if (ret == 0 && info->mode != last_mode) + ret = m5mols_set_mode(info, last_mode); + + return ret; +} + +static const struct v4l2_ctrl_ops m5mols_ctrl_ops = { + .g_volatile_ctrl = m5mols_g_volatile_ctrl, + .s_ctrl = m5mols_s_ctrl, +}; + +/* Supported manual ISO values */ +static const s64 iso_qmenu[] = { + /* AE_ISO: 0x01...0x07 (ISO: 50...3200) */ + 50000, 100000, 200000, 400000, 800000, 1600000, 3200000 +}; + +/* Supported Exposure Bias values, -2.0EV...+2.0EV */ +static const s64 ev_bias_qmenu[] = { + /* AE_INDEX: 0x00...0x08 */ + -2000, -1500, -1000, -500, 0, 500, 1000, 1500, 2000 +}; + +int m5mols_init_controls(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + u16 exposure_max; + u16 zoom_step; + int ret; + + /* Determine the firmware dependant control range and step values */ + ret = m5mols_read_u16(sd, AE_MAX_GAIN_MON, &exposure_max); + if (ret < 0) + return ret; + + zoom_step = is_manufacturer(info, REG_SAMSUNG_OPTICS) ? 31 : 1; + v4l2_ctrl_handler_init(&info->handle, 20); + + info->auto_wb = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, + 9, ~0x3fe, V4L2_WHITE_BALANCE_AUTO); + + /* Exposure control cluster */ + info->auto_exposure = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, + 1, ~0x03, V4L2_EXPOSURE_AUTO); + + info->exposure = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE, + 0, exposure_max, 1, exposure_max / 2); + + info->exposure_bias = v4l2_ctrl_new_int_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_AUTO_EXPOSURE_BIAS, + ARRAY_SIZE(ev_bias_qmenu) - 1, + ARRAY_SIZE(ev_bias_qmenu)/2 - 1, + ev_bias_qmenu); + + info->metering = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE_METERING, + 2, ~0x7, V4L2_EXPOSURE_METERING_AVERAGE); + + /* ISO control cluster */ + info->auto_iso = v4l2_ctrl_new_std_menu(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_ISO_SENSITIVITY_AUTO, 1, ~0x03, 1); + + info->iso = v4l2_ctrl_new_int_menu(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_ISO_SENSITIVITY, ARRAY_SIZE(iso_qmenu) - 1, + ARRAY_SIZE(iso_qmenu)/2 - 1, iso_qmenu); + + info->saturation = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_SATURATION, 1, 5, 1, 3); + + info->zoom = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_ZOOM_ABSOLUTE, 1, 70, zoom_step, 1); + + info->colorfx = v4l2_ctrl_new_std_menu(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_COLORFX, 4, 0, V4L2_COLORFX_NONE); + + info->wdr = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_WIDE_DYNAMIC_RANGE, 0, 1, 1, 0); + + info->stabilization = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_IMAGE_STABILIZATION, 0, 1, 1, 0); + + info->jpeg_quality = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, 1, 100, 1, 80); + + info->lock_3a = v4l2_ctrl_new_std(&info->handle, &m5mols_ctrl_ops, + V4L2_CID_3A_LOCK, 0, 0x7, 0, 0); + + if (info->handle.error) { + int ret = info->handle.error; + v4l2_err(sd, "Failed to initialize controls: %d\n", ret); + v4l2_ctrl_handler_free(&info->handle); + return ret; + } + + v4l2_ctrl_auto_cluster(4, &info->auto_exposure, 1, false); + info->auto_iso->flags |= V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_UPDATE; + v4l2_ctrl_auto_cluster(2, &info->auto_iso, 0, false); + + info->lock_3a->flags |= V4L2_CTRL_FLAG_VOLATILE; + + m5mols_set_ctrl_mode(info->auto_exposure, REG_PARAMETER); + m5mols_set_ctrl_mode(info->auto_wb, REG_PARAMETER); + m5mols_set_ctrl_mode(info->colorfx, REG_MONITOR); + + sd->ctrl_handler = &info->handle; + + return 0; +} diff --git a/drivers/media/i2c/m5mols/m5mols_core.c b/drivers/media/i2c/m5mols/m5mols_core.c new file mode 100644 index 000000000000..2f490ef26c38 --- /dev/null +++ b/drivers/media/i2c/m5mols/m5mols_core.c @@ -0,0 +1,1010 @@ +/* + * Driver for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "m5mols.h" +#include "m5mols_reg.h" + +int m5mols_debug; +module_param(m5mols_debug, int, 0644); + +#define MODULE_NAME "M5MOLS" +#define M5MOLS_I2C_CHECK_RETRY 500 + +/* The regulator consumer names for external voltage regulators */ +static struct regulator_bulk_data supplies[] = { + { + .supply = "core", /* ARM core power, 1.2V */ + }, { + .supply = "dig_18", /* digital power 1, 1.8V */ + }, { + .supply = "d_sensor", /* sensor power 1, 1.8V */ + }, { + .supply = "dig_28", /* digital power 2, 2.8V */ + }, { + .supply = "a_sensor", /* analog power */ + }, { + .supply = "dig_12", /* digital power 3, 1.2V */ + }, +}; + +static struct v4l2_mbus_framefmt m5mols_default_ffmt[M5MOLS_RESTYPE_MAX] = { + [M5MOLS_RESTYPE_MONITOR] = { + .width = 1920, + .height = 1080, + .code = V4L2_MBUS_FMT_VYUY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + [M5MOLS_RESTYPE_CAPTURE] = { + .width = 1920, + .height = 1080, + .code = V4L2_MBUS_FMT_JPEG_1X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, + }, +}; +#define SIZE_DEFAULT_FFMT ARRAY_SIZE(m5mols_default_ffmt) + +static const struct m5mols_resolution m5mols_reg_res[] = { + { 0x01, M5MOLS_RESTYPE_MONITOR, 128, 96 }, /* SUB-QCIF */ + { 0x03, M5MOLS_RESTYPE_MONITOR, 160, 120 }, /* QQVGA */ + { 0x05, M5MOLS_RESTYPE_MONITOR, 176, 144 }, /* QCIF */ + { 0x06, M5MOLS_RESTYPE_MONITOR, 176, 176 }, + { 0x08, M5MOLS_RESTYPE_MONITOR, 240, 320 }, /* QVGA */ + { 0x09, M5MOLS_RESTYPE_MONITOR, 320, 240 }, /* QVGA */ + { 0x0c, M5MOLS_RESTYPE_MONITOR, 240, 400 }, /* WQVGA */ + { 0x0d, M5MOLS_RESTYPE_MONITOR, 400, 240 }, /* WQVGA */ + { 0x0e, M5MOLS_RESTYPE_MONITOR, 352, 288 }, /* CIF */ + { 0x13, M5MOLS_RESTYPE_MONITOR, 480, 360 }, + { 0x15, M5MOLS_RESTYPE_MONITOR, 640, 360 }, /* qHD */ + { 0x17, M5MOLS_RESTYPE_MONITOR, 640, 480 }, /* VGA */ + { 0x18, M5MOLS_RESTYPE_MONITOR, 720, 480 }, + { 0x1a, M5MOLS_RESTYPE_MONITOR, 800, 480 }, /* WVGA */ + { 0x1f, M5MOLS_RESTYPE_MONITOR, 800, 600 }, /* SVGA */ + { 0x21, M5MOLS_RESTYPE_MONITOR, 1280, 720 }, /* HD */ + { 0x25, M5MOLS_RESTYPE_MONITOR, 1920, 1080 }, /* 1080p */ + { 0x29, M5MOLS_RESTYPE_MONITOR, 3264, 2448 }, /* 2.63fps 8M */ + { 0x39, M5MOLS_RESTYPE_MONITOR, 800, 602 }, /* AHS_MON debug */ + + { 0x02, M5MOLS_RESTYPE_CAPTURE, 320, 240 }, /* QVGA */ + { 0x04, M5MOLS_RESTYPE_CAPTURE, 400, 240 }, /* WQVGA */ + { 0x07, M5MOLS_RESTYPE_CAPTURE, 480, 360 }, + { 0x08, M5MOLS_RESTYPE_CAPTURE, 640, 360 }, /* qHD */ + { 0x09, M5MOLS_RESTYPE_CAPTURE, 640, 480 }, /* VGA */ + { 0x0a, M5MOLS_RESTYPE_CAPTURE, 800, 480 }, /* WVGA */ + { 0x10, M5MOLS_RESTYPE_CAPTURE, 1280, 720 }, /* HD */ + { 0x14, M5MOLS_RESTYPE_CAPTURE, 1280, 960 }, /* 1M */ + { 0x17, M5MOLS_RESTYPE_CAPTURE, 1600, 1200 }, /* 2M */ + { 0x19, M5MOLS_RESTYPE_CAPTURE, 1920, 1080 }, /* Full-HD */ + { 0x1a, M5MOLS_RESTYPE_CAPTURE, 2048, 1152 }, /* 3Mega */ + { 0x1b, M5MOLS_RESTYPE_CAPTURE, 2048, 1536 }, + { 0x1c, M5MOLS_RESTYPE_CAPTURE, 2560, 1440 }, /* 4Mega */ + { 0x1d, M5MOLS_RESTYPE_CAPTURE, 2560, 1536 }, + { 0x1f, M5MOLS_RESTYPE_CAPTURE, 2560, 1920 }, /* 5Mega */ + { 0x21, M5MOLS_RESTYPE_CAPTURE, 3264, 1836 }, /* 6Mega */ + { 0x22, M5MOLS_RESTYPE_CAPTURE, 3264, 1960 }, + { 0x25, M5MOLS_RESTYPE_CAPTURE, 3264, 2448 }, /* 8Mega */ +}; + +/** + * m5mols_swap_byte - an byte array to integer conversion function + * @size: size in bytes of I2C packet defined in the M-5MOLS datasheet + * + * Convert I2C data byte array with performing any required byte + * reordering to assure proper values for each data type, regardless + * of the architecture endianness. + */ +static u32 m5mols_swap_byte(u8 *data, u8 length) +{ + if (length == 1) + return *data; + else if (length == 2) + return be16_to_cpu(*((u16 *)data)); + else + return be32_to_cpu(*((u32 *)data)); +} + +/** + * m5mols_read - I2C read function + * @reg: combination of size, category and command for the I2C packet + * @size: desired size of I2C packet + * @val: read value + * + * Returns 0 on success, or else negative errno. + */ +static int m5mols_read(struct v4l2_subdev *sd, u32 size, u32 reg, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct m5mols_info *info = to_m5mols(sd); + u8 rbuf[M5MOLS_I2C_MAX_SIZE + 1]; + u8 category = I2C_CATEGORY(reg); + u8 cmd = I2C_COMMAND(reg); + struct i2c_msg msg[2]; + u8 wbuf[5]; + int ret; + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 5; + msg[0].buf = wbuf; + wbuf[0] = 5; + wbuf[1] = M5MOLS_BYTE_READ; + wbuf[2] = category; + wbuf[3] = cmd; + wbuf[4] = size; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = size + 1; + msg[1].buf = rbuf; + + /* minimum stabilization time */ + usleep_range(200, 200); + + ret = i2c_transfer(client->adapter, msg, 2); + + if (ret == 2) { + *val = m5mols_swap_byte(&rbuf[1], size); + return 0; + } + + if (info->isp_ready) + v4l2_err(sd, "read failed: size:%d cat:%02x cmd:%02x. %d\n", + size, category, cmd, ret); + + return ret < 0 ? ret : -EIO; +} + +int m5mols_read_u8(struct v4l2_subdev *sd, u32 reg, u8 *val) +{ + u32 val_32; + int ret; + + if (I2C_SIZE(reg) != 1) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + ret = m5mols_read(sd, I2C_SIZE(reg), reg, &val_32); + if (ret) + return ret; + + *val = (u8)val_32; + return ret; +} + +int m5mols_read_u16(struct v4l2_subdev *sd, u32 reg, u16 *val) +{ + u32 val_32; + int ret; + + if (I2C_SIZE(reg) != 2) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + ret = m5mols_read(sd, I2C_SIZE(reg), reg, &val_32); + if (ret) + return ret; + + *val = (u16)val_32; + return ret; +} + +int m5mols_read_u32(struct v4l2_subdev *sd, u32 reg, u32 *val) +{ + if (I2C_SIZE(reg) != 4) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + return m5mols_read(sd, I2C_SIZE(reg), reg, val); +} + +/** + * m5mols_write - I2C command write function + * @reg: combination of size, category and command for the I2C packet + * @val: value to write + * + * Returns 0 on success, or else negative errno. + */ +int m5mols_write(struct v4l2_subdev *sd, u32 reg, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct m5mols_info *info = to_m5mols(sd); + u8 wbuf[M5MOLS_I2C_MAX_SIZE + 4]; + u8 category = I2C_CATEGORY(reg); + u8 cmd = I2C_COMMAND(reg); + u8 size = I2C_SIZE(reg); + u32 *buf = (u32 *)&wbuf[4]; + struct i2c_msg msg[1]; + int ret; + + if (!client->adapter) + return -ENODEV; + + if (size != 1 && size != 2 && size != 4) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + msg->addr = client->addr; + msg->flags = 0; + msg->len = (u16)size + 4; + msg->buf = wbuf; + wbuf[0] = size + 4; + wbuf[1] = M5MOLS_BYTE_WRITE; + wbuf[2] = category; + wbuf[3] = cmd; + + *buf = m5mols_swap_byte((u8 *)&val, size); + + usleep_range(200, 200); + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret == 1) + return 0; + + if (info->isp_ready) + v4l2_err(sd, "write failed: cat:%02x cmd:%02x ret:%d\n", + category, cmd, ret); + + return ret < 0 ? ret : -EIO; +} + +/** + * m5mols_busy_wait - Busy waiting with I2C register polling + * @reg: the I2C_REG() address of an 8-bit status register to check + * @value: expected status register value + * @mask: bit mask for the read status register value + * @timeout: timeout in miliseconds, or -1 for default timeout + * + * The @reg register value is ORed with @mask before comparing with @value. + * + * Return: 0 if the requested condition became true within less than + * @timeout ms, or else negative errno. + */ +int m5mols_busy_wait(struct v4l2_subdev *sd, u32 reg, u32 value, u32 mask, + int timeout) +{ + int ms = timeout < 0 ? M5MOLS_BUSY_WAIT_DEF_TIMEOUT : timeout; + unsigned long end = jiffies + msecs_to_jiffies(ms); + u8 status; + + do { + int ret = m5mols_read_u8(sd, reg, &status); + + if (ret < 0 && !(mask & M5MOLS_I2C_RDY_WAIT_FL)) + return ret; + if (!ret && (status & mask & 0xff) == (value & 0xff)) + return 0; + usleep_range(100, 250); + } while (ms > 0 && time_is_after_jiffies(end)); + + return -EBUSY; +} + +/** + * m5mols_enable_interrupt - Clear interrupt pending bits and unmask interrupts + * + * Before writing desired interrupt value the INT_FACTOR register should + * be read to clear pending interrupts. + */ +int m5mols_enable_interrupt(struct v4l2_subdev *sd, u8 reg) +{ + struct m5mols_info *info = to_m5mols(sd); + u8 mask = is_available_af(info) ? REG_INT_AF : 0; + u8 dummy; + int ret; + + ret = m5mols_read_u8(sd, SYSTEM_INT_FACTOR, &dummy); + if (!ret) + ret = m5mols_write(sd, SYSTEM_INT_ENABLE, reg & ~mask); + return ret; +} + +int m5mols_wait_interrupt(struct v4l2_subdev *sd, u8 irq_mask, u32 timeout) +{ + struct m5mols_info *info = to_m5mols(sd); + + int ret = wait_event_interruptible_timeout(info->irq_waitq, + atomic_add_unless(&info->irq_done, -1, 0), + msecs_to_jiffies(timeout)); + if (ret <= 0) + return ret ? ret : -ETIMEDOUT; + + return m5mols_busy_wait(sd, SYSTEM_INT_FACTOR, irq_mask, + M5MOLS_I2C_RDY_WAIT_FL | irq_mask, -1); +} + +/** + * m5mols_reg_mode - Write the mode and check busy status + * + * It always accompanies a little delay changing the M-5MOLS mode, so it is + * needed checking current busy status to guarantee right mode. + */ +static int m5mols_reg_mode(struct v4l2_subdev *sd, u8 mode) +{ + int ret = m5mols_write(sd, SYSTEM_SYSMODE, mode); + if (ret < 0) + return ret; + return m5mols_busy_wait(sd, SYSTEM_SYSMODE, mode, 0xff, + M5MOLS_MODE_CHANGE_TIMEOUT); +} + +/** + * m5mols_set_mode - set the M-5MOLS controller mode + * @mode: the required operation mode + * + * The commands of M-5MOLS are grouped into specific modes. Each functionality + * can be guaranteed only when the sensor is operating in mode which a command + * belongs to. + */ +int m5mols_set_mode(struct m5mols_info *info, u8 mode) +{ + struct v4l2_subdev *sd = &info->sd; + int ret = -EINVAL; + u8 reg; + + if (mode < REG_PARAMETER || mode > REG_CAPTURE) + return ret; + + ret = m5mols_read_u8(sd, SYSTEM_SYSMODE, ®); + if (ret || reg == mode) + return ret; + + switch (reg) { + case REG_PARAMETER: + ret = m5mols_reg_mode(sd, REG_MONITOR); + if (mode == REG_MONITOR) + break; + if (!ret) + ret = m5mols_reg_mode(sd, REG_CAPTURE); + break; + + case REG_MONITOR: + if (mode == REG_PARAMETER) { + ret = m5mols_reg_mode(sd, REG_PARAMETER); + break; + } + + ret = m5mols_reg_mode(sd, REG_CAPTURE); + break; + + case REG_CAPTURE: + ret = m5mols_reg_mode(sd, REG_MONITOR); + if (mode == REG_MONITOR) + break; + if (!ret) + ret = m5mols_reg_mode(sd, REG_PARAMETER); + break; + + default: + v4l2_warn(sd, "Wrong mode: %d\n", mode); + } + + if (!ret) + info->mode = mode; + + return ret; +} + +/** + * m5mols_get_version - retrieve full revisions information of M-5MOLS + * + * The version information includes revisions of hardware and firmware, + * AutoFocus alghorithm version and the version string. + */ +static int m5mols_get_version(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + struct m5mols_version *ver = &info->ver; + u8 *str = ver->str; + int i; + int ret; + + ret = m5mols_read_u8(sd, SYSTEM_VER_CUSTOMER, &ver->customer); + if (!ret) + ret = m5mols_read_u8(sd, SYSTEM_VER_PROJECT, &ver->project); + if (!ret) + ret = m5mols_read_u16(sd, SYSTEM_VER_FIRMWARE, &ver->fw); + if (!ret) + ret = m5mols_read_u16(sd, SYSTEM_VER_HARDWARE, &ver->hw); + if (!ret) + ret = m5mols_read_u16(sd, SYSTEM_VER_PARAMETER, &ver->param); + if (!ret) + ret = m5mols_read_u16(sd, SYSTEM_VER_AWB, &ver->awb); + if (!ret) + ret = m5mols_read_u8(sd, AF_VERSION, &ver->af); + if (ret) + return ret; + + for (i = 0; i < VERSION_STRING_SIZE; i++) { + ret = m5mols_read_u8(sd, SYSTEM_VER_STRING, &str[i]); + if (ret) + return ret; + } + + ver->fw = be16_to_cpu(ver->fw); + ver->hw = be16_to_cpu(ver->hw); + ver->param = be16_to_cpu(ver->param); + ver->awb = be16_to_cpu(ver->awb); + + v4l2_info(sd, "Manufacturer\t[%s]\n", + is_manufacturer(info, REG_SAMSUNG_ELECTRO) ? + "Samsung Electro-Machanics" : + is_manufacturer(info, REG_SAMSUNG_OPTICS) ? + "Samsung Fiber-Optics" : + is_manufacturer(info, REG_SAMSUNG_TECHWIN) ? + "Samsung Techwin" : "None"); + v4l2_info(sd, "Customer/Project\t[0x%02x/0x%02x]\n", + info->ver.customer, info->ver.project); + + if (!is_available_af(info)) + v4l2_info(sd, "No support Auto Focus on this firmware\n"); + + return ret; +} + +/** + * __find_restype - Lookup M-5MOLS resolution type according to pixel code + * @code: pixel code + */ +static enum m5mols_restype __find_restype(enum v4l2_mbus_pixelcode code) +{ + enum m5mols_restype type = M5MOLS_RESTYPE_MONITOR; + + do { + if (code == m5mols_default_ffmt[type].code) + return type; + } while (type++ != SIZE_DEFAULT_FFMT); + + return 0; +} + +/** + * __find_resolution - Lookup preset and type of M-5MOLS's resolution + * @mf: pixel format to find/negotiate the resolution preset for + * @type: M-5MOLS resolution type + * @resolution: M-5MOLS resolution preset register value + * + * Find nearest resolution matching resolution preset and adjust mf + * to supported values. + */ +static int __find_resolution(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf, + enum m5mols_restype *type, + u32 *resolution) +{ + const struct m5mols_resolution *fsize = &m5mols_reg_res[0]; + const struct m5mols_resolution *match = NULL; + enum m5mols_restype stype = __find_restype(mf->code); + int i = ARRAY_SIZE(m5mols_reg_res); + unsigned int min_err = ~0; + + while (i--) { + int err; + if (stype == fsize->type) { + err = abs(fsize->width - mf->width) + + abs(fsize->height - mf->height); + + if (err < min_err) { + min_err = err; + match = fsize; + } + } + fsize++; + } + if (match) { + mf->width = match->width; + mf->height = match->height; + *resolution = match->reg; + *type = stype; + return 0; + } + + return -EINVAL; +} + +static struct v4l2_mbus_framefmt *__find_format(struct m5mols_info *info, + struct v4l2_subdev_fh *fh, + enum v4l2_subdev_format_whence which, + enum m5mols_restype type) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return fh ? v4l2_subdev_get_try_format(fh, 0) : NULL; + + return &info->ffmt[type]; +} + +static int m5mols_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *format; + int ret = 0; + + mutex_lock(&info->lock); + + format = __find_format(info, fh, fmt->which, info->res_type); + if (!format) + fmt->format = *format; + else + ret = -EINVAL; + + mutex_unlock(&info->lock); + return ret; +} + +static int m5mols_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *format = &fmt->format; + struct v4l2_mbus_framefmt *sfmt; + enum m5mols_restype type; + u32 resolution = 0; + int ret; + + ret = __find_resolution(sd, format, &type, &resolution); + if (ret < 0) + return ret; + + sfmt = __find_format(info, fh, fmt->which, type); + if (!sfmt) + return 0; + + mutex_lock(&info->lock); + + format->code = m5mols_default_ffmt[type].code; + format->colorspace = V4L2_COLORSPACE_JPEG; + format->field = V4L2_FIELD_NONE; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + *sfmt = *format; + info->resolution = resolution; + info->res_type = type; + } + + mutex_unlock(&info->lock); + return ret; +} + +static int m5mols_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (!code || code->index >= SIZE_DEFAULT_FFMT) + return -EINVAL; + + code->code = m5mols_default_ffmt[code->index].code; + + return 0; +} + +static struct v4l2_subdev_pad_ops m5mols_pad_ops = { + .enum_mbus_code = m5mols_enum_mbus_code, + .get_fmt = m5mols_get_fmt, + .set_fmt = m5mols_set_fmt, +}; + +/** + * m5mols_restore_controls - Apply current control values to the registers + * + * m5mols_do_scenemode() handles all parameters for which there is yet no + * individual control. It should be replaced at some point by setting each + * control individually, in required register set up order. + */ +int m5mols_restore_controls(struct m5mols_info *info) +{ + int ret; + + if (info->ctrl_sync) + return 0; + + ret = m5mols_do_scenemode(info, REG_SCENE_NORMAL); + if (ret) + return ret; + + ret = v4l2_ctrl_handler_setup(&info->handle); + info->ctrl_sync = !ret; + + return ret; +} + +/** + * m5mols_start_monitor - Start the monitor mode + * + * Before applying the controls setup the resolution and frame rate + * in PARAMETER mode, and then switch over to MONITOR mode. + */ +static int m5mols_start_monitor(struct m5mols_info *info) +{ + struct v4l2_subdev *sd = &info->sd; + int ret; + + ret = m5mols_set_mode(info, REG_PARAMETER); + if (!ret) + ret = m5mols_write(sd, PARM_MON_SIZE, info->resolution); + if (!ret) + ret = m5mols_write(sd, PARM_MON_FPS, REG_FPS_30); + if (!ret) + ret = m5mols_set_mode(info, REG_MONITOR); + if (!ret) + ret = m5mols_restore_controls(info); + + return ret; +} + +static int m5mols_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct m5mols_info *info = to_m5mols(sd); + u32 code; + int ret; + + mutex_lock(&info->lock); + code = info->ffmt[info->res_type].code; + + if (enable) { + if (is_code(code, M5MOLS_RESTYPE_MONITOR)) + ret = m5mols_start_monitor(info); + if (is_code(code, M5MOLS_RESTYPE_CAPTURE)) + ret = m5mols_start_capture(info); + else + ret = -EINVAL; + } else { + ret = m5mols_set_mode(info, REG_PARAMETER); + } + + mutex_unlock(&info->lock); + return ret; +} + +static const struct v4l2_subdev_video_ops m5mols_video_ops = { + .s_stream = m5mols_s_stream, +}; + +static int m5mols_sensor_power(struct m5mols_info *info, bool enable) +{ + struct v4l2_subdev *sd = &info->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + const struct m5mols_platform_data *pdata = info->pdata; + int ret; + + if (info->power == enable) + return 0; + + if (enable) { + if (info->set_power) { + ret = info->set_power(&client->dev, 1); + if (ret) + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies); + if (ret) { + info->set_power(&client->dev, 0); + return ret; + } + + gpio_set_value(pdata->gpio_reset, !pdata->reset_polarity); + info->power = 1; + + return ret; + } + + ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies); + if (ret) + return ret; + + if (info->set_power) + info->set_power(&client->dev, 0); + + gpio_set_value(pdata->gpio_reset, pdata->reset_polarity); + + info->isp_ready = 0; + info->power = 0; + + return ret; +} + +/* m5mols_update_fw - optional firmware update routine */ +int __attribute__ ((weak)) m5mols_update_fw(struct v4l2_subdev *sd, + int (*set_power)(struct m5mols_info *, bool)) +{ + return 0; +} + +/** + * m5mols_fw_start - M-5MOLS internal ARM controller initialization + * + * Execute the M-5MOLS internal ARM controller initialization sequence. + * This function should be called after the supply voltage has been + * applied and before any requests to the device are made. + */ +static int m5mols_fw_start(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + int ret; + + atomic_set(&info->irq_done, 0); + /* Wait until I2C slave is initialized in Flash Writer mode */ + ret = m5mols_busy_wait(sd, FLASH_CAM_START, REG_IN_FLASH_MODE, + M5MOLS_I2C_RDY_WAIT_FL | 0xff, -1); + if (!ret) + ret = m5mols_write(sd, FLASH_CAM_START, REG_START_ARM_BOOT); + if (!ret) + ret = m5mols_wait_interrupt(sd, REG_INT_MODE, 2000); + if (ret < 0) + return ret; + + info->isp_ready = 1; + + ret = m5mols_get_version(sd); + if (!ret) + ret = m5mols_update_fw(sd, m5mols_sensor_power); + if (ret) + return ret; + + v4l2_dbg(1, m5mols_debug, sd, "Success ARM Booting\n"); + + ret = m5mols_write(sd, PARM_INTERFACE, REG_INTERFACE_MIPI); + if (!ret) + ret = m5mols_enable_interrupt(sd, + REG_INT_AF | REG_INT_CAPTURE); + + return ret; +} + +/* Execute the lens soft-landing algorithm */ +static int m5mols_auto_focus_stop(struct m5mols_info *info) +{ + int ret; + + ret = m5mols_write(&info->sd, AF_EXECUTE, REG_AF_STOP); + if (!ret) + ret = m5mols_write(&info->sd, AF_MODE, REG_AF_POWEROFF); + if (!ret) + ret = m5mols_busy_wait(&info->sd, SYSTEM_STATUS, REG_AF_IDLE, + 0xff, -1); + return ret; +} + +/** + * m5mols_s_power - Main sensor power control function + * + * To prevent breaking the lens when the sensor is powered off the Soft-Landing + * algorithm is called where available. The Soft-Landing algorithm availability + * dependends on the firmware provider. + */ +static int m5mols_s_power(struct v4l2_subdev *sd, int on) +{ + struct m5mols_info *info = to_m5mols(sd); + int ret; + + mutex_lock(&info->lock); + + if (on) { + ret = m5mols_sensor_power(info, true); + if (!ret) + ret = m5mols_fw_start(sd); + } else { + if (is_manufacturer(info, REG_SAMSUNG_TECHWIN)) { + ret = m5mols_set_mode(info, REG_MONITOR); + if (!ret) + ret = m5mols_auto_focus_stop(info); + if (ret < 0) + v4l2_warn(sd, "Soft landing lens failed\n"); + } + ret = m5mols_sensor_power(info, false); + + info->ctrl_sync = 0; + } + + mutex_unlock(&info->lock); + return ret; +} + +static int m5mols_log_status(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + + v4l2_ctrl_handler_log_status(&info->handle, sd->name); + + return 0; +} + +static const struct v4l2_subdev_core_ops m5mols_core_ops = { + .s_power = m5mols_s_power, + .log_status = m5mols_log_status, +}; + +/* + * V4L2 subdev internal operations + */ +static int m5mols_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(fh, 0); + + *format = m5mols_default_ffmt[0]; + return 0; +} + +static const struct v4l2_subdev_internal_ops m5mols_subdev_internal_ops = { + .open = m5mols_open, +}; + +static const struct v4l2_subdev_ops m5mols_ops = { + .core = &m5mols_core_ops, + .pad = &m5mols_pad_ops, + .video = &m5mols_video_ops, +}; + +static irqreturn_t m5mols_irq_handler(int irq, void *data) +{ + struct m5mols_info *info = to_m5mols(data); + + atomic_set(&info->irq_done, 1); + wake_up_interruptible(&info->irq_waitq); + + return IRQ_HANDLED; +} + +static int __devinit m5mols_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct m5mols_platform_data *pdata = client->dev.platform_data; + struct m5mols_info *info; + struct v4l2_subdev *sd; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!gpio_is_valid(pdata->gpio_reset)) { + dev_err(&client->dev, "No valid RESET GPIO specified\n"); + return -EINVAL; + } + + if (!client->irq) { + dev_err(&client->dev, "Interrupt not assigned\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(struct m5mols_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdata = pdata; + info->set_power = pdata->set_power; + + ret = gpio_request(pdata->gpio_reset, "M5MOLS_NRST"); + if (ret) { + dev_err(&client->dev, "Failed to request gpio: %d\n", ret); + goto out_free; + } + gpio_direction_output(pdata->gpio_reset, pdata->reset_polarity); + + ret = regulator_bulk_get(&client->dev, ARRAY_SIZE(supplies), supplies); + if (ret) { + dev_err(&client->dev, "Failed to get regulators: %d\n", ret); + goto out_gpio; + } + + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &m5mols_ops); + strlcpy(sd->name, MODULE_NAME, sizeof(sd->name)); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + sd->internal_ops = &m5mols_subdev_internal_ops; + info->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_init(&sd->entity, 1, &info->pad, 0); + if (ret < 0) + goto out_reg; + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; + + init_waitqueue_head(&info->irq_waitq); + mutex_init(&info->lock); + + ret = request_irq(client->irq, m5mols_irq_handler, + IRQF_TRIGGER_RISING, MODULE_NAME, sd); + if (ret) { + dev_err(&client->dev, "Interrupt request failed: %d\n", ret); + goto out_me; + } + info->res_type = M5MOLS_RESTYPE_MONITOR; + info->ffmt[0] = m5mols_default_ffmt[0]; + info->ffmt[1] = m5mols_default_ffmt[1]; + + ret = m5mols_sensor_power(info, true); + if (ret) + goto out_irq; + + ret = m5mols_fw_start(sd); + if (!ret) + ret = m5mols_init_controls(sd); + + ret = m5mols_sensor_power(info, false); + if (!ret) + return 0; +out_irq: + free_irq(client->irq, sd); +out_me: + media_entity_cleanup(&sd->entity); +out_reg: + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); +out_gpio: + gpio_free(pdata->gpio_reset); +out_free: + kfree(info); + return ret; +} + +static int __devexit m5mols_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct m5mols_info *info = to_m5mols(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + free_irq(client->irq, sd); + + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); + gpio_free(info->pdata->gpio_reset); + media_entity_cleanup(&sd->entity); + kfree(info); + return 0; +} + +static const struct i2c_device_id m5mols_id[] = { + { MODULE_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, m5mols_id); + +static struct i2c_driver m5mols_i2c_driver = { + .driver = { + .name = MODULE_NAME, + }, + .probe = m5mols_probe, + .remove = __devexit_p(m5mols_remove), + .id_table = m5mols_id, +}; + +module_i2c_driver(m5mols_i2c_driver); + +MODULE_AUTHOR("HeungJun Kim "); +MODULE_AUTHOR("Dongsoo Kim "); +MODULE_DESCRIPTION("Fujitsu M-5MOLS 8M Pixel camera driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/m5mols/m5mols_reg.h b/drivers/media/i2c/m5mols/m5mols_reg.h similarity index 100% rename from drivers/media/video/m5mols/m5mols_reg.h rename to drivers/media/i2c/m5mols/m5mols_reg.h diff --git a/drivers/media/i2c/msp3400-driver.c b/drivers/media/i2c/msp3400-driver.c new file mode 100644 index 000000000000..766305f69a28 --- /dev/null +++ b/drivers/media/i2c/msp3400-driver.c @@ -0,0 +1,927 @@ +/* + * Programming the mspx4xx sound processor family + * + * (c) 1997-2001 Gerd Knorr + * + * what works and what doesn't: + * + * AM-Mono + * Support for Hauppauge cards added (decoding handled by tuner) added by + * Frederic Crozat + * + * FM-Mono + * should work. The stereo modes are backward compatible to FM-mono, + * therefore FM-Mono should be allways available. + * + * FM-Stereo (B/G, used in germany) + * should work, with autodetect + * + * FM-Stereo (satellite) + * should work, no autodetect (i.e. default is mono, but you can + * switch to stereo -- untested) + * + * NICAM (B/G, L , used in UK, Scandinavia, Spain and France) + * should work, with autodetect. Support for NICAM was added by + * Pekka Pietikainen + * + * TODO: + * - better SAT support + * + * 980623 Thomas Sailer (sailer@ife.ee.ethz.ch) + * using soundcore instead of OSS + * + * 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. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msp3400-driver.h" + +/* ---------------------------------------------------------------------- */ + +MODULE_DESCRIPTION("device driver for msp34xx TV sound processor"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("GPL"); + +/* module parameters */ +static int opmode = OPMODE_AUTO; +int msp_debug; /* msp_debug output */ +bool msp_once; /* no continuous stereo monitoring */ +bool msp_amsound; /* hard-wire AM sound at 6.5 Hz (france), + the autoscan seems work well only with FM... */ +int msp_standard = 1; /* Override auto detect of audio msp_standard, + if needed. */ +bool msp_dolby; + +int msp_stereo_thresh = 0x190; /* a2 threshold for stereo/bilingual + (msp34xxg only) 0x00a0-0x03c0 */ + +/* read-only */ +module_param(opmode, int, 0444); + +/* read-write */ +module_param_named(once, msp_once, bool, 0644); +module_param_named(debug, msp_debug, int, 0644); +module_param_named(stereo_threshold, msp_stereo_thresh, int, 0644); +module_param_named(standard, msp_standard, int, 0644); +module_param_named(amsound, msp_amsound, bool, 0644); +module_param_named(dolby, msp_dolby, bool, 0644); + +MODULE_PARM_DESC(opmode, "Forces a MSP3400 opmode. 0=Manual, 1=Autodetect, 2=Autodetect and autoselect"); +MODULE_PARM_DESC(once, "No continuous stereo monitoring"); +MODULE_PARM_DESC(debug, "Enable debug messages [0-3]"); +MODULE_PARM_DESC(stereo_threshold, "Sets signal threshold to activate stereo"); +MODULE_PARM_DESC(standard, "Specify audio standard: 32 = NTSC, 64 = radio, Default: Autodetect"); +MODULE_PARM_DESC(amsound, "Hardwire AM sound at 6.5Hz (France), FM can autoscan"); +MODULE_PARM_DESC(dolby, "Activates Dolby processing"); + +/* ---------------------------------------------------------------------- */ + +/* control subaddress */ +#define I2C_MSP_CONTROL 0x00 +/* demodulator unit subaddress */ +#define I2C_MSP_DEM 0x10 +/* DSP unit subaddress */ +#define I2C_MSP_DSP 0x12 + + +/* ----------------------------------------------------------------------- */ +/* functions for talking to the MSP3400C Sound processor */ + +int msp_reset(struct i2c_client *client) +{ + /* reset and read revision code */ + static u8 reset_off[3] = { I2C_MSP_CONTROL, 0x80, 0x00 }; + static u8 reset_on[3] = { I2C_MSP_CONTROL, 0x00, 0x00 }; + static u8 write[3] = { I2C_MSP_DSP + 1, 0x00, 0x1e }; + u8 read[2]; + struct i2c_msg reset[2] = { + { + .addr = client->addr, + .flags = I2C_M_IGNORE_NAK, + .len = 3, + .buf = reset_off + }, + { + .addr = client->addr, + .flags = I2C_M_IGNORE_NAK, + .len = 3, + .buf = reset_on + }, + }; + struct i2c_msg test[2] = { + { + .addr = client->addr, + .len = 3, + .buf = write + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 2, + .buf = read + }, + }; + + v4l_dbg(3, msp_debug, client, "msp_reset\n"); + if (i2c_transfer(client->adapter, &reset[0], 1) != 1 || + i2c_transfer(client->adapter, &reset[1], 1) != 1 || + i2c_transfer(client->adapter, test, 2) != 2) { + v4l_err(client, "chip reset failed\n"); + return -1; + } + return 0; +} + +static int msp_read(struct i2c_client *client, int dev, int addr) +{ + int err, retval; + u8 write[3]; + u8 read[2]; + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .len = 3, + .buf = write + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 2, + .buf = read + } + }; + + write[0] = dev + 1; + write[1] = addr >> 8; + write[2] = addr & 0xff; + + for (err = 0; err < 3; err++) { + if (i2c_transfer(client->adapter, msgs, 2) == 2) + break; + v4l_warn(client, "I/O error #%d (read 0x%02x/0x%02x)\n", err, + dev, addr); + schedule_timeout_interruptible(msecs_to_jiffies(10)); + } + if (err == 3) { + v4l_warn(client, "resetting chip, sound will go off.\n"); + msp_reset(client); + return -1; + } + retval = read[0] << 8 | read[1]; + v4l_dbg(3, msp_debug, client, "msp_read(0x%x, 0x%x): 0x%x\n", + dev, addr, retval); + return retval; +} + +int msp_read_dem(struct i2c_client *client, int addr) +{ + return msp_read(client, I2C_MSP_DEM, addr); +} + +int msp_read_dsp(struct i2c_client *client, int addr) +{ + return msp_read(client, I2C_MSP_DSP, addr); +} + +static int msp_write(struct i2c_client *client, int dev, int addr, int val) +{ + int err; + u8 buffer[5]; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + + v4l_dbg(3, msp_debug, client, "msp_write(0x%x, 0x%x, 0x%x)\n", + dev, addr, val); + for (err = 0; err < 3; err++) { + if (i2c_master_send(client, buffer, 5) == 5) + break; + v4l_warn(client, "I/O error #%d (write 0x%02x/0x%02x)\n", err, + dev, addr); + schedule_timeout_interruptible(msecs_to_jiffies(10)); + } + if (err == 3) { + v4l_warn(client, "resetting chip, sound will go off.\n"); + msp_reset(client); + return -1; + } + return 0; +} + +int msp_write_dem(struct i2c_client *client, int addr, int val) +{ + return msp_write(client, I2C_MSP_DEM, addr, val); +} + +int msp_write_dsp(struct i2c_client *client, int addr, int val) +{ + return msp_write(client, I2C_MSP_DSP, addr, val); +} + +/* ----------------------------------------------------------------------- * + * bits 9 8 5 - SCART DSP input Select: + * 0 0 0 - SCART 1 to DSP input (reset position) + * 0 1 0 - MONO to DSP input + * 1 0 0 - SCART 2 to DSP input + * 1 1 1 - Mute DSP input + * + * bits 11 10 6 - SCART 1 Output Select: + * 0 0 0 - undefined (reset position) + * 0 1 0 - SCART 2 Input to SCART 1 Output (for devices with 2 SCARTS) + * 1 0 0 - MONO input to SCART 1 Output + * 1 1 0 - SCART 1 DA to SCART 1 Output + * 0 0 1 - SCART 2 DA to SCART 1 Output + * 0 1 1 - SCART 1 Input to SCART 1 Output + * 1 1 1 - Mute SCART 1 Output + * + * bits 13 12 7 - SCART 2 Output Select (for devices with 2 Output SCART): + * 0 0 0 - SCART 1 DA to SCART 2 Output (reset position) + * 0 1 0 - SCART 1 Input to SCART 2 Output + * 1 0 0 - MONO input to SCART 2 Output + * 0 0 1 - SCART 2 DA to SCART 2 Output + * 0 1 1 - SCART 2 Input to SCART 2 Output + * 1 1 0 - Mute SCART 2 Output + * + * Bits 4 to 0 should be zero. + * ----------------------------------------------------------------------- */ + +static int scarts[3][9] = { + /* MASK IN1 IN2 IN3 IN4 IN1_DA IN2_DA MONO MUTE */ + /* SCART DSP Input select */ + { 0x0320, 0x0000, 0x0200, 0x0300, 0x0020, -1, -1, 0x0100, 0x0320 }, + /* SCART1 Output select */ + { 0x0c40, 0x0440, 0x0400, 0x0000, 0x0840, 0x0c00, 0x0040, 0x0800, 0x0c40 }, + /* SCART2 Output select */ + { 0x3080, 0x1000, 0x1080, 0x2080, 0x3080, 0x0000, 0x0080, 0x2000, 0x3000 }, +}; + +static char *scart_names[] = { + "in1", "in2", "in3", "in4", "in1 da", "in2 da", "mono", "mute" +}; + +void msp_set_scart(struct i2c_client *client, int in, int out) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + state->in_scart = in; + + if (in >= 0 && in <= 7 && out >= 0 && out <= 2) { + if (-1 == scarts[out][in + 1]) + return; + + state->acb &= ~scarts[out][0]; + state->acb |= scarts[out][in + 1]; + } else + state->acb = 0xf60; /* Mute Input and SCART 1 Output */ + + v4l_dbg(1, msp_debug, client, "scart switch: %s => %d (ACB=0x%04x)\n", + scart_names[in], out, state->acb); + msp_write_dsp(client, 0x13, state->acb); + + /* Sets I2S speed 0 = 1.024 Mbps, 1 = 2.048 Mbps */ + if (state->has_i2s_conf) + msp_write_dem(client, 0x40, state->i2s_mode); +} + +/* ------------------------------------------------------------------------ */ + +static void msp_wake_thread(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + if (NULL == state->kthread) + return; + state->watch_stereo = 0; + state->restart = 1; + wake_up_interruptible(&state->wq); +} + +int msp_sleep(struct msp_state *state, int timeout) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&state->wq, &wait); + if (!kthread_should_stop()) { + if (timeout < 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } else { + schedule_timeout_interruptible + (msecs_to_jiffies(timeout)); + } + } + + remove_wait_queue(&state->wq, &wait); + try_to_freeze(); + return state->restart; +} + +/* ------------------------------------------------------------------------ */ + +static int msp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct msp_state *state = ctrl_to_state(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(&state->sd); + int val = ctrl->val; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: { + /* audio volume cluster */ + int reallymuted = state->muted->val | state->scan_in_progress; + + if (!reallymuted) + val = (val * 0x7f / 65535) << 8; + + v4l_dbg(1, msp_debug, client, "mute=%s scanning=%s volume=%d\n", + state->muted->val ? "on" : "off", + state->scan_in_progress ? "yes" : "no", + state->volume->val); + + msp_write_dsp(client, 0x0000, val); + msp_write_dsp(client, 0x0007, reallymuted ? 0x1 : (val | 0x1)); + if (state->has_scart2_out_volume) + msp_write_dsp(client, 0x0040, reallymuted ? 0x1 : (val | 0x1)); + if (state->has_headphones) + msp_write_dsp(client, 0x0006, val); + break; + } + + case V4L2_CID_AUDIO_BASS: + val = ((val - 32768) * 0x60 / 65535) << 8; + msp_write_dsp(client, 0x0002, val); + if (state->has_headphones) + msp_write_dsp(client, 0x0031, val); + break; + + case V4L2_CID_AUDIO_TREBLE: + val = ((val - 32768) * 0x60 / 65535) << 8; + msp_write_dsp(client, 0x0003, val); + if (state->has_headphones) + msp_write_dsp(client, 0x0032, val); + break; + + case V4L2_CID_AUDIO_LOUDNESS: + val = val ? ((5 * 4) << 8) : 0; + msp_write_dsp(client, 0x0004, val); + if (state->has_headphones) + msp_write_dsp(client, 0x0033, val); + break; + + case V4L2_CID_AUDIO_BALANCE: + val = (u8)((val / 256) - 128); + msp_write_dsp(client, 0x0001, val << 8); + if (state->has_headphones) + msp_write_dsp(client, 0x0030, val << 8); + break; + + default: + return -EINVAL; + } + return 0; +} + +void msp_update_volume(struct msp_state *state) +{ + /* Force an update of the volume/mute cluster */ + v4l2_ctrl_lock(state->volume); + state->volume->val = state->volume->cur.val; + state->muted->val = state->muted->cur.val; + msp_s_ctrl(state->volume); + v4l2_ctrl_unlock(state->volume); +} + +/* --- v4l2 ioctls --- */ +static int msp_s_radio(struct v4l2_subdev *sd) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (state->radio) + return 0; + state->radio = 1; + v4l_dbg(1, msp_debug, client, "switching to radio mode\n"); + state->watch_stereo = 0; + switch (state->opmode) { + case OPMODE_MANUAL: + /* set msp3400 to FM radio mode */ + msp3400c_set_mode(client, MSP_MODE_FM_RADIO); + msp3400c_set_carrier(client, MSP_CARRIER(10.7), + MSP_CARRIER(10.7)); + msp_update_volume(state); + break; + case OPMODE_AUTODETECT: + case OPMODE_AUTOSELECT: + /* the thread will do for us */ + msp_wake_thread(client); + break; + } + return 0; +} + +static int msp_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *freq) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* new channel -- kick audio carrier scan */ + msp_wake_thread(client); + return 0; +} + +static int msp_querystd(struct v4l2_subdev *sd, v4l2_std_id *id) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + *id &= state->detected_std; + + v4l_dbg(2, msp_debug, client, + "detected standard: %s(0x%08Lx)\n", + msp_standard_std_name(state->std), state->detected_std); + + return 0; +} + +static int msp_s_std(struct v4l2_subdev *sd, v4l2_std_id id) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int update = state->radio || state->v4l2_std != id; + + state->v4l2_std = id; + state->radio = 0; + if (update) + msp_wake_thread(client); + return 0; +} + +static int msp_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int tuner = (input >> 3) & 1; + int sc_in = input & 0x7; + int sc1_out = output & 0xf; + int sc2_out = (output >> 4) & 0xf; + u16 val, reg; + int i; + int extern_input = 1; + + if (state->route_in == input && state->route_out == output) + return 0; + state->route_in = input; + state->route_out = output; + /* check if the tuner input is used */ + for (i = 0; i < 5; i++) { + if (((input >> (4 + i * 4)) & 0xf) == 0) + extern_input = 0; + } + state->mode = extern_input ? MSP_MODE_EXTERN : MSP_MODE_AM_DETECT; + state->rxsubchans = V4L2_TUNER_SUB_STEREO; + msp_set_scart(client, sc_in, 0); + msp_set_scart(client, sc1_out, 1); + msp_set_scart(client, sc2_out, 2); + msp_set_audmode(client); + reg = (state->opmode == OPMODE_AUTOSELECT) ? 0x30 : 0xbb; + val = msp_read_dem(client, reg); + msp_write_dem(client, reg, (val & ~0x100) | (tuner << 8)); + /* wake thread when a new input is chosen */ + msp_wake_thread(client); + return 0; +} + +static int msp_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (vt->type != V4L2_TUNER_ANALOG_TV) + return 0; + if (!state->radio) { + if (state->opmode == OPMODE_AUTOSELECT) + msp_detect_stereo(client); + vt->rxsubchans = state->rxsubchans; + } + vt->audmode = state->audmode; + vt->capability |= V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; + return 0; +} + +static int msp_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (state->radio) /* TODO: add mono/stereo support for radio */ + return 0; + if (state->audmode == vt->audmode) + return 0; + state->audmode = vt->audmode; + /* only set audmode */ + msp_set_audmode(client); + return 0; +} + +static int msp_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + v4l_dbg(1, msp_debug, client, "Setting I2S speed to %d\n", freq); + + switch (freq) { + case 1024000: + state->i2s_mode = 0; + break; + case 2048000: + state->i2s_mode = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int msp_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return v4l2_chip_ident_i2c_client(client, chip, state->ident, + (state->rev1 << 16) | state->rev2); +} + +static int msp_log_status(struct v4l2_subdev *sd) +{ + struct msp_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + const char *p; + char prefix[V4L2_SUBDEV_NAME_SIZE + 20]; + + if (state->opmode == OPMODE_AUTOSELECT) + msp_detect_stereo(client); + v4l_info(client, "%s rev1 = 0x%04x rev2 = 0x%04x\n", + client->name, state->rev1, state->rev2); + snprintf(prefix, sizeof(prefix), "%s: Audio: ", sd->name); + v4l2_ctrl_handler_log_status(&state->hdl, prefix); + switch (state->mode) { + case MSP_MODE_AM_DETECT: p = "AM (for carrier detect)"; break; + case MSP_MODE_FM_RADIO: p = "FM Radio"; break; + case MSP_MODE_FM_TERRA: p = "Terrestrial FM-mono/stereo"; break; + case MSP_MODE_FM_SAT: p = "Satellite FM-mono"; break; + case MSP_MODE_FM_NICAM1: p = "NICAM/FM (B/G, D/K)"; break; + case MSP_MODE_FM_NICAM2: p = "NICAM/FM (I)"; break; + case MSP_MODE_AM_NICAM: p = "NICAM/AM (L)"; break; + case MSP_MODE_BTSC: p = "BTSC"; break; + case MSP_MODE_EXTERN: p = "External input"; break; + default: p = "unknown"; break; + } + if (state->mode == MSP_MODE_EXTERN) { + v4l_info(client, "Mode: %s\n", p); + } else if (state->opmode == OPMODE_MANUAL) { + v4l_info(client, "Mode: %s (%s%s)\n", p, + (state->rxsubchans & V4L2_TUNER_SUB_STEREO) ? "stereo" : "mono", + (state->rxsubchans & V4L2_TUNER_SUB_LANG2) ? ", dual" : ""); + } else { + if (state->opmode == OPMODE_AUTODETECT) + v4l_info(client, "Mode: %s\n", p); + v4l_info(client, "Standard: %s (%s%s)\n", + msp_standard_std_name(state->std), + (state->rxsubchans & V4L2_TUNER_SUB_STEREO) ? "stereo" : "mono", + (state->rxsubchans & V4L2_TUNER_SUB_LANG2) ? ", dual" : ""); + } + v4l_info(client, "Audmode: 0x%04x\n", state->audmode); + v4l_info(client, "Routing: 0x%08x (input) 0x%08x (output)\n", + state->route_in, state->route_out); + v4l_info(client, "ACB: 0x%04x\n", state->acb); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int msp_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + v4l_dbg(1, msp_debug, client, "suspend\n"); + msp_reset(client); + return 0; +} + +static int msp_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + v4l_dbg(1, msp_debug, client, "resume\n"); + msp_wake_thread(client); + return 0; +} +#endif + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops msp_ctrl_ops = { + .s_ctrl = msp_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops msp_core_ops = { + .log_status = msp_log_status, + .g_chip_ident = msp_g_chip_ident, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, + .s_std = msp_s_std, +}; + +static const struct v4l2_subdev_video_ops msp_video_ops = { + .querystd = msp_querystd, +}; + +static const struct v4l2_subdev_tuner_ops msp_tuner_ops = { + .s_frequency = msp_s_frequency, + .g_tuner = msp_g_tuner, + .s_tuner = msp_s_tuner, + .s_radio = msp_s_radio, +}; + +static const struct v4l2_subdev_audio_ops msp_audio_ops = { + .s_routing = msp_s_routing, + .s_i2s_clock_freq = msp_s_i2s_clock_freq, +}; + +static const struct v4l2_subdev_ops msp_ops = { + .core = &msp_core_ops, + .video = &msp_video_ops, + .tuner = &msp_tuner_ops, + .audio = &msp_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int msp_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct msp_state *state; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + int (*thread_func)(void *data) = NULL; + int msp_hard; + int msp_family; + int msp_revision; + int msp_product, msp_prod_hi, msp_prod_lo; + int msp_rom; + + if (!id) + strlcpy(client->name, "msp3400", sizeof(client->name)); + + if (msp_reset(client) == -1) { + v4l_dbg(1, msp_debug, client, "msp3400 not found\n"); + return -ENODEV; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &msp_ops); + + state->v4l2_std = V4L2_STD_NTSC; + state->detected_std = V4L2_STD_ALL; + state->audmode = V4L2_TUNER_MODE_STEREO; + state->input = -1; + state->i2s_mode = 0; + init_waitqueue_head(&state->wq); + /* These are the reset input/output positions */ + state->route_in = MSP_INPUT_DEFAULT; + state->route_out = MSP_OUTPUT_DEFAULT; + + state->rev1 = msp_read_dsp(client, 0x1e); + if (state->rev1 != -1) + state->rev2 = msp_read_dsp(client, 0x1f); + v4l_dbg(1, msp_debug, client, "rev1=0x%04x, rev2=0x%04x\n", + state->rev1, state->rev2); + if (state->rev1 == -1 || (state->rev1 == 0 && state->rev2 == 0)) { + v4l_dbg(1, msp_debug, client, + "not an msp3400 (cannot read chip version)\n"); + kfree(state); + return -ENODEV; + } + + msp_family = ((state->rev1 >> 4) & 0x0f) + 3; + msp_product = (state->rev2 >> 8) & 0xff; + msp_prod_hi = msp_product / 10; + msp_prod_lo = msp_product % 10; + msp_revision = (state->rev1 & 0x0f) + '@'; + msp_hard = ((state->rev1 >> 8) & 0xff) + '@'; + msp_rom = state->rev2 & 0x1f; + /* Rev B=2, C=3, D=4, G=7 */ + state->ident = msp_family * 10000 + 4000 + msp_product * 10 + + msp_revision - '@'; + + /* Has NICAM support: all mspx41x and mspx45x products have NICAM */ + state->has_nicam = + msp_prod_hi == 1 || msp_prod_hi == 5; + /* Has radio support: was added with revision G */ + state->has_radio = + msp_revision >= 'G'; + /* Has headphones output: not for stripped down products */ + state->has_headphones = + msp_prod_lo < 5; + /* Has scart2 input: not in stripped down products of the '3' family */ + state->has_scart2 = + msp_family >= 4 || msp_prod_lo < 7; + /* Has scart3 input: not in stripped down products of the '3' family */ + state->has_scart3 = + msp_family >= 4 || msp_prod_lo < 5; + /* Has scart4 input: not in pre D revisions, not in stripped D revs */ + state->has_scart4 = + msp_family >= 4 || (msp_revision >= 'D' && msp_prod_lo < 5); + /* Has scart2 output: not in stripped down products of + * the '3' family */ + state->has_scart2_out = + msp_family >= 4 || msp_prod_lo < 5; + /* Has scart2 a volume control? Not in pre-D revisions. */ + state->has_scart2_out_volume = + msp_revision > 'C' && state->has_scart2_out; + /* Has a configurable i2s out? */ + state->has_i2s_conf = + msp_revision >= 'G' && msp_prod_lo < 7; + /* Has subwoofer output: not in pre-D revs and not in stripped down + * products */ + state->has_subwoofer = + msp_revision >= 'D' && msp_prod_lo < 5; + /* Has soundprocessing (bass/treble/balance/loudness/equalizer): + * not in stripped down products */ + state->has_sound_processing = + msp_prod_lo < 7; + /* Has Virtual Dolby Surround: only in msp34x1 */ + state->has_virtual_dolby_surround = + msp_revision == 'G' && msp_prod_lo == 1; + /* Has Virtual Dolby Surround & Dolby Pro Logic: only in msp34x2 */ + state->has_dolby_pro_logic = + msp_revision == 'G' && msp_prod_lo == 2; + /* The msp343xG supports BTSC only and cannot do Automatic Standard + * Detection. */ + state->force_btsc = + msp_family == 3 && msp_revision == 'G' && msp_prod_hi == 3; + + state->opmode = opmode; + if (state->opmode == OPMODE_AUTO) { + /* MSP revision G and up have both autodetect and autoselect */ + if (msp_revision >= 'G') + state->opmode = OPMODE_AUTOSELECT; + /* MSP revision D and up have autodetect */ + else if (msp_revision >= 'D') + state->opmode = OPMODE_AUTODETECT; + else + state->opmode = OPMODE_MANUAL; + } + + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 6); + if (state->has_sound_processing) { + v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_BASS, 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_LOUDNESS, 0, 1, 1, 0); + } + state->volume = v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 65535, 65535 / 100, 58880); + v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, 0, 65535, 65535 / 100, 32768); + state->muted = v4l2_ctrl_new_std(hdl, &msp_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + kfree(state); + return err; + } + + v4l2_ctrl_cluster(2, &state->volume); + v4l2_ctrl_handler_setup(hdl); + + /* hello world :-) */ + v4l_info(client, "MSP%d4%02d%c-%c%d found @ 0x%x (%s)\n", + msp_family, msp_product, + msp_revision, msp_hard, msp_rom, + client->addr << 1, client->adapter->name); + v4l_info(client, "%s ", client->name); + if (state->has_nicam && state->has_radio) + printk(KERN_CONT "supports nicam and radio, "); + else if (state->has_nicam) + printk(KERN_CONT "supports nicam, "); + else if (state->has_radio) + printk(KERN_CONT "supports radio, "); + printk(KERN_CONT "mode is "); + + /* version-specific initialization */ + switch (state->opmode) { + case OPMODE_MANUAL: + printk(KERN_CONT "manual"); + thread_func = msp3400c_thread; + break; + case OPMODE_AUTODETECT: + printk(KERN_CONT "autodetect"); + thread_func = msp3410d_thread; + break; + case OPMODE_AUTOSELECT: + printk(KERN_CONT "autodetect and autoselect"); + thread_func = msp34xxg_thread; + break; + } + printk(KERN_CONT "\n"); + + /* startup control thread if needed */ + if (thread_func) { + state->kthread = kthread_run(thread_func, client, "msp34xx"); + + if (IS_ERR(state->kthread)) + v4l_warn(client, "kernel_thread() failed\n"); + msp_wake_thread(client); + } + return 0; +} + +static int msp_remove(struct i2c_client *client) +{ + struct msp_state *state = to_state(i2c_get_clientdata(client)); + + v4l2_device_unregister_subdev(&state->sd); + /* shutdown control thread */ + if (state->kthread) { + state->restart = 1; + kthread_stop(state->kthread); + } + msp_reset(client); + + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct dev_pm_ops msp3400_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(msp_suspend, msp_resume) +}; + +static const struct i2c_device_id msp_id[] = { + { "msp3400", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, msp_id); + +static struct i2c_driver msp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "msp3400", + .pm = &msp3400_pm_ops, + }, + .probe = msp_probe, + .remove = msp_remove, + .id_table = msp_id, +}; + +module_i2c_driver(msp_driver); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/msp3400-driver.h b/drivers/media/i2c/msp3400-driver.h similarity index 100% rename from drivers/media/video/msp3400-driver.h rename to drivers/media/i2c/msp3400-driver.h diff --git a/drivers/media/video/msp3400-kthreads.c b/drivers/media/i2c/msp3400-kthreads.c similarity index 100% rename from drivers/media/video/msp3400-kthreads.c rename to drivers/media/i2c/msp3400-kthreads.c diff --git a/drivers/media/i2c/mt9m032.c b/drivers/media/i2c/mt9m032.c new file mode 100644 index 000000000000..f80c1d7ec884 --- /dev/null +++ b/drivers/media/i2c/mt9m032.c @@ -0,0 +1,878 @@ +/* + * Driver for MT9M032 CMOS Image Sensor from Micron + * + * Copyright (C) 2010-2011 Lund Engineering + * Contact: Gil Lund + * Author: Martin Hostettler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "aptina-pll.h" + +/* + * width and height include active boundary and black parts + * + * column 0- 15 active boundary + * column 16-1455 image + * column 1456-1471 active boundary + * column 1472-1599 black + * + * row 0- 51 black + * row 53- 59 active boundary + * row 60-1139 image + * row 1140-1147 active boundary + * row 1148-1151 black + */ + +#define MT9M032_PIXEL_ARRAY_WIDTH 1600 +#define MT9M032_PIXEL_ARRAY_HEIGHT 1152 + +#define MT9M032_CHIP_VERSION 0x00 +#define MT9M032_CHIP_VERSION_VALUE 0x1402 +#define MT9M032_ROW_START 0x01 +#define MT9M032_ROW_START_MIN 0 +#define MT9M032_ROW_START_MAX 1152 +#define MT9M032_ROW_START_DEF 60 +#define MT9M032_COLUMN_START 0x02 +#define MT9M032_COLUMN_START_MIN 0 +#define MT9M032_COLUMN_START_MAX 1600 +#define MT9M032_COLUMN_START_DEF 16 +#define MT9M032_ROW_SIZE 0x03 +#define MT9M032_ROW_SIZE_MIN 32 +#define MT9M032_ROW_SIZE_MAX 1152 +#define MT9M032_ROW_SIZE_DEF 1080 +#define MT9M032_COLUMN_SIZE 0x04 +#define MT9M032_COLUMN_SIZE_MIN 32 +#define MT9M032_COLUMN_SIZE_MAX 1600 +#define MT9M032_COLUMN_SIZE_DEF 1440 +#define MT9M032_HBLANK 0x05 +#define MT9M032_VBLANK 0x06 +#define MT9M032_VBLANK_MAX 0x7ff +#define MT9M032_SHUTTER_WIDTH_HIGH 0x08 +#define MT9M032_SHUTTER_WIDTH_LOW 0x09 +#define MT9M032_SHUTTER_WIDTH_MIN 1 +#define MT9M032_SHUTTER_WIDTH_MAX 1048575 +#define MT9M032_SHUTTER_WIDTH_DEF 1943 +#define MT9M032_PIX_CLK_CTRL 0x0a +#define MT9M032_PIX_CLK_CTRL_INV_PIXCLK 0x8000 +#define MT9M032_RESTART 0x0b +#define MT9M032_RESET 0x0d +#define MT9M032_PLL_CONFIG1 0x11 +#define MT9M032_PLL_CONFIG1_OUTDIV_MASK 0x3f +#define MT9M032_PLL_CONFIG1_MUL_SHIFT 8 +#define MT9M032_READ_MODE1 0x1e +#define MT9M032_READ_MODE2 0x20 +#define MT9M032_READ_MODE2_VFLIP_SHIFT 15 +#define MT9M032_READ_MODE2_HFLIP_SHIFT 14 +#define MT9M032_READ_MODE2_ROW_BLC 0x40 +#define MT9M032_GAIN_GREEN1 0x2b +#define MT9M032_GAIN_BLUE 0x2c +#define MT9M032_GAIN_RED 0x2d +#define MT9M032_GAIN_GREEN2 0x2e + +/* write only */ +#define MT9M032_GAIN_ALL 0x35 +#define MT9M032_GAIN_DIGITAL_MASK 0x7f +#define MT9M032_GAIN_DIGITAL_SHIFT 8 +#define MT9M032_GAIN_AMUL_SHIFT 6 +#define MT9M032_GAIN_ANALOG_MASK 0x3f +#define MT9M032_FORMATTER1 0x9e +#define MT9M032_FORMATTER2 0x9f +#define MT9M032_FORMATTER2_DOUT_EN 0x1000 +#define MT9M032_FORMATTER2_PIXCLK_EN 0x2000 + +/* + * The available MT9M032 datasheet is missing documentation for register 0x10 + * MT9P031 seems to be close enough, so use constants from that datasheet for + * now. + * But keep the name MT9P031 to remind us, that this isn't really confirmed + * for this sensor. + */ +#define MT9P031_PLL_CONTROL 0x10 +#define MT9P031_PLL_CONTROL_PWROFF 0x0050 +#define MT9P031_PLL_CONTROL_PWRON 0x0051 +#define MT9P031_PLL_CONTROL_USEPLL 0x0052 +#define MT9P031_PLL_CONFIG2 0x11 +#define MT9P031_PLL_CONFIG2_P1_DIV_MASK 0x1f + +struct mt9m032 { + struct v4l2_subdev subdev; + struct media_pad pad; + struct mt9m032_platform_data *pdata; + + unsigned int pix_clock; + + struct v4l2_ctrl_handler ctrls; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + + struct mutex lock; /* Protects streaming, format, interval and crop */ + + bool streaming; + + struct v4l2_mbus_framefmt format; + struct v4l2_rect crop; + struct v4l2_fract frame_interval; +}; + +#define to_mt9m032(sd) container_of(sd, struct mt9m032, subdev) +#define to_dev(sensor) \ + (&((struct i2c_client *)v4l2_get_subdevdata(&(sensor)->subdev))->dev) + +static int mt9m032_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int mt9m032_write(struct i2c_client *client, u8 reg, const u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static u32 mt9m032_row_time(struct mt9m032 *sensor, unsigned int width) +{ + unsigned int effective_width; + u32 ns; + + effective_width = width + 716; /* empirical value */ + ns = div_u64(1000000000ULL * effective_width, sensor->pix_clock); + dev_dbg(to_dev(sensor), "MT9M032 line time: %u ns\n", ns); + return ns; +} + +static int mt9m032_update_timing(struct mt9m032 *sensor, + struct v4l2_fract *interval) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + struct v4l2_rect *crop = &sensor->crop; + unsigned int min_vblank; + unsigned int vblank; + u32 row_time; + + if (!interval) + interval = &sensor->frame_interval; + + row_time = mt9m032_row_time(sensor, crop->width); + + vblank = div_u64(1000000000ULL * interval->numerator, + (u64)row_time * interval->denominator) + - crop->height; + + if (vblank > MT9M032_VBLANK_MAX) { + /* hardware limits to 11 bit values */ + interval->denominator = 1000; + interval->numerator = + div_u64((crop->height + MT9M032_VBLANK_MAX) * + (u64)row_time * interval->denominator, + 1000000000ULL); + vblank = div_u64(1000000000ULL * interval->numerator, + (u64)row_time * interval->denominator) + - crop->height; + } + /* enforce minimal 1.6ms blanking time. */ + min_vblank = 1600000 / row_time; + vblank = clamp_t(unsigned int, vblank, min_vblank, MT9M032_VBLANK_MAX); + + return mt9m032_write(client, MT9M032_VBLANK, vblank); +} + +static int mt9m032_update_geom_timing(struct mt9m032 *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int ret; + + ret = mt9m032_write(client, MT9M032_COLUMN_SIZE, + sensor->crop.width - 1); + if (!ret) + ret = mt9m032_write(client, MT9M032_ROW_SIZE, + sensor->crop.height - 1); + if (!ret) + ret = mt9m032_write(client, MT9M032_COLUMN_START, + sensor->crop.left); + if (!ret) + ret = mt9m032_write(client, MT9M032_ROW_START, + sensor->crop.top); + if (!ret) + ret = mt9m032_update_timing(sensor, NULL); + return ret; +} + +static int update_formatter2(struct mt9m032 *sensor, bool streaming) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + u16 reg_val = MT9M032_FORMATTER2_DOUT_EN + | 0x0070; /* parts reserved! */ + /* possibly for changing to 14-bit mode */ + + if (streaming) + reg_val |= MT9M032_FORMATTER2_PIXCLK_EN; /* pixclock enable */ + + return mt9m032_write(client, MT9M032_FORMATTER2, reg_val); +} + +static int mt9m032_setup_pll(struct mt9m032 *sensor) +{ + static const struct aptina_pll_limits limits = { + .ext_clock_min = 8000000, + .ext_clock_max = 16500000, + .int_clock_min = 2000000, + .int_clock_max = 24000000, + .out_clock_min = 322000000, + .out_clock_max = 693000000, + .pix_clock_max = 99000000, + .n_min = 1, + .n_max = 64, + .m_min = 16, + .m_max = 255, + .p1_min = 1, + .p1_max = 128, + }; + + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + struct mt9m032_platform_data *pdata = sensor->pdata; + struct aptina_pll pll; + int ret; + + pll.ext_clock = pdata->ext_clock; + pll.pix_clock = pdata->pix_clock; + + ret = aptina_pll_calculate(&client->dev, &limits, &pll); + if (ret < 0) + return ret; + + sensor->pix_clock = pdata->pix_clock; + + ret = mt9m032_write(client, MT9M032_PLL_CONFIG1, + (pll.m << MT9M032_PLL_CONFIG1_MUL_SHIFT) + | (pll.p1 - 1)); + if (!ret) + ret = mt9m032_write(client, MT9P031_PLL_CONFIG2, pll.n - 1); + if (!ret) + ret = mt9m032_write(client, MT9P031_PLL_CONTROL, + MT9P031_PLL_CONTROL_PWRON | + MT9P031_PLL_CONTROL_USEPLL); + if (!ret) /* more reserved, Continuous, Master Mode */ + ret = mt9m032_write(client, MT9M032_READ_MODE1, 0x8006); + if (!ret) /* Set 14-bit mode, select 7 divider */ + ret = mt9m032_write(client, MT9M032_FORMATTER1, 0x111e); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Subdev pad operations + */ + +static int mt9m032_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = V4L2_MBUS_FMT_Y8_1X8; + return 0; +} + +static int mt9m032_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index != 0 || fse->code != V4L2_MBUS_FMT_Y8_1X8) + return -EINVAL; + + fse->min_width = MT9M032_COLUMN_SIZE_DEF; + fse->max_width = MT9M032_COLUMN_SIZE_DEF; + fse->min_height = MT9M032_ROW_SIZE_DEF; + fse->max_height = MT9M032_ROW_SIZE_DEF; + + return 0; +} + +/** + * __mt9m032_get_pad_crop() - get crop rect + * @sensor: pointer to the sensor struct + * @fh: file handle for getting the try crop rect from + * @which: select try or active crop rect + * + * Returns a pointer the current active or fh relative try crop rect + */ +static struct v4l2_rect * +__mt9m032_get_pad_crop(struct mt9m032 *sensor, struct v4l2_subdev_fh *fh, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(fh, 0); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &sensor->crop; + default: + return NULL; + } +} + +/** + * __mt9m032_get_pad_format() - get format + * @sensor: pointer to the sensor struct + * @fh: file handle for getting the try format from + * @which: select try or active format + * + * Returns a pointer the current active or fh relative try format + */ +static struct v4l2_mbus_framefmt * +__mt9m032_get_pad_format(struct mt9m032 *sensor, struct v4l2_subdev_fh *fh, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(fh, 0); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &sensor->format; + default: + return NULL; + } +} + +static int mt9m032_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + + mutex_lock(&sensor->lock); + fmt->format = *__mt9m032_get_pad_format(sensor, fh, fmt->which); + mutex_unlock(&sensor->lock); + + return 0; +} + +static int mt9m032_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + int ret; + + mutex_lock(&sensor->lock); + + if (sensor->streaming && fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ret = -EBUSY; + goto done; + } + + /* Scaling is not supported, the format is thus fixed. */ + fmt->format = *__mt9m032_get_pad_format(sensor, fh, fmt->which); + ret = 0; + +done: + mutex_unlock(&sensor->lock); + return ret; +} + +static int mt9m032_get_pad_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + + mutex_lock(&sensor->lock); + crop->rect = *__mt9m032_get_pad_crop(sensor, fh, crop->which); + mutex_unlock(&sensor->lock); + + return 0; +} + +static int mt9m032_set_pad_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *__crop; + struct v4l2_rect rect; + int ret = 0; + + mutex_lock(&sensor->lock); + + if (sensor->streaming && crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ret = -EBUSY; + goto done; + } + + /* Clamp the crop rectangle boundaries and align them to a multiple of 2 + * pixels to ensure a GRBG Bayer pattern. + */ + rect.left = clamp(ALIGN(crop->rect.left, 2), MT9M032_COLUMN_START_MIN, + MT9M032_COLUMN_START_MAX); + rect.top = clamp(ALIGN(crop->rect.top, 2), MT9M032_ROW_START_MIN, + MT9M032_ROW_START_MAX); + rect.width = clamp(ALIGN(crop->rect.width, 2), MT9M032_COLUMN_SIZE_MIN, + MT9M032_COLUMN_SIZE_MAX); + rect.height = clamp(ALIGN(crop->rect.height, 2), MT9M032_ROW_SIZE_MIN, + MT9M032_ROW_SIZE_MAX); + + rect.width = min(rect.width, MT9M032_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min(rect.height, MT9M032_PIXEL_ARRAY_HEIGHT - rect.top); + + __crop = __mt9m032_get_pad_crop(sensor, fh, crop->which); + + if (rect.width != __crop->width || rect.height != __crop->height) { + /* Reset the output image size if the crop rectangle size has + * been modified. + */ + format = __mt9m032_get_pad_format(sensor, fh, crop->which); + format->width = rect.width; + format->height = rect.height; + } + + *__crop = rect; + crop->rect = rect; + + if (crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) + ret = mt9m032_update_geom_timing(sensor); + +done: + mutex_unlock(&sensor->lock); + return ret; +} + +static int mt9m032_get_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + + mutex_lock(&sensor->lock); + memset(fi, 0, sizeof(*fi)); + fi->interval = sensor->frame_interval; + mutex_unlock(&sensor->lock); + + return 0; +} + +static int mt9m032_set_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + int ret; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto done; + } + + /* Avoid divisions by 0. */ + if (fi->interval.denominator == 0) + fi->interval.denominator = 1; + + ret = mt9m032_update_timing(sensor, &fi->interval); + if (!ret) + sensor->frame_interval = fi->interval; + +done: + mutex_unlock(&sensor->lock); + return ret; +} + +static int mt9m032_s_stream(struct v4l2_subdev *subdev, int streaming) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + int ret; + + mutex_lock(&sensor->lock); + ret = update_formatter2(sensor, streaming); + if (!ret) + sensor->streaming = streaming; + mutex_unlock(&sensor->lock); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9m032_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct mt9m032 *sensor = to_mt9m032(sd); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int val; + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + if (reg->match.addr != client->addr) + return -ENODEV; + + val = mt9m032_read(client, reg->reg); + if (val < 0) + return -EIO; + + reg->size = 2; + reg->val = val; + + return 0; +} + +static int mt9m032_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct mt9m032 *sensor = to_mt9m032(sd); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + return mt9m032_write(client, reg->reg, reg->val); +} +#endif + +/* ----------------------------------------------------------------------------- + * V4L2 subdev control operations + */ + +static int update_read_mode2(struct mt9m032 *sensor, bool vflip, bool hflip) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int reg_val = (vflip << MT9M032_READ_MODE2_VFLIP_SHIFT) + | (hflip << MT9M032_READ_MODE2_HFLIP_SHIFT) + | MT9M032_READ_MODE2_ROW_BLC + | 0x0007; + + return mt9m032_write(client, MT9M032_READ_MODE2, reg_val); +} + +static int mt9m032_set_gain(struct mt9m032 *sensor, s32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int digital_gain_val; /* in 1/8th (0..127) */ + int analog_mul; /* 0 or 1 */ + int analog_gain_val; /* in 1/16th. (0..63) */ + u16 reg_val; + + digital_gain_val = 51; /* from setup example */ + + if (val < 63) { + analog_mul = 0; + analog_gain_val = val; + } else { + analog_mul = 1; + analog_gain_val = val / 2; + } + + /* a_gain = (1 + analog_mul) + (analog_gain_val + 1) / 16 */ + /* overall_gain = a_gain * (1 + digital_gain_val / 8) */ + + reg_val = ((digital_gain_val & MT9M032_GAIN_DIGITAL_MASK) + << MT9M032_GAIN_DIGITAL_SHIFT) + | ((analog_mul & 1) << MT9M032_GAIN_AMUL_SHIFT) + | (analog_gain_val & MT9M032_GAIN_ANALOG_MASK); + + return mt9m032_write(client, MT9M032_GAIN_ALL, reg_val); +} + +static int mt9m032_try_ctrl(struct v4l2_ctrl *ctrl) +{ + if (ctrl->id == V4L2_CID_GAIN && ctrl->val >= 63) { + /* round because of multiplier used for values >= 63 */ + ctrl->val &= ~1; + } + + return 0; +} + +static int mt9m032_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m032 *sensor = + container_of(ctrl->handler, struct mt9m032, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int ret; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + return mt9m032_set_gain(sensor, ctrl->val); + + case V4L2_CID_HFLIP: + /* case V4L2_CID_VFLIP: -- In the same cluster */ + return update_read_mode2(sensor, sensor->vflip->val, + sensor->hflip->val); + + case V4L2_CID_EXPOSURE: + ret = mt9m032_write(client, MT9M032_SHUTTER_WIDTH_HIGH, + (ctrl->val >> 16) & 0xffff); + if (ret < 0) + return ret; + + return mt9m032_write(client, MT9M032_SHUTTER_WIDTH_LOW, + ctrl->val & 0xffff); + } + + return 0; +} + +static struct v4l2_ctrl_ops mt9m032_ctrl_ops = { + .s_ctrl = mt9m032_set_ctrl, + .try_ctrl = mt9m032_try_ctrl, +}; + +/* -------------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops mt9m032_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9m032_g_register, + .s_register = mt9m032_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops mt9m032_video_ops = { + .s_stream = mt9m032_s_stream, + .g_frame_interval = mt9m032_get_frame_interval, + .s_frame_interval = mt9m032_set_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops mt9m032_pad_ops = { + .enum_mbus_code = mt9m032_enum_mbus_code, + .enum_frame_size = mt9m032_enum_frame_size, + .get_fmt = mt9m032_get_pad_format, + .set_fmt = mt9m032_set_pad_format, + .set_crop = mt9m032_set_pad_crop, + .get_crop = mt9m032_get_pad_crop, +}; + +static const struct v4l2_subdev_ops mt9m032_ops = { + .core = &mt9m032_core_ops, + .video = &mt9m032_video_ops, + .pad = &mt9m032_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Driver initialization and probing + */ + +static int mt9m032_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct mt9m032_platform_data *pdata = client->dev.platform_data; + struct i2c_adapter *adapter = client->adapter; + struct mt9m032 *sensor; + int chip_version; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&client->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + if (!client->dev.platform_data) + return -ENODEV; + + sensor = kzalloc(sizeof(*sensor), GFP_KERNEL); + if (sensor == NULL) + return -ENOMEM; + + mutex_init(&sensor->lock); + + sensor->pdata = pdata; + + v4l2_i2c_subdev_init(&sensor->subdev, client, &mt9m032_ops); + sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + chip_version = mt9m032_read(client, MT9M032_CHIP_VERSION); + if (chip_version != MT9M032_CHIP_VERSION_VALUE) { + dev_err(&client->dev, "MT9M032 not detected, wrong version " + "0x%04x\n", chip_version); + ret = -ENODEV; + goto error_sensor; + } + + dev_info(&client->dev, "MT9M032 detected at address 0x%02x\n", + client->addr); + + sensor->frame_interval.numerator = 1; + sensor->frame_interval.denominator = 30; + + sensor->crop.left = MT9M032_COLUMN_START_DEF; + sensor->crop.top = MT9M032_ROW_START_DEF; + sensor->crop.width = MT9M032_COLUMN_SIZE_DEF; + sensor->crop.height = MT9M032_ROW_SIZE_DEF; + + sensor->format.width = sensor->crop.width; + sensor->format.height = sensor->crop.height; + sensor->format.code = V4L2_MBUS_FMT_Y8_1X8; + sensor->format.field = V4L2_FIELD_NONE; + sensor->format.colorspace = V4L2_COLORSPACE_SRGB; + + v4l2_ctrl_handler_init(&sensor->ctrls, 5); + + v4l2_ctrl_new_std(&sensor->ctrls, &mt9m032_ctrl_ops, + V4L2_CID_GAIN, 0, 127, 1, 64); + + sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, + &mt9m032_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, + &mt9m032_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&sensor->ctrls, &mt9m032_ctrl_ops, + V4L2_CID_EXPOSURE, MT9M032_SHUTTER_WIDTH_MIN, + MT9M032_SHUTTER_WIDTH_MAX, 1, + MT9M032_SHUTTER_WIDTH_DEF); + v4l2_ctrl_new_std(&sensor->ctrls, &mt9m032_ctrl_ops, + V4L2_CID_PIXEL_RATE, pdata->pix_clock, + pdata->pix_clock, 1, pdata->pix_clock); + + if (sensor->ctrls.error) { + ret = sensor->ctrls.error; + dev_err(&client->dev, "control initialization error %d\n", ret); + goto error_ctrl; + } + + v4l2_ctrl_cluster(2, &sensor->hflip); + + sensor->subdev.ctrl_handler = &sensor->ctrls; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_init(&sensor->subdev.entity, 1, &sensor->pad, 0); + if (ret < 0) + goto error_ctrl; + + ret = mt9m032_write(client, MT9M032_RESET, 1); /* reset on */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, MT9M032_RESET, 0); /* reset off */ + if (ret < 0) + goto error_entity; + + ret = mt9m032_setup_pll(sensor); + if (ret < 0) + goto error_entity; + usleep_range(10000, 11000); + + ret = v4l2_ctrl_handler_setup(&sensor->ctrls); + if (ret < 0) + goto error_entity; + + /* SIZE */ + ret = mt9m032_update_geom_timing(sensor); + if (ret < 0) + goto error_entity; + + ret = mt9m032_write(client, 0x41, 0x0000); /* reserved !!! */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, 0x42, 0x0003); /* reserved !!! */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, 0x43, 0x0003); /* reserved !!! */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, 0x7f, 0x0000); /* reserved !!! */ + if (ret < 0) + goto error_entity; + if (sensor->pdata->invert_pixclock) { + ret = mt9m032_write(client, MT9M032_PIX_CLK_CTRL, + MT9M032_PIX_CLK_CTRL_INV_PIXCLK); + if (ret < 0) + goto error_entity; + } + + ret = mt9m032_write(client, MT9M032_RESTART, 1); /* Restart on */ + if (ret < 0) + goto error_entity; + msleep(100); + ret = mt9m032_write(client, MT9M032_RESTART, 0); /* Restart off */ + if (ret < 0) + goto error_entity; + msleep(100); + ret = update_formatter2(sensor, false); + if (ret < 0) + goto error_entity; + + return ret; + +error_entity: + media_entity_cleanup(&sensor->subdev.entity); +error_ctrl: + v4l2_ctrl_handler_free(&sensor->ctrls); +error_sensor: + mutex_destroy(&sensor->lock); + kfree(sensor); + return ret; +} + +static int mt9m032_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct mt9m032 *sensor = to_mt9m032(subdev); + + v4l2_device_unregister_subdev(subdev); + v4l2_ctrl_handler_free(&sensor->ctrls); + media_entity_cleanup(&subdev->entity); + mutex_destroy(&sensor->lock); + kfree(sensor); + return 0; +} + +static const struct i2c_device_id mt9m032_id_table[] = { + { MT9M032_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, mt9m032_id_table); + +static struct i2c_driver mt9m032_i2c_driver = { + .driver = { + .name = MT9M032_NAME, + }, + .probe = mt9m032_probe, + .remove = mt9m032_remove, + .id_table = mt9m032_id_table, +}; + +module_i2c_driver(mt9m032_i2c_driver); + +MODULE_AUTHOR("Martin Hostettler "); +MODULE_DESCRIPTION("MT9M032 camera sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/mt9p031.c b/drivers/media/i2c/mt9p031.c new file mode 100644 index 000000000000..2c0f4077c491 --- /dev/null +++ b/drivers/media/i2c/mt9p031.c @@ -0,0 +1,1071 @@ +/* + * Driver for MT9P031 CMOS Image Sensor from Aptina + * + * Copyright (C) 2011, Laurent Pinchart + * Copyright (C) 2011, Javier Martin + * Copyright (C) 2011, Guennadi Liakhovetski + * + * Based on the MT9V032 driver and Bastian Hecht's code. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "aptina-pll.h" + +#define MT9P031_PIXEL_ARRAY_WIDTH 2752 +#define MT9P031_PIXEL_ARRAY_HEIGHT 2004 + +#define MT9P031_CHIP_VERSION 0x00 +#define MT9P031_CHIP_VERSION_VALUE 0x1801 +#define MT9P031_ROW_START 0x01 +#define MT9P031_ROW_START_MIN 0 +#define MT9P031_ROW_START_MAX 2004 +#define MT9P031_ROW_START_DEF 54 +#define MT9P031_COLUMN_START 0x02 +#define MT9P031_COLUMN_START_MIN 0 +#define MT9P031_COLUMN_START_MAX 2750 +#define MT9P031_COLUMN_START_DEF 16 +#define MT9P031_WINDOW_HEIGHT 0x03 +#define MT9P031_WINDOW_HEIGHT_MIN 2 +#define MT9P031_WINDOW_HEIGHT_MAX 2006 +#define MT9P031_WINDOW_HEIGHT_DEF 1944 +#define MT9P031_WINDOW_WIDTH 0x04 +#define MT9P031_WINDOW_WIDTH_MIN 2 +#define MT9P031_WINDOW_WIDTH_MAX 2752 +#define MT9P031_WINDOW_WIDTH_DEF 2592 +#define MT9P031_HORIZONTAL_BLANK 0x05 +#define MT9P031_HORIZONTAL_BLANK_MIN 0 +#define MT9P031_HORIZONTAL_BLANK_MAX 4095 +#define MT9P031_VERTICAL_BLANK 0x06 +#define MT9P031_VERTICAL_BLANK_MIN 1 +#define MT9P031_VERTICAL_BLANK_MAX 4096 +#define MT9P031_VERTICAL_BLANK_DEF 26 +#define MT9P031_OUTPUT_CONTROL 0x07 +#define MT9P031_OUTPUT_CONTROL_CEN 2 +#define MT9P031_OUTPUT_CONTROL_SYN 1 +#define MT9P031_OUTPUT_CONTROL_DEF 0x1f82 +#define MT9P031_SHUTTER_WIDTH_UPPER 0x08 +#define MT9P031_SHUTTER_WIDTH_LOWER 0x09 +#define MT9P031_SHUTTER_WIDTH_MIN 1 +#define MT9P031_SHUTTER_WIDTH_MAX 1048575 +#define MT9P031_SHUTTER_WIDTH_DEF 1943 +#define MT9P031_PLL_CONTROL 0x10 +#define MT9P031_PLL_CONTROL_PWROFF 0x0050 +#define MT9P031_PLL_CONTROL_PWRON 0x0051 +#define MT9P031_PLL_CONTROL_USEPLL 0x0052 +#define MT9P031_PLL_CONFIG_1 0x11 +#define MT9P031_PLL_CONFIG_2 0x12 +#define MT9P031_PIXEL_CLOCK_CONTROL 0x0a +#define MT9P031_FRAME_RESTART 0x0b +#define MT9P031_SHUTTER_DELAY 0x0c +#define MT9P031_RST 0x0d +#define MT9P031_RST_ENABLE 1 +#define MT9P031_RST_DISABLE 0 +#define MT9P031_READ_MODE_1 0x1e +#define MT9P031_READ_MODE_2 0x20 +#define MT9P031_READ_MODE_2_ROW_MIR (1 << 15) +#define MT9P031_READ_MODE_2_COL_MIR (1 << 14) +#define MT9P031_READ_MODE_2_ROW_BLC (1 << 6) +#define MT9P031_ROW_ADDRESS_MODE 0x22 +#define MT9P031_COLUMN_ADDRESS_MODE 0x23 +#define MT9P031_GLOBAL_GAIN 0x35 +#define MT9P031_GLOBAL_GAIN_MIN 8 +#define MT9P031_GLOBAL_GAIN_MAX 1024 +#define MT9P031_GLOBAL_GAIN_DEF 8 +#define MT9P031_GLOBAL_GAIN_MULT (1 << 6) +#define MT9P031_ROW_BLACK_TARGET 0x49 +#define MT9P031_ROW_BLACK_DEF_OFFSET 0x4b +#define MT9P031_GREEN1_OFFSET 0x60 +#define MT9P031_GREEN2_OFFSET 0x61 +#define MT9P031_BLACK_LEVEL_CALIBRATION 0x62 +#define MT9P031_BLC_MANUAL_BLC (1 << 0) +#define MT9P031_RED_OFFSET 0x63 +#define MT9P031_BLUE_OFFSET 0x64 +#define MT9P031_TEST_PATTERN 0xa0 +#define MT9P031_TEST_PATTERN_SHIFT 3 +#define MT9P031_TEST_PATTERN_ENABLE (1 << 0) +#define MT9P031_TEST_PATTERN_DISABLE (0 << 0) +#define MT9P031_TEST_PATTERN_GREEN 0xa1 +#define MT9P031_TEST_PATTERN_RED 0xa2 +#define MT9P031_TEST_PATTERN_BLUE 0xa3 + +enum mt9p031_model { + MT9P031_MODEL_COLOR, + MT9P031_MODEL_MONOCHROME, +}; + +struct mt9p031 { + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_rect crop; /* Sensor window */ + struct v4l2_mbus_framefmt format; + struct mt9p031_platform_data *pdata; + struct mutex power_lock; /* lock to protect power_count */ + int power_count; + + enum mt9p031_model model; + struct aptina_pll pll; + int reset; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *blc_auto; + struct v4l2_ctrl *blc_offset; + + /* Registers cache */ + u16 output_control; + u16 mode2; +}; + +static struct mt9p031 *to_mt9p031(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mt9p031, subdev); +} + +static int mt9p031_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int mt9p031_write(struct i2c_client *client, u8 reg, u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static int mt9p031_set_output_control(struct mt9p031 *mt9p031, u16 clear, + u16 set) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + u16 value = (mt9p031->output_control & ~clear) | set; + int ret; + + ret = mt9p031_write(client, MT9P031_OUTPUT_CONTROL, value); + if (ret < 0) + return ret; + + mt9p031->output_control = value; + return 0; +} + +static int mt9p031_set_mode2(struct mt9p031 *mt9p031, u16 clear, u16 set) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + u16 value = (mt9p031->mode2 & ~clear) | set; + int ret; + + ret = mt9p031_write(client, MT9P031_READ_MODE_2, value); + if (ret < 0) + return ret; + + mt9p031->mode2 = value; + return 0; +} + +static int mt9p031_reset(struct mt9p031 *mt9p031) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + int ret; + + /* Disable chip output, synchronous option update */ + ret = mt9p031_write(client, MT9P031_RST, MT9P031_RST_ENABLE); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_RST, MT9P031_RST_DISABLE); + if (ret < 0) + return ret; + + return mt9p031_set_output_control(mt9p031, MT9P031_OUTPUT_CONTROL_CEN, + 0); +} + +static int mt9p031_pll_setup(struct mt9p031 *mt9p031) +{ + static const struct aptina_pll_limits limits = { + .ext_clock_min = 6000000, + .ext_clock_max = 27000000, + .int_clock_min = 2000000, + .int_clock_max = 13500000, + .out_clock_min = 180000000, + .out_clock_max = 360000000, + .pix_clock_max = 96000000, + .n_min = 1, + .n_max = 64, + .m_min = 16, + .m_max = 255, + .p1_min = 1, + .p1_max = 128, + }; + + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + struct mt9p031_platform_data *pdata = mt9p031->pdata; + + mt9p031->pll.ext_clock = pdata->ext_freq; + mt9p031->pll.pix_clock = pdata->target_freq; + + return aptina_pll_calculate(&client->dev, &limits, &mt9p031->pll); +} + +static int mt9p031_pll_enable(struct mt9p031 *mt9p031) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + int ret; + + ret = mt9p031_write(client, MT9P031_PLL_CONTROL, + MT9P031_PLL_CONTROL_PWRON); + if (ret < 0) + return ret; + + ret = mt9p031_write(client, MT9P031_PLL_CONFIG_1, + (mt9p031->pll.m << 8) | (mt9p031->pll.n - 1)); + if (ret < 0) + return ret; + + ret = mt9p031_write(client, MT9P031_PLL_CONFIG_2, mt9p031->pll.p1 - 1); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + ret = mt9p031_write(client, MT9P031_PLL_CONTROL, + MT9P031_PLL_CONTROL_PWRON | + MT9P031_PLL_CONTROL_USEPLL); + return ret; +} + +static inline int mt9p031_pll_disable(struct mt9p031 *mt9p031) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + + return mt9p031_write(client, MT9P031_PLL_CONTROL, + MT9P031_PLL_CONTROL_PWROFF); +} + +static int mt9p031_power_on(struct mt9p031 *mt9p031) +{ + /* Ensure RESET_BAR is low */ + if (mt9p031->reset != -1) { + gpio_set_value(mt9p031->reset, 0); + usleep_range(1000, 2000); + } + + /* Emable clock */ + if (mt9p031->pdata->set_xclk) + mt9p031->pdata->set_xclk(&mt9p031->subdev, + mt9p031->pdata->ext_freq); + + /* Now RESET_BAR must be high */ + if (mt9p031->reset != -1) { + gpio_set_value(mt9p031->reset, 1); + usleep_range(1000, 2000); + } + + return 0; +} + +static void mt9p031_power_off(struct mt9p031 *mt9p031) +{ + if (mt9p031->reset != -1) { + gpio_set_value(mt9p031->reset, 0); + usleep_range(1000, 2000); + } + + if (mt9p031->pdata->set_xclk) + mt9p031->pdata->set_xclk(&mt9p031->subdev, 0); +} + +static int __mt9p031_set_power(struct mt9p031 *mt9p031, bool on) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + int ret; + + if (!on) { + mt9p031_power_off(mt9p031); + return 0; + } + + ret = mt9p031_power_on(mt9p031); + if (ret < 0) + return ret; + + ret = mt9p031_reset(mt9p031); + if (ret < 0) { + dev_err(&client->dev, "Failed to reset the camera\n"); + return ret; + } + + return v4l2_ctrl_handler_setup(&mt9p031->ctrls); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev video operations + */ + +static int mt9p031_set_params(struct mt9p031 *mt9p031) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + struct v4l2_mbus_framefmt *format = &mt9p031->format; + const struct v4l2_rect *crop = &mt9p031->crop; + unsigned int hblank; + unsigned int vblank; + unsigned int xskip; + unsigned int yskip; + unsigned int xbin; + unsigned int ybin; + int ret; + + /* Windows position and size. + * + * TODO: Make sure the start coordinates and window size match the + * skipping, binning and mirroring (see description of registers 2 and 4 + * in table 13, and Binning section on page 41). + */ + ret = mt9p031_write(client, MT9P031_COLUMN_START, crop->left); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_ROW_START, crop->top); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_WINDOW_WIDTH, crop->width - 1); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_WINDOW_HEIGHT, crop->height - 1); + if (ret < 0) + return ret; + + /* Row and column binning and skipping. Use the maximum binning value + * compatible with the skipping settings. + */ + xskip = DIV_ROUND_CLOSEST(crop->width, format->width); + yskip = DIV_ROUND_CLOSEST(crop->height, format->height); + xbin = 1 << (ffs(xskip) - 1); + ybin = 1 << (ffs(yskip) - 1); + + ret = mt9p031_write(client, MT9P031_COLUMN_ADDRESS_MODE, + ((xbin - 1) << 4) | (xskip - 1)); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_ROW_ADDRESS_MODE, + ((ybin - 1) << 4) | (yskip - 1)); + if (ret < 0) + return ret; + + /* Blanking - use minimum value for horizontal blanking and default + * value for vertical blanking. + */ + hblank = 346 * ybin + 64 + (80 >> min_t(unsigned int, xbin, 3)); + vblank = MT9P031_VERTICAL_BLANK_DEF; + + ret = mt9p031_write(client, MT9P031_HORIZONTAL_BLANK, hblank - 1); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_VERTICAL_BLANK, vblank - 1); + if (ret < 0) + return ret; + + return ret; +} + +static int mt9p031_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + int ret; + + if (!enable) { + /* Stop sensor readout */ + ret = mt9p031_set_output_control(mt9p031, + MT9P031_OUTPUT_CONTROL_CEN, 0); + if (ret < 0) + return ret; + + return mt9p031_pll_disable(mt9p031); + } + + ret = mt9p031_set_params(mt9p031); + if (ret < 0) + return ret; + + /* Switch to master "normal" mode */ + ret = mt9p031_set_output_control(mt9p031, 0, + MT9P031_OUTPUT_CONTROL_CEN); + if (ret < 0) + return ret; + + return mt9p031_pll_enable(mt9p031); +} + +static int mt9p031_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + if (code->pad || code->index) + return -EINVAL; + + code->code = mt9p031->format.code; + return 0; +} + +static int mt9p031_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + if (fse->index >= 8 || fse->code != mt9p031->format.code) + return -EINVAL; + + fse->min_width = MT9P031_WINDOW_WIDTH_DEF + / min_t(unsigned int, 7, fse->index + 1); + fse->max_width = fse->min_width; + fse->min_height = MT9P031_WINDOW_HEIGHT_DEF / (fse->index + 1); + fse->max_height = fse->min_height; + + return 0; +} + +static struct v4l2_mbus_framefmt * +__mt9p031_get_pad_format(struct mt9p031 *mt9p031, struct v4l2_subdev_fh *fh, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(fh, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9p031->format; + default: + return NULL; + } +} + +static struct v4l2_rect * +__mt9p031_get_pad_crop(struct mt9p031 *mt9p031, struct v4l2_subdev_fh *fh, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(fh, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9p031->crop; + default: + return NULL; + } +} + +static int mt9p031_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + fmt->format = *__mt9p031_get_pad_format(mt9p031, fh, fmt->pad, + fmt->which); + return 0; +} + +static int mt9p031_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *format) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + unsigned int width; + unsigned int height; + unsigned int hratio; + unsigned int vratio; + + __crop = __mt9p031_get_pad_crop(mt9p031, fh, format->pad, + format->which); + + /* Clamp the width and height to avoid dividing by zero. */ + width = clamp_t(unsigned int, ALIGN(format->format.width, 2), + max(__crop->width / 7, MT9P031_WINDOW_WIDTH_MIN), + __crop->width); + height = clamp_t(unsigned int, ALIGN(format->format.height, 2), + max(__crop->height / 8, MT9P031_WINDOW_HEIGHT_MIN), + __crop->height); + + hratio = DIV_ROUND_CLOSEST(__crop->width, width); + vratio = DIV_ROUND_CLOSEST(__crop->height, height); + + __format = __mt9p031_get_pad_format(mt9p031, fh, format->pad, + format->which); + __format->width = __crop->width / hratio; + __format->height = __crop->height / vratio; + + format->format = *__format; + + return 0; +} + +static int mt9p031_get_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + crop->rect = *__mt9p031_get_pad_crop(mt9p031, fh, crop->pad, + crop->which); + return 0; +} + +static int mt9p031_set_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + struct v4l2_rect rect; + + /* Clamp the crop rectangle boundaries and align them to a multiple of 2 + * pixels to ensure a GRBG Bayer pattern. + */ + rect.left = clamp(ALIGN(crop->rect.left, 2), MT9P031_COLUMN_START_MIN, + MT9P031_COLUMN_START_MAX); + rect.top = clamp(ALIGN(crop->rect.top, 2), MT9P031_ROW_START_MIN, + MT9P031_ROW_START_MAX); + rect.width = clamp(ALIGN(crop->rect.width, 2), + MT9P031_WINDOW_WIDTH_MIN, + MT9P031_WINDOW_WIDTH_MAX); + rect.height = clamp(ALIGN(crop->rect.height, 2), + MT9P031_WINDOW_HEIGHT_MIN, + MT9P031_WINDOW_HEIGHT_MAX); + + rect.width = min(rect.width, MT9P031_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min(rect.height, MT9P031_PIXEL_ARRAY_HEIGHT - rect.top); + + __crop = __mt9p031_get_pad_crop(mt9p031, fh, crop->pad, crop->which); + + if (rect.width != __crop->width || rect.height != __crop->height) { + /* Reset the output image size if the crop rectangle size has + * been modified. + */ + __format = __mt9p031_get_pad_format(mt9p031, fh, crop->pad, + crop->which); + __format->width = rect.width; + __format->height = rect.height; + } + + *__crop = rect; + crop->rect = rect; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev control operations + */ + +#define V4L2_CID_TEST_PATTERN (V4L2_CID_USER_BASE | 0x1001) +#define V4L2_CID_BLC_AUTO (V4L2_CID_USER_BASE | 0x1002) +#define V4L2_CID_BLC_TARGET_LEVEL (V4L2_CID_USER_BASE | 0x1003) +#define V4L2_CID_BLC_ANALOG_OFFSET (V4L2_CID_USER_BASE | 0x1004) +#define V4L2_CID_BLC_DIGITAL_OFFSET (V4L2_CID_USER_BASE | 0x1005) + +static int mt9p031_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9p031 *mt9p031 = + container_of(ctrl->handler, struct mt9p031, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); + u16 data; + int ret; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = mt9p031_write(client, MT9P031_SHUTTER_WIDTH_UPPER, + (ctrl->val >> 16) & 0xffff); + if (ret < 0) + return ret; + + return mt9p031_write(client, MT9P031_SHUTTER_WIDTH_LOWER, + ctrl->val & 0xffff); + + case V4L2_CID_GAIN: + /* Gain is controlled by 2 analog stages and a digital stage. + * Valid values for the 3 stages are + * + * Stage Min Max Step + * ------------------------------------------ + * First analog stage x1 x2 1 + * Second analog stage x1 x4 0.125 + * Digital stage x1 x16 0.125 + * + * To minimize noise, the gain stages should be used in the + * second analog stage, first analog stage, digital stage order. + * Gain from a previous stage should be pushed to its maximum + * value before the next stage is used. + */ + if (ctrl->val <= 32) { + data = ctrl->val; + } else if (ctrl->val <= 64) { + ctrl->val &= ~1; + data = (1 << 6) | (ctrl->val >> 1); + } else { + ctrl->val &= ~7; + data = ((ctrl->val - 64) << 5) | (1 << 6) | 32; + } + + return mt9p031_write(client, MT9P031_GLOBAL_GAIN, data); + + case V4L2_CID_HFLIP: + if (ctrl->val) + return mt9p031_set_mode2(mt9p031, + 0, MT9P031_READ_MODE_2_COL_MIR); + else + return mt9p031_set_mode2(mt9p031, + MT9P031_READ_MODE_2_COL_MIR, 0); + + case V4L2_CID_VFLIP: + if (ctrl->val) + return mt9p031_set_mode2(mt9p031, + 0, MT9P031_READ_MODE_2_ROW_MIR); + else + return mt9p031_set_mode2(mt9p031, + MT9P031_READ_MODE_2_ROW_MIR, 0); + + case V4L2_CID_TEST_PATTERN: + if (!ctrl->val) { + /* Restore the black level compensation settings. */ + if (mt9p031->blc_auto->cur.val != 0) { + ret = mt9p031_s_ctrl(mt9p031->blc_auto); + if (ret < 0) + return ret; + } + if (mt9p031->blc_offset->cur.val != 0) { + ret = mt9p031_s_ctrl(mt9p031->blc_offset); + if (ret < 0) + return ret; + } + return mt9p031_write(client, MT9P031_TEST_PATTERN, + MT9P031_TEST_PATTERN_DISABLE); + } + + ret = mt9p031_write(client, MT9P031_TEST_PATTERN_GREEN, 0x05a0); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_TEST_PATTERN_RED, 0x0a50); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_TEST_PATTERN_BLUE, 0x0aa0); + if (ret < 0) + return ret; + + /* Disable digital black level compensation when using a test + * pattern. + */ + ret = mt9p031_set_mode2(mt9p031, MT9P031_READ_MODE_2_ROW_BLC, + 0); + if (ret < 0) + return ret; + + ret = mt9p031_write(client, MT9P031_ROW_BLACK_DEF_OFFSET, 0); + if (ret < 0) + return ret; + + return mt9p031_write(client, MT9P031_TEST_PATTERN, + ((ctrl->val - 1) << MT9P031_TEST_PATTERN_SHIFT) + | MT9P031_TEST_PATTERN_ENABLE); + + case V4L2_CID_BLC_AUTO: + ret = mt9p031_set_mode2(mt9p031, + ctrl->val ? 0 : MT9P031_READ_MODE_2_ROW_BLC, + ctrl->val ? MT9P031_READ_MODE_2_ROW_BLC : 0); + if (ret < 0) + return ret; + + return mt9p031_write(client, MT9P031_BLACK_LEVEL_CALIBRATION, + ctrl->val ? 0 : MT9P031_BLC_MANUAL_BLC); + + case V4L2_CID_BLC_TARGET_LEVEL: + return mt9p031_write(client, MT9P031_ROW_BLACK_TARGET, + ctrl->val); + + case V4L2_CID_BLC_ANALOG_OFFSET: + data = ctrl->val & ((1 << 9) - 1); + + ret = mt9p031_write(client, MT9P031_GREEN1_OFFSET, data); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_GREEN2_OFFSET, data); + if (ret < 0) + return ret; + ret = mt9p031_write(client, MT9P031_RED_OFFSET, data); + if (ret < 0) + return ret; + return mt9p031_write(client, MT9P031_BLUE_OFFSET, data); + + case V4L2_CID_BLC_DIGITAL_OFFSET: + return mt9p031_write(client, MT9P031_ROW_BLACK_DEF_OFFSET, + ctrl->val & ((1 << 12) - 1)); + } + + return 0; +} + +static struct v4l2_ctrl_ops mt9p031_ctrl_ops = { + .s_ctrl = mt9p031_s_ctrl, +}; + +static const char * const mt9p031_test_pattern_menu[] = { + "Disabled", + "Color Field", + "Horizontal Gradient", + "Vertical Gradient", + "Diagonal Gradient", + "Classic Test Pattern", + "Walking 1s", + "Monochrome Horizontal Bars", + "Monochrome Vertical Bars", + "Vertical Color Bars", +}; + +static const struct v4l2_ctrl_config mt9p031_ctrls[] = { + { + .ops = &mt9p031_ctrl_ops, + .id = V4L2_CID_TEST_PATTERN, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Test Pattern", + .min = 0, + .max = ARRAY_SIZE(mt9p031_test_pattern_menu) - 1, + .step = 0, + .def = 0, + .flags = 0, + .menu_skip_mask = 0, + .qmenu = mt9p031_test_pattern_menu, + }, { + .ops = &mt9p031_ctrl_ops, + .id = V4L2_CID_BLC_AUTO, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "BLC, Auto", + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .flags = 0, + }, { + .ops = &mt9p031_ctrl_ops, + .id = V4L2_CID_BLC_TARGET_LEVEL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "BLC Target Level", + .min = 0, + .max = 4095, + .step = 1, + .def = 168, + .flags = 0, + }, { + .ops = &mt9p031_ctrl_ops, + .id = V4L2_CID_BLC_ANALOG_OFFSET, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "BLC Analog Offset", + .min = -255, + .max = 255, + .step = 1, + .def = 32, + .flags = 0, + }, { + .ops = &mt9p031_ctrl_ops, + .id = V4L2_CID_BLC_DIGITAL_OFFSET, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "BLC Digital Offset", + .min = -2048, + .max = 2047, + .step = 1, + .def = 40, + .flags = 0, + } +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int mt9p031_set_power(struct v4l2_subdev *subdev, int on) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + int ret = 0; + + mutex_lock(&mt9p031->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (mt9p031->power_count == !on) { + ret = __mt9p031_set_power(mt9p031, !!on); + if (ret < 0) + goto out; + } + + /* Update the power count. */ + mt9p031->power_count += on ? 1 : -1; + WARN_ON(mt9p031->power_count < 0); + +out: + mutex_unlock(&mt9p031->power_lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev internal operations + */ + +static int mt9p031_registered(struct v4l2_subdev *subdev) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + s32 data; + int ret; + + ret = mt9p031_power_on(mt9p031); + if (ret < 0) { + dev_err(&client->dev, "MT9P031 power up failed\n"); + return ret; + } + + /* Read out the chip version register */ + data = mt9p031_read(client, MT9P031_CHIP_VERSION); + if (data != MT9P031_CHIP_VERSION_VALUE) { + dev_err(&client->dev, "MT9P031 not detected, wrong version " + "0x%04x\n", data); + return -ENODEV; + } + + mt9p031_power_off(mt9p031); + + dev_info(&client->dev, "MT9P031 detected at address 0x%02x\n", + client->addr); + + return ret; +} + +static int mt9p031_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + crop = v4l2_subdev_get_try_crop(fh, 0); + crop->left = MT9P031_COLUMN_START_DEF; + crop->top = MT9P031_ROW_START_DEF; + crop->width = MT9P031_WINDOW_WIDTH_DEF; + crop->height = MT9P031_WINDOW_HEIGHT_DEF; + + format = v4l2_subdev_get_try_format(fh, 0); + + if (mt9p031->model == MT9P031_MODEL_MONOCHROME) + format->code = V4L2_MBUS_FMT_Y12_1X12; + else + format->code = V4L2_MBUS_FMT_SGRBG12_1X12; + + format->width = MT9P031_WINDOW_WIDTH_DEF; + format->height = MT9P031_WINDOW_HEIGHT_DEF; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + return mt9p031_set_power(subdev, 1); +} + +static int mt9p031_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + return mt9p031_set_power(subdev, 0); +} + +static struct v4l2_subdev_core_ops mt9p031_subdev_core_ops = { + .s_power = mt9p031_set_power, +}; + +static struct v4l2_subdev_video_ops mt9p031_subdev_video_ops = { + .s_stream = mt9p031_s_stream, +}; + +static struct v4l2_subdev_pad_ops mt9p031_subdev_pad_ops = { + .enum_mbus_code = mt9p031_enum_mbus_code, + .enum_frame_size = mt9p031_enum_frame_size, + .get_fmt = mt9p031_get_format, + .set_fmt = mt9p031_set_format, + .get_crop = mt9p031_get_crop, + .set_crop = mt9p031_set_crop, +}; + +static struct v4l2_subdev_ops mt9p031_subdev_ops = { + .core = &mt9p031_subdev_core_ops, + .video = &mt9p031_subdev_video_ops, + .pad = &mt9p031_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops mt9p031_subdev_internal_ops = { + .registered = mt9p031_registered, + .open = mt9p031_open, + .close = mt9p031_close, +}; + +/* ----------------------------------------------------------------------------- + * Driver initialization and probing + */ + +static int mt9p031_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9p031_platform_data *pdata = client->dev.platform_data; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct mt9p031 *mt9p031; + unsigned int i; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&client->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9p031 = kzalloc(sizeof(*mt9p031), GFP_KERNEL); + if (mt9p031 == NULL) + return -ENOMEM; + + mt9p031->pdata = pdata; + mt9p031->output_control = MT9P031_OUTPUT_CONTROL_DEF; + mt9p031->mode2 = MT9P031_READ_MODE_2_ROW_BLC; + mt9p031->model = did->driver_data; + mt9p031->reset = -1; + + v4l2_ctrl_handler_init(&mt9p031->ctrls, ARRAY_SIZE(mt9p031_ctrls) + 5); + + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_EXPOSURE, MT9P031_SHUTTER_WIDTH_MIN, + MT9P031_SHUTTER_WIDTH_MAX, 1, + MT9P031_SHUTTER_WIDTH_DEF); + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_GAIN, MT9P031_GLOBAL_GAIN_MIN, + MT9P031_GLOBAL_GAIN_MAX, 1, MT9P031_GLOBAL_GAIN_DEF); + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9p031->ctrls, &mt9p031_ctrl_ops, + V4L2_CID_PIXEL_RATE, pdata->target_freq, + pdata->target_freq, 1, pdata->target_freq); + + for (i = 0; i < ARRAY_SIZE(mt9p031_ctrls); ++i) + v4l2_ctrl_new_custom(&mt9p031->ctrls, &mt9p031_ctrls[i], NULL); + + mt9p031->subdev.ctrl_handler = &mt9p031->ctrls; + + if (mt9p031->ctrls.error) { + printk(KERN_INFO "%s: control initialization error %d\n", + __func__, mt9p031->ctrls.error); + ret = mt9p031->ctrls.error; + goto done; + } + + mt9p031->blc_auto = v4l2_ctrl_find(&mt9p031->ctrls, V4L2_CID_BLC_AUTO); + mt9p031->blc_offset = v4l2_ctrl_find(&mt9p031->ctrls, + V4L2_CID_BLC_DIGITAL_OFFSET); + + mutex_init(&mt9p031->power_lock); + v4l2_i2c_subdev_init(&mt9p031->subdev, client, &mt9p031_subdev_ops); + mt9p031->subdev.internal_ops = &mt9p031_subdev_internal_ops; + + mt9p031->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_init(&mt9p031->subdev.entity, 1, &mt9p031->pad, 0); + if (ret < 0) + goto done; + + mt9p031->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + mt9p031->crop.width = MT9P031_WINDOW_WIDTH_DEF; + mt9p031->crop.height = MT9P031_WINDOW_HEIGHT_DEF; + mt9p031->crop.left = MT9P031_COLUMN_START_DEF; + mt9p031->crop.top = MT9P031_ROW_START_DEF; + + if (mt9p031->model == MT9P031_MODEL_MONOCHROME) + mt9p031->format.code = V4L2_MBUS_FMT_Y12_1X12; + else + mt9p031->format.code = V4L2_MBUS_FMT_SGRBG12_1X12; + + mt9p031->format.width = MT9P031_WINDOW_WIDTH_DEF; + mt9p031->format.height = MT9P031_WINDOW_HEIGHT_DEF; + mt9p031->format.field = V4L2_FIELD_NONE; + mt9p031->format.colorspace = V4L2_COLORSPACE_SRGB; + + if (pdata->reset != -1) { + ret = gpio_request_one(pdata->reset, GPIOF_OUT_INIT_LOW, + "mt9p031_rst"); + if (ret < 0) + goto done; + + mt9p031->reset = pdata->reset; + } + + ret = mt9p031_pll_setup(mt9p031); + +done: + if (ret < 0) { + if (mt9p031->reset != -1) + gpio_free(mt9p031->reset); + + v4l2_ctrl_handler_free(&mt9p031->ctrls); + media_entity_cleanup(&mt9p031->subdev.entity); + kfree(mt9p031); + } + + return ret; +} + +static int mt9p031_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct mt9p031 *mt9p031 = to_mt9p031(subdev); + + v4l2_ctrl_handler_free(&mt9p031->ctrls); + v4l2_device_unregister_subdev(subdev); + media_entity_cleanup(&subdev->entity); + if (mt9p031->reset != -1) + gpio_free(mt9p031->reset); + kfree(mt9p031); + + return 0; +} + +static const struct i2c_device_id mt9p031_id[] = { + { "mt9p031", MT9P031_MODEL_COLOR }, + { "mt9p031m", MT9P031_MODEL_MONOCHROME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9p031_id); + +static struct i2c_driver mt9p031_i2c_driver = { + .driver = { + .name = "mt9p031", + }, + .probe = mt9p031_probe, + .remove = mt9p031_remove, + .id_table = mt9p031_id, +}; + +module_i2c_driver(mt9p031_i2c_driver); + +MODULE_DESCRIPTION("Aptina MT9P031 Camera driver"); +MODULE_AUTHOR("Bastian Hecht "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/mt9t001.c b/drivers/media/i2c/mt9t001.c similarity index 100% rename from drivers/media/video/mt9t001.c rename to drivers/media/i2c/mt9t001.c diff --git a/drivers/media/video/mt9v011.c b/drivers/media/i2c/mt9v011.c similarity index 100% rename from drivers/media/video/mt9v011.c rename to drivers/media/i2c/mt9v011.c diff --git a/drivers/media/i2c/mt9v032.c b/drivers/media/i2c/mt9v032.c new file mode 100644 index 000000000000..e2177405dad2 --- /dev/null +++ b/drivers/media/i2c/mt9v032.c @@ -0,0 +1,853 @@ +/* + * Driver for MT9V032 CMOS Image Sensor from Micron + * + * Copyright (C) 2010, Laurent Pinchart + * + * Based on the MT9M001 driver, + * + * Copyright (C) 2008, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MT9V032_PIXEL_ARRAY_HEIGHT 492 +#define MT9V032_PIXEL_ARRAY_WIDTH 782 + +#define MT9V032_SYSCLK_FREQ_DEF 26600000 + +#define MT9V032_CHIP_VERSION 0x00 +#define MT9V032_CHIP_ID_REV1 0x1311 +#define MT9V032_CHIP_ID_REV3 0x1313 +#define MT9V032_COLUMN_START 0x01 +#define MT9V032_COLUMN_START_MIN 1 +#define MT9V032_COLUMN_START_DEF 1 +#define MT9V032_COLUMN_START_MAX 752 +#define MT9V032_ROW_START 0x02 +#define MT9V032_ROW_START_MIN 4 +#define MT9V032_ROW_START_DEF 5 +#define MT9V032_ROW_START_MAX 482 +#define MT9V032_WINDOW_HEIGHT 0x03 +#define MT9V032_WINDOW_HEIGHT_MIN 1 +#define MT9V032_WINDOW_HEIGHT_DEF 480 +#define MT9V032_WINDOW_HEIGHT_MAX 480 +#define MT9V032_WINDOW_WIDTH 0x04 +#define MT9V032_WINDOW_WIDTH_MIN 1 +#define MT9V032_WINDOW_WIDTH_DEF 752 +#define MT9V032_WINDOW_WIDTH_MAX 752 +#define MT9V032_HORIZONTAL_BLANKING 0x05 +#define MT9V032_HORIZONTAL_BLANKING_MIN 43 +#define MT9V032_HORIZONTAL_BLANKING_DEF 94 +#define MT9V032_HORIZONTAL_BLANKING_MAX 1023 +#define MT9V032_VERTICAL_BLANKING 0x06 +#define MT9V032_VERTICAL_BLANKING_MIN 4 +#define MT9V032_VERTICAL_BLANKING_DEF 45 +#define MT9V032_VERTICAL_BLANKING_MAX 3000 +#define MT9V032_CHIP_CONTROL 0x07 +#define MT9V032_CHIP_CONTROL_MASTER_MODE (1 << 3) +#define MT9V032_CHIP_CONTROL_DOUT_ENABLE (1 << 7) +#define MT9V032_CHIP_CONTROL_SEQUENTIAL (1 << 8) +#define MT9V032_SHUTTER_WIDTH1 0x08 +#define MT9V032_SHUTTER_WIDTH2 0x09 +#define MT9V032_SHUTTER_WIDTH_CONTROL 0x0a +#define MT9V032_TOTAL_SHUTTER_WIDTH 0x0b +#define MT9V032_TOTAL_SHUTTER_WIDTH_MIN 1 +#define MT9V032_TOTAL_SHUTTER_WIDTH_DEF 480 +#define MT9V032_TOTAL_SHUTTER_WIDTH_MAX 32767 +#define MT9V032_RESET 0x0c +#define MT9V032_READ_MODE 0x0d +#define MT9V032_READ_MODE_ROW_BIN_MASK (3 << 0) +#define MT9V032_READ_MODE_ROW_BIN_SHIFT 0 +#define MT9V032_READ_MODE_COLUMN_BIN_MASK (3 << 2) +#define MT9V032_READ_MODE_COLUMN_BIN_SHIFT 2 +#define MT9V032_READ_MODE_ROW_FLIP (1 << 4) +#define MT9V032_READ_MODE_COLUMN_FLIP (1 << 5) +#define MT9V032_READ_MODE_DARK_COLUMNS (1 << 6) +#define MT9V032_READ_MODE_DARK_ROWS (1 << 7) +#define MT9V032_PIXEL_OPERATION_MODE 0x0f +#define MT9V032_PIXEL_OPERATION_MODE_COLOR (1 << 2) +#define MT9V032_PIXEL_OPERATION_MODE_HDR (1 << 6) +#define MT9V032_ANALOG_GAIN 0x35 +#define MT9V032_ANALOG_GAIN_MIN 16 +#define MT9V032_ANALOG_GAIN_DEF 16 +#define MT9V032_ANALOG_GAIN_MAX 64 +#define MT9V032_MAX_ANALOG_GAIN 0x36 +#define MT9V032_MAX_ANALOG_GAIN_MAX 127 +#define MT9V032_FRAME_DARK_AVERAGE 0x42 +#define MT9V032_DARK_AVG_THRESH 0x46 +#define MT9V032_DARK_AVG_LOW_THRESH_MASK (255 << 0) +#define MT9V032_DARK_AVG_LOW_THRESH_SHIFT 0 +#define MT9V032_DARK_AVG_HIGH_THRESH_MASK (255 << 8) +#define MT9V032_DARK_AVG_HIGH_THRESH_SHIFT 8 +#define MT9V032_ROW_NOISE_CORR_CONTROL 0x70 +#define MT9V032_ROW_NOISE_CORR_ENABLE (1 << 5) +#define MT9V032_ROW_NOISE_CORR_USE_BLK_AVG (1 << 7) +#define MT9V032_PIXEL_CLOCK 0x74 +#define MT9V032_PIXEL_CLOCK_INV_LINE (1 << 0) +#define MT9V032_PIXEL_CLOCK_INV_FRAME (1 << 1) +#define MT9V032_PIXEL_CLOCK_XOR_LINE (1 << 2) +#define MT9V032_PIXEL_CLOCK_CONT_LINE (1 << 3) +#define MT9V032_PIXEL_CLOCK_INV_PXL_CLK (1 << 4) +#define MT9V032_TEST_PATTERN 0x7f +#define MT9V032_TEST_PATTERN_DATA_MASK (1023 << 0) +#define MT9V032_TEST_PATTERN_DATA_SHIFT 0 +#define MT9V032_TEST_PATTERN_USE_DATA (1 << 10) +#define MT9V032_TEST_PATTERN_GRAY_MASK (3 << 11) +#define MT9V032_TEST_PATTERN_GRAY_NONE (0 << 11) +#define MT9V032_TEST_PATTERN_GRAY_VERTICAL (1 << 11) +#define MT9V032_TEST_PATTERN_GRAY_HORIZONTAL (2 << 11) +#define MT9V032_TEST_PATTERN_GRAY_DIAGONAL (3 << 11) +#define MT9V032_TEST_PATTERN_ENABLE (1 << 13) +#define MT9V032_TEST_PATTERN_FLIP (1 << 14) +#define MT9V032_AEC_AGC_ENABLE 0xaf +#define MT9V032_AEC_ENABLE (1 << 0) +#define MT9V032_AGC_ENABLE (1 << 1) +#define MT9V032_THERMAL_INFO 0xc1 + +struct mt9v032 { + struct v4l2_subdev subdev; + struct media_pad pad; + + struct v4l2_mbus_framefmt format; + struct v4l2_rect crop; + + struct v4l2_ctrl_handler ctrls; + struct { + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + }; + + struct mutex power_lock; + int power_count; + + struct mt9v032_platform_data *pdata; + + u32 sysclk; + u16 chip_control; + u16 aec_agc; + u16 hblank; +}; + +static struct mt9v032 *to_mt9v032(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mt9v032, subdev); +} + +static int mt9v032_read(struct i2c_client *client, const u8 reg) +{ + s32 data = i2c_smbus_read_word_swapped(client, reg); + dev_dbg(&client->dev, "%s: read 0x%04x from 0x%02x\n", __func__, + data, reg); + return data; +} + +static int mt9v032_write(struct i2c_client *client, const u8 reg, + const u16 data) +{ + dev_dbg(&client->dev, "%s: writing 0x%04x to 0x%02x\n", __func__, + data, reg); + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static int mt9v032_set_chip_control(struct mt9v032 *mt9v032, u16 clear, u16 set) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); + u16 value = (mt9v032->chip_control & ~clear) | set; + int ret; + + ret = mt9v032_write(client, MT9V032_CHIP_CONTROL, value); + if (ret < 0) + return ret; + + mt9v032->chip_control = value; + return 0; +} + +static int +mt9v032_update_aec_agc(struct mt9v032 *mt9v032, u16 which, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); + u16 value = mt9v032->aec_agc; + int ret; + + if (enable) + value |= which; + else + value &= ~which; + + ret = mt9v032_write(client, MT9V032_AEC_AGC_ENABLE, value); + if (ret < 0) + return ret; + + mt9v032->aec_agc = value; + return 0; +} + +static int +mt9v032_update_hblank(struct mt9v032 *mt9v032) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); + struct v4l2_rect *crop = &mt9v032->crop; + + return mt9v032_write(client, MT9V032_HORIZONTAL_BLANKING, + max_t(s32, mt9v032->hblank, 660 - crop->width)); +} + +#define EXT_CLK 25000000 + +static int mt9v032_power_on(struct mt9v032 *mt9v032) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); + int ret; + + if (mt9v032->pdata->set_clock) { + mt9v032->pdata->set_clock(&mt9v032->subdev, mt9v032->sysclk); + udelay(1); + } + + /* Reset the chip and stop data read out */ + ret = mt9v032_write(client, MT9V032_RESET, 1); + if (ret < 0) + return ret; + + ret = mt9v032_write(client, MT9V032_RESET, 0); + if (ret < 0) + return ret; + + return mt9v032_write(client, MT9V032_CHIP_CONTROL, 0); +} + +static void mt9v032_power_off(struct mt9v032 *mt9v032) +{ + if (mt9v032->pdata->set_clock) + mt9v032->pdata->set_clock(&mt9v032->subdev, 0); +} + +static int __mt9v032_set_power(struct mt9v032 *mt9v032, bool on) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); + int ret; + + if (!on) { + mt9v032_power_off(mt9v032); + return 0; + } + + ret = mt9v032_power_on(mt9v032); + if (ret < 0) + return ret; + + /* Configure the pixel clock polarity */ + if (mt9v032->pdata && mt9v032->pdata->clk_pol) { + ret = mt9v032_write(client, MT9V032_PIXEL_CLOCK, + MT9V032_PIXEL_CLOCK_INV_PXL_CLK); + if (ret < 0) + return ret; + } + + /* Disable the noise correction algorithm and restore the controls. */ + ret = mt9v032_write(client, MT9V032_ROW_NOISE_CORR_CONTROL, 0); + if (ret < 0) + return ret; + + return v4l2_ctrl_handler_setup(&mt9v032->ctrls); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev video operations + */ + +static struct v4l2_mbus_framefmt * +__mt9v032_get_pad_format(struct mt9v032 *mt9v032, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(fh, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9v032->format; + default: + return NULL; + } +} + +static struct v4l2_rect * +__mt9v032_get_pad_crop(struct mt9v032 *mt9v032, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(fh, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &mt9v032->crop; + default: + return NULL; + } +} + +static int mt9v032_s_stream(struct v4l2_subdev *subdev, int enable) +{ + const u16 mode = MT9V032_CHIP_CONTROL_MASTER_MODE + | MT9V032_CHIP_CONTROL_DOUT_ENABLE + | MT9V032_CHIP_CONTROL_SEQUENTIAL; + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + struct v4l2_mbus_framefmt *format = &mt9v032->format; + struct v4l2_rect *crop = &mt9v032->crop; + unsigned int hratio; + unsigned int vratio; + int ret; + + if (!enable) + return mt9v032_set_chip_control(mt9v032, mode, 0); + + /* Configure the window size and row/column bin */ + hratio = DIV_ROUND_CLOSEST(crop->width, format->width); + vratio = DIV_ROUND_CLOSEST(crop->height, format->height); + + ret = mt9v032_write(client, MT9V032_READ_MODE, + (hratio - 1) << MT9V032_READ_MODE_ROW_BIN_SHIFT | + (vratio - 1) << MT9V032_READ_MODE_COLUMN_BIN_SHIFT); + if (ret < 0) + return ret; + + ret = mt9v032_write(client, MT9V032_COLUMN_START, crop->left); + if (ret < 0) + return ret; + + ret = mt9v032_write(client, MT9V032_ROW_START, crop->top); + if (ret < 0) + return ret; + + ret = mt9v032_write(client, MT9V032_WINDOW_WIDTH, crop->width); + if (ret < 0) + return ret; + + ret = mt9v032_write(client, MT9V032_WINDOW_HEIGHT, crop->height); + if (ret < 0) + return ret; + + ret = mt9v032_update_hblank(mt9v032); + if (ret < 0) + return ret; + + /* Switch to master "normal" mode */ + return mt9v032_set_chip_control(mt9v032, 0, mode); +} + +static int mt9v032_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = V4L2_MBUS_FMT_SGRBG10_1X10; + return 0; +} + +static int mt9v032_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= 8 || fse->code != V4L2_MBUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = MT9V032_WINDOW_WIDTH_DEF / fse->index; + fse->max_width = fse->min_width; + fse->min_height = MT9V032_WINDOW_HEIGHT_DEF / fse->index; + fse->max_height = fse->min_height; + + return 0; +} + +static int mt9v032_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *format) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + + format->format = *__mt9v032_get_pad_format(mt9v032, fh, format->pad, + format->which); + return 0; +} + +static void mt9v032_configure_pixel_rate(struct mt9v032 *mt9v032, + unsigned int hratio) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); + int ret; + + ret = v4l2_ctrl_s_ctrl_int64(mt9v032->pixel_rate, + mt9v032->sysclk / hratio); + if (ret < 0) + dev_warn(&client->dev, "failed to set pixel rate (%d)\n", ret); +} + +static int mt9v032_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *format) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + unsigned int width; + unsigned int height; + unsigned int hratio; + unsigned int vratio; + + __crop = __mt9v032_get_pad_crop(mt9v032, fh, format->pad, + format->which); + + /* Clamp the width and height to avoid dividing by zero. */ + width = clamp_t(unsigned int, ALIGN(format->format.width, 2), + max(__crop->width / 8, MT9V032_WINDOW_WIDTH_MIN), + __crop->width); + height = clamp_t(unsigned int, ALIGN(format->format.height, 2), + max(__crop->height / 8, MT9V032_WINDOW_HEIGHT_MIN), + __crop->height); + + hratio = DIV_ROUND_CLOSEST(__crop->width, width); + vratio = DIV_ROUND_CLOSEST(__crop->height, height); + + __format = __mt9v032_get_pad_format(mt9v032, fh, format->pad, + format->which); + __format->width = __crop->width / hratio; + __format->height = __crop->height / vratio; + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + mt9v032_configure_pixel_rate(mt9v032, hratio); + + format->format = *__format; + + return 0; +} + +static int mt9v032_get_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + + crop->rect = *__mt9v032_get_pad_crop(mt9v032, fh, crop->pad, + crop->which); + return 0; +} + +static int mt9v032_set_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + struct v4l2_rect rect; + + /* Clamp the crop rectangle boundaries and align them to a non multiple + * of 2 pixels to ensure a GRBG Bayer pattern. + */ + rect.left = clamp(ALIGN(crop->rect.left + 1, 2) - 1, + MT9V032_COLUMN_START_MIN, + MT9V032_COLUMN_START_MAX); + rect.top = clamp(ALIGN(crop->rect.top + 1, 2) - 1, + MT9V032_ROW_START_MIN, + MT9V032_ROW_START_MAX); + rect.width = clamp(ALIGN(crop->rect.width, 2), + MT9V032_WINDOW_WIDTH_MIN, + MT9V032_WINDOW_WIDTH_MAX); + rect.height = clamp(ALIGN(crop->rect.height, 2), + MT9V032_WINDOW_HEIGHT_MIN, + MT9V032_WINDOW_HEIGHT_MAX); + + rect.width = min(rect.width, MT9V032_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min(rect.height, MT9V032_PIXEL_ARRAY_HEIGHT - rect.top); + + __crop = __mt9v032_get_pad_crop(mt9v032, fh, crop->pad, crop->which); + + if (rect.width != __crop->width || rect.height != __crop->height) { + /* Reset the output image size if the crop rectangle size has + * been modified. + */ + __format = __mt9v032_get_pad_format(mt9v032, fh, crop->pad, + crop->which); + __format->width = rect.width; + __format->height = rect.height; + if (crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) + mt9v032_configure_pixel_rate(mt9v032, 1); + } + + *__crop = rect; + crop->rect = rect; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev control operations + */ + +#define V4L2_CID_TEST_PATTERN (V4L2_CID_USER_BASE | 0x1001) + +static int mt9v032_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9v032 *mt9v032 = + container_of(ctrl->handler, struct mt9v032, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&mt9v032->subdev); + u32 freq; + u16 data; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + return mt9v032_update_aec_agc(mt9v032, MT9V032_AGC_ENABLE, + ctrl->val); + + case V4L2_CID_GAIN: + return mt9v032_write(client, MT9V032_ANALOG_GAIN, ctrl->val); + + case V4L2_CID_EXPOSURE_AUTO: + return mt9v032_update_aec_agc(mt9v032, MT9V032_AEC_ENABLE, + !ctrl->val); + + case V4L2_CID_EXPOSURE: + return mt9v032_write(client, MT9V032_TOTAL_SHUTTER_WIDTH, + ctrl->val); + + case V4L2_CID_HBLANK: + mt9v032->hblank = ctrl->val; + return mt9v032_update_hblank(mt9v032); + + case V4L2_CID_VBLANK: + return mt9v032_write(client, MT9V032_VERTICAL_BLANKING, + ctrl->val); + + case V4L2_CID_PIXEL_RATE: + case V4L2_CID_LINK_FREQ: + if (mt9v032->link_freq == NULL) + break; + + freq = mt9v032->pdata->link_freqs[mt9v032->link_freq->val]; + mt9v032->pixel_rate->val64 = freq; + mt9v032->sysclk = freq; + break; + + case V4L2_CID_TEST_PATTERN: + switch (ctrl->val) { + case 0: + data = 0; + break; + case 1: + data = MT9V032_TEST_PATTERN_GRAY_VERTICAL + | MT9V032_TEST_PATTERN_ENABLE; + break; + case 2: + data = MT9V032_TEST_PATTERN_GRAY_HORIZONTAL + | MT9V032_TEST_PATTERN_ENABLE; + break; + case 3: + data = MT9V032_TEST_PATTERN_GRAY_DIAGONAL + | MT9V032_TEST_PATTERN_ENABLE; + break; + default: + data = (ctrl->val << MT9V032_TEST_PATTERN_DATA_SHIFT) + | MT9V032_TEST_PATTERN_USE_DATA + | MT9V032_TEST_PATTERN_ENABLE + | MT9V032_TEST_PATTERN_FLIP; + break; + } + + return mt9v032_write(client, MT9V032_TEST_PATTERN, data); + } + + return 0; +} + +static struct v4l2_ctrl_ops mt9v032_ctrl_ops = { + .s_ctrl = mt9v032_s_ctrl, +}; + +static const struct v4l2_ctrl_config mt9v032_ctrls[] = { + { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_TEST_PATTERN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Test pattern", + .min = 0, + .max = 1023, + .step = 1, + .def = 0, + .flags = 0, + } +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int mt9v032_set_power(struct v4l2_subdev *subdev, int on) +{ + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + int ret = 0; + + mutex_lock(&mt9v032->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (mt9v032->power_count == !on) { + ret = __mt9v032_set_power(mt9v032, !!on); + if (ret < 0) + goto done; + } + + /* Update the power count. */ + mt9v032->power_count += on ? 1 : -1; + WARN_ON(mt9v032->power_count < 0); + +done: + mutex_unlock(&mt9v032->power_lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev internal operations + */ + +static int mt9v032_registered(struct v4l2_subdev *subdev) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + s32 data; + int ret; + + dev_info(&client->dev, "Probing MT9V032 at address 0x%02x\n", + client->addr); + + ret = mt9v032_power_on(mt9v032); + if (ret < 0) { + dev_err(&client->dev, "MT9V032 power up failed\n"); + return ret; + } + + /* Read and check the sensor version */ + data = mt9v032_read(client, MT9V032_CHIP_VERSION); + if (data != MT9V032_CHIP_ID_REV1 && data != MT9V032_CHIP_ID_REV3) { + dev_err(&client->dev, "MT9V032 not detected, wrong version " + "0x%04x\n", data); + return -ENODEV; + } + + mt9v032_power_off(mt9v032); + + dev_info(&client->dev, "MT9V032 detected at address 0x%02x\n", + client->addr); + + mt9v032_configure_pixel_rate(mt9v032, 1); + + return ret; +} + +static int mt9v032_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + crop = v4l2_subdev_get_try_crop(fh, 0); + crop->left = MT9V032_COLUMN_START_DEF; + crop->top = MT9V032_ROW_START_DEF; + crop->width = MT9V032_WINDOW_WIDTH_DEF; + crop->height = MT9V032_WINDOW_HEIGHT_DEF; + + format = v4l2_subdev_get_try_format(fh, 0); + format->code = V4L2_MBUS_FMT_SGRBG10_1X10; + format->width = MT9V032_WINDOW_WIDTH_DEF; + format->height = MT9V032_WINDOW_HEIGHT_DEF; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + return mt9v032_set_power(subdev, 1); +} + +static int mt9v032_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + return mt9v032_set_power(subdev, 0); +} + +static struct v4l2_subdev_core_ops mt9v032_subdev_core_ops = { + .s_power = mt9v032_set_power, +}; + +static struct v4l2_subdev_video_ops mt9v032_subdev_video_ops = { + .s_stream = mt9v032_s_stream, +}; + +static struct v4l2_subdev_pad_ops mt9v032_subdev_pad_ops = { + .enum_mbus_code = mt9v032_enum_mbus_code, + .enum_frame_size = mt9v032_enum_frame_size, + .get_fmt = mt9v032_get_format, + .set_fmt = mt9v032_set_format, + .get_crop = mt9v032_get_crop, + .set_crop = mt9v032_set_crop, +}; + +static struct v4l2_subdev_ops mt9v032_subdev_ops = { + .core = &mt9v032_subdev_core_ops, + .video = &mt9v032_subdev_video_ops, + .pad = &mt9v032_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops mt9v032_subdev_internal_ops = { + .registered = mt9v032_registered, + .open = mt9v032_open, + .close = mt9v032_close, +}; + +/* ----------------------------------------------------------------------------- + * Driver initialization and probing + */ + +static int mt9v032_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9v032_platform_data *pdata = client->dev.platform_data; + struct mt9v032 *mt9v032; + unsigned int i; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&client->adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9v032 = kzalloc(sizeof(*mt9v032), GFP_KERNEL); + if (!mt9v032) + return -ENOMEM; + + mutex_init(&mt9v032->power_lock); + mt9v032->pdata = pdata; + + v4l2_ctrl_handler_init(&mt9v032->ctrls, ARRAY_SIZE(mt9v032_ctrls) + 8); + + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_GAIN, MT9V032_ANALOG_GAIN_MIN, + MT9V032_ANALOG_GAIN_MAX, 1, MT9V032_ANALOG_GAIN_DEF); + v4l2_ctrl_new_std_menu(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_EXPOSURE, MT9V032_TOTAL_SHUTTER_WIDTH_MIN, + MT9V032_TOTAL_SHUTTER_WIDTH_MAX, 1, + MT9V032_TOTAL_SHUTTER_WIDTH_DEF); + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_HBLANK, MT9V032_HORIZONTAL_BLANKING_MIN, + MT9V032_HORIZONTAL_BLANKING_MAX, 1, + MT9V032_HORIZONTAL_BLANKING_DEF); + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_VBLANK, MT9V032_VERTICAL_BLANKING_MIN, + MT9V032_VERTICAL_BLANKING_MAX, 1, + MT9V032_VERTICAL_BLANKING_DEF); + + mt9v032->pixel_rate = + v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, 0, 1, 0); + + if (pdata && pdata->link_freqs) { + unsigned int def = 0; + + for (i = 0; pdata->link_freqs[i]; ++i) { + if (pdata->link_freqs[i] == pdata->link_def_freq) + def = i; + } + + mt9v032->link_freq = + v4l2_ctrl_new_int_menu(&mt9v032->ctrls, + &mt9v032_ctrl_ops, + V4L2_CID_LINK_FREQ, i - 1, def, + pdata->link_freqs); + v4l2_ctrl_cluster(2, &mt9v032->link_freq); + } + + for (i = 0; i < ARRAY_SIZE(mt9v032_ctrls); ++i) + v4l2_ctrl_new_custom(&mt9v032->ctrls, &mt9v032_ctrls[i], NULL); + + mt9v032->subdev.ctrl_handler = &mt9v032->ctrls; + + if (mt9v032->ctrls.error) + printk(KERN_INFO "%s: control initialization error %d\n", + __func__, mt9v032->ctrls.error); + + mt9v032->crop.left = MT9V032_COLUMN_START_DEF; + mt9v032->crop.top = MT9V032_ROW_START_DEF; + mt9v032->crop.width = MT9V032_WINDOW_WIDTH_DEF; + mt9v032->crop.height = MT9V032_WINDOW_HEIGHT_DEF; + + mt9v032->format.code = V4L2_MBUS_FMT_SGRBG10_1X10; + mt9v032->format.width = MT9V032_WINDOW_WIDTH_DEF; + mt9v032->format.height = MT9V032_WINDOW_HEIGHT_DEF; + mt9v032->format.field = V4L2_FIELD_NONE; + mt9v032->format.colorspace = V4L2_COLORSPACE_SRGB; + + mt9v032->aec_agc = MT9V032_AEC_ENABLE | MT9V032_AGC_ENABLE; + mt9v032->hblank = MT9V032_HORIZONTAL_BLANKING_DEF; + mt9v032->sysclk = MT9V032_SYSCLK_FREQ_DEF; + + v4l2_i2c_subdev_init(&mt9v032->subdev, client, &mt9v032_subdev_ops); + mt9v032->subdev.internal_ops = &mt9v032_subdev_internal_ops; + mt9v032->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + mt9v032->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_init(&mt9v032->subdev.entity, 1, &mt9v032->pad, 0); + if (ret < 0) + kfree(mt9v032); + + return ret; +} + +static int mt9v032_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct mt9v032 *mt9v032 = to_mt9v032(subdev); + + v4l2_device_unregister_subdev(subdev); + media_entity_cleanup(&subdev->entity); + kfree(mt9v032); + return 0; +} + +static const struct i2c_device_id mt9v032_id[] = { + { "mt9v032", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9v032_id); + +static struct i2c_driver mt9v032_driver = { + .driver = { + .name = "mt9v032", + }, + .probe = mt9v032_probe, + .remove = mt9v032_remove, + .id_table = mt9v032_id, +}; + +module_i2c_driver(mt9v032_driver); + +MODULE_DESCRIPTION("Aptina MT9V032 Camera driver"); +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/noon010pc30.c b/drivers/media/i2c/noon010pc30.c similarity index 100% rename from drivers/media/video/noon010pc30.c rename to drivers/media/i2c/noon010pc30.c diff --git a/drivers/media/video/ov7670.c b/drivers/media/i2c/ov7670.c similarity index 100% rename from drivers/media/video/ov7670.c rename to drivers/media/i2c/ov7670.c diff --git a/drivers/media/i2c/s5k4ecgx.c b/drivers/media/i2c/s5k4ecgx.c new file mode 100644 index 000000000000..49c1b3abb425 --- /dev/null +++ b/drivers/media/i2c/s5k4ecgx.c @@ -0,0 +1,1036 @@ +/* + * Driver for Samsung S5K4ECGX 1/4" 5Mp CMOS Image Sensor SoC + * with an Embedded Image Signal Processor. + * + * Copyright (C) 2012, Linaro, Sangwook Lee + * Copyright (C) 2012, Insignal Co,. Ltd, Homin Lee + * + * Based on s5k6aa and noon010pc30 driver + * Copyright (C) 2011, Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, int, 0644); + +#define S5K4ECGX_DRIVER_NAME "s5k4ecgx" +#define S5K4ECGX_FIRMWARE "s5k4ecgx.bin" + +/* Firmware revision information */ +#define REG_FW_REVISION 0x700001a6 +#define REG_FW_VERSION 0x700001a4 +#define S5K4ECGX_REVISION_1_1 0x11 +#define S5K4ECGX_FW_VERSION 0x4ec0 + +/* General purpose parameters */ +#define REG_USER_BRIGHTNESS 0x7000022c +#define REG_USER_CONTRAST 0x7000022e +#define REG_USER_SATURATION 0x70000230 + +#define REG_G_ENABLE_PREV 0x7000023e +#define REG_G_ENABLE_PREV_CHG 0x70000240 +#define REG_G_NEW_CFG_SYNC 0x7000024a +#define REG_G_PREV_IN_WIDTH 0x70000250 +#define REG_G_PREV_IN_HEIGHT 0x70000252 +#define REG_G_PREV_IN_XOFFS 0x70000254 +#define REG_G_PREV_IN_YOFFS 0x70000256 +#define REG_G_CAP_IN_WIDTH 0x70000258 +#define REG_G_CAP_IN_HEIGHT 0x7000025a +#define REG_G_CAP_IN_XOFFS 0x7000025c +#define REG_G_CAP_IN_YOFFS 0x7000025e +#define REG_G_INPUTS_CHANGE_REQ 0x70000262 +#define REG_G_ACTIVE_PREV_CFG 0x70000266 +#define REG_G_PREV_CFG_CHG 0x70000268 +#define REG_G_PREV_OPEN_AFTER_CH 0x7000026a + +/* Preview context register sets. n = 0...4. */ +#define PREG(n, x) ((n) * 0x30 + (x)) +#define REG_P_OUT_WIDTH(n) PREG(n, 0x700002a6) +#define REG_P_OUT_HEIGHT(n) PREG(n, 0x700002a8) +#define REG_P_FMT(n) PREG(n, 0x700002aa) +#define REG_P_PVI_MASK(n) PREG(n, 0x700002b4) +#define REG_P_FR_TIME_TYPE(n) PREG(n, 0x700002be) +#define FR_TIME_DYNAMIC 0 +#define FR_TIME_FIXED 1 +#define FR_TIME_FIXED_ACCURATE 2 +#define REG_P_FR_TIME_Q_TYPE(n) PREG(n, 0x700002c0) +#define FR_TIME_Q_DYNAMIC 0 +#define FR_TIME_Q_BEST_FRRATE 1 +#define FR_TIME_Q_BEST_QUALITY 2 + +/* Frame period in 0.1 ms units */ +#define REG_P_MAX_FR_TIME(n) PREG(n, 0x700002c2) +#define REG_P_MIN_FR_TIME(n) PREG(n, 0x700002c4) +#define US_TO_FR_TIME(__t) ((__t) / 100) +#define REG_P_PREV_MIRROR(n) PREG(n, 0x700002d0) +#define REG_P_CAP_MIRROR(n) PREG(n, 0x700002d2) + +#define REG_G_PREVZOOM_IN_WIDTH 0x70000494 +#define REG_G_PREVZOOM_IN_HEIGHT 0x70000496 +#define REG_G_PREVZOOM_IN_XOFFS 0x70000498 +#define REG_G_PREVZOOM_IN_YOFFS 0x7000049a +#define REG_G_CAPZOOM_IN_WIDTH 0x7000049c +#define REG_G_CAPZOOM_IN_HEIGHT 0x7000049e +#define REG_G_CAPZOOM_IN_XOFFS 0x700004a0 +#define REG_G_CAPZOOM_IN_YOFFS 0x700004a2 + +/* n = 0...4 */ +#define REG_USER_SHARPNESS(n) (0x70000a28 + (n) * 0xb6) + +/* Reduce sharpness range for user space API */ +#define SHARPNESS_DIV 8208 +#define TOK_TERM 0xffffffff + +/* + * FIXME: This is copied from s5k6aa, because of no information + * in the S5K4ECGX datasheet. + * H/W register Interface (0xd0000000 - 0xd0000fff) + */ +#define AHB_MSB_ADDR_PTR 0xfcfc +#define GEN_REG_OFFSH 0xd000 +#define REG_CMDWR_ADDRH 0x0028 +#define REG_CMDWR_ADDRL 0x002a +#define REG_CMDRD_ADDRH 0x002c +#define REG_CMDRD_ADDRL 0x002e +#define REG_CMDBUF0_ADDR 0x0f12 + +struct s5k4ecgx_frmsize { + struct v4l2_frmsize_discrete size; + /* Fixed sensor matrix crop rectangle */ + struct v4l2_rect input_window; +}; + +struct regval_list { + u32 addr; + u16 val; +}; + +/* + * TODO: currently only preview is supported and snapshot (capture) + * is not implemented yet + */ +static const struct s5k4ecgx_frmsize s5k4ecgx_prev_sizes[] = { + { + .size = { 176, 144 }, + .input_window = { 0x00, 0x00, 0x928, 0x780 }, + }, { + .size = { 352, 288 }, + .input_window = { 0x00, 0x00, 0x928, 0x780 }, + }, { + .size = { 640, 480 }, + .input_window = { 0x00, 0x00, 0xa00, 0x780 }, + }, { + .size = { 720, 480 }, + .input_window = { 0x00, 0x00, 0xa00, 0x6a8 }, + } +}; + +#define S5K4ECGX_NUM_PREV ARRAY_SIZE(s5k4ecgx_prev_sizes) + +struct s5k4ecgx_pixfmt { + enum v4l2_mbus_pixelcode code; + u32 colorspace; + /* REG_TC_PCFG_Format register value */ + u16 reg_p_format; +}; + +/* By default value, output from sensor will be YUV422 0-255 */ +static const struct s5k4ecgx_pixfmt s5k4ecgx_formats[] = { + { V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG, 5 }, +}; + +static const char * const s5k4ecgx_supply_names[] = { + /* + * Usually 2.8V is used for analog power (vdda) + * and digital IO (vddio, vdddcore) + */ + "vdda", + "vddio", + "vddcore", + "vddreg", /* The internal s5k4ecgx regulator's supply (1.8V) */ +}; + +#define S5K4ECGX_NUM_SUPPLIES ARRAY_SIZE(s5k4ecgx_supply_names) + +enum s5k4ecgx_gpio_id { + STBY, + RST, + GPIO_NUM, +}; + +struct s5k4ecgx { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler handler; + + struct s5k4ecgx_platform_data *pdata; + const struct s5k4ecgx_pixfmt *curr_pixfmt; + const struct s5k4ecgx_frmsize *curr_frmsize; + struct mutex lock; + u8 streaming; + u8 set_params; + + struct regulator_bulk_data supplies[S5K4ECGX_NUM_SUPPLIES]; + struct s5k4ecgx_gpio gpio[GPIO_NUM]; +}; + +static inline struct s5k4ecgx *to_s5k4ecgx(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s5k4ecgx, sd); +} + +static int s5k4ecgx_i2c_read(struct i2c_client *client, u16 addr, u16 *val) +{ + u8 wbuf[2] = { addr >> 8, addr & 0xff }; + struct i2c_msg msg[2]; + u8 rbuf[2]; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 2; + msg[0].buf = wbuf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = rbuf; + + ret = i2c_transfer(client->adapter, msg, 2); + *val = be16_to_cpu(*((u16 *)rbuf)); + + v4l2_dbg(4, debug, client, "i2c_read: 0x%04X : 0x%04x\n", addr, *val); + + return ret == 2 ? 0 : ret; +} + +static int s5k4ecgx_i2c_write(struct i2c_client *client, u16 addr, u16 val) +{ + u8 buf[4] = { addr >> 8, addr & 0xff, val >> 8, val & 0xff }; + + int ret = i2c_master_send(client, buf, 4); + v4l2_dbg(4, debug, client, "i2c_write: 0x%04x : 0x%04x\n", addr, val); + + return ret == 4 ? 0 : ret; +} + +static int s5k4ecgx_write(struct i2c_client *client, u32 addr, u16 val) +{ + u16 high = addr >> 16, low = addr & 0xffff; + int ret; + + v4l2_dbg(3, debug, client, "write: 0x%08x : 0x%04x\n", addr, val); + + ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRH, high); + if (!ret) + ret = s5k4ecgx_i2c_write(client, REG_CMDWR_ADDRL, low); + if (!ret) + ret = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); + + return ret; +} + +static int s5k4ecgx_read(struct i2c_client *client, u32 addr, u16 *val) +{ + u16 high = addr >> 16, low = addr & 0xffff; + int ret; + + ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRH, high); + if (!ret) + ret = s5k4ecgx_i2c_write(client, REG_CMDRD_ADDRL, low); + if (!ret) + ret = s5k4ecgx_i2c_read(client, REG_CMDBUF0_ADDR, val); + if (!ret) + dev_err(&client->dev, "Failed to execute read command\n"); + + return ret; +} + +static int s5k4ecgx_read_fw_ver(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u16 hw_rev, fw_ver = 0; + int ret; + + ret = s5k4ecgx_read(client, REG_FW_VERSION, &fw_ver); + if (ret < 0 || fw_ver != S5K4ECGX_FW_VERSION) { + v4l2_err(sd, "FW version check failed!\n"); + return -ENODEV; + } + + ret = s5k4ecgx_read(client, REG_FW_REVISION, &hw_rev); + if (ret < 0) + return ret; + + v4l2_info(sd, "chip found FW ver: 0x%x, HW rev: 0x%x\n", + fw_ver, hw_rev); + return 0; +} + +static int s5k4ecgx_set_ahb_address(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + /* Set APB peripherals start address */ + ret = s5k4ecgx_i2c_write(client, AHB_MSB_ADDR_PTR, GEN_REG_OFFSH); + if (ret < 0) + return ret; + /* + * FIXME: This is copied from s5k6aa, because of no information + * in s5k4ecgx's datasheet. + * sw_reset is activated to put device into idle status + */ + ret = s5k4ecgx_i2c_write(client, 0x0010, 0x0001); + if (ret < 0) + return ret; + + ret = s5k4ecgx_i2c_write(client, 0x1030, 0x0000); + if (ret < 0) + return ret; + /* Halt ARM CPU */ + return s5k4ecgx_i2c_write(client, 0x0014, 0x0001); +} + +#define FW_CRC_SIZE 4 +/* Register address, value are 4, 2 bytes */ +#define FW_RECORD_SIZE 6 +/* + * The firmware has following format: + * < total number of records (4 bytes + 2 bytes padding) N >, + * < record 0 >, ..., < record N - 1 >, < CRC32-CCITT (4-bytes) >, + * where "record" is a 4-byte register address followed by 2-byte + * register value (little endian). + * The firmware generator can be found in following git repository: + * git://git.linaro.org/people/sangwook/fimc-v4l2-app.git + */ +static int s5k4ecgx_load_firmware(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + const struct firmware *fw; + const u8 *ptr; + int err, i, regs_num; + u32 addr, crc, crc_file, addr_inc = 0; + u16 val; + + err = request_firmware(&fw, S5K4ECGX_FIRMWARE, sd->v4l2_dev->dev); + if (err) { + v4l2_err(sd, "Failed to read firmware %s\n", S5K4ECGX_FIRMWARE); + return err; + } + regs_num = le32_to_cpu(get_unaligned_le32(fw->data)); + + v4l2_dbg(3, debug, sd, "FW: %s size %d register sets %d\n", + S5K4ECGX_FIRMWARE, fw->size, regs_num); + + regs_num++; /* Add header */ + if (fw->size != regs_num * FW_RECORD_SIZE + FW_CRC_SIZE) { + err = -EINVAL; + goto fw_out; + } + crc_file = le32_to_cpu(get_unaligned_le32(fw->data + + regs_num * FW_RECORD_SIZE)); + crc = crc32_le(~0, fw->data, regs_num * FW_RECORD_SIZE); + if (crc != crc_file) { + v4l2_err(sd, "FW: invalid crc (%#x:%#x)\n", crc, crc_file); + err = -EINVAL; + goto fw_out; + } + ptr = fw->data + FW_RECORD_SIZE; + for (i = 1; i < regs_num; i++) { + addr = le32_to_cpu(get_unaligned_le32(ptr)); + ptr += sizeof(u32); + val = le16_to_cpu(get_unaligned_le16(ptr)); + ptr += sizeof(u16); + if (addr - addr_inc != 2) + err = s5k4ecgx_write(client, addr, val); + else + err = s5k4ecgx_i2c_write(client, REG_CMDBUF0_ADDR, val); + if (err) + break; + addr_inc = addr; + } +fw_out: + release_firmware(fw); + return err; +} + +/* Set preview and capture input window */ +static int s5k4ecgx_set_input_window(struct i2c_client *c, + const struct v4l2_rect *r) +{ + int ret; + + ret = s5k4ecgx_write(c, REG_G_PREV_IN_WIDTH, r->width); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREV_IN_HEIGHT, r->height); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREV_IN_XOFFS, r->left); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREV_IN_YOFFS, r->top); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAP_IN_WIDTH, r->width); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAP_IN_HEIGHT, r->height); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAP_IN_XOFFS, r->left); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAP_IN_YOFFS, r->top); + + return ret; +} + +/* Set preview and capture zoom input window */ +static int s5k4ecgx_set_zoom_window(struct i2c_client *c, + const struct v4l2_rect *r) +{ + int ret; + + ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_WIDTH, r->width); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_HEIGHT, r->height); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_XOFFS, r->left); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_PREVZOOM_IN_YOFFS, r->top); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_WIDTH, r->width); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_HEIGHT, r->height); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_XOFFS, r->left); + if (!ret) + ret = s5k4ecgx_write(c, REG_G_CAPZOOM_IN_YOFFS, r->top); + + return ret; +} + +static int s5k4ecgx_set_output_framefmt(struct s5k4ecgx *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int ret; + + ret = s5k4ecgx_write(client, REG_P_OUT_WIDTH(0), + priv->curr_frmsize->size.width); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_OUT_HEIGHT(0), + priv->curr_frmsize->size.height); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_FMT(0), + priv->curr_pixfmt->reg_p_format); + return ret; +} + +static int s5k4ecgx_init_sensor(struct v4l2_subdev *sd) +{ + int ret; + + ret = s5k4ecgx_set_ahb_address(sd); + + /* The delay is from manufacturer's settings */ + msleep(100); + + if (!ret) + ret = s5k4ecgx_load_firmware(sd); + if (ret) + v4l2_err(sd, "Failed to write initial settings\n"); + + return ret; +} + +static int s5k4ecgx_gpio_set_value(struct s5k4ecgx *priv, int id, u32 val) +{ + if (!gpio_is_valid(priv->gpio[id].gpio)) + return 0; + gpio_set_value(priv->gpio[id].gpio, val); + + return 1; +} + +static int __s5k4ecgx_power_on(struct s5k4ecgx *priv) +{ + int ret; + + ret = regulator_bulk_enable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); + if (ret) + return ret; + usleep_range(30, 50); + + /* The polarity of STBY is controlled by TSP */ + if (s5k4ecgx_gpio_set_value(priv, STBY, priv->gpio[STBY].level)) + usleep_range(30, 50); + + if (s5k4ecgx_gpio_set_value(priv, RST, priv->gpio[RST].level)) + usleep_range(30, 50); + + return 0; +} + +static int __s5k4ecgx_power_off(struct s5k4ecgx *priv) +{ + if (s5k4ecgx_gpio_set_value(priv, RST, !priv->gpio[RST].level)) + usleep_range(30, 50); + + if (s5k4ecgx_gpio_set_value(priv, STBY, !priv->gpio[STBY].level)) + usleep_range(30, 50); + + priv->streaming = 0; + + return regulator_bulk_disable(S5K4ECGX_NUM_SUPPLIES, priv->supplies); +} + +/* Find nearest matching image pixel size. */ +static int s5k4ecgx_try_frame_size(struct v4l2_mbus_framefmt *mf, + const struct s5k4ecgx_frmsize **size) +{ + unsigned int min_err = ~0; + int i = ARRAY_SIZE(s5k4ecgx_prev_sizes); + const struct s5k4ecgx_frmsize *fsize = &s5k4ecgx_prev_sizes[0], + *match = NULL; + + while (i--) { + int err = abs(fsize->size.width - mf->width) + + abs(fsize->size.height - mf->height); + if (err < min_err) { + min_err = err; + match = fsize; + } + fsize++; + } + if (match) { + mf->width = match->size.width; + mf->height = match->size.height; + if (size) + *size = match; + return 0; + } + + return -EINVAL; +} + +static int s5k4ecgx_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(s5k4ecgx_formats)) + return -EINVAL; + code->code = s5k4ecgx_formats[code->index].code; + + return 0; +} + +static int s5k4ecgx_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + struct v4l2_mbus_framefmt *mf; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + if (fh) { + mf = v4l2_subdev_get_try_format(fh, 0); + fmt->format = *mf; + } + return 0; + } + + mf = &fmt->format; + + mutex_lock(&priv->lock); + mf->width = priv->curr_frmsize->size.width; + mf->height = priv->curr_frmsize->size.height; + mf->code = priv->curr_pixfmt->code; + mf->colorspace = priv->curr_pixfmt->colorspace; + mf->field = V4L2_FIELD_NONE; + mutex_unlock(&priv->lock); + + return 0; +} + +static const struct s5k4ecgx_pixfmt *s5k4ecgx_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + int i = ARRAY_SIZE(s5k4ecgx_formats); + + while (--i) + if (mf->code == s5k4ecgx_formats[i].code) + break; + mf->code = s5k4ecgx_formats[i].code; + + return &s5k4ecgx_formats[i]; +} + +static int s5k4ecgx_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + const struct s5k4ecgx_frmsize *fsize = NULL; + const struct s5k4ecgx_pixfmt *pf; + struct v4l2_mbus_framefmt *mf; + int ret = 0; + + pf = s5k4ecgx_try_fmt(sd, &fmt->format); + s5k4ecgx_try_frame_size(&fmt->format, &fsize); + fmt->format.colorspace = V4L2_COLORSPACE_JPEG; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + if (fh) { + mf = v4l2_subdev_get_try_format(fh, 0); + *mf = fmt->format; + } + return 0; + } + + mutex_lock(&priv->lock); + if (!priv->streaming) { + priv->curr_frmsize = fsize; + priv->curr_pixfmt = pf; + priv->set_params = 1; + } else { + ret = -EBUSY; + } + mutex_unlock(&priv->lock); + + return ret; +} + +static const struct v4l2_subdev_pad_ops s5k4ecgx_pad_ops = { + .enum_mbus_code = s5k4ecgx_enum_mbus_code, + .get_fmt = s5k4ecgx_get_fmt, + .set_fmt = s5k4ecgx_set_fmt, +}; + +/* + * V4L2 subdev controls + */ +static int s5k4ecgx_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = &container_of(ctrl->handler, struct s5k4ecgx, + handler)->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + unsigned int i; + int err = 0; + + v4l2_dbg(1, debug, sd, "ctrl: 0x%x, value: %d\n", ctrl->id, ctrl->val); + + mutex_lock(&priv->lock); + switch (ctrl->id) { + case V4L2_CID_CONTRAST: + err = s5k4ecgx_write(client, REG_USER_CONTRAST, ctrl->val); + break; + + case V4L2_CID_SATURATION: + err = s5k4ecgx_write(client, REG_USER_SATURATION, ctrl->val); + break; + + case V4L2_CID_SHARPNESS: + /* TODO: Revisit, is this setting for all presets ? */ + for (i = 0; i < 4 && !err; i++) + err = s5k4ecgx_write(client, REG_USER_SHARPNESS(i), + ctrl->val * SHARPNESS_DIV); + break; + + case V4L2_CID_BRIGHTNESS: + err = s5k4ecgx_write(client, REG_USER_BRIGHTNESS, ctrl->val); + break; + } + mutex_unlock(&priv->lock); + if (err < 0) + v4l2_err(sd, "Failed to write s_ctrl err %d\n", err); + + return err; +} + +static const struct v4l2_ctrl_ops s5k4ecgx_ctrl_ops = { + .s_ctrl = s5k4ecgx_s_ctrl, +}; + +/* + * Reading s5k4ecgx version information + */ +static int s5k4ecgx_registered(struct v4l2_subdev *sd) +{ + int ret; + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + + mutex_lock(&priv->lock); + ret = __s5k4ecgx_power_on(priv); + if (!ret) { + ret = s5k4ecgx_read_fw_ver(sd); + __s5k4ecgx_power_off(priv); + } + mutex_unlock(&priv->lock); + + return ret; +} + +/* + * V4L2 subdev internal operations + */ +static int s5k4ecgx_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *mf = v4l2_subdev_get_try_format(fh, 0); + + mf->width = s5k4ecgx_prev_sizes[0].size.width; + mf->height = s5k4ecgx_prev_sizes[0].size.height; + mf->code = s5k4ecgx_formats[0].code; + mf->colorspace = V4L2_COLORSPACE_JPEG; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static const struct v4l2_subdev_internal_ops s5k4ecgx_subdev_internal_ops = { + .registered = s5k4ecgx_registered, + .open = s5k4ecgx_open, +}; + +static int s5k4ecgx_s_power(struct v4l2_subdev *sd, int on) +{ + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + int ret; + + v4l2_dbg(1, debug, sd, "Switching %s\n", on ? "on" : "off"); + + if (on) { + ret = __s5k4ecgx_power_on(priv); + if (ret < 0) + return ret; + /* Time to stabilize sensor */ + msleep(100); + ret = s5k4ecgx_init_sensor(sd); + if (ret < 0) + __s5k4ecgx_power_off(priv); + else + priv->set_params = 1; + } else { + ret = __s5k4ecgx_power_off(priv); + } + + return ret; +} + +static int s5k4ecgx_log_status(struct v4l2_subdev *sd) +{ + v4l2_ctrl_handler_log_status(sd->ctrl_handler, sd->name); + + return 0; +} + +static const struct v4l2_subdev_core_ops s5k4ecgx_core_ops = { + .s_power = s5k4ecgx_s_power, + .log_status = s5k4ecgx_log_status, +}; + +static int __s5k4ecgx_s_params(struct s5k4ecgx *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + const struct v4l2_rect *crop_rect = &priv->curr_frmsize->input_window; + int ret; + + ret = s5k4ecgx_set_input_window(client, crop_rect); + if (!ret) + ret = s5k4ecgx_set_zoom_window(client, crop_rect); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_INPUTS_CHANGE_REQ, 1); + if (!ret) + ret = s5k4ecgx_write(client, 0x70000a1e, 0x28); + if (!ret) + ret = s5k4ecgx_write(client, 0x70000ad4, 0x3c); + if (!ret) + ret = s5k4ecgx_set_output_framefmt(priv); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_PVI_MASK(0), 0x52); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_FR_TIME_TYPE(0), + FR_TIME_DYNAMIC); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_FR_TIME_Q_TYPE(0), + FR_TIME_Q_BEST_FRRATE); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_MIN_FR_TIME(0), + US_TO_FR_TIME(33300)); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_MAX_FR_TIME(0), + US_TO_FR_TIME(66600)); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_PREV_MIRROR(0), 0); + if (!ret) + ret = s5k4ecgx_write(client, REG_P_CAP_MIRROR(0), 0); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_ACTIVE_PREV_CFG, 0); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_PREV_OPEN_AFTER_CH, 1); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_NEW_CFG_SYNC, 1); + if (!ret) + ret = s5k4ecgx_write(client, REG_G_PREV_CFG_CHG, 1); + + return ret; +} + +static int __s5k4ecgx_s_stream(struct s5k4ecgx *priv, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int ret; + + if (on && priv->set_params) { + ret = __s5k4ecgx_s_params(priv); + if (ret < 0) + return ret; + priv->set_params = 0; + } + /* + * This enables/disables preview stream only. Capture requests + * are not supported yet. + */ + ret = s5k4ecgx_write(client, REG_G_ENABLE_PREV, on); + if (ret < 0) + return ret; + return s5k4ecgx_write(client, REG_G_ENABLE_PREV_CHG, 1); +} + +static int s5k4ecgx_s_stream(struct v4l2_subdev *sd, int on) +{ + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + int ret = 0; + + v4l2_dbg(1, debug, sd, "Turn streaming %s\n", on ? "on" : "off"); + + mutex_lock(&priv->lock); + + if (priv->streaming == !on) { + ret = __s5k4ecgx_s_stream(priv, on); + if (!ret) + priv->streaming = on & 1; + } + + mutex_unlock(&priv->lock); + return ret; +} + +static const struct v4l2_subdev_video_ops s5k4ecgx_video_ops = { + .s_stream = s5k4ecgx_s_stream, +}; + +static const struct v4l2_subdev_ops s5k4ecgx_ops = { + .core = &s5k4ecgx_core_ops, + .pad = &s5k4ecgx_pad_ops, + .video = &s5k4ecgx_video_ops, +}; + +/* + * GPIO setup + */ +static int s5k4ecgx_config_gpio(int nr, int val, const char *name) +{ + unsigned long flags = val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + int ret; + + if (!gpio_is_valid(nr)) + return 0; + ret = gpio_request_one(nr, flags, name); + if (!ret) + gpio_export(nr, 0); + + return ret; +} + +static void s5k4ecgx_free_gpios(struct s5k4ecgx *priv) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(priv->gpio); i++) { + if (!gpio_is_valid(priv->gpio[i].gpio)) + continue; + gpio_free(priv->gpio[i].gpio); + priv->gpio[i].gpio = -EINVAL; + } +} + +static int s5k4ecgx_config_gpios(struct s5k4ecgx *priv, + const struct s5k4ecgx_platform_data *pdata) +{ + const struct s5k4ecgx_gpio *gpio = &pdata->gpio_stby; + int ret; + + priv->gpio[STBY].gpio = -EINVAL; + priv->gpio[RST].gpio = -EINVAL; + + ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_STBY"); + + if (ret) { + s5k4ecgx_free_gpios(priv); + return ret; + } + priv->gpio[STBY] = *gpio; + if (gpio_is_valid(gpio->gpio)) + gpio_set_value(gpio->gpio, 0); + + gpio = &pdata->gpio_reset; + + ret = s5k4ecgx_config_gpio(gpio->gpio, gpio->level, "S5K4ECGX_RST"); + if (ret) { + s5k4ecgx_free_gpios(priv); + return ret; + } + priv->gpio[RST] = *gpio; + if (gpio_is_valid(gpio->gpio)) + gpio_set_value(gpio->gpio, 0); + + return 0; +} + +static int s5k4ecgx_init_v4l2_ctrls(struct s5k4ecgx *priv) +{ + const struct v4l2_ctrl_ops *ops = &s5k4ecgx_ctrl_ops; + struct v4l2_ctrl_handler *hdl = &priv->handler; + int ret; + + ret = v4l2_ctrl_handler_init(hdl, 4); + if (ret) + return ret; + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -208, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, -127, 127, 1, 0); + + /* Sharpness default is 24612, and then (24612/SHARPNESS_DIV) = 2 */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, -32704/SHARPNESS_DIV, + 24612/SHARPNESS_DIV, 1, 2); + if (hdl->error) { + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + priv->sd.ctrl_handler = hdl; + + return 0; +}; + +static int s5k4ecgx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; + struct v4l2_subdev *sd; + struct s5k4ecgx *priv; + int ret, i; + + if (pdata == NULL) { + dev_err(&client->dev, "platform data is missing!\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&client->dev, sizeof(struct s5k4ecgx), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->lock); + priv->streaming = 0; + + sd = &priv->sd; + /* Registering subdev */ + v4l2_i2c_subdev_init(sd, client, &s5k4ecgx_ops); + strlcpy(sd->name, S5K4ECGX_DRIVER_NAME, sizeof(sd->name)); + + sd->internal_ops = &s5k4ecgx_subdev_internal_ops; + /* Support v4l2 sub-device user space API */ + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + priv->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; + ret = media_entity_init(&sd->entity, 1, &priv->pad, 0); + if (ret) + return ret; + + ret = s5k4ecgx_config_gpios(priv, pdata); + if (ret) { + dev_err(&client->dev, "Failed to set gpios\n"); + goto out_err1; + } + for (i = 0; i < S5K4ECGX_NUM_SUPPLIES; i++) + priv->supplies[i].supply = s5k4ecgx_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, S5K4ECGX_NUM_SUPPLIES, + priv->supplies); + if (ret) { + dev_err(&client->dev, "Failed to get regulators\n"); + goto out_err2; + } + ret = s5k4ecgx_init_v4l2_ctrls(priv); + if (ret) + goto out_err2; + + priv->curr_pixfmt = &s5k4ecgx_formats[0]; + priv->curr_frmsize = &s5k4ecgx_prev_sizes[0]; + + return 0; + +out_err2: + s5k4ecgx_free_gpios(priv); +out_err1: + media_entity_cleanup(&priv->sd.entity); + + return ret; +} + +static int s5k4ecgx_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct s5k4ecgx *priv = to_s5k4ecgx(sd); + + mutex_destroy(&priv->lock); + s5k4ecgx_free_gpios(priv); + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&priv->handler); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct i2c_device_id s5k4ecgx_id[] = { + { S5K4ECGX_DRIVER_NAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, s5k4ecgx_id); + +static struct i2c_driver v4l2_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = S5K4ECGX_DRIVER_NAME, + }, + .probe = s5k4ecgx_probe, + .remove = s5k4ecgx_remove, + .id_table = s5k4ecgx_id, +}; + +module_i2c_driver(v4l2_i2c_driver); + +MODULE_DESCRIPTION("Samsung S5K4ECGX 5MP SOC camera"); +MODULE_AUTHOR("Sangwook Lee "); +MODULE_AUTHOR("Seok-Young Jang "); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(S5K4ECGX_FIRMWARE); diff --git a/drivers/media/i2c/s5k6aa.c b/drivers/media/i2c/s5k6aa.c new file mode 100644 index 000000000000..57cd4fa0193d --- /dev/null +++ b/drivers/media/i2c/s5k6aa.c @@ -0,0 +1,1664 @@ +/* + * Driver for Samsung S5K6AAFX SXGA 1/6" 1.3M CMOS Image Sensor + * with embedded SoC ISP. + * + * Copyright (C) 2011, Samsung Electronics Co., Ltd. + * Sylwester Nawrocki + * + * Based on a driver authored by Dongsoo Nathaniel Kim. + * Copyright (C) 2009, Dongsoo Nathaniel Kim + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, int, 0644); + +#define DRIVER_NAME "S5K6AA" + +/* The token to indicate array termination */ +#define S5K6AA_TERM 0xffff +#define S5K6AA_OUT_WIDTH_DEF 640 +#define S5K6AA_OUT_HEIGHT_DEF 480 +#define S5K6AA_WIN_WIDTH_MAX 1280 +#define S5K6AA_WIN_HEIGHT_MAX 1024 +#define S5K6AA_WIN_WIDTH_MIN 8 +#define S5K6AA_WIN_HEIGHT_MIN 8 + +/* + * H/W register Interface (0xD0000000 - 0xD0000FFF) + */ +#define AHB_MSB_ADDR_PTR 0xfcfc +#define GEN_REG_OFFSH 0xd000 +#define REG_CMDWR_ADDRH 0x0028 +#define REG_CMDWR_ADDRL 0x002a +#define REG_CMDRD_ADDRH 0x002c +#define REG_CMDRD_ADDRL 0x002e +#define REG_CMDBUF0_ADDR 0x0f12 +#define REG_CMDBUF1_ADDR 0x0f10 + +/* + * Host S/W Register interface (0x70000000 - 0x70002000) + * The value of the two most significant address bytes is 0x7000, + * (HOST_SWIF_OFFS_H). The register addresses below specify 2 LSBs. + */ +#define HOST_SWIF_OFFSH 0x7000 + +/* Initialization parameters */ +/* Master clock frequency in KHz */ +#define REG_I_INCLK_FREQ_L 0x01b8 +#define REG_I_INCLK_FREQ_H 0x01ba +#define MIN_MCLK_FREQ_KHZ 6000U +#define MAX_MCLK_FREQ_KHZ 27000U +#define REG_I_USE_NPVI_CLOCKS 0x01c6 +#define REG_I_USE_NMIPI_CLOCKS 0x01c8 + +/* Clock configurations, n = 0..2. REG_I_* frequency unit is 4 kHz. */ +#define REG_I_OPCLK_4KHZ(n) ((n) * 6 + 0x01cc) +#define REG_I_MIN_OUTRATE_4KHZ(n) ((n) * 6 + 0x01ce) +#define REG_I_MAX_OUTRATE_4KHZ(n) ((n) * 6 + 0x01d0) +#define SYS_PLL_OUT_FREQ (48000000 / 4000) +#define PCLK_FREQ_MIN (24000000 / 4000) +#define PCLK_FREQ_MAX (48000000 / 4000) +#define REG_I_INIT_PARAMS_UPDATED 0x01e0 +#define REG_I_ERROR_INFO 0x01e2 + +/* General purpose parameters */ +#define REG_USER_BRIGHTNESS 0x01e4 +#define REG_USER_CONTRAST 0x01e6 +#define REG_USER_SATURATION 0x01e8 +#define REG_USER_SHARPBLUR 0x01ea + +#define REG_G_SPEC_EFFECTS 0x01ee +#define REG_G_ENABLE_PREV 0x01f0 +#define REG_G_ENABLE_PREV_CHG 0x01f2 +#define REG_G_NEW_CFG_SYNC 0x01f8 +#define REG_G_PREVZOOM_IN_WIDTH 0x020a +#define REG_G_PREVZOOM_IN_HEIGHT 0x020c +#define REG_G_PREVZOOM_IN_XOFFS 0x020e +#define REG_G_PREVZOOM_IN_YOFFS 0x0210 +#define REG_G_INPUTS_CHANGE_REQ 0x021a +#define REG_G_ACTIVE_PREV_CFG 0x021c +#define REG_G_PREV_CFG_CHG 0x021e +#define REG_G_PREV_OPEN_AFTER_CH 0x0220 +#define REG_G_PREV_CFG_ERROR 0x0222 + +/* Preview control section. n = 0...4. */ +#define PREG(n, x) ((n) * 0x26 + x) +#define REG_P_OUT_WIDTH(n) PREG(n, 0x0242) +#define REG_P_OUT_HEIGHT(n) PREG(n, 0x0244) +#define REG_P_FMT(n) PREG(n, 0x0246) +#define REG_P_MAX_OUT_RATE(n) PREG(n, 0x0248) +#define REG_P_MIN_OUT_RATE(n) PREG(n, 0x024a) +#define REG_P_PVI_MASK(n) PREG(n, 0x024c) +#define REG_P_CLK_INDEX(n) PREG(n, 0x024e) +#define REG_P_FR_RATE_TYPE(n) PREG(n, 0x0250) +#define FR_RATE_DYNAMIC 0 +#define FR_RATE_FIXED 1 +#define FR_RATE_FIXED_ACCURATE 2 +#define REG_P_FR_RATE_Q_TYPE(n) PREG(n, 0x0252) +#define FR_RATE_Q_BEST_FRRATE 1 /* Binning enabled */ +#define FR_RATE_Q_BEST_QUALITY 2 /* Binning disabled */ +/* Frame period in 0.1 ms units */ +#define REG_P_MAX_FR_TIME(n) PREG(n, 0x0254) +#define REG_P_MIN_FR_TIME(n) PREG(n, 0x0256) +/* Conversion to REG_P_[MAX/MIN]_FR_TIME value; __t: time in us */ +#define US_TO_FR_TIME(__t) ((__t) / 100) +#define S5K6AA_MIN_FR_TIME 33300 /* us */ +#define S5K6AA_MAX_FR_TIME 650000 /* us */ +#define S5K6AA_MAX_HIGHRES_FR_TIME 666 /* x100 us */ +/* The below 5 registers are for "device correction" values */ +#define REG_P_COLORTEMP(n) PREG(n, 0x025e) +#define REG_P_PREV_MIRROR(n) PREG(n, 0x0262) + +/* Extended image property controls */ +/* Exposure time in 10 us units */ +#define REG_SF_USR_EXPOSURE_L 0x03c6 +#define REG_SF_USR_EXPOSURE_H 0x03c8 +#define REG_SF_USR_EXPOSURE_CHG 0x03ca +#define REG_SF_USR_TOT_GAIN 0x03cc +#define REG_SF_USR_TOT_GAIN_CHG 0x03ce +#define REG_SF_RGAIN 0x03d0 +#define REG_SF_RGAIN_CHG 0x03d2 +#define REG_SF_GGAIN 0x03d4 +#define REG_SF_GGAIN_CHG 0x03d6 +#define REG_SF_BGAIN 0x03d8 +#define REG_SF_BGAIN_CHG 0x03da +#define REG_SF_FLICKER_QUANT 0x03dc +#define REG_SF_FLICKER_QUANT_CHG 0x03de + +/* Output interface (parallel/MIPI) setup */ +#define REG_OIF_EN_MIPI_LANES 0x03fa +#define REG_OIF_EN_PACKETS 0x03fc +#define REG_OIF_CFG_CHG 0x03fe + +/* Auto-algorithms enable mask */ +#define REG_DBG_AUTOALG_EN 0x0400 +#define AALG_ALL_EN_MASK (1 << 0) +#define AALG_AE_EN_MASK (1 << 1) +#define AALG_DIVLEI_EN_MASK (1 << 2) +#define AALG_WB_EN_MASK (1 << 3) +#define AALG_FLICKER_EN_MASK (1 << 5) +#define AALG_FIT_EN_MASK (1 << 6) +#define AALG_WRHW_EN_MASK (1 << 7) + +/* Firmware revision information */ +#define REG_FW_APIVER 0x012e +#define S5K6AAFX_FW_APIVER 0x0001 +#define REG_FW_REVISION 0x0130 + +/* For now we use only one user configuration register set */ +#define S5K6AA_MAX_PRESETS 1 + +static const char * const s5k6aa_supply_names[] = { + "vdd_core", /* Digital core supply 1.5V (1.4V to 1.6V) */ + "vdda", /* Analog power supply 2.8V (2.6V to 3.0V) */ + "vdd_reg", /* Regulator input power 1.8V (1.7V to 1.9V) + or 2.8V (2.6V to 3.0) */ + "vddio", /* I/O supply 1.8V (1.65V to 1.95V) + or 2.8V (2.5V to 3.1V) */ +}; +#define S5K6AA_NUM_SUPPLIES ARRAY_SIZE(s5k6aa_supply_names) + +enum s5k6aa_gpio_id { + STBY, + RST, + GPIO_NUM, +}; + +struct s5k6aa_regval { + u16 addr; + u16 val; +}; + +struct s5k6aa_pixfmt { + enum v4l2_mbus_pixelcode code; + u32 colorspace; + /* REG_P_FMT(x) register value */ + u16 reg_p_fmt; +}; + +struct s5k6aa_preset { + /* output pixel format and resolution */ + struct v4l2_mbus_framefmt mbus_fmt; + u8 clk_id; + u8 index; +}; + +struct s5k6aa_ctrls { + struct v4l2_ctrl_handler handler; + /* Auto / manual white balance cluster */ + struct v4l2_ctrl *awb; + struct v4l2_ctrl *gain_red; + struct v4l2_ctrl *gain_blue; + struct v4l2_ctrl *gain_green; + /* Mirror cluster */ + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + /* Auto exposure / manual exposure and gain cluster */ + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; +}; + +struct s5k6aa_interval { + u16 reg_fr_time; + struct v4l2_fract interval; + /* Maximum rectangle for the interval */ + struct v4l2_frmsize_discrete size; +}; + +struct s5k6aa { + struct v4l2_subdev sd; + struct media_pad pad; + + enum v4l2_mbus_type bus_type; + u8 mipi_lanes; + + int (*s_power)(int enable); + struct regulator_bulk_data supplies[S5K6AA_NUM_SUPPLIES]; + struct s5k6aa_gpio gpio[GPIO_NUM]; + + /* external master clock frequency */ + unsigned long mclk_frequency; + /* ISP internal master clock frequency */ + u16 clk_fop; + /* output pixel clock frequency range */ + u16 pclk_fmin; + u16 pclk_fmax; + + unsigned int inv_hflip:1; + unsigned int inv_vflip:1; + + /* protects the struct members below */ + struct mutex lock; + + /* sensor matrix scan window */ + struct v4l2_rect ccd_rect; + + struct s5k6aa_ctrls ctrls; + struct s5k6aa_preset presets[S5K6AA_MAX_PRESETS]; + struct s5k6aa_preset *preset; + const struct s5k6aa_interval *fiv; + + unsigned int streaming:1; + unsigned int apply_cfg:1; + unsigned int apply_crop:1; + unsigned int power; +}; + +static struct s5k6aa_regval s5k6aa_analog_config[] = { + /* Analog settings */ + { 0x112a, 0x0000 }, { 0x1132, 0x0000 }, + { 0x113e, 0x0000 }, { 0x115c, 0x0000 }, + { 0x1164, 0x0000 }, { 0x1174, 0x0000 }, + { 0x1178, 0x0000 }, { 0x077a, 0x0000 }, + { 0x077c, 0x0000 }, { 0x077e, 0x0000 }, + { 0x0780, 0x0000 }, { 0x0782, 0x0000 }, + { 0x0784, 0x0000 }, { 0x0786, 0x0000 }, + { 0x0788, 0x0000 }, { 0x07a2, 0x0000 }, + { 0x07a4, 0x0000 }, { 0x07a6, 0x0000 }, + { 0x07a8, 0x0000 }, { 0x07b6, 0x0000 }, + { 0x07b8, 0x0002 }, { 0x07ba, 0x0004 }, + { 0x07bc, 0x0004 }, { 0x07be, 0x0005 }, + { 0x07c0, 0x0005 }, { S5K6AA_TERM, 0 }, +}; + +/* TODO: Add RGB888 and Bayer format */ +static const struct s5k6aa_pixfmt s5k6aa_formats[] = { + { V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG, 5 }, + /* range 16-240 */ + { V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_REC709, 6 }, + { V4L2_MBUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_JPEG, 0 }, +}; + +static const struct s5k6aa_interval s5k6aa_intervals[] = { + { 1000, {10000, 1000000}, {1280, 1024} }, /* 10 fps */ + { 666, {15000, 1000000}, {1280, 1024} }, /* 15 fps */ + { 500, {20000, 1000000}, {1280, 720} }, /* 20 fps */ + { 400, {25000, 1000000}, {640, 480} }, /* 25 fps */ + { 333, {33300, 1000000}, {640, 480} }, /* 30 fps */ +}; + +#define S5K6AA_INTERVAL_DEF_INDEX 1 /* 15 fps */ + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct s5k6aa, ctrls.handler)->sd; +} + +static inline struct s5k6aa *to_s5k6aa(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s5k6aa, sd); +} + +/* Set initial values for all preview presets */ +static void s5k6aa_presets_data_init(struct s5k6aa *s5k6aa) +{ + struct s5k6aa_preset *preset = &s5k6aa->presets[0]; + int i; + + for (i = 0; i < S5K6AA_MAX_PRESETS; i++) { + preset->mbus_fmt.width = S5K6AA_OUT_WIDTH_DEF; + preset->mbus_fmt.height = S5K6AA_OUT_HEIGHT_DEF; + preset->mbus_fmt.code = s5k6aa_formats[0].code; + preset->index = i; + preset->clk_id = 0; + preset++; + } + + s5k6aa->fiv = &s5k6aa_intervals[S5K6AA_INTERVAL_DEF_INDEX]; + s5k6aa->preset = &s5k6aa->presets[0]; +} + +static int s5k6aa_i2c_read(struct i2c_client *client, u16 addr, u16 *val) +{ + u8 wbuf[2] = {addr >> 8, addr & 0xFF}; + struct i2c_msg msg[2]; + u8 rbuf[2]; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 2; + msg[0].buf = wbuf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = rbuf; + + ret = i2c_transfer(client->adapter, msg, 2); + *val = be16_to_cpu(*((u16 *)rbuf)); + + v4l2_dbg(3, debug, client, "i2c_read: 0x%04X : 0x%04x\n", addr, *val); + + return ret == 2 ? 0 : ret; +} + +static int s5k6aa_i2c_write(struct i2c_client *client, u16 addr, u16 val) +{ + u8 buf[4] = {addr >> 8, addr & 0xFF, val >> 8, val & 0xFF}; + + int ret = i2c_master_send(client, buf, 4); + v4l2_dbg(3, debug, client, "i2c_write: 0x%04X : 0x%04x\n", addr, val); + + return ret == 4 ? 0 : ret; +} + +/* The command register write, assumes Command_Wr_addH = 0x7000. */ +static int s5k6aa_write(struct i2c_client *c, u16 addr, u16 val) +{ + int ret = s5k6aa_i2c_write(c, REG_CMDWR_ADDRL, addr); + if (ret) + return ret; + return s5k6aa_i2c_write(c, REG_CMDBUF0_ADDR, val); +} + +/* The command register read, assumes Command_Rd_addH = 0x7000. */ +static int s5k6aa_read(struct i2c_client *client, u16 addr, u16 *val) +{ + int ret = s5k6aa_i2c_write(client, REG_CMDRD_ADDRL, addr); + if (ret) + return ret; + return s5k6aa_i2c_read(client, REG_CMDBUF0_ADDR, val); +} + +static int s5k6aa_write_array(struct v4l2_subdev *sd, + const struct s5k6aa_regval *msg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u16 addr_incr = 0; + int ret = 0; + + while (msg->addr != S5K6AA_TERM) { + if (addr_incr != 2) + ret = s5k6aa_i2c_write(client, REG_CMDWR_ADDRL, + msg->addr); + if (ret) + break; + ret = s5k6aa_i2c_write(client, REG_CMDBUF0_ADDR, msg->val); + if (ret) + break; + /* Assume that msg->addr is always less than 0xfffc */ + addr_incr = (msg + 1)->addr - msg->addr; + msg++; + } + + return ret; +} + +/* Configure the AHB high address bytes for GTG registers access */ +static int s5k6aa_set_ahb_address(struct i2c_client *client) +{ + int ret = s5k6aa_i2c_write(client, AHB_MSB_ADDR_PTR, GEN_REG_OFFSH); + if (ret) + return ret; + ret = s5k6aa_i2c_write(client, REG_CMDRD_ADDRH, HOST_SWIF_OFFSH); + if (ret) + return ret; + return s5k6aa_i2c_write(client, REG_CMDWR_ADDRH, HOST_SWIF_OFFSH); +} + +/** + * s5k6aa_configure_pixel_clock - apply ISP main clock/PLL configuration + * + * Configure the internal ISP PLL for the required output frequency. + * Locking: called with s5k6aa.lock mutex held. + */ +static int s5k6aa_configure_pixel_clocks(struct s5k6aa *s5k6aa) +{ + struct i2c_client *c = v4l2_get_subdevdata(&s5k6aa->sd); + unsigned long fmclk = s5k6aa->mclk_frequency / 1000; + u16 status; + int ret; + + if (WARN(fmclk < MIN_MCLK_FREQ_KHZ || fmclk > MAX_MCLK_FREQ_KHZ, + "Invalid clock frequency: %ld\n", fmclk)) + return -EINVAL; + + s5k6aa->pclk_fmin = PCLK_FREQ_MIN; + s5k6aa->pclk_fmax = PCLK_FREQ_MAX; + s5k6aa->clk_fop = SYS_PLL_OUT_FREQ; + + /* External input clock frequency in kHz */ + ret = s5k6aa_write(c, REG_I_INCLK_FREQ_H, fmclk >> 16); + if (!ret) + ret = s5k6aa_write(c, REG_I_INCLK_FREQ_L, fmclk & 0xFFFF); + if (!ret) + ret = s5k6aa_write(c, REG_I_USE_NPVI_CLOCKS, 1); + /* Internal PLL frequency */ + if (!ret) + ret = s5k6aa_write(c, REG_I_OPCLK_4KHZ(0), s5k6aa->clk_fop); + if (!ret) + ret = s5k6aa_write(c, REG_I_MIN_OUTRATE_4KHZ(0), + s5k6aa->pclk_fmin); + if (!ret) + ret = s5k6aa_write(c, REG_I_MAX_OUTRATE_4KHZ(0), + s5k6aa->pclk_fmax); + if (!ret) + ret = s5k6aa_write(c, REG_I_INIT_PARAMS_UPDATED, 1); + if (!ret) + ret = s5k6aa_read(c, REG_I_ERROR_INFO, &status); + + return ret ? ret : (status ? -EINVAL : 0); +} + +/* Set horizontal and vertical image flipping */ +static int s5k6aa_set_mirror(struct s5k6aa *s5k6aa, int horiz_flip) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + int index = s5k6aa->preset->index; + + unsigned int vflip = s5k6aa->ctrls.vflip->val ^ s5k6aa->inv_vflip; + unsigned int flip = (horiz_flip ^ s5k6aa->inv_hflip) | (vflip << 1); + + return s5k6aa_write(client, REG_P_PREV_MIRROR(index), flip); +} + +/* Configure auto/manual white balance and R/G/B gains */ +static int s5k6aa_set_awb(struct s5k6aa *s5k6aa, int awb) +{ + struct i2c_client *c = v4l2_get_subdevdata(&s5k6aa->sd); + struct s5k6aa_ctrls *ctrls = &s5k6aa->ctrls; + u16 reg; + + int ret = s5k6aa_read(c, REG_DBG_AUTOALG_EN, ®); + + if (!ret && !awb) { + ret = s5k6aa_write(c, REG_SF_RGAIN, ctrls->gain_red->val); + if (!ret) + ret = s5k6aa_write(c, REG_SF_RGAIN_CHG, 1); + if (ret) + return ret; + + ret = s5k6aa_write(c, REG_SF_GGAIN, ctrls->gain_green->val); + if (!ret) + ret = s5k6aa_write(c, REG_SF_GGAIN_CHG, 1); + if (ret) + return ret; + + ret = s5k6aa_write(c, REG_SF_BGAIN, ctrls->gain_blue->val); + if (!ret) + ret = s5k6aa_write(c, REG_SF_BGAIN_CHG, 1); + } + if (!ret) { + reg = awb ? reg | AALG_WB_EN_MASK : reg & ~AALG_WB_EN_MASK; + ret = s5k6aa_write(c, REG_DBG_AUTOALG_EN, reg); + } + + return ret; +} + +/* Program FW with exposure time, 'exposure' in us units */ +static int s5k6aa_set_user_exposure(struct i2c_client *client, int exposure) +{ + unsigned int time = exposure / 10; + + int ret = s5k6aa_write(client, REG_SF_USR_EXPOSURE_L, time & 0xffff); + if (!ret) + ret = s5k6aa_write(client, REG_SF_USR_EXPOSURE_H, time >> 16); + if (ret) + return ret; + return s5k6aa_write(client, REG_SF_USR_EXPOSURE_CHG, 1); +} + +static int s5k6aa_set_user_gain(struct i2c_client *client, int gain) +{ + int ret = s5k6aa_write(client, REG_SF_USR_TOT_GAIN, gain); + if (ret) + return ret; + return s5k6aa_write(client, REG_SF_USR_TOT_GAIN_CHG, 1); +} + +/* Set auto/manual exposure and total gain */ +static int s5k6aa_set_auto_exposure(struct s5k6aa *s5k6aa, int value) +{ + struct i2c_client *c = v4l2_get_subdevdata(&s5k6aa->sd); + unsigned int exp_time = s5k6aa->ctrls.exposure->val; + u16 auto_alg; + + int ret = s5k6aa_read(c, REG_DBG_AUTOALG_EN, &auto_alg); + if (ret) + return ret; + + v4l2_dbg(1, debug, c, "man_exp: %d, auto_exp: %d, a_alg: 0x%x\n", + exp_time, value, auto_alg); + + if (value == V4L2_EXPOSURE_AUTO) { + auto_alg |= AALG_AE_EN_MASK | AALG_DIVLEI_EN_MASK; + } else { + ret = s5k6aa_set_user_exposure(c, exp_time); + if (ret) + return ret; + ret = s5k6aa_set_user_gain(c, s5k6aa->ctrls.gain->val); + if (ret) + return ret; + auto_alg &= ~(AALG_AE_EN_MASK | AALG_DIVLEI_EN_MASK); + } + + return s5k6aa_write(c, REG_DBG_AUTOALG_EN, auto_alg); +} + +static int s5k6aa_set_anti_flicker(struct s5k6aa *s5k6aa, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + u16 auto_alg; + int ret; + + ret = s5k6aa_read(client, REG_DBG_AUTOALG_EN, &auto_alg); + if (ret) + return ret; + + if (value == V4L2_CID_POWER_LINE_FREQUENCY_AUTO) { + auto_alg |= AALG_FLICKER_EN_MASK; + } else { + auto_alg &= ~AALG_FLICKER_EN_MASK; + /* The V4L2_CID_LINE_FREQUENCY control values match + * the register values */ + ret = s5k6aa_write(client, REG_SF_FLICKER_QUANT, value); + if (ret) + return ret; + ret = s5k6aa_write(client, REG_SF_FLICKER_QUANT_CHG, 1); + if (ret) + return ret; + } + + return s5k6aa_write(client, REG_DBG_AUTOALG_EN, auto_alg); +} + +static int s5k6aa_set_colorfx(struct s5k6aa *s5k6aa, int val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + static const struct v4l2_control colorfx[] = { + { V4L2_COLORFX_NONE, 0 }, + { V4L2_COLORFX_BW, 1 }, + { V4L2_COLORFX_NEGATIVE, 2 }, + { V4L2_COLORFX_SEPIA, 3 }, + { V4L2_COLORFX_SKY_BLUE, 4 }, + { V4L2_COLORFX_SKETCH, 5 }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(colorfx); i++) { + if (colorfx[i].id == val) + return s5k6aa_write(client, REG_G_SPEC_EFFECTS, + colorfx[i].value); + } + return -EINVAL; +} + +static int s5k6aa_preview_config_status(struct i2c_client *client) +{ + u16 error = 0; + int ret = s5k6aa_read(client, REG_G_PREV_CFG_ERROR, &error); + + v4l2_dbg(1, debug, client, "error: 0x%x (%d)\n", error, ret); + return ret ? ret : (error ? -EINVAL : 0); +} + +static int s5k6aa_get_pixfmt_index(struct s5k6aa *s5k6aa, + struct v4l2_mbus_framefmt *mf) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s5k6aa_formats); i++) + if (mf->colorspace == s5k6aa_formats[i].colorspace && + mf->code == s5k6aa_formats[i].code) + return i; + return 0; +} + +static int s5k6aa_set_output_framefmt(struct s5k6aa *s5k6aa, + struct s5k6aa_preset *preset) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + int fmt_index = s5k6aa_get_pixfmt_index(s5k6aa, &preset->mbus_fmt); + int ret; + + ret = s5k6aa_write(client, REG_P_OUT_WIDTH(preset->index), + preset->mbus_fmt.width); + if (!ret) + ret = s5k6aa_write(client, REG_P_OUT_HEIGHT(preset->index), + preset->mbus_fmt.height); + if (!ret) + ret = s5k6aa_write(client, REG_P_FMT(preset->index), + s5k6aa_formats[fmt_index].reg_p_fmt); + return ret; +} + +static int s5k6aa_set_input_params(struct s5k6aa *s5k6aa) +{ + struct i2c_client *c = v4l2_get_subdevdata(&s5k6aa->sd); + struct v4l2_rect *r = &s5k6aa->ccd_rect; + int ret; + + ret = s5k6aa_write(c, REG_G_PREVZOOM_IN_WIDTH, r->width); + if (!ret) + ret = s5k6aa_write(c, REG_G_PREVZOOM_IN_HEIGHT, r->height); + if (!ret) + ret = s5k6aa_write(c, REG_G_PREVZOOM_IN_XOFFS, r->left); + if (!ret) + ret = s5k6aa_write(c, REG_G_PREVZOOM_IN_YOFFS, r->top); + if (!ret) + ret = s5k6aa_write(c, REG_G_INPUTS_CHANGE_REQ, 1); + if (!ret) + s5k6aa->apply_crop = 0; + + return ret; +} + +/** + * s5k6aa_configure_video_bus - configure the video output interface + * @bus_type: video bus type: parallel or MIPI-CSI + * @nlanes: number of MIPI lanes to be used (MIPI-CSI only) + * + * Note: Only parallel bus operation has been tested. + */ +static int s5k6aa_configure_video_bus(struct s5k6aa *s5k6aa, + enum v4l2_mbus_type bus_type, int nlanes) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + u16 cfg = 0; + int ret; + + /* + * TODO: The sensor is supposed to support BT.601 and BT.656 + * but there is nothing indicating how to switch between both + * in the datasheet. For now default BT.601 interface is assumed. + */ + if (bus_type == V4L2_MBUS_CSI2) + cfg = nlanes; + else if (bus_type != V4L2_MBUS_PARALLEL) + return -EINVAL; + + ret = s5k6aa_write(client, REG_OIF_EN_MIPI_LANES, cfg); + if (ret) + return ret; + return s5k6aa_write(client, REG_OIF_CFG_CHG, 1); +} + +/* This function should be called when switching to new user configuration set*/ +static int s5k6aa_new_config_sync(struct i2c_client *client, int timeout, + int cid) +{ + unsigned long end = jiffies + msecs_to_jiffies(timeout); + u16 reg = 1; + int ret; + + ret = s5k6aa_write(client, REG_G_ACTIVE_PREV_CFG, cid); + if (!ret) + ret = s5k6aa_write(client, REG_G_PREV_CFG_CHG, 1); + if (!ret) + ret = s5k6aa_write(client, REG_G_NEW_CFG_SYNC, 1); + if (timeout == 0) + return ret; + + while (ret >= 0 && time_is_after_jiffies(end)) { + ret = s5k6aa_read(client, REG_G_NEW_CFG_SYNC, ®); + if (!reg) + return 0; + usleep_range(1000, 5000); + } + return ret ? ret : -ETIMEDOUT; +} + +/** + * s5k6aa_set_prev_config - write user preview register set + * + * Configure output resolution and color fromat, pixel clock + * frequency range, device frame rate type and frame period range. + */ +static int s5k6aa_set_prev_config(struct s5k6aa *s5k6aa, + struct s5k6aa_preset *preset) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + int idx = preset->index; + u16 frame_rate_q; + int ret; + + if (s5k6aa->fiv->reg_fr_time >= S5K6AA_MAX_HIGHRES_FR_TIME) + frame_rate_q = FR_RATE_Q_BEST_FRRATE; + else + frame_rate_q = FR_RATE_Q_BEST_QUALITY; + + ret = s5k6aa_set_output_framefmt(s5k6aa, preset); + if (!ret) + ret = s5k6aa_write(client, REG_P_MAX_OUT_RATE(idx), + s5k6aa->pclk_fmax); + if (!ret) + ret = s5k6aa_write(client, REG_P_MIN_OUT_RATE(idx), + s5k6aa->pclk_fmin); + if (!ret) + ret = s5k6aa_write(client, REG_P_CLK_INDEX(idx), + preset->clk_id); + if (!ret) + ret = s5k6aa_write(client, REG_P_FR_RATE_TYPE(idx), + FR_RATE_DYNAMIC); + if (!ret) + ret = s5k6aa_write(client, REG_P_FR_RATE_Q_TYPE(idx), + frame_rate_q); + if (!ret) + ret = s5k6aa_write(client, REG_P_MAX_FR_TIME(idx), + s5k6aa->fiv->reg_fr_time + 33); + if (!ret) + ret = s5k6aa_write(client, REG_P_MIN_FR_TIME(idx), + s5k6aa->fiv->reg_fr_time - 33); + if (!ret) + ret = s5k6aa_new_config_sync(client, 250, idx); + if (!ret) + ret = s5k6aa_preview_config_status(client); + if (!ret) + s5k6aa->apply_cfg = 0; + + v4l2_dbg(1, debug, client, "Frame interval: %d +/- 3.3ms. (%d)\n", + s5k6aa->fiv->reg_fr_time, ret); + return ret; +} + +/** + * s5k6aa_initialize_isp - basic ISP MCU initialization + * + * Configure AHB addresses for registers read/write; configure PLLs for + * required output pixel clock. The ISP power supply needs to be already + * enabled, with an optional H/W reset. + * Locking: called with s5k6aa.lock mutex held. + */ +static int s5k6aa_initialize_isp(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret; + + s5k6aa->apply_crop = 1; + s5k6aa->apply_cfg = 1; + msleep(100); + + ret = s5k6aa_set_ahb_address(client); + if (ret) + return ret; + ret = s5k6aa_configure_video_bus(s5k6aa, s5k6aa->bus_type, + s5k6aa->mipi_lanes); + if (ret) + return ret; + ret = s5k6aa_write_array(sd, s5k6aa_analog_config); + if (ret) + return ret; + msleep(20); + + return s5k6aa_configure_pixel_clocks(s5k6aa); +} + +static int s5k6aa_gpio_set_value(struct s5k6aa *priv, int id, u32 val) +{ + if (!gpio_is_valid(priv->gpio[id].gpio)) + return 0; + gpio_set_value(priv->gpio[id].gpio, !!val); + return 1; +} + +static int s5k6aa_gpio_assert(struct s5k6aa *priv, int id) +{ + return s5k6aa_gpio_set_value(priv, id, priv->gpio[id].level); +} + +static int s5k6aa_gpio_deassert(struct s5k6aa *priv, int id) +{ + return s5k6aa_gpio_set_value(priv, id, !priv->gpio[id].level); +} + +static int __s5k6aa_power_on(struct s5k6aa *s5k6aa) +{ + int ret; + + ret = regulator_bulk_enable(S5K6AA_NUM_SUPPLIES, s5k6aa->supplies); + if (ret) + return ret; + if (s5k6aa_gpio_deassert(s5k6aa, STBY)) + usleep_range(150, 200); + + if (s5k6aa->s_power) + ret = s5k6aa->s_power(1); + usleep_range(4000, 4000); + + if (s5k6aa_gpio_deassert(s5k6aa, RST)) + msleep(20); + + return ret; +} + +static int __s5k6aa_power_off(struct s5k6aa *s5k6aa) +{ + int ret; + + if (s5k6aa_gpio_assert(s5k6aa, RST)) + usleep_range(100, 150); + + if (s5k6aa->s_power) { + ret = s5k6aa->s_power(0); + if (ret) + return ret; + } + if (s5k6aa_gpio_assert(s5k6aa, STBY)) + usleep_range(50, 100); + s5k6aa->streaming = 0; + + return regulator_bulk_disable(S5K6AA_NUM_SUPPLIES, s5k6aa->supplies); +} + +/* + * V4L2 subdev core and video operations + */ +static int s5k6aa_set_power(struct v4l2_subdev *sd, int on) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret = 0; + + mutex_lock(&s5k6aa->lock); + + if (!on == s5k6aa->power) { + if (on) { + ret = __s5k6aa_power_on(s5k6aa); + if (!ret) + ret = s5k6aa_initialize_isp(sd); + } else { + ret = __s5k6aa_power_off(s5k6aa); + } + + if (!ret) + s5k6aa->power += on ? 1 : -1; + } + + mutex_unlock(&s5k6aa->lock); + + if (!on || ret || s5k6aa->power != 1) + return ret; + + return v4l2_ctrl_handler_setup(sd->ctrl_handler); +} + +static int __s5k6aa_stream(struct s5k6aa *s5k6aa, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + int ret = 0; + + ret = s5k6aa_write(client, REG_G_ENABLE_PREV, enable); + if (!ret) + ret = s5k6aa_write(client, REG_G_ENABLE_PREV_CHG, 1); + if (!ret) + s5k6aa->streaming = enable; + + return ret; +} + +static int s5k6aa_s_stream(struct v4l2_subdev *sd, int on) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret = 0; + + mutex_lock(&s5k6aa->lock); + + if (s5k6aa->streaming == !on) { + if (!ret && s5k6aa->apply_cfg) + ret = s5k6aa_set_prev_config(s5k6aa, s5k6aa->preset); + if (s5k6aa->apply_crop) + ret = s5k6aa_set_input_params(s5k6aa); + if (!ret) + ret = __s5k6aa_stream(s5k6aa, !!on); + } + mutex_unlock(&s5k6aa->lock); + + return ret; +} + +static int s5k6aa_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + + mutex_lock(&s5k6aa->lock); + fi->interval = s5k6aa->fiv->interval; + mutex_unlock(&s5k6aa->lock); + + return 0; +} + +static int __s5k6aa_set_frame_interval(struct s5k6aa *s5k6aa, + struct v4l2_subdev_frame_interval *fi) +{ + struct v4l2_mbus_framefmt *mbus_fmt = &s5k6aa->preset->mbus_fmt; + const struct s5k6aa_interval *fiv = &s5k6aa_intervals[0]; + unsigned int err, min_err = UINT_MAX; + unsigned int i, fr_time; + + if (fi->interval.denominator == 0) + return -EINVAL; + + fr_time = fi->interval.numerator * 10000 / fi->interval.denominator; + + for (i = 0; i < ARRAY_SIZE(s5k6aa_intervals); i++) { + const struct s5k6aa_interval *iv = &s5k6aa_intervals[i]; + + if (mbus_fmt->width > iv->size.width || + mbus_fmt->height > iv->size.height) + continue; + + err = abs(iv->reg_fr_time - fr_time); + if (err < min_err) { + fiv = iv; + min_err = err; + } + } + s5k6aa->fiv = fiv; + + v4l2_dbg(1, debug, &s5k6aa->sd, "Changed frame interval to %d us\n", + fiv->reg_fr_time * 100); + return 0; +} + +static int s5k6aa_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret; + + v4l2_dbg(1, debug, sd, "Setting %d/%d frame interval\n", + fi->interval.numerator, fi->interval.denominator); + + mutex_lock(&s5k6aa->lock); + ret = __s5k6aa_set_frame_interval(s5k6aa, fi); + s5k6aa->apply_cfg = 1; + + mutex_unlock(&s5k6aa->lock); + return ret; +} + +/* + * V4L2 subdev pad level and video operations + */ +static int s5k6aa_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + const struct s5k6aa_interval *fi; + int ret = 0; + + if (fie->index > ARRAY_SIZE(s5k6aa_intervals)) + return -EINVAL; + + v4l_bound_align_image(&fie->width, S5K6AA_WIN_WIDTH_MIN, + S5K6AA_WIN_WIDTH_MAX, 1, + &fie->height, S5K6AA_WIN_HEIGHT_MIN, + S5K6AA_WIN_HEIGHT_MAX, 1, 0); + + mutex_lock(&s5k6aa->lock); + fi = &s5k6aa_intervals[fie->index]; + if (fie->width > fi->size.width || fie->height > fi->size.height) + ret = -EINVAL; + else + fie->interval = fi->interval; + mutex_unlock(&s5k6aa->lock); + + return ret; +} + +static int s5k6aa_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(s5k6aa_formats)) + return -EINVAL; + + code->code = s5k6aa_formats[code->index].code; + return 0; +} + +static int s5k6aa_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + int i = ARRAY_SIZE(s5k6aa_formats); + + if (fse->index > 0) + return -EINVAL; + + while (--i) + if (fse->code == s5k6aa_formats[i].code) + break; + + fse->code = s5k6aa_formats[i].code; + fse->min_width = S5K6AA_WIN_WIDTH_MIN; + fse->max_width = S5K6AA_WIN_WIDTH_MAX; + fse->max_height = S5K6AA_WIN_HEIGHT_MIN; + fse->min_height = S5K6AA_WIN_HEIGHT_MAX; + + return 0; +} + +static struct v4l2_rect * +__s5k6aa_get_crop_rect(struct s5k6aa *s5k6aa, struct v4l2_subdev_fh *fh, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) + return &s5k6aa->ccd_rect; + + WARN_ON(which != V4L2_SUBDEV_FORMAT_TRY); + return v4l2_subdev_get_try_crop(fh, 0); +} + +static void s5k6aa_try_format(struct s5k6aa *s5k6aa, + struct v4l2_mbus_framefmt *mf) +{ + unsigned int index; + + v4l_bound_align_image(&mf->width, S5K6AA_WIN_WIDTH_MIN, + S5K6AA_WIN_WIDTH_MAX, 1, + &mf->height, S5K6AA_WIN_HEIGHT_MIN, + S5K6AA_WIN_HEIGHT_MAX, 1, 0); + + if (mf->colorspace != V4L2_COLORSPACE_JPEG && + mf->colorspace != V4L2_COLORSPACE_REC709) + mf->colorspace = V4L2_COLORSPACE_JPEG; + + index = s5k6aa_get_pixfmt_index(s5k6aa, mf); + + mf->colorspace = s5k6aa_formats[index].colorspace; + mf->code = s5k6aa_formats[index].code; + mf->field = V4L2_FIELD_NONE; +} + +static int s5k6aa_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + struct v4l2_mbus_framefmt *mf; + + memset(fmt->reserved, 0, sizeof(fmt->reserved)); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(fh, 0); + fmt->format = *mf; + return 0; + } + + mutex_lock(&s5k6aa->lock); + fmt->format = s5k6aa->preset->mbus_fmt; + mutex_unlock(&s5k6aa->lock); + + return 0; +} + +static int s5k6aa_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + struct s5k6aa_preset *preset = s5k6aa->preset; + struct v4l2_mbus_framefmt *mf; + struct v4l2_rect *crop; + int ret = 0; + + mutex_lock(&s5k6aa->lock); + s5k6aa_try_format(s5k6aa, &fmt->format); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(fh, fmt->pad); + crop = v4l2_subdev_get_try_crop(fh, 0); + } else { + if (s5k6aa->streaming) { + ret = -EBUSY; + } else { + mf = &preset->mbus_fmt; + crop = &s5k6aa->ccd_rect; + s5k6aa->apply_cfg = 1; + } + } + + if (ret == 0) { + struct v4l2_subdev_frame_interval fiv = { + .interval = {0, 1} + }; + + *mf = fmt->format; + /* + * Make sure the crop window is valid, i.e. its size is + * greater than the output window, as the ISP supports + * only down-scaling. + */ + crop->width = clamp_t(unsigned int, crop->width, mf->width, + S5K6AA_WIN_WIDTH_MAX); + crop->height = clamp_t(unsigned int, crop->height, mf->height, + S5K6AA_WIN_HEIGHT_MAX); + crop->left = clamp_t(unsigned int, crop->left, 0, + S5K6AA_WIN_WIDTH_MAX - crop->width); + crop->top = clamp_t(unsigned int, crop->top, 0, + S5K6AA_WIN_HEIGHT_MAX - crop->height); + + /* Reset to minimum possible frame interval */ + ret = __s5k6aa_set_frame_interval(s5k6aa, &fiv); + } + mutex_unlock(&s5k6aa->lock); + + return ret; +} + +static int s5k6aa_get_crop(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + struct v4l2_rect *rect; + + memset(crop->reserved, 0, sizeof(crop->reserved)); + + mutex_lock(&s5k6aa->lock); + rect = __s5k6aa_get_crop_rect(s5k6aa, fh, crop->which); + crop->rect = *rect; + mutex_unlock(&s5k6aa->lock); + + v4l2_dbg(1, debug, sd, "Current crop rectangle: (%d,%d)/%dx%d\n", + rect->left, rect->top, rect->width, rect->height); + + return 0; +} + +static int s5k6aa_set_crop(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + struct v4l2_mbus_framefmt *mf; + unsigned int max_x, max_y; + struct v4l2_rect *crop_r; + + mutex_lock(&s5k6aa->lock); + crop_r = __s5k6aa_get_crop_rect(s5k6aa, fh, crop->which); + + if (crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + mf = &s5k6aa->preset->mbus_fmt; + s5k6aa->apply_crop = 1; + } else { + mf = v4l2_subdev_get_try_format(fh, 0); + } + v4l_bound_align_image(&crop->rect.width, mf->width, + S5K6AA_WIN_WIDTH_MAX, 1, + &crop->rect.height, mf->height, + S5K6AA_WIN_HEIGHT_MAX, 1, 0); + + max_x = (S5K6AA_WIN_WIDTH_MAX - crop->rect.width) & ~1; + max_y = (S5K6AA_WIN_HEIGHT_MAX - crop->rect.height) & ~1; + + crop->rect.left = clamp_t(unsigned int, crop->rect.left, 0, max_x); + crop->rect.top = clamp_t(unsigned int, crop->rect.top, 0, max_y); + + *crop_r = crop->rect; + + mutex_unlock(&s5k6aa->lock); + + v4l2_dbg(1, debug, sd, "Set crop rectangle: (%d,%d)/%dx%d\n", + crop_r->left, crop_r->top, crop_r->width, crop_r->height); + + return 0; +} + +static const struct v4l2_subdev_pad_ops s5k6aa_pad_ops = { + .enum_mbus_code = s5k6aa_enum_mbus_code, + .enum_frame_size = s5k6aa_enum_frame_size, + .enum_frame_interval = s5k6aa_enum_frame_interval, + .get_fmt = s5k6aa_get_fmt, + .set_fmt = s5k6aa_set_fmt, + .get_crop = s5k6aa_get_crop, + .set_crop = s5k6aa_set_crop, +}; + +static const struct v4l2_subdev_video_ops s5k6aa_video_ops = { + .g_frame_interval = s5k6aa_g_frame_interval, + .s_frame_interval = s5k6aa_s_frame_interval, + .s_stream = s5k6aa_s_stream, +}; + +/* + * V4L2 subdev controls + */ + +static int s5k6aa_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int idx, err = 0; + + v4l2_dbg(1, debug, sd, "ctrl: 0x%x, value: %d\n", ctrl->id, ctrl->val); + + mutex_lock(&s5k6aa->lock); + /* + * If the device is not powered up by the host driver do + * not apply any controls to H/W at this time. Instead + * the controls will be restored right after power-up. + */ + if (s5k6aa->power == 0) + goto unlock; + idx = s5k6aa->preset->index; + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + err = s5k6aa_set_awb(s5k6aa, ctrl->val); + break; + + case V4L2_CID_BRIGHTNESS: + err = s5k6aa_write(client, REG_USER_BRIGHTNESS, ctrl->val); + break; + + case V4L2_CID_COLORFX: + err = s5k6aa_set_colorfx(s5k6aa, ctrl->val); + break; + + case V4L2_CID_CONTRAST: + err = s5k6aa_write(client, REG_USER_CONTRAST, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + err = s5k6aa_set_auto_exposure(s5k6aa, ctrl->val); + break; + + case V4L2_CID_HFLIP: + err = s5k6aa_set_mirror(s5k6aa, ctrl->val); + if (err) + break; + err = s5k6aa_write(client, REG_G_PREV_CFG_CHG, 1); + break; + + case V4L2_CID_POWER_LINE_FREQUENCY: + err = s5k6aa_set_anti_flicker(s5k6aa, ctrl->val); + break; + + case V4L2_CID_SATURATION: + err = s5k6aa_write(client, REG_USER_SATURATION, ctrl->val); + break; + + case V4L2_CID_SHARPNESS: + err = s5k6aa_write(client, REG_USER_SHARPBLUR, ctrl->val); + break; + + case V4L2_CID_WHITE_BALANCE_TEMPERATURE: + err = s5k6aa_write(client, REG_P_COLORTEMP(idx), ctrl->val); + if (err) + break; + err = s5k6aa_write(client, REG_G_PREV_CFG_CHG, 1); + break; + } +unlock: + mutex_unlock(&s5k6aa->lock); + return err; +} + +static const struct v4l2_ctrl_ops s5k6aa_ctrl_ops = { + .s_ctrl = s5k6aa_s_ctrl, +}; + +static int s5k6aa_log_status(struct v4l2_subdev *sd) +{ + v4l2_ctrl_handler_log_status(sd->ctrl_handler, sd->name); + return 0; +} + +#define V4L2_CID_RED_GAIN (V4L2_CTRL_CLASS_CAMERA | 0x1001) +#define V4L2_CID_GREEN_GAIN (V4L2_CTRL_CLASS_CAMERA | 0x1002) +#define V4L2_CID_BLUE_GAIN (V4L2_CTRL_CLASS_CAMERA | 0x1003) + +static const struct v4l2_ctrl_config s5k6aa_ctrls[] = { + { + .ops = &s5k6aa_ctrl_ops, + .id = V4L2_CID_RED_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Red", + .min = 0, + .max = 256, + .def = 127, + .step = 1, + }, { + .ops = &s5k6aa_ctrl_ops, + .id = V4L2_CID_GREEN_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Green", + .min = 0, + .max = 256, + .def = 127, + .step = 1, + }, { + .ops = &s5k6aa_ctrl_ops, + .id = V4L2_CID_BLUE_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain, Blue", + .min = 0, + .max = 256, + .def = 127, + .step = 1, + }, +}; + +static int s5k6aa_initialize_ctrls(struct s5k6aa *s5k6aa) +{ + const struct v4l2_ctrl_ops *ops = &s5k6aa_ctrl_ops; + struct s5k6aa_ctrls *ctrls = &s5k6aa->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + + int ret = v4l2_ctrl_handler_init(hdl, 16); + if (ret) + return ret; + /* Auto white balance cluster */ + ctrls->awb = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + ctrls->gain_red = v4l2_ctrl_new_custom(hdl, &s5k6aa_ctrls[0], NULL); + ctrls->gain_green = v4l2_ctrl_new_custom(hdl, &s5k6aa_ctrls[1], NULL); + ctrls->gain_blue = v4l2_ctrl_new_custom(hdl, &s5k6aa_ctrls[2], NULL); + v4l2_ctrl_auto_cluster(4, &ctrls->awb, 0, false); + + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_cluster(2, &ctrls->hflip); + + ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, V4L2_EXPOSURE_AUTO); + /* Exposure time: x 1 us */ + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + 0, 6000000U, 1, 100000U); + /* Total gain: 256 <=> 1x */ + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, + 0, 256, 1, 256); + v4l2_ctrl_auto_cluster(3, &ctrls->auto_exp, 0, false); + + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO); + + v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_COLORFX, + V4L2_COLORFX_SKY_BLUE, ~0x6f, V4L2_COLORFX_NONE); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_WHITE_BALANCE_TEMPERATURE, + 0, 256, 1, 0); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -127, 127, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SHARPNESS, -127, 127, 1, 0); + + if (hdl->error) { + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + s5k6aa->sd.ctrl_handler = hdl; + return 0; +} + +/* + * V4L2 subdev internal operations + */ +static int s5k6aa_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(fh, 0); + struct v4l2_rect *crop = v4l2_subdev_get_try_crop(fh, 0); + + format->colorspace = s5k6aa_formats[0].colorspace; + format->code = s5k6aa_formats[0].code; + format->width = S5K6AA_OUT_WIDTH_DEF; + format->height = S5K6AA_OUT_HEIGHT_DEF; + format->field = V4L2_FIELD_NONE; + + crop->width = S5K6AA_WIN_WIDTH_MAX; + crop->height = S5K6AA_WIN_HEIGHT_MAX; + crop->left = 0; + crop->top = 0; + + return 0; +} + +static int s5k6aa_check_fw_revision(struct s5k6aa *s5k6aa) +{ + struct i2c_client *client = v4l2_get_subdevdata(&s5k6aa->sd); + u16 api_ver = 0, fw_rev = 0; + + int ret = s5k6aa_set_ahb_address(client); + + if (!ret) + ret = s5k6aa_read(client, REG_FW_APIVER, &api_ver); + if (!ret) + ret = s5k6aa_read(client, REG_FW_REVISION, &fw_rev); + if (ret) { + v4l2_err(&s5k6aa->sd, "FW revision check failed!\n"); + return ret; + } + + v4l2_info(&s5k6aa->sd, "FW API ver.: 0x%X, FW rev.: 0x%X\n", + api_ver, fw_rev); + + return api_ver == S5K6AAFX_FW_APIVER ? 0 : -ENODEV; +} + +static int s5k6aa_registered(struct v4l2_subdev *sd) +{ + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + int ret; + + mutex_lock(&s5k6aa->lock); + ret = __s5k6aa_power_on(s5k6aa); + if (!ret) { + msleep(100); + ret = s5k6aa_check_fw_revision(s5k6aa); + __s5k6aa_power_off(s5k6aa); + } + mutex_unlock(&s5k6aa->lock); + + return ret; +} + +static const struct v4l2_subdev_internal_ops s5k6aa_subdev_internal_ops = { + .registered = s5k6aa_registered, + .open = s5k6aa_open, +}; + +static const struct v4l2_subdev_core_ops s5k6aa_core_ops = { + .s_power = s5k6aa_set_power, + .log_status = s5k6aa_log_status, +}; + +static const struct v4l2_subdev_ops s5k6aa_subdev_ops = { + .core = &s5k6aa_core_ops, + .pad = &s5k6aa_pad_ops, + .video = &s5k6aa_video_ops, +}; + +/* + * GPIO setup + */ +static int s5k6aa_configure_gpio(int nr, int val, const char *name) +{ + unsigned long flags = val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + int ret; + + if (!gpio_is_valid(nr)) + return 0; + ret = gpio_request_one(nr, flags, name); + if (!ret) + gpio_export(nr, 0); + return ret; +} + +static void s5k6aa_free_gpios(struct s5k6aa *s5k6aa) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s5k6aa->gpio); i++) { + if (!gpio_is_valid(s5k6aa->gpio[i].gpio)) + continue; + gpio_free(s5k6aa->gpio[i].gpio); + s5k6aa->gpio[i].gpio = -EINVAL; + } +} + +static int s5k6aa_configure_gpios(struct s5k6aa *s5k6aa, + const struct s5k6aa_platform_data *pdata) +{ + const struct s5k6aa_gpio *gpio = &pdata->gpio_stby; + int ret; + + s5k6aa->gpio[STBY].gpio = -EINVAL; + s5k6aa->gpio[RST].gpio = -EINVAL; + + ret = s5k6aa_configure_gpio(gpio->gpio, gpio->level, "S5K6AA_STBY"); + if (ret) { + s5k6aa_free_gpios(s5k6aa); + return ret; + } + s5k6aa->gpio[STBY] = *gpio; + if (gpio_is_valid(gpio->gpio)) + gpio_set_value(gpio->gpio, 0); + + gpio = &pdata->gpio_reset; + ret = s5k6aa_configure_gpio(gpio->gpio, gpio->level, "S5K6AA_RST"); + if (ret) { + s5k6aa_free_gpios(s5k6aa); + return ret; + } + s5k6aa->gpio[RST] = *gpio; + if (gpio_is_valid(gpio->gpio)) + gpio_set_value(gpio->gpio, 0); + + return 0; +} + +static int s5k6aa_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct s5k6aa_platform_data *pdata = client->dev.platform_data; + struct v4l2_subdev *sd; + struct s5k6aa *s5k6aa; + int i, ret; + + if (pdata == NULL) { + dev_err(&client->dev, "Platform data not specified\n"); + return -EINVAL; + } + + if (pdata->mclk_frequency == 0) { + dev_err(&client->dev, "MCLK frequency not specified\n"); + return -EINVAL; + } + + s5k6aa = devm_kzalloc(&client->dev, sizeof(*s5k6aa), GFP_KERNEL); + if (!s5k6aa) + return -ENOMEM; + + mutex_init(&s5k6aa->lock); + + s5k6aa->mclk_frequency = pdata->mclk_frequency; + s5k6aa->bus_type = pdata->bus_type; + s5k6aa->mipi_lanes = pdata->nlanes; + s5k6aa->s_power = pdata->set_power; + s5k6aa->inv_hflip = pdata->horiz_flip; + s5k6aa->inv_vflip = pdata->vert_flip; + + sd = &s5k6aa->sd; + v4l2_i2c_subdev_init(sd, client, &s5k6aa_subdev_ops); + strlcpy(sd->name, DRIVER_NAME, sizeof(sd->name)); + + sd->internal_ops = &s5k6aa_subdev_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + s5k6aa->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; + ret = media_entity_init(&sd->entity, 1, &s5k6aa->pad, 0); + if (ret) + return ret; + + ret = s5k6aa_configure_gpios(s5k6aa, pdata); + if (ret) + goto out_err2; + + for (i = 0; i < S5K6AA_NUM_SUPPLIES; i++) + s5k6aa->supplies[i].supply = s5k6aa_supply_names[i]; + + ret = regulator_bulk_get(&client->dev, S5K6AA_NUM_SUPPLIES, + s5k6aa->supplies); + if (ret) { + dev_err(&client->dev, "Failed to get regulators\n"); + goto out_err3; + } + + ret = s5k6aa_initialize_ctrls(s5k6aa); + if (ret) + goto out_err4; + + s5k6aa_presets_data_init(s5k6aa); + + s5k6aa->ccd_rect.width = S5K6AA_WIN_WIDTH_MAX; + s5k6aa->ccd_rect.height = S5K6AA_WIN_HEIGHT_MAX; + s5k6aa->ccd_rect.left = 0; + s5k6aa->ccd_rect.top = 0; + + return 0; + +out_err4: + regulator_bulk_free(S5K6AA_NUM_SUPPLIES, s5k6aa->supplies); +out_err3: + s5k6aa_free_gpios(s5k6aa); +out_err2: + media_entity_cleanup(&s5k6aa->sd.entity); + return ret; +} + +static int s5k6aa_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct s5k6aa *s5k6aa = to_s5k6aa(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + media_entity_cleanup(&sd->entity); + regulator_bulk_free(S5K6AA_NUM_SUPPLIES, s5k6aa->supplies); + s5k6aa_free_gpios(s5k6aa); + + return 0; +} + +static const struct i2c_device_id s5k6aa_id[] = { + { DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, s5k6aa_id); + + +static struct i2c_driver s5k6aa_i2c_driver = { + .driver = { + .name = DRIVER_NAME + }, + .probe = s5k6aa_probe, + .remove = s5k6aa_remove, + .id_table = s5k6aa_id, +}; + +module_i2c_driver(s5k6aa_i2c_driver); + +MODULE_DESCRIPTION("Samsung S5K6AA(FX) SXGA camera driver"); +MODULE_AUTHOR("Sylwester Nawrocki "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/saa6588.c b/drivers/media/i2c/saa6588.c similarity index 100% rename from drivers/media/video/saa6588.c rename to drivers/media/i2c/saa6588.c diff --git a/drivers/media/video/saa7110.c b/drivers/media/i2c/saa7110.c similarity index 100% rename from drivers/media/video/saa7110.c rename to drivers/media/i2c/saa7110.c diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c new file mode 100644 index 000000000000..6b6788cd08f6 --- /dev/null +++ b/drivers/media/i2c/saa7115.c @@ -0,0 +1,1728 @@ +/* saa711x - Philips SAA711x video decoder driver + * This driver can work with saa7111, saa7111a, saa7113, saa7114, + * saa7115 and saa7118. + * + * Based on saa7114 driver by Maxim Yevtyushkin, which is based on + * the saa7111 driver by Dave Perks. + * + * Copyright (C) 1998 Dave Perks + * Copyright (C) 2002 Maxim Yevtyushkin + * + * Slight changes for video timing and attachment output by + * Wolfgang Scherr + * + * Moved over to the linux >= 2.4.x i2c protocol (1/1/2003) + * by Ronald Bultje + * + * Added saa7115 support by Kevin Thayer + * (2/17/2003) + * + * VBI support (2004) and cleanups (2005) by Hans Verkuil + * + * Copyright (c) 2005-2006 Mauro Carvalho Chehab + * SAA7111, SAA7113 and SAA7118 support + * + * 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. + */ + +#include "saa711x_regs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VRES_60HZ (480+16) + +MODULE_DESCRIPTION("Philips SAA7111/SAA7113/SAA7114/SAA7115/SAA7118 video decoder driver"); +MODULE_AUTHOR( "Maxim Yevtyushkin, Kevin Thayer, Chris Kennedy, " + "Hans Verkuil, Mauro Carvalho Chehab"); +MODULE_LICENSE("GPL"); + +static bool debug; +module_param(debug, bool, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +struct saa711x_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + + struct { + /* chroma gain control cluster */ + struct v4l2_ctrl *agc; + struct v4l2_ctrl *gain; + }; + + v4l2_std_id std; + int input; + int output; + int enable; + int radio; + int width; + int height; + u32 ident; + u32 audclk_freq; + u32 crystal_freq; + u8 ucgc; + u8 cgcdiv; + u8 apll; +}; + +static inline struct saa711x_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa711x_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct saa711x_state, hdl)->sd; +} + +/* ----------------------------------------------------------------------- */ + +static inline int saa711x_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* Sanity routine to check if a register is present */ +static int saa711x_has_reg(const int id, const u8 reg) +{ + if (id == V4L2_IDENT_SAA7111) + return reg < 0x20 && reg != 0x01 && reg != 0x0f && + (reg < 0x13 || reg > 0x19) && reg != 0x1d && reg != 0x1e; + if (id == V4L2_IDENT_SAA7111A) + return reg < 0x20 && reg != 0x01 && reg != 0x0f && + reg != 0x14 && reg != 0x18 && reg != 0x19 && + reg != 0x1d && reg != 0x1e; + + /* common for saa7113/4/5/8 */ + if (unlikely((reg >= 0x3b && reg <= 0x3f) || reg == 0x5c || reg == 0x5f || + reg == 0xa3 || reg == 0xa7 || reg == 0xab || reg == 0xaf || (reg >= 0xb5 && reg <= 0xb7) || + reg == 0xd3 || reg == 0xd7 || reg == 0xdb || reg == 0xdf || (reg >= 0xe5 && reg <= 0xe7) || + reg == 0x82 || (reg >= 0x89 && reg <= 0x8e))) + return 0; + + switch (id) { + case V4L2_IDENT_SAA7113: + return reg != 0x14 && (reg < 0x18 || reg > 0x1e) && (reg < 0x20 || reg > 0x3f) && + reg != 0x5d && reg < 0x63; + case V4L2_IDENT_SAA7114: + return (reg < 0x1a || reg > 0x1e) && (reg < 0x20 || reg > 0x2f) && + (reg < 0x63 || reg > 0x7f) && reg != 0x33 && reg != 0x37 && + reg != 0x81 && reg < 0xf0; + case V4L2_IDENT_SAA7115: + return (reg < 0x20 || reg > 0x2f) && reg != 0x65 && (reg < 0xfc || reg > 0xfe); + case V4L2_IDENT_SAA7118: + return (reg < 0x1a || reg > 0x1d) && (reg < 0x20 || reg > 0x22) && + (reg < 0x26 || reg > 0x28) && reg != 0x33 && reg != 0x37 && + (reg < 0x63 || reg > 0x7f) && reg != 0x81 && reg < 0xf0; + } + return 1; +} + +static int saa711x_writeregs(struct v4l2_subdev *sd, const unsigned char *regs) +{ + struct saa711x_state *state = to_state(sd); + unsigned char reg, data; + + while (*regs != 0x00) { + reg = *(regs++); + data = *(regs++); + + /* According with datasheets, reserved regs should be + filled with 0 - seems better not to touch on they */ + if (saa711x_has_reg(state->ident, reg)) { + if (saa711x_write(sd, reg, data) < 0) + return -1; + } else { + v4l2_dbg(1, debug, sd, "tried to access reserved reg 0x%02x\n", reg); + } + } + return 0; +} + +static inline int saa711x_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +/* SAA7111 initialization table */ +static const unsigned char saa7111_init[] = { + R_01_INC_DELAY, 0x00, /* reserved */ + + /*front end */ + R_02_INPUT_CNTL_1, 0xd0, /* FUSE=3, GUDL=2, MODE=0 */ + R_03_INPUT_CNTL_2, 0x23, /* HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, + * GAFIX=0, GAI1=256, GAI2=256 */ + R_04_INPUT_CNTL_3, 0x00, /* GAI1=256 */ + R_05_INPUT_CNTL_4, 0x00, /* GAI2=256 */ + + /* decoder */ + R_06_H_SYNC_START, 0xf3, /* HSB at 13(50Hz) / 17(60Hz) + * pixels after end of last line */ + R_07_H_SYNC_STOP, 0xe8, /* HSS seems to be needed to + * work with NTSC, too */ + R_08_SYNC_CNTL, 0xc8, /* AUFD=1, FSEL=1, EXFIL=0, + * VTRC=1, HPLL=0, VNOI=0 */ + R_09_LUMA_CNTL, 0x01, /* BYPS=0, PREF=0, BPSS=0, + * VBLB=0, UPTCV=0, APER=1 */ + R_0A_LUMA_BRIGHT_CNTL, 0x80, + R_0B_LUMA_CONTRAST_CNTL, 0x47, /* 0b - CONT=1.109 */ + R_0C_CHROMA_SAT_CNTL, 0x40, + R_0D_CHROMA_HUE_CNTL, 0x00, + R_0E_CHROMA_CNTL_1, 0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, + * FCTC=0, CHBW=1 */ + R_0F_CHROMA_GAIN_CNTL, 0x00, /* reserved */ + R_10_CHROMA_CNTL_2, 0x48, /* 10 - OFTS=1, HDEL=0, VRLN=1, YDEL=0 */ + R_11_MODE_DELAY_CNTL, 0x1c, /* 11 - GPSW=0, CM99=0, FECO=0, COMPO=1, + * OEYC=1, OEHV=1, VIPB=0, COLO=0 */ + R_12_RT_SIGNAL_CNTL, 0x00, /* 12 - output control 2 */ + R_13_RT_X_PORT_OUT_CNTL, 0x00, /* 13 - output control 3 */ + R_14_ANAL_ADC_COMPAT_CNTL, 0x00, + R_15_VGATE_START_FID_CHG, 0x00, + R_16_VGATE_STOP, 0x00, + R_17_MISC_VGATE_CONF_AND_MSB, 0x00, + + 0x00, 0x00 +}; + +/* SAA7113 init codes */ +static const unsigned char saa7113_init[] = { + R_01_INC_DELAY, 0x08, + R_02_INPUT_CNTL_1, 0xc2, + R_03_INPUT_CNTL_2, 0x30, + R_04_INPUT_CNTL_3, 0x00, + R_05_INPUT_CNTL_4, 0x00, + R_06_H_SYNC_START, 0x89, + R_07_H_SYNC_STOP, 0x0d, + R_08_SYNC_CNTL, 0x88, + R_09_LUMA_CNTL, 0x01, + R_0A_LUMA_BRIGHT_CNTL, 0x80, + R_0B_LUMA_CONTRAST_CNTL, 0x47, + R_0C_CHROMA_SAT_CNTL, 0x40, + R_0D_CHROMA_HUE_CNTL, 0x00, + R_0E_CHROMA_CNTL_1, 0x01, + R_0F_CHROMA_GAIN_CNTL, 0x2a, + R_10_CHROMA_CNTL_2, 0x08, + R_11_MODE_DELAY_CNTL, 0x0c, + R_12_RT_SIGNAL_CNTL, 0x07, + R_13_RT_X_PORT_OUT_CNTL, 0x00, + R_14_ANAL_ADC_COMPAT_CNTL, 0x00, + R_15_VGATE_START_FID_CHG, 0x00, + R_16_VGATE_STOP, 0x00, + R_17_MISC_VGATE_CONF_AND_MSB, 0x00, + + 0x00, 0x00 +}; + +/* If a value differs from the Hauppauge driver values, then the comment starts with + 'was 0xXX' to denote the Hauppauge value. Otherwise the value is identical to what the + Hauppauge driver sets. */ + +/* SAA7114 and SAA7115 initialization table */ +static const unsigned char saa7115_init_auto_input[] = { + /* Front-End Part */ + R_01_INC_DELAY, 0x48, /* white peak control disabled */ + R_03_INPUT_CNTL_2, 0x20, /* was 0x30. 0x20: long vertical blanking */ + R_04_INPUT_CNTL_3, 0x90, /* analog gain set to 0 */ + R_05_INPUT_CNTL_4, 0x90, /* analog gain set to 0 */ + /* Decoder Part */ + R_06_H_SYNC_START, 0xeb, /* horiz sync begin = -21 */ + R_07_H_SYNC_STOP, 0xe0, /* horiz sync stop = -17 */ + R_09_LUMA_CNTL, 0x53, /* 0x53, was 0x56 for 60hz. luminance control */ + R_0A_LUMA_BRIGHT_CNTL, 0x80, /* was 0x88. decoder brightness, 0x80 is itu standard */ + R_0B_LUMA_CONTRAST_CNTL, 0x44, /* was 0x48. decoder contrast, 0x44 is itu standard */ + R_0C_CHROMA_SAT_CNTL, 0x40, /* was 0x47. decoder saturation, 0x40 is itu standard */ + R_0D_CHROMA_HUE_CNTL, 0x00, + R_0F_CHROMA_GAIN_CNTL, 0x00, /* use automatic gain */ + R_10_CHROMA_CNTL_2, 0x06, /* chroma: active adaptive combfilter */ + R_11_MODE_DELAY_CNTL, 0x00, + R_12_RT_SIGNAL_CNTL, 0x9d, /* RTS0 output control: VGATE */ + R_13_RT_X_PORT_OUT_CNTL, 0x80, /* ITU656 standard mode, RTCO output enable RTCE */ + R_14_ANAL_ADC_COMPAT_CNTL, 0x00, + R_18_RAW_DATA_GAIN_CNTL, 0x40, /* gain 0x00 = nominal */ + R_19_RAW_DATA_OFF_CNTL, 0x80, + R_1A_COLOR_KILL_LVL_CNTL, 0x77, /* recommended value */ + R_1B_MISC_TVVCRDET, 0x42, /* recommended value */ + R_1C_ENHAN_COMB_CTRL1, 0xa9, /* recommended value */ + R_1D_ENHAN_COMB_CTRL2, 0x01, /* recommended value */ + + + R_80_GLOBAL_CNTL_1, 0x0, /* No tasks enabled at init */ + + /* Power Device Control */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset device */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, /* set device programmed, all in operational mode */ + 0x00, 0x00 +}; + +/* Used to reset saa7113, saa7114 and saa7115 */ +static const unsigned char saa7115_cfg_reset_scaler[] = { + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x00, /* disable I-port output */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, /* activate scaler */ + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x01, /* enable I-port output */ + 0x00, 0x00 +}; + +/* ============== SAA7715 VIDEO templates ============= */ + +static const unsigned char saa7115_cfg_60hz_video[] = { + R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + + R_15_VGATE_START_FID_CHG, 0x03, + R_16_VGATE_STOP, 0x11, + R_17_MISC_VGATE_CONF_AND_MSB, 0x9c, + + R_08_SYNC_CNTL, 0x68, /* 0xBO: auto detection, 0x68 = NTSC */ + R_0E_CHROMA_CNTL_1, 0x07, /* video autodetection is on */ + + R_5A_V_OFF_FOR_SLICER, 0x06, /* standard 60hz value for ITU656 line counting */ + + /* Task A */ + R_90_A_TASK_HANDLING_CNTL, 0x80, + R_91_A_X_PORT_FORMATS_AND_CONF, 0x48, + R_92_A_X_PORT_INPUT_REFERENCE_SIGNAL, 0x40, + R_93_A_I_PORT_OUTPUT_FORMATS_AND_CONF, 0x84, + + /* hoffset low (input), 0x0002 is minimum */ + R_94_A_HORIZ_INPUT_WINDOW_START, 0x01, + R_95_A_HORIZ_INPUT_WINDOW_START_MSB, 0x00, + + /* hsize low (input), 0x02d0 = 720 */ + R_96_A_HORIZ_INPUT_WINDOW_LENGTH, 0xd0, + R_97_A_HORIZ_INPUT_WINDOW_LENGTH_MSB, 0x02, + + R_98_A_VERT_INPUT_WINDOW_START, 0x05, + R_99_A_VERT_INPUT_WINDOW_START_MSB, 0x00, + + R_9A_A_VERT_INPUT_WINDOW_LENGTH, 0x0c, + R_9B_A_VERT_INPUT_WINDOW_LENGTH_MSB, 0x00, + + R_9C_A_HORIZ_OUTPUT_WINDOW_LENGTH, 0xa0, + R_9D_A_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, 0x05, + + R_9E_A_VERT_OUTPUT_WINDOW_LENGTH, 0x0c, + R_9F_A_VERT_OUTPUT_WINDOW_LENGTH_MSB, 0x00, + + /* Task B */ + R_C0_B_TASK_HANDLING_CNTL, 0x00, + R_C1_B_X_PORT_FORMATS_AND_CONF, 0x08, + R_C2_B_INPUT_REFERENCE_SIGNAL_DEFINITION, 0x00, + R_C3_B_I_PORT_FORMATS_AND_CONF, 0x80, + + /* 0x0002 is minimum */ + R_C4_B_HORIZ_INPUT_WINDOW_START, 0x02, + R_C5_B_HORIZ_INPUT_WINDOW_START_MSB, 0x00, + + /* 0x02d0 = 720 */ + R_C6_B_HORIZ_INPUT_WINDOW_LENGTH, 0xd0, + R_C7_B_HORIZ_INPUT_WINDOW_LENGTH_MSB, 0x02, + + /* vwindow start 0x12 = 18 */ + R_C8_B_VERT_INPUT_WINDOW_START, 0x12, + R_C9_B_VERT_INPUT_WINDOW_START_MSB, 0x00, + + /* vwindow length 0xf8 = 248 */ + R_CA_B_VERT_INPUT_WINDOW_LENGTH, VRES_60HZ>>1, + R_CB_B_VERT_INPUT_WINDOW_LENGTH_MSB, VRES_60HZ>>9, + + /* hwindow 0x02d0 = 720 */ + R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH, 0xd0, + R_CD_B_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, 0x02, + + R_F0_LFCO_PER_LINE, 0xad, /* Set PLL Register. 60hz 525 lines per frame, 27 MHz */ + R_F1_P_I_PARAM_SELECT, 0x05, /* low bit with 0xF0 */ + R_F5_PULSGEN_LINE_LENGTH, 0xad, + R_F6_PULSE_A_POS_LSB_AND_PULSEGEN_CONFIG, 0x01, + + 0x00, 0x00 +}; + +static const unsigned char saa7115_cfg_50hz_video[] = { + R_80_GLOBAL_CNTL_1, 0x00, + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + + R_15_VGATE_START_FID_CHG, 0x37, /* VGATE start */ + R_16_VGATE_STOP, 0x16, + R_17_MISC_VGATE_CONF_AND_MSB, 0x99, + + R_08_SYNC_CNTL, 0x28, /* 0x28 = PAL */ + R_0E_CHROMA_CNTL_1, 0x07, + + R_5A_V_OFF_FOR_SLICER, 0x03, /* standard 50hz value */ + + /* Task A */ + R_90_A_TASK_HANDLING_CNTL, 0x81, + R_91_A_X_PORT_FORMATS_AND_CONF, 0x48, + R_92_A_X_PORT_INPUT_REFERENCE_SIGNAL, 0x40, + R_93_A_I_PORT_OUTPUT_FORMATS_AND_CONF, 0x84, + + /* This is weird: the datasheet says that you should use 2 as the minimum value, */ + /* but Hauppauge uses 0, and changing that to 2 causes indeed problems (for 50hz) */ + /* hoffset low (input), 0x0002 is minimum */ + R_94_A_HORIZ_INPUT_WINDOW_START, 0x00, + R_95_A_HORIZ_INPUT_WINDOW_START_MSB, 0x00, + + /* hsize low (input), 0x02d0 = 720 */ + R_96_A_HORIZ_INPUT_WINDOW_LENGTH, 0xd0, + R_97_A_HORIZ_INPUT_WINDOW_LENGTH_MSB, 0x02, + + R_98_A_VERT_INPUT_WINDOW_START, 0x03, + R_99_A_VERT_INPUT_WINDOW_START_MSB, 0x00, + + /* vsize 0x12 = 18 */ + R_9A_A_VERT_INPUT_WINDOW_LENGTH, 0x12, + R_9B_A_VERT_INPUT_WINDOW_LENGTH_MSB, 0x00, + + /* hsize 0x05a0 = 1440 */ + R_9C_A_HORIZ_OUTPUT_WINDOW_LENGTH, 0xa0, + R_9D_A_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, 0x05, /* hsize hi (output) */ + R_9E_A_VERT_OUTPUT_WINDOW_LENGTH, 0x12, /* vsize low (output), 0x12 = 18 */ + R_9F_A_VERT_OUTPUT_WINDOW_LENGTH_MSB, 0x00, /* vsize hi (output) */ + + /* Task B */ + R_C0_B_TASK_HANDLING_CNTL, 0x00, + R_C1_B_X_PORT_FORMATS_AND_CONF, 0x08, + R_C2_B_INPUT_REFERENCE_SIGNAL_DEFINITION, 0x00, + R_C3_B_I_PORT_FORMATS_AND_CONF, 0x80, + + /* This is weird: the datasheet says that you should use 2 as the minimum value, */ + /* but Hauppauge uses 0, and changing that to 2 causes indeed problems (for 50hz) */ + /* hoffset low (input), 0x0002 is minimum. See comment above. */ + R_C4_B_HORIZ_INPUT_WINDOW_START, 0x00, + R_C5_B_HORIZ_INPUT_WINDOW_START_MSB, 0x00, + + /* hsize 0x02d0 = 720 */ + R_C6_B_HORIZ_INPUT_WINDOW_LENGTH, 0xd0, + R_C7_B_HORIZ_INPUT_WINDOW_LENGTH_MSB, 0x02, + + /* voffset 0x16 = 22 */ + R_C8_B_VERT_INPUT_WINDOW_START, 0x16, + R_C9_B_VERT_INPUT_WINDOW_START_MSB, 0x00, + + /* vsize 0x0120 = 288 */ + R_CA_B_VERT_INPUT_WINDOW_LENGTH, 0x20, + R_CB_B_VERT_INPUT_WINDOW_LENGTH_MSB, 0x01, + + /* hsize 0x02d0 = 720 */ + R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH, 0xd0, + R_CD_B_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, 0x02, + + R_F0_LFCO_PER_LINE, 0xb0, /* Set PLL Register. 50hz 625 lines per frame, 27 MHz */ + R_F1_P_I_PARAM_SELECT, 0x05, /* low bit with 0xF0, (was 0x05) */ + R_F5_PULSGEN_LINE_LENGTH, 0xb0, + R_F6_PULSE_A_POS_LSB_AND_PULSEGEN_CONFIG, 0x01, + + 0x00, 0x00 +}; + +/* ============== SAA7715 VIDEO templates (end) ======= */ + +static const unsigned char saa7115_cfg_vbi_on[] = { + R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + R_80_GLOBAL_CNTL_1, 0x30, /* Activate both tasks */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, /* activate scaler */ + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x01, /* Enable I-port output */ + + 0x00, 0x00 +}; + +static const unsigned char saa7115_cfg_vbi_off[] = { + R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, /* reset scaler */ + R_80_GLOBAL_CNTL_1, 0x20, /* Activate only task "B" */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, /* activate scaler */ + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x01, /* Enable I-port output */ + + 0x00, 0x00 +}; + + +static const unsigned char saa7115_init_misc[] = { + R_81_V_SYNC_FLD_ID_SRC_SEL_AND_RETIMED_V_F, 0x01, + R_83_X_PORT_I_O_ENA_AND_OUT_CLK, 0x01, + R_84_I_PORT_SIGNAL_DEF, 0x20, + R_85_I_PORT_SIGNAL_POLAR, 0x21, + R_86_I_PORT_FIFO_FLAG_CNTL_AND_ARBIT, 0xc5, + R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, 0x01, + + /* Task A */ + R_A0_A_HORIZ_PRESCALING, 0x01, + R_A1_A_ACCUMULATION_LENGTH, 0x00, + R_A2_A_PRESCALER_DC_GAIN_AND_FIR_PREFILTER, 0x00, + + /* Configure controls at nominal value*/ + R_A4_A_LUMA_BRIGHTNESS_CNTL, 0x80, + R_A5_A_LUMA_CONTRAST_CNTL, 0x40, + R_A6_A_CHROMA_SATURATION_CNTL, 0x40, + + /* note: 2 x zoom ensures that VBI lines have same length as video lines. */ + R_A8_A_HORIZ_LUMA_SCALING_INC, 0x00, + R_A9_A_HORIZ_LUMA_SCALING_INC_MSB, 0x02, + + R_AA_A_HORIZ_LUMA_PHASE_OFF, 0x00, + + /* must be horiz lum scaling / 2 */ + R_AC_A_HORIZ_CHROMA_SCALING_INC, 0x00, + R_AD_A_HORIZ_CHROMA_SCALING_INC_MSB, 0x01, + + /* must be offset luma / 2 */ + R_AE_A_HORIZ_CHROMA_PHASE_OFF, 0x00, + + R_B0_A_VERT_LUMA_SCALING_INC, 0x00, + R_B1_A_VERT_LUMA_SCALING_INC_MSB, 0x04, + + R_B2_A_VERT_CHROMA_SCALING_INC, 0x00, + R_B3_A_VERT_CHROMA_SCALING_INC_MSB, 0x04, + + R_B4_A_VERT_SCALING_MODE_CNTL, 0x01, + + R_B8_A_VERT_CHROMA_PHASE_OFF_00, 0x00, + R_B9_A_VERT_CHROMA_PHASE_OFF_01, 0x00, + R_BA_A_VERT_CHROMA_PHASE_OFF_10, 0x00, + R_BB_A_VERT_CHROMA_PHASE_OFF_11, 0x00, + + R_BC_A_VERT_LUMA_PHASE_OFF_00, 0x00, + R_BD_A_VERT_LUMA_PHASE_OFF_01, 0x00, + R_BE_A_VERT_LUMA_PHASE_OFF_10, 0x00, + R_BF_A_VERT_LUMA_PHASE_OFF_11, 0x00, + + /* Task B */ + R_D0_B_HORIZ_PRESCALING, 0x01, + R_D1_B_ACCUMULATION_LENGTH, 0x00, + R_D2_B_PRESCALER_DC_GAIN_AND_FIR_PREFILTER, 0x00, + + /* Configure controls at nominal value*/ + R_D4_B_LUMA_BRIGHTNESS_CNTL, 0x80, + R_D5_B_LUMA_CONTRAST_CNTL, 0x40, + R_D6_B_CHROMA_SATURATION_CNTL, 0x40, + + /* hor lum scaling 0x0400 = 1 */ + R_D8_B_HORIZ_LUMA_SCALING_INC, 0x00, + R_D9_B_HORIZ_LUMA_SCALING_INC_MSB, 0x04, + + R_DA_B_HORIZ_LUMA_PHASE_OFF, 0x00, + + /* must be hor lum scaling / 2 */ + R_DC_B_HORIZ_CHROMA_SCALING, 0x00, + R_DD_B_HORIZ_CHROMA_SCALING_MSB, 0x02, + + /* must be offset luma / 2 */ + R_DE_B_HORIZ_PHASE_OFFSET_CRHOMA, 0x00, + + R_E0_B_VERT_LUMA_SCALING_INC, 0x00, + R_E1_B_VERT_LUMA_SCALING_INC_MSB, 0x04, + + R_E2_B_VERT_CHROMA_SCALING_INC, 0x00, + R_E3_B_VERT_CHROMA_SCALING_INC_MSB, 0x04, + + R_E4_B_VERT_SCALING_MODE_CNTL, 0x01, + + R_E8_B_VERT_CHROMA_PHASE_OFF_00, 0x00, + R_E9_B_VERT_CHROMA_PHASE_OFF_01, 0x00, + R_EA_B_VERT_CHROMA_PHASE_OFF_10, 0x00, + R_EB_B_VERT_CHROMA_PHASE_OFF_11, 0x00, + + R_EC_B_VERT_LUMA_PHASE_OFF_00, 0x00, + R_ED_B_VERT_LUMA_PHASE_OFF_01, 0x00, + R_EE_B_VERT_LUMA_PHASE_OFF_10, 0x00, + R_EF_B_VERT_LUMA_PHASE_OFF_11, 0x00, + + R_F2_NOMINAL_PLL2_DTO, 0x50, /* crystal clock = 24.576 MHz, target = 27MHz */ + R_F3_PLL_INCREMENT, 0x46, + R_F4_PLL2_STATUS, 0x00, + R_F7_PULSE_A_POS_MSB, 0x4b, /* not the recommended settings! */ + R_F8_PULSE_B_POS, 0x00, + R_F9_PULSE_B_POS_MSB, 0x4b, + R_FA_PULSE_C_POS, 0x00, + R_FB_PULSE_C_POS_MSB, 0x4b, + + /* PLL2 lock detection settings: 71 lines 50% phase error */ + R_FF_S_PLL_MAX_PHASE_ERR_THRESH_NUM_LINES, 0x88, + + /* Turn off VBI */ + R_40_SLICER_CNTL_1, 0x20, /* No framing code errors allowed. */ + R_41_LCR_BASE, 0xff, + R_41_LCR_BASE+1, 0xff, + R_41_LCR_BASE+2, 0xff, + R_41_LCR_BASE+3, 0xff, + R_41_LCR_BASE+4, 0xff, + R_41_LCR_BASE+5, 0xff, + R_41_LCR_BASE+6, 0xff, + R_41_LCR_BASE+7, 0xff, + R_41_LCR_BASE+8, 0xff, + R_41_LCR_BASE+9, 0xff, + R_41_LCR_BASE+10, 0xff, + R_41_LCR_BASE+11, 0xff, + R_41_LCR_BASE+12, 0xff, + R_41_LCR_BASE+13, 0xff, + R_41_LCR_BASE+14, 0xff, + R_41_LCR_BASE+15, 0xff, + R_41_LCR_BASE+16, 0xff, + R_41_LCR_BASE+17, 0xff, + R_41_LCR_BASE+18, 0xff, + R_41_LCR_BASE+19, 0xff, + R_41_LCR_BASE+20, 0xff, + R_41_LCR_BASE+21, 0xff, + R_41_LCR_BASE+22, 0xff, + R_58_PROGRAM_FRAMING_CODE, 0x40, + R_59_H_OFF_FOR_SLICER, 0x47, + R_5B_FLD_OFF_AND_MSB_FOR_H_AND_V_OFF, 0x83, + R_5D_DID, 0xbd, + R_5E_SDID, 0x35, + + R_02_INPUT_CNTL_1, 0xc4, /* input tuner -> input 4, amplifier active */ + + R_80_GLOBAL_CNTL_1, 0x20, /* enable task B */ + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xd0, + R_88_POWER_SAVE_ADC_PORT_CNTL, 0xf0, + 0x00, 0x00 +}; + +static int saa711x_odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static int saa711x_decode_vps(u8 *dst, u8 *p) +{ + static const u8 biphase_tbl[] = { + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87, + 0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3, + 0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85, + 0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86, + 0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2, + 0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84, + 0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + }; + int i; + u8 c, err = 0; + + for (i = 0; i < 2 * 13; i += 2) { + err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]]; + c = (biphase_tbl[p[i + 1]] & 0xf) | ((biphase_tbl[p[i]] & 0xf) << 4); + dst[i / 2] = c; + } + return err & 0xf0; +} + +static int saa711x_decode_wss(u8 *p) +{ + static const int wss_bits[8] = { + 0, 0, 0, 1, 0, 1, 1, 1 + }; + unsigned char parity; + int wss = 0; + int i; + + for (i = 0; i < 16; i++) { + int b1 = wss_bits[p[i] & 7]; + int b2 = wss_bits[(p[i] >> 3) & 7]; + + if (b1 == b2) + return -1; + wss |= b2 << i; + } + parity = wss & 15; + parity ^= parity >> 2; + parity ^= parity >> 1; + + if (!(parity & 1)) + return -1; + + return wss; +} + +static int saa711x_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + struct saa711x_state *state = to_state(sd); + u32 acpf; + u32 acni; + u32 hz; + u64 f; + u8 acc = 0; /* reg 0x3a, audio clock control */ + + /* Checks for chips that don't have audio clock (saa7111, saa7113) */ + if (!saa711x_has_reg(state->ident, R_30_AUD_MAST_CLK_CYCLES_PER_FIELD)) + return 0; + + v4l2_dbg(1, debug, sd, "set audio clock freq: %d\n", freq); + + /* sanity check */ + if (freq < 32000 || freq > 48000) + return -EINVAL; + + /* hz is the refresh rate times 100 */ + hz = (state->std & V4L2_STD_525_60) ? 5994 : 5000; + /* acpf = (256 * freq) / field_frequency == (256 * 100 * freq) / hz */ + acpf = (25600 * freq) / hz; + /* acni = (256 * freq * 2^23) / crystal_frequency = + (freq * 2^(8+23)) / crystal_frequency = + (freq << 31) / crystal_frequency */ + f = freq; + f = f << 31; + do_div(f, state->crystal_freq); + acni = f; + if (state->ucgc) { + acpf = acpf * state->cgcdiv / 16; + acni = acni * state->cgcdiv / 16; + acc = 0x80; + if (state->cgcdiv == 3) + acc |= 0x40; + } + if (state->apll) + acc |= 0x08; + + saa711x_write(sd, R_38_CLK_RATIO_AMXCLK_TO_ASCLK, 0x03); + saa711x_write(sd, R_39_CLK_RATIO_ASCLK_TO_ALRCLK, 0x10); + saa711x_write(sd, R_3A_AUD_CLK_GEN_BASIC_SETUP, acc); + + saa711x_write(sd, R_30_AUD_MAST_CLK_CYCLES_PER_FIELD, acpf & 0xff); + saa711x_write(sd, R_30_AUD_MAST_CLK_CYCLES_PER_FIELD+1, + (acpf >> 8) & 0xff); + saa711x_write(sd, R_30_AUD_MAST_CLK_CYCLES_PER_FIELD+2, + (acpf >> 16) & 0x03); + + saa711x_write(sd, R_34_AUD_MAST_CLK_NOMINAL_INC, acni & 0xff); + saa711x_write(sd, R_34_AUD_MAST_CLK_NOMINAL_INC+1, (acni >> 8) & 0xff); + saa711x_write(sd, R_34_AUD_MAST_CLK_NOMINAL_INC+2, (acni >> 16) & 0x3f); + state->audclk_freq = freq; + return 0; +} + +static int saa711x_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct saa711x_state *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_CHROMA_AGC: + /* chroma gain cluster */ + if (state->agc->val) + state->gain->val = + saa711x_read(sd, R_0F_CHROMA_GAIN_CNTL) & 0x7f; + break; + } + return 0; +} + +static int saa711x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct saa711x_state *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + saa711x_write(sd, R_0A_LUMA_BRIGHT_CNTL, ctrl->val); + break; + + case V4L2_CID_CONTRAST: + saa711x_write(sd, R_0B_LUMA_CONTRAST_CNTL, ctrl->val); + break; + + case V4L2_CID_SATURATION: + saa711x_write(sd, R_0C_CHROMA_SAT_CNTL, ctrl->val); + break; + + case V4L2_CID_HUE: + saa711x_write(sd, R_0D_CHROMA_HUE_CNTL, ctrl->val); + break; + + case V4L2_CID_CHROMA_AGC: + /* chroma gain cluster */ + if (state->agc->val) + saa711x_write(sd, R_0F_CHROMA_GAIN_CNTL, state->gain->val); + else + saa711x_write(sd, R_0F_CHROMA_GAIN_CNTL, state->gain->val | 0x80); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int saa711x_set_size(struct v4l2_subdev *sd, int width, int height) +{ + struct saa711x_state *state = to_state(sd); + int HPSC, HFSC; + int VSCY; + int res; + int is_50hz = state->std & V4L2_STD_625_50; + int Vsrc = is_50hz ? 576 : 480; + + v4l2_dbg(1, debug, sd, "decoder set size to %ix%i\n", width, height); + + /* FIXME need better bounds checking here */ + if ((width < 1) || (width > 1440)) + return -EINVAL; + if ((height < 1) || (height > Vsrc)) + return -EINVAL; + + if (!saa711x_has_reg(state->ident, R_D0_B_HORIZ_PRESCALING)) { + /* Decoder only supports 720 columns and 480 or 576 lines */ + if (width != 720) + return -EINVAL; + if (height != Vsrc) + return -EINVAL; + } + + state->width = width; + state->height = height; + + if (!saa711x_has_reg(state->ident, R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH)) + return 0; + + /* probably have a valid size, let's set it */ + /* Set output width/height */ + /* width */ + + saa711x_write(sd, R_CC_B_HORIZ_OUTPUT_WINDOW_LENGTH, + (u8) (width & 0xff)); + saa711x_write(sd, R_CD_B_HORIZ_OUTPUT_WINDOW_LENGTH_MSB, + (u8) ((width >> 8) & 0xff)); + + /* Vertical Scaling uses height/2 */ + res = height / 2; + + /* On 60Hz, it is using a higher Vertical Output Size */ + if (!is_50hz) + res += (VRES_60HZ - 480) >> 1; + + /* height */ + saa711x_write(sd, R_CE_B_VERT_OUTPUT_WINDOW_LENGTH, + (u8) (res & 0xff)); + saa711x_write(sd, R_CF_B_VERT_OUTPUT_WINDOW_LENGTH_MSB, + (u8) ((res >> 8) & 0xff)); + + /* Scaling settings */ + /* Hprescaler is floor(inres/outres) */ + HPSC = (int)(720 / width); + /* 0 is not allowed (div. by zero) */ + HPSC = HPSC ? HPSC : 1; + HFSC = (int)((1024 * 720) / (HPSC * width)); + /* FIXME hardcodes to "Task B" + * write H prescaler integer */ + saa711x_write(sd, R_D0_B_HORIZ_PRESCALING, + (u8) (HPSC & 0x3f)); + + v4l2_dbg(1, debug, sd, "Hpsc: 0x%05x, Hfsc: 0x%05x\n", HPSC, HFSC); + /* write H fine-scaling (luminance) */ + saa711x_write(sd, R_D8_B_HORIZ_LUMA_SCALING_INC, + (u8) (HFSC & 0xff)); + saa711x_write(sd, R_D9_B_HORIZ_LUMA_SCALING_INC_MSB, + (u8) ((HFSC >> 8) & 0xff)); + /* write H fine-scaling (chrominance) + * must be lum/2, so i'll just bitshift :) */ + saa711x_write(sd, R_DC_B_HORIZ_CHROMA_SCALING, + (u8) ((HFSC >> 1) & 0xff)); + saa711x_write(sd, R_DD_B_HORIZ_CHROMA_SCALING_MSB, + (u8) ((HFSC >> 9) & 0xff)); + + VSCY = (int)((1024 * Vsrc) / height); + v4l2_dbg(1, debug, sd, "Vsrc: %d, Vscy: 0x%05x\n", Vsrc, VSCY); + + /* Correct Contrast and Luminance */ + saa711x_write(sd, R_D5_B_LUMA_CONTRAST_CNTL, + (u8) (64 * 1024 / VSCY)); + saa711x_write(sd, R_D6_B_CHROMA_SATURATION_CNTL, + (u8) (64 * 1024 / VSCY)); + + /* write V fine-scaling (luminance) */ + saa711x_write(sd, R_E0_B_VERT_LUMA_SCALING_INC, + (u8) (VSCY & 0xff)); + saa711x_write(sd, R_E1_B_VERT_LUMA_SCALING_INC_MSB, + (u8) ((VSCY >> 8) & 0xff)); + /* write V fine-scaling (chrominance) */ + saa711x_write(sd, R_E2_B_VERT_CHROMA_SCALING_INC, + (u8) (VSCY & 0xff)); + saa711x_write(sd, R_E3_B_VERT_CHROMA_SCALING_INC_MSB, + (u8) ((VSCY >> 8) & 0xff)); + + saa711x_writeregs(sd, saa7115_cfg_reset_scaler); + + /* Activates task "B" */ + saa711x_write(sd, R_80_GLOBAL_CNTL_1, + saa711x_read(sd, R_80_GLOBAL_CNTL_1) | 0x20); + + return 0; +} + +static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa711x_state *state = to_state(sd); + + /* Prevent unnecessary standard changes. During a standard + change the I-Port is temporarily disabled. Any devices + reading from that port can get confused. + Note that s_std is also used to switch from + radio to TV mode, so if a s_std is broadcast to + all I2C devices then you do not want to have an unwanted + side-effect here. */ + if (std == state->std) + return; + + state->std = std; + + // This works for NTSC-M, SECAM-L and the 50Hz PAL variants. + if (std & V4L2_STD_525_60) { + v4l2_dbg(1, debug, sd, "decoder set standard 60 Hz\n"); + saa711x_writeregs(sd, saa7115_cfg_60hz_video); + saa711x_set_size(sd, 720, 480); + } else { + v4l2_dbg(1, debug, sd, "decoder set standard 50 Hz\n"); + saa711x_writeregs(sd, saa7115_cfg_50hz_video); + saa711x_set_size(sd, 720, 576); + } + + /* Register 0E - Bits D6-D4 on NO-AUTO mode + (SAA7111 and SAA7113 doesn't have auto mode) + 50 Hz / 625 lines 60 Hz / 525 lines + 000 PAL BGDHI (4.43Mhz) NTSC M (3.58MHz) + 001 NTSC 4.43 (50 Hz) PAL 4.43 (60 Hz) + 010 Combination-PAL N (3.58MHz) NTSC 4.43 (60 Hz) + 011 NTSC N (3.58MHz) PAL M (3.58MHz) + 100 reserved NTSC-Japan (3.58MHz) + */ + if (state->ident <= V4L2_IDENT_SAA7113) { + u8 reg = saa711x_read(sd, R_0E_CHROMA_CNTL_1) & 0x8f; + + if (std == V4L2_STD_PAL_M) { + reg |= 0x30; + } else if (std == V4L2_STD_PAL_Nc) { + reg |= 0x20; + } else if (std == V4L2_STD_PAL_60) { + reg |= 0x10; + } else if (std == V4L2_STD_NTSC_M_JP) { + reg |= 0x40; + } else if (std & V4L2_STD_SECAM) { + reg |= 0x50; + } + saa711x_write(sd, R_0E_CHROMA_CNTL_1, reg); + } else { + /* restart task B if needed */ + int taskb = saa711x_read(sd, R_80_GLOBAL_CNTL_1) & 0x10; + + if (taskb && state->ident == V4L2_IDENT_SAA7114) { + saa711x_writeregs(sd, saa7115_cfg_vbi_on); + } + + /* switch audio mode too! */ + saa711x_s_clock_freq(sd, state->audclk_freq); + } +} + +/* setup the sliced VBI lcr registers according to the sliced VBI format */ +static void saa711x_set_lcr(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt) +{ + struct saa711x_state *state = to_state(sd); + int is_50hz = (state->std & V4L2_STD_625_50); + u8 lcr[24]; + int i, x; + +#if 1 + /* saa7113/7114/7118 VBI support are experimental */ + if (!saa711x_has_reg(state->ident, R_41_LCR_BASE)) + return; + +#else + /* SAA7113 and SAA7118 also should support VBI - Need testing */ + if (state->ident != V4L2_IDENT_SAA7115) + return; +#endif + + for (i = 0; i <= 23; i++) + lcr[i] = 0xff; + + if (fmt == NULL) { + /* raw VBI */ + if (is_50hz) + for (i = 6; i <= 23; i++) + lcr[i] = 0xdd; + else + for (i = 10; i <= 21; i++) + lcr[i] = 0xdd; + } else { + /* sliced VBI */ + /* first clear lines that cannot be captured */ + if (is_50hz) { + for (i = 0; i <= 5; i++) + fmt->service_lines[0][i] = + fmt->service_lines[1][i] = 0; + } + else { + for (i = 0; i <= 9; i++) + fmt->service_lines[0][i] = + fmt->service_lines[1][i] = 0; + for (i = 22; i <= 23; i++) + fmt->service_lines[0][i] = + fmt->service_lines[1][i] = 0; + } + + /* Now set the lcr values according to the specified service */ + for (i = 6; i <= 23; i++) { + lcr[i] = 0; + for (x = 0; x <= 1; x++) { + switch (fmt->service_lines[1-x][i]) { + case 0: + lcr[i] |= 0xf << (4 * x); + break; + case V4L2_SLICED_TELETEXT_B: + lcr[i] |= 1 << (4 * x); + break; + case V4L2_SLICED_CAPTION_525: + lcr[i] |= 4 << (4 * x); + break; + case V4L2_SLICED_WSS_625: + lcr[i] |= 5 << (4 * x); + break; + case V4L2_SLICED_VPS: + lcr[i] |= 7 << (4 * x); + break; + } + } + } + } + + /* write the lcr registers */ + for (i = 2; i <= 23; i++) { + saa711x_write(sd, i - 2 + R_41_LCR_BASE, lcr[i]); + } + + /* enable/disable raw VBI capturing */ + saa711x_writeregs(sd, fmt == NULL ? + saa7115_cfg_vbi_on : + saa7115_cfg_vbi_off); +} + +static int saa711x_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *sliced) +{ + static u16 lcr2vbi[] = { + 0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */ + 0, V4L2_SLICED_CAPTION_525, /* 4 */ + V4L2_SLICED_WSS_625, 0, /* 5 */ + V4L2_SLICED_VPS, 0, 0, 0, 0, /* 7 */ + 0, 0, 0, 0 + }; + int i; + + memset(sliced->service_lines, 0, sizeof(sliced->service_lines)); + sliced->service_set = 0; + /* done if using raw VBI */ + if (saa711x_read(sd, R_80_GLOBAL_CNTL_1) & 0x10) + return 0; + for (i = 2; i <= 23; i++) { + u8 v = saa711x_read(sd, i - 2 + R_41_LCR_BASE); + + sliced->service_lines[0][i] = lcr2vbi[v >> 4]; + sliced->service_lines[1][i] = lcr2vbi[v & 0xf]; + sliced->service_set |= + sliced->service_lines[0][i] | sliced->service_lines[1][i]; + } + return 0; +} + +static int saa711x_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt) +{ + saa711x_set_lcr(sd, NULL); + return 0; +} + +static int saa711x_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt) +{ + saa711x_set_lcr(sd, fmt); + return 0; +} + +static int saa711x_s_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt) +{ + if (fmt->code != V4L2_MBUS_FMT_FIXED) + return -EINVAL; + fmt->field = V4L2_FIELD_INTERLACED; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + return saa711x_set_size(sd, fmt->width, fmt->height); +} + +/* Decode the sliced VBI data stream as created by the saa7115. + The format is described in the saa7115 datasheet in Tables 25 and 26 + and in Figure 33. + The current implementation uses SAV/EAV codes and not the ancillary data + headers. The vbi->p pointer points to the R_5E_SDID byte right after the SAV + code. */ +static int saa711x_decode_vbi_line(struct v4l2_subdev *sd, struct v4l2_decode_vbi_line *vbi) +{ + struct saa711x_state *state = to_state(sd); + static const char vbi_no_data_pattern[] = { + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0 + }; + u8 *p = vbi->p; + u32 wss; + int id1, id2; /* the ID1 and ID2 bytes from the internal header */ + + vbi->type = 0; /* mark result as a failure */ + id1 = p[2]; + id2 = p[3]; + /* Note: the field bit is inverted for 60 Hz video */ + if (state->std & V4L2_STD_525_60) + id1 ^= 0x40; + + /* Skip internal header, p now points to the start of the payload */ + p += 4; + vbi->p = p; + + /* calculate field and line number of the VBI packet (1-23) */ + vbi->is_second_field = ((id1 & 0x40) != 0); + vbi->line = (id1 & 0x3f) << 3; + vbi->line |= (id2 & 0x70) >> 4; + + /* Obtain data type */ + id2 &= 0xf; + + /* If the VBI slicer does not detect any signal it will fill up + the payload buffer with 0xa0 bytes. */ + if (!memcmp(p, vbi_no_data_pattern, sizeof(vbi_no_data_pattern))) + return 0; + + /* decode payloads */ + switch (id2) { + case 1: + vbi->type = V4L2_SLICED_TELETEXT_B; + break; + case 4: + if (!saa711x_odd_parity(p[0]) || !saa711x_odd_parity(p[1])) + return 0; + vbi->type = V4L2_SLICED_CAPTION_525; + break; + case 5: + wss = saa711x_decode_wss(p); + if (wss == -1) + return 0; + p[0] = wss & 0xff; + p[1] = wss >> 8; + vbi->type = V4L2_SLICED_WSS_625; + break; + case 7: + if (saa711x_decode_vps(p, p) != 0) + return 0; + vbi->type = V4L2_SLICED_VPS; + break; + default: + break; + } + return 0; +} + +/* ============ SAA7115 AUDIO settings (end) ============= */ + +static int saa711x_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct saa711x_state *state = to_state(sd); + int status; + + if (state->radio) + return 0; + status = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + + v4l2_dbg(1, debug, sd, "status: 0x%02x\n", status); + vt->signal = ((status & (1 << 6)) == 0) ? 0xffff : 0x0; + return 0; +} + +static int saa711x_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa711x_state *state = to_state(sd); + + state->radio = 0; + saa711x_set_v4lstd(sd, std); + return 0; +} + +static int saa711x_s_radio(struct v4l2_subdev *sd) +{ + struct saa711x_state *state = to_state(sd); + + state->radio = 1; + return 0; +} + +static int saa711x_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct saa711x_state *state = to_state(sd); + u8 mask = (state->ident <= V4L2_IDENT_SAA7111A) ? 0xf8 : 0xf0; + + v4l2_dbg(1, debug, sd, "decoder set input %d output %d\n", + input, output); + + /* saa7111/3 does not have these inputs */ + if (state->ident <= V4L2_IDENT_SAA7113 && + (input == SAA7115_COMPOSITE4 || + input == SAA7115_COMPOSITE5)) { + return -EINVAL; + } + if (input > SAA7115_SVIDEO3) + return -EINVAL; + if (state->input == input && state->output == output) + return 0; + v4l2_dbg(1, debug, sd, "now setting %s input %s output\n", + (input >= SAA7115_SVIDEO0) ? "S-Video" : "Composite", + (output == SAA7115_IPORT_ON) ? "iport on" : "iport off"); + state->input = input; + + /* saa7111 has slightly different input numbering */ + if (state->ident <= V4L2_IDENT_SAA7111A) { + if (input >= SAA7115_COMPOSITE4) + input -= 2; + /* saa7111 specific */ + saa711x_write(sd, R_10_CHROMA_CNTL_2, + (saa711x_read(sd, R_10_CHROMA_CNTL_2) & 0x3f) | + ((output & 0xc0) ^ 0x40)); + saa711x_write(sd, R_13_RT_X_PORT_OUT_CNTL, + (saa711x_read(sd, R_13_RT_X_PORT_OUT_CNTL) & 0xf0) | + ((output & 2) ? 0x0a : 0)); + } + + /* select mode */ + saa711x_write(sd, R_02_INPUT_CNTL_1, + (saa711x_read(sd, R_02_INPUT_CNTL_1) & mask) | + input); + + /* bypass chrominance trap for S-Video modes */ + saa711x_write(sd, R_09_LUMA_CNTL, + (saa711x_read(sd, R_09_LUMA_CNTL) & 0x7f) | + (state->input >= SAA7115_SVIDEO0 ? 0x80 : 0x0)); + + state->output = output; + if (state->ident == V4L2_IDENT_SAA7114 || + state->ident == V4L2_IDENT_SAA7115) { + saa711x_write(sd, R_83_X_PORT_I_O_ENA_AND_OUT_CLK, + (saa711x_read(sd, R_83_X_PORT_I_O_ENA_AND_OUT_CLK) & 0xfe) | + (state->output & 0x01)); + } + return 0; +} + +static int saa711x_s_gpio(struct v4l2_subdev *sd, u32 val) +{ + struct saa711x_state *state = to_state(sd); + + if (state->ident > V4L2_IDENT_SAA7111A) + return -EINVAL; + saa711x_write(sd, 0x11, (saa711x_read(sd, 0x11) & 0x7f) | + (val ? 0x80 : 0)); + return 0; +} + +static int saa711x_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct saa711x_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s output\n", + enable ? "enable" : "disable"); + + if (state->enable == enable) + return 0; + state->enable = enable; + if (!saa711x_has_reg(state->ident, R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED)) + return 0; + saa711x_write(sd, R_87_I_PORT_I_O_ENA_OUT_CLK_AND_GATED, state->enable); + return 0; +} + +static int saa711x_s_crystal_freq(struct v4l2_subdev *sd, u32 freq, u32 flags) +{ + struct saa711x_state *state = to_state(sd); + + if (freq != SAA7115_FREQ_32_11_MHZ && freq != SAA7115_FREQ_24_576_MHZ) + return -EINVAL; + state->crystal_freq = freq; + state->cgcdiv = (flags & SAA7115_FREQ_FL_CGCDIV) ? 3 : 4; + state->ucgc = (flags & SAA7115_FREQ_FL_UCGC) ? 1 : 0; + state->apll = (flags & SAA7115_FREQ_FL_APLL) ? 1 : 0; + saa711x_s_clock_freq(sd, state->audclk_freq); + return 0; +} + +static int saa711x_reset(struct v4l2_subdev *sd, u32 val) +{ + v4l2_dbg(1, debug, sd, "decoder RESET\n"); + saa711x_writeregs(sd, saa7115_cfg_reset_scaler); + return 0; +} + +static int saa711x_g_vbi_data(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_data *data) +{ + /* Note: the internal field ID is inverted for NTSC, + so data->field 0 maps to the saa7115 even field, + whereas for PAL it maps to the saa7115 odd field. */ + switch (data->id) { + case V4L2_SLICED_WSS_625: + if (saa711x_read(sd, 0x6b) & 0xc0) + return -EIO; + data->data[0] = saa711x_read(sd, 0x6c); + data->data[1] = saa711x_read(sd, 0x6d); + return 0; + case V4L2_SLICED_CAPTION_525: + if (data->field == 0) { + /* CC */ + if (saa711x_read(sd, 0x66) & 0x30) + return -EIO; + data->data[0] = saa711x_read(sd, 0x69); + data->data[1] = saa711x_read(sd, 0x6a); + return 0; + } + /* XDS */ + if (saa711x_read(sd, 0x66) & 0xc0) + return -EIO; + data->data[0] = saa711x_read(sd, 0x67); + data->data[1] = saa711x_read(sd, 0x68); + return 0; + default: + return -EINVAL; + } +} + +static int saa711x_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct saa711x_state *state = to_state(sd); + int reg1f, reg1e; + + /* + * The V4L2 core already initializes std with all supported + * Standards. All driver needs to do is to mask it, to remove + * standards that don't apply from the mask + */ + + reg1f = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + v4l2_dbg(1, debug, sd, "Status byte 2 (0x1f)=0x%02x\n", reg1f); + + /* horizontal/vertical not locked */ + if (reg1f & 0x40) + goto ret; + + if (reg1f & 0x20) + *std &= V4L2_STD_525_60; + else + *std &= V4L2_STD_625_50; + + if (state->ident != V4L2_IDENT_SAA7115) + goto ret; + + reg1e = saa711x_read(sd, R_1E_STATUS_BYTE_1_VD_DEC); + + switch (reg1e & 0x03) { + case 1: + *std &= V4L2_STD_NTSC; + break; + case 2: + /* + * V4L2_STD_PAL just cover the european PAL standards. + * This is wrong, as the device could also be using an + * other PAL standard. + */ + *std &= V4L2_STD_PAL | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | + V4L2_STD_PAL_M | V4L2_STD_PAL_60; + break; + case 3: + *std &= V4L2_STD_SECAM; + break; + default: + /* Can't detect anything */ + break; + } + + v4l2_dbg(1, debug, sd, "Status byte 1 (0x1e)=0x%02x\n", reg1e); + +ret: + v4l2_dbg(1, debug, sd, "detected std mask = %08Lx\n", *std); + + return 0; +} + +static int saa711x_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct saa711x_state *state = to_state(sd); + int reg1e = 0x80; + int reg1f; + + *status = V4L2_IN_ST_NO_SIGNAL; + if (state->ident == V4L2_IDENT_SAA7115) + reg1e = saa711x_read(sd, R_1E_STATUS_BYTE_1_VD_DEC); + reg1f = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + if ((reg1f & 0xc1) == 0x81 && (reg1e & 0xc0) == 0x80) + *status = 0; + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int saa711x_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + reg->val = saa711x_read(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int saa711x_s_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + saa711x_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static int saa711x_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip) +{ + struct saa711x_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return v4l2_chip_ident_i2c_client(client, chip, state->ident, 0); +} + +static int saa711x_log_status(struct v4l2_subdev *sd) +{ + struct saa711x_state *state = to_state(sd); + int reg1e, reg1f; + int signalOk; + int vcr; + + v4l2_info(sd, "Audio frequency: %d Hz\n", state->audclk_freq); + if (state->ident != V4L2_IDENT_SAA7115) { + /* status for the saa7114 */ + reg1f = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + signalOk = (reg1f & 0xc1) == 0x81; + v4l2_info(sd, "Video signal: %s\n", signalOk ? "ok" : "bad"); + v4l2_info(sd, "Frequency: %s\n", (reg1f & 0x20) ? "60 Hz" : "50 Hz"); + return 0; + } + + /* status for the saa7115 */ + reg1e = saa711x_read(sd, R_1E_STATUS_BYTE_1_VD_DEC); + reg1f = saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC); + + signalOk = (reg1f & 0xc1) == 0x81 && (reg1e & 0xc0) == 0x80; + vcr = !(reg1f & 0x10); + + if (state->input >= 6) + v4l2_info(sd, "Input: S-Video %d\n", state->input - 6); + else + v4l2_info(sd, "Input: Composite %d\n", state->input); + v4l2_info(sd, "Video signal: %s\n", signalOk ? (vcr ? "VCR" : "broadcast/DVD") : "bad"); + v4l2_info(sd, "Frequency: %s\n", (reg1f & 0x20) ? "60 Hz" : "50 Hz"); + + switch (reg1e & 0x03) { + case 1: + v4l2_info(sd, "Detected format: NTSC\n"); + break; + case 2: + v4l2_info(sd, "Detected format: PAL\n"); + break; + case 3: + v4l2_info(sd, "Detected format: SECAM\n"); + break; + default: + v4l2_info(sd, "Detected format: BW/No color\n"); + break; + } + v4l2_info(sd, "Width, Height: %d, %d\n", state->width, state->height); + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops saa711x_ctrl_ops = { + .s_ctrl = saa711x_s_ctrl, + .g_volatile_ctrl = saa711x_g_volatile_ctrl, +}; + +static const struct v4l2_subdev_core_ops saa711x_core_ops = { + .log_status = saa711x_log_status, + .g_chip_ident = saa711x_g_chip_ident, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, + .s_std = saa711x_s_std, + .reset = saa711x_reset, + .s_gpio = saa711x_s_gpio, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = saa711x_g_register, + .s_register = saa711x_s_register, +#endif +}; + +static const struct v4l2_subdev_tuner_ops saa711x_tuner_ops = { + .s_radio = saa711x_s_radio, + .g_tuner = saa711x_g_tuner, +}; + +static const struct v4l2_subdev_audio_ops saa711x_audio_ops = { + .s_clock_freq = saa711x_s_clock_freq, +}; + +static const struct v4l2_subdev_video_ops saa711x_video_ops = { + .s_routing = saa711x_s_routing, + .s_crystal_freq = saa711x_s_crystal_freq, + .s_mbus_fmt = saa711x_s_mbus_fmt, + .s_stream = saa711x_s_stream, + .querystd = saa711x_querystd, + .g_input_status = saa711x_g_input_status, +}; + +static const struct v4l2_subdev_vbi_ops saa711x_vbi_ops = { + .g_vbi_data = saa711x_g_vbi_data, + .decode_vbi_line = saa711x_decode_vbi_line, + .g_sliced_fmt = saa711x_g_sliced_fmt, + .s_sliced_fmt = saa711x_s_sliced_fmt, + .s_raw_fmt = saa711x_s_raw_fmt, +}; + +static const struct v4l2_subdev_ops saa711x_ops = { + .core = &saa711x_core_ops, + .tuner = &saa711x_tuner_ops, + .audio = &saa711x_audio_ops, + .video = &saa711x_video_ops, + .vbi = &saa711x_vbi_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int saa711x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct saa711x_state *state; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + int i; + char name[17]; + char chip_id; + int autodetect = !id || id->driver_data == 1; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + for (i = 0; i < 0x0f; i++) { + i2c_smbus_write_byte_data(client, 0, i); + name[i] = (i2c_smbus_read_byte_data(client, 0) & 0x0f) + '0'; + if (name[i] > '9') + name[i] += 'a' - '9' - 1; + } + name[i] = '\0'; + + chip_id = name[5]; + + /* Check whether this chip is part of the saa711x series */ + if (memcmp(name + 1, "f711", 4)) { + v4l_dbg(1, debug, client, "chip found @ 0x%x (ID %s) does not match a known saa711x chip.\n", + client->addr << 1, name); + return -ENODEV; + } + + /* Safety check */ + if (!autodetect && id->name[6] != chip_id) { + v4l_warn(client, "found saa711%c while %s was expected\n", + chip_id, id->name); + } + snprintf(client->name, sizeof(client->name), "saa711%c", chip_id); + v4l_info(client, "saa711%c found (%s) @ 0x%x (%s)\n", chip_id, name, + client->addr << 1, client->adapter->name); + + state = kzalloc(sizeof(struct saa711x_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &saa711x_ops); + + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 6); + /* add in ascending ID order */ + v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + state->agc = v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_CHROMA_AGC, 0, 1, 1, 1); + state->gain = v4l2_ctrl_new_std(hdl, &saa711x_ctrl_ops, + V4L2_CID_CHROMA_GAIN, 0, 127, 1, 40); + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + kfree(state); + return err; + } + v4l2_ctrl_auto_cluster(2, &state->agc, 0, true); + + state->input = -1; + state->output = SAA7115_IPORT_ON; + state->enable = 1; + state->radio = 0; + switch (chip_id) { + case '1': + state->ident = V4L2_IDENT_SAA7111; + if (saa711x_read(sd, R_00_CHIP_VERSION) & 0xf0) { + v4l_info(client, "saa7111a variant found\n"); + state->ident = V4L2_IDENT_SAA7111A; + } + break; + case '3': + state->ident = V4L2_IDENT_SAA7113; + break; + case '4': + state->ident = V4L2_IDENT_SAA7114; + break; + case '5': + state->ident = V4L2_IDENT_SAA7115; + break; + case '8': + state->ident = V4L2_IDENT_SAA7118; + break; + default: + state->ident = V4L2_IDENT_SAA7111; + v4l2_info(sd, "WARNING: Chip is not known - Falling back to saa7111\n"); + break; + } + + state->audclk_freq = 48000; + + v4l2_dbg(1, debug, sd, "writing init values\n"); + + /* init to 60hz/48khz */ + state->crystal_freq = SAA7115_FREQ_24_576_MHZ; + switch (state->ident) { + case V4L2_IDENT_SAA7111: + case V4L2_IDENT_SAA7111A: + saa711x_writeregs(sd, saa7111_init); + break; + case V4L2_IDENT_SAA7113: + saa711x_writeregs(sd, saa7113_init); + break; + default: + state->crystal_freq = SAA7115_FREQ_32_11_MHZ; + saa711x_writeregs(sd, saa7115_init_auto_input); + } + if (state->ident > V4L2_IDENT_SAA7111A) + saa711x_writeregs(sd, saa7115_init_misc); + saa711x_set_v4lstd(sd, V4L2_STD_NTSC); + v4l2_ctrl_handler_setup(hdl); + + v4l2_dbg(1, debug, sd, "status: (1E) 0x%02x, (1F) 0x%02x\n", + saa711x_read(sd, R_1E_STATUS_BYTE_1_VD_DEC), + saa711x_read(sd, R_1F_STATUS_BYTE_2_VD_DEC)); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa711x_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + kfree(to_state(sd)); + return 0; +} + +static const struct i2c_device_id saa711x_id[] = { + { "saa7115_auto", 1 }, /* autodetect */ + { "saa7111", 0 }, + { "saa7113", 0 }, + { "saa7114", 0 }, + { "saa7115", 0 }, + { "saa7118", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, saa711x_id); + +static struct i2c_driver saa711x_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "saa7115", + }, + .probe = saa711x_probe, + .remove = saa711x_remove, + .id_table = saa711x_id, +}; + +module_i2c_driver(saa711x_driver); diff --git a/drivers/media/video/saa711x_regs.h b/drivers/media/i2c/saa711x_regs.h similarity index 100% rename from drivers/media/video/saa711x_regs.h rename to drivers/media/i2c/saa711x_regs.h diff --git a/drivers/media/i2c/saa7127.c b/drivers/media/i2c/saa7127.c new file mode 100644 index 000000000000..b745f68fbc92 --- /dev/null +++ b/drivers/media/i2c/saa7127.c @@ -0,0 +1,852 @@ +/* + * saa7127 - Philips SAA7127/SAA7129 video encoder driver + * + * Copyright (C) 2003 Roy Bulter + * + * Based on SAA7126 video encoder driver by Gillem & Andreas Oberritter + * + * Copyright (C) 2000-2001 Gillem + * Copyright (C) 2002 Andreas Oberritter + * + * Based on Stadis 4:2:2 MPEG-2 Decoder Driver by Nathan Laredo + * + * Copyright (C) 1999 Nathan Laredo + * + * This driver is designed for the Hauppauge 250/350 Linux driver + * from the ivtv Project + * + * Copyright (C) 2003 Kevin Thayer + * + * Dual output support: + * Copyright (C) 2004 Eric Varsanyi + * + * NTSC Tuning and 7.5 IRE Setup + * Copyright (C) 2004 Chris Kennedy + * + * VBI additions & cleanup: + * Copyright (C) 2004, 2005 Hans Verkuil + * + * Note: the saa7126 is identical to the saa7127, and the saa7128 is + * identical to the saa7129, except that the saa7126 and saa7128 have + * macrovision anti-taping support. This driver will almost certainly + * work fine for those chips, except of course for the missing anti-taping + * support. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +static int debug; +static int test_image; + +MODULE_DESCRIPTION("Philips SAA7127/9 video encoder driver"); +MODULE_AUTHOR("Kevin Thayer, Chris Kennedy, Hans Verkuil"); +MODULE_LICENSE("GPL"); +module_param(debug, int, 0644); +module_param(test_image, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); +MODULE_PARM_DESC(test_image, "test_image (0-1)"); + + +/* + * SAA7127 registers + */ + +#define SAA7127_REG_STATUS 0x00 +#define SAA7127_REG_WIDESCREEN_CONFIG 0x26 +#define SAA7127_REG_WIDESCREEN_ENABLE 0x27 +#define SAA7127_REG_BURST_START 0x28 +#define SAA7127_REG_BURST_END 0x29 +#define SAA7127_REG_COPYGEN_0 0x2a +#define SAA7127_REG_COPYGEN_1 0x2b +#define SAA7127_REG_COPYGEN_2 0x2c +#define SAA7127_REG_OUTPUT_PORT_CONTROL 0x2d +#define SAA7127_REG_GAIN_LUMINANCE_RGB 0x38 +#define SAA7127_REG_GAIN_COLORDIFF_RGB 0x39 +#define SAA7127_REG_INPUT_PORT_CONTROL_1 0x3a +#define SAA7129_REG_FADE_KEY_COL2 0x4f +#define SAA7127_REG_CHROMA_PHASE 0x5a +#define SAA7127_REG_GAINU 0x5b +#define SAA7127_REG_GAINV 0x5c +#define SAA7127_REG_BLACK_LEVEL 0x5d +#define SAA7127_REG_BLANKING_LEVEL 0x5e +#define SAA7127_REG_VBI_BLANKING 0x5f +#define SAA7127_REG_DAC_CONTROL 0x61 +#define SAA7127_REG_BURST_AMP 0x62 +#define SAA7127_REG_SUBC3 0x63 +#define SAA7127_REG_SUBC2 0x64 +#define SAA7127_REG_SUBC1 0x65 +#define SAA7127_REG_SUBC0 0x66 +#define SAA7127_REG_LINE_21_ODD_0 0x67 +#define SAA7127_REG_LINE_21_ODD_1 0x68 +#define SAA7127_REG_LINE_21_EVEN_0 0x69 +#define SAA7127_REG_LINE_21_EVEN_1 0x6a +#define SAA7127_REG_RCV_PORT_CONTROL 0x6b +#define SAA7127_REG_VTRIG 0x6c +#define SAA7127_REG_HTRIG_HI 0x6d +#define SAA7127_REG_MULTI 0x6e +#define SAA7127_REG_CLOSED_CAPTION 0x6f +#define SAA7127_REG_RCV2_OUTPUT_START 0x70 +#define SAA7127_REG_RCV2_OUTPUT_END 0x71 +#define SAA7127_REG_RCV2_OUTPUT_MSBS 0x72 +#define SAA7127_REG_TTX_REQUEST_H_START 0x73 +#define SAA7127_REG_TTX_REQUEST_H_DELAY_LENGTH 0x74 +#define SAA7127_REG_CSYNC_ADVANCE_VSYNC_SHIFT 0x75 +#define SAA7127_REG_TTX_ODD_REQ_VERT_START 0x76 +#define SAA7127_REG_TTX_ODD_REQ_VERT_END 0x77 +#define SAA7127_REG_TTX_EVEN_REQ_VERT_START 0x78 +#define SAA7127_REG_TTX_EVEN_REQ_VERT_END 0x79 +#define SAA7127_REG_FIRST_ACTIVE 0x7a +#define SAA7127_REG_LAST_ACTIVE 0x7b +#define SAA7127_REG_MSB_VERTICAL 0x7c +#define SAA7127_REG_DISABLE_TTX_LINE_LO_0 0x7e +#define SAA7127_REG_DISABLE_TTX_LINE_LO_1 0x7f + +/* + ********************************************************************** + * + * Arrays with configuration parameters for the SAA7127 + * + ********************************************************************** + */ + +struct i2c_reg_value { + unsigned char reg; + unsigned char value; +}; + +static const struct i2c_reg_value saa7129_init_config_extra[] = { + { SAA7127_REG_OUTPUT_PORT_CONTROL, 0x38 }, + { SAA7127_REG_VTRIG, 0xfa }, + { 0, 0 } +}; + +static const struct i2c_reg_value saa7127_init_config_common[] = { + { SAA7127_REG_WIDESCREEN_CONFIG, 0x0d }, + { SAA7127_REG_WIDESCREEN_ENABLE, 0x00 }, + { SAA7127_REG_COPYGEN_0, 0x77 }, + { SAA7127_REG_COPYGEN_1, 0x41 }, + { SAA7127_REG_COPYGEN_2, 0x00 }, /* Macrovision enable/disable */ + { SAA7127_REG_OUTPUT_PORT_CONTROL, 0xbf }, + { SAA7127_REG_GAIN_LUMINANCE_RGB, 0x00 }, + { SAA7127_REG_GAIN_COLORDIFF_RGB, 0x00 }, + { SAA7127_REG_INPUT_PORT_CONTROL_1, 0x80 }, /* for color bars */ + { SAA7127_REG_LINE_21_ODD_0, 0x77 }, + { SAA7127_REG_LINE_21_ODD_1, 0x41 }, + { SAA7127_REG_LINE_21_EVEN_0, 0x88 }, + { SAA7127_REG_LINE_21_EVEN_1, 0x41 }, + { SAA7127_REG_RCV_PORT_CONTROL, 0x12 }, + { SAA7127_REG_VTRIG, 0xf9 }, + { SAA7127_REG_HTRIG_HI, 0x00 }, + { SAA7127_REG_RCV2_OUTPUT_START, 0x41 }, + { SAA7127_REG_RCV2_OUTPUT_END, 0xc3 }, + { SAA7127_REG_RCV2_OUTPUT_MSBS, 0x00 }, + { SAA7127_REG_TTX_REQUEST_H_START, 0x3e }, + { SAA7127_REG_TTX_REQUEST_H_DELAY_LENGTH, 0xb8 }, + { SAA7127_REG_CSYNC_ADVANCE_VSYNC_SHIFT, 0x03 }, + { SAA7127_REG_TTX_ODD_REQ_VERT_START, 0x15 }, + { SAA7127_REG_TTX_ODD_REQ_VERT_END, 0x16 }, + { SAA7127_REG_TTX_EVEN_REQ_VERT_START, 0x15 }, + { SAA7127_REG_TTX_EVEN_REQ_VERT_END, 0x16 }, + { SAA7127_REG_FIRST_ACTIVE, 0x1a }, + { SAA7127_REG_LAST_ACTIVE, 0x01 }, + { SAA7127_REG_MSB_VERTICAL, 0xc0 }, + { SAA7127_REG_DISABLE_TTX_LINE_LO_0, 0x00 }, + { SAA7127_REG_DISABLE_TTX_LINE_LO_1, 0x00 }, + { 0, 0 } +}; + +#define SAA7127_60HZ_DAC_CONTROL 0x15 +static const struct i2c_reg_value saa7127_init_config_60hz[] = { + { SAA7127_REG_BURST_START, 0x19 }, + /* BURST_END is also used as a chip ID in saa7127_probe */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0xa3 }, + { SAA7127_REG_GAINU, 0x98 }, + { SAA7127_REG_GAINV, 0xd3 }, + { SAA7127_REG_BLACK_LEVEL, 0x39 }, + { SAA7127_REG_BLANKING_LEVEL, 0x2e }, + { SAA7127_REG_VBI_BLANKING, 0x2e }, + { SAA7127_REG_DAC_CONTROL, 0x15 }, + { SAA7127_REG_BURST_AMP, 0x4d }, + { SAA7127_REG_SUBC3, 0x1f }, + { SAA7127_REG_SUBC2, 0x7c }, + { SAA7127_REG_SUBC1, 0xf0 }, + { SAA7127_REG_SUBC0, 0x21 }, + { SAA7127_REG_MULTI, 0x90 }, + { SAA7127_REG_CLOSED_CAPTION, 0x11 }, + { 0, 0 } +}; + +#define SAA7127_50HZ_PAL_DAC_CONTROL 0x02 +static struct i2c_reg_value saa7127_init_config_50hz_pal[] = { + { SAA7127_REG_BURST_START, 0x21 }, + /* BURST_END is also used as a chip ID in saa7127_probe */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0x3f }, + { SAA7127_REG_GAINU, 0x7d }, + { SAA7127_REG_GAINV, 0xaf }, + { SAA7127_REG_BLACK_LEVEL, 0x33 }, + { SAA7127_REG_BLANKING_LEVEL, 0x35 }, + { SAA7127_REG_VBI_BLANKING, 0x35 }, + { SAA7127_REG_DAC_CONTROL, 0x02 }, + { SAA7127_REG_BURST_AMP, 0x2f }, + { SAA7127_REG_SUBC3, 0xcb }, + { SAA7127_REG_SUBC2, 0x8a }, + { SAA7127_REG_SUBC1, 0x09 }, + { SAA7127_REG_SUBC0, 0x2a }, + { SAA7127_REG_MULTI, 0xa0 }, + { SAA7127_REG_CLOSED_CAPTION, 0x00 }, + { 0, 0 } +}; + +#define SAA7127_50HZ_SECAM_DAC_CONTROL 0x08 +static struct i2c_reg_value saa7127_init_config_50hz_secam[] = { + { SAA7127_REG_BURST_START, 0x21 }, + /* BURST_END is also used as a chip ID in saa7127_probe */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0x3f }, + { SAA7127_REG_GAINU, 0x6a }, + { SAA7127_REG_GAINV, 0x81 }, + { SAA7127_REG_BLACK_LEVEL, 0x33 }, + { SAA7127_REG_BLANKING_LEVEL, 0x35 }, + { SAA7127_REG_VBI_BLANKING, 0x35 }, + { SAA7127_REG_DAC_CONTROL, 0x08 }, + { SAA7127_REG_BURST_AMP, 0x2f }, + { SAA7127_REG_SUBC3, 0xb2 }, + { SAA7127_REG_SUBC2, 0x3b }, + { SAA7127_REG_SUBC1, 0xa3 }, + { SAA7127_REG_SUBC0, 0x28 }, + { SAA7127_REG_MULTI, 0x90 }, + { SAA7127_REG_CLOSED_CAPTION, 0x00 }, + { 0, 0 } +}; + +/* + ********************************************************************** + * + * Encoder Struct, holds the configuration state of the encoder + * + ********************************************************************** + */ + +struct saa7127_state { + struct v4l2_subdev sd; + v4l2_std_id std; + u32 ident; + enum saa7127_input_type input_type; + enum saa7127_output_type output_type; + int video_enable; + int wss_enable; + u16 wss_mode; + int cc_enable; + u16 cc_data; + int xds_enable; + u16 xds_data; + int vps_enable; + u8 vps_data[5]; + u8 reg_2d; + u8 reg_3a; + u8 reg_3a_cb; /* colorbar bit */ + u8 reg_61; +}; + +static inline struct saa7127_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa7127_state, sd); +} + +static const char * const output_strs[] = +{ + "S-Video + Composite", + "Composite", + "S-Video", + "RGB", + "YUV C", + "YUV V" +}; + +static const char * const wss_strs[] = { + "invalid", + "letterbox 14:9 center", + "letterbox 14:9 top", + "invalid", + "letterbox 16:9 top", + "invalid", + "invalid", + "16:9 full format anamorphic", + "4:3 full format", + "invalid", + "invalid", + "letterbox 16:9 center", + "invalid", + "letterbox >16:9 center", + "14:9 full format center", + "invalid", +}; + +/* ----------------------------------------------------------------------- */ + +static int saa7127_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i; + + for (i = 0; i < 3; i++) { + if (i2c_smbus_write_byte_data(client, reg, val) == 0) + return 0; + } + v4l2_err(sd, "I2C Write Problem\n"); + return -1; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_write_inittab(struct v4l2_subdev *sd, + const struct i2c_reg_value *regs) +{ + while (regs->reg != 0) { + saa7127_write(sd, regs->reg, regs->value); + regs++; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_vps(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = to_state(sd); + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 16)) + return -EINVAL; + if (state->vps_enable != enable) { + v4l2_dbg(1, debug, sd, "Turn VPS Signal %s\n", enable ? "on" : "off"); + saa7127_write(sd, 0x54, enable << 7); + state->vps_enable = enable; + } + if (!enable) + return 0; + + state->vps_data[0] = data->data[2]; + state->vps_data[1] = data->data[8]; + state->vps_data[2] = data->data[9]; + state->vps_data[3] = data->data[10]; + state->vps_data[4] = data->data[11]; + v4l2_dbg(1, debug, sd, "Set VPS data %*ph\n", 5, state->vps_data); + saa7127_write(sd, 0x55, state->vps_data[0]); + saa7127_write(sd, 0x56, state->vps_data[1]); + saa7127_write(sd, 0x57, state->vps_data[2]); + saa7127_write(sd, 0x58, state->vps_data[3]); + saa7127_write(sd, 0x59, state->vps_data[4]); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_cc(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = to_state(sd); + u16 cc = data->data[1] << 8 | data->data[0]; + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 21)) + return -EINVAL; + if (state->cc_enable != enable) { + v4l2_dbg(1, debug, sd, + "Turn CC %s\n", enable ? "on" : "off"); + saa7127_write(sd, SAA7127_REG_CLOSED_CAPTION, + (state->xds_enable << 7) | (enable << 6) | 0x11); + state->cc_enable = enable; + } + if (!enable) + return 0; + + v4l2_dbg(2, debug, sd, "CC data: %04x\n", cc); + saa7127_write(sd, SAA7127_REG_LINE_21_ODD_0, cc & 0xff); + saa7127_write(sd, SAA7127_REG_LINE_21_ODD_1, cc >> 8); + state->cc_data = cc; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_xds(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = to_state(sd); + u16 xds = data->data[1] << 8 | data->data[0]; + int enable = (data->line != 0); + + if (enable && (data->field != 1 || data->line != 21)) + return -EINVAL; + if (state->xds_enable != enable) { + v4l2_dbg(1, debug, sd, "Turn XDS %s\n", enable ? "on" : "off"); + saa7127_write(sd, SAA7127_REG_CLOSED_CAPTION, + (enable << 7) | (state->cc_enable << 6) | 0x11); + state->xds_enable = enable; + } + if (!enable) + return 0; + + v4l2_dbg(2, debug, sd, "XDS data: %04x\n", xds); + saa7127_write(sd, SAA7127_REG_LINE_21_EVEN_0, xds & 0xff); + saa7127_write(sd, SAA7127_REG_LINE_21_EVEN_1, xds >> 8); + state->xds_data = xds; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_wss(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = to_state(sd); + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 23)) + return -EINVAL; + if (state->wss_enable != enable) { + v4l2_dbg(1, debug, sd, "Turn WSS %s\n", enable ? "on" : "off"); + saa7127_write(sd, 0x27, enable << 7); + state->wss_enable = enable; + } + if (!enable) + return 0; + + saa7127_write(sd, 0x26, data->data[0]); + saa7127_write(sd, 0x27, 0x80 | (data->data[1] & 0x3f)); + v4l2_dbg(1, debug, sd, + "WSS mode: %s\n", wss_strs[data->data[0] & 0xf]); + state->wss_mode = (data->data[1] & 0x3f) << 8 | data->data[0]; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_video_enable(struct v4l2_subdev *sd, int enable) +{ + struct saa7127_state *state = to_state(sd); + + if (enable) { + v4l2_dbg(1, debug, sd, "Enable Video Output\n"); + saa7127_write(sd, 0x2d, state->reg_2d); + saa7127_write(sd, 0x61, state->reg_61); + } else { + v4l2_dbg(1, debug, sd, "Disable Video Output\n"); + saa7127_write(sd, 0x2d, (state->reg_2d & 0xf0)); + saa7127_write(sd, 0x61, (state->reg_61 | 0xc0)); + } + state->video_enable = enable; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa7127_state *state = to_state(sd); + const struct i2c_reg_value *inittab; + + if (std & V4L2_STD_525_60) { + v4l2_dbg(1, debug, sd, "Selecting 60 Hz video Standard\n"); + inittab = saa7127_init_config_60hz; + state->reg_61 = SAA7127_60HZ_DAC_CONTROL; + + } else if (state->ident == V4L2_IDENT_SAA7129 && + (std & V4L2_STD_SECAM) && + !(std & (V4L2_STD_625_50 & ~V4L2_STD_SECAM))) { + + /* If and only if SECAM, with a SAA712[89] */ + v4l2_dbg(1, debug, sd, + "Selecting 50 Hz SECAM video Standard\n"); + inittab = saa7127_init_config_50hz_secam; + state->reg_61 = SAA7127_50HZ_SECAM_DAC_CONTROL; + + } else { + v4l2_dbg(1, debug, sd, "Selecting 50 Hz PAL video Standard\n"); + inittab = saa7127_init_config_50hz_pal; + state->reg_61 = SAA7127_50HZ_PAL_DAC_CONTROL; + } + + /* Write Table */ + saa7127_write_inittab(sd, inittab); + state->std = std; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_output_type(struct v4l2_subdev *sd, int output) +{ + struct saa7127_state *state = to_state(sd); + + switch (output) { + case SAA7127_OUTPUT_TYPE_RGB: + state->reg_2d = 0x0f; /* RGB + CVBS (for sync) */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_COMPOSITE: + if (state->ident == V4L2_IDENT_SAA7129) + state->reg_2d = 0x20; /* CVBS only */ + else + state->reg_2d = 0x08; /* 00001000 CVBS only, RGB DAC's off (high impedance mode) */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_SVIDEO: + if (state->ident == V4L2_IDENT_SAA7129) + state->reg_2d = 0x18; /* Y + C */ + else + state->reg_2d = 0xff; /*11111111 croma -> R, luma -> CVBS + G + B */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_YUV_V: + state->reg_2d = 0x4f; /* reg 2D = 01001111, all DAC's on, RGB + VBS */ + state->reg_3a = 0x0b; /* reg 3A = 00001011, bypass RGB-matrix */ + break; + + case SAA7127_OUTPUT_TYPE_YUV_C: + state->reg_2d = 0x0f; /* reg 2D = 00001111, all DAC's on, RGB + CVBS */ + state->reg_3a = 0x0b; /* reg 3A = 00001011, bypass RGB-matrix */ + break; + + case SAA7127_OUTPUT_TYPE_BOTH: + if (state->ident == V4L2_IDENT_SAA7129) + state->reg_2d = 0x38; + else + state->reg_2d = 0xbf; + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + default: + return -EINVAL; + } + v4l2_dbg(1, debug, sd, + "Selecting %s output type\n", output_strs[output]); + + /* Configure Encoder */ + saa7127_write(sd, 0x2d, state->reg_2d); + saa7127_write(sd, 0x3a, state->reg_3a | state->reg_3a_cb); + state->output_type = output; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_input_type(struct v4l2_subdev *sd, int input) +{ + struct saa7127_state *state = to_state(sd); + + switch (input) { + case SAA7127_INPUT_TYPE_NORMAL: /* avia */ + v4l2_dbg(1, debug, sd, "Selecting Normal Encoder Input\n"); + state->reg_3a_cb = 0; + break; + + case SAA7127_INPUT_TYPE_TEST_IMAGE: /* color bar */ + v4l2_dbg(1, debug, sd, "Selecting Color Bar generator\n"); + state->reg_3a_cb = 0x80; + break; + + default: + return -EINVAL; + } + saa7127_write(sd, 0x3a, state->reg_3a | state->reg_3a_cb); + state->input_type = input; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct saa7127_state *state = to_state(sd); + + if (state->std == std) + return 0; + return saa7127_set_std(sd, std); +} + +static int saa7127_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct saa7127_state *state = to_state(sd); + int rc = 0; + + if (state->input_type != input) + rc = saa7127_set_input_type(sd, input); + if (rc == 0 && state->output_type != output) + rc = saa7127_set_output_type(sd, output); + return rc; +} + +static int saa7127_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct saa7127_state *state = to_state(sd); + + if (state->video_enable == enable) + return 0; + return saa7127_set_video_enable(sd, enable); +} + +static int saa7127_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt) +{ + struct saa7127_state *state = to_state(sd); + + memset(fmt->service_lines, 0, sizeof(fmt->service_lines)); + if (state->vps_enable) + fmt->service_lines[0][16] = V4L2_SLICED_VPS; + if (state->wss_enable) + fmt->service_lines[0][23] = V4L2_SLICED_WSS_625; + if (state->cc_enable) { + fmt->service_lines[0][21] = V4L2_SLICED_CAPTION_525; + fmt->service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } + fmt->service_set = + (state->vps_enable ? V4L2_SLICED_VPS : 0) | + (state->wss_enable ? V4L2_SLICED_WSS_625 : 0) | + (state->cc_enable ? V4L2_SLICED_CAPTION_525 : 0); + return 0; +} + +static int saa7127_s_vbi_data(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *data) +{ + switch (data->id) { + case V4L2_SLICED_WSS_625: + return saa7127_set_wss(sd, data); + case V4L2_SLICED_VPS: + return saa7127_set_vps(sd, data); + case V4L2_SLICED_CAPTION_525: + if (data->field == 0) + return saa7127_set_cc(sd, data); + return saa7127_set_xds(sd, data); + default: + return -EINVAL; + } + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int saa7127_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + reg->val = saa7127_read(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int saa7127_s_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + saa7127_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static int saa7127_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip) +{ + struct saa7127_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return v4l2_chip_ident_i2c_client(client, chip, state->ident, 0); +} + +static int saa7127_log_status(struct v4l2_subdev *sd) +{ + struct saa7127_state *state = to_state(sd); + + v4l2_info(sd, "Standard: %s\n", (state->std & V4L2_STD_525_60) ? "60 Hz" : "50 Hz"); + v4l2_info(sd, "Input: %s\n", state->input_type ? "color bars" : "normal"); + v4l2_info(sd, "Output: %s\n", state->video_enable ? + output_strs[state->output_type] : "disabled"); + v4l2_info(sd, "WSS: %s\n", state->wss_enable ? + wss_strs[state->wss_mode] : "disabled"); + v4l2_info(sd, "VPS: %s\n", state->vps_enable ? "enabled" : "disabled"); + v4l2_info(sd, "CC: %s\n", state->cc_enable ? "enabled" : "disabled"); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops saa7127_core_ops = { + .log_status = saa7127_log_status, + .g_chip_ident = saa7127_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = saa7127_g_register, + .s_register = saa7127_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops saa7127_video_ops = { + .s_std_output = saa7127_s_std_output, + .s_routing = saa7127_s_routing, + .s_stream = saa7127_s_stream, +}; + +static const struct v4l2_subdev_vbi_ops saa7127_vbi_ops = { + .s_vbi_data = saa7127_s_vbi_data, + .g_sliced_fmt = saa7127_g_sliced_fmt, +}; + +static const struct v4l2_subdev_ops saa7127_ops = { + .core = &saa7127_core_ops, + .video = &saa7127_video_ops, + .vbi = &saa7127_vbi_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int saa7127_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct saa7127_state *state; + struct v4l2_subdev *sd; + struct v4l2_sliced_vbi_data vbi = { 0, 0, 0, 0 }; /* set to disabled */ + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_dbg(1, debug, client, "detecting saa7127 client on address 0x%x\n", + client->addr << 1); + + state = kzalloc(sizeof(struct saa7127_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &saa7127_ops); + + /* First test register 0: Bits 5-7 are a version ID (should be 0), + and bit 2 should also be 0. + This is rather general, so the second test is more specific and + looks at the 'ending point of burst in clock cycles' which is + 0x1d after a reset and not expected to ever change. */ + if ((saa7127_read(sd, 0) & 0xe4) != 0 || + (saa7127_read(sd, 0x29) & 0x3f) != 0x1d) { + v4l2_dbg(1, debug, sd, "saa7127 not found\n"); + kfree(state); + return -ENODEV; + } + + if (id->driver_data) { /* Chip type is already known */ + state->ident = id->driver_data; + } else { /* Needs detection */ + int read_result; + + /* Detect if it's an saa7129 */ + read_result = saa7127_read(sd, SAA7129_REG_FADE_KEY_COL2); + saa7127_write(sd, SAA7129_REG_FADE_KEY_COL2, 0xaa); + if (saa7127_read(sd, SAA7129_REG_FADE_KEY_COL2) == 0xaa) { + saa7127_write(sd, SAA7129_REG_FADE_KEY_COL2, + read_result); + state->ident = V4L2_IDENT_SAA7129; + strlcpy(client->name, "saa7129", I2C_NAME_SIZE); + } else { + state->ident = V4L2_IDENT_SAA7127; + strlcpy(client->name, "saa7127", I2C_NAME_SIZE); + } + } + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + v4l2_dbg(1, debug, sd, "Configuring encoder\n"); + saa7127_write_inittab(sd, saa7127_init_config_common); + saa7127_set_std(sd, V4L2_STD_NTSC); + saa7127_set_output_type(sd, SAA7127_OUTPUT_TYPE_BOTH); + saa7127_set_vps(sd, &vbi); + saa7127_set_wss(sd, &vbi); + saa7127_set_cc(sd, &vbi); + saa7127_set_xds(sd, &vbi); + if (test_image == 1) + /* The Encoder has an internal Colorbar generator */ + /* This can be used for debugging */ + saa7127_set_input_type(sd, SAA7127_INPUT_TYPE_TEST_IMAGE); + else + saa7127_set_input_type(sd, SAA7127_INPUT_TYPE_NORMAL); + saa7127_set_video_enable(sd, 1); + + if (state->ident == V4L2_IDENT_SAA7129) + saa7127_write_inittab(sd, saa7129_init_config_extra); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + /* Turn off TV output */ + saa7127_set_video_enable(sd, 0); + kfree(to_state(sd)); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_device_id saa7127_id[] = { + { "saa7127_auto", 0 }, /* auto-detection */ + { "saa7126", V4L2_IDENT_SAA7127 }, + { "saa7127", V4L2_IDENT_SAA7127 }, + { "saa7128", V4L2_IDENT_SAA7129 }, + { "saa7129", V4L2_IDENT_SAA7129 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, saa7127_id); + +static struct i2c_driver saa7127_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "saa7127", + }, + .probe = saa7127_probe, + .remove = saa7127_remove, + .id_table = saa7127_id, +}; + +module_i2c_driver(saa7127_driver); diff --git a/drivers/media/video/saa717x.c b/drivers/media/i2c/saa717x.c similarity index 100% rename from drivers/media/video/saa717x.c rename to drivers/media/i2c/saa717x.c diff --git a/drivers/media/video/saa7185.c b/drivers/media/i2c/saa7185.c similarity index 100% rename from drivers/media/video/saa7185.c rename to drivers/media/i2c/saa7185.c diff --git a/drivers/media/video/saa7191.c b/drivers/media/i2c/saa7191.c similarity index 100% rename from drivers/media/video/saa7191.c rename to drivers/media/i2c/saa7191.c diff --git a/drivers/media/video/saa7191.h b/drivers/media/i2c/saa7191.h similarity index 100% rename from drivers/media/video/saa7191.h rename to drivers/media/i2c/saa7191.h diff --git a/drivers/media/i2c/smiapp-pll.c b/drivers/media/i2c/smiapp-pll.c new file mode 100644 index 000000000000..a577614bd84f --- /dev/null +++ b/drivers/media/i2c/smiapp-pll.c @@ -0,0 +1,418 @@ +/* + * drivers/media/i2c/smiapp-pll.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "smiapp-pll.h" + +/* Return an even number or one. */ +static inline uint32_t clk_div_even(uint32_t a) +{ + return max_t(uint32_t, 1, a & ~1); +} + +/* Return an even number or one. */ +static inline uint32_t clk_div_even_up(uint32_t a) +{ + if (a == 1) + return 1; + return (a + 1) & ~1; +} + +static inline uint32_t is_one_or_even(uint32_t a) +{ + if (a == 1) + return 1; + if (a & 1) + return 0; + + return 1; +} + +static int bounds_check(struct device *dev, uint32_t val, + uint32_t min, uint32_t max, char *str) +{ + if (val >= min && val <= max) + return 0; + + dev_warn(dev, "%s out of bounds: %d (%d--%d)\n", str, val, min, max); + + return -EINVAL; +} + +static void print_pll(struct device *dev, struct smiapp_pll *pll) +{ + dev_dbg(dev, "pre_pll_clk_div\t%d\n", pll->pre_pll_clk_div); + dev_dbg(dev, "pll_multiplier \t%d\n", pll->pll_multiplier); + if (pll->flags != SMIAPP_PLL_FLAG_NO_OP_CLOCKS) { + dev_dbg(dev, "op_sys_clk_div \t%d\n", pll->op_sys_clk_div); + dev_dbg(dev, "op_pix_clk_div \t%d\n", pll->op_pix_clk_div); + } + dev_dbg(dev, "vt_sys_clk_div \t%d\n", pll->vt_sys_clk_div); + dev_dbg(dev, "vt_pix_clk_div \t%d\n", pll->vt_pix_clk_div); + + dev_dbg(dev, "ext_clk_freq_hz \t%d\n", pll->ext_clk_freq_hz); + dev_dbg(dev, "pll_ip_clk_freq_hz \t%d\n", pll->pll_ip_clk_freq_hz); + dev_dbg(dev, "pll_op_clk_freq_hz \t%d\n", pll->pll_op_clk_freq_hz); + if (pll->flags & SMIAPP_PLL_FLAG_NO_OP_CLOCKS) { + dev_dbg(dev, "op_sys_clk_freq_hz \t%d\n", + pll->op_sys_clk_freq_hz); + dev_dbg(dev, "op_pix_clk_freq_hz \t%d\n", + pll->op_pix_clk_freq_hz); + } + dev_dbg(dev, "vt_sys_clk_freq_hz \t%d\n", pll->vt_sys_clk_freq_hz); + dev_dbg(dev, "vt_pix_clk_freq_hz \t%d\n", pll->vt_pix_clk_freq_hz); +} + +int smiapp_pll_calculate(struct device *dev, struct smiapp_pll_limits *limits, + struct smiapp_pll *pll) +{ + uint32_t sys_div; + uint32_t best_pix_div = INT_MAX >> 1; + uint32_t vt_op_binning_div; + uint32_t lane_op_clock_ratio; + uint32_t mul, div; + uint32_t more_mul_min, more_mul_max; + uint32_t more_mul_factor; + uint32_t min_vt_div, max_vt_div, vt_div; + uint32_t min_sys_div, max_sys_div; + unsigned int i; + int rval; + + if (pll->flags & SMIAPP_PLL_FLAG_OP_PIX_CLOCK_PER_LANE) + lane_op_clock_ratio = pll->lanes; + else + lane_op_clock_ratio = 1; + dev_dbg(dev, "lane_op_clock_ratio: %d\n", lane_op_clock_ratio); + + dev_dbg(dev, "binning: %dx%d\n", pll->binning_horizontal, + pll->binning_vertical); + + /* CSI transfers 2 bits per clock per lane; thus times 2 */ + pll->pll_op_clk_freq_hz = pll->link_freq * 2 + * (pll->lanes / lane_op_clock_ratio); + + /* Figure out limits for pre-pll divider based on extclk */ + dev_dbg(dev, "min / max pre_pll_clk_div: %d / %d\n", + limits->min_pre_pll_clk_div, limits->max_pre_pll_clk_div); + limits->max_pre_pll_clk_div = + min_t(uint16_t, limits->max_pre_pll_clk_div, + clk_div_even(pll->ext_clk_freq_hz / + limits->min_pll_ip_freq_hz)); + limits->min_pre_pll_clk_div = + max_t(uint16_t, limits->min_pre_pll_clk_div, + clk_div_even_up( + DIV_ROUND_UP(pll->ext_clk_freq_hz, + limits->max_pll_ip_freq_hz))); + dev_dbg(dev, "pre-pll check: min / max pre_pll_clk_div: %d / %d\n", + limits->min_pre_pll_clk_div, limits->max_pre_pll_clk_div); + + i = gcd(pll->pll_op_clk_freq_hz, pll->ext_clk_freq_hz); + mul = div_u64(pll->pll_op_clk_freq_hz, i); + div = pll->ext_clk_freq_hz / i; + dev_dbg(dev, "mul %d / div %d\n", mul, div); + + limits->min_pre_pll_clk_div = + max_t(uint16_t, limits->min_pre_pll_clk_div, + clk_div_even_up( + DIV_ROUND_UP(mul * pll->ext_clk_freq_hz, + limits->max_pll_op_freq_hz))); + dev_dbg(dev, "pll_op check: min / max pre_pll_clk_div: %d / %d\n", + limits->min_pre_pll_clk_div, limits->max_pre_pll_clk_div); + + if (limits->min_pre_pll_clk_div > limits->max_pre_pll_clk_div) { + dev_err(dev, "unable to compute pre_pll divisor\n"); + return -EINVAL; + } + + pll->pre_pll_clk_div = limits->min_pre_pll_clk_div; + + /* + * Get pre_pll_clk_div so that our pll_op_clk_freq_hz won't be + * too high. + */ + dev_dbg(dev, "pre_pll_clk_div %d\n", pll->pre_pll_clk_div); + + /* Don't go above max pll multiplier. */ + more_mul_max = limits->max_pll_multiplier / mul; + dev_dbg(dev, "more_mul_max: max_pll_multiplier check: %d\n", + more_mul_max); + /* Don't go above max pll op frequency. */ + more_mul_max = + min_t(int, + more_mul_max, + limits->max_pll_op_freq_hz + / (pll->ext_clk_freq_hz / pll->pre_pll_clk_div * mul)); + dev_dbg(dev, "more_mul_max: max_pll_op_freq_hz check: %d\n", + more_mul_max); + /* Don't go above the division capability of op sys clock divider. */ + more_mul_max = min(more_mul_max, + limits->max_op_sys_clk_div * pll->pre_pll_clk_div + / div); + dev_dbg(dev, "more_mul_max: max_op_sys_clk_div check: %d\n", + more_mul_max); + /* Ensure we won't go above min_pll_multiplier. */ + more_mul_max = min(more_mul_max, + DIV_ROUND_UP(limits->max_pll_multiplier, mul)); + dev_dbg(dev, "more_mul_max: min_pll_multiplier check: %d\n", + more_mul_max); + + /* Ensure we won't go below min_pll_op_freq_hz. */ + more_mul_min = DIV_ROUND_UP(limits->min_pll_op_freq_hz, + pll->ext_clk_freq_hz / pll->pre_pll_clk_div + * mul); + dev_dbg(dev, "more_mul_min: min_pll_op_freq_hz check: %d\n", + more_mul_min); + /* Ensure we won't go below min_pll_multiplier. */ + more_mul_min = max(more_mul_min, + DIV_ROUND_UP(limits->min_pll_multiplier, mul)); + dev_dbg(dev, "more_mul_min: min_pll_multiplier check: %d\n", + more_mul_min); + + if (more_mul_min > more_mul_max) { + dev_warn(dev, + "unable to compute more_mul_min and more_mul_max"); + return -EINVAL; + } + + more_mul_factor = lcm(div, pll->pre_pll_clk_div) / div; + dev_dbg(dev, "more_mul_factor: %d\n", more_mul_factor); + more_mul_factor = lcm(more_mul_factor, limits->min_op_sys_clk_div); + dev_dbg(dev, "more_mul_factor: min_op_sys_clk_div: %d\n", + more_mul_factor); + i = roundup(more_mul_min, more_mul_factor); + if (!is_one_or_even(i)) + i <<= 1; + + dev_dbg(dev, "final more_mul: %d\n", i); + if (i > more_mul_max) { + dev_warn(dev, "final more_mul is bad, max %d", more_mul_max); + return -EINVAL; + } + + pll->pll_multiplier = mul * i; + pll->op_sys_clk_div = div * i / pll->pre_pll_clk_div; + dev_dbg(dev, "op_sys_clk_div: %d\n", pll->op_sys_clk_div); + + pll->pll_ip_clk_freq_hz = pll->ext_clk_freq_hz + / pll->pre_pll_clk_div; + + pll->pll_op_clk_freq_hz = pll->pll_ip_clk_freq_hz + * pll->pll_multiplier; + + /* Derive pll_op_clk_freq_hz. */ + pll->op_sys_clk_freq_hz = + pll->pll_op_clk_freq_hz / pll->op_sys_clk_div; + + pll->op_pix_clk_div = pll->bits_per_pixel; + dev_dbg(dev, "op_pix_clk_div: %d\n", pll->op_pix_clk_div); + + pll->op_pix_clk_freq_hz = + pll->op_sys_clk_freq_hz / pll->op_pix_clk_div; + + /* + * Some sensors perform analogue binning and some do this + * digitally. The ones doing this digitally can be roughly be + * found out using this formula. The ones doing this digitally + * should run at higher clock rate, so smaller divisor is used + * on video timing side. + */ + if (limits->min_line_length_pck_bin > limits->min_line_length_pck + / pll->binning_horizontal) + vt_op_binning_div = pll->binning_horizontal; + else + vt_op_binning_div = 1; + dev_dbg(dev, "vt_op_binning_div: %d\n", vt_op_binning_div); + + /* + * Profile 2 supports vt_pix_clk_div E [4, 10] + * + * Horizontal binning can be used as a base for difference in + * divisors. One must make sure that horizontal blanking is + * enough to accommodate the CSI-2 sync codes. + * + * Take scaling factor into account as well. + * + * Find absolute limits for the factor of vt divider. + */ + dev_dbg(dev, "scale_m: %d\n", pll->scale_m); + min_vt_div = DIV_ROUND_UP(pll->op_pix_clk_div * pll->op_sys_clk_div + * pll->scale_n, + lane_op_clock_ratio * vt_op_binning_div + * pll->scale_m); + + /* Find smallest and biggest allowed vt divisor. */ + dev_dbg(dev, "min_vt_div: %d\n", min_vt_div); + min_vt_div = max(min_vt_div, + DIV_ROUND_UP(pll->pll_op_clk_freq_hz, + limits->max_vt_pix_clk_freq_hz)); + dev_dbg(dev, "min_vt_div: max_vt_pix_clk_freq_hz: %d\n", + min_vt_div); + min_vt_div = max_t(uint32_t, min_vt_div, + limits->min_vt_pix_clk_div + * limits->min_vt_sys_clk_div); + dev_dbg(dev, "min_vt_div: min_vt_clk_div: %d\n", min_vt_div); + + max_vt_div = limits->max_vt_sys_clk_div * limits->max_vt_pix_clk_div; + dev_dbg(dev, "max_vt_div: %d\n", max_vt_div); + max_vt_div = min(max_vt_div, + DIV_ROUND_UP(pll->pll_op_clk_freq_hz, + limits->min_vt_pix_clk_freq_hz)); + dev_dbg(dev, "max_vt_div: min_vt_pix_clk_freq_hz: %d\n", + max_vt_div); + + /* + * Find limitsits for sys_clk_div. Not all values are possible + * with all values of pix_clk_div. + */ + min_sys_div = limits->min_vt_sys_clk_div; + dev_dbg(dev, "min_sys_div: %d\n", min_sys_div); + min_sys_div = max(min_sys_div, + DIV_ROUND_UP(min_vt_div, + limits->max_vt_pix_clk_div)); + dev_dbg(dev, "min_sys_div: max_vt_pix_clk_div: %d\n", min_sys_div); + min_sys_div = max(min_sys_div, + pll->pll_op_clk_freq_hz + / limits->max_vt_sys_clk_freq_hz); + dev_dbg(dev, "min_sys_div: max_pll_op_clk_freq_hz: %d\n", min_sys_div); + min_sys_div = clk_div_even_up(min_sys_div); + dev_dbg(dev, "min_sys_div: one or even: %d\n", min_sys_div); + + max_sys_div = limits->max_vt_sys_clk_div; + dev_dbg(dev, "max_sys_div: %d\n", max_sys_div); + max_sys_div = min(max_sys_div, + DIV_ROUND_UP(max_vt_div, + limits->min_vt_pix_clk_div)); + dev_dbg(dev, "max_sys_div: min_vt_pix_clk_div: %d\n", max_sys_div); + max_sys_div = min(max_sys_div, + DIV_ROUND_UP(pll->pll_op_clk_freq_hz, + limits->min_vt_pix_clk_freq_hz)); + dev_dbg(dev, "max_sys_div: min_vt_pix_clk_freq_hz: %d\n", max_sys_div); + + /* + * Find pix_div such that a legal pix_div * sys_div results + * into a value which is not smaller than div, the desired + * divisor. + */ + for (vt_div = min_vt_div; vt_div <= max_vt_div; + vt_div += 2 - (vt_div & 1)) { + for (sys_div = min_sys_div; + sys_div <= max_sys_div; + sys_div += 2 - (sys_div & 1)) { + int pix_div = DIV_ROUND_UP(vt_div, sys_div); + + if (pix_div < limits->min_vt_pix_clk_div + || pix_div > limits->max_vt_pix_clk_div) { + dev_dbg(dev, + "pix_div %d too small or too big (%d--%d)\n", + pix_div, + limits->min_vt_pix_clk_div, + limits->max_vt_pix_clk_div); + continue; + } + + /* Check if this one is better. */ + if (pix_div * sys_div + <= roundup(min_vt_div, best_pix_div)) + best_pix_div = pix_div; + } + if (best_pix_div < INT_MAX >> 1) + break; + } + + pll->vt_sys_clk_div = DIV_ROUND_UP(min_vt_div, best_pix_div); + pll->vt_pix_clk_div = best_pix_div; + + pll->vt_sys_clk_freq_hz = + pll->pll_op_clk_freq_hz / pll->vt_sys_clk_div; + pll->vt_pix_clk_freq_hz = + pll->vt_sys_clk_freq_hz / pll->vt_pix_clk_div; + + pll->pixel_rate_csi = + pll->op_pix_clk_freq_hz * lane_op_clock_ratio; + + print_pll(dev, pll); + + rval = bounds_check(dev, pll->pre_pll_clk_div, + limits->min_pre_pll_clk_div, + limits->max_pre_pll_clk_div, "pre_pll_clk_div"); + if (!rval) + rval = bounds_check( + dev, pll->pll_ip_clk_freq_hz, + limits->min_pll_ip_freq_hz, limits->max_pll_ip_freq_hz, + "pll_ip_clk_freq_hz"); + if (!rval) + rval = bounds_check( + dev, pll->pll_multiplier, + limits->min_pll_multiplier, limits->max_pll_multiplier, + "pll_multiplier"); + if (!rval) + rval = bounds_check( + dev, pll->pll_op_clk_freq_hz, + limits->min_pll_op_freq_hz, limits->max_pll_op_freq_hz, + "pll_op_clk_freq_hz"); + if (!rval) + rval = bounds_check( + dev, pll->op_sys_clk_div, + limits->min_op_sys_clk_div, limits->max_op_sys_clk_div, + "op_sys_clk_div"); + if (!rval) + rval = bounds_check( + dev, pll->op_pix_clk_div, + limits->min_op_pix_clk_div, limits->max_op_pix_clk_div, + "op_pix_clk_div"); + if (!rval) + rval = bounds_check( + dev, pll->op_sys_clk_freq_hz, + limits->min_op_sys_clk_freq_hz, + limits->max_op_sys_clk_freq_hz, + "op_sys_clk_freq_hz"); + if (!rval) + rval = bounds_check( + dev, pll->op_pix_clk_freq_hz, + limits->min_op_pix_clk_freq_hz, + limits->max_op_pix_clk_freq_hz, + "op_pix_clk_freq_hz"); + if (!rval) + rval = bounds_check( + dev, pll->vt_sys_clk_freq_hz, + limits->min_vt_sys_clk_freq_hz, + limits->max_vt_sys_clk_freq_hz, + "vt_sys_clk_freq_hz"); + if (!rval) + rval = bounds_check( + dev, pll->vt_pix_clk_freq_hz, + limits->min_vt_pix_clk_freq_hz, + limits->max_vt_pix_clk_freq_hz, + "vt_pix_clk_freq_hz"); + + return rval; +} +EXPORT_SYMBOL_GPL(smiapp_pll_calculate); + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_DESCRIPTION("Generic SMIA/SMIA++ PLL calculator"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/smiapp-pll.h b/drivers/media/i2c/smiapp-pll.h new file mode 100644 index 000000000000..cb2d2db5d02d --- /dev/null +++ b/drivers/media/i2c/smiapp-pll.h @@ -0,0 +1,103 @@ +/* + * drivers/media/i2c/smiapp-pll.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef SMIAPP_PLL_H +#define SMIAPP_PLL_H + +#include + +struct smiapp_pll { + uint8_t lanes; + uint8_t binning_horizontal; + uint8_t binning_vertical; + uint8_t scale_m; + uint8_t scale_n; + uint8_t bits_per_pixel; + uint16_t flags; + uint32_t link_freq; + + uint16_t pre_pll_clk_div; + uint16_t pll_multiplier; + uint16_t op_sys_clk_div; + uint16_t op_pix_clk_div; + uint16_t vt_sys_clk_div; + uint16_t vt_pix_clk_div; + + uint32_t ext_clk_freq_hz; + uint32_t pll_ip_clk_freq_hz; + uint32_t pll_op_clk_freq_hz; + uint32_t op_sys_clk_freq_hz; + uint32_t op_pix_clk_freq_hz; + uint32_t vt_sys_clk_freq_hz; + uint32_t vt_pix_clk_freq_hz; + + uint32_t pixel_rate_csi; +}; + +struct smiapp_pll_limits { + /* Strict PLL limits */ + uint32_t min_ext_clk_freq_hz; + uint32_t max_ext_clk_freq_hz; + uint16_t min_pre_pll_clk_div; + uint16_t max_pre_pll_clk_div; + uint32_t min_pll_ip_freq_hz; + uint32_t max_pll_ip_freq_hz; + uint16_t min_pll_multiplier; + uint16_t max_pll_multiplier; + uint32_t min_pll_op_freq_hz; + uint32_t max_pll_op_freq_hz; + + uint16_t min_vt_sys_clk_div; + uint16_t max_vt_sys_clk_div; + uint32_t min_vt_sys_clk_freq_hz; + uint32_t max_vt_sys_clk_freq_hz; + uint16_t min_vt_pix_clk_div; + uint16_t max_vt_pix_clk_div; + uint32_t min_vt_pix_clk_freq_hz; + uint32_t max_vt_pix_clk_freq_hz; + + uint16_t min_op_sys_clk_div; + uint16_t max_op_sys_clk_div; + uint32_t min_op_sys_clk_freq_hz; + uint32_t max_op_sys_clk_freq_hz; + uint16_t min_op_pix_clk_div; + uint16_t max_op_pix_clk_div; + uint32_t min_op_pix_clk_freq_hz; + uint32_t max_op_pix_clk_freq_hz; + + /* Other relevant limits */ + uint32_t min_line_length_pck_bin; + uint32_t min_line_length_pck; +}; + +/* op pix clock is for all lanes in total normally */ +#define SMIAPP_PLL_FLAG_OP_PIX_CLOCK_PER_LANE (1 << 0) +#define SMIAPP_PLL_FLAG_NO_OP_CLOCKS (1 << 1) + +struct device; + +int smiapp_pll_calculate(struct device *dev, struct smiapp_pll_limits *limits, + struct smiapp_pll *pll); + +#endif /* SMIAPP_PLL_H */ diff --git a/drivers/media/video/smiapp/Kconfig b/drivers/media/i2c/smiapp/Kconfig similarity index 100% rename from drivers/media/video/smiapp/Kconfig rename to drivers/media/i2c/smiapp/Kconfig diff --git a/drivers/media/i2c/smiapp/Makefile b/drivers/media/i2c/smiapp/Makefile new file mode 100644 index 000000000000..f45a003cbe7e --- /dev/null +++ b/drivers/media/i2c/smiapp/Makefile @@ -0,0 +1,5 @@ +smiapp-objs += smiapp-core.o smiapp-regs.o \ + smiapp-quirk.o smiapp-limits.o +obj-$(CONFIG_VIDEO_SMIAPP) += smiapp.o + +ccflags-y += -Idrivers/media/i2c diff --git a/drivers/media/i2c/smiapp/smiapp-core.c b/drivers/media/i2c/smiapp/smiapp-core.c new file mode 100644 index 000000000000..e08e588ad24b --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-core.c @@ -0,0 +1,2898 @@ +/* + * drivers/media/i2c/smiapp/smiapp-core.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2010--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * Based on smiapp driver by Vimarsh Zutshi + * Based on jt8ev1.c by Vimarsh Zutshi + * Based on smia-sensor.c by Tuukka Toivonen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smiapp.h" + +#define SMIAPP_ALIGN_DIM(dim, flags) \ + ((flags) & V4L2_SEL_FLAG_GE \ + ? ALIGN((dim), 2) \ + : (dim) & ~1) + +/* + * smiapp_module_idents - supported camera modules + */ +static const struct smiapp_module_ident smiapp_module_idents[] = { + SMIAPP_IDENT_L(0x01, 0x022b, -1, "vs6555"), + SMIAPP_IDENT_L(0x01, 0x022e, -1, "vw6558"), + SMIAPP_IDENT_L(0x07, 0x7698, -1, "ovm7698"), + SMIAPP_IDENT_L(0x0b, 0x4242, -1, "smiapp-003"), + SMIAPP_IDENT_L(0x0c, 0x208a, -1, "tcm8330md"), + SMIAPP_IDENT_LQ(0x0c, 0x2134, -1, "tcm8500md", &smiapp_tcm8500md_quirk), + SMIAPP_IDENT_L(0x0c, 0x213e, -1, "et8en2"), + SMIAPP_IDENT_L(0x0c, 0x2184, -1, "tcm8580md"), + SMIAPP_IDENT_LQ(0x0c, 0x560f, -1, "jt8ew9", &smiapp_jt8ew9_quirk), + SMIAPP_IDENT_LQ(0x10, 0x4141, -1, "jt8ev1", &smiapp_jt8ev1_quirk), + SMIAPP_IDENT_LQ(0x10, 0x4241, -1, "imx125es", &smiapp_imx125es_quirk), +}; + +/* + * + * Dynamic Capability Identification + * + */ + +static int smiapp_read_frame_fmt(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + u32 fmt_model_type, fmt_model_subtype, ncol_desc, nrow_desc; + unsigned int i; + int rval; + int line_count = 0; + int embedded_start = -1, embedded_end = -1; + int image_start = 0; + + rval = smiapp_read(sensor, SMIAPP_REG_U8_FRAME_FORMAT_MODEL_TYPE, + &fmt_model_type); + if (rval) + return rval; + + rval = smiapp_read(sensor, SMIAPP_REG_U8_FRAME_FORMAT_MODEL_SUBTYPE, + &fmt_model_subtype); + if (rval) + return rval; + + ncol_desc = (fmt_model_subtype + & SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_MASK) + >> SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_SHIFT; + nrow_desc = fmt_model_subtype + & SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NROWS_MASK; + + dev_dbg(&client->dev, "format_model_type %s\n", + fmt_model_type == SMIAPP_FRAME_FORMAT_MODEL_TYPE_2BYTE + ? "2 byte" : + fmt_model_type == SMIAPP_FRAME_FORMAT_MODEL_TYPE_4BYTE + ? "4 byte" : "is simply bad"); + + for (i = 0; i < ncol_desc + nrow_desc; i++) { + u32 desc; + u32 pixelcode; + u32 pixels; + char *which; + char *what; + + if (fmt_model_type == SMIAPP_FRAME_FORMAT_MODEL_TYPE_2BYTE) { + rval = smiapp_read( + sensor, + SMIAPP_REG_U16_FRAME_FORMAT_DESCRIPTOR_2(i), + &desc); + if (rval) + return rval; + + pixelcode = + (desc + & SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_MASK) + >> SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_SHIFT; + pixels = desc & SMIAPP_FRAME_FORMAT_DESC_2_PIXELS_MASK; + } else if (fmt_model_type + == SMIAPP_FRAME_FORMAT_MODEL_TYPE_4BYTE) { + rval = smiapp_read( + sensor, + SMIAPP_REG_U32_FRAME_FORMAT_DESCRIPTOR_4(i), + &desc); + if (rval) + return rval; + + pixelcode = + (desc + & SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_MASK) + >> SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_SHIFT; + pixels = desc & SMIAPP_FRAME_FORMAT_DESC_4_PIXELS_MASK; + } else { + dev_dbg(&client->dev, + "invalid frame format model type %d\n", + fmt_model_type); + return -EINVAL; + } + + if (i < ncol_desc) + which = "columns"; + else + which = "rows"; + + switch (pixelcode) { + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_EMBEDDED: + what = "embedded"; + break; + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DUMMY: + what = "dummy"; + break; + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_BLACK: + what = "black"; + break; + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DARK: + what = "dark"; + break; + case SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_VISIBLE: + what = "visible"; + break; + default: + what = "invalid"; + dev_dbg(&client->dev, "pixelcode %d\n", pixelcode); + break; + } + + dev_dbg(&client->dev, "%s pixels: %d %s\n", + what, pixels, which); + + if (i < ncol_desc) + continue; + + /* Handle row descriptors */ + if (pixelcode + == SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_EMBEDDED) { + embedded_start = line_count; + } else { + if (pixelcode == SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_VISIBLE + || pixels >= sensor->limits[SMIAPP_LIMIT_MIN_FRAME_LENGTH_LINES] / 2) + image_start = line_count; + if (embedded_start != -1 && embedded_end == -1) + embedded_end = line_count; + } + line_count += pixels; + } + + if (embedded_start == -1 || embedded_end == -1) { + embedded_start = 0; + embedded_end = 0; + } + + dev_dbg(&client->dev, "embedded data from lines %d to %d\n", + embedded_start, embedded_end); + dev_dbg(&client->dev, "image data starts at line %d\n", image_start); + + return 0; +} + +static int smiapp_pll_configure(struct smiapp_sensor *sensor) +{ + struct smiapp_pll *pll = &sensor->pll; + int rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_VT_PIX_CLK_DIV, pll->vt_pix_clk_div); + if (rval < 0) + return rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_VT_SYS_CLK_DIV, pll->vt_sys_clk_div); + if (rval < 0) + return rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_PRE_PLL_CLK_DIV, pll->pre_pll_clk_div); + if (rval < 0) + return rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_PLL_MULTIPLIER, pll->pll_multiplier); + if (rval < 0) + return rval; + + /* Lane op clock ratio does not apply here. */ + rval = smiapp_write( + sensor, SMIAPP_REG_U32_REQUESTED_LINK_BIT_RATE_MBPS, + DIV_ROUND_UP(pll->op_sys_clk_freq_hz, 1000000 / 256 / 256)); + if (rval < 0 || sensor->minfo.smiapp_profile == SMIAPP_PROFILE_0) + return rval; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_OP_PIX_CLK_DIV, pll->op_pix_clk_div); + if (rval < 0) + return rval; + + return smiapp_write( + sensor, SMIAPP_REG_U16_OP_SYS_CLK_DIV, pll->op_sys_clk_div); +} + +static int smiapp_pll_update(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct smiapp_pll_limits lim = { + .min_pre_pll_clk_div = sensor->limits[SMIAPP_LIMIT_MIN_PRE_PLL_CLK_DIV], + .max_pre_pll_clk_div = sensor->limits[SMIAPP_LIMIT_MAX_PRE_PLL_CLK_DIV], + .min_pll_ip_freq_hz = sensor->limits[SMIAPP_LIMIT_MIN_PLL_IP_FREQ_HZ], + .max_pll_ip_freq_hz = sensor->limits[SMIAPP_LIMIT_MAX_PLL_IP_FREQ_HZ], + .min_pll_multiplier = sensor->limits[SMIAPP_LIMIT_MIN_PLL_MULTIPLIER], + .max_pll_multiplier = sensor->limits[SMIAPP_LIMIT_MAX_PLL_MULTIPLIER], + .min_pll_op_freq_hz = sensor->limits[SMIAPP_LIMIT_MIN_PLL_OP_FREQ_HZ], + .max_pll_op_freq_hz = sensor->limits[SMIAPP_LIMIT_MAX_PLL_OP_FREQ_HZ], + + .min_op_sys_clk_div = sensor->limits[SMIAPP_LIMIT_MIN_OP_SYS_CLK_DIV], + .max_op_sys_clk_div = sensor->limits[SMIAPP_LIMIT_MAX_OP_SYS_CLK_DIV], + .min_op_pix_clk_div = sensor->limits[SMIAPP_LIMIT_MIN_OP_PIX_CLK_DIV], + .max_op_pix_clk_div = sensor->limits[SMIAPP_LIMIT_MAX_OP_PIX_CLK_DIV], + .min_op_sys_clk_freq_hz = sensor->limits[SMIAPP_LIMIT_MIN_OP_SYS_CLK_FREQ_HZ], + .max_op_sys_clk_freq_hz = sensor->limits[SMIAPP_LIMIT_MAX_OP_SYS_CLK_FREQ_HZ], + .min_op_pix_clk_freq_hz = sensor->limits[SMIAPP_LIMIT_MIN_OP_PIX_CLK_FREQ_HZ], + .max_op_pix_clk_freq_hz = sensor->limits[SMIAPP_LIMIT_MAX_OP_PIX_CLK_FREQ_HZ], + + .min_vt_sys_clk_div = sensor->limits[SMIAPP_LIMIT_MIN_VT_SYS_CLK_DIV], + .max_vt_sys_clk_div = sensor->limits[SMIAPP_LIMIT_MAX_VT_SYS_CLK_DIV], + .min_vt_pix_clk_div = sensor->limits[SMIAPP_LIMIT_MIN_VT_PIX_CLK_DIV], + .max_vt_pix_clk_div = sensor->limits[SMIAPP_LIMIT_MAX_VT_PIX_CLK_DIV], + .min_vt_sys_clk_freq_hz = sensor->limits[SMIAPP_LIMIT_MIN_VT_SYS_CLK_FREQ_HZ], + .max_vt_sys_clk_freq_hz = sensor->limits[SMIAPP_LIMIT_MAX_VT_SYS_CLK_FREQ_HZ], + .min_vt_pix_clk_freq_hz = sensor->limits[SMIAPP_LIMIT_MIN_VT_PIX_CLK_FREQ_HZ], + .max_vt_pix_clk_freq_hz = sensor->limits[SMIAPP_LIMIT_MAX_VT_PIX_CLK_FREQ_HZ], + + .min_line_length_pck_bin = sensor->limits[SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK_BIN], + .min_line_length_pck = sensor->limits[SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK], + }; + struct smiapp_pll *pll = &sensor->pll; + int rval; + + memset(&sensor->pll, 0, sizeof(sensor->pll)); + + pll->lanes = sensor->platform_data->lanes; + pll->ext_clk_freq_hz = sensor->platform_data->ext_clk; + + if (sensor->minfo.smiapp_profile == SMIAPP_PROFILE_0) { + /* + * Fill in operational clock divisors limits from the + * video timing ones. On profile 0 sensors the + * requirements regarding them are essentially the + * same as on VT ones. + */ + lim.min_op_sys_clk_div = lim.min_vt_sys_clk_div; + lim.max_op_sys_clk_div = lim.max_vt_sys_clk_div; + lim.min_op_pix_clk_div = lim.min_vt_pix_clk_div; + lim.max_op_pix_clk_div = lim.max_vt_pix_clk_div; + lim.min_op_sys_clk_freq_hz = lim.min_vt_sys_clk_freq_hz; + lim.max_op_sys_clk_freq_hz = lim.max_vt_sys_clk_freq_hz; + lim.min_op_pix_clk_freq_hz = lim.min_vt_pix_clk_freq_hz; + lim.max_op_pix_clk_freq_hz = lim.max_vt_pix_clk_freq_hz; + /* Profile 0 sensors have no separate OP clock branch. */ + pll->flags |= SMIAPP_PLL_FLAG_NO_OP_CLOCKS; + } + + if (smiapp_needs_quirk(sensor, + SMIAPP_QUIRK_FLAG_OP_PIX_CLOCK_PER_LANE)) + pll->flags |= SMIAPP_PLL_FLAG_OP_PIX_CLOCK_PER_LANE; + + pll->binning_horizontal = sensor->binning_horizontal; + pll->binning_vertical = sensor->binning_vertical; + pll->link_freq = + sensor->link_freq->qmenu_int[sensor->link_freq->val]; + pll->scale_m = sensor->scale_m; + pll->scale_n = sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN]; + pll->bits_per_pixel = sensor->csi_format->compressed; + + rval = smiapp_pll_calculate(&client->dev, &lim, pll); + if (rval < 0) + return rval; + + sensor->pixel_rate_parray->cur.val64 = pll->vt_pix_clk_freq_hz; + sensor->pixel_rate_csi->cur.val64 = pll->pixel_rate_csi; + + return 0; +} + + +/* + * + * V4L2 Controls handling + * + */ + +static void __smiapp_update_exposure_limits(struct smiapp_sensor *sensor) +{ + struct v4l2_ctrl *ctrl = sensor->exposure; + int max; + + max = sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height + + sensor->vblank->val + - sensor->limits[SMIAPP_LIMIT_COARSE_INTEGRATION_TIME_MAX_MARGIN]; + + ctrl->maximum = max; + if (ctrl->default_value > max) + ctrl->default_value = max; + if (ctrl->val > max) + ctrl->val = max; + if (ctrl->cur.val > max) + ctrl->cur.val = max; +} + +/* + * Order matters. + * + * 1. Bits-per-pixel, descending. + * 2. Bits-per-pixel compressed, descending. + * 3. Pixel order, same as in pixel_order_str. Formats for all four pixel + * orders must be defined. + */ +static const struct smiapp_csi_data_format smiapp_csi_data_formats[] = { + { V4L2_MBUS_FMT_SGRBG12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_GRBG, }, + { V4L2_MBUS_FMT_SRGGB12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_RGGB, }, + { V4L2_MBUS_FMT_SBGGR12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_BGGR, }, + { V4L2_MBUS_FMT_SGBRG12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_GBRG, }, + { V4L2_MBUS_FMT_SGRBG10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_GRBG, }, + { V4L2_MBUS_FMT_SRGGB10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_RGGB, }, + { V4L2_MBUS_FMT_SBGGR10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_BGGR, }, + { V4L2_MBUS_FMT_SGBRG10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_GBRG, }, + { V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_GRBG, }, + { V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_RGGB, }, + { V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_BGGR, }, + { V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_GBRG, }, + { V4L2_MBUS_FMT_SGRBG8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_GRBG, }, + { V4L2_MBUS_FMT_SRGGB8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_RGGB, }, + { V4L2_MBUS_FMT_SBGGR8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_BGGR, }, + { V4L2_MBUS_FMT_SGBRG8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_GBRG, }, +}; + +const char *pixel_order_str[] = { "GRBG", "RGGB", "BGGR", "GBRG" }; + +#define to_csi_format_idx(fmt) (((unsigned long)(fmt) \ + - (unsigned long)smiapp_csi_data_formats) \ + / sizeof(*smiapp_csi_data_formats)) + +static u32 smiapp_pixel_order(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int flip = 0; + + if (sensor->hflip) { + if (sensor->hflip->val) + flip |= SMIAPP_IMAGE_ORIENTATION_HFLIP; + + if (sensor->vflip->val) + flip |= SMIAPP_IMAGE_ORIENTATION_VFLIP; + } + + flip ^= sensor->hvflip_inv_mask; + + dev_dbg(&client->dev, "flip %d\n", flip); + return sensor->default_pixel_order ^ flip; +} + +static void smiapp_update_mbus_formats(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int csi_format_idx = + to_csi_format_idx(sensor->csi_format) & ~3; + unsigned int internal_csi_format_idx = + to_csi_format_idx(sensor->internal_csi_format) & ~3; + unsigned int pixel_order = smiapp_pixel_order(sensor); + + sensor->mbus_frame_fmts = + sensor->default_mbus_frame_fmts << pixel_order; + sensor->csi_format = + &smiapp_csi_data_formats[csi_format_idx + pixel_order]; + sensor->internal_csi_format = + &smiapp_csi_data_formats[internal_csi_format_idx + + pixel_order]; + + BUG_ON(max(internal_csi_format_idx, csi_format_idx) + pixel_order + >= ARRAY_SIZE(smiapp_csi_data_formats)); + BUG_ON(min(internal_csi_format_idx, csi_format_idx) < 0); + + dev_dbg(&client->dev, "new pixel order %s\n", + pixel_order_str[pixel_order]); +} + +static int smiapp_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct smiapp_sensor *sensor = + container_of(ctrl->handler, struct smiapp_subdev, ctrl_handler) + ->sensor; + u32 orient = 0; + int exposure; + int rval; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + return smiapp_write( + sensor, + SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GLOBAL, ctrl->val); + + case V4L2_CID_EXPOSURE: + return smiapp_write( + sensor, + SMIAPP_REG_U16_COARSE_INTEGRATION_TIME, ctrl->val); + + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + if (sensor->streaming) + return -EBUSY; + + if (sensor->hflip->val) + orient |= SMIAPP_IMAGE_ORIENTATION_HFLIP; + + if (sensor->vflip->val) + orient |= SMIAPP_IMAGE_ORIENTATION_VFLIP; + + orient ^= sensor->hvflip_inv_mask; + rval = smiapp_write(sensor, + SMIAPP_REG_U8_IMAGE_ORIENTATION, + orient); + if (rval < 0) + return rval; + + smiapp_update_mbus_formats(sensor); + + return 0; + + case V4L2_CID_VBLANK: + exposure = sensor->exposure->val; + + __smiapp_update_exposure_limits(sensor); + + if (exposure > sensor->exposure->maximum) { + sensor->exposure->val = + sensor->exposure->maximum; + rval = smiapp_set_ctrl( + sensor->exposure); + if (rval < 0) + return rval; + } + + return smiapp_write( + sensor, SMIAPP_REG_U16_FRAME_LENGTH_LINES, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height + + ctrl->val); + + case V4L2_CID_HBLANK: + return smiapp_write( + sensor, SMIAPP_REG_U16_LINE_LENGTH_PCK, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width + + ctrl->val); + + case V4L2_CID_LINK_FREQ: + if (sensor->streaming) + return -EBUSY; + + return smiapp_pll_update(sensor); + + default: + return -EINVAL; + } +} + +static const struct v4l2_ctrl_ops smiapp_ctrl_ops = { + .s_ctrl = smiapp_set_ctrl, +}; + +static int smiapp_init_controls(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int max; + int rval; + + rval = v4l2_ctrl_handler_init(&sensor->pixel_array->ctrl_handler, 7); + if (rval) + return rval; + sensor->pixel_array->ctrl_handler.lock = &sensor->mutex; + + sensor->analog_gain = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, + sensor->limits[SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MIN], + sensor->limits[SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MAX], + max(sensor->limits[SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_STEP], 1U), + sensor->limits[SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MIN]); + + /* Exposure limits will be updated soon, use just something here. */ + sensor->exposure = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 0, 1, 0); + + sensor->hflip = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + sensor->vflip = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + sensor->vblank = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_VBLANK, 0, 1, 1, 0); + + if (sensor->vblank) + sensor->vblank->flags |= V4L2_CTRL_FLAG_UPDATE; + + sensor->hblank = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_HBLANK, 0, 1, 1, 0); + + if (sensor->hblank) + sensor->hblank->flags |= V4L2_CTRL_FLAG_UPDATE; + + sensor->pixel_rate_parray = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, 0, 1, 0); + + if (sensor->pixel_array->ctrl_handler.error) { + dev_err(&client->dev, + "pixel array controls initialization failed (%d)\n", + sensor->pixel_array->ctrl_handler.error); + rval = sensor->pixel_array->ctrl_handler.error; + goto error; + } + + sensor->pixel_array->sd.ctrl_handler = + &sensor->pixel_array->ctrl_handler; + + v4l2_ctrl_cluster(2, &sensor->hflip); + + rval = v4l2_ctrl_handler_init(&sensor->src->ctrl_handler, 0); + if (rval) + goto error; + sensor->src->ctrl_handler.lock = &sensor->mutex; + + for (max = 0; sensor->platform_data->op_sys_clock[max + 1]; max++); + + sensor->link_freq = v4l2_ctrl_new_int_menu( + &sensor->src->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_LINK_FREQ, max, 0, + sensor->platform_data->op_sys_clock); + + sensor->pixel_rate_csi = v4l2_ctrl_new_std( + &sensor->src->ctrl_handler, &smiapp_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, 0, 1, 0); + + if (sensor->src->ctrl_handler.error) { + dev_err(&client->dev, + "src controls initialization failed (%d)\n", + sensor->src->ctrl_handler.error); + rval = sensor->src->ctrl_handler.error; + goto error; + } + + sensor->src->sd.ctrl_handler = + &sensor->src->ctrl_handler; + + return 0; + +error: + v4l2_ctrl_handler_free(&sensor->pixel_array->ctrl_handler); + v4l2_ctrl_handler_free(&sensor->src->ctrl_handler); + + return rval; +} + +static void smiapp_free_controls(struct smiapp_sensor *sensor) +{ + unsigned int i; + + for (i = 0; i < sensor->ssds_used; i++) + v4l2_ctrl_handler_free(&sensor->ssds[i].ctrl_handler); +} + +static int smiapp_get_limits(struct smiapp_sensor *sensor, int const *limit, + unsigned int n) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int i; + u32 val; + int rval; + + for (i = 0; i < n; i++) { + rval = smiapp_read( + sensor, smiapp_reg_limits[limit[i]].addr, &val); + if (rval) + return rval; + sensor->limits[limit[i]] = val; + dev_dbg(&client->dev, "0x%8.8x \"%s\" = %d, 0x%x\n", + smiapp_reg_limits[limit[i]].addr, + smiapp_reg_limits[limit[i]].what, val, val); + } + + return 0; +} + +static int smiapp_get_all_limits(struct smiapp_sensor *sensor) +{ + unsigned int i; + int rval; + + for (i = 0; i < SMIAPP_LIMIT_LAST; i++) { + rval = smiapp_get_limits(sensor, &i, 1); + if (rval < 0) + return rval; + } + + if (sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN] == 0) + smiapp_replace_limit(sensor, SMIAPP_LIMIT_SCALER_N_MIN, 16); + + return 0; +} + +static int smiapp_get_limits_binning(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + static u32 const limits[] = { + SMIAPP_LIMIT_MIN_FRAME_LENGTH_LINES_BIN, + SMIAPP_LIMIT_MAX_FRAME_LENGTH_LINES_BIN, + SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK_BIN, + SMIAPP_LIMIT_MAX_LINE_LENGTH_PCK_BIN, + SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK_BIN, + SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MIN_BIN, + SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN, + }; + static u32 const limits_replace[] = { + SMIAPP_LIMIT_MIN_FRAME_LENGTH_LINES, + SMIAPP_LIMIT_MAX_FRAME_LENGTH_LINES, + SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK, + SMIAPP_LIMIT_MAX_LINE_LENGTH_PCK, + SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK, + SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MIN, + SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MAX_MARGIN, + }; + unsigned int i; + int rval; + + if (sensor->limits[SMIAPP_LIMIT_BINNING_CAPABILITY] == + SMIAPP_BINNING_CAPABILITY_NO) { + for (i = 0; i < ARRAY_SIZE(limits); i++) + sensor->limits[limits[i]] = + sensor->limits[limits_replace[i]]; + + return 0; + } + + rval = smiapp_get_limits(sensor, limits, ARRAY_SIZE(limits)); + if (rval < 0) + return rval; + + /* + * Sanity check whether the binning limits are valid. If not, + * use the non-binning ones. + */ + if (sensor->limits[SMIAPP_LIMIT_MIN_FRAME_LENGTH_LINES_BIN] + && sensor->limits[SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK_BIN] + && sensor->limits[SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK_BIN]) + return 0; + + for (i = 0; i < ARRAY_SIZE(limits); i++) { + dev_dbg(&client->dev, + "replace limit 0x%8.8x \"%s\" = %d, 0x%x\n", + smiapp_reg_limits[limits[i]].addr, + smiapp_reg_limits[limits[i]].what, + sensor->limits[limits_replace[i]], + sensor->limits[limits_replace[i]]); + sensor->limits[limits[i]] = + sensor->limits[limits_replace[i]]; + } + + return 0; +} + +static int smiapp_get_mbus_formats(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int type, n; + unsigned int i, pixel_order; + int rval; + + rval = smiapp_read( + sensor, SMIAPP_REG_U8_DATA_FORMAT_MODEL_TYPE, &type); + if (rval) + return rval; + + dev_dbg(&client->dev, "data_format_model_type %d\n", type); + + rval = smiapp_read(sensor, SMIAPP_REG_U8_PIXEL_ORDER, + &pixel_order); + if (rval) + return rval; + + if (pixel_order >= ARRAY_SIZE(pixel_order_str)) { + dev_dbg(&client->dev, "bad pixel order %d\n", pixel_order); + return -EINVAL; + } + + dev_dbg(&client->dev, "pixel order %d (%s)\n", pixel_order, + pixel_order_str[pixel_order]); + + switch (type) { + case SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL: + n = SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL_N; + break; + case SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED: + n = SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED_N; + break; + default: + return -EINVAL; + } + + sensor->default_pixel_order = pixel_order; + sensor->mbus_frame_fmts = 0; + + for (i = 0; i < n; i++) { + unsigned int fmt, j; + + rval = smiapp_read( + sensor, + SMIAPP_REG_U16_DATA_FORMAT_DESCRIPTOR(i), &fmt); + if (rval) + return rval; + + dev_dbg(&client->dev, "bpp %d, compressed %d\n", + fmt >> 8, (u8)fmt); + + for (j = 0; j < ARRAY_SIZE(smiapp_csi_data_formats); j++) { + const struct smiapp_csi_data_format *f = + &smiapp_csi_data_formats[j]; + + if (f->pixel_order != SMIAPP_PIXEL_ORDER_GRBG) + continue; + + if (f->width != fmt >> 8 || f->compressed != (u8)fmt) + continue; + + dev_dbg(&client->dev, "jolly good! %d\n", j); + + sensor->default_mbus_frame_fmts |= 1 << j; + if (!sensor->csi_format + || f->width > sensor->csi_format->width + || (f->width == sensor->csi_format->width + && f->compressed + > sensor->csi_format->compressed)) { + sensor->csi_format = f; + sensor->internal_csi_format = f; + } + } + } + + if (!sensor->csi_format) { + dev_err(&client->dev, "no supported mbus code found\n"); + return -EINVAL; + } + + smiapp_update_mbus_formats(sensor); + + return 0; +} + +static void smiapp_update_blanking(struct smiapp_sensor *sensor) +{ + struct v4l2_ctrl *vblank = sensor->vblank; + struct v4l2_ctrl *hblank = sensor->hblank; + + vblank->minimum = + max_t(int, + sensor->limits[SMIAPP_LIMIT_MIN_FRAME_BLANKING_LINES], + sensor->limits[SMIAPP_LIMIT_MIN_FRAME_LENGTH_LINES_BIN] - + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height); + vblank->maximum = + sensor->limits[SMIAPP_LIMIT_MAX_FRAME_LENGTH_LINES_BIN] - + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height; + + vblank->val = clamp_t(int, vblank->val, + vblank->minimum, vblank->maximum); + vblank->default_value = vblank->minimum; + vblank->val = vblank->val; + vblank->cur.val = vblank->val; + + hblank->minimum = + max_t(int, + sensor->limits[SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK_BIN] - + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width, + sensor->limits[SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK_BIN]); + hblank->maximum = + sensor->limits[SMIAPP_LIMIT_MAX_LINE_LENGTH_PCK_BIN] - + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width; + + hblank->val = clamp_t(int, hblank->val, + hblank->minimum, hblank->maximum); + hblank->default_value = hblank->minimum; + hblank->val = hblank->val; + hblank->cur.val = hblank->val; + + __smiapp_update_exposure_limits(sensor); +} + +static int smiapp_update_mode(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int binning_mode; + int rval; + + dev_dbg(&client->dev, "frame size: %dx%d\n", + sensor->src->crop[SMIAPP_PAD_SRC].width, + sensor->src->crop[SMIAPP_PAD_SRC].height); + dev_dbg(&client->dev, "csi format width: %d\n", + sensor->csi_format->width); + + /* Binning has to be set up here; it affects limits */ + if (sensor->binning_horizontal == 1 && + sensor->binning_vertical == 1) { + binning_mode = 0; + } else { + u8 binning_type = + (sensor->binning_horizontal << 4) + | sensor->binning_vertical; + + rval = smiapp_write( + sensor, SMIAPP_REG_U8_BINNING_TYPE, binning_type); + if (rval < 0) + return rval; + + binning_mode = 1; + } + rval = smiapp_write(sensor, SMIAPP_REG_U8_BINNING_MODE, binning_mode); + if (rval < 0) + return rval; + + /* Get updated limits due to binning */ + rval = smiapp_get_limits_binning(sensor); + if (rval < 0) + return rval; + + rval = smiapp_pll_update(sensor); + if (rval < 0) + return rval; + + /* Output from pixel array, including blanking */ + smiapp_update_blanking(sensor); + + dev_dbg(&client->dev, "vblank\t\t%d\n", sensor->vblank->val); + dev_dbg(&client->dev, "hblank\t\t%d\n", sensor->hblank->val); + + dev_dbg(&client->dev, "real timeperframe\t100/%d\n", + sensor->pll.vt_pix_clk_freq_hz / + ((sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width + + sensor->hblank->val) * + (sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height + + sensor->vblank->val) / 100)); + + return 0; +} + +/* + * + * SMIA++ NVM handling + * + */ +static int smiapp_read_nvm(struct smiapp_sensor *sensor, + unsigned char *nvm) +{ + u32 i, s, p, np, v; + int rval = 0, rval2; + + np = sensor->nvm_size / SMIAPP_NVM_PAGE_SIZE; + for (p = 0; p < np; p++) { + rval = smiapp_write( + sensor, + SMIAPP_REG_U8_DATA_TRANSFER_IF_1_PAGE_SELECT, p); + if (rval) + goto out; + + rval = smiapp_write(sensor, + SMIAPP_REG_U8_DATA_TRANSFER_IF_1_CTRL, + SMIAPP_DATA_TRANSFER_IF_1_CTRL_EN | + SMIAPP_DATA_TRANSFER_IF_1_CTRL_RD_EN); + if (rval) + goto out; + + for (i = 0; i < 1000; i++) { + rval = smiapp_read( + sensor, + SMIAPP_REG_U8_DATA_TRANSFER_IF_1_STATUS, &s); + + if (rval) + goto out; + + if (s & SMIAPP_DATA_TRANSFER_IF_1_STATUS_RD_READY) + break; + + if (--i == 0) { + rval = -ETIMEDOUT; + goto out; + } + + } + + for (i = 0; i < SMIAPP_NVM_PAGE_SIZE; i++) { + rval = smiapp_read( + sensor, + SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_0 + i, + &v); + if (rval) + goto out; + + *nvm++ = v; + } + } + +out: + rval2 = smiapp_write(sensor, SMIAPP_REG_U8_DATA_TRANSFER_IF_1_CTRL, 0); + if (rval < 0) + return rval; + else + return rval2; +} + +/* + * + * SMIA++ CCI address control + * + */ +static int smiapp_change_cci_addr(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + u32 val; + + client->addr = sensor->platform_data->i2c_addr_dfl; + + rval = smiapp_write(sensor, + SMIAPP_REG_U8_CCI_ADDRESS_CONTROL, + sensor->platform_data->i2c_addr_alt << 1); + if (rval) + return rval; + + client->addr = sensor->platform_data->i2c_addr_alt; + + /* verify addr change went ok */ + rval = smiapp_read(sensor, SMIAPP_REG_U8_CCI_ADDRESS_CONTROL, &val); + if (rval) + return rval; + + if (val != sensor->platform_data->i2c_addr_alt << 1) + return -ENODEV; + + return 0; +} + +/* + * + * SMIA++ Mode Control + * + */ +static int smiapp_setup_flash_strobe(struct smiapp_sensor *sensor) +{ + struct smiapp_flash_strobe_parms *strobe_setup; + unsigned int ext_freq = sensor->platform_data->ext_clk; + u32 tmp; + u32 strobe_adjustment; + u32 strobe_width_high_rs; + int rval; + + strobe_setup = sensor->platform_data->strobe_setup; + + /* + * How to calculate registers related to strobe length. Please + * do not change, or if you do at least know what you're + * doing. :-) + * + * Sakari Ailus 2010-10-25 + * + * flash_strobe_length [us] / 10^6 = (tFlash_strobe_width_ctrl + * / EXTCLK freq [Hz]) * flash_strobe_adjustment + * + * tFlash_strobe_width_ctrl E N, [1 - 0xffff] + * flash_strobe_adjustment E N, [1 - 0xff] + * + * The formula above is written as below to keep it on one + * line: + * + * l / 10^6 = w / e * a + * + * Let's mark w * a by x: + * + * x = w * a + * + * Thus, we get: + * + * x = l * e / 10^6 + * + * The strobe width must be at least as long as requested, + * thus rounding upwards is needed. + * + * x = (l * e + 10^6 - 1) / 10^6 + * ----------------------------- + * + * Maximum possible accuracy is wanted at all times. Thus keep + * a as small as possible. + * + * Calculate a, assuming maximum w, with rounding upwards: + * + * a = (x + (2^16 - 1) - 1) / (2^16 - 1) + * ------------------------------------- + * + * Thus, we also get w, with that a, with rounding upwards: + * + * w = (x + a - 1) / a + * ------------------- + * + * To get limits: + * + * x E [1, (2^16 - 1) * (2^8 - 1)] + * + * Substituting maximum x to the original formula (with rounding), + * the maximum l is thus + * + * (2^16 - 1) * (2^8 - 1) * 10^6 = l * e + 10^6 - 1 + * + * l = (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / e + * -------------------------------------------------- + * + * flash_strobe_length must be clamped between 1 and + * (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / EXTCLK freq. + * + * Then, + * + * flash_strobe_adjustment = ((flash_strobe_length * + * EXTCLK freq + 10^6 - 1) / 10^6 + (2^16 - 1) - 1) / (2^16 - 1) + * + * tFlash_strobe_width_ctrl = ((flash_strobe_length * + * EXTCLK freq + 10^6 - 1) / 10^6 + + * flash_strobe_adjustment - 1) / flash_strobe_adjustment + */ + tmp = div_u64(1000000ULL * ((1 << 16) - 1) * ((1 << 8) - 1) - + 1000000 + 1, ext_freq); + strobe_setup->strobe_width_high_us = + clamp_t(u32, strobe_setup->strobe_width_high_us, 1, tmp); + + tmp = div_u64(((u64)strobe_setup->strobe_width_high_us * (u64)ext_freq + + 1000000 - 1), 1000000ULL); + strobe_adjustment = (tmp + (1 << 16) - 1 - 1) / ((1 << 16) - 1); + strobe_width_high_rs = (tmp + strobe_adjustment - 1) / + strobe_adjustment; + + rval = smiapp_write(sensor, SMIAPP_REG_U8_FLASH_MODE_RS, + strobe_setup->mode); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U8_FLASH_STROBE_ADJUSTMENT, + strobe_adjustment); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_HIGH_RS_CTRL, + strobe_width_high_rs); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U16_TFLASH_STROBE_DELAY_RS_CTRL, + strobe_setup->strobe_delay); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U16_FLASH_STROBE_START_POINT, + strobe_setup->stobe_start_point); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U8_FLASH_TRIGGER_RS, + strobe_setup->trigger); + +out: + sensor->platform_data->strobe_setup->trigger = 0; + + return rval; +} + +/* ----------------------------------------------------------------------------- + * Power management + */ + +static int smiapp_power_on(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int sleep; + int rval; + + rval = regulator_enable(sensor->vana); + if (rval) { + dev_err(&client->dev, "failed to enable vana regulator\n"); + return rval; + } + usleep_range(1000, 1000); + + if (sensor->platform_data->set_xclk) + rval = sensor->platform_data->set_xclk( + &sensor->src->sd, sensor->platform_data->ext_clk); + else + rval = clk_enable(sensor->ext_clk); + if (rval < 0) { + dev_dbg(&client->dev, "failed to set xclk\n"); + goto out_xclk_fail; + } + usleep_range(1000, 1000); + + if (sensor->platform_data->xshutdown != SMIAPP_NO_XSHUTDOWN) + gpio_set_value(sensor->platform_data->xshutdown, 1); + + sleep = SMIAPP_RESET_DELAY(sensor->platform_data->ext_clk); + usleep_range(sleep, sleep); + + /* + * Failures to respond to the address change command have been noticed. + * Those failures seem to be caused by the sensor requiring a longer + * boot time than advertised. An additional 10ms delay seems to work + * around the issue, but the SMIA++ I2C write retry hack makes the delay + * unnecessary. The failures need to be investigated to find a proper + * fix, and a delay will likely need to be added here if the I2C write + * retry hack is reverted before the root cause of the boot time issue + * is found. + */ + + if (sensor->platform_data->i2c_addr_alt) { + rval = smiapp_change_cci_addr(sensor); + if (rval) { + dev_err(&client->dev, "cci address change error\n"); + goto out_cci_addr_fail; + } + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_SOFTWARE_RESET, + SMIAPP_SOFTWARE_RESET); + if (rval < 0) { + dev_err(&client->dev, "software reset failed\n"); + goto out_cci_addr_fail; + } + + if (sensor->platform_data->i2c_addr_alt) { + rval = smiapp_change_cci_addr(sensor); + if (rval) { + dev_err(&client->dev, "cci address change error\n"); + goto out_cci_addr_fail; + } + } + + rval = smiapp_write(sensor, SMIAPP_REG_U16_COMPRESSION_MODE, + SMIAPP_COMPRESSION_MODE_SIMPLE_PREDICTOR); + if (rval) { + dev_err(&client->dev, "compression mode set failed\n"); + goto out_cci_addr_fail; + } + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_EXTCLK_FREQUENCY_MHZ, + sensor->platform_data->ext_clk / (1000000 / (1 << 8))); + if (rval) { + dev_err(&client->dev, "extclk frequency set failed\n"); + goto out_cci_addr_fail; + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_CSI_LANE_MODE, + sensor->platform_data->lanes - 1); + if (rval) { + dev_err(&client->dev, "csi lane mode set failed\n"); + goto out_cci_addr_fail; + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_FAST_STANDBY_CTRL, + SMIAPP_FAST_STANDBY_CTRL_IMMEDIATE); + if (rval) { + dev_err(&client->dev, "fast standby set failed\n"); + goto out_cci_addr_fail; + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_CSI_SIGNALLING_MODE, + sensor->platform_data->csi_signalling_mode); + if (rval) { + dev_err(&client->dev, "csi signalling mode set failed\n"); + goto out_cci_addr_fail; + } + + /* DPHY control done by sensor based on requested link rate */ + rval = smiapp_write(sensor, SMIAPP_REG_U8_DPHY_CTRL, + SMIAPP_DPHY_CTRL_UI); + if (rval < 0) + return rval; + + rval = smiapp_call_quirk(sensor, post_poweron); + if (rval) { + dev_err(&client->dev, "post_poweron quirks failed\n"); + goto out_cci_addr_fail; + } + + /* Are we still initialising...? If yes, return here. */ + if (!sensor->pixel_array) + return 0; + + rval = v4l2_ctrl_handler_setup( + &sensor->pixel_array->ctrl_handler); + if (rval) + goto out_cci_addr_fail; + + rval = v4l2_ctrl_handler_setup(&sensor->src->ctrl_handler); + if (rval) + goto out_cci_addr_fail; + + mutex_lock(&sensor->mutex); + rval = smiapp_update_mode(sensor); + mutex_unlock(&sensor->mutex); + if (rval < 0) + goto out_cci_addr_fail; + + return 0; + +out_cci_addr_fail: + if (sensor->platform_data->xshutdown != SMIAPP_NO_XSHUTDOWN) + gpio_set_value(sensor->platform_data->xshutdown, 0); + if (sensor->platform_data->set_xclk) + sensor->platform_data->set_xclk(&sensor->src->sd, 0); + else + clk_disable(sensor->ext_clk); + +out_xclk_fail: + regulator_disable(sensor->vana); + return rval; +} + +static void smiapp_power_off(struct smiapp_sensor *sensor) +{ + /* + * Currently power/clock to lens are enable/disabled separately + * but they are essentially the same signals. So if the sensor is + * powered off while the lens is powered on the sensor does not + * really see a power off and next time the cci address change + * will fail. So do a soft reset explicitly here. + */ + if (sensor->platform_data->i2c_addr_alt) + smiapp_write(sensor, + SMIAPP_REG_U8_SOFTWARE_RESET, + SMIAPP_SOFTWARE_RESET); + + if (sensor->platform_data->xshutdown != SMIAPP_NO_XSHUTDOWN) + gpio_set_value(sensor->platform_data->xshutdown, 0); + if (sensor->platform_data->set_xclk) + sensor->platform_data->set_xclk(&sensor->src->sd, 0); + else + clk_disable(sensor->ext_clk); + usleep_range(5000, 5000); + regulator_disable(sensor->vana); + sensor->streaming = 0; +} + +static int smiapp_set_power(struct v4l2_subdev *subdev, int on) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int ret = 0; + + mutex_lock(&sensor->power_mutex); + + /* + * If the power count is modified from 0 to != 0 or from != 0 + * to 0, update the power state. + */ + if (!sensor->power_count == !on) + goto out; + + if (on) { + /* Power on and perform initialisation. */ + ret = smiapp_power_on(sensor); + if (ret < 0) + goto out; + } else { + smiapp_power_off(sensor); + } + + /* Update the power count. */ + sensor->power_count += on ? 1 : -1; + WARN_ON(sensor->power_count < 0); + +out: + mutex_unlock(&sensor->power_mutex); + return ret; +} + +/* ----------------------------------------------------------------------------- + * Video stream management + */ + +static int smiapp_start_streaming(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + mutex_lock(&sensor->mutex); + + rval = smiapp_write(sensor, SMIAPP_REG_U16_CSI_DATA_FORMAT, + (sensor->csi_format->width << 8) | + sensor->csi_format->compressed); + if (rval) + goto out; + + rval = smiapp_pll_configure(sensor); + if (rval) + goto out; + + /* Analog crop start coordinates */ + rval = smiapp_write(sensor, SMIAPP_REG_U16_X_ADDR_START, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].left); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U16_Y_ADDR_START, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].top); + if (rval < 0) + goto out; + + /* Analog crop end coordinates */ + rval = smiapp_write( + sensor, SMIAPP_REG_U16_X_ADDR_END, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].left + + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width - 1); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_Y_ADDR_END, + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].top + + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height - 1); + if (rval < 0) + goto out; + + /* + * Output from pixel array, including blanking, is set using + * controls below. No need to set here. + */ + + /* Digital crop */ + if (sensor->limits[SMIAPP_LIMIT_DIGITAL_CROP_CAPABILITY] + == SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP) { + rval = smiapp_write( + sensor, SMIAPP_REG_U16_DIGITAL_CROP_X_OFFSET, + sensor->scaler->crop[SMIAPP_PAD_SINK].left); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_DIGITAL_CROP_Y_OFFSET, + sensor->scaler->crop[SMIAPP_PAD_SINK].top); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_WIDTH, + sensor->scaler->crop[SMIAPP_PAD_SINK].width); + if (rval < 0) + goto out; + + rval = smiapp_write( + sensor, SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_HEIGHT, + sensor->scaler->crop[SMIAPP_PAD_SINK].height); + if (rval < 0) + goto out; + } + + /* Scaling */ + if (sensor->limits[SMIAPP_LIMIT_SCALING_CAPABILITY] + != SMIAPP_SCALING_CAPABILITY_NONE) { + rval = smiapp_write(sensor, SMIAPP_REG_U16_SCALING_MODE, + sensor->scaling_mode); + if (rval < 0) + goto out; + + rval = smiapp_write(sensor, SMIAPP_REG_U16_SCALE_M, + sensor->scale_m); + if (rval < 0) + goto out; + } + + /* Output size from sensor */ + rval = smiapp_write(sensor, SMIAPP_REG_U16_X_OUTPUT_SIZE, + sensor->src->crop[SMIAPP_PAD_SRC].width); + if (rval < 0) + goto out; + rval = smiapp_write(sensor, SMIAPP_REG_U16_Y_OUTPUT_SIZE, + sensor->src->crop[SMIAPP_PAD_SRC].height); + if (rval < 0) + goto out; + + if ((sensor->flash_capability & + (SMIAPP_FLASH_MODE_CAPABILITY_SINGLE_STROBE | + SMIAPP_FLASH_MODE_CAPABILITY_MULTIPLE_STROBE)) && + sensor->platform_data->strobe_setup != NULL && + sensor->platform_data->strobe_setup->trigger != 0) { + rval = smiapp_setup_flash_strobe(sensor); + if (rval) + goto out; + } + + rval = smiapp_call_quirk(sensor, pre_streamon); + if (rval) { + dev_err(&client->dev, "pre_streamon quirks failed\n"); + goto out; + } + + rval = smiapp_write(sensor, SMIAPP_REG_U8_MODE_SELECT, + SMIAPP_MODE_SELECT_STREAMING); + +out: + mutex_unlock(&sensor->mutex); + + return rval; +} + +static int smiapp_stop_streaming(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + mutex_lock(&sensor->mutex); + rval = smiapp_write(sensor, SMIAPP_REG_U8_MODE_SELECT, + SMIAPP_MODE_SELECT_SOFTWARE_STANDBY); + if (rval) + goto out; + + rval = smiapp_call_quirk(sensor, post_streamoff); + if (rval) + dev_err(&client->dev, "post_streamoff quirks failed\n"); + +out: + mutex_unlock(&sensor->mutex); + return rval; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev video operations + */ + +static int smiapp_set_stream(struct v4l2_subdev *subdev, int enable) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int rval; + + if (sensor->streaming == enable) + return 0; + + if (enable) { + sensor->streaming = 1; + rval = smiapp_start_streaming(sensor); + if (rval < 0) + sensor->streaming = 0; + } else { + rval = smiapp_stop_streaming(sensor); + sensor->streaming = 0; + } + + return rval; +} + +static int smiapp_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + unsigned int i; + int idx = -1; + int rval = -EINVAL; + + mutex_lock(&sensor->mutex); + + dev_err(&client->dev, "subdev %s, pad %d, index %d\n", + subdev->name, code->pad, code->index); + + if (subdev != &sensor->src->sd || code->pad != SMIAPP_PAD_SRC) { + if (code->index) + goto out; + + code->code = sensor->internal_csi_format->code; + rval = 0; + goto out; + } + + for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) { + if (sensor->mbus_frame_fmts & (1 << i)) + idx++; + + if (idx == code->index) { + code->code = smiapp_csi_data_formats[i].code; + dev_err(&client->dev, "found index %d, i %d, code %x\n", + code->index, i, code->code); + rval = 0; + break; + } + } + +out: + mutex_unlock(&sensor->mutex); + + return rval; +} + +static u32 __smiapp_get_mbus_code(struct v4l2_subdev *subdev, + unsigned int pad) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + + if (subdev == &sensor->src->sd && pad == SMIAPP_PAD_SRC) + return sensor->csi_format->code; + else + return sensor->internal_csi_format->code; +} + +static int __smiapp_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + fmt->format = *v4l2_subdev_get_try_format(fh, fmt->pad); + } else { + struct v4l2_rect *r; + + if (fmt->pad == ssd->source_pad) + r = &ssd->crop[ssd->source_pad]; + else + r = &ssd->sink_fmt; + + fmt->format.code = __smiapp_get_mbus_code(subdev, fmt->pad); + fmt->format.width = r->width; + fmt->format.height = r->height; + } + + return 0; +} + +static int smiapp_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int rval; + + mutex_lock(&sensor->mutex); + rval = __smiapp_get_format(subdev, fh, fmt); + mutex_unlock(&sensor->mutex); + + return rval; +} + +static void smiapp_get_crop_compose(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_rect **crops, + struct v4l2_rect **comps, int which) +{ + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + unsigned int i; + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) { + if (crops) + for (i = 0; i < subdev->entity.num_pads; i++) + crops[i] = &ssd->crop[i]; + if (comps) + *comps = &ssd->compose; + } else { + if (crops) { + for (i = 0; i < subdev->entity.num_pads; i++) { + crops[i] = v4l2_subdev_get_try_crop(fh, i); + BUG_ON(!crops[i]); + } + } + if (comps) { + *comps = v4l2_subdev_get_try_compose(fh, + SMIAPP_PAD_SINK); + BUG_ON(!*comps); + } + } +} + +/* Changes require propagation only on sink pad. */ +static void smiapp_propagate(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, int which, + int target) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *comp, *crops[SMIAPP_PADS]; + + smiapp_get_crop_compose(subdev, fh, crops, &comp, which); + + switch (target) { + case V4L2_SEL_TGT_CROP: + comp->width = crops[SMIAPP_PAD_SINK]->width; + comp->height = crops[SMIAPP_PAD_SINK]->height; + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) { + if (ssd == sensor->scaler) { + sensor->scale_m = + sensor->limits[ + SMIAPP_LIMIT_SCALER_N_MIN]; + sensor->scaling_mode = + SMIAPP_SCALING_MODE_NONE; + } else if (ssd == sensor->binner) { + sensor->binning_horizontal = 1; + sensor->binning_vertical = 1; + } + } + /* Fall through */ + case V4L2_SEL_TGT_COMPOSE: + *crops[SMIAPP_PAD_SRC] = *comp; + break; + default: + BUG(); + } +} + +static const struct smiapp_csi_data_format +*smiapp_validate_csi_data_format(struct smiapp_sensor *sensor, u32 code) +{ + const struct smiapp_csi_data_format *csi_format = sensor->csi_format; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) { + if (sensor->mbus_frame_fmts & (1 << i) + && smiapp_csi_data_formats[i].code == code) + return &smiapp_csi_data_formats[i]; + } + + return csi_format; +} + +static int smiapp_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *crops[SMIAPP_PADS]; + + mutex_lock(&sensor->mutex); + + /* + * Media bus code is changeable on src subdev's source pad. On + * other source pads we just get format here. + */ + if (fmt->pad == ssd->source_pad) { + u32 code = fmt->format.code; + int rval = __smiapp_get_format(subdev, fh, fmt); + + if (!rval && subdev == &sensor->src->sd) { + const struct smiapp_csi_data_format *csi_format = + smiapp_validate_csi_data_format(sensor, code); + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + sensor->csi_format = csi_format; + fmt->format.code = csi_format->code; + } + + mutex_unlock(&sensor->mutex); + return rval; + } + + /* Sink pad. Width and height are changeable here. */ + fmt->format.code = __smiapp_get_mbus_code(subdev, fmt->pad); + fmt->format.width &= ~1; + fmt->format.height &= ~1; + + fmt->format.width = + clamp(fmt->format.width, + sensor->limits[SMIAPP_LIMIT_MIN_X_OUTPUT_SIZE], + sensor->limits[SMIAPP_LIMIT_MAX_X_OUTPUT_SIZE]); + fmt->format.height = + clamp(fmt->format.height, + sensor->limits[SMIAPP_LIMIT_MIN_Y_OUTPUT_SIZE], + sensor->limits[SMIAPP_LIMIT_MAX_Y_OUTPUT_SIZE]); + + smiapp_get_crop_compose(subdev, fh, crops, NULL, fmt->which); + + crops[ssd->sink_pad]->left = 0; + crops[ssd->sink_pad]->top = 0; + crops[ssd->sink_pad]->width = fmt->format.width; + crops[ssd->sink_pad]->height = fmt->format.height; + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + ssd->sink_fmt = *crops[ssd->sink_pad]; + smiapp_propagate(subdev, fh, fmt->which, + V4L2_SEL_TGT_CROP); + + mutex_unlock(&sensor->mutex); + + return 0; +} + +/* + * Calculate goodness of scaled image size compared to expected image + * size and flags provided. + */ +#define SCALING_GOODNESS 100000 +#define SCALING_GOODNESS_EXTREME 100000000 +static int scaling_goodness(struct v4l2_subdev *subdev, int w, int ask_w, + int h, int ask_h, u32 flags) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int val = 0; + + w &= ~1; + ask_w &= ~1; + h &= ~1; + ask_h &= ~1; + + if (flags & V4L2_SEL_FLAG_GE) { + if (w < ask_w) + val -= SCALING_GOODNESS; + if (h < ask_h) + val -= SCALING_GOODNESS; + } + + if (flags & V4L2_SEL_FLAG_LE) { + if (w > ask_w) + val -= SCALING_GOODNESS; + if (h > ask_h) + val -= SCALING_GOODNESS; + } + + val -= abs(w - ask_w); + val -= abs(h - ask_h); + + if (w < sensor->limits[SMIAPP_LIMIT_MIN_X_OUTPUT_SIZE]) + val -= SCALING_GOODNESS_EXTREME; + + dev_dbg(&client->dev, "w %d ask_w %d h %d ask_h %d goodness %d\n", + w, ask_h, h, ask_h, val); + + return val; +} + +static void smiapp_set_compose_binner(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel, + struct v4l2_rect **crops, + struct v4l2_rect *comp) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + unsigned int i; + unsigned int binh = 1, binv = 1; + unsigned int best = scaling_goodness( + subdev, + crops[SMIAPP_PAD_SINK]->width, sel->r.width, + crops[SMIAPP_PAD_SINK]->height, sel->r.height, sel->flags); + + for (i = 0; i < sensor->nbinning_subtypes; i++) { + int this = scaling_goodness( + subdev, + crops[SMIAPP_PAD_SINK]->width + / sensor->binning_subtypes[i].horizontal, + sel->r.width, + crops[SMIAPP_PAD_SINK]->height + / sensor->binning_subtypes[i].vertical, + sel->r.height, sel->flags); + + if (this > best) { + binh = sensor->binning_subtypes[i].horizontal; + binv = sensor->binning_subtypes[i].vertical; + best = this; + } + } + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + sensor->binning_vertical = binv; + sensor->binning_horizontal = binh; + } + + sel->r.width = (crops[SMIAPP_PAD_SINK]->width / binh) & ~1; + sel->r.height = (crops[SMIAPP_PAD_SINK]->height / binv) & ~1; +} + +/* + * Calculate best scaling ratio and mode for given output resolution. + * + * Try all of these: horizontal ratio, vertical ratio and smallest + * size possible (horizontally). + * + * Also try whether horizontal scaler or full scaler gives a better + * result. + */ +static void smiapp_set_compose_scaler(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel, + struct v4l2_rect **crops, + struct v4l2_rect *comp) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + u32 min, max, a, b, max_m; + u32 scale_m = sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN]; + int mode = SMIAPP_SCALING_MODE_HORIZONTAL; + u32 try[4]; + u32 ntry = 0; + unsigned int i; + int best = INT_MIN; + + sel->r.width = min_t(unsigned int, sel->r.width, + crops[SMIAPP_PAD_SINK]->width); + sel->r.height = min_t(unsigned int, sel->r.height, + crops[SMIAPP_PAD_SINK]->height); + + a = crops[SMIAPP_PAD_SINK]->width + * sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN] / sel->r.width; + b = crops[SMIAPP_PAD_SINK]->height + * sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN] / sel->r.height; + max_m = crops[SMIAPP_PAD_SINK]->width + * sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN] + / sensor->limits[SMIAPP_LIMIT_MIN_X_OUTPUT_SIZE]; + + a = min(sensor->limits[SMIAPP_LIMIT_SCALER_M_MAX], + max(a, sensor->limits[SMIAPP_LIMIT_SCALER_M_MIN])); + b = min(sensor->limits[SMIAPP_LIMIT_SCALER_M_MAX], + max(b, sensor->limits[SMIAPP_LIMIT_SCALER_M_MIN])); + max_m = min(sensor->limits[SMIAPP_LIMIT_SCALER_M_MAX], + max(max_m, sensor->limits[SMIAPP_LIMIT_SCALER_M_MIN])); + + dev_dbg(&client->dev, "scaling: a %d b %d max_m %d\n", a, b, max_m); + + min = min(max_m, min(a, b)); + max = min(max_m, max(a, b)); + + try[ntry] = min; + ntry++; + if (min != max) { + try[ntry] = max; + ntry++; + } + if (max != max_m) { + try[ntry] = min + 1; + ntry++; + if (min != max) { + try[ntry] = max + 1; + ntry++; + } + } + + for (i = 0; i < ntry; i++) { + int this = scaling_goodness( + subdev, + crops[SMIAPP_PAD_SINK]->width + / try[i] + * sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN], + sel->r.width, + crops[SMIAPP_PAD_SINK]->height, + sel->r.height, + sel->flags); + + dev_dbg(&client->dev, "trying factor %d (%d)\n", try[i], i); + + if (this > best) { + scale_m = try[i]; + mode = SMIAPP_SCALING_MODE_HORIZONTAL; + best = this; + } + + if (sensor->limits[SMIAPP_LIMIT_SCALING_CAPABILITY] + == SMIAPP_SCALING_CAPABILITY_HORIZONTAL) + continue; + + this = scaling_goodness( + subdev, crops[SMIAPP_PAD_SINK]->width + / try[i] + * sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN], + sel->r.width, + crops[SMIAPP_PAD_SINK]->height + / try[i] + * sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN], + sel->r.height, + sel->flags); + + if (this > best) { + scale_m = try[i]; + mode = SMIAPP_SCALING_MODE_BOTH; + best = this; + } + } + + sel->r.width = + (crops[SMIAPP_PAD_SINK]->width + / scale_m + * sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN]) & ~1; + if (mode == SMIAPP_SCALING_MODE_BOTH) + sel->r.height = + (crops[SMIAPP_PAD_SINK]->height + / scale_m + * sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN]) + & ~1; + else + sel->r.height = crops[SMIAPP_PAD_SINK]->height; + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + sensor->scale_m = scale_m; + sensor->scaling_mode = mode; + } +} +/* We're only called on source pads. This function sets scaling. */ +static int smiapp_set_compose(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *comp, *crops[SMIAPP_PADS]; + + smiapp_get_crop_compose(subdev, fh, crops, &comp, sel->which); + + sel->r.top = 0; + sel->r.left = 0; + + if (ssd == sensor->binner) + smiapp_set_compose_binner(subdev, fh, sel, crops, comp); + else + smiapp_set_compose_scaler(subdev, fh, sel, crops, comp); + + *comp = sel->r; + smiapp_propagate(subdev, fh, sel->which, + V4L2_SEL_TGT_COMPOSE); + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return smiapp_update_mode(sensor); + + return 0; +} + +static int __smiapp_sel_supported(struct v4l2_subdev *subdev, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + + /* We only implement crop in three places. */ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_BOUNDS: + if (ssd == sensor->pixel_array + && sel->pad == SMIAPP_PA_PAD_SRC) + return 0; + if (ssd == sensor->src + && sel->pad == SMIAPP_PAD_SRC) + return 0; + if (ssd == sensor->scaler + && sel->pad == SMIAPP_PAD_SINK + && sensor->limits[SMIAPP_LIMIT_DIGITAL_CROP_CAPABILITY] + == SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP) + return 0; + return -EINVAL; + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + if (sel->pad == ssd->source_pad) + return -EINVAL; + if (ssd == sensor->binner) + return 0; + if (ssd == sensor->scaler + && sensor->limits[SMIAPP_LIMIT_SCALING_CAPABILITY] + != SMIAPP_SCALING_CAPABILITY_NONE) + return 0; + /* Fall through */ + default: + return -EINVAL; + } +} + +static int smiapp_set_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *src_size, *crops[SMIAPP_PADS]; + struct v4l2_rect _r; + + smiapp_get_crop_compose(subdev, fh, crops, NULL, sel->which); + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + if (sel->pad == ssd->sink_pad) + src_size = &ssd->sink_fmt; + else + src_size = &ssd->compose; + } else { + if (sel->pad == ssd->sink_pad) { + _r.left = 0; + _r.top = 0; + _r.width = v4l2_subdev_get_try_format(fh, sel->pad) + ->width; + _r.height = v4l2_subdev_get_try_format(fh, sel->pad) + ->height; + src_size = &_r; + } else { + src_size = + v4l2_subdev_get_try_compose( + fh, ssd->sink_pad); + } + } + + if (ssd == sensor->src && sel->pad == SMIAPP_PAD_SRC) { + sel->r.left = 0; + sel->r.top = 0; + } + + sel->r.width = min(sel->r.width, src_size->width); + sel->r.height = min(sel->r.height, src_size->height); + + sel->r.left = min(sel->r.left, src_size->width - sel->r.width); + sel->r.top = min(sel->r.top, src_size->height - sel->r.height); + + *crops[sel->pad] = sel->r; + + if (ssd != sensor->pixel_array && sel->pad == SMIAPP_PAD_SINK) + smiapp_propagate(subdev, fh, sel->which, + V4L2_SEL_TGT_CROP); + + return 0; +} + +static int __smiapp_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_subdev *ssd = to_smiapp_subdev(subdev); + struct v4l2_rect *comp, *crops[SMIAPP_PADS]; + struct v4l2_rect sink_fmt; + int ret; + + ret = __smiapp_sel_supported(subdev, sel); + if (ret) + return ret; + + smiapp_get_crop_compose(subdev, fh, crops, &comp, sel->which); + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + sink_fmt = ssd->sink_fmt; + } else { + struct v4l2_mbus_framefmt *fmt = + v4l2_subdev_get_try_format(fh, ssd->sink_pad); + + sink_fmt.left = 0; + sink_fmt.top = 0; + sink_fmt.width = fmt->width; + sink_fmt.height = fmt->height; + } + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + if (ssd == sensor->pixel_array) { + sel->r.width = + sensor->limits[SMIAPP_LIMIT_X_ADDR_MAX] + 1; + sel->r.height = + sensor->limits[SMIAPP_LIMIT_Y_ADDR_MAX] + 1; + } else if (sel->pad == ssd->sink_pad) { + sel->r = sink_fmt; + } else { + sel->r = *comp; + } + break; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + sel->r = *crops[sel->pad]; + break; + case V4L2_SEL_TGT_COMPOSE: + sel->r = *comp; + break; + } + + return 0; +} + +static int smiapp_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int rval; + + mutex_lock(&sensor->mutex); + rval = __smiapp_get_selection(subdev, fh, sel); + mutex_unlock(&sensor->mutex); + + return rval; +} +static int smiapp_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_selection *sel) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int ret; + + ret = __smiapp_sel_supported(subdev, sel); + if (ret) + return ret; + + mutex_lock(&sensor->mutex); + + sel->r.left = max(0, sel->r.left & ~1); + sel->r.top = max(0, sel->r.top & ~1); + sel->r.width = max(0, SMIAPP_ALIGN_DIM(sel->r.width, sel->flags)); + sel->r.height = max(0, SMIAPP_ALIGN_DIM(sel->r.height, sel->flags)); + + sel->r.width = max_t(unsigned int, + sensor->limits[SMIAPP_LIMIT_MIN_X_OUTPUT_SIZE], + sel->r.width); + sel->r.height = max_t(unsigned int, + sensor->limits[SMIAPP_LIMIT_MIN_Y_OUTPUT_SIZE], + sel->r.height); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + ret = smiapp_set_crop(subdev, fh, sel); + break; + case V4L2_SEL_TGT_COMPOSE: + ret = smiapp_set_compose(subdev, fh, sel); + break; + default: + BUG(); + } + + mutex_unlock(&sensor->mutex); + return ret; +} + +static int smiapp_get_skip_frames(struct v4l2_subdev *subdev, u32 *frames) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + + *frames = sensor->frame_skip; + return 0; +} + +/* ----------------------------------------------------------------------------- + * sysfs attributes + */ + +static ssize_t +smiapp_sysfs_nvm_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev)); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + unsigned int nbytes; + + if (!sensor->dev_init_done) + return -EBUSY; + + if (!sensor->nvm_size) { + /* NVM not read yet - read it now */ + sensor->nvm_size = sensor->platform_data->nvm_size; + if (smiapp_set_power(subdev, 1) < 0) + return -ENODEV; + if (smiapp_read_nvm(sensor, sensor->nvm)) { + dev_err(&client->dev, "nvm read failed\n"); + return -ENODEV; + } + smiapp_set_power(subdev, 0); + } + /* + * NVM is still way below a PAGE_SIZE, so we can safely + * assume this for now. + */ + nbytes = min_t(unsigned int, sensor->nvm_size, PAGE_SIZE); + memcpy(buf, sensor->nvm, nbytes); + + return nbytes; +} +static DEVICE_ATTR(nvm, S_IRUGO, smiapp_sysfs_nvm_read, NULL); + +static ssize_t +smiapp_sysfs_ident_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev)); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct smiapp_module_info *minfo = &sensor->minfo; + + return snprintf(buf, PAGE_SIZE, "%2.2x%4.4x%2.2x\n", + minfo->manufacturer_id, minfo->model_id, + minfo->revision_number_major) + 1; +} + +static DEVICE_ATTR(ident, S_IRUGO, smiapp_sysfs_ident_read, NULL); + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int smiapp_identify_module(struct v4l2_subdev *subdev) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct smiapp_module_info *minfo = &sensor->minfo; + unsigned int i; + int rval = 0; + + minfo->name = SMIAPP_NAME; + + /* Module info */ + rval = smiapp_read_8only(sensor, SMIAPP_REG_U8_MANUFACTURER_ID, + &minfo->manufacturer_id); + if (!rval) + rval = smiapp_read_8only(sensor, SMIAPP_REG_U16_MODEL_ID, + &minfo->model_id); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_REVISION_NUMBER_MAJOR, + &minfo->revision_number_major); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_REVISION_NUMBER_MINOR, + &minfo->revision_number_minor); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_MODULE_DATE_YEAR, + &minfo->module_year); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_MODULE_DATE_MONTH, + &minfo->module_month); + if (!rval) + rval = smiapp_read_8only(sensor, SMIAPP_REG_U8_MODULE_DATE_DAY, + &minfo->module_day); + + /* Sensor info */ + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_SENSOR_MANUFACTURER_ID, + &minfo->sensor_manufacturer_id); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U16_SENSOR_MODEL_ID, + &minfo->sensor_model_id); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_SENSOR_REVISION_NUMBER, + &minfo->sensor_revision_number); + if (!rval) + rval = smiapp_read_8only(sensor, + SMIAPP_REG_U8_SENSOR_FIRMWARE_VERSION, + &minfo->sensor_firmware_version); + + /* SMIA */ + if (!rval) + rval = smiapp_read_8only(sensor, SMIAPP_REG_U8_SMIA_VERSION, + &minfo->smia_version); + if (!rval) + rval = smiapp_read_8only(sensor, SMIAPP_REG_U8_SMIAPP_VERSION, + &minfo->smiapp_version); + + if (rval) { + dev_err(&client->dev, "sensor detection failed\n"); + return -ENODEV; + } + + dev_dbg(&client->dev, "module 0x%2.2x-0x%4.4x\n", + minfo->manufacturer_id, minfo->model_id); + + dev_dbg(&client->dev, + "module revision 0x%2.2x-0x%2.2x date %2.2d-%2.2d-%2.2d\n", + minfo->revision_number_major, minfo->revision_number_minor, + minfo->module_year, minfo->module_month, minfo->module_day); + + dev_dbg(&client->dev, "sensor 0x%2.2x-0x%4.4x\n", + minfo->sensor_manufacturer_id, minfo->sensor_model_id); + + dev_dbg(&client->dev, + "sensor revision 0x%2.2x firmware version 0x%2.2x\n", + minfo->sensor_revision_number, minfo->sensor_firmware_version); + + dev_dbg(&client->dev, "smia version %2.2d smiapp version %2.2d\n", + minfo->smia_version, minfo->smiapp_version); + + /* + * Some modules have bad data in the lvalues below. Hope the + * rvalues have better stuff. The lvalues are module + * parameters whereas the rvalues are sensor parameters. + */ + if (!minfo->manufacturer_id && !minfo->model_id) { + minfo->manufacturer_id = minfo->sensor_manufacturer_id; + minfo->model_id = minfo->sensor_model_id; + minfo->revision_number_major = minfo->sensor_revision_number; + } + + for (i = 0; i < ARRAY_SIZE(smiapp_module_idents); i++) { + if (smiapp_module_idents[i].manufacturer_id + != minfo->manufacturer_id) + continue; + if (smiapp_module_idents[i].model_id != minfo->model_id) + continue; + if (smiapp_module_idents[i].flags + & SMIAPP_MODULE_IDENT_FLAG_REV_LE) { + if (smiapp_module_idents[i].revision_number_major + < minfo->revision_number_major) + continue; + } else { + if (smiapp_module_idents[i].revision_number_major + != minfo->revision_number_major) + continue; + } + + minfo->name = smiapp_module_idents[i].name; + minfo->quirk = smiapp_module_idents[i].quirk; + break; + } + + if (i >= ARRAY_SIZE(smiapp_module_idents)) + dev_warn(&client->dev, + "no quirks for this module; let's hope it's fully compliant\n"); + + dev_dbg(&client->dev, "the sensor is called %s, ident %2.2x%4.4x%2.2x\n", + minfo->name, minfo->manufacturer_id, minfo->model_id, + minfo->revision_number_major); + + strlcpy(subdev->name, sensor->minfo.name, sizeof(subdev->name)); + + return 0; +} + +static const struct v4l2_subdev_ops smiapp_ops; +static const struct v4l2_subdev_internal_ops smiapp_internal_ops; +static const struct media_entity_operations smiapp_entity_ops; + +static int smiapp_registered(struct v4l2_subdev *subdev) +{ + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct smiapp_subdev *last = NULL; + u32 tmp; + unsigned int i; + int rval; + + sensor->vana = devm_regulator_get(&client->dev, "VANA"); + if (IS_ERR(sensor->vana)) { + dev_err(&client->dev, "could not get regulator for vana\n"); + return -ENODEV; + } + + if (!sensor->platform_data->set_xclk) { + sensor->ext_clk = devm_clk_get(&client->dev, + sensor->platform_data->ext_clk_name); + if (IS_ERR(sensor->ext_clk)) { + dev_err(&client->dev, "could not get clock %s\n", + sensor->platform_data->ext_clk_name); + return -ENODEV; + } + + rval = clk_set_rate(sensor->ext_clk, + sensor->platform_data->ext_clk); + if (rval < 0) { + dev_err(&client->dev, + "unable to set clock %s freq to %u\n", + sensor->platform_data->ext_clk_name, + sensor->platform_data->ext_clk); + return -ENODEV; + } + } + + if (sensor->platform_data->xshutdown != SMIAPP_NO_XSHUTDOWN) { + if (gpio_request_one(sensor->platform_data->xshutdown, 0, + "SMIA++ xshutdown") != 0) { + dev_err(&client->dev, + "unable to acquire reset gpio %d\n", + sensor->platform_data->xshutdown); + return -ENODEV; + } + } + + rval = smiapp_power_on(sensor); + if (rval) { + rval = -ENODEV; + goto out_smiapp_power_on; + } + + rval = smiapp_identify_module(subdev); + if (rval) { + rval = -ENODEV; + goto out_power_off; + } + + rval = smiapp_get_all_limits(sensor); + if (rval) { + rval = -ENODEV; + goto out_power_off; + } + + /* + * Handle Sensor Module orientation on the board. + * + * The application of H-FLIP and V-FLIP on the sensor is modified by + * the sensor orientation on the board. + * + * For SMIAPP_BOARD_SENSOR_ORIENT_180 the default behaviour is to set + * both H-FLIP and V-FLIP for normal operation which also implies + * that a set/unset operation for user space HFLIP and VFLIP v4l2 + * controls will need to be internally inverted. + * + * Rotation also changes the bayer pattern. + */ + if (sensor->platform_data->module_board_orient == + SMIAPP_MODULE_BOARD_ORIENT_180) + sensor->hvflip_inv_mask = SMIAPP_IMAGE_ORIENTATION_HFLIP | + SMIAPP_IMAGE_ORIENTATION_VFLIP; + + rval = smiapp_get_mbus_formats(sensor); + if (rval) { + rval = -ENODEV; + goto out_power_off; + } + + if (sensor->limits[SMIAPP_LIMIT_BINNING_CAPABILITY]) { + u32 val; + + rval = smiapp_read(sensor, + SMIAPP_REG_U8_BINNING_SUBTYPES, &val); + if (rval < 0) { + rval = -ENODEV; + goto out_power_off; + } + sensor->nbinning_subtypes = min_t(u8, val, + SMIAPP_BINNING_SUBTYPES); + + for (i = 0; i < sensor->nbinning_subtypes; i++) { + rval = smiapp_read( + sensor, SMIAPP_REG_U8_BINNING_TYPE_n(i), &val); + if (rval < 0) { + rval = -ENODEV; + goto out_power_off; + } + sensor->binning_subtypes[i] = + *(struct smiapp_binning_subtype *)&val; + + dev_dbg(&client->dev, "binning %xx%x\n", + sensor->binning_subtypes[i].horizontal, + sensor->binning_subtypes[i].vertical); + } + } + sensor->binning_horizontal = 1; + sensor->binning_vertical = 1; + + if (device_create_file(&client->dev, &dev_attr_ident) != 0) { + dev_err(&client->dev, "sysfs ident entry creation failed\n"); + rval = -ENOENT; + goto out_power_off; + } + /* SMIA++ NVM initialization - it will be read from the sensor + * when it is first requested by userspace. + */ + if (sensor->minfo.smiapp_version && sensor->platform_data->nvm_size) { + sensor->nvm = devm_kzalloc(&client->dev, + sensor->platform_data->nvm_size, GFP_KERNEL); + if (sensor->nvm == NULL) { + dev_err(&client->dev, "nvm buf allocation failed\n"); + rval = -ENOMEM; + goto out_ident_release; + } + + if (device_create_file(&client->dev, &dev_attr_nvm) != 0) { + dev_err(&client->dev, "sysfs nvm entry failed\n"); + rval = -EBUSY; + goto out_ident_release; + } + } + + rval = smiapp_call_quirk(sensor, limits); + if (rval) { + dev_err(&client->dev, "limits quirks failed\n"); + goto out_nvm_release; + } + + /* We consider this as profile 0 sensor if any of these are zero. */ + if (!sensor->limits[SMIAPP_LIMIT_MIN_OP_SYS_CLK_DIV] || + !sensor->limits[SMIAPP_LIMIT_MAX_OP_SYS_CLK_DIV] || + !sensor->limits[SMIAPP_LIMIT_MIN_OP_PIX_CLK_DIV] || + !sensor->limits[SMIAPP_LIMIT_MAX_OP_PIX_CLK_DIV]) { + sensor->minfo.smiapp_profile = SMIAPP_PROFILE_0; + } else if (sensor->limits[SMIAPP_LIMIT_SCALING_CAPABILITY] + != SMIAPP_SCALING_CAPABILITY_NONE) { + if (sensor->limits[SMIAPP_LIMIT_SCALING_CAPABILITY] + == SMIAPP_SCALING_CAPABILITY_HORIZONTAL) + sensor->minfo.smiapp_profile = SMIAPP_PROFILE_1; + else + sensor->minfo.smiapp_profile = SMIAPP_PROFILE_2; + sensor->scaler = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + } else if (sensor->limits[SMIAPP_LIMIT_DIGITAL_CROP_CAPABILITY] + == SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP) { + sensor->scaler = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + } + sensor->binner = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + sensor->pixel_array = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + + sensor->scale_m = sensor->limits[SMIAPP_LIMIT_SCALER_N_MIN]; + + for (i = 0; i < SMIAPP_SUBDEVS; i++) { + struct { + struct smiapp_subdev *ssd; + char *name; + } const __this[] = { + { sensor->scaler, "scaler", }, + { sensor->binner, "binner", }, + { sensor->pixel_array, "pixel array", }, + }, *_this = &__this[i]; + struct smiapp_subdev *this = _this->ssd; + + if (!this) + continue; + + if (this != sensor->src) + v4l2_subdev_init(&this->sd, &smiapp_ops); + + this->sensor = sensor; + + if (this == sensor->pixel_array) { + this->npads = 1; + } else { + this->npads = 2; + this->source_pad = 1; + } + + snprintf(this->sd.name, + sizeof(this->sd.name), "%s %s", + sensor->minfo.name, _this->name); + + this->sink_fmt.width = + sensor->limits[SMIAPP_LIMIT_X_ADDR_MAX] + 1; + this->sink_fmt.height = + sensor->limits[SMIAPP_LIMIT_Y_ADDR_MAX] + 1; + this->compose.width = this->sink_fmt.width; + this->compose.height = this->sink_fmt.height; + this->crop[this->source_pad] = this->compose; + this->pads[this->source_pad].flags = MEDIA_PAD_FL_SOURCE; + if (this != sensor->pixel_array) { + this->crop[this->sink_pad] = this->compose; + this->pads[this->sink_pad].flags = MEDIA_PAD_FL_SINK; + } + + this->sd.entity.ops = &smiapp_entity_ops; + + if (last == NULL) { + last = this; + continue; + } + + this->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + this->sd.internal_ops = &smiapp_internal_ops; + this->sd.owner = NULL; + v4l2_set_subdevdata(&this->sd, client); + + rval = media_entity_init(&this->sd.entity, + this->npads, this->pads, 0); + if (rval) { + dev_err(&client->dev, + "media_entity_init failed\n"); + goto out_nvm_release; + } + + rval = media_entity_create_link(&this->sd.entity, + this->source_pad, + &last->sd.entity, + last->sink_pad, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (rval) { + dev_err(&client->dev, + "media_entity_create_link failed\n"); + goto out_nvm_release; + } + + rval = v4l2_device_register_subdev(sensor->src->sd.v4l2_dev, + &this->sd); + if (rval) { + dev_err(&client->dev, + "v4l2_device_register_subdev failed\n"); + goto out_nvm_release; + } + + last = this; + } + + dev_dbg(&client->dev, "profile %d\n", sensor->minfo.smiapp_profile); + + sensor->pixel_array->sd.entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; + + /* final steps */ + smiapp_read_frame_fmt(sensor); + rval = smiapp_init_controls(sensor); + if (rval < 0) + goto out_nvm_release; + + rval = smiapp_update_mode(sensor); + if (rval) { + dev_err(&client->dev, "update mode failed\n"); + goto out_nvm_release; + } + + sensor->streaming = false; + sensor->dev_init_done = true; + + /* check flash capability */ + rval = smiapp_read(sensor, SMIAPP_REG_U8_FLASH_MODE_CAPABILITY, &tmp); + sensor->flash_capability = tmp; + if (rval) + goto out_nvm_release; + + smiapp_power_off(sensor); + + return 0; + +out_nvm_release: + device_remove_file(&client->dev, &dev_attr_nvm); + +out_ident_release: + device_remove_file(&client->dev, &dev_attr_ident); + +out_power_off: + smiapp_power_off(sensor); + +out_smiapp_power_on: + if (sensor->platform_data->xshutdown != SMIAPP_NO_XSHUTDOWN) + gpio_free(sensor->platform_data->xshutdown); + + return rval; +} + +static int smiapp_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct smiapp_subdev *ssd = to_smiapp_subdev(sd); + struct smiapp_sensor *sensor = ssd->sensor; + u32 mbus_code = + smiapp_csi_data_formats[smiapp_pixel_order(sensor)].code; + unsigned int i; + + mutex_lock(&sensor->mutex); + + for (i = 0; i < ssd->npads; i++) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(fh, i); + struct v4l2_rect *try_crop = v4l2_subdev_get_try_crop(fh, i); + struct v4l2_rect *try_comp; + + try_fmt->width = sensor->limits[SMIAPP_LIMIT_X_ADDR_MAX] + 1; + try_fmt->height = sensor->limits[SMIAPP_LIMIT_Y_ADDR_MAX] + 1; + try_fmt->code = mbus_code; + + try_crop->top = 0; + try_crop->left = 0; + try_crop->width = try_fmt->width; + try_crop->height = try_fmt->height; + + if (ssd != sensor->pixel_array) + continue; + + try_comp = v4l2_subdev_get_try_compose(fh, i); + *try_comp = *try_crop; + } + + mutex_unlock(&sensor->mutex); + + return smiapp_set_power(sd, 1); +} + +static int smiapp_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return smiapp_set_power(sd, 0); +} + +static const struct v4l2_subdev_video_ops smiapp_video_ops = { + .s_stream = smiapp_set_stream, +}; + +static const struct v4l2_subdev_core_ops smiapp_core_ops = { + .s_power = smiapp_set_power, +}; + +static const struct v4l2_subdev_pad_ops smiapp_pad_ops = { + .enum_mbus_code = smiapp_enum_mbus_code, + .get_fmt = smiapp_get_format, + .set_fmt = smiapp_set_format, + .get_selection = smiapp_get_selection, + .set_selection = smiapp_set_selection, +}; + +static const struct v4l2_subdev_sensor_ops smiapp_sensor_ops = { + .g_skip_frames = smiapp_get_skip_frames, +}; + +static const struct v4l2_subdev_ops smiapp_ops = { + .core = &smiapp_core_ops, + .video = &smiapp_video_ops, + .pad = &smiapp_pad_ops, + .sensor = &smiapp_sensor_ops, +}; + +static const struct media_entity_operations smiapp_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops smiapp_internal_src_ops = { + .registered = smiapp_registered, + .open = smiapp_open, + .close = smiapp_close, +}; + +static const struct v4l2_subdev_internal_ops smiapp_internal_ops = { + .open = smiapp_open, + .close = smiapp_close, +}; + +/* ----------------------------------------------------------------------------- + * I2C Driver + */ + +#ifdef CONFIG_PM + +static int smiapp_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + bool streaming; + + BUG_ON(mutex_is_locked(&sensor->mutex)); + + if (sensor->power_count == 0) + return 0; + + if (sensor->streaming) + smiapp_stop_streaming(sensor); + + streaming = sensor->streaming; + + smiapp_power_off(sensor); + + /* save state for resume */ + sensor->streaming = streaming; + + return 0; +} + +static int smiapp_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + int rval; + + if (sensor->power_count == 0) + return 0; + + rval = smiapp_power_on(sensor); + if (rval) + return rval; + + if (sensor->streaming) + rval = smiapp_start_streaming(sensor); + + return rval; +} + +#else + +#define smiapp_suspend NULL +#define smiapp_resume NULL + +#endif /* CONFIG_PM */ + +static int smiapp_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct smiapp_sensor *sensor; + + if (client->dev.platform_data == NULL) + return -ENODEV; + + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (sensor == NULL) + return -ENOMEM; + + sensor->platform_data = client->dev.platform_data; + mutex_init(&sensor->mutex); + mutex_init(&sensor->power_mutex); + sensor->src = &sensor->ssds[sensor->ssds_used]; + + v4l2_i2c_subdev_init(&sensor->src->sd, client, &smiapp_ops); + sensor->src->sd.internal_ops = &smiapp_internal_src_ops; + sensor->src->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->src->sensor = sensor; + + sensor->src->pads[0].flags = MEDIA_PAD_FL_SOURCE; + return media_entity_init(&sensor->src->sd.entity, 2, + sensor->src->pads, 0); +} + +static int __exit smiapp_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct smiapp_sensor *sensor = to_smiapp_sensor(subdev); + unsigned int i; + + if (sensor->power_count) { + if (sensor->platform_data->xshutdown != SMIAPP_NO_XSHUTDOWN) + gpio_set_value(sensor->platform_data->xshutdown, 0); + if (sensor->platform_data->set_xclk) + sensor->platform_data->set_xclk(&sensor->src->sd, 0); + else + clk_disable(sensor->ext_clk); + sensor->power_count = 0; + } + + device_remove_file(&client->dev, &dev_attr_ident); + if (sensor->nvm) + device_remove_file(&client->dev, &dev_attr_nvm); + + for (i = 0; i < sensor->ssds_used; i++) { + media_entity_cleanup(&sensor->ssds[i].sd.entity); + v4l2_device_unregister_subdev(&sensor->ssds[i].sd); + } + smiapp_free_controls(sensor); + if (sensor->platform_data->xshutdown != SMIAPP_NO_XSHUTDOWN) + gpio_free(sensor->platform_data->xshutdown); + + return 0; +} + +static const struct i2c_device_id smiapp_id_table[] = { + { SMIAPP_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, smiapp_id_table); + +static const struct dev_pm_ops smiapp_pm_ops = { + .suspend = smiapp_suspend, + .resume = smiapp_resume, +}; + +static struct i2c_driver smiapp_i2c_driver = { + .driver = { + .name = SMIAPP_NAME, + .pm = &smiapp_pm_ops, + }, + .probe = smiapp_probe, + .remove = __exit_p(smiapp_remove), + .id_table = smiapp_id_table, +}; + +module_i2c_driver(smiapp_i2c_driver); + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_DESCRIPTION("Generic SMIA/SMIA++ camera module driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/smiapp/smiapp-limits.c b/drivers/media/i2c/smiapp/smiapp-limits.c new file mode 100644 index 000000000000..fb2f81ad8c3b --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-limits.c @@ -0,0 +1,132 @@ +/* + * drivers/media/i2c/smiapp/smiapp-limits.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "smiapp.h" + +struct smiapp_reg_limits smiapp_reg_limits[] = { + { SMIAPP_REG_U16_ANALOGUE_GAIN_CAPABILITY, "analogue_gain_capability" }, /* 0 */ + { SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MIN, "analogue_gain_code_min" }, + { SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MAX, "analogue_gain_code_max" }, + { SMIAPP_REG_U8_THS_ZERO_MIN, "ths_zero_min" }, + { SMIAPP_REG_U8_TCLK_TRAIL_MIN, "tclk_trail_min" }, + { SMIAPP_REG_U16_INTEGRATION_TIME_CAPABILITY, "integration_time_capability" }, /* 5 */ + { SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MIN, "coarse_integration_time_min" }, + { SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MAX_MARGIN, "coarse_integration_time_max_margin" }, + { SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN, "fine_integration_time_min" }, + { SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN, "fine_integration_time_max_margin" }, + { SMIAPP_REG_U16_DIGITAL_GAIN_CAPABILITY, "digital_gain_capability" }, /* 10 */ + { SMIAPP_REG_U16_DIGITAL_GAIN_MIN, "digital_gain_min" }, + { SMIAPP_REG_U16_DIGITAL_GAIN_MAX, "digital_gain_max" }, + { SMIAPP_REG_F32_MIN_EXT_CLK_FREQ_HZ, "min_ext_clk_freq_hz" }, + { SMIAPP_REG_F32_MAX_EXT_CLK_FREQ_HZ, "max_ext_clk_freq_hz" }, + { SMIAPP_REG_U16_MIN_PRE_PLL_CLK_DIV, "min_pre_pll_clk_div" }, /* 15 */ + { SMIAPP_REG_U16_MAX_PRE_PLL_CLK_DIV, "max_pre_pll_clk_div" }, + { SMIAPP_REG_F32_MIN_PLL_IP_FREQ_HZ, "min_pll_ip_freq_hz" }, + { SMIAPP_REG_F32_MAX_PLL_IP_FREQ_HZ, "max_pll_ip_freq_hz" }, + { SMIAPP_REG_U16_MIN_PLL_MULTIPLIER, "min_pll_multiplier" }, + { SMIAPP_REG_U16_MAX_PLL_MULTIPLIER, "max_pll_multiplier" }, /* 20 */ + { SMIAPP_REG_F32_MIN_PLL_OP_FREQ_HZ, "min_pll_op_freq_hz" }, + { SMIAPP_REG_F32_MAX_PLL_OP_FREQ_HZ, "max_pll_op_freq_hz" }, + { SMIAPP_REG_U16_MIN_VT_SYS_CLK_DIV, "min_vt_sys_clk_div" }, + { SMIAPP_REG_U16_MAX_VT_SYS_CLK_DIV, "max_vt_sys_clk_div" }, + { SMIAPP_REG_F32_MIN_VT_SYS_CLK_FREQ_HZ, "min_vt_sys_clk_freq_hz" }, /* 25 */ + { SMIAPP_REG_F32_MAX_VT_SYS_CLK_FREQ_HZ, "max_vt_sys_clk_freq_hz" }, + { SMIAPP_REG_F32_MIN_VT_PIX_CLK_FREQ_HZ, "min_vt_pix_clk_freq_hz" }, + { SMIAPP_REG_F32_MAX_VT_PIX_CLK_FREQ_HZ, "max_vt_pix_clk_freq_hz" }, + { SMIAPP_REG_U16_MIN_VT_PIX_CLK_DIV, "min_vt_pix_clk_div" }, + { SMIAPP_REG_U16_MAX_VT_PIX_CLK_DIV, "max_vt_pix_clk_div" }, /* 30 */ + { SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES, "min_frame_length_lines" }, + { SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES, "max_frame_length_lines" }, + { SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK, "min_line_length_pck" }, + { SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK, "max_line_length_pck" }, + { SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK, "min_line_blanking_pck" }, /* 35 */ + { SMIAPP_REG_U16_MIN_FRAME_BLANKING_LINES, "min_frame_blanking_lines" }, + { SMIAPP_REG_U8_MIN_LINE_LENGTH_PCK_STEP_SIZE, "min_line_length_pck_step_size" }, + { SMIAPP_REG_U16_MIN_OP_SYS_CLK_DIV, "min_op_sys_clk_div" }, + { SMIAPP_REG_U16_MAX_OP_SYS_CLK_DIV, "max_op_sys_clk_div" }, + { SMIAPP_REG_F32_MIN_OP_SYS_CLK_FREQ_HZ, "min_op_sys_clk_freq_hz" }, /* 40 */ + { SMIAPP_REG_F32_MAX_OP_SYS_CLK_FREQ_HZ, "max_op_sys_clk_freq_hz" }, + { SMIAPP_REG_U16_MIN_OP_PIX_CLK_DIV, "min_op_pix_clk_div" }, + { SMIAPP_REG_U16_MAX_OP_PIX_CLK_DIV, "max_op_pix_clk_div" }, + { SMIAPP_REG_F32_MIN_OP_PIX_CLK_FREQ_HZ, "min_op_pix_clk_freq_hz" }, + { SMIAPP_REG_F32_MAX_OP_PIX_CLK_FREQ_HZ, "max_op_pix_clk_freq_hz" }, /* 45 */ + { SMIAPP_REG_U16_X_ADDR_MIN, "x_addr_min" }, + { SMIAPP_REG_U16_Y_ADDR_MIN, "y_addr_min" }, + { SMIAPP_REG_U16_X_ADDR_MAX, "x_addr_max" }, + { SMIAPP_REG_U16_Y_ADDR_MAX, "y_addr_max" }, + { SMIAPP_REG_U16_MIN_X_OUTPUT_SIZE, "min_x_output_size" }, /* 50 */ + { SMIAPP_REG_U16_MIN_Y_OUTPUT_SIZE, "min_y_output_size" }, + { SMIAPP_REG_U16_MAX_X_OUTPUT_SIZE, "max_x_output_size" }, + { SMIAPP_REG_U16_MAX_Y_OUTPUT_SIZE, "max_y_output_size" }, + { SMIAPP_REG_U16_MIN_EVEN_INC, "min_even_inc" }, + { SMIAPP_REG_U16_MAX_EVEN_INC, "max_even_inc" }, /* 55 */ + { SMIAPP_REG_U16_MIN_ODD_INC, "min_odd_inc" }, + { SMIAPP_REG_U16_MAX_ODD_INC, "max_odd_inc" }, + { SMIAPP_REG_U16_SCALING_CAPABILITY, "scaling_capability" }, + { SMIAPP_REG_U16_SCALER_M_MIN, "scaler_m_min" }, + { SMIAPP_REG_U16_SCALER_M_MAX, "scaler_m_max" }, /* 60 */ + { SMIAPP_REG_U16_SCALER_N_MIN, "scaler_n_min" }, + { SMIAPP_REG_U16_SCALER_N_MAX, "scaler_n_max" }, + { SMIAPP_REG_U16_SPATIAL_SAMPLING_CAPABILITY, "spatial_sampling_capability" }, + { SMIAPP_REG_U8_DIGITAL_CROP_CAPABILITY, "digital_crop_capability" }, + { SMIAPP_REG_U16_COMPRESSION_CAPABILITY, "compression_capability" }, /* 65 */ + { SMIAPP_REG_U8_FIFO_SUPPORT_CAPABILITY, "fifo_support_capability" }, + { SMIAPP_REG_U8_DPHY_CTRL_CAPABILITY, "dphy_ctrl_capability" }, + { SMIAPP_REG_U8_CSI_LANE_MODE_CAPABILITY, "csi_lane_mode_capability" }, + { SMIAPP_REG_U8_CSI_SIGNALLING_MODE_CAPABILITY, "csi_signalling_mode_capability" }, + { SMIAPP_REG_U8_FAST_STANDBY_CAPABILITY, "fast_standby_capability" }, /* 70 */ + { SMIAPP_REG_U8_CCI_ADDRESS_CONTROL_CAPABILITY, "cci_address_control_capability" }, + { SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_1_LANE_MODE_MBPS, "max_per_lane_bitrate_1_lane_mode_mbps" }, + { SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_2_LANE_MODE_MBPS, "max_per_lane_bitrate_2_lane_mode_mbps" }, + { SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_3_LANE_MODE_MBPS, "max_per_lane_bitrate_3_lane_mode_mbps" }, + { SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_4_LANE_MODE_MBPS, "max_per_lane_bitrate_4_lane_mode_mbps" }, /* 75 */ + { SMIAPP_REG_U8_TEMP_SENSOR_CAPABILITY, "temp_sensor_capability" }, + { SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES_BIN, "min_frame_length_lines_bin" }, + { SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES_BIN, "max_frame_length_lines_bin" }, + { SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK_BIN, "min_line_length_pck_bin" }, + { SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK_BIN, "max_line_length_pck_bin" }, /* 80 */ + { SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK_BIN, "min_line_blanking_pck_bin" }, + { SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN_BIN, "fine_integration_time_min_bin" }, + { SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN, "fine_integration_time_max_margin_bin" }, + { SMIAPP_REG_U8_BINNING_CAPABILITY, "binning_capability" }, + { SMIAPP_REG_U8_BINNING_WEIGHTING_CAPABILITY, "binning_weighting_capability" }, /* 85 */ + { SMIAPP_REG_U8_DATA_TRANSFER_IF_CAPABILITY, "data_transfer_if_capability" }, + { SMIAPP_REG_U8_SHADING_CORRECTION_CAPABILITY, "shading_correction_capability" }, + { SMIAPP_REG_U8_GREEN_IMBALANCE_CAPABILITY, "green_imbalance_capability" }, + { SMIAPP_REG_U8_BLACK_LEVEL_CAPABILITY, "black_level_capability" }, + { SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_CAPABILITY, "module_specific_correction_capability" }, /* 90 */ + { SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY, "defect_correction_capability" }, + { SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY_2, "defect_correction_capability_2" }, + { SMIAPP_REG_U8_EDOF_CAPABILITY, "edof_capability" }, + { SMIAPP_REG_U8_COLOUR_FEEDBACK_CAPABILITY, "colour_feedback_capability" }, + { SMIAPP_REG_U8_ESTIMATION_MODE_CAPABILITY, "estimation_mode_capability" }, /* 95 */ + { SMIAPP_REG_U8_ESTIMATION_ZONE_CAPABILITY, "estimation_zone_capability" }, + { SMIAPP_REG_U16_CAPABILITY_TRDY_MIN, "capability_trdy_min" }, + { SMIAPP_REG_U8_FLASH_MODE_CAPABILITY, "flash_mode_capability" }, + { SMIAPP_REG_U8_ACTUATOR_CAPABILITY, "actuator_capability" }, + { SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_1, "bracketing_lut_capability_1" }, /* 100 */ + { SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_2, "bracketing_lut_capability_2" }, + { SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_STEP, "analogue_gain_code_step" }, + { 0, NULL }, +}; diff --git a/drivers/media/i2c/smiapp/smiapp-limits.h b/drivers/media/i2c/smiapp/smiapp-limits.h new file mode 100644 index 000000000000..9ae765e23ea5 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-limits.h @@ -0,0 +1,128 @@ +/* + * drivers/media/i2c/smiapp/smiapp-limits.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#define SMIAPP_LIMIT_ANALOGUE_GAIN_CAPABILITY 0 +#define SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MIN 1 +#define SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MAX 2 +#define SMIAPP_LIMIT_THS_ZERO_MIN 3 +#define SMIAPP_LIMIT_TCLK_TRAIL_MIN 4 +#define SMIAPP_LIMIT_INTEGRATION_TIME_CAPABILITY 5 +#define SMIAPP_LIMIT_COARSE_INTEGRATION_TIME_MIN 6 +#define SMIAPP_LIMIT_COARSE_INTEGRATION_TIME_MAX_MARGIN 7 +#define SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MIN 8 +#define SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MAX_MARGIN 9 +#define SMIAPP_LIMIT_DIGITAL_GAIN_CAPABILITY 10 +#define SMIAPP_LIMIT_DIGITAL_GAIN_MIN 11 +#define SMIAPP_LIMIT_DIGITAL_GAIN_MAX 12 +#define SMIAPP_LIMIT_MIN_EXT_CLK_FREQ_HZ 13 +#define SMIAPP_LIMIT_MAX_EXT_CLK_FREQ_HZ 14 +#define SMIAPP_LIMIT_MIN_PRE_PLL_CLK_DIV 15 +#define SMIAPP_LIMIT_MAX_PRE_PLL_CLK_DIV 16 +#define SMIAPP_LIMIT_MIN_PLL_IP_FREQ_HZ 17 +#define SMIAPP_LIMIT_MAX_PLL_IP_FREQ_HZ 18 +#define SMIAPP_LIMIT_MIN_PLL_MULTIPLIER 19 +#define SMIAPP_LIMIT_MAX_PLL_MULTIPLIER 20 +#define SMIAPP_LIMIT_MIN_PLL_OP_FREQ_HZ 21 +#define SMIAPP_LIMIT_MAX_PLL_OP_FREQ_HZ 22 +#define SMIAPP_LIMIT_MIN_VT_SYS_CLK_DIV 23 +#define SMIAPP_LIMIT_MAX_VT_SYS_CLK_DIV 24 +#define SMIAPP_LIMIT_MIN_VT_SYS_CLK_FREQ_HZ 25 +#define SMIAPP_LIMIT_MAX_VT_SYS_CLK_FREQ_HZ 26 +#define SMIAPP_LIMIT_MIN_VT_PIX_CLK_FREQ_HZ 27 +#define SMIAPP_LIMIT_MAX_VT_PIX_CLK_FREQ_HZ 28 +#define SMIAPP_LIMIT_MIN_VT_PIX_CLK_DIV 29 +#define SMIAPP_LIMIT_MAX_VT_PIX_CLK_DIV 30 +#define SMIAPP_LIMIT_MIN_FRAME_LENGTH_LINES 31 +#define SMIAPP_LIMIT_MAX_FRAME_LENGTH_LINES 32 +#define SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK 33 +#define SMIAPP_LIMIT_MAX_LINE_LENGTH_PCK 34 +#define SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK 35 +#define SMIAPP_LIMIT_MIN_FRAME_BLANKING_LINES 36 +#define SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK_STEP_SIZE 37 +#define SMIAPP_LIMIT_MIN_OP_SYS_CLK_DIV 38 +#define SMIAPP_LIMIT_MAX_OP_SYS_CLK_DIV 39 +#define SMIAPP_LIMIT_MIN_OP_SYS_CLK_FREQ_HZ 40 +#define SMIAPP_LIMIT_MAX_OP_SYS_CLK_FREQ_HZ 41 +#define SMIAPP_LIMIT_MIN_OP_PIX_CLK_DIV 42 +#define SMIAPP_LIMIT_MAX_OP_PIX_CLK_DIV 43 +#define SMIAPP_LIMIT_MIN_OP_PIX_CLK_FREQ_HZ 44 +#define SMIAPP_LIMIT_MAX_OP_PIX_CLK_FREQ_HZ 45 +#define SMIAPP_LIMIT_X_ADDR_MIN 46 +#define SMIAPP_LIMIT_Y_ADDR_MIN 47 +#define SMIAPP_LIMIT_X_ADDR_MAX 48 +#define SMIAPP_LIMIT_Y_ADDR_MAX 49 +#define SMIAPP_LIMIT_MIN_X_OUTPUT_SIZE 50 +#define SMIAPP_LIMIT_MIN_Y_OUTPUT_SIZE 51 +#define SMIAPP_LIMIT_MAX_X_OUTPUT_SIZE 52 +#define SMIAPP_LIMIT_MAX_Y_OUTPUT_SIZE 53 +#define SMIAPP_LIMIT_MIN_EVEN_INC 54 +#define SMIAPP_LIMIT_MAX_EVEN_INC 55 +#define SMIAPP_LIMIT_MIN_ODD_INC 56 +#define SMIAPP_LIMIT_MAX_ODD_INC 57 +#define SMIAPP_LIMIT_SCALING_CAPABILITY 58 +#define SMIAPP_LIMIT_SCALER_M_MIN 59 +#define SMIAPP_LIMIT_SCALER_M_MAX 60 +#define SMIAPP_LIMIT_SCALER_N_MIN 61 +#define SMIAPP_LIMIT_SCALER_N_MAX 62 +#define SMIAPP_LIMIT_SPATIAL_SAMPLING_CAPABILITY 63 +#define SMIAPP_LIMIT_DIGITAL_CROP_CAPABILITY 64 +#define SMIAPP_LIMIT_COMPRESSION_CAPABILITY 65 +#define SMIAPP_LIMIT_FIFO_SUPPORT_CAPABILITY 66 +#define SMIAPP_LIMIT_DPHY_CTRL_CAPABILITY 67 +#define SMIAPP_LIMIT_CSI_LANE_MODE_CAPABILITY 68 +#define SMIAPP_LIMIT_CSI_SIGNALLING_MODE_CAPABILITY 69 +#define SMIAPP_LIMIT_FAST_STANDBY_CAPABILITY 70 +#define SMIAPP_LIMIT_CCI_ADDRESS_CONTROL_CAPABILITY 71 +#define SMIAPP_LIMIT_MAX_PER_LANE_BITRATE_1_LANE_MODE_MBPS 72 +#define SMIAPP_LIMIT_MAX_PER_LANE_BITRATE_2_LANE_MODE_MBPS 73 +#define SMIAPP_LIMIT_MAX_PER_LANE_BITRATE_3_LANE_MODE_MBPS 74 +#define SMIAPP_LIMIT_MAX_PER_LANE_BITRATE_4_LANE_MODE_MBPS 75 +#define SMIAPP_LIMIT_TEMP_SENSOR_CAPABILITY 76 +#define SMIAPP_LIMIT_MIN_FRAME_LENGTH_LINES_BIN 77 +#define SMIAPP_LIMIT_MAX_FRAME_LENGTH_LINES_BIN 78 +#define SMIAPP_LIMIT_MIN_LINE_LENGTH_PCK_BIN 79 +#define SMIAPP_LIMIT_MAX_LINE_LENGTH_PCK_BIN 80 +#define SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK_BIN 81 +#define SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MIN_BIN 82 +#define SMIAPP_LIMIT_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN 83 +#define SMIAPP_LIMIT_BINNING_CAPABILITY 84 +#define SMIAPP_LIMIT_BINNING_WEIGHTING_CAPABILITY 85 +#define SMIAPP_LIMIT_DATA_TRANSFER_IF_CAPABILITY 86 +#define SMIAPP_LIMIT_SHADING_CORRECTION_CAPABILITY 87 +#define SMIAPP_LIMIT_GREEN_IMBALANCE_CAPABILITY 88 +#define SMIAPP_LIMIT_BLACK_LEVEL_CAPABILITY 89 +#define SMIAPP_LIMIT_MODULE_SPECIFIC_CORRECTION_CAPABILITY 90 +#define SMIAPP_LIMIT_DEFECT_CORRECTION_CAPABILITY 91 +#define SMIAPP_LIMIT_DEFECT_CORRECTION_CAPABILITY_2 92 +#define SMIAPP_LIMIT_EDOF_CAPABILITY 93 +#define SMIAPP_LIMIT_COLOUR_FEEDBACK_CAPABILITY 94 +#define SMIAPP_LIMIT_ESTIMATION_MODE_CAPABILITY 95 +#define SMIAPP_LIMIT_ESTIMATION_ZONE_CAPABILITY 96 +#define SMIAPP_LIMIT_CAPABILITY_TRDY_MIN 97 +#define SMIAPP_LIMIT_FLASH_MODE_CAPABILITY 98 +#define SMIAPP_LIMIT_ACTUATOR_CAPABILITY 99 +#define SMIAPP_LIMIT_BRACKETING_LUT_CAPABILITY_1 100 +#define SMIAPP_LIMIT_BRACKETING_LUT_CAPABILITY_2 101 +#define SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_STEP 102 +#define SMIAPP_LIMIT_LAST 103 diff --git a/drivers/media/i2c/smiapp/smiapp-quirk.c b/drivers/media/i2c/smiapp/smiapp-quirk.c new file mode 100644 index 000000000000..725cf62836c6 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-quirk.c @@ -0,0 +1,286 @@ +/* + * drivers/media/i2c/smiapp/smiapp-quirk.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include + +#include "smiapp.h" + +static int smiapp_write_8(struct smiapp_sensor *sensor, u16 reg, u8 val) +{ + return smiapp_write(sensor, (SMIA_REG_8BIT << 16) | reg, val); +} + +static int smiapp_write_8s(struct smiapp_sensor *sensor, + struct smiapp_reg_8 *regs, int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + for (; len > 0; len--, regs++) { + rval = smiapp_write_8(sensor, regs->reg, regs->val); + if (rval < 0) { + dev_err(&client->dev, + "error %d writing reg 0x%4.4x, val 0x%2.2x", + rval, regs->reg, regs->val); + return rval; + } + } + + return 0; +} + +void smiapp_replace_limit(struct smiapp_sensor *sensor, + u32 limit, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + + dev_dbg(&client->dev, "quirk: 0x%8.8x \"%s\" = %d, 0x%x\n", + smiapp_reg_limits[limit].addr, + smiapp_reg_limits[limit].what, val, val); + sensor->limits[limit] = val; +} + +bool smiapp_quirk_reg(struct smiapp_sensor *sensor, + u32 reg, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + const struct smia_reg *sreg; + + if (!sensor->minfo.quirk) + return false; + + sreg = sensor->minfo.quirk->regs; + + if (!sreg) + return false; + + while (sreg->type) { + u16 type = reg >> 16; + u16 reg16 = reg; + + if (sreg->type != type || sreg->reg != reg16) { + sreg++; + continue; + } + + switch ((u8)type) { + case SMIA_REG_8BIT: + dev_dbg(&client->dev, "quirk: 0x%8.8x: 0x%2.2x\n", + reg, sreg->val); + break; + case SMIA_REG_16BIT: + dev_dbg(&client->dev, "quirk: 0x%8.8x: 0x%4.4x\n", + reg, sreg->val); + break; + case SMIA_REG_32BIT: + dev_dbg(&client->dev, "quirk: 0x%8.8x: 0x%8.8x\n", + reg, sreg->val); + break; + } + + *val = sreg->val; + + return true; + } + + return false; +} + +static int jt8ew9_limits(struct smiapp_sensor *sensor) +{ + if (sensor->minfo.revision_number_major < 0x03) + sensor->frame_skip = 1; + + /* Below 24 gain doesn't have effect at all, */ + /* but ~59 is needed for full dynamic range */ + smiapp_replace_limit(sensor, SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MIN, 59); + smiapp_replace_limit( + sensor, SMIAPP_LIMIT_ANALOGUE_GAIN_CODE_MAX, 6000); + + return 0; +} + +static int jt8ew9_post_poweron(struct smiapp_sensor *sensor) +{ + struct smiapp_reg_8 regs[] = { + { 0x30a3, 0xd8 }, /* Output port control : LVDS ports only */ + { 0x30ae, 0x00 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */ + { 0x30af, 0xd0 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */ + { 0x322d, 0x04 }, /* Adjusting Processing Image Size to Scaler Toshiba Recommendation Setting */ + { 0x3255, 0x0f }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */ + { 0x3256, 0x15 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */ + { 0x3258, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */ + { 0x3259, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */ + { 0x325f, 0x7c }, /* Analog Gain Control Toshiba Recommendation Setting */ + { 0x3302, 0x06 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x3304, 0x00 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x3307, 0x22 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x3308, 0x8d }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x331e, 0x0f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3320, 0x30 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3321, 0x11 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3322, 0x98 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3323, 0x64 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3325, 0x83 }, /* Read Out Timing Control Toshiba Recommendation Setting */ + { 0x3330, 0x18 }, /* Read Out Timing Control Toshiba Recommendation Setting */ + { 0x333c, 0x01 }, /* Read Out Timing Control Toshiba Recommendation Setting */ + { 0x3345, 0x2f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x33de, 0x38 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */ + /* Taken from v03. No idea what the rest are. */ + { 0x32e0, 0x05 }, + { 0x32e1, 0x05 }, + { 0x32e2, 0x04 }, + { 0x32e5, 0x04 }, + { 0x32e6, 0x04 }, + + }; + + return smiapp_write_8s(sensor, regs, ARRAY_SIZE(regs)); +} + +const struct smiapp_quirk smiapp_jt8ew9_quirk = { + .limits = jt8ew9_limits, + .post_poweron = jt8ew9_post_poweron, +}; + +static int imx125es_post_poweron(struct smiapp_sensor *sensor) +{ + /* Taken from v02. No idea what the other two are. */ + struct smiapp_reg_8 regs[] = { + /* + * 0x3302: clk during frame blanking: + * 0x00 - HS mode, 0x01 - LP11 + */ + { 0x3302, 0x01 }, + { 0x302d, 0x00 }, + { 0x3b08, 0x8c }, + }; + + return smiapp_write_8s(sensor, regs, ARRAY_SIZE(regs)); +} + +const struct smiapp_quirk smiapp_imx125es_quirk = { + .post_poweron = imx125es_post_poweron, +}; + +static int jt8ev1_limits(struct smiapp_sensor *sensor) +{ + smiapp_replace_limit(sensor, SMIAPP_LIMIT_X_ADDR_MAX, 4271); + smiapp_replace_limit(sensor, + SMIAPP_LIMIT_MIN_LINE_BLANKING_PCK_BIN, 184); + + return 0; +} + +static int jt8ev1_post_poweron(struct smiapp_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + struct smiapp_reg_8 regs[] = { + { 0x3031, 0xcd }, /* For digital binning (EQ_MONI) */ + { 0x30a3, 0xd0 }, /* FLASH STROBE enable */ + { 0x3237, 0x00 }, /* For control of pulse timing for ADC */ + { 0x3238, 0x43 }, + { 0x3301, 0x06 }, /* For analog bias for sensor */ + { 0x3302, 0x06 }, + { 0x3304, 0x00 }, + { 0x3305, 0x88 }, + { 0x332a, 0x14 }, + { 0x332c, 0x6b }, + { 0x3336, 0x01 }, + { 0x333f, 0x1f }, + { 0x3355, 0x00 }, + { 0x3356, 0x20 }, + { 0x33bf, 0x20 }, /* Adjust the FBC speed */ + { 0x33c9, 0x20 }, + { 0x33ce, 0x30 }, /* Adjust the parameter for logic function */ + { 0x33cf, 0xec }, /* For Black sun */ + { 0x3328, 0x80 }, /* Ugh. No idea what's this. */ + }; + + struct smiapp_reg_8 regs_96[] = { + { 0x30ae, 0x00 }, /* For control of ADC clock */ + { 0x30af, 0xd0 }, + { 0x30b0, 0x01 }, + }; + + rval = smiapp_write_8s(sensor, regs, ARRAY_SIZE(regs)); + if (rval < 0) + return rval; + + switch (sensor->platform_data->ext_clk) { + case 9600000: + return smiapp_write_8s(sensor, regs_96, + ARRAY_SIZE(regs_96)); + default: + dev_warn(&client->dev, "no MSRs for %d Hz ext_clk\n", + sensor->platform_data->ext_clk); + return 0; + } +} + +static int jt8ev1_pre_streamon(struct smiapp_sensor *sensor) +{ + return smiapp_write_8(sensor, 0x3328, 0x00); +} + +static int jt8ev1_post_streamoff(struct smiapp_sensor *sensor) +{ + int rval; + + /* Workaround: allows fast standby to work properly */ + rval = smiapp_write_8(sensor, 0x3205, 0x04); + if (rval < 0) + return rval; + + /* Wait for 1 ms + one line => 2 ms is likely enough */ + usleep_range(2000, 2000); + + /* Restore it */ + rval = smiapp_write_8(sensor, 0x3205, 0x00); + if (rval < 0) + return rval; + + return smiapp_write_8(sensor, 0x3328, 0x80); +} + +const struct smiapp_quirk smiapp_jt8ev1_quirk = { + .limits = jt8ev1_limits, + .post_poweron = jt8ev1_post_poweron, + .pre_streamon = jt8ev1_pre_streamon, + .post_streamoff = jt8ev1_post_streamoff, + .flags = SMIAPP_QUIRK_FLAG_OP_PIX_CLOCK_PER_LANE, +}; + +static int tcm8500md_limits(struct smiapp_sensor *sensor) +{ + smiapp_replace_limit(sensor, SMIAPP_LIMIT_MIN_PLL_IP_FREQ_HZ, 2700000); + + return 0; +} + +const struct smiapp_quirk smiapp_tcm8500md_quirk = { + .limits = tcm8500md_limits, +}; diff --git a/drivers/media/i2c/smiapp/smiapp-quirk.h b/drivers/media/i2c/smiapp/smiapp-quirk.h new file mode 100644 index 000000000000..86fd3e8bfb0f --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-quirk.h @@ -0,0 +1,83 @@ +/* + * drivers/media/i2c/smiapp/smiapp-quirk.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __SMIAPP_QUIRK__ +#define __SMIAPP_QUIRK__ + +struct smiapp_sensor; + +/** + * struct smiapp_quirk - quirks for sensors that deviate from SMIA++ standard + * + * @limits: Replace sensor->limits with values which can't be read from + * sensor registers. Called the first time the sensor is powered up. + * @post_poweron: Called always after the sensor has been fully powered on. + * @pre_streamon: Called just before streaming is enabled. + * @post_streamon: Called right after stopping streaming. + */ +struct smiapp_quirk { + int (*limits)(struct smiapp_sensor *sensor); + int (*post_poweron)(struct smiapp_sensor *sensor); + int (*pre_streamon)(struct smiapp_sensor *sensor); + int (*post_streamoff)(struct smiapp_sensor *sensor); + const struct smia_reg *regs; + unsigned long flags; +}; + +/* op pix clock is for all lanes in total normally */ +#define SMIAPP_QUIRK_FLAG_OP_PIX_CLOCK_PER_LANE (1 << 0) +#define SMIAPP_QUIRK_FLAG_8BIT_READ_ONLY (1 << 1) + +struct smiapp_reg_8 { + u16 reg; + u8 val; +}; + +void smiapp_replace_limit(struct smiapp_sensor *sensor, + u32 limit, u32 val); +bool smiapp_quirk_reg(struct smiapp_sensor *sensor, + u32 reg, u32 *val); + +#define SMIAPP_MK_QUIRK_REG(_reg, _val) \ + { \ + .type = (_reg >> 16), \ + .reg = (u16)_reg, \ + .val = _val, \ + } + +#define smiapp_call_quirk(_sensor, _quirk, ...) \ + (_sensor->minfo.quirk && \ + _sensor->minfo.quirk->_quirk ? \ + _sensor->minfo.quirk->_quirk(_sensor, ##__VA_ARGS__) : 0) + +#define smiapp_needs_quirk(_sensor, _quirk) \ + (_sensor->minfo.quirk ? \ + _sensor->minfo.quirk->flags & _quirk : 0) + +extern const struct smiapp_quirk smiapp_jt8ev1_quirk; +extern const struct smiapp_quirk smiapp_imx125es_quirk; +extern const struct smiapp_quirk smiapp_jt8ew9_quirk; +extern const struct smiapp_quirk smiapp_tcm8500md_quirk; + +#endif /* __SMIAPP_QUIRK__ */ diff --git a/drivers/media/i2c/smiapp/smiapp-reg-defs.h b/drivers/media/i2c/smiapp/smiapp-reg-defs.h new file mode 100644 index 000000000000..defa7c5adebf --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-reg-defs.h @@ -0,0 +1,503 @@ +/* + * drivers/media/i2c/smiapp/smiapp-reg-defs.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#define SMIAPP_REG_MK_U8(r) ((SMIA_REG_8BIT << 16) | (r)) +#define SMIAPP_REG_MK_U16(r) ((SMIA_REG_16BIT << 16) | (r)) +#define SMIAPP_REG_MK_U32(r) ((SMIA_REG_32BIT << 16) | (r)) + +#define SMIAPP_REG_MK_F32(r) (SMIA_REG_FLAG_FLOAT | (SMIA_REG_32BIT << 16) | (r)) + +#define SMIAPP_REG_U16_MODEL_ID SMIAPP_REG_MK_U16(0x0000) +#define SMIAPP_REG_U8_REVISION_NUMBER_MAJOR SMIAPP_REG_MK_U8(0x0002) +#define SMIAPP_REG_U8_MANUFACTURER_ID SMIAPP_REG_MK_U8(0x0003) +#define SMIAPP_REG_U8_SMIA_VERSION SMIAPP_REG_MK_U8(0x0004) +#define SMIAPP_REG_U8_FRAME_COUNT SMIAPP_REG_MK_U8(0x0005) +#define SMIAPP_REG_U8_PIXEL_ORDER SMIAPP_REG_MK_U8(0x0006) +#define SMIAPP_REG_U16_DATA_PEDESTAL SMIAPP_REG_MK_U16(0x0008) +#define SMIAPP_REG_U8_PIXEL_DEPTH SMIAPP_REG_MK_U8(0x000c) +#define SMIAPP_REG_U8_REVISION_NUMBER_MINOR SMIAPP_REG_MK_U8(0x0010) +#define SMIAPP_REG_U8_SMIAPP_VERSION SMIAPP_REG_MK_U8(0x0011) +#define SMIAPP_REG_U8_MODULE_DATE_YEAR SMIAPP_REG_MK_U8(0x0012) +#define SMIAPP_REG_U8_MODULE_DATE_MONTH SMIAPP_REG_MK_U8(0x0013) +#define SMIAPP_REG_U8_MODULE_DATE_DAY SMIAPP_REG_MK_U8(0x0014) +#define SMIAPP_REG_U8_MODULE_DATE_PHASE SMIAPP_REG_MK_U8(0x0015) +#define SMIAPP_REG_U16_SENSOR_MODEL_ID SMIAPP_REG_MK_U16(0x0016) +#define SMIAPP_REG_U8_SENSOR_REVISION_NUMBER SMIAPP_REG_MK_U8(0x0018) +#define SMIAPP_REG_U8_SENSOR_MANUFACTURER_ID SMIAPP_REG_MK_U8(0x0019) +#define SMIAPP_REG_U8_SENSOR_FIRMWARE_VERSION SMIAPP_REG_MK_U8(0x001a) +#define SMIAPP_REG_U32_SERIAL_NUMBER SMIAPP_REG_MK_U32(0x001c) +#define SMIAPP_REG_U8_FRAME_FORMAT_MODEL_TYPE SMIAPP_REG_MK_U8(0x0040) +#define SMIAPP_REG_U8_FRAME_FORMAT_MODEL_SUBTYPE SMIAPP_REG_MK_U8(0x0041) +#define SMIAPP_REG_U16_FRAME_FORMAT_DESCRIPTOR_2(n) SMIAPP_REG_MK_U16(0x0042 + ((n) << 1)) /* 0 <= n <= 14 */ +#define SMIAPP_REG_U32_FRAME_FORMAT_DESCRIPTOR_4(n) SMIAPP_REG_MK_U32(0x0060 + ((n) << 2)) /* 0 <= n <= 7 */ +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CAPABILITY SMIAPP_REG_MK_U16(0x0080) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MIN SMIAPP_REG_MK_U16(0x0084) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MAX SMIAPP_REG_MK_U16(0x0086) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_STEP SMIAPP_REG_MK_U16(0x0088) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_TYPE SMIAPP_REG_MK_U16(0x008a) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_M0 SMIAPP_REG_MK_U16(0x008c) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_C0 SMIAPP_REG_MK_U16(0x008e) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_M1 SMIAPP_REG_MK_U16(0x0090) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_C1 SMIAPP_REG_MK_U16(0x0092) +#define SMIAPP_REG_U8_DATA_FORMAT_MODEL_TYPE SMIAPP_REG_MK_U8(0x00c0) +#define SMIAPP_REG_U8_DATA_FORMAT_MODEL_SUBTYPE SMIAPP_REG_MK_U8(0x00c1) +#define SMIAPP_REG_U16_DATA_FORMAT_DESCRIPTOR(n) SMIAPP_REG_MK_U16(0x00c2 + ((n) << 1)) +#define SMIAPP_REG_U8_MODE_SELECT SMIAPP_REG_MK_U8(0x0100) +#define SMIAPP_REG_U8_IMAGE_ORIENTATION SMIAPP_REG_MK_U8(0x0101) +#define SMIAPP_REG_U8_SOFTWARE_RESET SMIAPP_REG_MK_U8(0x0103) +#define SMIAPP_REG_U8_GROUPED_PARAMETER_HOLD SMIAPP_REG_MK_U8(0x0104) +#define SMIAPP_REG_U8_MASK_CORRUPTED_FRAMES SMIAPP_REG_MK_U8(0x0105) +#define SMIAPP_REG_U8_FAST_STANDBY_CTRL SMIAPP_REG_MK_U8(0x0106) +#define SMIAPP_REG_U8_CCI_ADDRESS_CONTROL SMIAPP_REG_MK_U8(0x0107) +#define SMIAPP_REG_U8_2ND_CCI_IF_CONTROL SMIAPP_REG_MK_U8(0x0108) +#define SMIAPP_REG_U8_2ND_CCI_ADDRESS_CONTROL SMIAPP_REG_MK_U8(0x0109) +#define SMIAPP_REG_U8_CSI_CHANNEL_IDENTIFIER SMIAPP_REG_MK_U8(0x0110) +#define SMIAPP_REG_U8_CSI_SIGNALLING_MODE SMIAPP_REG_MK_U8(0x0111) +#define SMIAPP_REG_U16_CSI_DATA_FORMAT SMIAPP_REG_MK_U16(0x0112) +#define SMIAPP_REG_U8_CSI_LANE_MODE SMIAPP_REG_MK_U8(0x0114) +#define SMIAPP_REG_U8_CSI2_10_TO_8_DT SMIAPP_REG_MK_U8(0x0115) +#define SMIAPP_REG_U8_CSI2_10_TO_7_DT SMIAPP_REG_MK_U8(0x0116) +#define SMIAPP_REG_U8_CSI2_10_TO_6_DT SMIAPP_REG_MK_U8(0x0117) +#define SMIAPP_REG_U8_CSI2_12_TO_8_DT SMIAPP_REG_MK_U8(0x0118) +#define SMIAPP_REG_U8_CSI2_12_TO_7_DT SMIAPP_REG_MK_U8(0x0119) +#define SMIAPP_REG_U8_CSI2_12_TO_6_DT SMIAPP_REG_MK_U8(0x011a) +#define SMIAPP_REG_U8_CSI2_14_TO_10_DT SMIAPP_REG_MK_U8(0x011b) +#define SMIAPP_REG_U8_CSI2_14_TO_8_DT SMIAPP_REG_MK_U8(0x011c) +#define SMIAPP_REG_U8_CSI2_16_TO_10_DT SMIAPP_REG_MK_U8(0x011d) +#define SMIAPP_REG_U8_CSI2_16_TO_8_DT SMIAPP_REG_MK_U8(0x011e) +#define SMIAPP_REG_U8_GAIN_MODE SMIAPP_REG_MK_U8(0x0120) +#define SMIAPP_REG_U16_VANA_VOLTAGE SMIAPP_REG_MK_U16(0x0130) +#define SMIAPP_REG_U16_VDIG_VOLTAGE SMIAPP_REG_MK_U16(0x0132) +#define SMIAPP_REG_U16_VIO_VOLTAGE SMIAPP_REG_MK_U16(0x0134) +#define SMIAPP_REG_U16_EXTCLK_FREQUENCY_MHZ SMIAPP_REG_MK_U16(0x0136) +#define SMIAPP_REG_U8_TEMP_SENSOR_CONTROL SMIAPP_REG_MK_U8(0x0138) +#define SMIAPP_REG_U8_TEMP_SENSOR_MODE SMIAPP_REG_MK_U8(0x0139) +#define SMIAPP_REG_U8_TEMP_SENSOR_OUTPUT SMIAPP_REG_MK_U8(0x013a) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME SMIAPP_REG_MK_U16(0x0200) +#define SMIAPP_REG_U16_COARSE_INTEGRATION_TIME SMIAPP_REG_MK_U16(0x0202) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GLOBAL SMIAPP_REG_MK_U16(0x0204) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GREENR SMIAPP_REG_MK_U16(0x0206) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_RED SMIAPP_REG_MK_U16(0x0208) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_BLUE SMIAPP_REG_MK_U16(0x020a) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GREENB SMIAPP_REG_MK_U16(0x020c) +#define SMIAPP_REG_U16_DIGITAL_GAIN_GREENR SMIAPP_REG_MK_U16(0x020e) +#define SMIAPP_REG_U16_DIGITAL_GAIN_RED SMIAPP_REG_MK_U16(0x0210) +#define SMIAPP_REG_U16_DIGITAL_GAIN_BLUE SMIAPP_REG_MK_U16(0x0212) +#define SMIAPP_REG_U16_DIGITAL_GAIN_GREENB SMIAPP_REG_MK_U16(0x0214) +#define SMIAPP_REG_U16_VT_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x0300) +#define SMIAPP_REG_U16_VT_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x0302) +#define SMIAPP_REG_U16_PRE_PLL_CLK_DIV SMIAPP_REG_MK_U16(0x0304) +#define SMIAPP_REG_U16_PLL_MULTIPLIER SMIAPP_REG_MK_U16(0x0306) +#define SMIAPP_REG_U16_OP_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x0308) +#define SMIAPP_REG_U16_OP_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x030a) +#define SMIAPP_REG_U16_FRAME_LENGTH_LINES SMIAPP_REG_MK_U16(0x0340) +#define SMIAPP_REG_U16_LINE_LENGTH_PCK SMIAPP_REG_MK_U16(0x0342) +#define SMIAPP_REG_U16_X_ADDR_START SMIAPP_REG_MK_U16(0x0344) +#define SMIAPP_REG_U16_Y_ADDR_START SMIAPP_REG_MK_U16(0x0346) +#define SMIAPP_REG_U16_X_ADDR_END SMIAPP_REG_MK_U16(0x0348) +#define SMIAPP_REG_U16_Y_ADDR_END SMIAPP_REG_MK_U16(0x034a) +#define SMIAPP_REG_U16_X_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x034c) +#define SMIAPP_REG_U16_Y_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x034e) +#define SMIAPP_REG_U16_X_EVEN_INC SMIAPP_REG_MK_U16(0x0380) +#define SMIAPP_REG_U16_X_ODD_INC SMIAPP_REG_MK_U16(0x0382) +#define SMIAPP_REG_U16_Y_EVEN_INC SMIAPP_REG_MK_U16(0x0384) +#define SMIAPP_REG_U16_Y_ODD_INC SMIAPP_REG_MK_U16(0x0386) +#define SMIAPP_REG_U16_SCALING_MODE SMIAPP_REG_MK_U16(0x0400) +#define SMIAPP_REG_U16_SPATIAL_SAMPLING SMIAPP_REG_MK_U16(0x0402) +#define SMIAPP_REG_U16_SCALE_M SMIAPP_REG_MK_U16(0x0404) +#define SMIAPP_REG_U16_SCALE_N SMIAPP_REG_MK_U16(0x0406) +#define SMIAPP_REG_U16_DIGITAL_CROP_X_OFFSET SMIAPP_REG_MK_U16(0x0408) +#define SMIAPP_REG_U16_DIGITAL_CROP_Y_OFFSET SMIAPP_REG_MK_U16(0x040a) +#define SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_WIDTH SMIAPP_REG_MK_U16(0x040c) +#define SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_HEIGHT SMIAPP_REG_MK_U16(0x040e) +#define SMIAPP_REG_U16_COMPRESSION_MODE SMIAPP_REG_MK_U16(0x0500) +#define SMIAPP_REG_U16_TEST_PATTERN_MODE SMIAPP_REG_MK_U16(0x0600) +#define SMIAPP_REG_U16_TEST_DATA_RED SMIAPP_REG_MK_U16(0x0602) +#define SMIAPP_REG_U16_TEST_DATA_GREENR SMIAPP_REG_MK_U16(0x0604) +#define SMIAPP_REG_U16_TEST_DATA_BLUE SMIAPP_REG_MK_U16(0x0606) +#define SMIAPP_REG_U16_TEST_DATA_GREENB SMIAPP_REG_MK_U16(0x0608) +#define SMIAPP_REG_U16_HORIZONTAL_CURSOR_WIDTH SMIAPP_REG_MK_U16(0x060a) +#define SMIAPP_REG_U16_HORIZONTAL_CURSOR_POSITION SMIAPP_REG_MK_U16(0x060c) +#define SMIAPP_REG_U16_VERTICAL_CURSOR_WIDTH SMIAPP_REG_MK_U16(0x060e) +#define SMIAPP_REG_U16_VERTICAL_CURSOR_POSITION SMIAPP_REG_MK_U16(0x0610) +#define SMIAPP_REG_U16_FIFO_WATER_MARK_PIXELS SMIAPP_REG_MK_U16(0x0700) +#define SMIAPP_REG_U8_TCLK_POST SMIAPP_REG_MK_U8(0x0800) +#define SMIAPP_REG_U8_THS_PREPARE SMIAPP_REG_MK_U8(0x0801) +#define SMIAPP_REG_U8_THS_ZERO_MIN SMIAPP_REG_MK_U8(0x0802) +#define SMIAPP_REG_U8_THS_TRAIL SMIAPP_REG_MK_U8(0x0803) +#define SMIAPP_REG_U8_TCLK_TRAIL_MIN SMIAPP_REG_MK_U8(0x0804) +#define SMIAPP_REG_U8_TCLK_PREPARE SMIAPP_REG_MK_U8(0x0805) +#define SMIAPP_REG_U8_TCLK_ZERO SMIAPP_REG_MK_U8(0x0806) +#define SMIAPP_REG_U8_TLPX SMIAPP_REG_MK_U8(0x0807) +#define SMIAPP_REG_U8_DPHY_CTRL SMIAPP_REG_MK_U8(0x0808) +#define SMIAPP_REG_U32_REQUESTED_LINK_BIT_RATE_MBPS SMIAPP_REG_MK_U32(0x0820) +#define SMIAPP_REG_U8_BINNING_MODE SMIAPP_REG_MK_U8(0x0900) +#define SMIAPP_REG_U8_BINNING_TYPE SMIAPP_REG_MK_U8(0x0901) +#define SMIAPP_REG_U8_BINNING_WEIGHTING SMIAPP_REG_MK_U8(0x0902) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_CTRL SMIAPP_REG_MK_U8(0x0a00) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_STATUS SMIAPP_REG_MK_U8(0x0a01) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_PAGE_SELECT SMIAPP_REG_MK_U8(0x0a02) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_0 SMIAPP_REG_MK_U8(0x0a04) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_1 SMIAPP_REG_MK_U8(0x0a05) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_2 SMIAPP_REG_MK_U8(0x0a06) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_3 SMIAPP_REG_MK_U8(0x0a07) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_4 SMIAPP_REG_MK_U8(0x0a08) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_5 SMIAPP_REG_MK_U8(0x0a09) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_12 SMIAPP_REG_MK_U8(0x0a10) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_13 SMIAPP_REG_MK_U8(0x0a11) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_14 SMIAPP_REG_MK_U8(0x0a12) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_15 SMIAPP_REG_MK_U8(0x0a13) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_16 SMIAPP_REG_MK_U8(0x0a14) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_17 SMIAPP_REG_MK_U8(0x0a15) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_18 SMIAPP_REG_MK_U8(0x0a16) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_19 SMIAPP_REG_MK_U8(0x0a17) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_20 SMIAPP_REG_MK_U8(0x0a18) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_21 SMIAPP_REG_MK_U8(0x0a19) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_22 SMIAPP_REG_MK_U8(0x0a1a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_23 SMIAPP_REG_MK_U8(0x0a1b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_24 SMIAPP_REG_MK_U8(0x0a1c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_25 SMIAPP_REG_MK_U8(0x0a1d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_26 SMIAPP_REG_MK_U8(0x0a1e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_27 SMIAPP_REG_MK_U8(0x0a1f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_28 SMIAPP_REG_MK_U8(0x0a20) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_29 SMIAPP_REG_MK_U8(0x0a21) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_30 SMIAPP_REG_MK_U8(0x0a22) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_31 SMIAPP_REG_MK_U8(0x0a23) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_32 SMIAPP_REG_MK_U8(0x0a24) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_33 SMIAPP_REG_MK_U8(0x0a25) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_34 SMIAPP_REG_MK_U8(0x0a26) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_35 SMIAPP_REG_MK_U8(0x0a27) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_36 SMIAPP_REG_MK_U8(0x0a28) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_37 SMIAPP_REG_MK_U8(0x0a29) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_38 SMIAPP_REG_MK_U8(0x0a2a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_39 SMIAPP_REG_MK_U8(0x0a2b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_40 SMIAPP_REG_MK_U8(0x0a2c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_41 SMIAPP_REG_MK_U8(0x0a2d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_42 SMIAPP_REG_MK_U8(0x0a2e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_43 SMIAPP_REG_MK_U8(0x0a2f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_44 SMIAPP_REG_MK_U8(0x0a30) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_45 SMIAPP_REG_MK_U8(0x0a31) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_46 SMIAPP_REG_MK_U8(0x0a32) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_47 SMIAPP_REG_MK_U8(0x0a33) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_48 SMIAPP_REG_MK_U8(0x0a34) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_49 SMIAPP_REG_MK_U8(0x0a35) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_50 SMIAPP_REG_MK_U8(0x0a36) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_51 SMIAPP_REG_MK_U8(0x0a37) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_52 SMIAPP_REG_MK_U8(0x0a38) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_53 SMIAPP_REG_MK_U8(0x0a39) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_54 SMIAPP_REG_MK_U8(0x0a3a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_55 SMIAPP_REG_MK_U8(0x0a3b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_56 SMIAPP_REG_MK_U8(0x0a3c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_57 SMIAPP_REG_MK_U8(0x0a3d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_58 SMIAPP_REG_MK_U8(0x0a3e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_59 SMIAPP_REG_MK_U8(0x0a3f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_60 SMIAPP_REG_MK_U8(0x0a40) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_61 SMIAPP_REG_MK_U8(0x0a41) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_62 SMIAPP_REG_MK_U8(0x0a42) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_63 SMIAPP_REG_MK_U8(0x0a43) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_CTRL SMIAPP_REG_MK_U8(0x0a44) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_STATUS SMIAPP_REG_MK_U8(0x0a45) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_PAGE_SELECT SMIAPP_REG_MK_U8(0x0a46) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_0 SMIAPP_REG_MK_U8(0x0a48) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_1 SMIAPP_REG_MK_U8(0x0a49) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_2 SMIAPP_REG_MK_U8(0x0a4a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_3 SMIAPP_REG_MK_U8(0x0a4b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_4 SMIAPP_REG_MK_U8(0x0a4c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_5 SMIAPP_REG_MK_U8(0x0a4d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_6 SMIAPP_REG_MK_U8(0x0a4e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_7 SMIAPP_REG_MK_U8(0x0a4f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_8 SMIAPP_REG_MK_U8(0x0a50) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_9 SMIAPP_REG_MK_U8(0x0a51) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_10 SMIAPP_REG_MK_U8(0x0a52) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_11 SMIAPP_REG_MK_U8(0x0a53) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_12 SMIAPP_REG_MK_U8(0x0a54) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_13 SMIAPP_REG_MK_U8(0x0a55) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_14 SMIAPP_REG_MK_U8(0x0a56) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_15 SMIAPP_REG_MK_U8(0x0a57) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_16 SMIAPP_REG_MK_U8(0x0a58) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_17 SMIAPP_REG_MK_U8(0x0a59) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_18 SMIAPP_REG_MK_U8(0x0a5a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_19 SMIAPP_REG_MK_U8(0x0a5b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_20 SMIAPP_REG_MK_U8(0x0a5c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_21 SMIAPP_REG_MK_U8(0x0a5d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_22 SMIAPP_REG_MK_U8(0x0a5e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_23 SMIAPP_REG_MK_U8(0x0a5f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_24 SMIAPP_REG_MK_U8(0x0a60) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_25 SMIAPP_REG_MK_U8(0x0a61) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_26 SMIAPP_REG_MK_U8(0x0a62) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_27 SMIAPP_REG_MK_U8(0x0a63) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_28 SMIAPP_REG_MK_U8(0x0a64) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_29 SMIAPP_REG_MK_U8(0x0a65) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_30 SMIAPP_REG_MK_U8(0x0a66) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_31 SMIAPP_REG_MK_U8(0x0a67) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_32 SMIAPP_REG_MK_U8(0x0a68) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_33 SMIAPP_REG_MK_U8(0x0a69) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_34 SMIAPP_REG_MK_U8(0x0a6a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_35 SMIAPP_REG_MK_U8(0x0a6b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_36 SMIAPP_REG_MK_U8(0x0a6c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_37 SMIAPP_REG_MK_U8(0x0a6d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_38 SMIAPP_REG_MK_U8(0x0a6e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_39 SMIAPP_REG_MK_U8(0x0a6f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_40 SMIAPP_REG_MK_U8(0x0a70) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_41 SMIAPP_REG_MK_U8(0x0a71) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_42 SMIAPP_REG_MK_U8(0x0a72) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_43 SMIAPP_REG_MK_U8(0x0a73) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_44 SMIAPP_REG_MK_U8(0x0a74) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_45 SMIAPP_REG_MK_U8(0x0a75) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_46 SMIAPP_REG_MK_U8(0x0a76) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_47 SMIAPP_REG_MK_U8(0x0a77) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_48 SMIAPP_REG_MK_U8(0x0a78) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_49 SMIAPP_REG_MK_U8(0x0a79) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_50 SMIAPP_REG_MK_U8(0x0a7a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_51 SMIAPP_REG_MK_U8(0x0a7b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_52 SMIAPP_REG_MK_U8(0x0a7c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_53 SMIAPP_REG_MK_U8(0x0a7d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_54 SMIAPP_REG_MK_U8(0x0a7e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_55 SMIAPP_REG_MK_U8(0x0a7f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_56 SMIAPP_REG_MK_U8(0x0a80) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_57 SMIAPP_REG_MK_U8(0x0a81) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_58 SMIAPP_REG_MK_U8(0x0a82) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_59 SMIAPP_REG_MK_U8(0x0a83) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_60 SMIAPP_REG_MK_U8(0x0a84) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_61 SMIAPP_REG_MK_U8(0x0a85) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_62 SMIAPP_REG_MK_U8(0x0a86) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_63 SMIAPP_REG_MK_U8(0x0a87) +#define SMIAPP_REG_U8_SHADING_CORRECTION_ENABLE SMIAPP_REG_MK_U8(0x0b00) +#define SMIAPP_REG_U8_LUMINANCE_CORRECTION_LEVEL SMIAPP_REG_MK_U8(0x0b01) +#define SMIAPP_REG_U8_GREEN_IMBALANCE_FILTER_ENABLE SMIAPP_REG_MK_U8(0x0b02) +#define SMIAPP_REG_U8_GREEN_IMBALANCE_FILTER_WEIGHT SMIAPP_REG_MK_U8(0x0b03) +#define SMIAPP_REG_U8_BLACK_LEVEL_CORRECTION_ENABLE SMIAPP_REG_MK_U8(0x0b04) +#define SMIAPP_REG_U8_MAPPED_COUPLET_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b05) +#define SMIAPP_REG_U8_SINGLE_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b06) +#define SMIAPP_REG_U8_SINGLE_DEFECT_CORRECT_WEIGHT SMIAPP_REG_MK_U8(0x0b07) +#define SMIAPP_REG_U8_DYNAMIC_COUPLET_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b08) +#define SMIAPP_REG_U8_DYNAMIC_COUPLET_CORRECT_WEIGHT SMIAPP_REG_MK_U8(0x0b09) +#define SMIAPP_REG_U8_COMBINED_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b0a) +#define SMIAPP_REG_U8_COMBINED_DEFECT_CORRECT_WEIGHT SMIAPP_REG_MK_U8(0x0b0b) +#define SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_ENABLE SMIAPP_REG_MK_U8(0x0b0c) +#define SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_WEIGHT SMIAPP_REG_MK_U8(0x0b0d) +#define SMIAPP_REG_U8_MAPPED_LINE_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b0e) +#define SMIAPP_REG_U8_MAPPED_LINE_DEFECT_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b0f) +#define SMIAPP_REG_U8_MAPPED_COUPLET_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b10) +#define SMIAPP_REG_U8_MAPPED_TRIPLET_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b11) +#define SMIAPP_REG_U8_MAPPED_TRIPLET_DEFECT_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b12) +#define SMIAPP_REG_U8_DYNAMIC_TRIPLET_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b13) +#define SMIAPP_REG_U8_DYNAMIC_TRIPLET_DEFECT_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b14) +#define SMIAPP_REG_U8_DYNAMIC_LINE_DEFECT_CORRECT_ENABLE SMIAPP_REG_MK_U8(0x0b15) +#define SMIAPP_REG_U8_DYNAMIC_LINE_DEFECT_CORRECT_ADJUST SMIAPP_REG_MK_U8(0x0b16) +#define SMIAPP_REG_U8_EDOF_MODE SMIAPP_REG_MK_U8(0x0b80) +#define SMIAPP_REG_U8_SHARPNESS SMIAPP_REG_MK_U8(0x0b83) +#define SMIAPP_REG_U8_DENOISING SMIAPP_REG_MK_U8(0x0b84) +#define SMIAPP_REG_U8_MODULE_SPECIFIC SMIAPP_REG_MK_U8(0x0b85) +#define SMIAPP_REG_U16_DEPTH_OF_FIELD SMIAPP_REG_MK_U16(0x0b86) +#define SMIAPP_REG_U16_FOCUS_DISTANCE SMIAPP_REG_MK_U16(0x0b88) +#define SMIAPP_REG_U8_ESTIMATION_MODE_CTRL SMIAPP_REG_MK_U8(0x0b8a) +#define SMIAPP_REG_U16_COLOUR_TEMPERATURE SMIAPP_REG_MK_U16(0x0b8c) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_GREENR SMIAPP_REG_MK_U16(0x0b8e) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_RED SMIAPP_REG_MK_U16(0x0b90) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_BLUE SMIAPP_REG_MK_U16(0x0b92) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_GREENB SMIAPP_REG_MK_U16(0x0b94) +#define SMIAPP_REG_U8_ESTIMATION_ZONE_MODE SMIAPP_REG_MK_U8(0x0bc0) +#define SMIAPP_REG_U16_FIXED_ZONE_WEIGHTING SMIAPP_REG_MK_U16(0x0bc2) +#define SMIAPP_REG_U16_CUSTOM_ZONE_X_START SMIAPP_REG_MK_U16(0x0bc4) +#define SMIAPP_REG_U16_CUSTOM_ZONE_Y_START SMIAPP_REG_MK_U16(0x0bc6) +#define SMIAPP_REG_U16_CUSTOM_ZONE_WIDTH SMIAPP_REG_MK_U16(0x0bc8) +#define SMIAPP_REG_U16_CUSTOM_ZONE_HEIGHT SMIAPP_REG_MK_U16(0x0bca) +#define SMIAPP_REG_U8_GLOBAL_RESET_CTRL1 SMIAPP_REG_MK_U8(0x0c00) +#define SMIAPP_REG_U8_GLOBAL_RESET_CTRL2 SMIAPP_REG_MK_U8(0x0c01) +#define SMIAPP_REG_U8_GLOBAL_RESET_MODE_CONFIG_1 SMIAPP_REG_MK_U8(0x0c02) +#define SMIAPP_REG_U8_GLOBAL_RESET_MODE_CONFIG_2 SMIAPP_REG_MK_U8(0x0c03) +#define SMIAPP_REG_U16_TRDY_CTRL SMIAPP_REG_MK_U16(0x0c04) +#define SMIAPP_REG_U16_TRDOUT_CTRL SMIAPP_REG_MK_U16(0x0c06) +#define SMIAPP_REG_U16_TSHUTTER_STROBE_DELAY_CTRL SMIAPP_REG_MK_U16(0x0c08) +#define SMIAPP_REG_U16_TSHUTTER_STROBE_WIDTH_CTRL SMIAPP_REG_MK_U16(0x0c0a) +#define SMIAPP_REG_U16_TFLASH_STROBE_DELAY_CTRL SMIAPP_REG_MK_U16(0x0c0c) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_HIGH_CTRL SMIAPP_REG_MK_U16(0x0c0e) +#define SMIAPP_REG_U16_TGRST_INTERVAL_CTRL SMIAPP_REG_MK_U16(0x0c10) +#define SMIAPP_REG_U8_FLASH_STROBE_ADJUSTMENT SMIAPP_REG_MK_U8(0x0c12) +#define SMIAPP_REG_U16_FLASH_STROBE_START_POINT SMIAPP_REG_MK_U16(0x0c14) +#define SMIAPP_REG_U16_TFLASH_STROBE_DELAY_RS_CTRL SMIAPP_REG_MK_U16(0x0c16) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_HIGH_RS_CTRL SMIAPP_REG_MK_U16(0x0c18) +#define SMIAPP_REG_U8_FLASH_MODE_RS SMIAPP_REG_MK_U8(0x0c1a) +#define SMIAPP_REG_U8_FLASH_TRIGGER_RS SMIAPP_REG_MK_U8(0x0c1b) +#define SMIAPP_REG_U8_FLASH_STATUS SMIAPP_REG_MK_U8(0x0c1c) +#define SMIAPP_REG_U8_SA_STROBE_MODE SMIAPP_REG_MK_U8(0x0c1d) +#define SMIAPP_REG_U16_SA_STROBE_START_POINT SMIAPP_REG_MK_U16(0x0c1e) +#define SMIAPP_REG_U16_TSA_STROBE_DELAY_CTRL SMIAPP_REG_MK_U16(0x0c20) +#define SMIAPP_REG_U16_TSA_STROBE_WIDTH_CTRL SMIAPP_REG_MK_U16(0x0c22) +#define SMIAPP_REG_U8_SA_STROBE_TRIGGER SMIAPP_REG_MK_U8(0x0c24) +#define SMIAPP_REG_U8_SPECIAL_ACTUATOR_STATUS SMIAPP_REG_MK_U8(0x0c25) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH2_HIGH_RS_CTRL SMIAPP_REG_MK_U16(0x0c26) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_LOW_RS_CTRL SMIAPP_REG_MK_U16(0x0c28) +#define SMIAPP_REG_U8_TFLASH_STROBE_COUNT_RS_CTRL SMIAPP_REG_MK_U8(0x0c2a) +#define SMIAPP_REG_U8_TFLASH_STROBE_COUNT_CTRL SMIAPP_REG_MK_U8(0x0c2b) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH2_HIGH_CTRL SMIAPP_REG_MK_U16(0x0c2c) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_LOW_CTRL SMIAPP_REG_MK_U16(0x0c2e) +#define SMIAPP_REG_U8_LOW_LEVEL_CTRL SMIAPP_REG_MK_U8(0x0c80) +#define SMIAPP_REG_U16_MAIN_TRIGGER_REF_POINT SMIAPP_REG_MK_U16(0x0c82) +#define SMIAPP_REG_U16_MAIN_TRIGGER_T3 SMIAPP_REG_MK_U16(0x0c84) +#define SMIAPP_REG_U8_MAIN_TRIGGER_COUNT SMIAPP_REG_MK_U8(0x0c86) +#define SMIAPP_REG_U16_PHASE1_TRIGGER_T3 SMIAPP_REG_MK_U16(0x0c88) +#define SMIAPP_REG_U8_PHASE1_TRIGGER_COUNT SMIAPP_REG_MK_U8(0x0c8a) +#define SMIAPP_REG_U16_PHASE2_TRIGGER_T3 SMIAPP_REG_MK_U16(0x0c8c) +#define SMIAPP_REG_U8_PHASE2_TRIGGER_COUNT SMIAPP_REG_MK_U8(0x0c8e) +#define SMIAPP_REG_U8_MECH_SHUTTER_CTRL SMIAPP_REG_MK_U8(0x0d00) +#define SMIAPP_REG_U8_OPERATION_MODE SMIAPP_REG_MK_U8(0x0d01) +#define SMIAPP_REG_U8_ACT_STATE1 SMIAPP_REG_MK_U8(0x0d02) +#define SMIAPP_REG_U8_ACT_STATE2 SMIAPP_REG_MK_U8(0x0d03) +#define SMIAPP_REG_U16_FOCUS_CHANGE SMIAPP_REG_MK_U16(0x0d80) +#define SMIAPP_REG_U16_FOCUS_CHANGE_CONTROL SMIAPP_REG_MK_U16(0x0d82) +#define SMIAPP_REG_U16_FOCUS_CHANGE_NUMBER_PHASE1 SMIAPP_REG_MK_U16(0x0d84) +#define SMIAPP_REG_U16_FOCUS_CHANGE_NUMBER_PHASE2 SMIAPP_REG_MK_U16(0x0d86) +#define SMIAPP_REG_U8_STROBE_COUNT_PHASE1 SMIAPP_REG_MK_U8(0x0d88) +#define SMIAPP_REG_U8_STROBE_COUNT_PHASE2 SMIAPP_REG_MK_U8(0x0d89) +#define SMIAPP_REG_U8_POSITION SMIAPP_REG_MK_U8(0x0d8a) +#define SMIAPP_REG_U8_BRACKETING_LUT_CONTROL SMIAPP_REG_MK_U8(0x0e00) +#define SMIAPP_REG_U8_BRACKETING_LUT_MODE SMIAPP_REG_MK_U8(0x0e01) +#define SMIAPP_REG_U8_BRACKETING_LUT_ENTRY_CONTROL SMIAPP_REG_MK_U8(0x0e02) +#define SMIAPP_REG_U8_LUT_PARAMETERS_START SMIAPP_REG_MK_U8(0x0e10) +#define SMIAPP_REG_U8_LUT_PARAMETERS_END SMIAPP_REG_MK_U8(0x0eff) +#define SMIAPP_REG_U16_INTEGRATION_TIME_CAPABILITY SMIAPP_REG_MK_U16(0x1000) +#define SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MIN SMIAPP_REG_MK_U16(0x1004) +#define SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MAX_MARGIN SMIAPP_REG_MK_U16(0x1006) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN SMIAPP_REG_MK_U16(0x1008) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN SMIAPP_REG_MK_U16(0x100a) +#define SMIAPP_REG_U16_DIGITAL_GAIN_CAPABILITY SMIAPP_REG_MK_U16(0x1080) +#define SMIAPP_REG_U16_DIGITAL_GAIN_MIN SMIAPP_REG_MK_U16(0x1084) +#define SMIAPP_REG_U16_DIGITAL_GAIN_MAX SMIAPP_REG_MK_U16(0x1086) +#define SMIAPP_REG_U16_DIGITAL_GAIN_STEP_SIZE SMIAPP_REG_MK_U16(0x1088) +#define SMIAPP_REG_F32_MIN_EXT_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1100) +#define SMIAPP_REG_F32_MAX_EXT_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1104) +#define SMIAPP_REG_U16_MIN_PRE_PLL_CLK_DIV SMIAPP_REG_MK_U16(0x1108) +#define SMIAPP_REG_U16_MAX_PRE_PLL_CLK_DIV SMIAPP_REG_MK_U16(0x110a) +#define SMIAPP_REG_F32_MIN_PLL_IP_FREQ_HZ SMIAPP_REG_MK_F32(0x110c) +#define SMIAPP_REG_F32_MAX_PLL_IP_FREQ_HZ SMIAPP_REG_MK_F32(0x1110) +#define SMIAPP_REG_U16_MIN_PLL_MULTIPLIER SMIAPP_REG_MK_U16(0x1114) +#define SMIAPP_REG_U16_MAX_PLL_MULTIPLIER SMIAPP_REG_MK_U16(0x1116) +#define SMIAPP_REG_F32_MIN_PLL_OP_FREQ_HZ SMIAPP_REG_MK_F32(0x1118) +#define SMIAPP_REG_F32_MAX_PLL_OP_FREQ_HZ SMIAPP_REG_MK_F32(0x111c) +#define SMIAPP_REG_U16_MIN_VT_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x1120) +#define SMIAPP_REG_U16_MAX_VT_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x1122) +#define SMIAPP_REG_F32_MIN_VT_SYS_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1124) +#define SMIAPP_REG_F32_MAX_VT_SYS_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1128) +#define SMIAPP_REG_F32_MIN_VT_PIX_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x112c) +#define SMIAPP_REG_F32_MAX_VT_PIX_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1130) +#define SMIAPP_REG_U16_MIN_VT_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x1134) +#define SMIAPP_REG_U16_MAX_VT_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x1136) +#define SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES SMIAPP_REG_MK_U16(0x1140) +#define SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES SMIAPP_REG_MK_U16(0x1142) +#define SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK SMIAPP_REG_MK_U16(0x1144) +#define SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK SMIAPP_REG_MK_U16(0x1146) +#define SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK SMIAPP_REG_MK_U16(0x1148) +#define SMIAPP_REG_U16_MIN_FRAME_BLANKING_LINES SMIAPP_REG_MK_U16(0x114a) +#define SMIAPP_REG_U8_MIN_LINE_LENGTH_PCK_STEP_SIZE SMIAPP_REG_MK_U8(0x114c) +#define SMIAPP_REG_U16_MIN_OP_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x1160) +#define SMIAPP_REG_U16_MAX_OP_SYS_CLK_DIV SMIAPP_REG_MK_U16(0x1162) +#define SMIAPP_REG_F32_MIN_OP_SYS_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1164) +#define SMIAPP_REG_F32_MAX_OP_SYS_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1168) +#define SMIAPP_REG_U16_MIN_OP_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x116c) +#define SMIAPP_REG_U16_MAX_OP_PIX_CLK_DIV SMIAPP_REG_MK_U16(0x116e) +#define SMIAPP_REG_F32_MIN_OP_PIX_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1170) +#define SMIAPP_REG_F32_MAX_OP_PIX_CLK_FREQ_HZ SMIAPP_REG_MK_F32(0x1174) +#define SMIAPP_REG_U16_X_ADDR_MIN SMIAPP_REG_MK_U16(0x1180) +#define SMIAPP_REG_U16_Y_ADDR_MIN SMIAPP_REG_MK_U16(0x1182) +#define SMIAPP_REG_U16_X_ADDR_MAX SMIAPP_REG_MK_U16(0x1184) +#define SMIAPP_REG_U16_Y_ADDR_MAX SMIAPP_REG_MK_U16(0x1186) +#define SMIAPP_REG_U16_MIN_X_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x1188) +#define SMIAPP_REG_U16_MIN_Y_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x118a) +#define SMIAPP_REG_U16_MAX_X_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x118c) +#define SMIAPP_REG_U16_MAX_Y_OUTPUT_SIZE SMIAPP_REG_MK_U16(0x118e) +#define SMIAPP_REG_U16_MIN_EVEN_INC SMIAPP_REG_MK_U16(0x11c0) +#define SMIAPP_REG_U16_MAX_EVEN_INC SMIAPP_REG_MK_U16(0x11c2) +#define SMIAPP_REG_U16_MIN_ODD_INC SMIAPP_REG_MK_U16(0x11c4) +#define SMIAPP_REG_U16_MAX_ODD_INC SMIAPP_REG_MK_U16(0x11c6) +#define SMIAPP_REG_U16_SCALING_CAPABILITY SMIAPP_REG_MK_U16(0x1200) +#define SMIAPP_REG_U16_SCALER_M_MIN SMIAPP_REG_MK_U16(0x1204) +#define SMIAPP_REG_U16_SCALER_M_MAX SMIAPP_REG_MK_U16(0x1206) +#define SMIAPP_REG_U16_SCALER_N_MIN SMIAPP_REG_MK_U16(0x1208) +#define SMIAPP_REG_U16_SCALER_N_MAX SMIAPP_REG_MK_U16(0x120a) +#define SMIAPP_REG_U16_SPATIAL_SAMPLING_CAPABILITY SMIAPP_REG_MK_U16(0x120c) +#define SMIAPP_REG_U8_DIGITAL_CROP_CAPABILITY SMIAPP_REG_MK_U8(0x120e) +#define SMIAPP_REG_U16_COMPRESSION_CAPABILITY SMIAPP_REG_MK_U16(0x1300) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_REDINRED SMIAPP_REG_MK_U16(0x1400) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_GREENINRED SMIAPP_REG_MK_U16(0x1402) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_BLUEINRED SMIAPP_REG_MK_U16(0x1404) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_REDINGREEN SMIAPP_REG_MK_U16(0x1406) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_GREENINGREEN SMIAPP_REG_MK_U16(0x1408) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_BLUEINGREEN SMIAPP_REG_MK_U16(0x140a) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_REDINBLUE SMIAPP_REG_MK_U16(0x140c) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_GREENINBLUE SMIAPP_REG_MK_U16(0x140e) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_BLUEINBLUE SMIAPP_REG_MK_U16(0x1410) +#define SMIAPP_REG_U16_FIFO_SIZE_PIXELS SMIAPP_REG_MK_U16(0x1500) +#define SMIAPP_REG_U8_FIFO_SUPPORT_CAPABILITY SMIAPP_REG_MK_U8(0x1502) +#define SMIAPP_REG_U8_DPHY_CTRL_CAPABILITY SMIAPP_REG_MK_U8(0x1600) +#define SMIAPP_REG_U8_CSI_LANE_MODE_CAPABILITY SMIAPP_REG_MK_U8(0x1601) +#define SMIAPP_REG_U8_CSI_SIGNALLING_MODE_CAPABILITY SMIAPP_REG_MK_U8(0x1602) +#define SMIAPP_REG_U8_FAST_STANDBY_CAPABILITY SMIAPP_REG_MK_U8(0x1603) +#define SMIAPP_REG_U8_CCI_ADDRESS_CONTROL_CAPABILITY SMIAPP_REG_MK_U8(0x1604) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_1_LANE_MODE_MBPS SMIAPP_REG_MK_U32(0x1608) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_2_LANE_MODE_MBPS SMIAPP_REG_MK_U32(0x160c) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_3_LANE_MODE_MBPS SMIAPP_REG_MK_U32(0x1610) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_4_LANE_MODE_MBPS SMIAPP_REG_MK_U32(0x1614) +#define SMIAPP_REG_U8_TEMP_SENSOR_CAPABILITY SMIAPP_REG_MK_U8(0x1618) +#define SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES_BIN SMIAPP_REG_MK_U16(0x1700) +#define SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES_BIN SMIAPP_REG_MK_U16(0x1702) +#define SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK_BIN SMIAPP_REG_MK_U16(0x1704) +#define SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK_BIN SMIAPP_REG_MK_U16(0x1706) +#define SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK_BIN SMIAPP_REG_MK_U16(0x1708) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN_BIN SMIAPP_REG_MK_U16(0x170a) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN SMIAPP_REG_MK_U16(0x170c) +#define SMIAPP_REG_U8_BINNING_CAPABILITY SMIAPP_REG_MK_U8(0x1710) +#define SMIAPP_REG_U8_BINNING_WEIGHTING_CAPABILITY SMIAPP_REG_MK_U8(0x1711) +#define SMIAPP_REG_U8_BINNING_SUBTYPES SMIAPP_REG_MK_U8(0x1712) +#define SMIAPP_REG_U8_BINNING_TYPE_n(n) SMIAPP_REG_MK_U8(0x1713 + (n)) /* 1 <= n <= 237 */ +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_CAPABILITY SMIAPP_REG_MK_U8(0x1800) +#define SMIAPP_REG_U8_SHADING_CORRECTION_CAPABILITY SMIAPP_REG_MK_U8(0x1900) +#define SMIAPP_REG_U8_GREEN_IMBALANCE_CAPABILITY SMIAPP_REG_MK_U8(0x1901) +#define SMIAPP_REG_U8_BLACK_LEVEL_CAPABILITY SMIAPP_REG_MK_U8(0x1902) +#define SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_CAPABILITY SMIAPP_REG_MK_U8(0x1903) +#define SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY SMIAPP_REG_MK_U16(0x1904) +#define SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY_2 SMIAPP_REG_MK_U16(0x1906) +#define SMIAPP_REG_U8_EDOF_CAPABILITY SMIAPP_REG_MK_U8(0x1980) +#define SMIAPP_REG_U8_ESTIMATION_FRAMES SMIAPP_REG_MK_U8(0x1981) +#define SMIAPP_REG_U8_SUPPORTS_SHARPNESS_ADJ SMIAPP_REG_MK_U8(0x1982) +#define SMIAPP_REG_U8_SUPPORTS_DENOISING_ADJ SMIAPP_REG_MK_U8(0x1983) +#define SMIAPP_REG_U8_SUPPORTS_MODULE_SPECIFIC_ADJ SMIAPP_REG_MK_U8(0x1984) +#define SMIAPP_REG_U8_SUPPORTS_DEPTH_OF_FIELD_ADJ SMIAPP_REG_MK_U8(0x1985) +#define SMIAPP_REG_U8_SUPPORTS_FOCUS_DISTANCE_ADJ SMIAPP_REG_MK_U8(0x1986) +#define SMIAPP_REG_U8_COLOUR_FEEDBACK_CAPABILITY SMIAPP_REG_MK_U8(0x1987) +#define SMIAPP_REG_U8_EDOF_SUPPORT_AB_NXM SMIAPP_REG_MK_U8(0x1988) +#define SMIAPP_REG_U8_ESTIMATION_MODE_CAPABILITY SMIAPP_REG_MK_U8(0x19c0) +#define SMIAPP_REG_U8_ESTIMATION_ZONE_CAPABILITY SMIAPP_REG_MK_U8(0x19c1) +#define SMIAPP_REG_U16_EST_DEPTH_OF_FIELD SMIAPP_REG_MK_U16(0x19c2) +#define SMIAPP_REG_U16_EST_FOCUS_DISTANCE SMIAPP_REG_MK_U16(0x19c4) +#define SMIAPP_REG_U16_CAPABILITY_TRDY_MIN SMIAPP_REG_MK_U16(0x1a00) +#define SMIAPP_REG_U8_FLASH_MODE_CAPABILITY SMIAPP_REG_MK_U8(0x1a02) +#define SMIAPP_REG_U16_MECH_SHUT_AND_ACT_START_ADDR SMIAPP_REG_MK_U16(0x1b02) +#define SMIAPP_REG_U8_ACTUATOR_CAPABILITY SMIAPP_REG_MK_U8(0x1b04) +#define SMIAPP_REG_U16_ACTUATOR_TYPE SMIAPP_REG_MK_U16(0x1b40) +#define SMIAPP_REG_U8_AF_DEVICE_ADDRESS SMIAPP_REG_MK_U8(0x1b42) +#define SMIAPP_REG_U16_FOCUS_CHANGE_ADDRESS SMIAPP_REG_MK_U16(0x1b44) +#define SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_1 SMIAPP_REG_MK_U8(0x1c00) +#define SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_2 SMIAPP_REG_MK_U8(0x1c01) +#define SMIAPP_REG_U8_BRACKETING_LUT_SIZE SMIAPP_REG_MK_U8(0x1c02) diff --git a/drivers/media/i2c/smiapp/smiapp-reg.h b/drivers/media/i2c/smiapp/smiapp-reg.h new file mode 100644 index 000000000000..54568ca2fe6d --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-reg.h @@ -0,0 +1,122 @@ +/* + * drivers/media/i2c/smiapp/smiapp-reg.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __SMIAPP_REG_H_ +#define __SMIAPP_REG_H_ + +#include "smiapp-reg-defs.h" + +/* Bits for above register */ +#define SMIAPP_IMAGE_ORIENTATION_HFLIP (1 << 0) +#define SMIAPP_IMAGE_ORIENTATION_VFLIP (1 << 1) + +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_EN (1 << 0) +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_RD_EN (0 << 1) +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_WR_EN (1 << 1) +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_ERR_CLEAR (1 << 2) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_RD_READY (1 << 0) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_WR_READY (1 << 1) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_EDATA (1 << 2) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_EUSAGE (1 << 3) + +#define SMIAPP_SOFTWARE_RESET (1 << 0) + +#define SMIAPP_FLASH_MODE_CAPABILITY_SINGLE_STROBE (1 << 0) +#define SMIAPP_FLASH_MODE_CAPABILITY_MULTIPLE_STROBE (1 << 1) + +#define SMIAPP_DPHY_CTRL_AUTOMATIC 0 +/* DPHY control based on REQUESTED_LINK_BIT_RATE_MBPS */ +#define SMIAPP_DPHY_CTRL_UI 1 +#define SMIAPP_DPHY_CTRL_REGISTER 2 + +#define SMIAPP_COMPRESSION_MODE_SIMPLE_PREDICTOR 1 +#define SMIAPP_COMPRESSION_MODE_ADVANCED_PREDICTOR 2 + +#define SMIAPP_MODE_SELECT_SOFTWARE_STANDBY 0 +#define SMIAPP_MODE_SELECT_STREAMING 1 + +#define SMIAPP_SCALING_MODE_NONE 0 +#define SMIAPP_SCALING_MODE_HORIZONTAL 1 +#define SMIAPP_SCALING_MODE_BOTH 2 + +#define SMIAPP_SCALING_CAPABILITY_NONE 0 +#define SMIAPP_SCALING_CAPABILITY_HORIZONTAL 1 +#define SMIAPP_SCALING_CAPABILITY_BOTH 2 /* horizontal/both */ + +/* digital crop right before scaler */ +#define SMIAPP_DIGITAL_CROP_CAPABILITY_NONE 0 +#define SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP 1 + +#define SMIAPP_BINNING_CAPABILITY_NO 0 +#define SMIAPP_BINNING_CAPABILITY_YES 1 + +/* Maximum number of binning subtypes */ +#define SMIAPP_BINNING_SUBTYPES 253 + +#define SMIAPP_PIXEL_ORDER_GRBG 0 +#define SMIAPP_PIXEL_ORDER_RGGB 1 +#define SMIAPP_PIXEL_ORDER_BGGR 2 +#define SMIAPP_PIXEL_ORDER_GBRG 3 + +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL 1 +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED 2 +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL_N 8 +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED_N 16 + +#define SMIAPP_FRAME_FORMAT_MODEL_TYPE_2BYTE 0x01 +#define SMIAPP_FRAME_FORMAT_MODEL_TYPE_4BYTE 0x02 +#define SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NROWS_MASK 0x0f +#define SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_MASK 0xf0 +#define SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_SHIFT 4 + +#define SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_MASK 0xf000 +#define SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_SHIFT 12 +#define SMIAPP_FRAME_FORMAT_DESC_2_PIXELS_MASK 0x0fff + +#define SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_MASK 0xf0000000 +#define SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_SHIFT 28 +#define SMIAPP_FRAME_FORMAT_DESC_4_PIXELS_MASK 0x0000ffff + +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_EMBEDDED 1 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DUMMY 2 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_BLACK 3 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DARK 4 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_VISIBLE 5 + +#define SMIAPP_FAST_STANDBY_CTRL_COMPLETE_FRAMES 0 +#define SMIAPP_FAST_STANDBY_CTRL_IMMEDIATE 1 + +/* Scaling N factor */ +#define SMIAPP_SCALE_N 16 + +/* Image statistics registers */ +/* Registers 0x2000 to 0x2fff are reserved for future + * use for statistics features. + */ + +/* Manufacturer Specific Registers: 0x3000 to 0x3fff + * The manufacturer specifies these as a black box. + */ + +#endif /* __SMIAPP_REG_H_ */ diff --git a/drivers/media/i2c/smiapp/smiapp-regs.c b/drivers/media/i2c/smiapp/smiapp-regs.c new file mode 100644 index 000000000000..70e0d8db0130 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp-regs.c @@ -0,0 +1,273 @@ +/* + * drivers/media/i2c/smiapp/smiapp-regs.c + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include + +#include "smiapp.h" +#include "smiapp-regs.h" + +static uint32_t float_to_u32_mul_1000000(struct i2c_client *client, + uint32_t phloat) +{ + int32_t exp; + uint64_t man; + + if (phloat >= 0x80000000) { + dev_err(&client->dev, "this is a negative number\n"); + return 0; + } + + if (phloat == 0x7f800000) + return ~0; /* Inf. */ + + if ((phloat & 0x7f800000) == 0x7f800000) { + dev_err(&client->dev, "NaN or other special number\n"); + return 0; + } + + /* Valid cases begin here */ + if (phloat == 0) + return 0; /* Valid zero */ + + if (phloat > 0x4f800000) + return ~0; /* larger than 4294967295 */ + + /* + * Unbias exponent (note how phloat is now guaranteed to + * have 0 in the high bit) + */ + exp = ((int32_t)phloat >> 23) - 127; + + /* Extract mantissa, add missing '1' bit and it's in MHz */ + man = ((phloat & 0x7fffff) | 0x800000) * 1000000ULL; + + if (exp < 0) + man >>= -exp; + else + man <<= exp; + + man >>= 23; /* Remove mantissa bias */ + + return man & 0xffffffff; +} + + +/* + * Read a 8/16/32-bit i2c register. The value is returned in 'val'. + * Returns zero if successful, or non-zero otherwise. + */ +static int ____smiapp_read(struct smiapp_sensor *sensor, u16 reg, + u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct i2c_msg msg; + unsigned char data[4]; + u16 offset = reg; + int r; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = data; + + /* high byte goes out first */ + data[0] = (u8) (offset >> 8); + data[1] = (u8) offset; + r = i2c_transfer(client->adapter, &msg, 1); + if (r != 1) { + if (r >= 0) + r = -EBUSY; + goto err; + } + + msg.len = len; + msg.flags = I2C_M_RD; + r = i2c_transfer(client->adapter, &msg, 1); + if (r != 1) { + if (r >= 0) + r = -EBUSY; + goto err; + } + + *val = 0; + /* high byte comes first */ + switch (len) { + case SMIA_REG_32BIT: + *val = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + + data[3]; + break; + case SMIA_REG_16BIT: + *val = (data[0] << 8) + data[1]; + break; + case SMIA_REG_8BIT: + *val = data[0]; + break; + default: + BUG(); + } + + return 0; + +err: + dev_err(&client->dev, "read from offset 0x%x error %d\n", offset, r); + + return r; +} + +/* Read a register using 8-bit access only. */ +static int ____smiapp_read_8only(struct smiapp_sensor *sensor, u16 reg, + u16 len, u32 *val) +{ + unsigned int i; + int rval; + + *val = 0; + + for (i = 0; i < len; i++) { + u32 val8; + + rval = ____smiapp_read(sensor, reg + i, 1, &val8); + if (rval < 0) + return rval; + *val |= val8 << ((len - i - 1) << 3); + } + + return 0; +} + +/* + * Read a 8/16/32-bit i2c register. The value is returned in 'val'. + * Returns zero if successful, or non-zero otherwise. + */ +static int __smiapp_read(struct smiapp_sensor *sensor, u32 reg, u32 *val, + bool only8) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int len = (u8)(reg >> 16); + int rval; + + if (len != SMIA_REG_8BIT && len != SMIA_REG_16BIT + && len != SMIA_REG_32BIT) + return -EINVAL; + + if (smiapp_quirk_reg(sensor, reg, val)) + goto found_quirk; + + if (len == SMIA_REG_8BIT && !only8) + rval = ____smiapp_read(sensor, (u16)reg, len, val); + else + rval = ____smiapp_read_8only(sensor, (u16)reg, len, val); + if (rval < 0) + return rval; + +found_quirk: + if (reg & SMIA_REG_FLAG_FLOAT) + *val = float_to_u32_mul_1000000(client, *val); + + return 0; +} + +int smiapp_read(struct smiapp_sensor *sensor, u32 reg, u32 *val) +{ + return __smiapp_read( + sensor, reg, val, + smiapp_needs_quirk(sensor, + SMIAPP_QUIRK_FLAG_8BIT_READ_ONLY)); +} + +int smiapp_read_8only(struct smiapp_sensor *sensor, u32 reg, u32 *val) +{ + return __smiapp_read(sensor, reg, val, true); +} + +/* + * Write to a 8/16-bit register. + * Returns zero if successful, or non-zero otherwise. + */ +int smiapp_write(struct smiapp_sensor *sensor, u32 reg, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct i2c_msg msg; + unsigned char data[6]; + unsigned int retries; + unsigned int flags = reg >> 24; + unsigned int len = (u8)(reg >> 16); + u16 offset = reg; + int r; + + if ((len != SMIA_REG_8BIT && len != SMIA_REG_16BIT && + len != SMIA_REG_32BIT) || flags) + return -EINVAL; + + msg.addr = client->addr; + msg.flags = 0; /* Write */ + msg.len = 2 + len; + msg.buf = data; + + /* high byte goes out first */ + data[0] = (u8) (reg >> 8); + data[1] = (u8) (reg & 0xff); + + switch (len) { + case SMIA_REG_8BIT: + data[2] = val; + break; + case SMIA_REG_16BIT: + data[2] = val >> 8; + data[3] = val; + break; + case SMIA_REG_32BIT: + data[2] = val >> 24; + data[3] = val >> 16; + data[4] = val >> 8; + data[5] = val; + break; + default: + BUG(); + } + + for (retries = 0; retries < 5; retries++) { + /* + * Due to unknown reason sensor stops responding. This + * loop is a temporaty solution until the root cause + * is found. + */ + r = i2c_transfer(client->adapter, &msg, 1); + if (r == 1) { + if (retries) + dev_err(&client->dev, + "sensor i2c stall encountered. " + "retries: %d\n", retries); + return 0; + } + + usleep_range(2000, 2000); + } + + dev_err(&client->dev, + "wrote 0x%x to offset 0x%x error %d\n", val, offset, r); + + return r; +} diff --git a/drivers/media/video/smiapp/smiapp-regs.h b/drivers/media/i2c/smiapp/smiapp-regs.h similarity index 100% rename from drivers/media/video/smiapp/smiapp-regs.h rename to drivers/media/i2c/smiapp/smiapp-regs.h diff --git a/drivers/media/i2c/smiapp/smiapp.h b/drivers/media/i2c/smiapp/smiapp.h new file mode 100644 index 000000000000..4182a695ab53 --- /dev/null +++ b/drivers/media/i2c/smiapp/smiapp.h @@ -0,0 +1,252 @@ +/* + * drivers/media/i2c/smiapp/smiapp.h + * + * Generic driver for SMIA/SMIA++ compliant camera modules + * + * Copyright (C) 2010--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __SMIAPP_PRIV_H_ +#define __SMIAPP_PRIV_H_ + +#include +#include +#include +#include + +#include "smiapp-pll.h" +#include "smiapp-reg.h" +#include "smiapp-regs.h" +#include "smiapp-quirk.h" + +/* + * Standard SMIA++ constants + */ +#define SMIA_VERSION_1 10 +#define SMIAPP_VERSION_0_8 8 /* Draft 0.8 */ +#define SMIAPP_VERSION_0_9 9 /* Draft 0.9 */ +#define SMIAPP_VERSION_1 10 + +#define SMIAPP_PROFILE_0 0 +#define SMIAPP_PROFILE_1 1 +#define SMIAPP_PROFILE_2 2 + +#define SMIAPP_NVM_PAGE_SIZE 64 /* bytes */ + +#define SMIAPP_RESET_DELAY_CLOCKS 2400 +#define SMIAPP_RESET_DELAY(clk) \ + (1000 + (SMIAPP_RESET_DELAY_CLOCKS * 1000 \ + + (clk) / 1000 - 1) / ((clk) / 1000)) + +#include "smiapp-limits.h" + +struct smiapp_quirk; + +#define SMIAPP_MODULE_IDENT_FLAG_REV_LE (1 << 0) + +struct smiapp_module_ident { + u8 manufacturer_id; + u16 model_id; + u8 revision_number_major; + + u8 flags; + + char *name; + const struct smiapp_quirk *quirk; +}; + +struct smiapp_module_info { + u32 manufacturer_id; + u32 model_id; + u32 revision_number_major; + u32 revision_number_minor; + + u32 module_year; + u32 module_month; + u32 module_day; + + u32 sensor_manufacturer_id; + u32 sensor_model_id; + u32 sensor_revision_number; + u32 sensor_firmware_version; + + u32 smia_version; + u32 smiapp_version; + + u32 smiapp_profile; + + char *name; + const struct smiapp_quirk *quirk; +}; + +#define SMIAPP_IDENT_FQ(manufacturer, model, rev, fl, _name, _quirk) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = fl, \ + .name = _name, \ + .quirk = _quirk, } + +#define SMIAPP_IDENT_LQ(manufacturer, model, rev, _name, _quirk) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = SMIAPP_MODULE_IDENT_FLAG_REV_LE, \ + .name = _name, \ + .quirk = _quirk, } + +#define SMIAPP_IDENT_L(manufacturer, model, rev, _name) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = SMIAPP_MODULE_IDENT_FLAG_REV_LE, \ + .name = _name, } + +#define SMIAPP_IDENT_Q(manufacturer, model, rev, _name, _quirk) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = 0, \ + .name = _name, \ + .quirk = _quirk, } + +#define SMIAPP_IDENT(manufacturer, model, rev, _name) \ + { .manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = 0, \ + .name = _name, } + +struct smiapp_reg_limits { + u32 addr; + char *what; +}; + +extern struct smiapp_reg_limits smiapp_reg_limits[]; + +struct smiapp_csi_data_format { + u32 code; + u8 width; + u8 compressed; + u8 pixel_order; +}; + +#define SMIAPP_SUBDEVS 3 + +#define SMIAPP_PA_PAD_SRC 0 +#define SMIAPP_PAD_SINK 0 +#define SMIAPP_PAD_SRC 1 +#define SMIAPP_PADS 2 + +struct smiapp_binning_subtype { + u8 horizontal:4; + u8 vertical:4; +} __packed; + +struct smiapp_subdev { + struct v4l2_subdev sd; + struct media_pad pads[2]; + struct v4l2_rect sink_fmt; + struct v4l2_rect crop[2]; + struct v4l2_rect compose; /* compose on sink */ + unsigned short sink_pad; + unsigned short source_pad; + int npads; + struct smiapp_sensor *sensor; + struct v4l2_ctrl_handler ctrl_handler; +}; + +/* + * struct smiapp_sensor - Main device structure + */ +struct smiapp_sensor { + /* + * "mutex" is used to serialise access to all fields here + * except v4l2_ctrls at the end of the struct. "mutex" is also + * used to serialise access to file handle specific + * information. The exception to this rule is the power_mutex + * below. + */ + struct mutex mutex; + /* + * power_mutex is used to serialise power management related + * activities. Acquiring "mutex" at that time isn't necessary + * since there are no other users anyway. + */ + struct mutex power_mutex; + struct smiapp_subdev ssds[SMIAPP_SUBDEVS]; + u32 ssds_used; + struct smiapp_subdev *src; + struct smiapp_subdev *binner; + struct smiapp_subdev *scaler; + struct smiapp_subdev *pixel_array; + struct smiapp_platform_data *platform_data; + struct regulator *vana; + struct clk *ext_clk; + u32 limits[SMIAPP_LIMIT_LAST]; + u8 nbinning_subtypes; + struct smiapp_binning_subtype binning_subtypes[SMIAPP_BINNING_SUBTYPES]; + u32 mbus_frame_fmts; + const struct smiapp_csi_data_format *csi_format; + const struct smiapp_csi_data_format *internal_csi_format; + u32 default_mbus_frame_fmts; + int default_pixel_order; + + u8 binning_horizontal; + u8 binning_vertical; + + u8 scale_m; + u8 scaling_mode; + + u8 hvflip_inv_mask; /* H/VFLIP inversion due to sensor orientation */ + u8 flash_capability; + u8 frame_skip; + + int power_count; + + bool streaming; + bool dev_init_done; + + u8 *nvm; /* nvm memory buffer */ + unsigned int nvm_size; /* bytes */ + + struct smiapp_module_info minfo; + + struct smiapp_pll pll; + + /* Pixel array controls */ + struct v4l2_ctrl *analog_gain; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *pixel_rate_parray; + /* src controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate_csi; +}; + +#define to_smiapp_subdev(_sd) \ + container_of(_sd, struct smiapp_subdev, sd) + +#define to_smiapp_sensor(_sd) \ + (to_smiapp_subdev(_sd)->sensor) + +#endif /* __SMIAPP_PRIV_H_ */ diff --git a/drivers/media/i2c/soc_camera/Kconfig b/drivers/media/i2c/soc_camera/Kconfig new file mode 100644 index 000000000000..6dff2b7ad520 --- /dev/null +++ b/drivers/media/i2c/soc_camera/Kconfig @@ -0,0 +1,89 @@ +comment "soc_camera sensor drivers" + +config SOC_CAMERA_IMX074 + tristate "imx074 support" + depends on SOC_CAMERA && I2C + help + This driver supports IMX074 cameras from Sony + +config SOC_CAMERA_MT9M001 + tristate "mt9m001 support" + depends on SOC_CAMERA && I2C + select GPIO_PCA953X if MT9M001_PCA9536_SWITCH + help + This driver supports MT9M001 cameras from Micron, monochrome + and colour models. + +config SOC_CAMERA_MT9M111 + tristate "mt9m111, mt9m112 and mt9m131 support" + depends on SOC_CAMERA && I2C + help + This driver supports MT9M111, MT9M112 and MT9M131 cameras from + Micron/Aptina + +config SOC_CAMERA_MT9T031 + tristate "mt9t031 support" + depends on SOC_CAMERA && I2C + help + This driver supports MT9T031 cameras from Micron. + +config SOC_CAMERA_MT9T112 + tristate "mt9t112 support" + depends on SOC_CAMERA && I2C + help + This driver supports MT9T112 cameras from Aptina. + +config SOC_CAMERA_MT9V022 + tristate "mt9v022 and mt9v024 support" + depends on SOC_CAMERA && I2C + select GPIO_PCA953X if MT9V022_PCA9536_SWITCH + help + This driver supports MT9V022 cameras from Micron + +config SOC_CAMERA_OV2640 + tristate "ov2640 camera support" + depends on SOC_CAMERA && I2C + help + This is a ov2640 camera driver + +config SOC_CAMERA_OV5642 + tristate "ov5642 camera support" + depends on SOC_CAMERA && I2C + help + This is a V4L2 camera driver for the OmniVision OV5642 sensor + +config SOC_CAMERA_OV6650 + tristate "ov6650 sensor support" + depends on SOC_CAMERA && I2C + ---help--- + This is a V4L2 SoC camera driver for the OmniVision OV6650 sensor + +config SOC_CAMERA_OV772X + tristate "ov772x camera support" + depends on SOC_CAMERA && I2C + help + This is a ov772x camera driver + +config SOC_CAMERA_OV9640 + tristate "ov9640 camera support" + depends on SOC_CAMERA && I2C + help + This is a ov9640 camera driver + +config SOC_CAMERA_OV9740 + tristate "ov9740 camera support" + depends on SOC_CAMERA && I2C + help + This is a ov9740 camera driver + +config SOC_CAMERA_RJ54N1 + tristate "rj54n1cb0c support" + depends on SOC_CAMERA && I2C + help + This is a rj54n1cb0c video driver + +config SOC_CAMERA_TW9910 + tristate "tw9910 support" + depends on SOC_CAMERA && I2C + help + This is a tw9910 video driver diff --git a/drivers/media/i2c/soc_camera/Makefile b/drivers/media/i2c/soc_camera/Makefile new file mode 100644 index 000000000000..d0421feaa796 --- /dev/null +++ b/drivers/media/i2c/soc_camera/Makefile @@ -0,0 +1,14 @@ +obj-$(CONFIG_SOC_CAMERA_IMX074) += imx074.o +obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o +obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m111.o +obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o +obj-$(CONFIG_SOC_CAMERA_MT9T112) += mt9t112.o +obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o +obj-$(CONFIG_SOC_CAMERA_OV2640) += ov2640.o +obj-$(CONFIG_SOC_CAMERA_OV5642) += ov5642.o +obj-$(CONFIG_SOC_CAMERA_OV6650) += ov6650.o +obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o +obj-$(CONFIG_SOC_CAMERA_OV9640) += ov9640.o +obj-$(CONFIG_SOC_CAMERA_OV9740) += ov9740.o +obj-$(CONFIG_SOC_CAMERA_RJ54N1) += rj54n1cb0c.o +obj-$(CONFIG_SOC_CAMERA_TW9910) += tw9910.o diff --git a/drivers/media/i2c/soc_camera/imx074.c b/drivers/media/i2c/soc_camera/imx074.c new file mode 100644 index 000000000000..f8534eec9de9 --- /dev/null +++ b/drivers/media/i2c/soc_camera/imx074.c @@ -0,0 +1,495 @@ +/* + * Driver for IMX074 CMOS Image Sensor from Sony + * + * Copyright (C) 2010, Guennadi Liakhovetski + * + * Partially inspired by the IMX074 driver from the Android / MSM tree + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* IMX074 registers */ + +#define MODE_SELECT 0x0100 +#define IMAGE_ORIENTATION 0x0101 +#define GROUPED_PARAMETER_HOLD 0x0104 + +/* Integration Time */ +#define COARSE_INTEGRATION_TIME_HI 0x0202 +#define COARSE_INTEGRATION_TIME_LO 0x0203 +/* Gain */ +#define ANALOGUE_GAIN_CODE_GLOBAL_HI 0x0204 +#define ANALOGUE_GAIN_CODE_GLOBAL_LO 0x0205 + +/* PLL registers */ +#define PRE_PLL_CLK_DIV 0x0305 +#define PLL_MULTIPLIER 0x0307 +#define PLSTATIM 0x302b +#define VNDMY_ABLMGSHLMT 0x300a +#define Y_OPBADDR_START_DI 0x3014 +/* mode setting */ +#define FRAME_LENGTH_LINES_HI 0x0340 +#define FRAME_LENGTH_LINES_LO 0x0341 +#define LINE_LENGTH_PCK_HI 0x0342 +#define LINE_LENGTH_PCK_LO 0x0343 +#define YADDR_START 0x0347 +#define YADDR_END 0x034b +#define X_OUTPUT_SIZE_MSB 0x034c +#define X_OUTPUT_SIZE_LSB 0x034d +#define Y_OUTPUT_SIZE_MSB 0x034e +#define Y_OUTPUT_SIZE_LSB 0x034f +#define X_EVEN_INC 0x0381 +#define X_ODD_INC 0x0383 +#define Y_EVEN_INC 0x0385 +#define Y_ODD_INC 0x0387 + +#define HMODEADD 0x3001 +#define VMODEADD 0x3016 +#define VAPPLINE_START 0x3069 +#define VAPPLINE_END 0x306b +#define SHUTTER 0x3086 +#define HADDAVE 0x30e8 +#define LANESEL 0x3301 + +/* IMX074 supported geometry */ +#define IMX074_WIDTH 1052 +#define IMX074_HEIGHT 780 + +/* IMX074 has only one fixed colorspace per pixelcode */ +struct imx074_datafmt { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; +}; + +struct imx074 { + struct v4l2_subdev subdev; + const struct imx074_datafmt *fmt; +}; + +static const struct imx074_datafmt imx074_colour_fmts[] = { + {V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB}, +}; + +static struct imx074 *to_imx074(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct imx074, subdev); +} + +/* Find a data format by a pixel code in an array */ +static const struct imx074_datafmt *imx074_find_datafmt(enum v4l2_mbus_pixelcode code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(imx074_colour_fmts); i++) + if (imx074_colour_fmts[i].code == code) + return imx074_colour_fmts + i; + + return NULL; +} + +static int reg_write(struct i2c_client *client, const u16 addr, const u8 data) +{ + struct i2c_adapter *adap = client->adapter; + struct i2c_msg msg; + unsigned char tx[3]; + int ret; + + msg.addr = client->addr; + msg.buf = tx; + msg.len = 3; + msg.flags = 0; + + tx[0] = addr >> 8; + tx[1] = addr & 0xff; + tx[2] = data; + + ret = i2c_transfer(adap, &msg, 1); + + mdelay(2); + + return ret == 1 ? 0 : -EIO; +} + +static int reg_read(struct i2c_client *client, const u16 addr) +{ + u8 buf[2] = {addr >> 8, addr & 0xff}; + int ret; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = buf, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 2, + .buf = buf, + }, + }; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + dev_warn(&client->dev, "Reading register %x from %x failed\n", + addr, client->addr); + return ret; + } + + return buf[0] & 0xff; /* no sign-extension */ +} + +static int imx074_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + const struct imx074_datafmt *fmt = imx074_find_datafmt(mf->code); + + dev_dbg(sd->v4l2_dev->dev, "%s(%u)\n", __func__, mf->code); + + if (!fmt) { + mf->code = imx074_colour_fmts[0].code; + mf->colorspace = imx074_colour_fmts[0].colorspace; + } + + mf->width = IMX074_WIDTH; + mf->height = IMX074_HEIGHT; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int imx074_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct imx074 *priv = to_imx074(client); + + dev_dbg(sd->v4l2_dev->dev, "%s(%u)\n", __func__, mf->code); + + /* MIPI CSI could have changed the format, double-check */ + if (!imx074_find_datafmt(mf->code)) + return -EINVAL; + + imx074_try_fmt(sd, mf); + + priv->fmt = imx074_find_datafmt(mf->code); + + return 0; +} + +static int imx074_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct imx074 *priv = to_imx074(client); + + const struct imx074_datafmt *fmt = priv->fmt; + + mf->code = fmt->code; + mf->colorspace = fmt->colorspace; + mf->width = IMX074_WIDTH; + mf->height = IMX074_HEIGHT; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int imx074_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct v4l2_rect *rect = &a->c; + + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + rect->top = 0; + rect->left = 0; + rect->width = IMX074_WIDTH; + rect->height = IMX074_HEIGHT; + + return 0; +} + +static int imx074_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = IMX074_WIDTH; + a->bounds.height = IMX074_HEIGHT; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int imx074_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if ((unsigned int)index >= ARRAY_SIZE(imx074_colour_fmts)) + return -EINVAL; + + *code = imx074_colour_fmts[index].code; + return 0; +} + +static int imx074_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* MODE_SELECT: stream or standby */ + return reg_write(client, MODE_SELECT, !!enable); +} + +static int imx074_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + if (id->match.addr != client->addr) + return -ENODEV; + + id->ident = V4L2_IDENT_IMX074; + id->revision = 0; + + return 0; +} + +static int imx074_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +static int imx074_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + cfg->type = V4L2_MBUS_CSI2; + cfg->flags = V4L2_MBUS_CSI2_2_LANE | + V4L2_MBUS_CSI2_CHANNEL_0 | + V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + + return 0; +} + +static struct v4l2_subdev_video_ops imx074_subdev_video_ops = { + .s_stream = imx074_s_stream, + .s_mbus_fmt = imx074_s_fmt, + .g_mbus_fmt = imx074_g_fmt, + .try_mbus_fmt = imx074_try_fmt, + .enum_mbus_fmt = imx074_enum_fmt, + .g_crop = imx074_g_crop, + .cropcap = imx074_cropcap, + .g_mbus_config = imx074_g_mbus_config, +}; + +static struct v4l2_subdev_core_ops imx074_subdev_core_ops = { + .g_chip_ident = imx074_g_chip_ident, + .s_power = imx074_s_power, +}; + +static struct v4l2_subdev_ops imx074_subdev_ops = { + .core = &imx074_subdev_core_ops, + .video = &imx074_subdev_video_ops, +}; + +static int imx074_video_probe(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + int ret; + u16 id; + + ret = imx074_s_power(subdev, 1); + if (ret < 0) + return ret; + + /* Read sensor Model ID */ + ret = reg_read(client, 0); + if (ret < 0) + goto done; + + id = ret << 8; + + ret = reg_read(client, 1); + if (ret < 0) + goto done; + + id |= ret; + + dev_info(&client->dev, "Chip ID 0x%04x detected\n", id); + + if (id != 0x74) { + ret = -ENODEV; + goto done; + } + + /* PLL Setting EXTCLK=24MHz, 22.5times */ + reg_write(client, PLL_MULTIPLIER, 0x2D); + reg_write(client, PRE_PLL_CLK_DIV, 0x02); + reg_write(client, PLSTATIM, 0x4B); + + /* 2-lane mode */ + reg_write(client, 0x3024, 0x00); + + reg_write(client, IMAGE_ORIENTATION, 0x00); + + /* select RAW mode: + * 0x08+0x08 = top 8 bits + * 0x0a+0x08 = compressed 8-bits + * 0x0a+0x0a = 10 bits + */ + reg_write(client, 0x0112, 0x08); + reg_write(client, 0x0113, 0x08); + + /* Base setting for High frame mode */ + reg_write(client, VNDMY_ABLMGSHLMT, 0x80); + reg_write(client, Y_OPBADDR_START_DI, 0x08); + reg_write(client, 0x3015, 0x37); + reg_write(client, 0x301C, 0x01); + reg_write(client, 0x302C, 0x05); + reg_write(client, 0x3031, 0x26); + reg_write(client, 0x3041, 0x60); + reg_write(client, 0x3051, 0x24); + reg_write(client, 0x3053, 0x34); + reg_write(client, 0x3057, 0xC0); + reg_write(client, 0x305C, 0x09); + reg_write(client, 0x305D, 0x07); + reg_write(client, 0x3060, 0x30); + reg_write(client, 0x3065, 0x00); + reg_write(client, 0x30AA, 0x08); + reg_write(client, 0x30AB, 0x1C); + reg_write(client, 0x30B0, 0x32); + reg_write(client, 0x30B2, 0x83); + reg_write(client, 0x30D3, 0x04); + reg_write(client, 0x3106, 0x78); + reg_write(client, 0x310C, 0x82); + reg_write(client, 0x3304, 0x05); + reg_write(client, 0x3305, 0x04); + reg_write(client, 0x3306, 0x11); + reg_write(client, 0x3307, 0x02); + reg_write(client, 0x3308, 0x0C); + reg_write(client, 0x3309, 0x06); + reg_write(client, 0x330A, 0x08); + reg_write(client, 0x330B, 0x04); + reg_write(client, 0x330C, 0x08); + reg_write(client, 0x330D, 0x06); + reg_write(client, 0x330E, 0x01); + reg_write(client, 0x3381, 0x00); + + /* V : 1/2V-addition (1,3), H : 1/2H-averaging (1,3) -> Full HD */ + /* 1608 = 1560 + 48 (black lines) */ + reg_write(client, FRAME_LENGTH_LINES_HI, 0x06); + reg_write(client, FRAME_LENGTH_LINES_LO, 0x48); + reg_write(client, YADDR_START, 0x00); + reg_write(client, YADDR_END, 0x2F); + /* 0x838 == 2104 */ + reg_write(client, X_OUTPUT_SIZE_MSB, 0x08); + reg_write(client, X_OUTPUT_SIZE_LSB, 0x38); + /* 0x618 == 1560 */ + reg_write(client, Y_OUTPUT_SIZE_MSB, 0x06); + reg_write(client, Y_OUTPUT_SIZE_LSB, 0x18); + reg_write(client, X_EVEN_INC, 0x01); + reg_write(client, X_ODD_INC, 0x03); + reg_write(client, Y_EVEN_INC, 0x01); + reg_write(client, Y_ODD_INC, 0x03); + reg_write(client, HMODEADD, 0x00); + reg_write(client, VMODEADD, 0x16); + reg_write(client, VAPPLINE_START, 0x24); + reg_write(client, VAPPLINE_END, 0x53); + reg_write(client, SHUTTER, 0x00); + reg_write(client, HADDAVE, 0x80); + + reg_write(client, LANESEL, 0x00); + + reg_write(client, GROUPED_PARAMETER_HOLD, 0x00); /* off */ + + ret = 0; + +done: + imx074_s_power(subdev, 0); + return ret; +} + +static int imx074_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct imx074 *priv; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + if (!icl) { + dev_err(&client->dev, "IMX074: missing platform data!\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE\n"); + return -EIO; + } + + priv = kzalloc(sizeof(struct imx074), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + v4l2_i2c_subdev_init(&priv->subdev, client, &imx074_subdev_ops); + + priv->fmt = &imx074_colour_fmts[0]; + + ret = imx074_video_probe(client); + if (ret < 0) { + kfree(priv); + return ret; + } + + return ret; +} + +static int imx074_remove(struct i2c_client *client) +{ + struct imx074 *priv = to_imx074(client); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + if (icl->free_bus) + icl->free_bus(icl); + kfree(priv); + + return 0; +} + +static const struct i2c_device_id imx074_id[] = { + { "imx074", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, imx074_id); + +static struct i2c_driver imx074_i2c_driver = { + .driver = { + .name = "imx074", + }, + .probe = imx074_probe, + .remove = imx074_remove, + .id_table = imx074_id, +}; + +module_i2c_driver(imx074_i2c_driver); + +MODULE_DESCRIPTION("Sony IMX074 Camera driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/soc_camera/mt9m001.c b/drivers/media/i2c/soc_camera/mt9m001.c new file mode 100644 index 000000000000..19f8a07764f9 --- /dev/null +++ b/drivers/media/i2c/soc_camera/mt9m001.c @@ -0,0 +1,757 @@ +/* + * Driver for MT9M001 CMOS Image Sensor from Micron + * + * Copyright (C) 2008, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * mt9m001 i2c address 0x5d + * The platform has to define struct i2c_board_info objects and link to them + * from struct soc_camera_link + */ + +/* mt9m001 selected register addresses */ +#define MT9M001_CHIP_VERSION 0x00 +#define MT9M001_ROW_START 0x01 +#define MT9M001_COLUMN_START 0x02 +#define MT9M001_WINDOW_HEIGHT 0x03 +#define MT9M001_WINDOW_WIDTH 0x04 +#define MT9M001_HORIZONTAL_BLANKING 0x05 +#define MT9M001_VERTICAL_BLANKING 0x06 +#define MT9M001_OUTPUT_CONTROL 0x07 +#define MT9M001_SHUTTER_WIDTH 0x09 +#define MT9M001_FRAME_RESTART 0x0b +#define MT9M001_SHUTTER_DELAY 0x0c +#define MT9M001_RESET 0x0d +#define MT9M001_READ_OPTIONS1 0x1e +#define MT9M001_READ_OPTIONS2 0x20 +#define MT9M001_GLOBAL_GAIN 0x35 +#define MT9M001_CHIP_ENABLE 0xF1 + +#define MT9M001_MAX_WIDTH 1280 +#define MT9M001_MAX_HEIGHT 1024 +#define MT9M001_MIN_WIDTH 48 +#define MT9M001_MIN_HEIGHT 32 +#define MT9M001_COLUMN_SKIP 20 +#define MT9M001_ROW_SKIP 12 + +/* MT9M001 has only one fixed colorspace per pixelcode */ +struct mt9m001_datafmt { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; +}; + +/* Find a data format by a pixel code in an array */ +static const struct mt9m001_datafmt *mt9m001_find_datafmt( + enum v4l2_mbus_pixelcode code, const struct mt9m001_datafmt *fmt, + int n) +{ + int i; + for (i = 0; i < n; i++) + if (fmt[i].code == code) + return fmt + i; + + return NULL; +} + +static const struct mt9m001_datafmt mt9m001_colour_fmts[] = { + /* + * Order important: first natively supported, + * second supported with a GPIO extender + */ + {V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB}, +}; + +static const struct mt9m001_datafmt mt9m001_monochrome_fmts[] = { + /* Order important - see above */ + {V4L2_MBUS_FMT_Y10_1X10, V4L2_COLORSPACE_JPEG}, + {V4L2_MBUS_FMT_Y8_1X8, V4L2_COLORSPACE_JPEG}, +}; + +struct mt9m001 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct { + /* exposure/auto-exposure cluster */ + struct v4l2_ctrl *autoexposure; + struct v4l2_ctrl *exposure; + }; + struct v4l2_rect rect; /* Sensor window */ + const struct mt9m001_datafmt *fmt; + const struct mt9m001_datafmt *fmts; + int num_fmts; + int model; /* V4L2_IDENT_MT9M001* codes from v4l2-chip-ident.h */ + unsigned int total_h; + unsigned short y_skip_top; /* Lines to skip at the top */ +}; + +static struct mt9m001 *to_mt9m001(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct mt9m001, subdev); +} + +static int reg_read(struct i2c_client *client, const u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int reg_write(struct i2c_client *client, const u8 reg, + const u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static int reg_set(struct i2c_client *client, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, ret | data); +} + +static int reg_clear(struct i2c_client *client, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, ret & ~data); +} + +static int mt9m001_init(struct i2c_client *client) +{ + int ret; + + dev_dbg(&client->dev, "%s\n", __func__); + + /* + * We don't know, whether platform provides reset, issue a soft reset + * too. This returns all registers to their default values. + */ + ret = reg_write(client, MT9M001_RESET, 1); + if (!ret) + ret = reg_write(client, MT9M001_RESET, 0); + + /* Disable chip, synchronous option update */ + if (!ret) + ret = reg_write(client, MT9M001_OUTPUT_CONTROL, 0); + + return ret; +} + +static int mt9m001_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* Switch to master "normal" mode or stop sensor readout */ + if (reg_write(client, MT9M001_OUTPUT_CONTROL, enable ? 2 : 0) < 0) + return -EIO; + return 0; +} + +static int mt9m001_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + struct v4l2_rect rect = a->c; + int ret; + const u16 hblank = 9, vblank = 25; + + if (mt9m001->fmts == mt9m001_colour_fmts) + /* + * Bayer format - even number of rows for simplicity, + * but let the user play with the top row. + */ + rect.height = ALIGN(rect.height, 2); + + /* Datasheet requirement: see register description */ + rect.width = ALIGN(rect.width, 2); + rect.left = ALIGN(rect.left, 2); + + soc_camera_limit_side(&rect.left, &rect.width, + MT9M001_COLUMN_SKIP, MT9M001_MIN_WIDTH, MT9M001_MAX_WIDTH); + + soc_camera_limit_side(&rect.top, &rect.height, + MT9M001_ROW_SKIP, MT9M001_MIN_HEIGHT, MT9M001_MAX_HEIGHT); + + mt9m001->total_h = rect.height + mt9m001->y_skip_top + vblank; + + /* Blanking and start values - default... */ + ret = reg_write(client, MT9M001_HORIZONTAL_BLANKING, hblank); + if (!ret) + ret = reg_write(client, MT9M001_VERTICAL_BLANKING, vblank); + + /* + * The caller provides a supported format, as verified per + * call to .try_mbus_fmt() + */ + if (!ret) + ret = reg_write(client, MT9M001_COLUMN_START, rect.left); + if (!ret) + ret = reg_write(client, MT9M001_ROW_START, rect.top); + if (!ret) + ret = reg_write(client, MT9M001_WINDOW_WIDTH, rect.width - 1); + if (!ret) + ret = reg_write(client, MT9M001_WINDOW_HEIGHT, + rect.height + mt9m001->y_skip_top - 1); + if (!ret && v4l2_ctrl_g_ctrl(mt9m001->autoexposure) == V4L2_EXPOSURE_AUTO) + ret = reg_write(client, MT9M001_SHUTTER_WIDTH, mt9m001->total_h); + + if (!ret) + mt9m001->rect = rect; + + return ret; +} + +static int mt9m001_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + + a->c = mt9m001->rect; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int mt9m001_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = MT9M001_COLUMN_SKIP; + a->bounds.top = MT9M001_ROW_SKIP; + a->bounds.width = MT9M001_MAX_WIDTH; + a->bounds.height = MT9M001_MAX_HEIGHT; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int mt9m001_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + + mf->width = mt9m001->rect.width; + mf->height = mt9m001->rect.height; + mf->code = mt9m001->fmt->code; + mf->colorspace = mt9m001->fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mt9m001_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + struct v4l2_crop a = { + .c = { + .left = mt9m001->rect.left, + .top = mt9m001->rect.top, + .width = mf->width, + .height = mf->height, + }, + }; + int ret; + + /* No support for scaling so far, just crop. TODO: use skipping */ + ret = mt9m001_s_crop(sd, &a); + if (!ret) { + mf->width = mt9m001->rect.width; + mf->height = mt9m001->rect.height; + mt9m001->fmt = mt9m001_find_datafmt(mf->code, + mt9m001->fmts, mt9m001->num_fmts); + mf->colorspace = mt9m001->fmt->colorspace; + } + + return ret; +} + +static int mt9m001_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + const struct mt9m001_datafmt *fmt; + + v4l_bound_align_image(&mf->width, MT9M001_MIN_WIDTH, + MT9M001_MAX_WIDTH, 1, + &mf->height, MT9M001_MIN_HEIGHT + mt9m001->y_skip_top, + MT9M001_MAX_HEIGHT + mt9m001->y_skip_top, 0, 0); + + if (mt9m001->fmts == mt9m001_colour_fmts) + mf->height = ALIGN(mf->height - 1, 2); + + fmt = mt9m001_find_datafmt(mf->code, mt9m001->fmts, + mt9m001->num_fmts); + if (!fmt) { + fmt = mt9m001->fmt; + mf->code = fmt->code; + } + + mf->colorspace = fmt->colorspace; + + return 0; +} + +static int mt9m001_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + + if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + if (id->match.addr != client->addr) + return -ENODEV; + + id->ident = mt9m001->model; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9m001_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + reg->size = 2; + reg->val = reg_read(client, reg->reg); + + if (reg->val > 0xffff) + return -EIO; + + return 0; +} + +static int mt9m001_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + if (reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int mt9m001_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +static int mt9m001_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m001 *mt9m001 = container_of(ctrl->handler, + struct mt9m001, hdl); + s32 min, max; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE_AUTO: + min = mt9m001->exposure->minimum; + max = mt9m001->exposure->maximum; + mt9m001->exposure->val = + (524 + (mt9m001->total_h - 1) * (max - min)) / 1048 + min; + break; + } + return 0; +} + +static int mt9m001_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m001 *mt9m001 = container_of(ctrl->handler, + struct mt9m001, hdl); + struct v4l2_subdev *sd = &mt9m001->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_ctrl *exp = mt9m001->exposure; + int data; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->val) + data = reg_set(client, MT9M001_READ_OPTIONS2, 0x8000); + else + data = reg_clear(client, MT9M001_READ_OPTIONS2, 0x8000); + if (data < 0) + return -EIO; + return 0; + + case V4L2_CID_GAIN: + /* See Datasheet Table 7, Gain settings. */ + if (ctrl->val <= ctrl->default_value) { + /* Pack it into 0..1 step 0.125, register values 0..8 */ + unsigned long range = ctrl->default_value - ctrl->minimum; + data = ((ctrl->val - ctrl->minimum) * 8 + range / 2) / range; + + dev_dbg(&client->dev, "Setting gain %d\n", data); + data = reg_write(client, MT9M001_GLOBAL_GAIN, data); + if (data < 0) + return -EIO; + } else { + /* Pack it into 1.125..15 variable step, register values 9..67 */ + /* We assume qctrl->maximum - qctrl->default_value - 1 > 0 */ + unsigned long range = ctrl->maximum - ctrl->default_value - 1; + unsigned long gain = ((ctrl->val - ctrl->default_value - 1) * + 111 + range / 2) / range + 9; + + if (gain <= 32) + data = gain; + else if (gain <= 64) + data = ((gain - 32) * 16 + 16) / 32 + 80; + else + data = ((gain - 64) * 7 + 28) / 56 + 96; + + dev_dbg(&client->dev, "Setting gain from %d to %d\n", + reg_read(client, MT9M001_GLOBAL_GAIN), data); + data = reg_write(client, MT9M001_GLOBAL_GAIN, data); + if (data < 0) + return -EIO; + } + return 0; + + case V4L2_CID_EXPOSURE_AUTO: + if (ctrl->val == V4L2_EXPOSURE_MANUAL) { + unsigned long range = exp->maximum - exp->minimum; + unsigned long shutter = ((exp->val - exp->minimum) * 1048 + + range / 2) / range + 1; + + dev_dbg(&client->dev, + "Setting shutter width from %d to %lu\n", + reg_read(client, MT9M001_SHUTTER_WIDTH), shutter); + if (reg_write(client, MT9M001_SHUTTER_WIDTH, shutter) < 0) + return -EIO; + } else { + const u16 vblank = 25; + + mt9m001->total_h = mt9m001->rect.height + + mt9m001->y_skip_top + vblank; + if (reg_write(client, MT9M001_SHUTTER_WIDTH, mt9m001->total_h) < 0) + return -EIO; + } + return 0; + } + return -EINVAL; +} + +/* + * Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one + */ +static int mt9m001_video_probe(struct soc_camera_link *icl, + struct i2c_client *client) +{ + struct mt9m001 *mt9m001 = to_mt9m001(client); + s32 data; + unsigned long flags; + int ret; + + ret = mt9m001_s_power(&mt9m001->subdev, 1); + if (ret < 0) + return ret; + + /* Enable the chip */ + data = reg_write(client, MT9M001_CHIP_ENABLE, 1); + dev_dbg(&client->dev, "write: %d\n", data); + + /* Read out the chip version register */ + data = reg_read(client, MT9M001_CHIP_VERSION); + + /* must be 0x8411 or 0x8421 for colour sensor and 8431 for bw */ + switch (data) { + case 0x8411: + case 0x8421: + mt9m001->model = V4L2_IDENT_MT9M001C12ST; + mt9m001->fmts = mt9m001_colour_fmts; + break; + case 0x8431: + mt9m001->model = V4L2_IDENT_MT9M001C12STM; + mt9m001->fmts = mt9m001_monochrome_fmts; + break; + default: + dev_err(&client->dev, + "No MT9M001 chip detected, register read %x\n", data); + ret = -ENODEV; + goto done; + } + + mt9m001->num_fmts = 0; + + /* + * This is a 10bit sensor, so by default we only allow 10bit. + * The platform may support different bus widths due to + * different routing of the data lines. + */ + if (icl->query_bus_param) + flags = icl->query_bus_param(icl); + else + flags = SOCAM_DATAWIDTH_10; + + if (flags & SOCAM_DATAWIDTH_10) + mt9m001->num_fmts++; + else + mt9m001->fmts++; + + if (flags & SOCAM_DATAWIDTH_8) + mt9m001->num_fmts++; + + mt9m001->fmt = &mt9m001->fmts[0]; + + dev_info(&client->dev, "Detected a MT9M001 chip ID %x (%s)\n", data, + data == 0x8431 ? "C12STM" : "C12ST"); + + ret = mt9m001_init(client); + if (ret < 0) { + dev_err(&client->dev, "Failed to initialise the camera\n"); + goto done; + } + + /* mt9m001_init() has reset the chip, returning registers to defaults */ + ret = v4l2_ctrl_handler_setup(&mt9m001->hdl); + +done: + mt9m001_s_power(&mt9m001->subdev, 0); + return ret; +} + +static void mt9m001_video_remove(struct soc_camera_link *icl) +{ + if (icl->free_bus) + icl->free_bus(icl); +} + +static int mt9m001_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + + *lines = mt9m001->y_skip_top; + + return 0; +} + +static const struct v4l2_ctrl_ops mt9m001_ctrl_ops = { + .g_volatile_ctrl = mt9m001_g_volatile_ctrl, + .s_ctrl = mt9m001_s_ctrl, +}; + +static struct v4l2_subdev_core_ops mt9m001_subdev_core_ops = { + .g_chip_ident = mt9m001_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9m001_g_register, + .s_register = mt9m001_s_register, +#endif + .s_power = mt9m001_s_power, +}; + +static int mt9m001_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m001 *mt9m001 = to_mt9m001(client); + + if (index >= mt9m001->num_fmts) + return -EINVAL; + + *code = mt9m001->fmts[index].code; + return 0; +} + +static int mt9m001_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + /* MT9M001 has all capture_format parameters fixed */ + cfg->flags = V4L2_MBUS_PCLK_SAMPLE_FALLING | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_HIGH | V4L2_MBUS_MASTER; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static int mt9m001_s_mbus_config(struct v4l2_subdev *sd, + const struct v4l2_mbus_config *cfg) +{ + const struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct mt9m001 *mt9m001 = to_mt9m001(client); + unsigned int bps = soc_mbus_get_fmtdesc(mt9m001->fmt->code)->bits_per_sample; + + if (icl->set_bus_param) + return icl->set_bus_param(icl, 1 << (bps - 1)); + + /* + * Without board specific bus width settings we only support the + * sensors native bus width + */ + return bps == 10 ? 0 : -EINVAL; +} + +static struct v4l2_subdev_video_ops mt9m001_subdev_video_ops = { + .s_stream = mt9m001_s_stream, + .s_mbus_fmt = mt9m001_s_fmt, + .g_mbus_fmt = mt9m001_g_fmt, + .try_mbus_fmt = mt9m001_try_fmt, + .s_crop = mt9m001_s_crop, + .g_crop = mt9m001_g_crop, + .cropcap = mt9m001_cropcap, + .enum_mbus_fmt = mt9m001_enum_fmt, + .g_mbus_config = mt9m001_g_mbus_config, + .s_mbus_config = mt9m001_s_mbus_config, +}; + +static struct v4l2_subdev_sensor_ops mt9m001_subdev_sensor_ops = { + .g_skip_top_lines = mt9m001_g_skip_top_lines, +}; + +static struct v4l2_subdev_ops mt9m001_subdev_ops = { + .core = &mt9m001_subdev_core_ops, + .video = &mt9m001_subdev_video_ops, + .sensor = &mt9m001_subdev_sensor_ops, +}; + +static int mt9m001_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9m001 *mt9m001; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + if (!icl) { + dev_err(&client->dev, "MT9M001 driver needs platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9m001 = kzalloc(sizeof(struct mt9m001), GFP_KERNEL); + if (!mt9m001) + return -ENOMEM; + + v4l2_i2c_subdev_init(&mt9m001->subdev, client, &mt9m001_subdev_ops); + v4l2_ctrl_handler_init(&mt9m001->hdl, 4); + v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops, + V4L2_CID_GAIN, 0, 127, 1, 64); + mt9m001->exposure = v4l2_ctrl_new_std(&mt9m001->hdl, &mt9m001_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 255, 1, 255); + /* + * Simulated autoexposure. If enabled, we calculate shutter width + * ourselves in the driver based on vertical blanking and frame width + */ + mt9m001->autoexposure = v4l2_ctrl_new_std_menu(&mt9m001->hdl, + &mt9m001_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0, + V4L2_EXPOSURE_AUTO); + mt9m001->subdev.ctrl_handler = &mt9m001->hdl; + if (mt9m001->hdl.error) { + int err = mt9m001->hdl.error; + + kfree(mt9m001); + return err; + } + v4l2_ctrl_auto_cluster(2, &mt9m001->autoexposure, + V4L2_EXPOSURE_MANUAL, true); + + /* Second stage probe - when a capture adapter is there */ + mt9m001->y_skip_top = 0; + mt9m001->rect.left = MT9M001_COLUMN_SKIP; + mt9m001->rect.top = MT9M001_ROW_SKIP; + mt9m001->rect.width = MT9M001_MAX_WIDTH; + mt9m001->rect.height = MT9M001_MAX_HEIGHT; + + ret = mt9m001_video_probe(icl, client); + if (ret) { + v4l2_ctrl_handler_free(&mt9m001->hdl); + kfree(mt9m001); + } + + return ret; +} + +static int mt9m001_remove(struct i2c_client *client) +{ + struct mt9m001 *mt9m001 = to_mt9m001(client); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + v4l2_device_unregister_subdev(&mt9m001->subdev); + v4l2_ctrl_handler_free(&mt9m001->hdl); + mt9m001_video_remove(icl); + kfree(mt9m001); + + return 0; +} + +static const struct i2c_device_id mt9m001_id[] = { + { "mt9m001", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9m001_id); + +static struct i2c_driver mt9m001_i2c_driver = { + .driver = { + .name = "mt9m001", + }, + .probe = mt9m001_probe, + .remove = mt9m001_remove, + .id_table = mt9m001_id, +}; + +module_i2c_driver(mt9m001_i2c_driver); + +MODULE_DESCRIPTION("Micron MT9M001 Camera driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/soc_camera/mt9m111.c b/drivers/media/i2c/soc_camera/mt9m111.c new file mode 100644 index 000000000000..62fd94af599b --- /dev/null +++ b/drivers/media/i2c/soc_camera/mt9m111.c @@ -0,0 +1,1046 @@ +/* + * Driver for MT9M111/MT9M112/MT9M131 CMOS Image Sensor from Micron/Aptina + * + * Copyright (C) 2008, Robert Jarzmik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * MT9M111, MT9M112 and MT9M131: + * i2c address is 0x48 or 0x5d (depending on SADDR pin) + * The platform has to define i2c_board_info and call i2c_register_board_info() + */ + +/* + * Sensor core register addresses (0x000..0x0ff) + */ +#define MT9M111_CHIP_VERSION 0x000 +#define MT9M111_ROW_START 0x001 +#define MT9M111_COLUMN_START 0x002 +#define MT9M111_WINDOW_HEIGHT 0x003 +#define MT9M111_WINDOW_WIDTH 0x004 +#define MT9M111_HORIZONTAL_BLANKING_B 0x005 +#define MT9M111_VERTICAL_BLANKING_B 0x006 +#define MT9M111_HORIZONTAL_BLANKING_A 0x007 +#define MT9M111_VERTICAL_BLANKING_A 0x008 +#define MT9M111_SHUTTER_WIDTH 0x009 +#define MT9M111_ROW_SPEED 0x00a +#define MT9M111_EXTRA_DELAY 0x00b +#define MT9M111_SHUTTER_DELAY 0x00c +#define MT9M111_RESET 0x00d +#define MT9M111_READ_MODE_B 0x020 +#define MT9M111_READ_MODE_A 0x021 +#define MT9M111_FLASH_CONTROL 0x023 +#define MT9M111_GREEN1_GAIN 0x02b +#define MT9M111_BLUE_GAIN 0x02c +#define MT9M111_RED_GAIN 0x02d +#define MT9M111_GREEN2_GAIN 0x02e +#define MT9M111_GLOBAL_GAIN 0x02f +#define MT9M111_CONTEXT_CONTROL 0x0c8 +#define MT9M111_PAGE_MAP 0x0f0 +#define MT9M111_BYTE_WISE_ADDR 0x0f1 + +#define MT9M111_RESET_SYNC_CHANGES (1 << 15) +#define MT9M111_RESET_RESTART_BAD_FRAME (1 << 9) +#define MT9M111_RESET_SHOW_BAD_FRAMES (1 << 8) +#define MT9M111_RESET_RESET_SOC (1 << 5) +#define MT9M111_RESET_OUTPUT_DISABLE (1 << 4) +#define MT9M111_RESET_CHIP_ENABLE (1 << 3) +#define MT9M111_RESET_ANALOG_STANDBY (1 << 2) +#define MT9M111_RESET_RESTART_FRAME (1 << 1) +#define MT9M111_RESET_RESET_MODE (1 << 0) + +#define MT9M111_RM_FULL_POWER_RD (0 << 10) +#define MT9M111_RM_LOW_POWER_RD (1 << 10) +#define MT9M111_RM_COL_SKIP_4X (1 << 5) +#define MT9M111_RM_ROW_SKIP_4X (1 << 4) +#define MT9M111_RM_COL_SKIP_2X (1 << 3) +#define MT9M111_RM_ROW_SKIP_2X (1 << 2) +#define MT9M111_RMB_MIRROR_COLS (1 << 1) +#define MT9M111_RMB_MIRROR_ROWS (1 << 0) +#define MT9M111_CTXT_CTRL_RESTART (1 << 15) +#define MT9M111_CTXT_CTRL_DEFECTCOR_B (1 << 12) +#define MT9M111_CTXT_CTRL_RESIZE_B (1 << 10) +#define MT9M111_CTXT_CTRL_CTRL2_B (1 << 9) +#define MT9M111_CTXT_CTRL_GAMMA_B (1 << 8) +#define MT9M111_CTXT_CTRL_XENON_EN (1 << 7) +#define MT9M111_CTXT_CTRL_READ_MODE_B (1 << 3) +#define MT9M111_CTXT_CTRL_LED_FLASH_EN (1 << 2) +#define MT9M111_CTXT_CTRL_VBLANK_SEL_B (1 << 1) +#define MT9M111_CTXT_CTRL_HBLANK_SEL_B (1 << 0) + +/* + * Colorpipe register addresses (0x100..0x1ff) + */ +#define MT9M111_OPER_MODE_CTRL 0x106 +#define MT9M111_OUTPUT_FORMAT_CTRL 0x108 +#define MT9M111_REDUCER_XZOOM_B 0x1a0 +#define MT9M111_REDUCER_XSIZE_B 0x1a1 +#define MT9M111_REDUCER_YZOOM_B 0x1a3 +#define MT9M111_REDUCER_YSIZE_B 0x1a4 +#define MT9M111_REDUCER_XZOOM_A 0x1a6 +#define MT9M111_REDUCER_XSIZE_A 0x1a7 +#define MT9M111_REDUCER_YZOOM_A 0x1a9 +#define MT9M111_REDUCER_YSIZE_A 0x1aa + +#define MT9M111_OUTPUT_FORMAT_CTRL2_A 0x13a +#define MT9M111_OUTPUT_FORMAT_CTRL2_B 0x19b + +#define MT9M111_OPMODE_AUTOEXPO_EN (1 << 14) +#define MT9M111_OPMODE_AUTOWHITEBAL_EN (1 << 1) +#define MT9M111_OUTFMT_FLIP_BAYER_COL (1 << 9) +#define MT9M111_OUTFMT_FLIP_BAYER_ROW (1 << 8) +#define MT9M111_OUTFMT_PROCESSED_BAYER (1 << 14) +#define MT9M111_OUTFMT_BYPASS_IFP (1 << 10) +#define MT9M111_OUTFMT_INV_PIX_CLOCK (1 << 9) +#define MT9M111_OUTFMT_RGB (1 << 8) +#define MT9M111_OUTFMT_RGB565 (0 << 6) +#define MT9M111_OUTFMT_RGB555 (1 << 6) +#define MT9M111_OUTFMT_RGB444x (2 << 6) +#define MT9M111_OUTFMT_RGBx444 (3 << 6) +#define MT9M111_OUTFMT_TST_RAMP_OFF (0 << 4) +#define MT9M111_OUTFMT_TST_RAMP_COL (1 << 4) +#define MT9M111_OUTFMT_TST_RAMP_ROW (2 << 4) +#define MT9M111_OUTFMT_TST_RAMP_FRAME (3 << 4) +#define MT9M111_OUTFMT_SHIFT_3_UP (1 << 3) +#define MT9M111_OUTFMT_AVG_CHROMA (1 << 2) +#define MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN (1 << 1) +#define MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B (1 << 0) + +/* + * Camera control register addresses (0x200..0x2ff not implemented) + */ + +#define reg_read(reg) mt9m111_reg_read(client, MT9M111_##reg) +#define reg_write(reg, val) mt9m111_reg_write(client, MT9M111_##reg, (val)) +#define reg_set(reg, val) mt9m111_reg_set(client, MT9M111_##reg, (val)) +#define reg_clear(reg, val) mt9m111_reg_clear(client, MT9M111_##reg, (val)) +#define reg_mask(reg, val, mask) mt9m111_reg_mask(client, MT9M111_##reg, \ + (val), (mask)) + +#define MT9M111_MIN_DARK_ROWS 8 +#define MT9M111_MIN_DARK_COLS 26 +#define MT9M111_MAX_HEIGHT 1024 +#define MT9M111_MAX_WIDTH 1280 + +struct mt9m111_context { + u16 read_mode; + u16 blanking_h; + u16 blanking_v; + u16 reducer_xzoom; + u16 reducer_yzoom; + u16 reducer_xsize; + u16 reducer_ysize; + u16 output_fmt_ctrl2; + u16 control; +}; + +static struct mt9m111_context context_a = { + .read_mode = MT9M111_READ_MODE_A, + .blanking_h = MT9M111_HORIZONTAL_BLANKING_A, + .blanking_v = MT9M111_VERTICAL_BLANKING_A, + .reducer_xzoom = MT9M111_REDUCER_XZOOM_A, + .reducer_yzoom = MT9M111_REDUCER_YZOOM_A, + .reducer_xsize = MT9M111_REDUCER_XSIZE_A, + .reducer_ysize = MT9M111_REDUCER_YSIZE_A, + .output_fmt_ctrl2 = MT9M111_OUTPUT_FORMAT_CTRL2_A, + .control = MT9M111_CTXT_CTRL_RESTART, +}; + +static struct mt9m111_context context_b = { + .read_mode = MT9M111_READ_MODE_B, + .blanking_h = MT9M111_HORIZONTAL_BLANKING_B, + .blanking_v = MT9M111_VERTICAL_BLANKING_B, + .reducer_xzoom = MT9M111_REDUCER_XZOOM_B, + .reducer_yzoom = MT9M111_REDUCER_YZOOM_B, + .reducer_xsize = MT9M111_REDUCER_XSIZE_B, + .reducer_ysize = MT9M111_REDUCER_YSIZE_B, + .output_fmt_ctrl2 = MT9M111_OUTPUT_FORMAT_CTRL2_B, + .control = MT9M111_CTXT_CTRL_RESTART | + MT9M111_CTXT_CTRL_DEFECTCOR_B | MT9M111_CTXT_CTRL_RESIZE_B | + MT9M111_CTXT_CTRL_CTRL2_B | MT9M111_CTXT_CTRL_GAMMA_B | + MT9M111_CTXT_CTRL_READ_MODE_B | MT9M111_CTXT_CTRL_VBLANK_SEL_B | + MT9M111_CTXT_CTRL_HBLANK_SEL_B, +}; + +/* MT9M111 has only one fixed colorspace per pixelcode */ +struct mt9m111_datafmt { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; +}; + +static const struct mt9m111_datafmt mt9m111_colour_fmts[] = { + {V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG}, + {V4L2_MBUS_FMT_YVYU8_2X8, V4L2_COLORSPACE_JPEG}, + {V4L2_MBUS_FMT_UYVY8_2X8, V4L2_COLORSPACE_JPEG}, + {V4L2_MBUS_FMT_VYUY8_2X8, V4L2_COLORSPACE_JPEG}, + {V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_RGB565_2X8_LE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_BGR565_2X8_LE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_BGR565_2X8_BE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE, V4L2_COLORSPACE_SRGB}, +}; + +struct mt9m111 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *gain; + int model; /* V4L2_IDENT_MT9M111 or V4L2_IDENT_MT9M112 code + * from v4l2-chip-ident.h */ + struct mt9m111_context *ctx; + struct v4l2_rect rect; /* cropping rectangle */ + int width; /* output */ + int height; /* sizes */ + struct mutex power_lock; /* lock to protect power_count */ + int power_count; + const struct mt9m111_datafmt *fmt; + int lastpage; /* PageMap cache value */ +}; + +/* Find a data format by a pixel code */ +static const struct mt9m111_datafmt *mt9m111_find_datafmt(struct mt9m111 *mt9m111, + enum v4l2_mbus_pixelcode code) +{ + int i; + for (i = 0; i < ARRAY_SIZE(mt9m111_colour_fmts); i++) + if (mt9m111_colour_fmts[i].code == code) + return mt9m111_colour_fmts + i; + + return mt9m111->fmt; +} + +static struct mt9m111 *to_mt9m111(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct mt9m111, subdev); +} + +static int reg_page_map_set(struct i2c_client *client, const u16 reg) +{ + int ret; + u16 page; + struct mt9m111 *mt9m111 = to_mt9m111(client); + + page = (reg >> 8); + if (page == mt9m111->lastpage) + return 0; + if (page > 2) + return -EINVAL; + + ret = i2c_smbus_write_word_swapped(client, MT9M111_PAGE_MAP, page); + if (!ret) + mt9m111->lastpage = page; + return ret; +} + +static int mt9m111_reg_read(struct i2c_client *client, const u16 reg) +{ + int ret; + + ret = reg_page_map_set(client, reg); + if (!ret) + ret = i2c_smbus_read_word_swapped(client, reg & 0xff); + + dev_dbg(&client->dev, "read reg.%03x -> %04x\n", reg, ret); + return ret; +} + +static int mt9m111_reg_write(struct i2c_client *client, const u16 reg, + const u16 data) +{ + int ret; + + ret = reg_page_map_set(client, reg); + if (!ret) + ret = i2c_smbus_write_word_swapped(client, reg & 0xff, data); + dev_dbg(&client->dev, "write reg.%03x = %04x -> %d\n", reg, data, ret); + return ret; +} + +static int mt9m111_reg_set(struct i2c_client *client, const u16 reg, + const u16 data) +{ + int ret; + + ret = mt9m111_reg_read(client, reg); + if (ret >= 0) + ret = mt9m111_reg_write(client, reg, ret | data); + return ret; +} + +static int mt9m111_reg_clear(struct i2c_client *client, const u16 reg, + const u16 data) +{ + int ret; + + ret = mt9m111_reg_read(client, reg); + if (ret >= 0) + ret = mt9m111_reg_write(client, reg, ret & ~data); + return ret; +} + +static int mt9m111_reg_mask(struct i2c_client *client, const u16 reg, + const u16 data, const u16 mask) +{ + int ret; + + ret = mt9m111_reg_read(client, reg); + if (ret >= 0) + ret = mt9m111_reg_write(client, reg, (ret & ~mask) | data); + return ret; +} + +static int mt9m111_set_context(struct mt9m111 *mt9m111, + struct mt9m111_context *ctx) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + return reg_write(CONTEXT_CONTROL, ctx->control); +} + +static int mt9m111_setup_rect_ctx(struct mt9m111 *mt9m111, + struct mt9m111_context *ctx, struct v4l2_rect *rect, + unsigned int width, unsigned int height) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret = mt9m111_reg_write(client, ctx->reducer_xzoom, rect->width); + if (!ret) + ret = mt9m111_reg_write(client, ctx->reducer_yzoom, rect->height); + if (!ret) + ret = mt9m111_reg_write(client, ctx->reducer_xsize, width); + if (!ret) + ret = mt9m111_reg_write(client, ctx->reducer_ysize, height); + return ret; +} + +static int mt9m111_setup_geometry(struct mt9m111 *mt9m111, struct v4l2_rect *rect, + int width, int height, enum v4l2_mbus_pixelcode code) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + ret = reg_write(COLUMN_START, rect->left); + if (!ret) + ret = reg_write(ROW_START, rect->top); + + if (!ret) + ret = reg_write(WINDOW_WIDTH, rect->width); + if (!ret) + ret = reg_write(WINDOW_HEIGHT, rect->height); + + if (code != V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE) { + /* IFP in use, down-scaling possible */ + if (!ret) + ret = mt9m111_setup_rect_ctx(mt9m111, &context_b, + rect, width, height); + if (!ret) + ret = mt9m111_setup_rect_ctx(mt9m111, &context_a, + rect, width, height); + } + + dev_dbg(&client->dev, "%s(%x): %ux%u@%u:%u -> %ux%u = %d\n", + __func__, code, rect->width, rect->height, rect->left, rect->top, + width, height, ret); + + return ret; +} + +static int mt9m111_enable(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + return reg_write(RESET, MT9M111_RESET_CHIP_ENABLE); +} + +static int mt9m111_reset(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + ret = reg_set(RESET, MT9M111_RESET_RESET_MODE); + if (!ret) + ret = reg_set(RESET, MT9M111_RESET_RESET_SOC); + if (!ret) + ret = reg_clear(RESET, MT9M111_RESET_RESET_MODE + | MT9M111_RESET_RESET_SOC); + + return ret; +} + +static int mt9m111_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) +{ + struct v4l2_rect rect = a->c; + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + int width, height; + int ret; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (mt9m111->fmt->code == V4L2_MBUS_FMT_SBGGR8_1X8 || + mt9m111->fmt->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE) { + /* Bayer format - even size lengths */ + rect.width = ALIGN(rect.width, 2); + rect.height = ALIGN(rect.height, 2); + /* Let the user play with the starting pixel */ + } + + /* FIXME: the datasheet doesn't specify minimum sizes */ + soc_camera_limit_side(&rect.left, &rect.width, + MT9M111_MIN_DARK_COLS, 2, MT9M111_MAX_WIDTH); + + soc_camera_limit_side(&rect.top, &rect.height, + MT9M111_MIN_DARK_ROWS, 2, MT9M111_MAX_HEIGHT); + + width = min(mt9m111->width, rect.width); + height = min(mt9m111->height, rect.height); + + ret = mt9m111_setup_geometry(mt9m111, &rect, width, height, mt9m111->fmt->code); + if (!ret) { + mt9m111->rect = rect; + mt9m111->width = width; + mt9m111->height = height; + } + + return ret; +} + +static int mt9m111_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + + a->c = mt9m111->rect; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int mt9m111_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + a->bounds.left = MT9M111_MIN_DARK_COLS; + a->bounds.top = MT9M111_MIN_DARK_ROWS; + a->bounds.width = MT9M111_MAX_WIDTH; + a->bounds.height = MT9M111_MAX_HEIGHT; + a->defrect = a->bounds; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int mt9m111_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + + mf->width = mt9m111->width; + mf->height = mt9m111->height; + mf->code = mt9m111->fmt->code; + mf->colorspace = mt9m111->fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mt9m111_set_pixfmt(struct mt9m111 *mt9m111, + enum v4l2_mbus_pixelcode code) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + u16 data_outfmt2, mask_outfmt2 = MT9M111_OUTFMT_PROCESSED_BAYER | + MT9M111_OUTFMT_BYPASS_IFP | MT9M111_OUTFMT_RGB | + MT9M111_OUTFMT_RGB565 | MT9M111_OUTFMT_RGB555 | + MT9M111_OUTFMT_RGB444x | MT9M111_OUTFMT_RGBx444 | + MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN | + MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + int ret; + + switch (code) { + case V4L2_MBUS_FMT_SBGGR8_1X8: + data_outfmt2 = MT9M111_OUTFMT_PROCESSED_BAYER | + MT9M111_OUTFMT_RGB; + break; + case V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE: + data_outfmt2 = MT9M111_OUTFMT_BYPASS_IFP | MT9M111_OUTFMT_RGB; + break; + case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB555 | + MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN; + break; + case V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB555; + break; + case V4L2_MBUS_FMT_RGB565_2X8_LE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB565 | + MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN; + break; + case V4L2_MBUS_FMT_RGB565_2X8_BE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB565; + break; + case V4L2_MBUS_FMT_BGR565_2X8_BE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB565 | + MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + break; + case V4L2_MBUS_FMT_BGR565_2X8_LE: + data_outfmt2 = MT9M111_OUTFMT_RGB | MT9M111_OUTFMT_RGB565 | + MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN | + MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + break; + case V4L2_MBUS_FMT_UYVY8_2X8: + data_outfmt2 = 0; + break; + case V4L2_MBUS_FMT_VYUY8_2X8: + data_outfmt2 = MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + break; + case V4L2_MBUS_FMT_YUYV8_2X8: + data_outfmt2 = MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN; + break; + case V4L2_MBUS_FMT_YVYU8_2X8: + data_outfmt2 = MT9M111_OUTFMT_SWAP_YCbCr_C_Y_RGB_EVEN | + MT9M111_OUTFMT_SWAP_YCbCr_Cb_Cr_RGB_R_B; + break; + default: + dev_err(&client->dev, "Pixel format not handled: %x\n", code); + return -EINVAL; + } + + ret = mt9m111_reg_mask(client, context_a.output_fmt_ctrl2, + data_outfmt2, mask_outfmt2); + if (!ret) + ret = mt9m111_reg_mask(client, context_b.output_fmt_ctrl2, + data_outfmt2, mask_outfmt2); + + return ret; +} + +static int mt9m111_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + const struct mt9m111_datafmt *fmt; + struct v4l2_rect *rect = &mt9m111->rect; + bool bayer; + + fmt = mt9m111_find_datafmt(mt9m111, mf->code); + + bayer = fmt->code == V4L2_MBUS_FMT_SBGGR8_1X8 || + fmt->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE; + + /* + * With Bayer format enforce even side lengths, but let the user play + * with the starting pixel + */ + if (bayer) { + rect->width = ALIGN(rect->width, 2); + rect->height = ALIGN(rect->height, 2); + } + + if (fmt->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE) { + /* IFP bypass mode, no scaling */ + mf->width = rect->width; + mf->height = rect->height; + } else { + /* No upscaling */ + if (mf->width > rect->width) + mf->width = rect->width; + if (mf->height > rect->height) + mf->height = rect->height; + } + + dev_dbg(&client->dev, "%s(): %ux%u, code=%x\n", __func__, + mf->width, mf->height, fmt->code); + + mf->code = fmt->code; + mf->colorspace = fmt->colorspace; + + return 0; +} + +static int mt9m111_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + const struct mt9m111_datafmt *fmt; + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + struct v4l2_rect *rect = &mt9m111->rect; + int ret; + + mt9m111_try_fmt(sd, mf); + fmt = mt9m111_find_datafmt(mt9m111, mf->code); + /* try_fmt() guarantees fmt != NULL && fmt->code == mf->code */ + + ret = mt9m111_setup_geometry(mt9m111, rect, mf->width, mf->height, mf->code); + if (!ret) + ret = mt9m111_set_pixfmt(mt9m111, mf->code); + if (!ret) { + mt9m111->width = mf->width; + mt9m111->height = mf->height; + mt9m111->fmt = fmt; + } + + return ret; +} + +static int mt9m111_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + + if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + if (id->match.addr != client->addr) + return -ENODEV; + + id->ident = mt9m111->model; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9m111_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int val; + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0x2ff) + return -EINVAL; + if (reg->match.addr != client->addr) + return -ENODEV; + + val = mt9m111_reg_read(client, reg->reg); + reg->size = 2; + reg->val = (u64)val; + + if (reg->val > 0xffff) + return -EIO; + + return 0; +} + +static int mt9m111_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0x2ff) + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + if (mt9m111_reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int mt9m111_set_flip(struct mt9m111 *mt9m111, int flip, int mask) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + if (flip) + ret = mt9m111_reg_set(client, mt9m111->ctx->read_mode, mask); + else + ret = mt9m111_reg_clear(client, mt9m111->ctx->read_mode, mask); + + return ret; +} + +static int mt9m111_get_global_gain(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int data; + + data = reg_read(GLOBAL_GAIN); + if (data >= 0) + return (data & 0x2f) * (1 << ((data >> 10) & 1)) * + (1 << ((data >> 9) & 1)); + return data; +} + +static int mt9m111_set_global_gain(struct mt9m111 *mt9m111, int gain) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + u16 val; + + if (gain > 63 * 2 * 2) + return -EINVAL; + + if ((gain >= 64 * 2) && (gain < 63 * 2 * 2)) + val = (1 << 10) | (1 << 9) | (gain / 4); + else if ((gain >= 64) && (gain < 64 * 2)) + val = (1 << 9) | (gain / 2); + else + val = gain; + + return reg_write(GLOBAL_GAIN, val); +} + +static int mt9m111_set_autoexposure(struct mt9m111 *mt9m111, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + + if (on) + return reg_set(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOEXPO_EN); + return reg_clear(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOEXPO_EN); +} + +static int mt9m111_set_autowhitebalance(struct mt9m111 *mt9m111, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + + if (on) + return reg_set(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOWHITEBAL_EN); + return reg_clear(OPER_MODE_CTRL, MT9M111_OPMODE_AUTOWHITEBAL_EN); +} + +static int mt9m111_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m111 *mt9m111 = container_of(ctrl->handler, + struct mt9m111, hdl); + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + return mt9m111_set_flip(mt9m111, ctrl->val, + MT9M111_RMB_MIRROR_ROWS); + case V4L2_CID_HFLIP: + return mt9m111_set_flip(mt9m111, ctrl->val, + MT9M111_RMB_MIRROR_COLS); + case V4L2_CID_GAIN: + return mt9m111_set_global_gain(mt9m111, ctrl->val); + case V4L2_CID_EXPOSURE_AUTO: + return mt9m111_set_autoexposure(mt9m111, ctrl->val); + case V4L2_CID_AUTO_WHITE_BALANCE: + return mt9m111_set_autowhitebalance(mt9m111, ctrl->val); + } + + return -EINVAL; +} + +static int mt9m111_suspend(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + v4l2_ctrl_s_ctrl(mt9m111->gain, mt9m111_get_global_gain(mt9m111)); + + ret = reg_set(RESET, MT9M111_RESET_RESET_MODE); + if (!ret) + ret = reg_set(RESET, MT9M111_RESET_RESET_SOC | + MT9M111_RESET_OUTPUT_DISABLE | + MT9M111_RESET_ANALOG_STANDBY); + if (!ret) + ret = reg_clear(RESET, MT9M111_RESET_CHIP_ENABLE); + + return ret; +} + +static void mt9m111_restore_state(struct mt9m111 *mt9m111) +{ + mt9m111_set_context(mt9m111, mt9m111->ctx); + mt9m111_set_pixfmt(mt9m111, mt9m111->fmt->code); + mt9m111_setup_geometry(mt9m111, &mt9m111->rect, + mt9m111->width, mt9m111->height, mt9m111->fmt->code); + v4l2_ctrl_handler_setup(&mt9m111->hdl); +} + +static int mt9m111_resume(struct mt9m111 *mt9m111) +{ + int ret = mt9m111_enable(mt9m111); + if (!ret) + ret = mt9m111_reset(mt9m111); + if (!ret) + mt9m111_restore_state(mt9m111); + + return ret; +} + +static int mt9m111_init(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + int ret; + + /* Default HIGHPOWER context */ + mt9m111->ctx = &context_b; + ret = mt9m111_enable(mt9m111); + if (!ret) + ret = mt9m111_reset(mt9m111); + if (!ret) + ret = mt9m111_set_context(mt9m111, mt9m111->ctx); + if (ret) + dev_err(&client->dev, "mt9m111 init failed: %d\n", ret); + return ret; +} + +static int mt9m111_power_on(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + ret = soc_camera_power_on(&client->dev, icl); + if (ret < 0) + return ret; + + ret = mt9m111_resume(mt9m111); + if (ret < 0) { + dev_err(&client->dev, "Failed to resume the sensor: %d\n", ret); + soc_camera_power_off(&client->dev, icl); + } + + return ret; +} + +static void mt9m111_power_off(struct mt9m111 *mt9m111) +{ + struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + mt9m111_suspend(mt9m111); + soc_camera_power_off(&client->dev, icl); +} + +static int mt9m111_s_power(struct v4l2_subdev *sd, int on) +{ + struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev); + int ret = 0; + + mutex_lock(&mt9m111->power_lock); + + /* + * If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (mt9m111->power_count == !on) { + if (on) + ret = mt9m111_power_on(mt9m111); + else + mt9m111_power_off(mt9m111); + } + + if (!ret) { + /* Update the power count. */ + mt9m111->power_count += on ? 1 : -1; + WARN_ON(mt9m111->power_count < 0); + } + + mutex_unlock(&mt9m111->power_lock); + return ret; +} + +static const struct v4l2_ctrl_ops mt9m111_ctrl_ops = { + .s_ctrl = mt9m111_s_ctrl, +}; + +static struct v4l2_subdev_core_ops mt9m111_subdev_core_ops = { + .g_chip_ident = mt9m111_g_chip_ident, + .s_power = mt9m111_s_power, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9m111_g_register, + .s_register = mt9m111_s_register, +#endif +}; + +static int mt9m111_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(mt9m111_colour_fmts)) + return -EINVAL; + + *code = mt9m111_colour_fmts[index].code; + return 0; +} + +static int mt9m111_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_PCLK_SAMPLE_RISING | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static struct v4l2_subdev_video_ops mt9m111_subdev_video_ops = { + .s_mbus_fmt = mt9m111_s_fmt, + .g_mbus_fmt = mt9m111_g_fmt, + .try_mbus_fmt = mt9m111_try_fmt, + .s_crop = mt9m111_s_crop, + .g_crop = mt9m111_g_crop, + .cropcap = mt9m111_cropcap, + .enum_mbus_fmt = mt9m111_enum_fmt, + .g_mbus_config = mt9m111_g_mbus_config, +}; + +static struct v4l2_subdev_ops mt9m111_subdev_ops = { + .core = &mt9m111_subdev_core_ops, + .video = &mt9m111_subdev_video_ops, +}; + +/* + * Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one + */ +static int mt9m111_video_probe(struct i2c_client *client) +{ + struct mt9m111 *mt9m111 = to_mt9m111(client); + s32 data; + int ret; + + ret = mt9m111_s_power(&mt9m111->subdev, 1); + if (ret < 0) + return ret; + + data = reg_read(CHIP_VERSION); + + switch (data) { + case 0x143a: /* MT9M111 or MT9M131 */ + mt9m111->model = V4L2_IDENT_MT9M111; + dev_info(&client->dev, + "Detected a MT9M111/MT9M131 chip ID %x\n", data); + break; + case 0x148c: /* MT9M112 */ + mt9m111->model = V4L2_IDENT_MT9M112; + dev_info(&client->dev, "Detected a MT9M112 chip ID %x\n", data); + break; + default: + dev_err(&client->dev, + "No MT9M111/MT9M112/MT9M131 chip detected register read %x\n", + data); + ret = -ENODEV; + goto done; + } + + ret = mt9m111_init(mt9m111); + if (ret) + goto done; + + ret = v4l2_ctrl_handler_setup(&mt9m111->hdl); + +done: + mt9m111_s_power(&mt9m111->subdev, 0); + return ret; +} + +static int mt9m111_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9m111 *mt9m111; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + if (!icl) { + dev_err(&client->dev, "mt9m111: driver needs platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9m111 = kzalloc(sizeof(struct mt9m111), GFP_KERNEL); + if (!mt9m111) + return -ENOMEM; + + v4l2_i2c_subdev_init(&mt9m111->subdev, client, &mt9m111_subdev_ops); + v4l2_ctrl_handler_init(&mt9m111->hdl, 5); + v4l2_ctrl_new_std(&mt9m111->hdl, &mt9m111_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9m111->hdl, &mt9m111_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9m111->hdl, &mt9m111_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + mt9m111->gain = v4l2_ctrl_new_std(&mt9m111->hdl, &mt9m111_ctrl_ops, + V4L2_CID_GAIN, 0, 63 * 2 * 2, 1, 32); + v4l2_ctrl_new_std_menu(&mt9m111->hdl, + &mt9m111_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0, + V4L2_EXPOSURE_AUTO); + mt9m111->subdev.ctrl_handler = &mt9m111->hdl; + if (mt9m111->hdl.error) { + int err = mt9m111->hdl.error; + + kfree(mt9m111); + return err; + } + + /* Second stage probe - when a capture adapter is there */ + mt9m111->rect.left = MT9M111_MIN_DARK_COLS; + mt9m111->rect.top = MT9M111_MIN_DARK_ROWS; + mt9m111->rect.width = MT9M111_MAX_WIDTH; + mt9m111->rect.height = MT9M111_MAX_HEIGHT; + mt9m111->fmt = &mt9m111_colour_fmts[0]; + mt9m111->lastpage = -1; + mutex_init(&mt9m111->power_lock); + + ret = mt9m111_video_probe(client); + if (ret) { + v4l2_ctrl_handler_free(&mt9m111->hdl); + kfree(mt9m111); + } + + return ret; +} + +static int mt9m111_remove(struct i2c_client *client) +{ + struct mt9m111 *mt9m111 = to_mt9m111(client); + + v4l2_device_unregister_subdev(&mt9m111->subdev); + v4l2_ctrl_handler_free(&mt9m111->hdl); + kfree(mt9m111); + + return 0; +} + +static const struct i2c_device_id mt9m111_id[] = { + { "mt9m111", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9m111_id); + +static struct i2c_driver mt9m111_i2c_driver = { + .driver = { + .name = "mt9m111", + }, + .probe = mt9m111_probe, + .remove = mt9m111_remove, + .id_table = mt9m111_id, +}; + +module_i2c_driver(mt9m111_i2c_driver); + +MODULE_DESCRIPTION("Micron/Aptina MT9M111/MT9M112/MT9M131 Camera driver"); +MODULE_AUTHOR("Robert Jarzmik"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/soc_camera/mt9t031.c b/drivers/media/i2c/soc_camera/mt9t031.c new file mode 100644 index 000000000000..40800b10a080 --- /dev/null +++ b/drivers/media/i2c/soc_camera/mt9t031.c @@ -0,0 +1,857 @@ +/* + * Driver for MT9T031 CMOS Image Sensor from Micron + * + * Copyright (C) 2008, Guennadi Liakhovetski, DENX Software Engineering + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * ATTENTION: this driver still cannot be used outside of the soc-camera + * framework because of its PM implementation, using the video_device node. + * If hardware becomes available for testing, alternative PM approaches shall + * be considered and tested. + */ + +/* + * mt9t031 i2c address 0x5d + * The platform has to define i2c_board_info and link to it from + * struct soc_camera_link + */ + +/* mt9t031 selected register addresses */ +#define MT9T031_CHIP_VERSION 0x00 +#define MT9T031_ROW_START 0x01 +#define MT9T031_COLUMN_START 0x02 +#define MT9T031_WINDOW_HEIGHT 0x03 +#define MT9T031_WINDOW_WIDTH 0x04 +#define MT9T031_HORIZONTAL_BLANKING 0x05 +#define MT9T031_VERTICAL_BLANKING 0x06 +#define MT9T031_OUTPUT_CONTROL 0x07 +#define MT9T031_SHUTTER_WIDTH_UPPER 0x08 +#define MT9T031_SHUTTER_WIDTH 0x09 +#define MT9T031_PIXEL_CLOCK_CONTROL 0x0a +#define MT9T031_FRAME_RESTART 0x0b +#define MT9T031_SHUTTER_DELAY 0x0c +#define MT9T031_RESET 0x0d +#define MT9T031_READ_MODE_1 0x1e +#define MT9T031_READ_MODE_2 0x20 +#define MT9T031_READ_MODE_3 0x21 +#define MT9T031_ROW_ADDRESS_MODE 0x22 +#define MT9T031_COLUMN_ADDRESS_MODE 0x23 +#define MT9T031_GLOBAL_GAIN 0x35 +#define MT9T031_CHIP_ENABLE 0xF8 + +#define MT9T031_MAX_HEIGHT 1536 +#define MT9T031_MAX_WIDTH 2048 +#define MT9T031_MIN_HEIGHT 2 +#define MT9T031_MIN_WIDTH 18 +#define MT9T031_HORIZONTAL_BLANK 142 +#define MT9T031_VERTICAL_BLANK 25 +#define MT9T031_COLUMN_SKIP 32 +#define MT9T031_ROW_SKIP 20 + +struct mt9t031 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct { + /* exposure/auto-exposure cluster */ + struct v4l2_ctrl *autoexposure; + struct v4l2_ctrl *exposure; + }; + struct v4l2_rect rect; /* Sensor window */ + int model; /* V4L2_IDENT_MT9T031* codes from v4l2-chip-ident.h */ + u16 xskip; + u16 yskip; + unsigned int total_h; + unsigned short y_skip_top; /* Lines to skip at the top */ +}; + +static struct mt9t031 *to_mt9t031(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct mt9t031, subdev); +} + +static int reg_read(struct i2c_client *client, const u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int reg_write(struct i2c_client *client, const u8 reg, + const u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static int reg_set(struct i2c_client *client, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, ret | data); +} + +static int reg_clear(struct i2c_client *client, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, ret & ~data); +} + +static int set_shutter(struct i2c_client *client, const u32 data) +{ + int ret; + + ret = reg_write(client, MT9T031_SHUTTER_WIDTH_UPPER, data >> 16); + + if (ret >= 0) + ret = reg_write(client, MT9T031_SHUTTER_WIDTH, data & 0xffff); + + return ret; +} + +static int get_shutter(struct i2c_client *client, u32 *data) +{ + int ret; + + ret = reg_read(client, MT9T031_SHUTTER_WIDTH_UPPER); + *data = ret << 16; + + if (ret >= 0) + ret = reg_read(client, MT9T031_SHUTTER_WIDTH); + *data |= ret & 0xffff; + + return ret < 0 ? ret : 0; +} + +static int mt9t031_idle(struct i2c_client *client) +{ + int ret; + + /* Disable chip output, synchronous option update */ + ret = reg_write(client, MT9T031_RESET, 1); + if (ret >= 0) + ret = reg_write(client, MT9T031_RESET, 0); + if (ret >= 0) + ret = reg_clear(client, MT9T031_OUTPUT_CONTROL, 2); + + return ret >= 0 ? 0 : -EIO; +} + +static int mt9t031_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + if (enable) + /* Switch to master "normal" mode */ + ret = reg_set(client, MT9T031_OUTPUT_CONTROL, 2); + else + /* Stop sensor readout */ + ret = reg_clear(client, MT9T031_OUTPUT_CONTROL, 2); + + if (ret < 0) + return -EIO; + + return 0; +} + +/* target must be _even_ */ +static u16 mt9t031_skip(s32 *source, s32 target, s32 max) +{ + unsigned int skip; + + if (*source < target + target / 2) { + *source = target; + return 1; + } + + skip = min(max, *source + target / 2) / target; + if (skip > 8) + skip = 8; + *source = target * skip; + + return skip; +} + +/* rect is the sensor rectangle, the caller guarantees parameter validity */ +static int mt9t031_set_params(struct i2c_client *client, + struct v4l2_rect *rect, u16 xskip, u16 yskip) +{ + struct mt9t031 *mt9t031 = to_mt9t031(client); + int ret; + u16 xbin, ybin; + const u16 hblank = MT9T031_HORIZONTAL_BLANK, + vblank = MT9T031_VERTICAL_BLANK; + + xbin = min(xskip, (u16)3); + ybin = min(yskip, (u16)3); + + /* + * Could just do roundup(rect->left, [xy]bin * 2); but this is cheaper. + * There is always a valid suitably aligned value. The worst case is + * xbin = 3, width = 2048. Then we will start at 36, the last read out + * pixel will be 2083, which is < 2085 - first black pixel. + * + * MT9T031 datasheet imposes window left border alignment, depending on + * the selected xskip. Failing to conform to this requirement produces + * dark horizontal stripes in the image. However, even obeying to this + * requirement doesn't eliminate the stripes in all configurations. They + * appear "locally reproducibly," but can differ between tests under + * different lighting conditions. + */ + switch (xbin) { + case 1: + rect->left &= ~1; + break; + case 2: + rect->left &= ~3; + break; + case 3: + rect->left = rect->left > roundup(MT9T031_COLUMN_SKIP, 6) ? + (rect->left / 6) * 6 : roundup(MT9T031_COLUMN_SKIP, 6); + } + + rect->top &= ~1; + + dev_dbg(&client->dev, "skip %u:%u, rect %ux%u@%u:%u\n", + xskip, yskip, rect->width, rect->height, rect->left, rect->top); + + /* Disable register update, reconfigure atomically */ + ret = reg_set(client, MT9T031_OUTPUT_CONTROL, 1); + if (ret < 0) + return ret; + + /* Blanking and start values - default... */ + ret = reg_write(client, MT9T031_HORIZONTAL_BLANKING, hblank); + if (ret >= 0) + ret = reg_write(client, MT9T031_VERTICAL_BLANKING, vblank); + + if (yskip != mt9t031->yskip || xskip != mt9t031->xskip) { + /* Binning, skipping */ + if (ret >= 0) + ret = reg_write(client, MT9T031_COLUMN_ADDRESS_MODE, + ((xbin - 1) << 4) | (xskip - 1)); + if (ret >= 0) + ret = reg_write(client, MT9T031_ROW_ADDRESS_MODE, + ((ybin - 1) << 4) | (yskip - 1)); + } + dev_dbg(&client->dev, "new physical left %u, top %u\n", + rect->left, rect->top); + + /* + * The caller provides a supported format, as guaranteed by + * .try_mbus_fmt(), soc_camera_s_crop() and soc_camera_cropcap() + */ + if (ret >= 0) + ret = reg_write(client, MT9T031_COLUMN_START, rect->left); + if (ret >= 0) + ret = reg_write(client, MT9T031_ROW_START, rect->top); + if (ret >= 0) + ret = reg_write(client, MT9T031_WINDOW_WIDTH, rect->width - 1); + if (ret >= 0) + ret = reg_write(client, MT9T031_WINDOW_HEIGHT, + rect->height + mt9t031->y_skip_top - 1); + if (ret >= 0 && v4l2_ctrl_g_ctrl(mt9t031->autoexposure) == V4L2_EXPOSURE_AUTO) { + mt9t031->total_h = rect->height + mt9t031->y_skip_top + vblank; + + ret = set_shutter(client, mt9t031->total_h); + } + + /* Re-enable register update, commit all changes */ + if (ret >= 0) + ret = reg_clear(client, MT9T031_OUTPUT_CONTROL, 1); + + if (ret >= 0) { + mt9t031->rect = *rect; + mt9t031->xskip = xskip; + mt9t031->yskip = yskip; + } + + return ret < 0 ? ret : 0; +} + +static int mt9t031_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) +{ + struct v4l2_rect rect = a->c; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t031 *mt9t031 = to_mt9t031(client); + + rect.width = ALIGN(rect.width, 2); + rect.height = ALIGN(rect.height, 2); + + soc_camera_limit_side(&rect.left, &rect.width, + MT9T031_COLUMN_SKIP, MT9T031_MIN_WIDTH, MT9T031_MAX_WIDTH); + + soc_camera_limit_side(&rect.top, &rect.height, + MT9T031_ROW_SKIP, MT9T031_MIN_HEIGHT, MT9T031_MAX_HEIGHT); + + return mt9t031_set_params(client, &rect, mt9t031->xskip, mt9t031->yskip); +} + +static int mt9t031_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t031 *mt9t031 = to_mt9t031(client); + + a->c = mt9t031->rect; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int mt9t031_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = MT9T031_COLUMN_SKIP; + a->bounds.top = MT9T031_ROW_SKIP; + a->bounds.width = MT9T031_MAX_WIDTH; + a->bounds.height = MT9T031_MAX_HEIGHT; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int mt9t031_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t031 *mt9t031 = to_mt9t031(client); + + mf->width = mt9t031->rect.width / mt9t031->xskip; + mf->height = mt9t031->rect.height / mt9t031->yskip; + mf->code = V4L2_MBUS_FMT_SBGGR10_1X10; + mf->colorspace = V4L2_COLORSPACE_SRGB; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mt9t031_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t031 *mt9t031 = to_mt9t031(client); + u16 xskip, yskip; + struct v4l2_rect rect = mt9t031->rect; + + /* + * try_fmt has put width and height within limits. + * S_FMT: use binning and skipping for scaling + */ + xskip = mt9t031_skip(&rect.width, mf->width, MT9T031_MAX_WIDTH); + yskip = mt9t031_skip(&rect.height, mf->height, MT9T031_MAX_HEIGHT); + + mf->code = V4L2_MBUS_FMT_SBGGR10_1X10; + mf->colorspace = V4L2_COLORSPACE_SRGB; + + /* mt9t031_set_params() doesn't change width and height */ + return mt9t031_set_params(client, &rect, xskip, yskip); +} + +/* + * If a user window larger than sensor window is requested, we'll increase the + * sensor window. + */ +static int mt9t031_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + v4l_bound_align_image( + &mf->width, MT9T031_MIN_WIDTH, MT9T031_MAX_WIDTH, 1, + &mf->height, MT9T031_MIN_HEIGHT, MT9T031_MAX_HEIGHT, 1, 0); + + mf->code = V4L2_MBUS_FMT_SBGGR10_1X10; + mf->colorspace = V4L2_COLORSPACE_SRGB; + + return 0; +} + +static int mt9t031_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t031 *mt9t031 = to_mt9t031(client); + + if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + if (id->match.addr != client->addr) + return -ENODEV; + + id->ident = mt9t031->model; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9t031_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + reg->val = reg_read(client, reg->reg); + + if (reg->val > 0xffff) + return -EIO; + + return 0; +} + +static int mt9t031_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + if (reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int mt9t031_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9t031 *mt9t031 = container_of(ctrl->handler, + struct mt9t031, hdl); + const u32 shutter_max = MT9T031_MAX_HEIGHT + MT9T031_VERTICAL_BLANK; + s32 min, max; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE_AUTO: + min = mt9t031->exposure->minimum; + max = mt9t031->exposure->maximum; + mt9t031->exposure->val = + (shutter_max / 2 + (mt9t031->total_h - 1) * (max - min)) + / shutter_max + min; + break; + } + return 0; +} + +static int mt9t031_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9t031 *mt9t031 = container_of(ctrl->handler, + struct mt9t031, hdl); + struct v4l2_subdev *sd = &mt9t031->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_ctrl *exp = mt9t031->exposure; + int data; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->val) + data = reg_set(client, MT9T031_READ_MODE_2, 0x8000); + else + data = reg_clear(client, MT9T031_READ_MODE_2, 0x8000); + if (data < 0) + return -EIO; + return 0; + case V4L2_CID_HFLIP: + if (ctrl->val) + data = reg_set(client, MT9T031_READ_MODE_2, 0x4000); + else + data = reg_clear(client, MT9T031_READ_MODE_2, 0x4000); + if (data < 0) + return -EIO; + return 0; + case V4L2_CID_GAIN: + /* See Datasheet Table 7, Gain settings. */ + if (ctrl->val <= ctrl->default_value) { + /* Pack it into 0..1 step 0.125, register values 0..8 */ + unsigned long range = ctrl->default_value - ctrl->minimum; + data = ((ctrl->val - ctrl->minimum) * 8 + range / 2) / range; + + dev_dbg(&client->dev, "Setting gain %d\n", data); + data = reg_write(client, MT9T031_GLOBAL_GAIN, data); + if (data < 0) + return -EIO; + } else { + /* Pack it into 1.125..128 variable step, register values 9..0x7860 */ + /* We assume qctrl->maximum - qctrl->default_value - 1 > 0 */ + unsigned long range = ctrl->maximum - ctrl->default_value - 1; + /* calculated gain: map 65..127 to 9..1024 step 0.125 */ + unsigned long gain = ((ctrl->val - ctrl->default_value - 1) * + 1015 + range / 2) / range + 9; + + if (gain <= 32) /* calculated gain 9..32 -> 9..32 */ + data = gain; + else if (gain <= 64) /* calculated gain 33..64 -> 0x51..0x60 */ + data = ((gain - 32) * 16 + 16) / 32 + 80; + else + /* calculated gain 65..1024 -> (1..120) << 8 + 0x60 */ + data = (((gain - 64 + 7) * 32) & 0xff00) | 0x60; + + dev_dbg(&client->dev, "Set gain from 0x%x to 0x%x\n", + reg_read(client, MT9T031_GLOBAL_GAIN), data); + data = reg_write(client, MT9T031_GLOBAL_GAIN, data); + if (data < 0) + return -EIO; + } + return 0; + + case V4L2_CID_EXPOSURE_AUTO: + if (ctrl->val == V4L2_EXPOSURE_MANUAL) { + unsigned int range = exp->maximum - exp->minimum; + unsigned int shutter = ((exp->val - exp->minimum) * 1048 + + range / 2) / range + 1; + u32 old; + + get_shutter(client, &old); + dev_dbg(&client->dev, "Set shutter from %u to %u\n", + old, shutter); + if (set_shutter(client, shutter) < 0) + return -EIO; + } else { + const u16 vblank = MT9T031_VERTICAL_BLANK; + mt9t031->total_h = mt9t031->rect.height + + mt9t031->y_skip_top + vblank; + + if (set_shutter(client, mt9t031->total_h) < 0) + return -EIO; + } + return 0; + default: + return -EINVAL; + } + return 0; +} + +/* + * Power Management: + * This function does nothing for now but must be present for pm to work + */ +static int mt9t031_runtime_suspend(struct device *dev) +{ + return 0; +} + +/* + * Power Management: + * COLUMN_ADDRESS_MODE and ROW_ADDRESS_MODE are not rewritten if unchanged + * they are however changed at reset if the platform hook is present + * thus we rewrite them with the values stored by the driver + */ +static int mt9t031_runtime_resume(struct device *dev) +{ + struct video_device *vdev = to_video_device(dev); + struct v4l2_subdev *sd = soc_camera_vdev_to_subdev(vdev); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t031 *mt9t031 = to_mt9t031(client); + + int ret; + u16 xbin, ybin; + + xbin = min(mt9t031->xskip, (u16)3); + ybin = min(mt9t031->yskip, (u16)3); + + ret = reg_write(client, MT9T031_COLUMN_ADDRESS_MODE, + ((xbin - 1) << 4) | (mt9t031->xskip - 1)); + if (ret < 0) + return ret; + + ret = reg_write(client, MT9T031_ROW_ADDRESS_MODE, + ((ybin - 1) << 4) | (mt9t031->yskip - 1)); + if (ret < 0) + return ret; + + return 0; +} + +static struct dev_pm_ops mt9t031_dev_pm_ops = { + .runtime_suspend = mt9t031_runtime_suspend, + .runtime_resume = mt9t031_runtime_resume, +}; + +static struct device_type mt9t031_dev_type = { + .name = "MT9T031", + .pm = &mt9t031_dev_pm_ops, +}; + +static int mt9t031_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct video_device *vdev = soc_camera_i2c_to_vdev(client); + int ret; + + if (on) { + ret = soc_camera_power_on(&client->dev, icl); + if (ret < 0) + return ret; + vdev->dev.type = &mt9t031_dev_type; + } else { + vdev->dev.type = NULL; + soc_camera_power_off(&client->dev, icl); + } + + return 0; +} + +/* + * Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one + */ +static int mt9t031_video_probe(struct i2c_client *client) +{ + struct mt9t031 *mt9t031 = to_mt9t031(client); + s32 data; + int ret; + + ret = mt9t031_s_power(&mt9t031->subdev, 1); + if (ret < 0) + return ret; + + ret = mt9t031_idle(client); + if (ret < 0) { + dev_err(&client->dev, "Failed to initialise the camera\n"); + goto done; + } + + /* Read out the chip version register */ + data = reg_read(client, MT9T031_CHIP_VERSION); + + switch (data) { + case 0x1621: + mt9t031->model = V4L2_IDENT_MT9T031; + break; + default: + dev_err(&client->dev, + "No MT9T031 chip detected, register read %x\n", data); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, "Detected a MT9T031 chip ID %x\n", data); + + ret = v4l2_ctrl_handler_setup(&mt9t031->hdl); + +done: + mt9t031_s_power(&mt9t031->subdev, 0); + + return ret; +} + +static int mt9t031_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t031 *mt9t031 = to_mt9t031(client); + + *lines = mt9t031->y_skip_top; + + return 0; +} + +static const struct v4l2_ctrl_ops mt9t031_ctrl_ops = { + .g_volatile_ctrl = mt9t031_g_volatile_ctrl, + .s_ctrl = mt9t031_s_ctrl, +}; + +static struct v4l2_subdev_core_ops mt9t031_subdev_core_ops = { + .g_chip_ident = mt9t031_g_chip_ident, + .s_power = mt9t031_s_power, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9t031_g_register, + .s_register = mt9t031_s_register, +#endif +}; + +static int mt9t031_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index) + return -EINVAL; + + *code = V4L2_MBUS_FMT_SBGGR10_1X10; + return 0; +} + +static int mt9t031_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_PCLK_SAMPLE_RISING | + V4L2_MBUS_PCLK_SAMPLE_FALLING | V4L2_MBUS_HSYNC_ACTIVE_HIGH | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static int mt9t031_s_mbus_config(struct v4l2_subdev *sd, + const struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + if (soc_camera_apply_board_flags(icl, cfg) & + V4L2_MBUS_PCLK_SAMPLE_FALLING) + return reg_clear(client, MT9T031_PIXEL_CLOCK_CONTROL, 0x8000); + else + return reg_set(client, MT9T031_PIXEL_CLOCK_CONTROL, 0x8000); +} + +static struct v4l2_subdev_video_ops mt9t031_subdev_video_ops = { + .s_stream = mt9t031_s_stream, + .s_mbus_fmt = mt9t031_s_fmt, + .g_mbus_fmt = mt9t031_g_fmt, + .try_mbus_fmt = mt9t031_try_fmt, + .s_crop = mt9t031_s_crop, + .g_crop = mt9t031_g_crop, + .cropcap = mt9t031_cropcap, + .enum_mbus_fmt = mt9t031_enum_fmt, + .g_mbus_config = mt9t031_g_mbus_config, + .s_mbus_config = mt9t031_s_mbus_config, +}; + +static struct v4l2_subdev_sensor_ops mt9t031_subdev_sensor_ops = { + .g_skip_top_lines = mt9t031_g_skip_top_lines, +}; + +static struct v4l2_subdev_ops mt9t031_subdev_ops = { + .core = &mt9t031_subdev_core_ops, + .video = &mt9t031_subdev_video_ops, + .sensor = &mt9t031_subdev_sensor_ops, +}; + +static int mt9t031_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9t031 *mt9t031; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret; + + if (!icl) { + dev_err(&client->dev, "MT9T031 driver needs platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9t031 = kzalloc(sizeof(struct mt9t031), GFP_KERNEL); + if (!mt9t031) + return -ENOMEM; + + v4l2_i2c_subdev_init(&mt9t031->subdev, client, &mt9t031_subdev_ops); + v4l2_ctrl_handler_init(&mt9t031->hdl, 5); + v4l2_ctrl_new_std(&mt9t031->hdl, &mt9t031_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9t031->hdl, &mt9t031_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9t031->hdl, &mt9t031_ctrl_ops, + V4L2_CID_GAIN, 0, 127, 1, 64); + + /* + * Simulated autoexposure. If enabled, we calculate shutter width + * ourselves in the driver based on vertical blanking and frame width + */ + mt9t031->autoexposure = v4l2_ctrl_new_std_menu(&mt9t031->hdl, + &mt9t031_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0, + V4L2_EXPOSURE_AUTO); + mt9t031->exposure = v4l2_ctrl_new_std(&mt9t031->hdl, &mt9t031_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 255, 1, 255); + + mt9t031->subdev.ctrl_handler = &mt9t031->hdl; + if (mt9t031->hdl.error) { + int err = mt9t031->hdl.error; + + kfree(mt9t031); + return err; + } + v4l2_ctrl_auto_cluster(2, &mt9t031->autoexposure, + V4L2_EXPOSURE_MANUAL, true); + + mt9t031->y_skip_top = 0; + mt9t031->rect.left = MT9T031_COLUMN_SKIP; + mt9t031->rect.top = MT9T031_ROW_SKIP; + mt9t031->rect.width = MT9T031_MAX_WIDTH; + mt9t031->rect.height = MT9T031_MAX_HEIGHT; + + mt9t031->xskip = 1; + mt9t031->yskip = 1; + + ret = mt9t031_video_probe(client); + if (ret) { + v4l2_ctrl_handler_free(&mt9t031->hdl); + kfree(mt9t031); + } + + return ret; +} + +static int mt9t031_remove(struct i2c_client *client) +{ + struct mt9t031 *mt9t031 = to_mt9t031(client); + + v4l2_device_unregister_subdev(&mt9t031->subdev); + v4l2_ctrl_handler_free(&mt9t031->hdl); + kfree(mt9t031); + + return 0; +} + +static const struct i2c_device_id mt9t031_id[] = { + { "mt9t031", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9t031_id); + +static struct i2c_driver mt9t031_i2c_driver = { + .driver = { + .name = "mt9t031", + }, + .probe = mt9t031_probe, + .remove = mt9t031_remove, + .id_table = mt9t031_id, +}; + +module_i2c_driver(mt9t031_i2c_driver); + +MODULE_DESCRIPTION("Micron MT9T031 Camera driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/soc_camera/mt9t112.c b/drivers/media/i2c/soc_camera/mt9t112.c new file mode 100644 index 000000000000..de7cd836b0a2 --- /dev/null +++ b/drivers/media/i2c/soc_camera/mt9t112.c @@ -0,0 +1,1142 @@ +/* + * mt9t112 Camera Driver + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Kuninori Morimoto + * + * Based on ov772x driver, mt9m111 driver, + * + * Copyright (C) 2008 Kuninori Morimoto + * Copyright (C) 2008, Robert Jarzmik + * Copyright 2006-7 Jonathan Corbet + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* you can check PLL/clock info */ +/* #define EXT_CLOCK 24000000 */ + +/************************************************************************ + macro +************************************************************************/ +/* + * frame size + */ +#define MAX_WIDTH 2048 +#define MAX_HEIGHT 1536 + +#define VGA_WIDTH 640 +#define VGA_HEIGHT 480 + +/* + * macro of read/write + */ +#define ECHECKER(ret, x) \ + do { \ + (ret) = (x); \ + if ((ret) < 0) \ + return (ret); \ + } while (0) + +#define mt9t112_reg_write(ret, client, a, b) \ + ECHECKER(ret, __mt9t112_reg_write(client, a, b)) +#define mt9t112_mcu_write(ret, client, a, b) \ + ECHECKER(ret, __mt9t112_mcu_write(client, a, b)) + +#define mt9t112_reg_mask_set(ret, client, a, b, c) \ + ECHECKER(ret, __mt9t112_reg_mask_set(client, a, b, c)) +#define mt9t112_mcu_mask_set(ret, client, a, b, c) \ + ECHECKER(ret, __mt9t112_mcu_mask_set(client, a, b, c)) + +#define mt9t112_reg_read(ret, client, a) \ + ECHECKER(ret, __mt9t112_reg_read(client, a)) + +/* + * Logical address + */ +#define _VAR(id, offset, base) (base | (id & 0x1f) << 10 | (offset & 0x3ff)) +#define VAR(id, offset) _VAR(id, offset, 0x0000) +#define VAR8(id, offset) _VAR(id, offset, 0x8000) + +/************************************************************************ + struct +************************************************************************/ +struct mt9t112_format { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; + u16 fmt; + u16 order; +}; + +struct mt9t112_priv { + struct v4l2_subdev subdev; + struct mt9t112_camera_info *info; + struct i2c_client *client; + struct v4l2_rect frame; + const struct mt9t112_format *format; + int model; + u32 flags; +/* for flags */ +#define INIT_DONE (1 << 0) +#define PCLK_RISING (1 << 1) +}; + +/************************************************************************ + supported format +************************************************************************/ + +static const struct mt9t112_format mt9t112_cfmts[] = { + { + .code = V4L2_MBUS_FMT_UYVY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 0, + }, { + .code = V4L2_MBUS_FMT_VYUY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 1, + }, { + .code = V4L2_MBUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 2, + }, { + .code = V4L2_MBUS_FMT_YVYU8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 3, + }, { + .code = V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt = 8, + .order = 2, + }, { + .code = V4L2_MBUS_FMT_RGB565_2X8_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt = 4, + .order = 2, + }, +}; + +/************************************************************************ + general function +************************************************************************/ +static struct mt9t112_priv *to_mt9t112(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), + struct mt9t112_priv, + subdev); +} + +static int __mt9t112_reg_read(const struct i2c_client *client, u16 command) +{ + struct i2c_msg msg[2]; + u8 buf[2]; + int ret; + + command = swab16(command); + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 2; + msg[0].buf = (u8 *)&command; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = buf; + + /* + * if return value of this function is < 0, + * it mean error. + * else, under 16bit is valid data. + */ + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + + memcpy(&ret, buf, 2); + return swab16(ret); +} + +static int __mt9t112_reg_write(const struct i2c_client *client, + u16 command, u16 data) +{ + struct i2c_msg msg; + u8 buf[4]; + int ret; + + command = swab16(command); + data = swab16(data); + + memcpy(buf + 0, &command, 2); + memcpy(buf + 2, &data, 2); + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 4; + msg.buf = buf; + + /* + * i2c_transfer return message length, + * but this function should return 0 if correct case + */ + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret >= 0) + ret = 0; + + return ret; +} + +static int __mt9t112_reg_mask_set(const struct i2c_client *client, + u16 command, + u16 mask, + u16 set) +{ + int val = __mt9t112_reg_read(client, command); + if (val < 0) + return val; + + val &= ~mask; + val |= set & mask; + + return __mt9t112_reg_write(client, command, val); +} + +/* mcu access */ +static int __mt9t112_mcu_read(const struct i2c_client *client, u16 command) +{ + int ret; + + ret = __mt9t112_reg_write(client, 0x098E, command); + if (ret < 0) + return ret; + + return __mt9t112_reg_read(client, 0x0990); +} + +static int __mt9t112_mcu_write(const struct i2c_client *client, + u16 command, u16 data) +{ + int ret; + + ret = __mt9t112_reg_write(client, 0x098E, command); + if (ret < 0) + return ret; + + return __mt9t112_reg_write(client, 0x0990, data); +} + +static int __mt9t112_mcu_mask_set(const struct i2c_client *client, + u16 command, + u16 mask, + u16 set) +{ + int val = __mt9t112_mcu_read(client, command); + if (val < 0) + return val; + + val &= ~mask; + val |= set & mask; + + return __mt9t112_mcu_write(client, command, val); +} + +static int mt9t112_reset(const struct i2c_client *client) +{ + int ret; + + mt9t112_reg_mask_set(ret, client, 0x001a, 0x0001, 0x0001); + msleep(1); + mt9t112_reg_mask_set(ret, client, 0x001a, 0x0001, 0x0000); + + return ret; +} + +#ifndef EXT_CLOCK +#define CLOCK_INFO(a, b) +#else +#define CLOCK_INFO(a, b) mt9t112_clock_info(a, b) +static int mt9t112_clock_info(const struct i2c_client *client, u32 ext) +{ + int m, n, p1, p2, p3, p4, p5, p6, p7; + u32 vco, clk; + char *enable; + + ext /= 1000; /* kbyte order */ + + mt9t112_reg_read(n, client, 0x0012); + p1 = n & 0x000f; + n = n >> 4; + p2 = n & 0x000f; + n = n >> 4; + p3 = n & 0x000f; + + mt9t112_reg_read(n, client, 0x002a); + p4 = n & 0x000f; + n = n >> 4; + p5 = n & 0x000f; + n = n >> 4; + p6 = n & 0x000f; + + mt9t112_reg_read(n, client, 0x002c); + p7 = n & 0x000f; + + mt9t112_reg_read(n, client, 0x0010); + m = n & 0x00ff; + n = (n >> 8) & 0x003f; + + enable = ((6000 > ext) || (54000 < ext)) ? "X" : ""; + dev_dbg(&client->dev, "EXTCLK : %10u K %s\n", ext, enable); + + vco = 2 * m * ext / (n+1); + enable = ((384000 > vco) || (768000 < vco)) ? "X" : ""; + dev_dbg(&client->dev, "VCO : %10u K %s\n", vco, enable); + + clk = vco / (p1+1) / (p2+1); + enable = (96000 < clk) ? "X" : ""; + dev_dbg(&client->dev, "PIXCLK : %10u K %s\n", clk, enable); + + clk = vco / (p3+1); + enable = (768000 < clk) ? "X" : ""; + dev_dbg(&client->dev, "MIPICLK : %10u K %s\n", clk, enable); + + clk = vco / (p6+1); + enable = (96000 < clk) ? "X" : ""; + dev_dbg(&client->dev, "MCU CLK : %10u K %s\n", clk, enable); + + clk = vco / (p5+1); + enable = (54000 < clk) ? "X" : ""; + dev_dbg(&client->dev, "SOC CLK : %10u K %s\n", clk, enable); + + clk = vco / (p4+1); + enable = (70000 < clk) ? "X" : ""; + dev_dbg(&client->dev, "Sensor CLK : %10u K %s\n", clk, enable); + + clk = vco / (p7+1); + dev_dbg(&client->dev, "External sensor : %10u K\n", clk); + + clk = ext / (n+1); + enable = ((2000 > clk) || (24000 < clk)) ? "X" : ""; + dev_dbg(&client->dev, "PFD : %10u K %s\n", clk, enable); + + return 0; +} +#endif + +static void mt9t112_frame_check(u32 *width, u32 *height, u32 *left, u32 *top) +{ + soc_camera_limit_side(left, width, 0, 0, MAX_WIDTH); + soc_camera_limit_side(top, height, 0, 0, MAX_HEIGHT); +} + +static int mt9t112_set_a_frame_size(const struct i2c_client *client, + u16 width, + u16 height) +{ + int ret; + u16 wstart = (MAX_WIDTH - width) / 2; + u16 hstart = (MAX_HEIGHT - height) / 2; + + /* (Context A) Image Width/Height */ + mt9t112_mcu_write(ret, client, VAR(26, 0), width); + mt9t112_mcu_write(ret, client, VAR(26, 2), height); + + /* (Context A) Output Width/Height */ + mt9t112_mcu_write(ret, client, VAR(18, 43), 8 + width); + mt9t112_mcu_write(ret, client, VAR(18, 45), 8 + height); + + /* (Context A) Start Row/Column */ + mt9t112_mcu_write(ret, client, VAR(18, 2), 4 + hstart); + mt9t112_mcu_write(ret, client, VAR(18, 4), 4 + wstart); + + /* (Context A) End Row/Column */ + mt9t112_mcu_write(ret, client, VAR(18, 6), 11 + height + hstart); + mt9t112_mcu_write(ret, client, VAR(18, 8), 11 + width + wstart); + + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x06); + + return ret; +} + +static int mt9t112_set_pll_dividers(const struct i2c_client *client, + u8 m, u8 n, + u8 p1, u8 p2, u8 p3, + u8 p4, u8 p5, u8 p6, + u8 p7) +{ + int ret; + u16 val; + + /* N/M */ + val = (n << 8) | + (m << 0); + mt9t112_reg_mask_set(ret, client, 0x0010, 0x3fff, val); + + /* P1/P2/P3 */ + val = ((p3 & 0x0F) << 8) | + ((p2 & 0x0F) << 4) | + ((p1 & 0x0F) << 0); + mt9t112_reg_mask_set(ret, client, 0x0012, 0x0fff, val); + + /* P4/P5/P6 */ + val = (0x7 << 12) | + ((p6 & 0x0F) << 8) | + ((p5 & 0x0F) << 4) | + ((p4 & 0x0F) << 0); + mt9t112_reg_mask_set(ret, client, 0x002A, 0x7fff, val); + + /* P7 */ + val = (0x1 << 12) | + ((p7 & 0x0F) << 0); + mt9t112_reg_mask_set(ret, client, 0x002C, 0x100f, val); + + return ret; +} + +static int mt9t112_init_pll(const struct i2c_client *client) +{ + struct mt9t112_priv *priv = to_mt9t112(client); + int data, i, ret; + + mt9t112_reg_mask_set(ret, client, 0x0014, 0x003, 0x0001); + + /* PLL control: BYPASS PLL = 8517 */ + mt9t112_reg_write(ret, client, 0x0014, 0x2145); + + /* Replace these registers when new timing parameters are generated */ + mt9t112_set_pll_dividers(client, + priv->info->divider.m, + priv->info->divider.n, + priv->info->divider.p1, + priv->info->divider.p2, + priv->info->divider.p3, + priv->info->divider.p4, + priv->info->divider.p5, + priv->info->divider.p6, + priv->info->divider.p7); + + /* + * TEST_BYPASS on + * PLL_ENABLE on + * SEL_LOCK_DET on + * TEST_BYPASS off + */ + mt9t112_reg_write(ret, client, 0x0014, 0x2525); + mt9t112_reg_write(ret, client, 0x0014, 0x2527); + mt9t112_reg_write(ret, client, 0x0014, 0x3427); + mt9t112_reg_write(ret, client, 0x0014, 0x3027); + + mdelay(10); + + /* + * PLL_BYPASS off + * Reference clock count + * I2C Master Clock Divider + */ + mt9t112_reg_write(ret, client, 0x0014, 0x3046); + mt9t112_reg_write(ret, client, 0x0016, 0x0400); /* JPEG initialization workaround */ + mt9t112_reg_write(ret, client, 0x0022, 0x0190); + mt9t112_reg_write(ret, client, 0x3B84, 0x0212); + + /* External sensor clock is PLL bypass */ + mt9t112_reg_write(ret, client, 0x002E, 0x0500); + + mt9t112_reg_mask_set(ret, client, 0x0018, 0x0002, 0x0002); + mt9t112_reg_mask_set(ret, client, 0x3B82, 0x0004, 0x0004); + + /* MCU disabled */ + mt9t112_reg_mask_set(ret, client, 0x0018, 0x0004, 0x0004); + + /* out of standby */ + mt9t112_reg_mask_set(ret, client, 0x0018, 0x0001, 0); + + mdelay(50); + + /* + * Standby Workaround + * Disable Secondary I2C Pads + */ + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + mt9t112_reg_write(ret, client, 0x0614, 0x0001); + mdelay(1); + + /* poll to verify out of standby. Must Poll this bit */ + for (i = 0; i < 100; i++) { + mt9t112_reg_read(data, client, 0x0018); + if (!(0x4000 & data)) + break; + + mdelay(10); + } + + return ret; +} + +static int mt9t112_init_setting(const struct i2c_client *client) +{ + + int ret; + + /* Adaptive Output Clock (A) */ + mt9t112_mcu_mask_set(ret, client, VAR(26, 160), 0x0040, 0x0000); + + /* Read Mode (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 12), 0x0024); + + /* Fine Correction (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 15), 0x00CC); + + /* Fine IT Min (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 17), 0x01f1); + + /* Fine IT Max Margin (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 19), 0x00fF); + + /* Base Frame Lines (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 29), 0x032D); + + /* Min Line Length (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 31), 0x073a); + + /* Line Length (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 37), 0x07d0); + + /* Adaptive Output Clock (B) */ + mt9t112_mcu_mask_set(ret, client, VAR(27, 160), 0x0040, 0x0000); + + /* Row Start (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 74), 0x004); + + /* Column Start (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 76), 0x004); + + /* Row End (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 78), 0x60B); + + /* Column End (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 80), 0x80B); + + /* Fine Correction (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 87), 0x008C); + + /* Fine IT Min (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 89), 0x01F1); + + /* Fine IT Max Margin (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 91), 0x00FF); + + /* Base Frame Lines (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 101), 0x0668); + + /* Min Line Length (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 103), 0x0AF0); + + /* Line Length (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 109), 0x0AF0); + + /* + * Flicker Dectection registers + * This section should be replaced whenever new Timing file is generated + * All the following registers need to be replaced + * Following registers are generated from Register Wizard but user can + * modify them. For detail see auto flicker detection tuning + */ + + /* FD_FDPERIOD_SELECT */ + mt9t112_mcu_write(ret, client, VAR8(8, 5), 0x01); + + /* PRI_B_CONFIG_FD_ALGO_RUN */ + mt9t112_mcu_write(ret, client, VAR(27, 17), 0x0003); + + /* PRI_A_CONFIG_FD_ALGO_RUN */ + mt9t112_mcu_write(ret, client, VAR(26, 17), 0x0003); + + /* + * AFD range detection tuning registers + */ + + /* search_f1_50 */ + mt9t112_mcu_write(ret, client, VAR8(18, 165), 0x25); + + /* search_f2_50 */ + mt9t112_mcu_write(ret, client, VAR8(18, 166), 0x28); + + /* search_f1_60 */ + mt9t112_mcu_write(ret, client, VAR8(18, 167), 0x2C); + + /* search_f2_60 */ + mt9t112_mcu_write(ret, client, VAR8(18, 168), 0x2F); + + /* period_50Hz (A) */ + mt9t112_mcu_write(ret, client, VAR8(18, 68), 0xBA); + + /* secret register by aptina */ + /* period_50Hz (A MSB) */ + mt9t112_mcu_write(ret, client, VAR8(18, 303), 0x00); + + /* period_60Hz (A) */ + mt9t112_mcu_write(ret, client, VAR8(18, 69), 0x9B); + + /* secret register by aptina */ + /* period_60Hz (A MSB) */ + mt9t112_mcu_write(ret, client, VAR8(18, 301), 0x00); + + /* period_50Hz (B) */ + mt9t112_mcu_write(ret, client, VAR8(18, 140), 0x82); + + /* secret register by aptina */ + /* period_50Hz (B) MSB */ + mt9t112_mcu_write(ret, client, VAR8(18, 304), 0x00); + + /* period_60Hz (B) */ + mt9t112_mcu_write(ret, client, VAR8(18, 141), 0x6D); + + /* secret register by aptina */ + /* period_60Hz (B) MSB */ + mt9t112_mcu_write(ret, client, VAR8(18, 302), 0x00); + + /* FD Mode */ + mt9t112_mcu_write(ret, client, VAR8(8, 2), 0x10); + + /* Stat_min */ + mt9t112_mcu_write(ret, client, VAR8(8, 9), 0x02); + + /* Stat_max */ + mt9t112_mcu_write(ret, client, VAR8(8, 10), 0x03); + + /* Min_amplitude */ + mt9t112_mcu_write(ret, client, VAR8(8, 12), 0x0A); + + /* RX FIFO Watermark (A) */ + mt9t112_mcu_write(ret, client, VAR(18, 70), 0x0014); + + /* RX FIFO Watermark (B) */ + mt9t112_mcu_write(ret, client, VAR(18, 142), 0x0014); + + /* MCLK: 16MHz + * PCLK: 73MHz + * CorePixCLK: 36.5 MHz + */ + mt9t112_mcu_write(ret, client, VAR8(18, 0x0044), 133); + mt9t112_mcu_write(ret, client, VAR8(18, 0x0045), 110); + mt9t112_mcu_write(ret, client, VAR8(18, 0x008c), 130); + mt9t112_mcu_write(ret, client, VAR8(18, 0x008d), 108); + + mt9t112_mcu_write(ret, client, VAR8(18, 0x00A5), 27); + mt9t112_mcu_write(ret, client, VAR8(18, 0x00a6), 30); + mt9t112_mcu_write(ret, client, VAR8(18, 0x00a7), 32); + mt9t112_mcu_write(ret, client, VAR8(18, 0x00a8), 35); + + return ret; +} + +static int mt9t112_auto_focus_setting(const struct i2c_client *client) +{ + int ret; + + mt9t112_mcu_write(ret, client, VAR(12, 13), 0x000F); + mt9t112_mcu_write(ret, client, VAR(12, 23), 0x0F0F); + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x06); + + mt9t112_reg_write(ret, client, 0x0614, 0x0000); + + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x05); + mt9t112_mcu_write(ret, client, VAR8(12, 2), 0x02); + mt9t112_mcu_write(ret, client, VAR(12, 3), 0x0002); + mt9t112_mcu_write(ret, client, VAR(17, 3), 0x8001); + mt9t112_mcu_write(ret, client, VAR(17, 11), 0x0025); + mt9t112_mcu_write(ret, client, VAR(17, 13), 0x0193); + mt9t112_mcu_write(ret, client, VAR8(17, 33), 0x18); + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x05); + + return ret; +} + +static int mt9t112_auto_focus_trigger(const struct i2c_client *client) +{ + int ret; + + mt9t112_mcu_write(ret, client, VAR8(12, 25), 0x01); + + return ret; +} + +static int mt9t112_init_camera(const struct i2c_client *client) +{ + int ret; + + ECHECKER(ret, mt9t112_reset(client)); + + ECHECKER(ret, mt9t112_init_pll(client)); + + ECHECKER(ret, mt9t112_init_setting(client)); + + ECHECKER(ret, mt9t112_auto_focus_setting(client)); + + mt9t112_reg_mask_set(ret, client, 0x0018, 0x0004, 0); + + /* Analog setting B */ + mt9t112_reg_write(ret, client, 0x3084, 0x2409); + mt9t112_reg_write(ret, client, 0x3092, 0x0A49); + mt9t112_reg_write(ret, client, 0x3094, 0x4949); + mt9t112_reg_write(ret, client, 0x3096, 0x4950); + + /* + * Disable adaptive clock + * PRI_A_CONFIG_JPEG_OB_TX_CONTROL_VAR + * PRI_B_CONFIG_JPEG_OB_TX_CONTROL_VAR + */ + mt9t112_mcu_write(ret, client, VAR(26, 160), 0x0A2E); + mt9t112_mcu_write(ret, client, VAR(27, 160), 0x0A2E); + + /* Configure STatus in Status_before_length Format and enable header */ + /* PRI_B_CONFIG_JPEG_OB_TX_CONTROL_VAR */ + mt9t112_mcu_write(ret, client, VAR(27, 144), 0x0CB4); + + /* Enable JPEG in context B */ + /* PRI_B_CONFIG_JPEG_OB_TX_CONTROL_VAR */ + mt9t112_mcu_write(ret, client, VAR8(27, 142), 0x01); + + /* Disable Dac_TXLO */ + mt9t112_reg_write(ret, client, 0x316C, 0x350F); + + /* Set max slew rates */ + mt9t112_reg_write(ret, client, 0x1E, 0x777); + + return ret; +} + +/************************************************************************ + v4l2_subdev_core_ops +************************************************************************/ +static int mt9t112_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + + id->ident = priv->model; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9t112_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + reg->size = 2; + mt9t112_reg_read(ret, client, reg->reg); + + reg->val = (__u64)ret; + + return 0; +} + +static int mt9t112_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + mt9t112_reg_write(ret, client, reg->reg, reg->val); + + return ret; +} +#endif + +static int mt9t112_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +static struct v4l2_subdev_core_ops mt9t112_subdev_core_ops = { + .g_chip_ident = mt9t112_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9t112_g_register, + .s_register = mt9t112_s_register, +#endif + .s_power = mt9t112_s_power, +}; + + +/************************************************************************ + v4l2_subdev_video_ops +************************************************************************/ +static int mt9t112_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + int ret = 0; + + if (!enable) { + /* FIXME + * + * If user selected large output size, + * and used it long time, + * mt9t112 camera will be very warm. + * + * But current driver can not stop mt9t112 camera. + * So, set small size here to solve this problem. + */ + mt9t112_set_a_frame_size(client, VGA_WIDTH, VGA_HEIGHT); + return ret; + } + + if (!(priv->flags & INIT_DONE)) { + u16 param = PCLK_RISING & priv->flags ? 0x0001 : 0x0000; + + ECHECKER(ret, mt9t112_init_camera(client)); + + /* Invert PCLK (Data sampled on falling edge of pixclk) */ + mt9t112_reg_write(ret, client, 0x3C20, param); + + mdelay(5); + + priv->flags |= INIT_DONE; + } + + mt9t112_mcu_write(ret, client, VAR(26, 7), priv->format->fmt); + mt9t112_mcu_write(ret, client, VAR(26, 9), priv->format->order); + mt9t112_mcu_write(ret, client, VAR8(1, 0), 0x06); + + mt9t112_set_a_frame_size(client, + priv->frame.width, + priv->frame.height); + + ECHECKER(ret, mt9t112_auto_focus_trigger(client)); + + dev_dbg(&client->dev, "format : %d\n", priv->format->code); + dev_dbg(&client->dev, "size : %d x %d\n", + priv->frame.width, + priv->frame.height); + + CLOCK_INFO(client, EXT_CLOCK); + + return ret; +} + +static int mt9t112_set_params(struct mt9t112_priv *priv, + const struct v4l2_rect *rect, + enum v4l2_mbus_pixelcode code) +{ + int i; + + /* + * get color format + */ + for (i = 0; i < ARRAY_SIZE(mt9t112_cfmts); i++) + if (mt9t112_cfmts[i].code == code) + break; + + if (i == ARRAY_SIZE(mt9t112_cfmts)) + return -EINVAL; + + priv->frame = *rect; + + /* + * frame size check + */ + mt9t112_frame_check(&priv->frame.width, &priv->frame.height, + &priv->frame.left, &priv->frame.top); + + priv->format = mt9t112_cfmts + i; + + return 0; +} + +static int mt9t112_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = MAX_WIDTH; + a->bounds.height = MAX_HEIGHT; + a->defrect.left = 0; + a->defrect.top = 0; + a->defrect.width = VGA_WIDTH; + a->defrect.height = VGA_HEIGHT; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int mt9t112_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + + a->c = priv->frame; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int mt9t112_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + const struct v4l2_rect *rect = &a->c; + + return mt9t112_set_params(priv, rect, priv->format->code); +} + +static int mt9t112_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + + mf->width = priv->frame.width; + mf->height = priv->frame.height; + mf->colorspace = priv->format->colorspace; + mf->code = priv->format->code; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mt9t112_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9t112_priv *priv = to_mt9t112(client); + struct v4l2_rect rect = { + .width = mf->width, + .height = mf->height, + .left = priv->frame.left, + .top = priv->frame.top, + }; + int ret; + + ret = mt9t112_set_params(priv, &rect, mf->code); + + if (!ret) + mf->colorspace = priv->format->colorspace; + + return ret; +} + +static int mt9t112_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + unsigned int top, left; + int i; + + for (i = 0; i < ARRAY_SIZE(mt9t112_cfmts); i++) + if (mt9t112_cfmts[i].code == mf->code) + break; + + if (i == ARRAY_SIZE(mt9t112_cfmts)) { + mf->code = V4L2_MBUS_FMT_UYVY8_2X8; + mf->colorspace = V4L2_COLORSPACE_JPEG; + } else { + mf->colorspace = mt9t112_cfmts[i].colorspace; + } + + mt9t112_frame_check(&mf->width, &mf->height, &left, &top); + + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mt9t112_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(mt9t112_cfmts)) + return -EINVAL; + + *code = mt9t112_cfmts[index].code; + + return 0; +} + +static int mt9t112_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_VSYNC_ACTIVE_HIGH | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_DATA_ACTIVE_HIGH | + V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static int mt9t112_s_mbus_config(struct v4l2_subdev *sd, + const struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct mt9t112_priv *priv = to_mt9t112(client); + + if (soc_camera_apply_board_flags(icl, cfg) & V4L2_MBUS_PCLK_SAMPLE_RISING) + priv->flags |= PCLK_RISING; + + return 0; +} + +static struct v4l2_subdev_video_ops mt9t112_subdev_video_ops = { + .s_stream = mt9t112_s_stream, + .g_mbus_fmt = mt9t112_g_fmt, + .s_mbus_fmt = mt9t112_s_fmt, + .try_mbus_fmt = mt9t112_try_fmt, + .cropcap = mt9t112_cropcap, + .g_crop = mt9t112_g_crop, + .s_crop = mt9t112_s_crop, + .enum_mbus_fmt = mt9t112_enum_fmt, + .g_mbus_config = mt9t112_g_mbus_config, + .s_mbus_config = mt9t112_s_mbus_config, +}; + +/************************************************************************ + i2c driver +************************************************************************/ +static struct v4l2_subdev_ops mt9t112_subdev_ops = { + .core = &mt9t112_subdev_core_ops, + .video = &mt9t112_subdev_video_ops, +}; + +static int mt9t112_camera_probe(struct i2c_client *client) +{ + struct mt9t112_priv *priv = to_mt9t112(client); + const char *devname; + int chipid; + int ret; + + ret = mt9t112_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * check and show chip ID + */ + mt9t112_reg_read(chipid, client, 0x0000); + + switch (chipid) { + case 0x2680: + devname = "mt9t111"; + priv->model = V4L2_IDENT_MT9T111; + break; + case 0x2682: + devname = "mt9t112"; + priv->model = V4L2_IDENT_MT9T112; + break; + default: + dev_err(&client->dev, "Product ID error %04x\n", chipid); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, "%s chip ID %04x\n", devname, chipid); + +done: + mt9t112_s_power(&priv->subdev, 0); + return ret; +} + +static int mt9t112_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9t112_priv *priv; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct v4l2_rect rect = { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .left = (MAX_WIDTH - VGA_WIDTH) / 2, + .top = (MAX_HEIGHT - VGA_HEIGHT) / 2, + }; + int ret; + + if (!icl || !icl->priv) { + dev_err(&client->dev, "mt9t112: missing platform data!\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = icl->priv; + + v4l2_i2c_subdev_init(&priv->subdev, client, &mt9t112_subdev_ops); + + ret = mt9t112_camera_probe(client); + if (ret) { + kfree(priv); + return ret; + } + + /* Cannot fail: using the default supported pixel code */ + mt9t112_set_params(priv, &rect, V4L2_MBUS_FMT_UYVY8_2X8); + + return ret; +} + +static int mt9t112_remove(struct i2c_client *client) +{ + struct mt9t112_priv *priv = to_mt9t112(client); + + kfree(priv); + return 0; +} + +static const struct i2c_device_id mt9t112_id[] = { + { "mt9t112", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9t112_id); + +static struct i2c_driver mt9t112_i2c_driver = { + .driver = { + .name = "mt9t112", + }, + .probe = mt9t112_probe, + .remove = mt9t112_remove, + .id_table = mt9t112_id, +}; + +module_i2c_driver(mt9t112_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for mt9t112"); +MODULE_AUTHOR("Kuninori Morimoto"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/soc_camera/mt9v022.c b/drivers/media/i2c/soc_camera/mt9v022.c new file mode 100644 index 000000000000..13057b966ee9 --- /dev/null +++ b/drivers/media/i2c/soc_camera/mt9v022.c @@ -0,0 +1,919 @@ +/* + * Driver for MT9V022 CMOS Image Sensor from Micron + * + * Copyright (C) 2008, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * mt9v022 i2c address 0x48, 0x4c, 0x58, 0x5c + * The platform has to define struct i2c_board_info objects and link to them + * from struct soc_camera_link + */ + +static char *sensor_type; +module_param(sensor_type, charp, S_IRUGO); +MODULE_PARM_DESC(sensor_type, "Sensor type: \"colour\" or \"monochrome\""); + +/* mt9v022 selected register addresses */ +#define MT9V022_CHIP_VERSION 0x00 +#define MT9V022_COLUMN_START 0x01 +#define MT9V022_ROW_START 0x02 +#define MT9V022_WINDOW_HEIGHT 0x03 +#define MT9V022_WINDOW_WIDTH 0x04 +#define MT9V022_HORIZONTAL_BLANKING 0x05 +#define MT9V022_VERTICAL_BLANKING 0x06 +#define MT9V022_CHIP_CONTROL 0x07 +#define MT9V022_SHUTTER_WIDTH1 0x08 +#define MT9V022_SHUTTER_WIDTH2 0x09 +#define MT9V022_SHUTTER_WIDTH_CTRL 0x0a +#define MT9V022_TOTAL_SHUTTER_WIDTH 0x0b +#define MT9V022_RESET 0x0c +#define MT9V022_READ_MODE 0x0d +#define MT9V022_MONITOR_MODE 0x0e +#define MT9V022_PIXEL_OPERATION_MODE 0x0f +#define MT9V022_LED_OUT_CONTROL 0x1b +#define MT9V022_ADC_MODE_CONTROL 0x1c +#define MT9V022_ANALOG_GAIN 0x35 +#define MT9V022_BLACK_LEVEL_CALIB_CTRL 0x47 +#define MT9V022_PIXCLK_FV_LV 0x74 +#define MT9V022_DIGITAL_TEST_PATTERN 0x7f +#define MT9V022_AEC_AGC_ENABLE 0xAF +#define MT9V022_MAX_TOTAL_SHUTTER_WIDTH 0xBD + +/* mt9v024 partial list register addresses changes with respect to mt9v022 */ +#define MT9V024_PIXCLK_FV_LV 0x72 +#define MT9V024_MAX_TOTAL_SHUTTER_WIDTH 0xAD + +/* Progressive scan, master, defaults */ +#define MT9V022_CHIP_CONTROL_DEFAULT 0x188 + +#define MT9V022_MAX_WIDTH 752 +#define MT9V022_MAX_HEIGHT 480 +#define MT9V022_MIN_WIDTH 48 +#define MT9V022_MIN_HEIGHT 32 +#define MT9V022_COLUMN_SKIP 1 +#define MT9V022_ROW_SKIP 4 + +#define is_mt9v024(id) (id == 0x1324) + +/* MT9V022 has only one fixed colorspace per pixelcode */ +struct mt9v022_datafmt { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; +}; + +/* Find a data format by a pixel code in an array */ +static const struct mt9v022_datafmt *mt9v022_find_datafmt( + enum v4l2_mbus_pixelcode code, const struct mt9v022_datafmt *fmt, + int n) +{ + int i; + for (i = 0; i < n; i++) + if (fmt[i].code == code) + return fmt + i; + + return NULL; +} + +static const struct mt9v022_datafmt mt9v022_colour_fmts[] = { + /* + * Order important: first natively supported, + * second supported with a GPIO extender + */ + {V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB}, +}; + +static const struct mt9v022_datafmt mt9v022_monochrome_fmts[] = { + /* Order important - see above */ + {V4L2_MBUS_FMT_Y10_1X10, V4L2_COLORSPACE_JPEG}, + {V4L2_MBUS_FMT_Y8_1X8, V4L2_COLORSPACE_JPEG}, +}; + +/* only registers with different addresses on different mt9v02x sensors */ +struct mt9v02x_register { + u8 max_total_shutter_width; + u8 pixclk_fv_lv; +}; + +static const struct mt9v02x_register mt9v022_register = { + .max_total_shutter_width = MT9V022_MAX_TOTAL_SHUTTER_WIDTH, + .pixclk_fv_lv = MT9V022_PIXCLK_FV_LV, +}; + +static const struct mt9v02x_register mt9v024_register = { + .max_total_shutter_width = MT9V024_MAX_TOTAL_SHUTTER_WIDTH, + .pixclk_fv_lv = MT9V024_PIXCLK_FV_LV, +}; + +struct mt9v022 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct { + /* exposure/auto-exposure cluster */ + struct v4l2_ctrl *autoexposure; + struct v4l2_ctrl *exposure; + }; + struct { + /* gain/auto-gain cluster */ + struct v4l2_ctrl *autogain; + struct v4l2_ctrl *gain; + }; + struct v4l2_rect rect; /* Sensor window */ + const struct mt9v022_datafmt *fmt; + const struct mt9v022_datafmt *fmts; + const struct mt9v02x_register *reg; + int num_fmts; + int model; /* V4L2_IDENT_MT9V022* codes from v4l2-chip-ident.h */ + u16 chip_control; + unsigned short y_skip_top; /* Lines to skip at the top */ +}; + +static struct mt9v022 *to_mt9v022(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct mt9v022, subdev); +} + +static int reg_read(struct i2c_client *client, const u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int reg_write(struct i2c_client *client, const u8 reg, + const u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static int reg_set(struct i2c_client *client, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, ret | data); +} + +static int reg_clear(struct i2c_client *client, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, ret & ~data); +} + +static int mt9v022_init(struct i2c_client *client) +{ + struct mt9v022 *mt9v022 = to_mt9v022(client); + int ret; + + /* + * Almost the default mode: master, parallel, simultaneous, and an + * undocumented bit 0x200, which is present in table 7, but not in 8, + * plus snapshot mode to disable scan for now + */ + mt9v022->chip_control |= 0x10; + ret = reg_write(client, MT9V022_CHIP_CONTROL, mt9v022->chip_control); + if (!ret) + ret = reg_write(client, MT9V022_READ_MODE, 0x300); + + /* All defaults */ + if (!ret) + /* AEC, AGC on */ + ret = reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x3); + if (!ret) + ret = reg_write(client, MT9V022_ANALOG_GAIN, 16); + if (!ret) + ret = reg_write(client, MT9V022_TOTAL_SHUTTER_WIDTH, 480); + if (!ret) + ret = reg_write(client, mt9v022->reg->max_total_shutter_width, 480); + if (!ret) + /* default - auto */ + ret = reg_clear(client, MT9V022_BLACK_LEVEL_CALIB_CTRL, 1); + if (!ret) + ret = reg_write(client, MT9V022_DIGITAL_TEST_PATTERN, 0); + if (!ret) + return v4l2_ctrl_handler_setup(&mt9v022->hdl); + + return ret; +} + +static int mt9v022_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9v022 *mt9v022 = to_mt9v022(client); + + if (enable) + /* Switch to master "normal" mode */ + mt9v022->chip_control &= ~0x10; + else + /* Switch to snapshot mode */ + mt9v022->chip_control |= 0x10; + + if (reg_write(client, MT9V022_CHIP_CONTROL, mt9v022->chip_control) < 0) + return -EIO; + return 0; +} + +static int mt9v022_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9v022 *mt9v022 = to_mt9v022(client); + struct v4l2_rect rect = a->c; + int ret; + + /* Bayer format - even size lengths */ + if (mt9v022->fmts == mt9v022_colour_fmts) { + rect.width = ALIGN(rect.width, 2); + rect.height = ALIGN(rect.height, 2); + /* Let the user play with the starting pixel */ + } + + soc_camera_limit_side(&rect.left, &rect.width, + MT9V022_COLUMN_SKIP, MT9V022_MIN_WIDTH, MT9V022_MAX_WIDTH); + + soc_camera_limit_side(&rect.top, &rect.height, + MT9V022_ROW_SKIP, MT9V022_MIN_HEIGHT, MT9V022_MAX_HEIGHT); + + /* Like in example app. Contradicts the datasheet though */ + ret = reg_read(client, MT9V022_AEC_AGC_ENABLE); + if (ret >= 0) { + if (ret & 1) /* Autoexposure */ + ret = reg_write(client, mt9v022->reg->max_total_shutter_width, + rect.height + mt9v022->y_skip_top + 43); + else + ret = reg_write(client, MT9V022_TOTAL_SHUTTER_WIDTH, + rect.height + mt9v022->y_skip_top + 43); + } + /* Setup frame format: defaults apart from width and height */ + if (!ret) + ret = reg_write(client, MT9V022_COLUMN_START, rect.left); + if (!ret) + ret = reg_write(client, MT9V022_ROW_START, rect.top); + if (!ret) + /* + * Default 94, Phytec driver says: + * "width + horizontal blank >= 660" + */ + ret = reg_write(client, MT9V022_HORIZONTAL_BLANKING, + rect.width > 660 - 43 ? 43 : + 660 - rect.width); + if (!ret) + ret = reg_write(client, MT9V022_VERTICAL_BLANKING, 45); + if (!ret) + ret = reg_write(client, MT9V022_WINDOW_WIDTH, rect.width); + if (!ret) + ret = reg_write(client, MT9V022_WINDOW_HEIGHT, + rect.height + mt9v022->y_skip_top); + + if (ret < 0) + return ret; + + dev_dbg(&client->dev, "Frame %dx%d pixel\n", rect.width, rect.height); + + mt9v022->rect = rect; + + return 0; +} + +static int mt9v022_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9v022 *mt9v022 = to_mt9v022(client); + + a->c = mt9v022->rect; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int mt9v022_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = MT9V022_COLUMN_SKIP; + a->bounds.top = MT9V022_ROW_SKIP; + a->bounds.width = MT9V022_MAX_WIDTH; + a->bounds.height = MT9V022_MAX_HEIGHT; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int mt9v022_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9v022 *mt9v022 = to_mt9v022(client); + + mf->width = mt9v022->rect.width; + mf->height = mt9v022->rect.height; + mf->code = mt9v022->fmt->code; + mf->colorspace = mt9v022->fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mt9v022_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9v022 *mt9v022 = to_mt9v022(client); + struct v4l2_crop a = { + .c = { + .left = mt9v022->rect.left, + .top = mt9v022->rect.top, + .width = mf->width, + .height = mf->height, + }, + }; + int ret; + + /* + * The caller provides a supported format, as verified per call to + * .try_mbus_fmt(), datawidth is from our supported format list + */ + switch (mf->code) { + case V4L2_MBUS_FMT_Y8_1X8: + case V4L2_MBUS_FMT_Y10_1X10: + if (mt9v022->model != V4L2_IDENT_MT9V022IX7ATM) + return -EINVAL; + break; + case V4L2_MBUS_FMT_SBGGR8_1X8: + case V4L2_MBUS_FMT_SBGGR10_1X10: + if (mt9v022->model != V4L2_IDENT_MT9V022IX7ATC) + return -EINVAL; + break; + default: + return -EINVAL; + } + + /* No support for scaling on this camera, just crop. */ + ret = mt9v022_s_crop(sd, &a); + if (!ret) { + mf->width = mt9v022->rect.width; + mf->height = mt9v022->rect.height; + mt9v022->fmt = mt9v022_find_datafmt(mf->code, + mt9v022->fmts, mt9v022->num_fmts); + mf->colorspace = mt9v022->fmt->colorspace; + } + + return ret; +} + +static int mt9v022_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9v022 *mt9v022 = to_mt9v022(client); + const struct mt9v022_datafmt *fmt; + int align = mf->code == V4L2_MBUS_FMT_SBGGR8_1X8 || + mf->code == V4L2_MBUS_FMT_SBGGR10_1X10; + + v4l_bound_align_image(&mf->width, MT9V022_MIN_WIDTH, + MT9V022_MAX_WIDTH, align, + &mf->height, MT9V022_MIN_HEIGHT + mt9v022->y_skip_top, + MT9V022_MAX_HEIGHT + mt9v022->y_skip_top, align, 0); + + fmt = mt9v022_find_datafmt(mf->code, mt9v022->fmts, + mt9v022->num_fmts); + if (!fmt) { + fmt = mt9v022->fmt; + mf->code = fmt->code; + } + + mf->colorspace = fmt->colorspace; + + return 0; +} + +static int mt9v022_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9v022 *mt9v022 = to_mt9v022(client); + + if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + if (id->match.addr != client->addr) + return -ENODEV; + + id->ident = mt9v022->model; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9v022_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + reg->size = 2; + reg->val = reg_read(client, reg->reg); + + if (reg->val > 0xffff) + return -EIO; + + return 0; +} + +static int mt9v022_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + if (reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int mt9v022_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +static int mt9v022_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9v022 *mt9v022 = container_of(ctrl->handler, + struct mt9v022, hdl); + struct v4l2_subdev *sd = &mt9v022->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_ctrl *gain = mt9v022->gain; + struct v4l2_ctrl *exp = mt9v022->exposure; + unsigned long range; + int data; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + data = reg_read(client, MT9V022_ANALOG_GAIN); + if (data < 0) + return -EIO; + + range = gain->maximum - gain->minimum; + gain->val = ((data - 16) * range + 24) / 48 + gain->minimum; + return 0; + case V4L2_CID_EXPOSURE_AUTO: + data = reg_read(client, MT9V022_TOTAL_SHUTTER_WIDTH); + if (data < 0) + return -EIO; + + range = exp->maximum - exp->minimum; + exp->val = ((data - 1) * range + 239) / 479 + exp->minimum; + return 0; + } + return -EINVAL; +} + +static int mt9v022_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9v022 *mt9v022 = container_of(ctrl->handler, + struct mt9v022, hdl); + struct v4l2_subdev *sd = &mt9v022->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int data; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->val) + data = reg_set(client, MT9V022_READ_MODE, 0x10); + else + data = reg_clear(client, MT9V022_READ_MODE, 0x10); + if (data < 0) + return -EIO; + return 0; + case V4L2_CID_HFLIP: + if (ctrl->val) + data = reg_set(client, MT9V022_READ_MODE, 0x20); + else + data = reg_clear(client, MT9V022_READ_MODE, 0x20); + if (data < 0) + return -EIO; + return 0; + case V4L2_CID_AUTOGAIN: + if (ctrl->val) { + if (reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x2) < 0) + return -EIO; + } else { + struct v4l2_ctrl *gain = mt9v022->gain; + /* mt9v022 has minimum == default */ + unsigned long range = gain->maximum - gain->minimum; + /* Valid values 16 to 64, 32 to 64 must be even. */ + unsigned long gain_val = ((gain->val - gain->minimum) * + 48 + range / 2) / range + 16; + + if (gain_val >= 32) + gain_val &= ~1; + + /* + * The user wants to set gain manually, hope, she + * knows, what she's doing... Switch AGC off. + */ + if (reg_clear(client, MT9V022_AEC_AGC_ENABLE, 0x2) < 0) + return -EIO; + + dev_dbg(&client->dev, "Setting gain from %d to %lu\n", + reg_read(client, MT9V022_ANALOG_GAIN), gain_val); + if (reg_write(client, MT9V022_ANALOG_GAIN, gain_val) < 0) + return -EIO; + } + return 0; + case V4L2_CID_EXPOSURE_AUTO: + if (ctrl->val == V4L2_EXPOSURE_AUTO) { + data = reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x1); + } else { + struct v4l2_ctrl *exp = mt9v022->exposure; + unsigned long range = exp->maximum - exp->minimum; + unsigned long shutter = ((exp->val - exp->minimum) * + 479 + range / 2) / range + 1; + + /* + * The user wants to set shutter width manually, hope, + * she knows, what she's doing... Switch AEC off. + */ + data = reg_clear(client, MT9V022_AEC_AGC_ENABLE, 0x1); + if (data < 0) + return -EIO; + dev_dbg(&client->dev, "Shutter width from %d to %lu\n", + reg_read(client, MT9V022_TOTAL_SHUTTER_WIDTH), + shutter); + if (reg_write(client, MT9V022_TOTAL_SHUTTER_WIDTH, + shutter) < 0) + return -EIO; + } + return 0; + } + return -EINVAL; +} + +/* + * Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one + */ +static int mt9v022_video_probe(struct i2c_client *client) +{ + struct mt9v022 *mt9v022 = to_mt9v022(client); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + s32 data; + int ret; + unsigned long flags; + + ret = mt9v022_s_power(&mt9v022->subdev, 1); + if (ret < 0) + return ret; + + /* Read out the chip version register */ + data = reg_read(client, MT9V022_CHIP_VERSION); + + /* must be 0x1311, 0x1313 or 0x1324 */ + if (data != 0x1311 && data != 0x1313 && data != 0x1324) { + ret = -ENODEV; + dev_info(&client->dev, "No MT9V022 found, ID register 0x%x\n", + data); + goto ei2c; + } + + mt9v022->reg = is_mt9v024(data) ? &mt9v024_register : + &mt9v022_register; + + /* Soft reset */ + ret = reg_write(client, MT9V022_RESET, 1); + if (ret < 0) + goto ei2c; + /* 15 clock cycles */ + udelay(200); + if (reg_read(client, MT9V022_RESET)) { + dev_err(&client->dev, "Resetting MT9V022 failed!\n"); + if (ret > 0) + ret = -EIO; + goto ei2c; + } + + /* Set monochrome or colour sensor type */ + if (sensor_type && (!strcmp("colour", sensor_type) || + !strcmp("color", sensor_type))) { + ret = reg_write(client, MT9V022_PIXEL_OPERATION_MODE, 4 | 0x11); + mt9v022->model = V4L2_IDENT_MT9V022IX7ATC; + mt9v022->fmts = mt9v022_colour_fmts; + } else { + ret = reg_write(client, MT9V022_PIXEL_OPERATION_MODE, 0x11); + mt9v022->model = V4L2_IDENT_MT9V022IX7ATM; + mt9v022->fmts = mt9v022_monochrome_fmts; + } + + if (ret < 0) + goto ei2c; + + mt9v022->num_fmts = 0; + + /* + * This is a 10bit sensor, so by default we only allow 10bit. + * The platform may support different bus widths due to + * different routing of the data lines. + */ + if (icl->query_bus_param) + flags = icl->query_bus_param(icl); + else + flags = SOCAM_DATAWIDTH_10; + + if (flags & SOCAM_DATAWIDTH_10) + mt9v022->num_fmts++; + else + mt9v022->fmts++; + + if (flags & SOCAM_DATAWIDTH_8) + mt9v022->num_fmts++; + + mt9v022->fmt = &mt9v022->fmts[0]; + + dev_info(&client->dev, "Detected a MT9V022 chip ID %x, %s sensor\n", + data, mt9v022->model == V4L2_IDENT_MT9V022IX7ATM ? + "monochrome" : "colour"); + + ret = mt9v022_init(client); + if (ret < 0) + dev_err(&client->dev, "Failed to initialise the camera\n"); + +ei2c: + mt9v022_s_power(&mt9v022->subdev, 0); + return ret; +} + +static int mt9v022_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9v022 *mt9v022 = to_mt9v022(client); + + *lines = mt9v022->y_skip_top; + + return 0; +} + +static const struct v4l2_ctrl_ops mt9v022_ctrl_ops = { + .g_volatile_ctrl = mt9v022_g_volatile_ctrl, + .s_ctrl = mt9v022_s_ctrl, +}; + +static struct v4l2_subdev_core_ops mt9v022_subdev_core_ops = { + .g_chip_ident = mt9v022_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9v022_g_register, + .s_register = mt9v022_s_register, +#endif + .s_power = mt9v022_s_power, +}; + +static int mt9v022_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct mt9v022 *mt9v022 = to_mt9v022(client); + + if (index >= mt9v022->num_fmts) + return -EINVAL; + + *code = mt9v022->fmts[index].code; + return 0; +} + +static int mt9v022_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_MASTER | V4L2_MBUS_SLAVE | + V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_LOW | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_LOW | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static int mt9v022_s_mbus_config(struct v4l2_subdev *sd, + const struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct mt9v022 *mt9v022 = to_mt9v022(client); + unsigned long flags = soc_camera_apply_board_flags(icl, cfg); + unsigned int bps = soc_mbus_get_fmtdesc(mt9v022->fmt->code)->bits_per_sample; + int ret; + u16 pixclk = 0; + + if (icl->set_bus_param) { + ret = icl->set_bus_param(icl, 1 << (bps - 1)); + if (ret) + return ret; + } else if (bps != 10) { + /* + * Without board specific bus width settings we only support the + * sensors native bus width + */ + return -EINVAL; + } + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + pixclk |= 0x10; + + if (!(flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)) + pixclk |= 0x1; + + if (!(flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)) + pixclk |= 0x2; + + ret = reg_write(client, mt9v022->reg->pixclk_fv_lv, pixclk); + if (ret < 0) + return ret; + + if (!(flags & V4L2_MBUS_MASTER)) + mt9v022->chip_control &= ~0x8; + + ret = reg_write(client, MT9V022_CHIP_CONTROL, mt9v022->chip_control); + if (ret < 0) + return ret; + + dev_dbg(&client->dev, "Calculated pixclk 0x%x, chip control 0x%x\n", + pixclk, mt9v022->chip_control); + + return 0; +} + +static struct v4l2_subdev_video_ops mt9v022_subdev_video_ops = { + .s_stream = mt9v022_s_stream, + .s_mbus_fmt = mt9v022_s_fmt, + .g_mbus_fmt = mt9v022_g_fmt, + .try_mbus_fmt = mt9v022_try_fmt, + .s_crop = mt9v022_s_crop, + .g_crop = mt9v022_g_crop, + .cropcap = mt9v022_cropcap, + .enum_mbus_fmt = mt9v022_enum_fmt, + .g_mbus_config = mt9v022_g_mbus_config, + .s_mbus_config = mt9v022_s_mbus_config, +}; + +static struct v4l2_subdev_sensor_ops mt9v022_subdev_sensor_ops = { + .g_skip_top_lines = mt9v022_g_skip_top_lines, +}; + +static struct v4l2_subdev_ops mt9v022_subdev_ops = { + .core = &mt9v022_subdev_core_ops, + .video = &mt9v022_subdev_video_ops, + .sensor = &mt9v022_subdev_sensor_ops, +}; + +static int mt9v022_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9v022 *mt9v022; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret; + + if (!icl) { + dev_err(&client->dev, "MT9V022 driver needs platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9v022 = kzalloc(sizeof(struct mt9v022), GFP_KERNEL); + if (!mt9v022) + return -ENOMEM; + + v4l2_i2c_subdev_init(&mt9v022->subdev, client, &mt9v022_subdev_ops); + v4l2_ctrl_handler_init(&mt9v022->hdl, 6); + v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + mt9v022->autogain = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + mt9v022->gain = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops, + V4L2_CID_GAIN, 0, 127, 1, 64); + + /* + * Simulated autoexposure. If enabled, we calculate shutter width + * ourselves in the driver based on vertical blanking and frame width + */ + mt9v022->autoexposure = v4l2_ctrl_new_std_menu(&mt9v022->hdl, + &mt9v022_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0, + V4L2_EXPOSURE_AUTO); + mt9v022->exposure = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 255, 1, 255); + + mt9v022->subdev.ctrl_handler = &mt9v022->hdl; + if (mt9v022->hdl.error) { + int err = mt9v022->hdl.error; + + kfree(mt9v022); + return err; + } + v4l2_ctrl_auto_cluster(2, &mt9v022->autoexposure, + V4L2_EXPOSURE_MANUAL, true); + v4l2_ctrl_auto_cluster(2, &mt9v022->autogain, 0, true); + + mt9v022->chip_control = MT9V022_CHIP_CONTROL_DEFAULT; + + /* + * MT9V022 _really_ corrupts the first read out line. + * TODO: verify on i.MX31 + */ + mt9v022->y_skip_top = 1; + mt9v022->rect.left = MT9V022_COLUMN_SKIP; + mt9v022->rect.top = MT9V022_ROW_SKIP; + mt9v022->rect.width = MT9V022_MAX_WIDTH; + mt9v022->rect.height = MT9V022_MAX_HEIGHT; + + ret = mt9v022_video_probe(client); + if (ret) { + v4l2_ctrl_handler_free(&mt9v022->hdl); + kfree(mt9v022); + } + + return ret; +} + +static int mt9v022_remove(struct i2c_client *client) +{ + struct mt9v022 *mt9v022 = to_mt9v022(client); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + v4l2_device_unregister_subdev(&mt9v022->subdev); + if (icl->free_bus) + icl->free_bus(icl); + v4l2_ctrl_handler_free(&mt9v022->hdl); + kfree(mt9v022); + + return 0; +} +static const struct i2c_device_id mt9v022_id[] = { + { "mt9v022", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9v022_id); + +static struct i2c_driver mt9v022_i2c_driver = { + .driver = { + .name = "mt9v022", + }, + .probe = mt9v022_probe, + .remove = mt9v022_remove, + .id_table = mt9v022_id, +}; + +module_i2c_driver(mt9v022_i2c_driver); + +MODULE_DESCRIPTION("Micron MT9V022 Camera driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/soc_camera/ov2640.c b/drivers/media/i2c/soc_camera/ov2640.c new file mode 100644 index 000000000000..78ac5744cb5d --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov2640.c @@ -0,0 +1,1122 @@ +/* + * ov2640 Camera Driver + * + * Copyright (C) 2010 Alberto Panizzo + * + * Based on ov772x, ov9640 drivers and previous non merged implementations. + * + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2006, OmniVision + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define VAL_SET(x, mask, rshift, lshift) \ + ((((x) >> rshift) & mask) << lshift) +/* + * DSP registers + * register offset for BANK_SEL == BANK_SEL_DSP + */ +#define R_BYPASS 0x05 /* Bypass DSP */ +#define R_BYPASS_DSP_BYPAS 0x01 /* Bypass DSP, sensor out directly */ +#define R_BYPASS_USE_DSP 0x00 /* Use the internal DSP */ +#define QS 0x44 /* Quantization Scale Factor */ +#define CTRLI 0x50 +#define CTRLI_LP_DP 0x80 +#define CTRLI_ROUND 0x40 +#define CTRLI_V_DIV_SET(x) VAL_SET(x, 0x3, 0, 3) +#define CTRLI_H_DIV_SET(x) VAL_SET(x, 0x3, 0, 0) +#define HSIZE 0x51 /* H_SIZE[7:0] (real/4) */ +#define HSIZE_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define VSIZE 0x52 /* V_SIZE[7:0] (real/4) */ +#define VSIZE_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define XOFFL 0x53 /* OFFSET_X[7:0] */ +#define XOFFL_SET(x) VAL_SET(x, 0xFF, 0, 0) +#define YOFFL 0x54 /* OFFSET_Y[7:0] */ +#define YOFFL_SET(x) VAL_SET(x, 0xFF, 0, 0) +#define VHYX 0x55 /* Offset and size completion */ +#define VHYX_VSIZE_SET(x) VAL_SET(x, 0x1, (8+2), 7) +#define VHYX_HSIZE_SET(x) VAL_SET(x, 0x1, (8+2), 3) +#define VHYX_YOFF_SET(x) VAL_SET(x, 0x3, 8, 4) +#define VHYX_XOFF_SET(x) VAL_SET(x, 0x3, 8, 0) +#define DPRP 0x56 +#define TEST 0x57 /* Horizontal size completion */ +#define TEST_HSIZE_SET(x) VAL_SET(x, 0x1, (9+2), 7) +#define ZMOW 0x5A /* Zoom: Out Width OUTW[7:0] (real/4) */ +#define ZMOW_OUTW_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define ZMOH 0x5B /* Zoom: Out Height OUTH[7:0] (real/4) */ +#define ZMOH_OUTH_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define ZMHH 0x5C /* Zoom: Speed and H&W completion */ +#define ZMHH_ZSPEED_SET(x) VAL_SET(x, 0x0F, 0, 4) +#define ZMHH_OUTH_SET(x) VAL_SET(x, 0x1, (8+2), 2) +#define ZMHH_OUTW_SET(x) VAL_SET(x, 0x3, (8+2), 0) +#define BPADDR 0x7C /* SDE Indirect Register Access: Address */ +#define BPDATA 0x7D /* SDE Indirect Register Access: Data */ +#define CTRL2 0x86 /* DSP Module enable 2 */ +#define CTRL2_DCW_EN 0x20 +#define CTRL2_SDE_EN 0x10 +#define CTRL2_UV_ADJ_EN 0x08 +#define CTRL2_UV_AVG_EN 0x04 +#define CTRL2_CMX_EN 0x01 +#define CTRL3 0x87 /* DSP Module enable 3 */ +#define CTRL3_BPC_EN 0x80 +#define CTRL3_WPC_EN 0x40 +#define SIZEL 0x8C /* Image Size Completion */ +#define SIZEL_HSIZE8_11_SET(x) VAL_SET(x, 0x1, 11, 6) +#define SIZEL_HSIZE8_SET(x) VAL_SET(x, 0x7, 0, 3) +#define SIZEL_VSIZE8_SET(x) VAL_SET(x, 0x7, 0, 0) +#define HSIZE8 0xC0 /* Image Horizontal Size HSIZE[10:3] */ +#define HSIZE8_SET(x) VAL_SET(x, 0xFF, 3, 0) +#define VSIZE8 0xC1 /* Image Vertical Size VSIZE[10:3] */ +#define VSIZE8_SET(x) VAL_SET(x, 0xFF, 3, 0) +#define CTRL0 0xC2 /* DSP Module enable 0 */ +#define CTRL0_AEC_EN 0x80 +#define CTRL0_AEC_SEL 0x40 +#define CTRL0_STAT_SEL 0x20 +#define CTRL0_VFIRST 0x10 +#define CTRL0_YUV422 0x08 +#define CTRL0_YUV_EN 0x04 +#define CTRL0_RGB_EN 0x02 +#define CTRL0_RAW_EN 0x01 +#define CTRL1 0xC3 /* DSP Module enable 1 */ +#define CTRL1_CIP 0x80 +#define CTRL1_DMY 0x40 +#define CTRL1_RAW_GMA 0x20 +#define CTRL1_DG 0x10 +#define CTRL1_AWB 0x08 +#define CTRL1_AWB_GAIN 0x04 +#define CTRL1_LENC 0x02 +#define CTRL1_PRE 0x01 +#define R_DVP_SP 0xD3 /* DVP output speed control */ +#define R_DVP_SP_AUTO_MODE 0x80 +#define R_DVP_SP_DVP_MASK 0x3F /* DVP PCLK = sysclk (48)/[6:0] (YUV0); + * = sysclk (48)/(2*[6:0]) (RAW);*/ +#define IMAGE_MODE 0xDA /* Image Output Format Select */ +#define IMAGE_MODE_Y8_DVP_EN 0x40 +#define IMAGE_MODE_JPEG_EN 0x10 +#define IMAGE_MODE_YUV422 0x00 +#define IMAGE_MODE_RAW10 0x04 /* (DVP) */ +#define IMAGE_MODE_RGB565 0x08 +#define IMAGE_MODE_HREF_VSYNC 0x02 /* HREF timing select in DVP JPEG output + * mode (0 for HREF is same as sensor) */ +#define IMAGE_MODE_LBYTE_FIRST 0x01 /* Byte swap enable for DVP + * 1: Low byte first UYVY (C2[4] =0) + * VYUY (C2[4] =1) + * 0: High byte first YUYV (C2[4]=0) + * YVYU (C2[4] = 1) */ +#define RESET 0xE0 /* Reset */ +#define RESET_MICROC 0x40 +#define RESET_SCCB 0x20 +#define RESET_JPEG 0x10 +#define RESET_DVP 0x04 +#define RESET_IPU 0x02 +#define RESET_CIF 0x01 +#define REGED 0xED /* Register ED */ +#define REGED_CLK_OUT_DIS 0x10 +#define MS_SP 0xF0 /* SCCB Master Speed */ +#define SS_ID 0xF7 /* SCCB Slave ID */ +#define SS_CTRL 0xF8 /* SCCB Slave Control */ +#define SS_CTRL_ADD_AUTO_INC 0x20 +#define SS_CTRL_EN 0x08 +#define SS_CTRL_DELAY_CLK 0x04 +#define SS_CTRL_ACC_EN 0x02 +#define SS_CTRL_SEN_PASS_THR 0x01 +#define MC_BIST 0xF9 /* Microcontroller misc register */ +#define MC_BIST_RESET 0x80 /* Microcontroller Reset */ +#define MC_BIST_BOOT_ROM_SEL 0x40 +#define MC_BIST_12KB_SEL 0x20 +#define MC_BIST_12KB_MASK 0x30 +#define MC_BIST_512KB_SEL 0x08 +#define MC_BIST_512KB_MASK 0x0C +#define MC_BIST_BUSY_BIT_R 0x02 +#define MC_BIST_MC_RES_ONE_SH_W 0x02 +#define MC_BIST_LAUNCH 0x01 +#define BANK_SEL 0xFF /* Register Bank Select */ +#define BANK_SEL_DSP 0x00 +#define BANK_SEL_SENS 0x01 + +/* + * Sensor registers + * register offset for BANK_SEL == BANK_SEL_SENS + */ +#define GAIN 0x00 /* AGC - Gain control gain setting */ +#define COM1 0x03 /* Common control 1 */ +#define COM1_1_DUMMY_FR 0x40 +#define COM1_3_DUMMY_FR 0x80 +#define COM1_7_DUMMY_FR 0xC0 +#define COM1_VWIN_LSB_UXGA 0x0F +#define COM1_VWIN_LSB_SVGA 0x0A +#define COM1_VWIN_LSB_CIF 0x06 +#define REG04 0x04 /* Register 04 */ +#define REG04_DEF 0x20 /* Always set */ +#define REG04_HFLIP_IMG 0x80 /* Horizontal mirror image ON/OFF */ +#define REG04_VFLIP_IMG 0x40 /* Vertical flip image ON/OFF */ +#define REG04_VREF_EN 0x10 +#define REG04_HREF_EN 0x08 +#define REG04_AEC_SET(x) VAL_SET(x, 0x3, 0, 0) +#define REG08 0x08 /* Frame Exposure One-pin Control Pre-charge Row Num */ +#define COM2 0x09 /* Common control 2 */ +#define COM2_SOFT_SLEEP_MODE 0x10 /* Soft sleep mode */ + /* Output drive capability */ +#define COM2_OCAP_Nx_SET(N) (((N) - 1) & 0x03) /* N = [1x .. 4x] */ +#define PID 0x0A /* Product ID Number MSB */ +#define VER 0x0B /* Product ID Number LSB */ +#define COM3 0x0C /* Common control 3 */ +#define COM3_BAND_50H 0x04 /* 0 For Banding at 60H */ +#define COM3_BAND_AUTO 0x02 /* Auto Banding */ +#define COM3_SING_FR_SNAPSH 0x01 /* 0 For enable live video output after the + * snapshot sequence*/ +#define AEC 0x10 /* AEC[9:2] Exposure Value */ +#define CLKRC 0x11 /* Internal clock */ +#define CLKRC_EN 0x80 +#define CLKRC_DIV_SET(x) (((x) - 1) & 0x1F) /* CLK = XVCLK/(x) */ +#define COM7 0x12 /* Common control 7 */ +#define COM7_SRST 0x80 /* Initiates system reset. All registers are + * set to factory default values after which + * the chip resumes normal operation */ +#define COM7_RES_UXGA 0x00 /* Resolution selectors for UXGA */ +#define COM7_RES_SVGA 0x40 /* SVGA */ +#define COM7_RES_CIF 0x20 /* CIF */ +#define COM7_ZOOM_EN 0x04 /* Enable Zoom mode */ +#define COM7_COLOR_BAR_TEST 0x02 /* Enable Color Bar Test Pattern */ +#define COM8 0x13 /* Common control 8 */ +#define COM8_DEF 0xC0 /* Banding filter ON/OFF */ +#define COM8_BNDF_EN 0x20 /* Banding filter ON/OFF */ +#define COM8_AGC_EN 0x04 /* AGC Auto/Manual control selection */ +#define COM8_AEC_EN 0x01 /* Auto/Manual Exposure control */ +#define COM9 0x14 /* Common control 9 + * Automatic gain ceiling - maximum AGC value [7:5]*/ +#define COM9_AGC_GAIN_2x 0x00 /* 000 : 2x */ +#define COM9_AGC_GAIN_4x 0x20 /* 001 : 4x */ +#define COM9_AGC_GAIN_8x 0x40 /* 010 : 8x */ +#define COM9_AGC_GAIN_16x 0x60 /* 011 : 16x */ +#define COM9_AGC_GAIN_32x 0x80 /* 100 : 32x */ +#define COM9_AGC_GAIN_64x 0xA0 /* 101 : 64x */ +#define COM9_AGC_GAIN_128x 0xC0 /* 110 : 128x */ +#define COM10 0x15 /* Common control 10 */ +#define COM10_PCLK_HREF 0x20 /* PCLK output qualified by HREF */ +#define COM10_PCLK_RISE 0x10 /* Data is updated at the rising edge of + * PCLK (user can latch data at the next + * falling edge of PCLK). + * 0 otherwise. */ +#define COM10_HREF_INV 0x08 /* Invert HREF polarity: + * HREF negative for valid data*/ +#define COM10_VSINC_INV 0x02 /* Invert VSYNC polarity */ +#define HSTART 0x17 /* Horizontal Window start MSB 8 bit */ +#define HEND 0x18 /* Horizontal Window end MSB 8 bit */ +#define VSTART 0x19 /* Vertical Window start MSB 8 bit */ +#define VEND 0x1A /* Vertical Window end MSB 8 bit */ +#define MIDH 0x1C /* Manufacturer ID byte - high */ +#define MIDL 0x1D /* Manufacturer ID byte - low */ +#define AEW 0x24 /* AGC/AEC - Stable operating region (upper limit) */ +#define AEB 0x25 /* AGC/AEC - Stable operating region (lower limit) */ +#define VV 0x26 /* AGC/AEC Fast mode operating region */ +#define VV_HIGH_TH_SET(x) VAL_SET(x, 0xF, 0, 4) +#define VV_LOW_TH_SET(x) VAL_SET(x, 0xF, 0, 0) +#define REG2A 0x2A /* Dummy pixel insert MSB */ +#define FRARL 0x2B /* Dummy pixel insert LSB */ +#define ADDVFL 0x2D /* LSB of insert dummy lines in Vertical direction */ +#define ADDVFH 0x2E /* MSB of insert dummy lines in Vertical direction */ +#define YAVG 0x2F /* Y/G Channel Average value */ +#define REG32 0x32 /* Common Control 32 */ +#define REG32_PCLK_DIV_2 0x80 /* PCLK freq divided by 2 */ +#define REG32_PCLK_DIV_4 0xC0 /* PCLK freq divided by 4 */ +#define ARCOM2 0x34 /* Zoom: Horizontal start point */ +#define REG45 0x45 /* Register 45 */ +#define FLL 0x46 /* Frame Length Adjustment LSBs */ +#define FLH 0x47 /* Frame Length Adjustment MSBs */ +#define COM19 0x48 /* Zoom: Vertical start point */ +#define ZOOMS 0x49 /* Zoom: Vertical start point */ +#define COM22 0x4B /* Flash light control */ +#define COM25 0x4E /* For Banding operations */ +#define BD50 0x4F /* 50Hz Banding AEC 8 LSBs */ +#define BD60 0x50 /* 60Hz Banding AEC 8 LSBs */ +#define REG5D 0x5D /* AVGsel[7:0], 16-zone average weight option */ +#define REG5E 0x5E /* AVGsel[15:8], 16-zone average weight option */ +#define REG5F 0x5F /* AVGsel[23:16], 16-zone average weight option */ +#define REG60 0x60 /* AVGsel[31:24], 16-zone average weight option */ +#define HISTO_LOW 0x61 /* Histogram Algorithm Low Level */ +#define HISTO_HIGH 0x62 /* Histogram Algorithm High Level */ + +/* + * ID + */ +#define MANUFACTURER_ID 0x7FA2 +#define PID_OV2640 0x2642 +#define VERSION(pid, ver) ((pid << 8) | (ver & 0xFF)) + +/* + * Struct + */ +struct regval_list { + u8 reg_num; + u8 value; +}; + +/* Supported resolutions */ +enum ov2640_width { + W_QCIF = 176, + W_QVGA = 320, + W_CIF = 352, + W_VGA = 640, + W_SVGA = 800, + W_XGA = 1024, + W_SXGA = 1280, + W_UXGA = 1600, +}; + +enum ov2640_height { + H_QCIF = 144, + H_QVGA = 240, + H_CIF = 288, + H_VGA = 480, + H_SVGA = 600, + H_XGA = 768, + H_SXGA = 1024, + H_UXGA = 1200, +}; + +struct ov2640_win_size { + char *name; + enum ov2640_width width; + enum ov2640_height height; + const struct regval_list *regs; +}; + + +struct ov2640_priv { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + enum v4l2_mbus_pixelcode cfmt_code; + const struct ov2640_win_size *win; + int model; +}; + +/* + * Registers settings + */ + +#define ENDMARKER { 0xff, 0xff } + +static const struct regval_list ov2640_init_regs[] = { + { BANK_SEL, BANK_SEL_DSP }, + { 0x2c, 0xff }, + { 0x2e, 0xdf }, + { BANK_SEL, BANK_SEL_SENS }, + { 0x3c, 0x32 }, + { CLKRC, CLKRC_DIV_SET(1) }, + { COM2, COM2_OCAP_Nx_SET(3) }, + { REG04, REG04_DEF | REG04_HREF_EN }, + { COM8, COM8_DEF | COM8_BNDF_EN | COM8_AGC_EN | COM8_AEC_EN }, + { COM9, COM9_AGC_GAIN_8x | 0x08}, + { 0x2c, 0x0c }, + { 0x33, 0x78 }, + { 0x3a, 0x33 }, + { 0x3b, 0xfb }, + { 0x3e, 0x00 }, + { 0x43, 0x11 }, + { 0x16, 0x10 }, + { 0x39, 0x02 }, + { 0x35, 0x88 }, + { 0x22, 0x0a }, + { 0x37, 0x40 }, + { 0x23, 0x00 }, + { ARCOM2, 0xa0 }, + { 0x06, 0x02 }, + { 0x06, 0x88 }, + { 0x07, 0xc0 }, + { 0x0d, 0xb7 }, + { 0x0e, 0x01 }, + { 0x4c, 0x00 }, + { 0x4a, 0x81 }, + { 0x21, 0x99 }, + { AEW, 0x40 }, + { AEB, 0x38 }, + { VV, VV_HIGH_TH_SET(0x08) | VV_LOW_TH_SET(0x02) }, + { 0x5c, 0x00 }, + { 0x63, 0x00 }, + { FLL, 0x22 }, + { COM3, 0x38 | COM3_BAND_AUTO }, + { REG5D, 0x55 }, + { REG5E, 0x7d }, + { REG5F, 0x7d }, + { REG60, 0x55 }, + { HISTO_LOW, 0x70 }, + { HISTO_HIGH, 0x80 }, + { 0x7c, 0x05 }, + { 0x20, 0x80 }, + { 0x28, 0x30 }, + { 0x6c, 0x00 }, + { 0x6d, 0x80 }, + { 0x6e, 0x00 }, + { 0x70, 0x02 }, + { 0x71, 0x94 }, + { 0x73, 0xc1 }, + { 0x3d, 0x34 }, + { COM7, COM7_RES_UXGA | COM7_ZOOM_EN }, + { 0x5a, 0x57 }, + { BD50, 0xbb }, + { BD60, 0x9c }, + { BANK_SEL, BANK_SEL_DSP }, + { 0xe5, 0x7f }, + { MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL }, + { 0x41, 0x24 }, + { RESET, RESET_JPEG | RESET_DVP }, + { 0x76, 0xff }, + { 0x33, 0xa0 }, + { 0x42, 0x20 }, + { 0x43, 0x18 }, + { 0x4c, 0x00 }, + { CTRL3, CTRL3_BPC_EN | CTRL3_WPC_EN | 0x10 }, + { 0x88, 0x3f }, + { 0xd7, 0x03 }, + { 0xd9, 0x10 }, + { R_DVP_SP , R_DVP_SP_AUTO_MODE | 0x2 }, + { 0xc8, 0x08 }, + { 0xc9, 0x80 }, + { BPADDR, 0x00 }, + { BPDATA, 0x00 }, + { BPADDR, 0x03 }, + { BPDATA, 0x48 }, + { BPDATA, 0x48 }, + { BPADDR, 0x08 }, + { BPDATA, 0x20 }, + { BPDATA, 0x10 }, + { BPDATA, 0x0e }, + { 0x90, 0x00 }, + { 0x91, 0x0e }, + { 0x91, 0x1a }, + { 0x91, 0x31 }, + { 0x91, 0x5a }, + { 0x91, 0x69 }, + { 0x91, 0x75 }, + { 0x91, 0x7e }, + { 0x91, 0x88 }, + { 0x91, 0x8f }, + { 0x91, 0x96 }, + { 0x91, 0xa3 }, + { 0x91, 0xaf }, + { 0x91, 0xc4 }, + { 0x91, 0xd7 }, + { 0x91, 0xe8 }, + { 0x91, 0x20 }, + { 0x92, 0x00 }, + { 0x93, 0x06 }, + { 0x93, 0xe3 }, + { 0x93, 0x03 }, + { 0x93, 0x03 }, + { 0x93, 0x00 }, + { 0x93, 0x02 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x96, 0x00 }, + { 0x97, 0x08 }, + { 0x97, 0x19 }, + { 0x97, 0x02 }, + { 0x97, 0x0c }, + { 0x97, 0x24 }, + { 0x97, 0x30 }, + { 0x97, 0x28 }, + { 0x97, 0x26 }, + { 0x97, 0x02 }, + { 0x97, 0x98 }, + { 0x97, 0x80 }, + { 0x97, 0x00 }, + { 0x97, 0x00 }, + { 0xa4, 0x00 }, + { 0xa8, 0x00 }, + { 0xc5, 0x11 }, + { 0xc6, 0x51 }, + { 0xbf, 0x80 }, + { 0xc7, 0x10 }, + { 0xb6, 0x66 }, + { 0xb8, 0xA5 }, + { 0xb7, 0x64 }, + { 0xb9, 0x7C }, + { 0xb3, 0xaf }, + { 0xb4, 0x97 }, + { 0xb5, 0xFF }, + { 0xb0, 0xC5 }, + { 0xb1, 0x94 }, + { 0xb2, 0x0f }, + { 0xc4, 0x5c }, + { 0xa6, 0x00 }, + { 0xa7, 0x20 }, + { 0xa7, 0xd8 }, + { 0xa7, 0x1b }, + { 0xa7, 0x31 }, + { 0xa7, 0x00 }, + { 0xa7, 0x18 }, + { 0xa7, 0x20 }, + { 0xa7, 0xd8 }, + { 0xa7, 0x19 }, + { 0xa7, 0x31 }, + { 0xa7, 0x00 }, + { 0xa7, 0x18 }, + { 0xa7, 0x20 }, + { 0xa7, 0xd8 }, + { 0xa7, 0x19 }, + { 0xa7, 0x31 }, + { 0xa7, 0x00 }, + { 0xa7, 0x18 }, + { 0x7f, 0x00 }, + { 0xe5, 0x1f }, + { 0xe1, 0x77 }, + { 0xdd, 0x7f }, + { CTRL0, CTRL0_YUV422 | CTRL0_YUV_EN | CTRL0_RGB_EN }, + ENDMARKER, +}; + +/* + * Register settings for window size + * The preamble, setup the internal DSP to input an UXGA (1600x1200) image. + * Then the different zooming configurations will setup the output image size. + */ +static const struct regval_list ov2640_size_change_preamble_regs[] = { + { BANK_SEL, BANK_SEL_DSP }, + { RESET, RESET_DVP }, + { HSIZE8, HSIZE8_SET(W_UXGA) }, + { VSIZE8, VSIZE8_SET(H_UXGA) }, + { CTRL2, CTRL2_DCW_EN | CTRL2_SDE_EN | + CTRL2_UV_AVG_EN | CTRL2_CMX_EN | CTRL2_UV_ADJ_EN }, + { HSIZE, HSIZE_SET(W_UXGA) }, + { VSIZE, VSIZE_SET(H_UXGA) }, + { XOFFL, XOFFL_SET(0) }, + { YOFFL, YOFFL_SET(0) }, + { VHYX, VHYX_HSIZE_SET(W_UXGA) | VHYX_VSIZE_SET(H_UXGA) | + VHYX_XOFF_SET(0) | VHYX_YOFF_SET(0)}, + { TEST, TEST_HSIZE_SET(W_UXGA) }, + ENDMARKER, +}; + +#define PER_SIZE_REG_SEQ(x, y, v_div, h_div, pclk_div) \ + { CTRLI, CTRLI_LP_DP | CTRLI_V_DIV_SET(v_div) | \ + CTRLI_H_DIV_SET(h_div)}, \ + { ZMOW, ZMOW_OUTW_SET(x) }, \ + { ZMOH, ZMOH_OUTH_SET(y) }, \ + { ZMHH, ZMHH_OUTW_SET(x) | ZMHH_OUTH_SET(y) }, \ + { R_DVP_SP, pclk_div }, \ + { RESET, 0x00} + +static const struct regval_list ov2640_qcif_regs[] = { + PER_SIZE_REG_SEQ(W_QCIF, H_QCIF, 3, 3, 4), + ENDMARKER, +}; + +static const struct regval_list ov2640_qvga_regs[] = { + PER_SIZE_REG_SEQ(W_QVGA, H_QVGA, 2, 2, 4), + ENDMARKER, +}; + +static const struct regval_list ov2640_cif_regs[] = { + PER_SIZE_REG_SEQ(W_CIF, H_CIF, 2, 2, 8), + ENDMARKER, +}; + +static const struct regval_list ov2640_vga_regs[] = { + PER_SIZE_REG_SEQ(W_VGA, H_VGA, 0, 0, 2), + ENDMARKER, +}; + +static const struct regval_list ov2640_svga_regs[] = { + PER_SIZE_REG_SEQ(W_SVGA, H_SVGA, 1, 1, 2), + ENDMARKER, +}; + +static const struct regval_list ov2640_xga_regs[] = { + PER_SIZE_REG_SEQ(W_XGA, H_XGA, 0, 0, 2), + { CTRLI, 0x00}, + ENDMARKER, +}; + +static const struct regval_list ov2640_sxga_regs[] = { + PER_SIZE_REG_SEQ(W_SXGA, H_SXGA, 0, 0, 2), + { CTRLI, 0x00}, + { R_DVP_SP, 2 | R_DVP_SP_AUTO_MODE }, + ENDMARKER, +}; + +static const struct regval_list ov2640_uxga_regs[] = { + PER_SIZE_REG_SEQ(W_UXGA, H_UXGA, 0, 0, 0), + { CTRLI, 0x00}, + { R_DVP_SP, 0 | R_DVP_SP_AUTO_MODE }, + ENDMARKER, +}; + +#define OV2640_SIZE(n, w, h, r) \ + {.name = n, .width = w , .height = h, .regs = r } + +static const struct ov2640_win_size ov2640_supported_win_sizes[] = { + OV2640_SIZE("QCIF", W_QCIF, H_QCIF, ov2640_qcif_regs), + OV2640_SIZE("QVGA", W_QVGA, H_QVGA, ov2640_qvga_regs), + OV2640_SIZE("CIF", W_CIF, H_CIF, ov2640_cif_regs), + OV2640_SIZE("VGA", W_VGA, H_VGA, ov2640_vga_regs), + OV2640_SIZE("SVGA", W_SVGA, H_SVGA, ov2640_svga_regs), + OV2640_SIZE("XGA", W_XGA, H_XGA, ov2640_xga_regs), + OV2640_SIZE("SXGA", W_SXGA, H_SXGA, ov2640_sxga_regs), + OV2640_SIZE("UXGA", W_UXGA, H_UXGA, ov2640_uxga_regs), +}; + +/* + * Register settings for pixel formats + */ +static const struct regval_list ov2640_format_change_preamble_regs[] = { + { BANK_SEL, BANK_SEL_DSP }, + { R_BYPASS, R_BYPASS_USE_DSP }, + ENDMARKER, +}; + +static const struct regval_list ov2640_yuv422_regs[] = { + { IMAGE_MODE, IMAGE_MODE_LBYTE_FIRST | IMAGE_MODE_YUV422 }, + { 0xD7, 0x01 }, + { 0x33, 0xa0 }, + { 0xe1, 0x67 }, + { RESET, 0x00 }, + { R_BYPASS, R_BYPASS_USE_DSP }, + ENDMARKER, +}; + +static const struct regval_list ov2640_rgb565_regs[] = { + { IMAGE_MODE, IMAGE_MODE_LBYTE_FIRST | IMAGE_MODE_RGB565 }, + { 0xd7, 0x03 }, + { RESET, 0x00 }, + { R_BYPASS, R_BYPASS_USE_DSP }, + ENDMARKER, +}; + +static enum v4l2_mbus_pixelcode ov2640_codes[] = { + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_RGB565_2X8_LE, +}; + +/* + * General functions + */ +static struct ov2640_priv *to_ov2640(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ov2640_priv, + subdev); +} + +static int ov2640_write_array(struct i2c_client *client, + const struct regval_list *vals) +{ + int ret; + + while ((vals->reg_num != 0xff) || (vals->value != 0xff)) { + ret = i2c_smbus_write_byte_data(client, + vals->reg_num, vals->value); + dev_vdbg(&client->dev, "array: 0x%02x, 0x%02x", + vals->reg_num, vals->value); + + if (ret < 0) + return ret; + vals++; + } + return 0; +} + +static int ov2640_mask_set(struct i2c_client *client, + u8 reg, u8 mask, u8 set) +{ + s32 val = i2c_smbus_read_byte_data(client, reg); + if (val < 0) + return val; + + val &= ~mask; + val |= set & mask; + + dev_vdbg(&client->dev, "masks: 0x%02x, 0x%02x", reg, val); + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static int ov2640_reset(struct i2c_client *client) +{ + int ret; + const struct regval_list reset_seq[] = { + {BANK_SEL, BANK_SEL_SENS}, + {COM7, COM7_SRST}, + ENDMARKER, + }; + + ret = ov2640_write_array(client, reset_seq); + if (ret) + goto err; + + msleep(5); +err: + dev_dbg(&client->dev, "%s: (ret %d)", __func__, ret); + return ret; +} + +/* + * soc_camera_ops functions + */ +static int ov2640_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +static int ov2640_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = + &container_of(ctrl->handler, struct ov2640_priv, hdl)->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 val; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + val = ctrl->val ? REG04_VFLIP_IMG : 0x00; + return ov2640_mask_set(client, REG04, REG04_VFLIP_IMG, val); + case V4L2_CID_HFLIP: + val = ctrl->val ? REG04_HFLIP_IMG : 0x00; + return ov2640_mask_set(client, REG04, REG04_HFLIP_IMG, val); + } + + return -EINVAL; +} + +static int ov2640_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov2640_priv *priv = to_ov2640(client); + + id->ident = priv->model; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov2640_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + reg->size = 1; + if (reg->reg > 0xff) + return -EINVAL; + + ret = i2c_smbus_read_byte_data(client, reg->reg); + if (ret < 0) + return ret; + + reg->val = ret; + + return 0; +} + +static int ov2640_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0xff || + reg->val > 0xff) + return -EINVAL; + + return i2c_smbus_write_byte_data(client, reg->reg, reg->val); +} +#endif + +static int ov2640_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +/* Select the nearest higher resolution for capture */ +static const struct ov2640_win_size *ov2640_select_win(u32 *width, u32 *height) +{ + int i, default_size = ARRAY_SIZE(ov2640_supported_win_sizes) - 1; + + for (i = 0; i < ARRAY_SIZE(ov2640_supported_win_sizes); i++) { + if (ov2640_supported_win_sizes[i].width >= *width && + ov2640_supported_win_sizes[i].height >= *height) { + *width = ov2640_supported_win_sizes[i].width; + *height = ov2640_supported_win_sizes[i].height; + return &ov2640_supported_win_sizes[i]; + } + } + + *width = ov2640_supported_win_sizes[default_size].width; + *height = ov2640_supported_win_sizes[default_size].height; + return &ov2640_supported_win_sizes[default_size]; +} + +static int ov2640_set_params(struct i2c_client *client, u32 *width, u32 *height, + enum v4l2_mbus_pixelcode code) +{ + struct ov2640_priv *priv = to_ov2640(client); + const struct regval_list *selected_cfmt_regs; + int ret; + + /* select win */ + priv->win = ov2640_select_win(width, height); + + /* select format */ + priv->cfmt_code = 0; + switch (code) { + case V4L2_MBUS_FMT_RGB565_2X8_LE: + dev_dbg(&client->dev, "%s: Selected cfmt RGB565", __func__); + selected_cfmt_regs = ov2640_rgb565_regs; + break; + default: + case V4L2_MBUS_FMT_UYVY8_2X8: + dev_dbg(&client->dev, "%s: Selected cfmt YUV422", __func__); + selected_cfmt_regs = ov2640_yuv422_regs; + } + + /* reset hardware */ + ov2640_reset(client); + + /* initialize the sensor with default data */ + dev_dbg(&client->dev, "%s: Init default", __func__); + ret = ov2640_write_array(client, ov2640_init_regs); + if (ret < 0) + goto err; + + /* select preamble */ + dev_dbg(&client->dev, "%s: Set size to %s", __func__, priv->win->name); + ret = ov2640_write_array(client, ov2640_size_change_preamble_regs); + if (ret < 0) + goto err; + + /* set size win */ + ret = ov2640_write_array(client, priv->win->regs); + if (ret < 0) + goto err; + + /* cfmt preamble */ + dev_dbg(&client->dev, "%s: Set cfmt", __func__); + ret = ov2640_write_array(client, ov2640_format_change_preamble_regs); + if (ret < 0) + goto err; + + /* set cfmt */ + ret = ov2640_write_array(client, selected_cfmt_regs); + if (ret < 0) + goto err; + + priv->cfmt_code = code; + *width = priv->win->width; + *height = priv->win->height; + + return 0; + +err: + dev_err(&client->dev, "%s: Error %d", __func__, ret); + ov2640_reset(client); + priv->win = NULL; + + return ret; +} + +static int ov2640_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov2640_priv *priv = to_ov2640(client); + + if (!priv->win) { + u32 width = W_SVGA, height = H_SVGA; + priv->win = ov2640_select_win(&width, &height); + priv->cfmt_code = V4L2_MBUS_FMT_UYVY8_2X8; + } + + mf->width = priv->win->width; + mf->height = priv->win->height; + mf->code = priv->cfmt_code; + + switch (mf->code) { + case V4L2_MBUS_FMT_RGB565_2X8_LE: + mf->colorspace = V4L2_COLORSPACE_SRGB; + break; + default: + case V4L2_MBUS_FMT_UYVY8_2X8: + mf->colorspace = V4L2_COLORSPACE_JPEG; + } + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ov2640_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + + switch (mf->code) { + case V4L2_MBUS_FMT_RGB565_2X8_LE: + mf->colorspace = V4L2_COLORSPACE_SRGB; + break; + default: + mf->code = V4L2_MBUS_FMT_UYVY8_2X8; + case V4L2_MBUS_FMT_UYVY8_2X8: + mf->colorspace = V4L2_COLORSPACE_JPEG; + } + + ret = ov2640_set_params(client, &mf->width, &mf->height, mf->code); + + return ret; +} + +static int ov2640_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + const struct ov2640_win_size *win; + + /* + * select suitable win + */ + win = ov2640_select_win(&mf->width, &mf->height); + + mf->field = V4L2_FIELD_NONE; + + switch (mf->code) { + case V4L2_MBUS_FMT_RGB565_2X8_LE: + mf->colorspace = V4L2_COLORSPACE_SRGB; + break; + default: + mf->code = V4L2_MBUS_FMT_UYVY8_2X8; + case V4L2_MBUS_FMT_UYVY8_2X8: + mf->colorspace = V4L2_COLORSPACE_JPEG; + } + + return 0; +} + +static int ov2640_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(ov2640_codes)) + return -EINVAL; + + *code = ov2640_codes[index]; + return 0; +} + +static int ov2640_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + a->c.left = 0; + a->c.top = 0; + a->c.width = W_UXGA; + a->c.height = H_UXGA; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int ov2640_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = W_UXGA; + a->bounds.height = H_UXGA; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int ov2640_video_probe(struct i2c_client *client) +{ + struct ov2640_priv *priv = to_ov2640(client); + u8 pid, ver, midh, midl; + const char *devname; + int ret; + + ret = ov2640_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * check and show product ID and manufacturer ID + */ + i2c_smbus_write_byte_data(client, BANK_SEL, BANK_SEL_SENS); + pid = i2c_smbus_read_byte_data(client, PID); + ver = i2c_smbus_read_byte_data(client, VER); + midh = i2c_smbus_read_byte_data(client, MIDH); + midl = i2c_smbus_read_byte_data(client, MIDL); + + switch (VERSION(pid, ver)) { + case PID_OV2640: + devname = "ov2640"; + priv->model = V4L2_IDENT_OV2640; + break; + default: + dev_err(&client->dev, + "Product ID error %x:%x\n", pid, ver); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, + "%s Product ID %0x:%0x Manufacturer ID %x:%x\n", + devname, pid, ver, midh, midl); + + ret = v4l2_ctrl_handler_setup(&priv->hdl); + +done: + ov2640_s_power(&priv->subdev, 0); + return ret; +} + +static const struct v4l2_ctrl_ops ov2640_ctrl_ops = { + .s_ctrl = ov2640_s_ctrl, +}; + +static struct v4l2_subdev_core_ops ov2640_subdev_core_ops = { + .g_chip_ident = ov2640_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov2640_g_register, + .s_register = ov2640_s_register, +#endif + .s_power = ov2640_s_power, +}; + +static int ov2640_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static struct v4l2_subdev_video_ops ov2640_subdev_video_ops = { + .s_stream = ov2640_s_stream, + .g_mbus_fmt = ov2640_g_fmt, + .s_mbus_fmt = ov2640_s_fmt, + .try_mbus_fmt = ov2640_try_fmt, + .cropcap = ov2640_cropcap, + .g_crop = ov2640_g_crop, + .enum_mbus_fmt = ov2640_enum_fmt, + .g_mbus_config = ov2640_g_mbus_config, +}; + +static struct v4l2_subdev_ops ov2640_subdev_ops = { + .core = &ov2640_subdev_core_ops, + .video = &ov2640_subdev_video_ops, +}; + +/* + * i2c_driver functions + */ +static int ov2640_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov2640_priv *priv; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret; + + if (!icl) { + dev_err(&adapter->dev, + "OV2640: Missing platform_data for driver\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&adapter->dev, + "OV2640: I2C-Adapter doesn't support SMBUS\n"); + return -EIO; + } + + priv = kzalloc(sizeof(struct ov2640_priv), GFP_KERNEL); + if (!priv) { + dev_err(&adapter->dev, + "Failed to allocate memory for private data!\n"); + return -ENOMEM; + } + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov2640_subdev_ops); + v4l2_ctrl_handler_init(&priv->hdl, 2); + v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + priv->subdev.ctrl_handler = &priv->hdl; + if (priv->hdl.error) { + int err = priv->hdl.error; + + kfree(priv); + return err; + } + + ret = ov2640_video_probe(client); + if (ret) { + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + } else { + dev_info(&adapter->dev, "OV2640 Probed\n"); + } + + return ret; +} + +static int ov2640_remove(struct i2c_client *client) +{ + struct ov2640_priv *priv = to_ov2640(client); + + v4l2_device_unregister_subdev(&priv->subdev); + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + return 0; +} + +static const struct i2c_device_id ov2640_id[] = { + { "ov2640", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov2640_id); + +static struct i2c_driver ov2640_i2c_driver = { + .driver = { + .name = "ov2640", + }, + .probe = ov2640_probe, + .remove = ov2640_remove, + .id_table = ov2640_id, +}; + +module_i2c_driver(ov2640_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for Omni Vision 2640 sensor"); +MODULE_AUTHOR("Alberto Panizzo"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/soc_camera/ov5642.c b/drivers/media/i2c/soc_camera/ov5642.c new file mode 100644 index 000000000000..8577e0cfb7fe --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov5642.c @@ -0,0 +1,1088 @@ +/* + * Driver for OV5642 CMOS Image Sensor from Omnivision + * + * Copyright (C) 2011, Bastian Hecht + * + * Based on Sony IMX074 Camera Driver + * Copyright (C) 2010, Guennadi Liakhovetski + * + * Based on Omnivision OV7670 Camera Driver + * Copyright (C) 2006-7 Jonathan Corbet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* OV5642 registers */ +#define REG_CHIP_ID_HIGH 0x300a +#define REG_CHIP_ID_LOW 0x300b + +#define REG_WINDOW_START_X_HIGH 0x3800 +#define REG_WINDOW_START_X_LOW 0x3801 +#define REG_WINDOW_START_Y_HIGH 0x3802 +#define REG_WINDOW_START_Y_LOW 0x3803 +#define REG_WINDOW_WIDTH_HIGH 0x3804 +#define REG_WINDOW_WIDTH_LOW 0x3805 +#define REG_WINDOW_HEIGHT_HIGH 0x3806 +#define REG_WINDOW_HEIGHT_LOW 0x3807 +#define REG_OUT_WIDTH_HIGH 0x3808 +#define REG_OUT_WIDTH_LOW 0x3809 +#define REG_OUT_HEIGHT_HIGH 0x380a +#define REG_OUT_HEIGHT_LOW 0x380b +#define REG_OUT_TOTAL_WIDTH_HIGH 0x380c +#define REG_OUT_TOTAL_WIDTH_LOW 0x380d +#define REG_OUT_TOTAL_HEIGHT_HIGH 0x380e +#define REG_OUT_TOTAL_HEIGHT_LOW 0x380f +#define REG_OUTPUT_FORMAT 0x4300 +#define REG_ISP_CTRL_01 0x5001 +#define REG_AVG_WINDOW_END_X_HIGH 0x5682 +#define REG_AVG_WINDOW_END_X_LOW 0x5683 +#define REG_AVG_WINDOW_END_Y_HIGH 0x5686 +#define REG_AVG_WINDOW_END_Y_LOW 0x5687 + +/* active pixel array size */ +#define OV5642_SENSOR_SIZE_X 2592 +#define OV5642_SENSOR_SIZE_Y 1944 + +/* + * About OV5642 resolution, cropping and binning: + * This sensor supports it all, at least in the feature description. + * Unfortunately, no combination of appropriate registers settings could make + * the chip work the intended way. As it works with predefined register lists, + * some undocumented registers are presumably changed there to achieve their + * goals. + * This driver currently only works for resolutions up to 720 lines with a + * 1:1 scale. Hopefully these restrictions will be removed in the future. + */ +#define OV5642_MAX_WIDTH OV5642_SENSOR_SIZE_X +#define OV5642_MAX_HEIGHT 720 + +/* default sizes */ +#define OV5642_DEFAULT_WIDTH 1280 +#define OV5642_DEFAULT_HEIGHT OV5642_MAX_HEIGHT + +/* minimum extra blanking */ +#define BLANKING_EXTRA_WIDTH 500 +#define BLANKING_EXTRA_HEIGHT 20 + +/* + * the sensor's autoexposure is buggy when setting total_height low. + * It tries to expose longer than 1 frame period without taking care of it + * and this leads to weird output. So we set 1000 lines as minimum. + */ +#define BLANKING_MIN_HEIGHT 1000 + +struct regval_list { + u16 reg_num; + u8 value; +}; + +static struct regval_list ov5642_default_regs_init[] = { + { 0x3103, 0x93 }, + { 0x3008, 0x82 }, + { 0x3017, 0x7f }, + { 0x3018, 0xfc }, + { 0x3810, 0xc2 }, + { 0x3615, 0xf0 }, + { 0x3000, 0x0 }, + { 0x3001, 0x0 }, + { 0x3002, 0x0 }, + { 0x3003, 0x0 }, + { 0x3004, 0xff }, + { 0x3030, 0x2b }, + { 0x3011, 0x8 }, + { 0x3010, 0x10 }, + { 0x3604, 0x60 }, + { 0x3622, 0x60 }, + { 0x3621, 0x9 }, + { 0x3709, 0x0 }, + { 0x4000, 0x21 }, + { 0x401d, 0x22 }, + { 0x3600, 0x54 }, + { 0x3605, 0x4 }, + { 0x3606, 0x3f }, + { 0x3c01, 0x80 }, + { 0x300d, 0x22 }, + { 0x3623, 0x22 }, + { 0x5000, 0x4f }, + { 0x5020, 0x4 }, + { 0x5181, 0x79 }, + { 0x5182, 0x0 }, + { 0x5185, 0x22 }, + { 0x5197, 0x1 }, + { 0x5500, 0xa }, + { 0x5504, 0x0 }, + { 0x5505, 0x7f }, + { 0x5080, 0x8 }, + { 0x300e, 0x18 }, + { 0x4610, 0x0 }, + { 0x471d, 0x5 }, + { 0x4708, 0x6 }, + { 0x370c, 0xa0 }, + { 0x5687, 0x94 }, + { 0x501f, 0x0 }, + { 0x5000, 0x4f }, + { 0x5001, 0xcf }, + { 0x4300, 0x30 }, + { 0x4300, 0x30 }, + { 0x460b, 0x35 }, + { 0x471d, 0x0 }, + { 0x3002, 0xc }, + { 0x3002, 0x0 }, + { 0x4713, 0x3 }, + { 0x471c, 0x50 }, + { 0x4721, 0x2 }, + { 0x4402, 0x90 }, + { 0x460c, 0x22 }, + { 0x3815, 0x44 }, + { 0x3503, 0x7 }, + { 0x3501, 0x73 }, + { 0x3502, 0x80 }, + { 0x350b, 0x0 }, + { 0x3818, 0xc8 }, + { 0x3824, 0x11 }, + { 0x3a00, 0x78 }, + { 0x3a1a, 0x4 }, + { 0x3a13, 0x30 }, + { 0x3a18, 0x0 }, + { 0x3a19, 0x7c }, + { 0x3a08, 0x12 }, + { 0x3a09, 0xc0 }, + { 0x3a0a, 0xf }, + { 0x3a0b, 0xa0 }, + { 0x350c, 0x7 }, + { 0x350d, 0xd0 }, + { 0x3a0d, 0x8 }, + { 0x3a0e, 0x6 }, + { 0x3500, 0x0 }, + { 0x3501, 0x0 }, + { 0x3502, 0x0 }, + { 0x350a, 0x0 }, + { 0x350b, 0x0 }, + { 0x3503, 0x0 }, + { 0x3a0f, 0x3c }, + { 0x3a10, 0x32 }, + { 0x3a1b, 0x3c }, + { 0x3a1e, 0x32 }, + { 0x3a11, 0x80 }, + { 0x3a1f, 0x20 }, + { 0x3030, 0x2b }, + { 0x3a02, 0x0 }, + { 0x3a03, 0x7d }, + { 0x3a04, 0x0 }, + { 0x3a14, 0x0 }, + { 0x3a15, 0x7d }, + { 0x3a16, 0x0 }, + { 0x3a00, 0x78 }, + { 0x3a08, 0x9 }, + { 0x3a09, 0x60 }, + { 0x3a0a, 0x7 }, + { 0x3a0b, 0xd0 }, + { 0x3a0d, 0x10 }, + { 0x3a0e, 0xd }, + { 0x4407, 0x4 }, + { 0x5193, 0x70 }, + { 0x589b, 0x0 }, + { 0x589a, 0xc0 }, + { 0x401e, 0x20 }, + { 0x4001, 0x42 }, + { 0x401c, 0x6 }, + { 0x3825, 0xac }, + { 0x3827, 0xc }, + { 0x528a, 0x1 }, + { 0x528b, 0x4 }, + { 0x528c, 0x8 }, + { 0x528d, 0x10 }, + { 0x528e, 0x20 }, + { 0x528f, 0x28 }, + { 0x5290, 0x30 }, + { 0x5292, 0x0 }, + { 0x5293, 0x1 }, + { 0x5294, 0x0 }, + { 0x5295, 0x4 }, + { 0x5296, 0x0 }, + { 0x5297, 0x8 }, + { 0x5298, 0x0 }, + { 0x5299, 0x10 }, + { 0x529a, 0x0 }, + { 0x529b, 0x20 }, + { 0x529c, 0x0 }, + { 0x529d, 0x28 }, + { 0x529e, 0x0 }, + { 0x529f, 0x30 }, + { 0x5282, 0x0 }, + { 0x5300, 0x0 }, + { 0x5301, 0x20 }, + { 0x5302, 0x0 }, + { 0x5303, 0x7c }, + { 0x530c, 0x0 }, + { 0x530d, 0xc }, + { 0x530e, 0x20 }, + { 0x530f, 0x80 }, + { 0x5310, 0x20 }, + { 0x5311, 0x80 }, + { 0x5308, 0x20 }, + { 0x5309, 0x40 }, + { 0x5304, 0x0 }, + { 0x5305, 0x30 }, + { 0x5306, 0x0 }, + { 0x5307, 0x80 }, + { 0x5314, 0x8 }, + { 0x5315, 0x20 }, + { 0x5319, 0x30 }, + { 0x5316, 0x10 }, + { 0x5317, 0x0 }, + { 0x5318, 0x2 }, + { 0x5380, 0x1 }, + { 0x5381, 0x0 }, + { 0x5382, 0x0 }, + { 0x5383, 0x4e }, + { 0x5384, 0x0 }, + { 0x5385, 0xf }, + { 0x5386, 0x0 }, + { 0x5387, 0x0 }, + { 0x5388, 0x1 }, + { 0x5389, 0x15 }, + { 0x538a, 0x0 }, + { 0x538b, 0x31 }, + { 0x538c, 0x0 }, + { 0x538d, 0x0 }, + { 0x538e, 0x0 }, + { 0x538f, 0xf }, + { 0x5390, 0x0 }, + { 0x5391, 0xab }, + { 0x5392, 0x0 }, + { 0x5393, 0xa2 }, + { 0x5394, 0x8 }, + { 0x5480, 0x14 }, + { 0x5481, 0x21 }, + { 0x5482, 0x36 }, + { 0x5483, 0x57 }, + { 0x5484, 0x65 }, + { 0x5485, 0x71 }, + { 0x5486, 0x7d }, + { 0x5487, 0x87 }, + { 0x5488, 0x91 }, + { 0x5489, 0x9a }, + { 0x548a, 0xaa }, + { 0x548b, 0xb8 }, + { 0x548c, 0xcd }, + { 0x548d, 0xdd }, + { 0x548e, 0xea }, + { 0x548f, 0x1d }, + { 0x5490, 0x5 }, + { 0x5491, 0x0 }, + { 0x5492, 0x4 }, + { 0x5493, 0x20 }, + { 0x5494, 0x3 }, + { 0x5495, 0x60 }, + { 0x5496, 0x2 }, + { 0x5497, 0xb8 }, + { 0x5498, 0x2 }, + { 0x5499, 0x86 }, + { 0x549a, 0x2 }, + { 0x549b, 0x5b }, + { 0x549c, 0x2 }, + { 0x549d, 0x3b }, + { 0x549e, 0x2 }, + { 0x549f, 0x1c }, + { 0x54a0, 0x2 }, + { 0x54a1, 0x4 }, + { 0x54a2, 0x1 }, + { 0x54a3, 0xed }, + { 0x54a4, 0x1 }, + { 0x54a5, 0xc5 }, + { 0x54a6, 0x1 }, + { 0x54a7, 0xa5 }, + { 0x54a8, 0x1 }, + { 0x54a9, 0x6c }, + { 0x54aa, 0x1 }, + { 0x54ab, 0x41 }, + { 0x54ac, 0x1 }, + { 0x54ad, 0x20 }, + { 0x54ae, 0x0 }, + { 0x54af, 0x16 }, + { 0x54b0, 0x1 }, + { 0x54b1, 0x20 }, + { 0x54b2, 0x0 }, + { 0x54b3, 0x10 }, + { 0x54b4, 0x0 }, + { 0x54b5, 0xf0 }, + { 0x54b6, 0x0 }, + { 0x54b7, 0xdf }, + { 0x5402, 0x3f }, + { 0x5403, 0x0 }, + { 0x3406, 0x0 }, + { 0x5180, 0xff }, + { 0x5181, 0x52 }, + { 0x5182, 0x11 }, + { 0x5183, 0x14 }, + { 0x5184, 0x25 }, + { 0x5185, 0x24 }, + { 0x5186, 0x6 }, + { 0x5187, 0x8 }, + { 0x5188, 0x8 }, + { 0x5189, 0x7c }, + { 0x518a, 0x60 }, + { 0x518b, 0xb2 }, + { 0x518c, 0xb2 }, + { 0x518d, 0x44 }, + { 0x518e, 0x3d }, + { 0x518f, 0x58 }, + { 0x5190, 0x46 }, + { 0x5191, 0xf8 }, + { 0x5192, 0x4 }, + { 0x5193, 0x70 }, + { 0x5194, 0xf0 }, + { 0x5195, 0xf0 }, + { 0x5196, 0x3 }, + { 0x5197, 0x1 }, + { 0x5198, 0x4 }, + { 0x5199, 0x12 }, + { 0x519a, 0x4 }, + { 0x519b, 0x0 }, + { 0x519c, 0x6 }, + { 0x519d, 0x82 }, + { 0x519e, 0x0 }, + { 0x5025, 0x80 }, + { 0x3a0f, 0x38 }, + { 0x3a10, 0x30 }, + { 0x3a1b, 0x3a }, + { 0x3a1e, 0x2e }, + { 0x3a11, 0x60 }, + { 0x3a1f, 0x10 }, + { 0x5688, 0xa6 }, + { 0x5689, 0x6a }, + { 0x568a, 0xea }, + { 0x568b, 0xae }, + { 0x568c, 0xa6 }, + { 0x568d, 0x6a }, + { 0x568e, 0x62 }, + { 0x568f, 0x26 }, + { 0x5583, 0x40 }, + { 0x5584, 0x40 }, + { 0x5580, 0x2 }, + { 0x5000, 0xcf }, + { 0x5800, 0x27 }, + { 0x5801, 0x19 }, + { 0x5802, 0x12 }, + { 0x5803, 0xf }, + { 0x5804, 0x10 }, + { 0x5805, 0x15 }, + { 0x5806, 0x1e }, + { 0x5807, 0x2f }, + { 0x5808, 0x15 }, + { 0x5809, 0xd }, + { 0x580a, 0xa }, + { 0x580b, 0x9 }, + { 0x580c, 0xa }, + { 0x580d, 0xc }, + { 0x580e, 0x12 }, + { 0x580f, 0x19 }, + { 0x5810, 0xb }, + { 0x5811, 0x7 }, + { 0x5812, 0x4 }, + { 0x5813, 0x3 }, + { 0x5814, 0x3 }, + { 0x5815, 0x6 }, + { 0x5816, 0xa }, + { 0x5817, 0xf }, + { 0x5818, 0xa }, + { 0x5819, 0x5 }, + { 0x581a, 0x1 }, + { 0x581b, 0x0 }, + { 0x581c, 0x0 }, + { 0x581d, 0x3 }, + { 0x581e, 0x8 }, + { 0x581f, 0xc }, + { 0x5820, 0xa }, + { 0x5821, 0x5 }, + { 0x5822, 0x1 }, + { 0x5823, 0x0 }, + { 0x5824, 0x0 }, + { 0x5825, 0x3 }, + { 0x5826, 0x8 }, + { 0x5827, 0xc }, + { 0x5828, 0xe }, + { 0x5829, 0x8 }, + { 0x582a, 0x6 }, + { 0x582b, 0x4 }, + { 0x582c, 0x5 }, + { 0x582d, 0x7 }, + { 0x582e, 0xb }, + { 0x582f, 0x12 }, + { 0x5830, 0x18 }, + { 0x5831, 0x10 }, + { 0x5832, 0xc }, + { 0x5833, 0xa }, + { 0x5834, 0xb }, + { 0x5835, 0xe }, + { 0x5836, 0x15 }, + { 0x5837, 0x19 }, + { 0x5838, 0x32 }, + { 0x5839, 0x1f }, + { 0x583a, 0x18 }, + { 0x583b, 0x16 }, + { 0x583c, 0x17 }, + { 0x583d, 0x1e }, + { 0x583e, 0x26 }, + { 0x583f, 0x53 }, + { 0x5840, 0x10 }, + { 0x5841, 0xf }, + { 0x5842, 0xd }, + { 0x5843, 0xc }, + { 0x5844, 0xe }, + { 0x5845, 0x9 }, + { 0x5846, 0x11 }, + { 0x5847, 0x10 }, + { 0x5848, 0x10 }, + { 0x5849, 0x10 }, + { 0x584a, 0x10 }, + { 0x584b, 0xe }, + { 0x584c, 0x10 }, + { 0x584d, 0x10 }, + { 0x584e, 0x11 }, + { 0x584f, 0x10 }, + { 0x5850, 0xf }, + { 0x5851, 0xc }, + { 0x5852, 0xf }, + { 0x5853, 0x10 }, + { 0x5854, 0x10 }, + { 0x5855, 0xf }, + { 0x5856, 0xe }, + { 0x5857, 0xb }, + { 0x5858, 0x10 }, + { 0x5859, 0xd }, + { 0x585a, 0xd }, + { 0x585b, 0xc }, + { 0x585c, 0xc }, + { 0x585d, 0xc }, + { 0x585e, 0xb }, + { 0x585f, 0xc }, + { 0x5860, 0xc }, + { 0x5861, 0xc }, + { 0x5862, 0xd }, + { 0x5863, 0x8 }, + { 0x5864, 0x11 }, + { 0x5865, 0x18 }, + { 0x5866, 0x18 }, + { 0x5867, 0x19 }, + { 0x5868, 0x17 }, + { 0x5869, 0x19 }, + { 0x586a, 0x16 }, + { 0x586b, 0x13 }, + { 0x586c, 0x13 }, + { 0x586d, 0x12 }, + { 0x586e, 0x13 }, + { 0x586f, 0x16 }, + { 0x5870, 0x14 }, + { 0x5871, 0x12 }, + { 0x5872, 0x10 }, + { 0x5873, 0x11 }, + { 0x5874, 0x11 }, + { 0x5875, 0x16 }, + { 0x5876, 0x14 }, + { 0x5877, 0x11 }, + { 0x5878, 0x10 }, + { 0x5879, 0xf }, + { 0x587a, 0x10 }, + { 0x587b, 0x14 }, + { 0x587c, 0x13 }, + { 0x587d, 0x12 }, + { 0x587e, 0x11 }, + { 0x587f, 0x11 }, + { 0x5880, 0x12 }, + { 0x5881, 0x15 }, + { 0x5882, 0x14 }, + { 0x5883, 0x15 }, + { 0x5884, 0x15 }, + { 0x5885, 0x15 }, + { 0x5886, 0x13 }, + { 0x5887, 0x17 }, + { 0x3710, 0x10 }, + { 0x3632, 0x51 }, + { 0x3702, 0x10 }, + { 0x3703, 0xb2 }, + { 0x3704, 0x18 }, + { 0x370b, 0x40 }, + { 0x370d, 0x3 }, + { 0x3631, 0x1 }, + { 0x3632, 0x52 }, + { 0x3606, 0x24 }, + { 0x3620, 0x96 }, + { 0x5785, 0x7 }, + { 0x3a13, 0x30 }, + { 0x3600, 0x52 }, + { 0x3604, 0x48 }, + { 0x3606, 0x1b }, + { 0x370d, 0xb }, + { 0x370f, 0xc0 }, + { 0x3709, 0x1 }, + { 0x3823, 0x0 }, + { 0x5007, 0x0 }, + { 0x5009, 0x0 }, + { 0x5011, 0x0 }, + { 0x5013, 0x0 }, + { 0x519e, 0x0 }, + { 0x5086, 0x0 }, + { 0x5087, 0x0 }, + { 0x5088, 0x0 }, + { 0x5089, 0x0 }, + { 0x302b, 0x0 }, + { 0x3503, 0x7 }, + { 0x3011, 0x8 }, + { 0x350c, 0x2 }, + { 0x350d, 0xe4 }, + { 0x3621, 0xc9 }, + { 0x370a, 0x81 }, + { 0xffff, 0xff }, +}; + +static struct regval_list ov5642_default_regs_finalise[] = { + { 0x3810, 0xc2 }, + { 0x3818, 0xc9 }, + { 0x381c, 0x10 }, + { 0x381d, 0xa0 }, + { 0x381e, 0x5 }, + { 0x381f, 0xb0 }, + { 0x3820, 0x0 }, + { 0x3821, 0x0 }, + { 0x3824, 0x11 }, + { 0x3a08, 0x1b }, + { 0x3a09, 0xc0 }, + { 0x3a0a, 0x17 }, + { 0x3a0b, 0x20 }, + { 0x3a0d, 0x2 }, + { 0x3a0e, 0x1 }, + { 0x401c, 0x4 }, + { 0x5682, 0x5 }, + { 0x5683, 0x0 }, + { 0x5686, 0x2 }, + { 0x5687, 0xcc }, + { 0x5001, 0x4f }, + { 0x589b, 0x6 }, + { 0x589a, 0xc5 }, + { 0x3503, 0x0 }, + { 0x460c, 0x20 }, + { 0x460b, 0x37 }, + { 0x471c, 0xd0 }, + { 0x471d, 0x5 }, + { 0x3815, 0x1 }, + { 0x3818, 0xc1 }, + { 0x501f, 0x0 }, + { 0x5002, 0xe0 }, + { 0x4300, 0x32 }, /* UYVY */ + { 0x3002, 0x1c }, + { 0x4800, 0x14 }, + { 0x4801, 0xf }, + { 0x3007, 0x3b }, + { 0x300e, 0x4 }, + { 0x4803, 0x50 }, + { 0x3815, 0x1 }, + { 0x4713, 0x2 }, + { 0x4842, 0x1 }, + { 0x300f, 0xe }, + { 0x3003, 0x3 }, + { 0x3003, 0x1 }, + { 0xffff, 0xff }, +}; + +struct ov5642_datafmt { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; +}; + +struct ov5642 { + struct v4l2_subdev subdev; + const struct ov5642_datafmt *fmt; + struct v4l2_rect crop_rect; + + /* blanking information */ + int total_width; + int total_height; +}; + +static const struct ov5642_datafmt ov5642_colour_fmts[] = { + {V4L2_MBUS_FMT_UYVY8_2X8, V4L2_COLORSPACE_JPEG}, +}; + +static struct ov5642 *to_ov5642(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ov5642, subdev); +} + +/* Find a data format by a pixel code in an array */ +static const struct ov5642_datafmt + *ov5642_find_datafmt(enum v4l2_mbus_pixelcode code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ov5642_colour_fmts); i++) + if (ov5642_colour_fmts[i].code == code) + return ov5642_colour_fmts + i; + + return NULL; +} + +static int reg_read(struct i2c_client *client, u16 reg, u8 *val) +{ + int ret; + /* We have 16-bit i2c addresses - care for endianess */ + unsigned char data[2] = { reg >> 8, reg & 0xff }; + + ret = i2c_master_send(client, data, 2); + if (ret < 2) { + dev_err(&client->dev, "%s: i2c read error, reg: %x\n", + __func__, reg); + return ret < 0 ? ret : -EIO; + } + + ret = i2c_master_recv(client, val, 1); + if (ret < 1) { + dev_err(&client->dev, "%s: i2c read error, reg: %x\n", + __func__, reg); + return ret < 0 ? ret : -EIO; + } + return 0; +} + +static int reg_write(struct i2c_client *client, u16 reg, u8 val) +{ + int ret; + unsigned char data[3] = { reg >> 8, reg & 0xff, val }; + + ret = i2c_master_send(client, data, 3); + if (ret < 3) { + dev_err(&client->dev, "%s: i2c write error, reg: %x\n", + __func__, reg); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +/* + * convenience function to write 16 bit register values that are split up + * into two consecutive high and low parts + */ +static int reg_write16(struct i2c_client *client, u16 reg, u16 val16) +{ + int ret; + + ret = reg_write(client, reg, val16 >> 8); + if (ret) + return ret; + return reg_write(client, reg + 1, val16 & 0x00ff); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov5642_get_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val; + + if (reg->reg & ~0xffff) + return -EINVAL; + + reg->size = 1; + + ret = reg_read(client, reg->reg, &val); + if (!ret) + reg->val = (__u64)val; + + return ret; +} + +static int ov5642_set_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg & ~0xffff || reg->val & ~0xff) + return -EINVAL; + + return reg_write(client, reg->reg, reg->val); +} +#endif + +static int ov5642_write_array(struct i2c_client *client, + struct regval_list *vals) +{ + while (vals->reg_num != 0xffff || vals->value != 0xff) { + int ret = reg_write(client, vals->reg_num, vals->value); + if (ret < 0) + return ret; + vals++; + } + dev_dbg(&client->dev, "Register list loaded\n"); + return 0; +} + +static int ov5642_set_resolution(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov5642 *priv = to_ov5642(client); + int width = priv->crop_rect.width; + int height = priv->crop_rect.height; + int total_width = priv->total_width; + int total_height = priv->total_height; + int start_x = (OV5642_SENSOR_SIZE_X - width) / 2; + int start_y = (OV5642_SENSOR_SIZE_Y - height) / 2; + int ret; + + /* + * This should set the starting point for cropping. + * Doesn't work so far. + */ + ret = reg_write16(client, REG_WINDOW_START_X_HIGH, start_x); + if (!ret) + ret = reg_write16(client, REG_WINDOW_START_Y_HIGH, start_y); + if (!ret) { + priv->crop_rect.left = start_x; + priv->crop_rect.top = start_y; + } + + if (!ret) + ret = reg_write16(client, REG_WINDOW_WIDTH_HIGH, width); + if (!ret) + ret = reg_write16(client, REG_WINDOW_HEIGHT_HIGH, height); + if (ret) + return ret; + priv->crop_rect.width = width; + priv->crop_rect.height = height; + + /* Set the output window size. Only 1:1 scale is supported so far. */ + ret = reg_write16(client, REG_OUT_WIDTH_HIGH, width); + if (!ret) + ret = reg_write16(client, REG_OUT_HEIGHT_HIGH, height); + + /* Total width = output size + blanking */ + if (!ret) + ret = reg_write16(client, REG_OUT_TOTAL_WIDTH_HIGH, total_width); + if (!ret) + ret = reg_write16(client, REG_OUT_TOTAL_HEIGHT_HIGH, total_height); + + /* Sets the window for AWB calculations */ + if (!ret) + ret = reg_write16(client, REG_AVG_WINDOW_END_X_HIGH, width); + if (!ret) + ret = reg_write16(client, REG_AVG_WINDOW_END_Y_HIGH, height); + + return ret; +} + +static int ov5642_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov5642 *priv = to_ov5642(client); + const struct ov5642_datafmt *fmt = ov5642_find_datafmt(mf->code); + + mf->width = priv->crop_rect.width; + mf->height = priv->crop_rect.height; + + if (!fmt) { + mf->code = ov5642_colour_fmts[0].code; + mf->colorspace = ov5642_colour_fmts[0].colorspace; + } + + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ov5642_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov5642 *priv = to_ov5642(client); + + /* MIPI CSI could have changed the format, double-check */ + if (!ov5642_find_datafmt(mf->code)) + return -EINVAL; + + ov5642_try_fmt(sd, mf); + priv->fmt = ov5642_find_datafmt(mf->code); + + return 0; +} + +static int ov5642_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov5642 *priv = to_ov5642(client); + + const struct ov5642_datafmt *fmt = priv->fmt; + + mf->code = fmt->code; + mf->colorspace = fmt->colorspace; + mf->width = priv->crop_rect.width; + mf->height = priv->crop_rect.height; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ov5642_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(ov5642_colour_fmts)) + return -EINVAL; + + *code = ov5642_colour_fmts[index].code; + return 0; +} + +static int ov5642_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + if (id->match.addr != client->addr) + return -ENODEV; + + id->ident = V4L2_IDENT_OV5642; + id->revision = 0; + + return 0; +} + +static int ov5642_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov5642 *priv = to_ov5642(client); + struct v4l2_rect rect = a->c; + int ret; + + v4l_bound_align_image(&rect.width, 48, OV5642_MAX_WIDTH, 1, + &rect.height, 32, OV5642_MAX_HEIGHT, 1, 0); + + priv->crop_rect.width = rect.width; + priv->crop_rect.height = rect.height; + priv->total_width = rect.width + BLANKING_EXTRA_WIDTH; + priv->total_height = max_t(int, rect.height + + BLANKING_EXTRA_HEIGHT, + BLANKING_MIN_HEIGHT); + priv->crop_rect.width = rect.width; + priv->crop_rect.height = rect.height; + + ret = ov5642_write_array(client, ov5642_default_regs_init); + if (!ret) + ret = ov5642_set_resolution(sd); + if (!ret) + ret = ov5642_write_array(client, ov5642_default_regs_finalise); + + return ret; +} + +static int ov5642_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov5642 *priv = to_ov5642(client); + struct v4l2_rect *rect = &a->c; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + *rect = priv->crop_rect; + + return 0; +} + +static int ov5642_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = OV5642_MAX_WIDTH; + a->bounds.height = OV5642_MAX_HEIGHT; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int ov5642_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + cfg->type = V4L2_MBUS_CSI2; + cfg->flags = V4L2_MBUS_CSI2_2_LANE | V4L2_MBUS_CSI2_CHANNEL_0 | + V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + + return 0; +} + +static int ov5642_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + if (!on) + return soc_camera_power_off(&client->dev, icl); + + ret = soc_camera_power_on(&client->dev, icl); + if (ret < 0) + return ret; + + ret = ov5642_write_array(client, ov5642_default_regs_init); + if (!ret) + ret = ov5642_set_resolution(sd); + if (!ret) + ret = ov5642_write_array(client, ov5642_default_regs_finalise); + + return ret; +} + +static struct v4l2_subdev_video_ops ov5642_subdev_video_ops = { + .s_mbus_fmt = ov5642_s_fmt, + .g_mbus_fmt = ov5642_g_fmt, + .try_mbus_fmt = ov5642_try_fmt, + .enum_mbus_fmt = ov5642_enum_fmt, + .s_crop = ov5642_s_crop, + .g_crop = ov5642_g_crop, + .cropcap = ov5642_cropcap, + .g_mbus_config = ov5642_g_mbus_config, +}; + +static struct v4l2_subdev_core_ops ov5642_subdev_core_ops = { + .s_power = ov5642_s_power, + .g_chip_ident = ov5642_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov5642_get_register, + .s_register = ov5642_set_register, +#endif +}; + +static struct v4l2_subdev_ops ov5642_subdev_ops = { + .core = &ov5642_subdev_core_ops, + .video = &ov5642_subdev_video_ops, +}; + +static int ov5642_video_probe(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + int ret; + u8 id_high, id_low; + u16 id; + + ret = ov5642_s_power(subdev, 1); + if (ret < 0) + return ret; + + /* Read sensor Model ID */ + ret = reg_read(client, REG_CHIP_ID_HIGH, &id_high); + if (ret < 0) + goto done; + + id = id_high << 8; + + ret = reg_read(client, REG_CHIP_ID_LOW, &id_low); + if (ret < 0) + goto done; + + id |= id_low; + + dev_info(&client->dev, "Chip ID 0x%04x detected\n", id); + + if (id != 0x5642) { + ret = -ENODEV; + goto done; + } + + ret = 0; + +done: + ov5642_s_power(subdev, 0); + return ret; +} + +static int ov5642_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov5642 *priv; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + if (!icl) { + dev_err(&client->dev, "OV5642: missing platform data!\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(struct ov5642), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov5642_subdev_ops); + + priv->fmt = &ov5642_colour_fmts[0]; + + priv->crop_rect.width = OV5642_DEFAULT_WIDTH; + priv->crop_rect.height = OV5642_DEFAULT_HEIGHT; + priv->crop_rect.left = (OV5642_MAX_WIDTH - OV5642_DEFAULT_WIDTH) / 2; + priv->crop_rect.top = (OV5642_MAX_HEIGHT - OV5642_DEFAULT_HEIGHT) / 2; + priv->total_width = OV5642_DEFAULT_WIDTH + BLANKING_EXTRA_WIDTH; + priv->total_height = BLANKING_MIN_HEIGHT; + + ret = ov5642_video_probe(client); + if (ret < 0) + goto error; + + return 0; + +error: + kfree(priv); + return ret; +} + +static int ov5642_remove(struct i2c_client *client) +{ + struct ov5642 *priv = to_ov5642(client); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + if (icl->free_bus) + icl->free_bus(icl); + kfree(priv); + + return 0; +} + +static const struct i2c_device_id ov5642_id[] = { + { "ov5642", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov5642_id); + +static struct i2c_driver ov5642_i2c_driver = { + .driver = { + .name = "ov5642", + }, + .probe = ov5642_probe, + .remove = ov5642_remove, + .id_table = ov5642_id, +}; + +module_i2c_driver(ov5642_i2c_driver); + +MODULE_DESCRIPTION("Omnivision OV5642 Camera driver"); +MODULE_AUTHOR("Bastian Hecht "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/soc_camera/ov6650.c b/drivers/media/i2c/soc_camera/ov6650.c new file mode 100644 index 000000000000..e87feb0881e3 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov6650.c @@ -0,0 +1,1069 @@ +/* + * V4L2 SoC Camera driver for OmniVision OV6650 Camera Sensor + * + * Copyright (C) 2010 Janusz Krzysztofik + * + * Based on OmniVision OV96xx Camera Driver + * Copyright (C) 2009 Marek Vasut + * + * Based on ov772x camera driver: + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto + * + * Based on ov7670 and soc_camera_platform driver, + * Copyright 2006-7 Jonathan Corbet + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski + * + * Hardware specific bits initialy based on former work by Matt Callow + * drivers/media/video/omap/sensor_ov6650.c + * Copyright (C) 2006 Matt Callow + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Register definitions */ +#define REG_GAIN 0x00 /* range 00 - 3F */ +#define REG_BLUE 0x01 +#define REG_RED 0x02 +#define REG_SAT 0x03 /* [7:4] saturation [0:3] reserved */ +#define REG_HUE 0x04 /* [7:6] rsrvd [5] hue en [4:0] hue */ + +#define REG_BRT 0x06 + +#define REG_PIDH 0x0a +#define REG_PIDL 0x0b + +#define REG_AECH 0x10 +#define REG_CLKRC 0x11 /* Data Format and Internal Clock */ + /* [7:6] Input system clock (MHz)*/ + /* 00=8, 01=12, 10=16, 11=24 */ + /* [5:0]: Internal Clock Pre-Scaler */ +#define REG_COMA 0x12 /* [7] Reset */ +#define REG_COMB 0x13 +#define REG_COMC 0x14 +#define REG_COMD 0x15 +#define REG_COML 0x16 +#define REG_HSTRT 0x17 +#define REG_HSTOP 0x18 +#define REG_VSTRT 0x19 +#define REG_VSTOP 0x1a +#define REG_PSHFT 0x1b +#define REG_MIDH 0x1c +#define REG_MIDL 0x1d +#define REG_HSYNS 0x1e +#define REG_HSYNE 0x1f +#define REG_COME 0x20 +#define REG_YOFF 0x21 +#define REG_UOFF 0x22 +#define REG_VOFF 0x23 +#define REG_AEW 0x24 +#define REG_AEB 0x25 +#define REG_COMF 0x26 +#define REG_COMG 0x27 +#define REG_COMH 0x28 +#define REG_COMI 0x29 + +#define REG_FRARL 0x2b +#define REG_COMJ 0x2c +#define REG_COMK 0x2d +#define REG_AVGY 0x2e +#define REG_REF0 0x2f +#define REG_REF1 0x30 +#define REG_REF2 0x31 +#define REG_FRAJH 0x32 +#define REG_FRAJL 0x33 +#define REG_FACT 0x34 +#define REG_L1AEC 0x35 +#define REG_AVGU 0x36 +#define REG_AVGV 0x37 + +#define REG_SPCB 0x60 +#define REG_SPCC 0x61 +#define REG_GAM1 0x62 +#define REG_GAM2 0x63 +#define REG_GAM3 0x64 +#define REG_SPCD 0x65 + +#define REG_SPCE 0x68 +#define REG_ADCL 0x69 + +#define REG_RMCO 0x6c +#define REG_GMCO 0x6d +#define REG_BMCO 0x6e + + +/* Register bits, values, etc. */ +#define OV6650_PIDH 0x66 /* high byte of product ID number */ +#define OV6650_PIDL 0x50 /* low byte of product ID number */ +#define OV6650_MIDH 0x7F /* high byte of mfg ID */ +#define OV6650_MIDL 0xA2 /* low byte of mfg ID */ + +#define DEF_GAIN 0x00 +#define DEF_BLUE 0x80 +#define DEF_RED 0x80 + +#define SAT_SHIFT 4 +#define SAT_MASK (0xf << SAT_SHIFT) +#define SET_SAT(x) (((x) << SAT_SHIFT) & SAT_MASK) + +#define HUE_EN BIT(5) +#define HUE_MASK 0x1f +#define DEF_HUE 0x10 +#define SET_HUE(x) (HUE_EN | ((x) & HUE_MASK)) + +#define DEF_AECH 0x4D + +#define CLKRC_6MHz 0x00 +#define CLKRC_12MHz 0x40 +#define CLKRC_16MHz 0x80 +#define CLKRC_24MHz 0xc0 +#define CLKRC_DIV_MASK 0x3f +#define GET_CLKRC_DIV(x) (((x) & CLKRC_DIV_MASK) + 1) + +#define COMA_RESET BIT(7) +#define COMA_QCIF BIT(5) +#define COMA_RAW_RGB BIT(4) +#define COMA_RGB BIT(3) +#define COMA_BW BIT(2) +#define COMA_WORD_SWAP BIT(1) +#define COMA_BYTE_SWAP BIT(0) +#define DEF_COMA 0x00 + +#define COMB_FLIP_V BIT(7) +#define COMB_FLIP_H BIT(5) +#define COMB_BAND_FILTER BIT(4) +#define COMB_AWB BIT(2) +#define COMB_AGC BIT(1) +#define COMB_AEC BIT(0) +#define DEF_COMB 0x5f + +#define COML_ONE_CHANNEL BIT(7) + +#define DEF_HSTRT 0x24 +#define DEF_HSTOP 0xd4 +#define DEF_VSTRT 0x04 +#define DEF_VSTOP 0x94 + +#define COMF_HREF_LOW BIT(4) + +#define COMJ_PCLK_RISING BIT(4) +#define COMJ_VSYNC_HIGH BIT(0) + +/* supported resolutions */ +#define W_QCIF (DEF_HSTOP - DEF_HSTRT) +#define W_CIF (W_QCIF << 1) +#define H_QCIF (DEF_VSTOP - DEF_VSTRT) +#define H_CIF (H_QCIF << 1) + +#define FRAME_RATE_MAX 30 + + +struct ov6650_reg { + u8 reg; + u8 val; +}; + +struct ov6650 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct { + /* exposure/autoexposure cluster */ + struct v4l2_ctrl *autoexposure; + struct v4l2_ctrl *exposure; + }; + struct { + /* gain/autogain cluster */ + struct v4l2_ctrl *autogain; + struct v4l2_ctrl *gain; + }; + struct { + /* blue/red/autowhitebalance cluster */ + struct v4l2_ctrl *autowb; + struct v4l2_ctrl *blue; + struct v4l2_ctrl *red; + }; + bool half_scale; /* scale down output by 2 */ + struct v4l2_rect rect; /* sensor cropping window */ + unsigned long pclk_limit; /* from host */ + unsigned long pclk_max; /* from resolution and format */ + struct v4l2_fract tpf; /* as requested with s_parm */ + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; +}; + + +static enum v4l2_mbus_pixelcode ov6650_codes[] = { + V4L2_MBUS_FMT_YUYV8_2X8, + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_YVYU8_2X8, + V4L2_MBUS_FMT_VYUY8_2X8, + V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_MBUS_FMT_Y8_1X8, +}; + +/* read a register */ +static int ov6650_reg_read(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret; + u8 data = reg; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + msg.flags = I2C_M_RD; + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + *val = data; + return 0; + +err: + dev_err(&client->dev, "Failed reading register 0x%02x!\n", reg); + return ret; +} + +/* write a register */ +static int ov6650_reg_write(struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + unsigned char data[2] = { reg, val }; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + udelay(100); + + if (ret < 0) { + dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg); + return ret; + } + return 0; +} + + +/* Read a register, alter its bits, write it back */ +static int ov6650_reg_rmw(struct i2c_client *client, u8 reg, u8 set, u8 mask) +{ + u8 val; + int ret; + + ret = ov6650_reg_read(client, reg, &val); + if (ret) { + dev_err(&client->dev, + "[Read]-Modify-Write of register 0x%02x failed!\n", + reg); + return ret; + } + + val &= ~mask; + val |= set; + + ret = ov6650_reg_write(client, reg, val); + if (ret) + dev_err(&client->dev, + "Read-Modify-[Write] of register 0x%02x failed!\n", + reg); + + return ret; +} + +static struct ov6650 *to_ov6650(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ov6650, subdev); +} + +/* Start/Stop streaming from the device */ +static int ov6650_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +/* Get status of additional camera capabilities */ +static int ov6550_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov6650 *priv = container_of(ctrl->handler, struct ov6650, hdl); + struct v4l2_subdev *sd = &priv->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + uint8_t reg, reg2; + int ret; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + ret = ov6650_reg_read(client, REG_GAIN, ®); + if (!ret) + priv->gain->val = reg; + return ret; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov6650_reg_read(client, REG_BLUE, ®); + if (!ret) + ret = ov6650_reg_read(client, REG_RED, ®2); + if (!ret) { + priv->blue->val = reg; + priv->red->val = reg2; + } + return ret; + case V4L2_CID_EXPOSURE_AUTO: + ret = ov6650_reg_read(client, REG_AECH, ®); + if (!ret) + priv->exposure->val = reg; + return ret; + } + return -EINVAL; +} + +/* Set status of additional camera capabilities */ +static int ov6550_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov6650 *priv = container_of(ctrl->handler, struct ov6650, hdl); + struct v4l2_subdev *sd = &priv->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + ret = ov6650_reg_rmw(client, REG_COMB, + ctrl->val ? COMB_AGC : 0, COMB_AGC); + if (!ret && !ctrl->val) + ret = ov6650_reg_write(client, REG_GAIN, priv->gain->val); + return ret; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov6650_reg_rmw(client, REG_COMB, + ctrl->val ? COMB_AWB : 0, COMB_AWB); + if (!ret && !ctrl->val) { + ret = ov6650_reg_write(client, REG_BLUE, priv->blue->val); + if (!ret) + ret = ov6650_reg_write(client, REG_RED, + priv->red->val); + } + return ret; + case V4L2_CID_SATURATION: + return ov6650_reg_rmw(client, REG_SAT, SET_SAT(ctrl->val), + SAT_MASK); + case V4L2_CID_HUE: + return ov6650_reg_rmw(client, REG_HUE, SET_HUE(ctrl->val), + HUE_MASK); + case V4L2_CID_BRIGHTNESS: + return ov6650_reg_write(client, REG_BRT, ctrl->val); + case V4L2_CID_EXPOSURE_AUTO: + ret = ov6650_reg_rmw(client, REG_COMB, ctrl->val == + V4L2_EXPOSURE_AUTO ? COMB_AEC : 0, COMB_AEC); + if (!ret && ctrl->val == V4L2_EXPOSURE_MANUAL) + ret = ov6650_reg_write(client, REG_AECH, + priv->exposure->val); + return ret; + case V4L2_CID_GAMMA: + return ov6650_reg_write(client, REG_GAM1, ctrl->val); + case V4L2_CID_VFLIP: + return ov6650_reg_rmw(client, REG_COMB, + ctrl->val ? COMB_FLIP_V : 0, COMB_FLIP_V); + case V4L2_CID_HFLIP: + return ov6650_reg_rmw(client, REG_COMB, + ctrl->val ? COMB_FLIP_H : 0, COMB_FLIP_H); + } + + return -EINVAL; +} + +/* Get chip identification */ +static int ov6650_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + id->ident = V4L2_IDENT_OV6650; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov6650_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val; + + if (reg->reg & ~0xff) + return -EINVAL; + + reg->size = 1; + + ret = ov6650_reg_read(client, reg->reg, &val); + if (!ret) + reg->val = (__u64)val; + + return ret; +} + +static int ov6650_set_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg & ~0xff || reg->val & ~0xff) + return -EINVAL; + + return ov6650_reg_write(client, reg->reg, reg->val); +} +#endif + +static int ov6650_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +static int ov6650_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->c = priv->rect; + + return 0; +} + +static int ov6650_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + struct v4l2_rect rect = a->c; + int ret; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + rect.left = ALIGN(rect.left, 2); + rect.width = ALIGN(rect.width, 2); + rect.top = ALIGN(rect.top, 2); + rect.height = ALIGN(rect.height, 2); + soc_camera_limit_side(&rect.left, &rect.width, + DEF_HSTRT << 1, 2, W_CIF); + soc_camera_limit_side(&rect.top, &rect.height, + DEF_VSTRT << 1, 2, H_CIF); + + ret = ov6650_reg_write(client, REG_HSTRT, rect.left >> 1); + if (!ret) { + priv->rect.left = rect.left; + ret = ov6650_reg_write(client, REG_HSTOP, + (rect.left + rect.width) >> 1); + } + if (!ret) { + priv->rect.width = rect.width; + ret = ov6650_reg_write(client, REG_VSTRT, rect.top >> 1); + } + if (!ret) { + priv->rect.top = rect.top; + ret = ov6650_reg_write(client, REG_VSTOP, + (rect.top + rect.height) >> 1); + } + if (!ret) + priv->rect.height = rect.height; + + return ret; +} + +static int ov6650_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + a->bounds.left = DEF_HSTRT << 1; + a->bounds.top = DEF_VSTRT << 1; + a->bounds.width = W_CIF; + a->bounds.height = H_CIF; + a->defrect = a->bounds; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int ov6650_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + + mf->width = priv->rect.width >> priv->half_scale; + mf->height = priv->rect.height >> priv->half_scale; + mf->code = priv->code; + mf->colorspace = priv->colorspace; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static bool is_unscaled_ok(int width, int height, struct v4l2_rect *rect) +{ + return width > rect->width >> 1 || height > rect->height >> 1; +} + +static u8 to_clkrc(struct v4l2_fract *timeperframe, + unsigned long pclk_limit, unsigned long pclk_max) +{ + unsigned long pclk; + + if (timeperframe->numerator && timeperframe->denominator) + pclk = pclk_max * timeperframe->denominator / + (FRAME_RATE_MAX * timeperframe->numerator); + else + pclk = pclk_max; + + if (pclk_limit && pclk_limit < pclk) + pclk = pclk_limit; + + return (pclk_max - 1) / pclk; +} + +/* set the format we will capture in */ +static int ov6650_s_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_device *icd = v4l2_get_subdev_hostdata(sd); + struct soc_camera_sense *sense = icd->sense; + struct ov6650 *priv = to_ov6650(client); + bool half_scale = !is_unscaled_ok(mf->width, mf->height, &priv->rect); + struct v4l2_crop a = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .c = { + .left = priv->rect.left + (priv->rect.width >> 1) - + (mf->width >> (1 - half_scale)), + .top = priv->rect.top + (priv->rect.height >> 1) - + (mf->height >> (1 - half_scale)), + .width = mf->width << half_scale, + .height = mf->height << half_scale, + }, + }; + enum v4l2_mbus_pixelcode code = mf->code; + unsigned long mclk, pclk; + u8 coma_set = 0, coma_mask = 0, coml_set, coml_mask, clkrc; + int ret; + + /* select color matrix configuration for given color encoding */ + switch (code) { + case V4L2_MBUS_FMT_Y8_1X8: + dev_dbg(&client->dev, "pixel format GREY8_1X8\n"); + coma_mask |= COMA_RGB | COMA_WORD_SWAP | COMA_BYTE_SWAP; + coma_set |= COMA_BW; + break; + case V4L2_MBUS_FMT_YUYV8_2X8: + dev_dbg(&client->dev, "pixel format YUYV8_2X8_LE\n"); + coma_mask |= COMA_RGB | COMA_BW | COMA_BYTE_SWAP; + coma_set |= COMA_WORD_SWAP; + break; + case V4L2_MBUS_FMT_YVYU8_2X8: + dev_dbg(&client->dev, "pixel format YVYU8_2X8_LE (untested)\n"); + coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP | + COMA_BYTE_SWAP; + break; + case V4L2_MBUS_FMT_UYVY8_2X8: + dev_dbg(&client->dev, "pixel format YUYV8_2X8_BE\n"); + if (half_scale) { + coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP; + coma_set |= COMA_BYTE_SWAP; + } else { + coma_mask |= COMA_RGB | COMA_BW; + coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP; + } + break; + case V4L2_MBUS_FMT_VYUY8_2X8: + dev_dbg(&client->dev, "pixel format YVYU8_2X8_BE (untested)\n"); + if (half_scale) { + coma_mask |= COMA_RGB | COMA_BW; + coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP; + } else { + coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP; + coma_set |= COMA_BYTE_SWAP; + } + break; + case V4L2_MBUS_FMT_SBGGR8_1X8: + dev_dbg(&client->dev, "pixel format SBGGR8_1X8 (untested)\n"); + coma_mask |= COMA_BW | COMA_BYTE_SWAP | COMA_WORD_SWAP; + coma_set |= COMA_RAW_RGB | COMA_RGB; + break; + default: + dev_err(&client->dev, "Pixel format not handled: 0x%x\n", code); + return -EINVAL; + } + priv->code = code; + + if (code == V4L2_MBUS_FMT_Y8_1X8 || + code == V4L2_MBUS_FMT_SBGGR8_1X8) { + coml_mask = COML_ONE_CHANNEL; + coml_set = 0; + priv->pclk_max = 4000000; + } else { + coml_mask = 0; + coml_set = COML_ONE_CHANNEL; + priv->pclk_max = 8000000; + } + + if (code == V4L2_MBUS_FMT_SBGGR8_1X8) + priv->colorspace = V4L2_COLORSPACE_SRGB; + else if (code != 0) + priv->colorspace = V4L2_COLORSPACE_JPEG; + + if (half_scale) { + dev_dbg(&client->dev, "max resolution: QCIF\n"); + coma_set |= COMA_QCIF; + priv->pclk_max /= 2; + } else { + dev_dbg(&client->dev, "max resolution: CIF\n"); + coma_mask |= COMA_QCIF; + } + priv->half_scale = half_scale; + + if (sense) { + if (sense->master_clock == 8000000) { + dev_dbg(&client->dev, "8MHz input clock\n"); + clkrc = CLKRC_6MHz; + } else if (sense->master_clock == 12000000) { + dev_dbg(&client->dev, "12MHz input clock\n"); + clkrc = CLKRC_12MHz; + } else if (sense->master_clock == 16000000) { + dev_dbg(&client->dev, "16MHz input clock\n"); + clkrc = CLKRC_16MHz; + } else if (sense->master_clock == 24000000) { + dev_dbg(&client->dev, "24MHz input clock\n"); + clkrc = CLKRC_24MHz; + } else { + dev_err(&client->dev, + "unsupported input clock, check platform data\n"); + return -EINVAL; + } + mclk = sense->master_clock; + priv->pclk_limit = sense->pixel_clock_max; + } else { + clkrc = CLKRC_24MHz; + mclk = 24000000; + priv->pclk_limit = 0; + dev_dbg(&client->dev, "using default 24MHz input clock\n"); + } + + clkrc |= to_clkrc(&priv->tpf, priv->pclk_limit, priv->pclk_max); + + pclk = priv->pclk_max / GET_CLKRC_DIV(clkrc); + dev_dbg(&client->dev, "pixel clock divider: %ld.%ld\n", + mclk / pclk, 10 * mclk % pclk / pclk); + + ret = ov6650_s_crop(sd, &a); + if (!ret) + ret = ov6650_reg_rmw(client, REG_COMA, coma_set, coma_mask); + if (!ret) + ret = ov6650_reg_write(client, REG_CLKRC, clkrc); + if (!ret) + ret = ov6650_reg_rmw(client, REG_COML, coml_set, coml_mask); + + if (!ret) { + mf->colorspace = priv->colorspace; + mf->width = priv->rect.width >> half_scale; + mf->height = priv->rect.height >> half_scale; + } + + return ret; +} + +static int ov6650_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + + if (is_unscaled_ok(mf->width, mf->height, &priv->rect)) + v4l_bound_align_image(&mf->width, 2, W_CIF, 1, + &mf->height, 2, H_CIF, 1, 0); + + mf->field = V4L2_FIELD_NONE; + + switch (mf->code) { + case V4L2_MBUS_FMT_Y10_1X10: + mf->code = V4L2_MBUS_FMT_Y8_1X8; + case V4L2_MBUS_FMT_Y8_1X8: + case V4L2_MBUS_FMT_YVYU8_2X8: + case V4L2_MBUS_FMT_YUYV8_2X8: + case V4L2_MBUS_FMT_VYUY8_2X8: + case V4L2_MBUS_FMT_UYVY8_2X8: + mf->colorspace = V4L2_COLORSPACE_JPEG; + break; + default: + mf->code = V4L2_MBUS_FMT_SBGGR8_1X8; + case V4L2_MBUS_FMT_SBGGR8_1X8: + mf->colorspace = V4L2_COLORSPACE_SRGB; + break; + } + + return 0; +} + +static int ov6650_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(ov6650_codes)) + return -EINVAL; + + *code = ov6650_codes[index]; + return 0; +} + +static int ov6650_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + struct v4l2_captureparm *cp = &parms->parm.capture; + + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + memset(cp, 0, sizeof(*cp)); + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = GET_CLKRC_DIV(to_clkrc(&priv->tpf, + priv->pclk_limit, priv->pclk_max)); + cp->timeperframe.denominator = FRAME_RATE_MAX; + + dev_dbg(&client->dev, "Frame interval: %u/%u s\n", + cp->timeperframe.numerator, cp->timeperframe.denominator); + + return 0; +} + +static int ov6650_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov6650 *priv = to_ov6650(client); + struct v4l2_captureparm *cp = &parms->parm.capture; + struct v4l2_fract *tpf = &cp->timeperframe; + int div, ret; + u8 clkrc; + + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (cp->extendedmode != 0) + return -EINVAL; + + if (tpf->numerator == 0 || tpf->denominator == 0) + div = 1; /* Reset to full rate */ + else + div = (tpf->numerator * FRAME_RATE_MAX) / tpf->denominator; + + if (div == 0) + div = 1; + else if (div > GET_CLKRC_DIV(CLKRC_DIV_MASK)) + div = GET_CLKRC_DIV(CLKRC_DIV_MASK); + + /* + * Keep result to be used as tpf limit + * for subseqent clock divider calculations + */ + priv->tpf.numerator = div; + priv->tpf.denominator = FRAME_RATE_MAX; + + clkrc = to_clkrc(&priv->tpf, priv->pclk_limit, priv->pclk_max); + + ret = ov6650_reg_rmw(client, REG_CLKRC, clkrc, CLKRC_DIV_MASK); + if (!ret) { + tpf->numerator = GET_CLKRC_DIV(clkrc); + tpf->denominator = FRAME_RATE_MAX; + } + + return ret; +} + +/* Soft reset the camera. This has nothing to do with the RESET pin! */ +static int ov6650_reset(struct i2c_client *client) +{ + int ret; + + dev_dbg(&client->dev, "reset\n"); + + ret = ov6650_reg_rmw(client, REG_COMA, COMA_RESET, 0); + if (ret) + dev_err(&client->dev, + "An error occurred while entering soft reset!\n"); + + return ret; +} + +/* program default register values */ +static int ov6650_prog_dflt(struct i2c_client *client) +{ + int ret; + + dev_dbg(&client->dev, "initializing\n"); + + ret = ov6650_reg_write(client, REG_COMA, 0); /* ~COMA_RESET */ + if (!ret) + ret = ov6650_reg_rmw(client, REG_COMB, 0, COMB_BAND_FILTER); + + return ret; +} + +static int ov6650_video_probe(struct i2c_client *client) +{ + struct ov6650 *priv = to_ov6650(client); + u8 pidh, pidl, midh, midl; + int ret; + + ret = ov6650_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * check and show product ID and manufacturer ID + */ + ret = ov6650_reg_read(client, REG_PIDH, &pidh); + if (!ret) + ret = ov6650_reg_read(client, REG_PIDL, &pidl); + if (!ret) + ret = ov6650_reg_read(client, REG_MIDH, &midh); + if (!ret) + ret = ov6650_reg_read(client, REG_MIDL, &midl); + + if (ret) + goto done; + + if ((pidh != OV6650_PIDH) || (pidl != OV6650_PIDL)) { + dev_err(&client->dev, "Product ID error 0x%02x:0x%02x\n", + pidh, pidl); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, + "ov6650 Product ID 0x%02x:0x%02x Manufacturer ID 0x%02x:0x%02x\n", + pidh, pidl, midh, midl); + + ret = ov6650_reset(client); + if (!ret) + ret = ov6650_prog_dflt(client); + if (!ret) + ret = v4l2_ctrl_handler_setup(&priv->hdl); + +done: + ov6650_s_power(&priv->subdev, 0); + return ret; +} + +static const struct v4l2_ctrl_ops ov6550_ctrl_ops = { + .g_volatile_ctrl = ov6550_g_volatile_ctrl, + .s_ctrl = ov6550_s_ctrl, +}; + +static struct v4l2_subdev_core_ops ov6650_core_ops = { + .g_chip_ident = ov6650_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov6650_get_register, + .s_register = ov6650_set_register, +#endif + .s_power = ov6650_s_power, +}; + +/* Request bus settings on camera side */ +static int ov6650_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_MASTER | + V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_LOW | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_LOW | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +/* Alter bus settings on camera side */ +static int ov6650_s_mbus_config(struct v4l2_subdev *sd, + const struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + unsigned long flags = soc_camera_apply_board_flags(icl, cfg); + int ret; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + ret = ov6650_reg_rmw(client, REG_COMJ, COMJ_PCLK_RISING, 0); + else + ret = ov6650_reg_rmw(client, REG_COMJ, 0, COMJ_PCLK_RISING); + if (ret) + return ret; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + ret = ov6650_reg_rmw(client, REG_COMF, COMF_HREF_LOW, 0); + else + ret = ov6650_reg_rmw(client, REG_COMF, 0, COMF_HREF_LOW); + if (ret) + return ret; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + ret = ov6650_reg_rmw(client, REG_COMJ, COMJ_VSYNC_HIGH, 0); + else + ret = ov6650_reg_rmw(client, REG_COMJ, 0, COMJ_VSYNC_HIGH); + + return ret; +} + +static struct v4l2_subdev_video_ops ov6650_video_ops = { + .s_stream = ov6650_s_stream, + .g_mbus_fmt = ov6650_g_fmt, + .s_mbus_fmt = ov6650_s_fmt, + .try_mbus_fmt = ov6650_try_fmt, + .enum_mbus_fmt = ov6650_enum_fmt, + .cropcap = ov6650_cropcap, + .g_crop = ov6650_g_crop, + .s_crop = ov6650_s_crop, + .g_parm = ov6650_g_parm, + .s_parm = ov6650_s_parm, + .g_mbus_config = ov6650_g_mbus_config, + .s_mbus_config = ov6650_s_mbus_config, +}; + +static struct v4l2_subdev_ops ov6650_subdev_ops = { + .core = &ov6650_core_ops, + .video = &ov6650_video_ops, +}; + +/* + * i2c_driver function + */ +static int ov6650_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov6650 *priv; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + if (!icl) { + dev_err(&client->dev, "Missing platform_data for driver\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&client->dev, + "Failed to allocate memory for private data!\n"); + return -ENOMEM; + } + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov6650_subdev_ops); + v4l2_ctrl_handler_init(&priv->hdl, 13); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + priv->autogain = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + priv->gain = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_GAIN, 0, 0x3f, 1, DEF_GAIN); + priv->autowb = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + priv->blue = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_BLUE_BALANCE, 0, 0xff, 1, DEF_BLUE); + priv->red = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_RED_BALANCE, 0, 0xff, 1, DEF_RED); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_SATURATION, 0, 0xf, 1, 0x8); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_HUE, 0, HUE_MASK, 1, DEF_HUE); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 0xff, 1, 0x80); + priv->autoexposure = v4l2_ctrl_new_std_menu(&priv->hdl, + &ov6550_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, V4L2_EXPOSURE_AUTO); + priv->exposure = v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 0xff, 1, DEF_AECH); + v4l2_ctrl_new_std(&priv->hdl, &ov6550_ctrl_ops, + V4L2_CID_GAMMA, 0, 0xff, 1, 0x12); + + priv->subdev.ctrl_handler = &priv->hdl; + if (priv->hdl.error) { + int err = priv->hdl.error; + + kfree(priv); + return err; + } + v4l2_ctrl_auto_cluster(2, &priv->autogain, 0, true); + v4l2_ctrl_auto_cluster(3, &priv->autowb, 0, true); + v4l2_ctrl_auto_cluster(2, &priv->autoexposure, + V4L2_EXPOSURE_MANUAL, true); + + priv->rect.left = DEF_HSTRT << 1; + priv->rect.top = DEF_VSTRT << 1; + priv->rect.width = W_CIF; + priv->rect.height = H_CIF; + priv->half_scale = false; + priv->code = V4L2_MBUS_FMT_YUYV8_2X8; + priv->colorspace = V4L2_COLORSPACE_JPEG; + + ret = ov6650_video_probe(client); + if (ret) { + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + } + + return ret; +} + +static int ov6650_remove(struct i2c_client *client) +{ + struct ov6650 *priv = to_ov6650(client); + + v4l2_device_unregister_subdev(&priv->subdev); + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + return 0; +} + +static const struct i2c_device_id ov6650_id[] = { + { "ov6650", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov6650_id); + +static struct i2c_driver ov6650_i2c_driver = { + .driver = { + .name = "ov6650", + }, + .probe = ov6650_probe, + .remove = ov6650_remove, + .id_table = ov6650_id, +}; + +module_i2c_driver(ov6650_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV6650"); +MODULE_AUTHOR("Janusz Krzysztofik "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/soc_camera/ov772x.c b/drivers/media/i2c/soc_camera/ov772x.c new file mode 100644 index 000000000000..e4a10751894d --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov772x.c @@ -0,0 +1,1137 @@ +/* + * ov772x Camera Driver + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto + * + * Based on ov7670 and soc_camera_platform driver, + * + * Copyright 2006-7 Jonathan Corbet + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * register offset + */ +#define GAIN 0x00 /* AGC - Gain control gain setting */ +#define BLUE 0x01 /* AWB - Blue channel gain setting */ +#define RED 0x02 /* AWB - Red channel gain setting */ +#define GREEN 0x03 /* AWB - Green channel gain setting */ +#define COM1 0x04 /* Common control 1 */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define RAVG 0x07 /* V/R Average Level */ +#define AECH 0x08 /* Exposure Value - AEC MSBs */ +#define COM2 0x09 /* Common control 2 */ +#define PID 0x0A /* Product ID Number MSB */ +#define VER 0x0B /* Product ID Number LSB */ +#define COM3 0x0C /* Common control 3 */ +#define COM4 0x0D /* Common control 4 */ +#define COM5 0x0E /* Common control 5 */ +#define COM6 0x0F /* Common control 6 */ +#define AEC 0x10 /* Exposure Value */ +#define CLKRC 0x11 /* Internal clock */ +#define COM7 0x12 /* Common control 7 */ +#define COM8 0x13 /* Common control 8 */ +#define COM9 0x14 /* Common control 9 */ +#define COM10 0x15 /* Common control 10 */ +#define REG16 0x16 /* Register 16 */ +#define HSTART 0x17 /* Horizontal sensor size */ +#define HSIZE 0x18 /* Horizontal frame (HREF column) end high 8-bit */ +#define VSTART 0x19 /* Vertical frame (row) start high 8-bit */ +#define VSIZE 0x1A /* Vertical sensor size */ +#define PSHFT 0x1B /* Data format - pixel delay select */ +#define MIDH 0x1C /* Manufacturer ID byte - high */ +#define MIDL 0x1D /* Manufacturer ID byte - low */ +#define LAEC 0x1F /* Fine AEC value */ +#define COM11 0x20 /* Common control 11 */ +#define BDBASE 0x22 /* Banding filter Minimum AEC value */ +#define DBSTEP 0x23 /* Banding filter Maximum Setp */ +#define AEW 0x24 /* AGC/AEC - Stable operating region (upper limit) */ +#define AEB 0x25 /* AGC/AEC - Stable operating region (lower limit) */ +#define VPT 0x26 /* AGC/AEC Fast mode operating region */ +#define REG28 0x28 /* Register 28 */ +#define HOUTSIZE 0x29 /* Horizontal data output size MSBs */ +#define EXHCH 0x2A /* Dummy pixel insert MSB */ +#define EXHCL 0x2B /* Dummy pixel insert LSB */ +#define VOUTSIZE 0x2C /* Vertical data output size MSBs */ +#define ADVFL 0x2D /* LSB of insert dummy lines in Vertical direction */ +#define ADVFH 0x2E /* MSG of insert dummy lines in Vertical direction */ +#define YAVE 0x2F /* Y/G Channel Average value */ +#define LUMHTH 0x30 /* Histogram AEC/AGC Luminance high level threshold */ +#define LUMLTH 0x31 /* Histogram AEC/AGC Luminance low level threshold */ +#define HREF 0x32 /* Image start and size control */ +#define DM_LNL 0x33 /* Dummy line low 8 bits */ +#define DM_LNH 0x34 /* Dummy line high 8 bits */ +#define ADOFF_B 0x35 /* AD offset compensation value for B channel */ +#define ADOFF_R 0x36 /* AD offset compensation value for R channel */ +#define ADOFF_GB 0x37 /* AD offset compensation value for Gb channel */ +#define ADOFF_GR 0x38 /* AD offset compensation value for Gr channel */ +#define OFF_B 0x39 /* Analog process B channel offset value */ +#define OFF_R 0x3A /* Analog process R channel offset value */ +#define OFF_GB 0x3B /* Analog process Gb channel offset value */ +#define OFF_GR 0x3C /* Analog process Gr channel offset value */ +#define COM12 0x3D /* Common control 12 */ +#define COM13 0x3E /* Common control 13 */ +#define COM14 0x3F /* Common control 14 */ +#define COM15 0x40 /* Common control 15*/ +#define COM16 0x41 /* Common control 16 */ +#define TGT_B 0x42 /* BLC blue channel target value */ +#define TGT_R 0x43 /* BLC red channel target value */ +#define TGT_GB 0x44 /* BLC Gb channel target value */ +#define TGT_GR 0x45 /* BLC Gr channel target value */ +/* for ov7720 */ +#define LCC0 0x46 /* Lens correction control 0 */ +#define LCC1 0x47 /* Lens correction option 1 - X coordinate */ +#define LCC2 0x48 /* Lens correction option 2 - Y coordinate */ +#define LCC3 0x49 /* Lens correction option 3 */ +#define LCC4 0x4A /* Lens correction option 4 - radius of the circular */ +#define LCC5 0x4B /* Lens correction option 5 */ +#define LCC6 0x4C /* Lens correction option 6 */ +/* for ov7725 */ +#define LC_CTR 0x46 /* Lens correction control */ +#define LC_XC 0x47 /* X coordinate of lens correction center relative */ +#define LC_YC 0x48 /* Y coordinate of lens correction center relative */ +#define LC_COEF 0x49 /* Lens correction coefficient */ +#define LC_RADI 0x4A /* Lens correction radius */ +#define LC_COEFB 0x4B /* Lens B channel compensation coefficient */ +#define LC_COEFR 0x4C /* Lens R channel compensation coefficient */ + +#define FIXGAIN 0x4D /* Analog fix gain amplifer */ +#define AREF0 0x4E /* Sensor reference control */ +#define AREF1 0x4F /* Sensor reference current control */ +#define AREF2 0x50 /* Analog reference control */ +#define AREF3 0x51 /* ADC reference control */ +#define AREF4 0x52 /* ADC reference control */ +#define AREF5 0x53 /* ADC reference control */ +#define AREF6 0x54 /* Analog reference control */ +#define AREF7 0x55 /* Analog reference control */ +#define UFIX 0x60 /* U channel fixed value output */ +#define VFIX 0x61 /* V channel fixed value output */ +#define AWBB_BLK 0x62 /* AWB option for advanced AWB */ +#define AWB_CTRL0 0x63 /* AWB control byte 0 */ +#define DSP_CTRL1 0x64 /* DSP control byte 1 */ +#define DSP_CTRL2 0x65 /* DSP control byte 2 */ +#define DSP_CTRL3 0x66 /* DSP control byte 3 */ +#define DSP_CTRL4 0x67 /* DSP control byte 4 */ +#define AWB_BIAS 0x68 /* AWB BLC level clip */ +#define AWB_CTRL1 0x69 /* AWB control 1 */ +#define AWB_CTRL2 0x6A /* AWB control 2 */ +#define AWB_CTRL3 0x6B /* AWB control 3 */ +#define AWB_CTRL4 0x6C /* AWB control 4 */ +#define AWB_CTRL5 0x6D /* AWB control 5 */ +#define AWB_CTRL6 0x6E /* AWB control 6 */ +#define AWB_CTRL7 0x6F /* AWB control 7 */ +#define AWB_CTRL8 0x70 /* AWB control 8 */ +#define AWB_CTRL9 0x71 /* AWB control 9 */ +#define AWB_CTRL10 0x72 /* AWB control 10 */ +#define AWB_CTRL11 0x73 /* AWB control 11 */ +#define AWB_CTRL12 0x74 /* AWB control 12 */ +#define AWB_CTRL13 0x75 /* AWB control 13 */ +#define AWB_CTRL14 0x76 /* AWB control 14 */ +#define AWB_CTRL15 0x77 /* AWB control 15 */ +#define AWB_CTRL16 0x78 /* AWB control 16 */ +#define AWB_CTRL17 0x79 /* AWB control 17 */ +#define AWB_CTRL18 0x7A /* AWB control 18 */ +#define AWB_CTRL19 0x7B /* AWB control 19 */ +#define AWB_CTRL20 0x7C /* AWB control 20 */ +#define AWB_CTRL21 0x7D /* AWB control 21 */ +#define GAM1 0x7E /* Gamma Curve 1st segment input end point */ +#define GAM2 0x7F /* Gamma Curve 2nd segment input end point */ +#define GAM3 0x80 /* Gamma Curve 3rd segment input end point */ +#define GAM4 0x81 /* Gamma Curve 4th segment input end point */ +#define GAM5 0x82 /* Gamma Curve 5th segment input end point */ +#define GAM6 0x83 /* Gamma Curve 6th segment input end point */ +#define GAM7 0x84 /* Gamma Curve 7th segment input end point */ +#define GAM8 0x85 /* Gamma Curve 8th segment input end point */ +#define GAM9 0x86 /* Gamma Curve 9th segment input end point */ +#define GAM10 0x87 /* Gamma Curve 10th segment input end point */ +#define GAM11 0x88 /* Gamma Curve 11th segment input end point */ +#define GAM12 0x89 /* Gamma Curve 12th segment input end point */ +#define GAM13 0x8A /* Gamma Curve 13th segment input end point */ +#define GAM14 0x8B /* Gamma Curve 14th segment input end point */ +#define GAM15 0x8C /* Gamma Curve 15th segment input end point */ +#define SLOP 0x8D /* Gamma curve highest segment slope */ +#define DNSTH 0x8E /* De-noise threshold */ +#define EDGE_STRNGT 0x8F /* Edge strength control when manual mode */ +#define EDGE_TRSHLD 0x90 /* Edge threshold control when manual mode */ +#define DNSOFF 0x91 /* Auto De-noise threshold control */ +#define EDGE_UPPER 0x92 /* Edge strength upper limit when Auto mode */ +#define EDGE_LOWER 0x93 /* Edge strength lower limit when Auto mode */ +#define MTX1 0x94 /* Matrix coefficient 1 */ +#define MTX2 0x95 /* Matrix coefficient 2 */ +#define MTX3 0x96 /* Matrix coefficient 3 */ +#define MTX4 0x97 /* Matrix coefficient 4 */ +#define MTX5 0x98 /* Matrix coefficient 5 */ +#define MTX6 0x99 /* Matrix coefficient 6 */ +#define MTX_CTRL 0x9A /* Matrix control */ +#define BRIGHT 0x9B /* Brightness control */ +#define CNTRST 0x9C /* Contrast contrast */ +#define CNTRST_CTRL 0x9D /* Contrast contrast center */ +#define UVAD_J0 0x9E /* Auto UV adjust contrast 0 */ +#define UVAD_J1 0x9F /* Auto UV adjust contrast 1 */ +#define SCAL0 0xA0 /* Scaling control 0 */ +#define SCAL1 0xA1 /* Scaling control 1 */ +#define SCAL2 0xA2 /* Scaling control 2 */ +#define FIFODLYM 0xA3 /* FIFO manual mode delay control */ +#define FIFODLYA 0xA4 /* FIFO auto mode delay control */ +#define SDE 0xA6 /* Special digital effect control */ +#define USAT 0xA7 /* U component saturation control */ +#define VSAT 0xA8 /* V component saturation control */ +/* for ov7720 */ +#define HUE0 0xA9 /* Hue control 0 */ +#define HUE1 0xAA /* Hue control 1 */ +/* for ov7725 */ +#define HUECOS 0xA9 /* Cosine value */ +#define HUESIN 0xAA /* Sine value */ + +#define SIGN 0xAB /* Sign bit for Hue and contrast */ +#define DSPAUTO 0xAC /* DSP auto function ON/OFF control */ + +/* + * register detail + */ + +/* COM2 */ +#define SOFT_SLEEP_MODE 0x10 /* Soft sleep mode */ + /* Output drive capability */ +#define OCAP_1x 0x00 /* 1x */ +#define OCAP_2x 0x01 /* 2x */ +#define OCAP_3x 0x02 /* 3x */ +#define OCAP_4x 0x03 /* 4x */ + +/* COM3 */ +#define SWAP_MASK (SWAP_RGB | SWAP_YUV | SWAP_ML) +#define IMG_MASK (VFLIP_IMG | HFLIP_IMG) + +#define VFLIP_IMG 0x80 /* Vertical flip image ON/OFF selection */ +#define HFLIP_IMG 0x40 /* Horizontal mirror image ON/OFF selection */ +#define SWAP_RGB 0x20 /* Swap B/R output sequence in RGB mode */ +#define SWAP_YUV 0x10 /* Swap Y/UV output sequence in YUV mode */ +#define SWAP_ML 0x08 /* Swap output MSB/LSB */ + /* Tri-state option for output clock */ +#define NOTRI_CLOCK 0x04 /* 0: Tri-state at this period */ + /* 1: No tri-state at this period */ + /* Tri-state option for output data */ +#define NOTRI_DATA 0x02 /* 0: Tri-state at this period */ + /* 1: No tri-state at this period */ +#define SCOLOR_TEST 0x01 /* Sensor color bar test pattern */ + +/* COM4 */ + /* PLL frequency control */ +#define PLL_BYPASS 0x00 /* 00: Bypass PLL */ +#define PLL_4x 0x40 /* 01: PLL 4x */ +#define PLL_6x 0x80 /* 10: PLL 6x */ +#define PLL_8x 0xc0 /* 11: PLL 8x */ + /* AEC evaluate window */ +#define AEC_FULL 0x00 /* 00: Full window */ +#define AEC_1p2 0x10 /* 01: 1/2 window */ +#define AEC_1p4 0x20 /* 10: 1/4 window */ +#define AEC_2p3 0x30 /* 11: Low 2/3 window */ + +/* COM5 */ +#define AFR_ON_OFF 0x80 /* Auto frame rate control ON/OFF selection */ +#define AFR_SPPED 0x40 /* Auto frame rate control speed selection */ + /* Auto frame rate max rate control */ +#define AFR_NO_RATE 0x00 /* No reduction of frame rate */ +#define AFR_1p2 0x10 /* Max reduction to 1/2 frame rate */ +#define AFR_1p4 0x20 /* Max reduction to 1/4 frame rate */ +#define AFR_1p8 0x30 /* Max reduction to 1/8 frame rate */ + /* Auto frame rate active point control */ +#define AF_2x 0x00 /* Add frame when AGC reaches 2x gain */ +#define AF_4x 0x04 /* Add frame when AGC reaches 4x gain */ +#define AF_8x 0x08 /* Add frame when AGC reaches 8x gain */ +#define AF_16x 0x0c /* Add frame when AGC reaches 16x gain */ + /* AEC max step control */ +#define AEC_NO_LIMIT 0x01 /* 0 : AEC incease step has limit */ + /* 1 : No limit to AEC increase step */ + +/* COM7 */ + /* SCCB Register Reset */ +#define SCCB_RESET 0x80 /* 0 : No change */ + /* 1 : Resets all registers to default */ + /* Resolution selection */ +#define SLCT_MASK 0x40 /* Mask of VGA or QVGA */ +#define SLCT_VGA 0x00 /* 0 : VGA */ +#define SLCT_QVGA 0x40 /* 1 : QVGA */ +#define ITU656_ON_OFF 0x20 /* ITU656 protocol ON/OFF selection */ +#define SENSOR_RAW 0x10 /* Sensor RAW */ + /* RGB output format control */ +#define FMT_MASK 0x0c /* Mask of color format */ +#define FMT_GBR422 0x00 /* 00 : GBR 4:2:2 */ +#define FMT_RGB565 0x04 /* 01 : RGB 565 */ +#define FMT_RGB555 0x08 /* 10 : RGB 555 */ +#define FMT_RGB444 0x0c /* 11 : RGB 444 */ + /* Output format control */ +#define OFMT_MASK 0x03 /* Mask of output format */ +#define OFMT_YUV 0x00 /* 00 : YUV */ +#define OFMT_P_BRAW 0x01 /* 01 : Processed Bayer RAW */ +#define OFMT_RGB 0x02 /* 10 : RGB */ +#define OFMT_BRAW 0x03 /* 11 : Bayer RAW */ + +/* COM8 */ +#define FAST_ALGO 0x80 /* Enable fast AGC/AEC algorithm */ + /* AEC Setp size limit */ +#define UNLMT_STEP 0x40 /* 0 : Step size is limited */ + /* 1 : Unlimited step size */ +#define BNDF_ON_OFF 0x20 /* Banding filter ON/OFF */ +#define AEC_BND 0x10 /* Enable AEC below banding value */ +#define AEC_ON_OFF 0x08 /* Fine AEC ON/OFF control */ +#define AGC_ON 0x04 /* AGC Enable */ +#define AWB_ON 0x02 /* AWB Enable */ +#define AEC_ON 0x01 /* AEC Enable */ + +/* COM9 */ +#define BASE_AECAGC 0x80 /* Histogram or average based AEC/AGC */ + /* Automatic gain ceiling - maximum AGC value */ +#define GAIN_2x 0x00 /* 000 : 2x */ +#define GAIN_4x 0x10 /* 001 : 4x */ +#define GAIN_8x 0x20 /* 010 : 8x */ +#define GAIN_16x 0x30 /* 011 : 16x */ +#define GAIN_32x 0x40 /* 100 : 32x */ +#define GAIN_64x 0x50 /* 101 : 64x */ +#define GAIN_128x 0x60 /* 110 : 128x */ +#define DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */ +#define DROP_HREF 0x02 /* Drop HREF output of corrupt frame */ + +/* COM11 */ +#define SGLF_ON_OFF 0x02 /* Single frame ON/OFF selection */ +#define SGLF_TRIG 0x01 /* Single frame transfer trigger */ + +/* HREF */ +#define HREF_VSTART_SHIFT 6 /* VSTART LSB */ +#define HREF_HSTART_SHIFT 4 /* HSTART 2 LSBs */ +#define HREF_VSIZE_SHIFT 2 /* VSIZE LSB */ +#define HREF_HSIZE_SHIFT 0 /* HSIZE 2 LSBs */ + +/* EXHCH */ +#define EXHCH_VSIZE_SHIFT 2 /* VOUTSIZE LSB */ +#define EXHCH_HSIZE_SHIFT 0 /* HOUTSIZE 2 LSBs */ + +/* DSP_CTRL1 */ +#define FIFO_ON 0x80 /* FIFO enable/disable selection */ +#define UV_ON_OFF 0x40 /* UV adjust function ON/OFF selection */ +#define YUV444_2_422 0x20 /* YUV444 to 422 UV channel option selection */ +#define CLR_MTRX_ON_OFF 0x10 /* Color matrix ON/OFF selection */ +#define INTPLT_ON_OFF 0x08 /* Interpolation ON/OFF selection */ +#define GMM_ON_OFF 0x04 /* Gamma function ON/OFF selection */ +#define AUTO_BLK_ON_OFF 0x02 /* Black defect auto correction ON/OFF */ +#define AUTO_WHT_ON_OFF 0x01 /* White define auto correction ON/OFF */ + +/* DSP_CTRL3 */ +#define UV_MASK 0x80 /* UV output sequence option */ +#define UV_ON 0x80 /* ON */ +#define UV_OFF 0x00 /* OFF */ +#define CBAR_MASK 0x20 /* DSP Color bar mask */ +#define CBAR_ON 0x20 /* ON */ +#define CBAR_OFF 0x00 /* OFF */ + +/* DSP_CTRL4 */ +#define DSP_OFMT_YUV 0x00 +#define DSP_OFMT_RGB 0x00 +#define DSP_OFMT_RAW8 0x02 +#define DSP_OFMT_RAW10 0x03 + +/* DSPAUTO (DSP Auto Function ON/OFF Control) */ +#define AWB_ACTRL 0x80 /* AWB auto threshold control */ +#define DENOISE_ACTRL 0x40 /* De-noise auto threshold control */ +#define EDGE_ACTRL 0x20 /* Edge enhancement auto strength control */ +#define UV_ACTRL 0x10 /* UV adjust auto slope control */ +#define SCAL0_ACTRL 0x08 /* Auto scaling factor control */ +#define SCAL1_2_ACTRL 0x04 /* Auto scaling factor control */ + +#define VGA_WIDTH 640 +#define VGA_HEIGHT 480 +#define QVGA_WIDTH 320 +#define QVGA_HEIGHT 240 +#define OV772X_MAX_WIDTH VGA_WIDTH +#define OV772X_MAX_HEIGHT VGA_HEIGHT + +/* + * ID + */ +#define OV7720 0x7720 +#define OV7725 0x7721 +#define VERSION(pid, ver) ((pid<<8)|(ver&0xFF)) + +/* + * struct + */ + +struct ov772x_color_format { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; + u8 dsp3; + u8 dsp4; + u8 com3; + u8 com7; +}; + +struct ov772x_win_size { + char *name; + unsigned char com7_bit; + struct v4l2_rect rect; +}; + +struct ov772x_priv { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct ov772x_camera_info *info; + const struct ov772x_color_format *cfmt; + const struct ov772x_win_size *win; + int model; + unsigned short flag_vflip:1; + unsigned short flag_hflip:1; + /* band_filter = COM8[5] ? 256 - BDBASE : 0 */ + unsigned short band_filter; +}; + +/* + * supported color format list + */ +static const struct ov772x_color_format ov772x_cfmts[] = { + { + .code = V4L2_MBUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = SWAP_YUV, + .com7 = OFMT_YUV, + }, + { + .code = V4L2_MBUS_FMT_YVYU8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .dsp3 = UV_ON, + .dsp4 = DSP_OFMT_YUV, + .com3 = SWAP_YUV, + .com7 = OFMT_YUV, + }, + { + .code = V4L2_MBUS_FMT_UYVY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = 0x0, + .com7 = OFMT_YUV, + }, + { + .code = V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = SWAP_RGB, + .com7 = FMT_RGB555 | OFMT_RGB, + }, + { + .code = V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = 0x0, + .com7 = FMT_RGB555 | OFMT_RGB, + }, + { + .code = V4L2_MBUS_FMT_RGB565_2X8_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = SWAP_RGB, + .com7 = FMT_RGB565 | OFMT_RGB, + }, + { + .code = V4L2_MBUS_FMT_RGB565_2X8_BE, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_YUV, + .com3 = 0x0, + .com7 = FMT_RGB565 | OFMT_RGB, + }, + { + /* Setting DSP4 to DSP_OFMT_RAW8 still gives 10-bit output, + * regardless of the COM7 value. We can thus only support 10-bit + * Bayer until someone figures it out. + */ + .code = V4L2_MBUS_FMT_SBGGR10_1X10, + .colorspace = V4L2_COLORSPACE_SRGB, + .dsp3 = 0x0, + .dsp4 = DSP_OFMT_RAW10, + .com3 = 0x0, + .com7 = SENSOR_RAW | OFMT_BRAW, + }, +}; + + +/* + * window size list + */ + +static const struct ov772x_win_size ov772x_win_sizes[] = { + { + .name = "VGA", + .com7_bit = SLCT_VGA, + .rect = { + .left = 140, + .top = 14, + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + }, + }, { + .name = "QVGA", + .com7_bit = SLCT_QVGA, + .rect = { + .left = 252, + .top = 6, + .width = QVGA_WIDTH, + .height = QVGA_HEIGHT, + }, + }, +}; + +/* + * general function + */ + +static struct ov772x_priv *to_ov772x(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov772x_priv, subdev); +} + +static inline int ov772x_read(struct i2c_client *client, u8 addr) +{ + return i2c_smbus_read_byte_data(client, addr); +} + +static inline int ov772x_write(struct i2c_client *client, u8 addr, u8 value) +{ + return i2c_smbus_write_byte_data(client, addr, value); +} + +static int ov772x_mask_set(struct i2c_client *client, u8 command, u8 mask, + u8 set) +{ + s32 val = ov772x_read(client, command); + if (val < 0) + return val; + + val &= ~mask; + val |= set & mask; + + return ov772x_write(client, command, val); +} + +static int ov772x_reset(struct i2c_client *client) +{ + int ret; + + ret = ov772x_write(client, COM7, SCCB_RESET); + if (ret < 0) + return ret; + + msleep(1); + + return ov772x_mask_set(client, COM2, SOFT_SLEEP_MODE, SOFT_SLEEP_MODE); +} + +/* + * soc_camera_ops function + */ + +static int ov772x_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov772x_priv *priv = to_ov772x(sd); + + if (!enable) { + ov772x_mask_set(client, COM2, SOFT_SLEEP_MODE, SOFT_SLEEP_MODE); + return 0; + } + + ov772x_mask_set(client, COM2, SOFT_SLEEP_MODE, 0); + + dev_dbg(&client->dev, "format %d, win %s\n", + priv->cfmt->code, priv->win->name); + + return 0; +} + +static int ov772x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov772x_priv *priv = container_of(ctrl->handler, + struct ov772x_priv, hdl); + struct v4l2_subdev *sd = &priv->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + u8 val; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + val = ctrl->val ? VFLIP_IMG : 0x00; + priv->flag_vflip = ctrl->val; + if (priv->info->flags & OV772X_FLAG_VFLIP) + val ^= VFLIP_IMG; + return ov772x_mask_set(client, COM3, VFLIP_IMG, val); + case V4L2_CID_HFLIP: + val = ctrl->val ? HFLIP_IMG : 0x00; + priv->flag_hflip = ctrl->val; + if (priv->info->flags & OV772X_FLAG_HFLIP) + val ^= HFLIP_IMG; + return ov772x_mask_set(client, COM3, HFLIP_IMG, val); + case V4L2_CID_BAND_STOP_FILTER: + if (!ctrl->val) { + /* Switch the filter off, it is on now */ + ret = ov772x_mask_set(client, BDBASE, 0xff, 0xff); + if (!ret) + ret = ov772x_mask_set(client, COM8, + BNDF_ON_OFF, 0); + } else { + /* Switch the filter on, set AEC low limit */ + val = 256 - ctrl->val; + ret = ov772x_mask_set(client, COM8, + BNDF_ON_OFF, BNDF_ON_OFF); + if (!ret) + ret = ov772x_mask_set(client, BDBASE, + 0xff, val); + } + if (!ret) + priv->band_filter = ctrl->val; + return ret; + } + + return -EINVAL; +} + +static int ov772x_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct ov772x_priv *priv = to_ov772x(sd); + + id->ident = priv->model; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov772x_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + reg->size = 1; + if (reg->reg > 0xff) + return -EINVAL; + + ret = ov772x_read(client, reg->reg); + if (ret < 0) + return ret; + + reg->val = (__u64)ret; + + return 0; +} + +static int ov772x_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0xff || + reg->val > 0xff) + return -EINVAL; + + return ov772x_write(client, reg->reg, reg->val); +} +#endif + +static int ov772x_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +static const struct ov772x_win_size *ov772x_select_win(u32 width, u32 height) +{ + const struct ov772x_win_size *win = &ov772x_win_sizes[0]; + u32 best_diff = UINT_MAX; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ov772x_win_sizes); ++i) { + u32 diff = abs(width - ov772x_win_sizes[i].rect.width) + + abs(height - ov772x_win_sizes[i].rect.height); + if (diff < best_diff) { + best_diff = diff; + win = &ov772x_win_sizes[i]; + } + } + + return win; +} + +static void ov772x_select_params(const struct v4l2_mbus_framefmt *mf, + const struct ov772x_color_format **cfmt, + const struct ov772x_win_size **win) +{ + unsigned int i; + + /* Select a format. */ + *cfmt = &ov772x_cfmts[0]; + + for (i = 0; i < ARRAY_SIZE(ov772x_cfmts); i++) { + if (mf->code == ov772x_cfmts[i].code) { + *cfmt = &ov772x_cfmts[i]; + break; + } + } + + /* Select a window size. */ + *win = ov772x_select_win(mf->width, mf->height); +} + +static int ov772x_set_params(struct ov772x_priv *priv, + const struct ov772x_color_format *cfmt, + const struct ov772x_win_size *win) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev); + int ret; + u8 val; + + /* + * reset hardware + */ + ov772x_reset(client); + + /* + * Edge Ctrl + */ + if (priv->info->edgectrl.strength & OV772X_MANUAL_EDGE_CTRL) { + + /* + * Manual Edge Control Mode + * + * Edge auto strength bit is set by default. + * Remove it when manual mode. + */ + + ret = ov772x_mask_set(client, DSPAUTO, EDGE_ACTRL, 0x00); + if (ret < 0) + goto ov772x_set_fmt_error; + + ret = ov772x_mask_set(client, + EDGE_TRSHLD, OV772X_EDGE_THRESHOLD_MASK, + priv->info->edgectrl.threshold); + if (ret < 0) + goto ov772x_set_fmt_error; + + ret = ov772x_mask_set(client, + EDGE_STRNGT, OV772X_EDGE_STRENGTH_MASK, + priv->info->edgectrl.strength); + if (ret < 0) + goto ov772x_set_fmt_error; + + } else if (priv->info->edgectrl.upper > priv->info->edgectrl.lower) { + /* + * Auto Edge Control Mode + * + * set upper and lower limit + */ + ret = ov772x_mask_set(client, + EDGE_UPPER, OV772X_EDGE_UPPER_MASK, + priv->info->edgectrl.upper); + if (ret < 0) + goto ov772x_set_fmt_error; + + ret = ov772x_mask_set(client, + EDGE_LOWER, OV772X_EDGE_LOWER_MASK, + priv->info->edgectrl.lower); + if (ret < 0) + goto ov772x_set_fmt_error; + } + + /* Format and window size */ + ret = ov772x_write(client, HSTART, win->rect.left >> 2); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = ov772x_write(client, HSIZE, win->rect.width >> 2); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = ov772x_write(client, VSTART, win->rect.top >> 1); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = ov772x_write(client, VSIZE, win->rect.height >> 1); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = ov772x_write(client, HOUTSIZE, win->rect.width >> 2); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = ov772x_write(client, VOUTSIZE, win->rect.height >> 1); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = ov772x_write(client, HREF, + ((win->rect.top & 1) << HREF_VSTART_SHIFT) | + ((win->rect.left & 3) << HREF_HSTART_SHIFT) | + ((win->rect.height & 1) << HREF_VSIZE_SHIFT) | + ((win->rect.width & 3) << HREF_HSIZE_SHIFT)); + if (ret < 0) + goto ov772x_set_fmt_error; + ret = ov772x_write(client, EXHCH, + ((win->rect.height & 1) << EXHCH_VSIZE_SHIFT) | + ((win->rect.width & 3) << EXHCH_HSIZE_SHIFT)); + if (ret < 0) + goto ov772x_set_fmt_error; + + /* + * set DSP_CTRL3 + */ + val = cfmt->dsp3; + if (val) { + ret = ov772x_mask_set(client, + DSP_CTRL3, UV_MASK, val); + if (ret < 0) + goto ov772x_set_fmt_error; + } + + /* DSP_CTRL4: AEC reference point and DSP output format. */ + if (cfmt->dsp4) { + ret = ov772x_write(client, DSP_CTRL4, cfmt->dsp4); + if (ret < 0) + goto ov772x_set_fmt_error; + } + + /* + * set COM3 + */ + val = cfmt->com3; + if (priv->info->flags & OV772X_FLAG_VFLIP) + val |= VFLIP_IMG; + if (priv->info->flags & OV772X_FLAG_HFLIP) + val |= HFLIP_IMG; + if (priv->flag_vflip) + val ^= VFLIP_IMG; + if (priv->flag_hflip) + val ^= HFLIP_IMG; + + ret = ov772x_mask_set(client, + COM3, SWAP_MASK | IMG_MASK, val); + if (ret < 0) + goto ov772x_set_fmt_error; + + /* COM7: Sensor resolution and output format control. */ + ret = ov772x_write(client, COM7, win->com7_bit | cfmt->com7); + if (ret < 0) + goto ov772x_set_fmt_error; + + /* + * set COM8 + */ + if (priv->band_filter) { + ret = ov772x_mask_set(client, COM8, BNDF_ON_OFF, 1); + if (!ret) + ret = ov772x_mask_set(client, BDBASE, + 0xff, 256 - priv->band_filter); + if (ret < 0) + goto ov772x_set_fmt_error; + } + + return ret; + +ov772x_set_fmt_error: + + ov772x_reset(client); + + return ret; +} + +static int ov772x_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + a->c.left = 0; + a->c.top = 0; + a->c.width = VGA_WIDTH; + a->c.height = VGA_HEIGHT; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int ov772x_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = OV772X_MAX_WIDTH; + a->bounds.height = OV772X_MAX_HEIGHT; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int ov772x_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct ov772x_priv *priv = to_ov772x(sd); + + mf->width = priv->win->rect.width; + mf->height = priv->win->rect.height; + mf->code = priv->cfmt->code; + mf->colorspace = priv->cfmt->colorspace; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ov772x_s_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + struct ov772x_priv *priv = to_ov772x(sd); + const struct ov772x_color_format *cfmt; + const struct ov772x_win_size *win; + int ret; + + ov772x_select_params(mf, &cfmt, &win); + + ret = ov772x_set_params(priv, cfmt, win); + if (ret < 0) + return ret; + + priv->win = win; + priv->cfmt = cfmt; + + mf->code = cfmt->code; + mf->width = win->rect.width; + mf->height = win->rect.height; + mf->field = V4L2_FIELD_NONE; + mf->colorspace = cfmt->colorspace; + + return 0; +} + +static int ov772x_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + const struct ov772x_color_format *cfmt; + const struct ov772x_win_size *win; + + ov772x_select_params(mf, &cfmt, &win); + + mf->code = cfmt->code; + mf->width = win->rect.width; + mf->height = win->rect.height; + mf->field = V4L2_FIELD_NONE; + mf->colorspace = cfmt->colorspace; + + return 0; +} + +static int ov772x_video_probe(struct ov772x_priv *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev); + u8 pid, ver; + const char *devname; + int ret; + + ret = ov772x_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * check and show product ID and manufacturer ID + */ + pid = ov772x_read(client, PID); + ver = ov772x_read(client, VER); + + switch (VERSION(pid, ver)) { + case OV7720: + devname = "ov7720"; + priv->model = V4L2_IDENT_OV7720; + break; + case OV7725: + devname = "ov7725"; + priv->model = V4L2_IDENT_OV7725; + break; + default: + dev_err(&client->dev, + "Product ID error %x:%x\n", pid, ver); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, + "%s Product ID %0x:%0x Manufacturer ID %x:%x\n", + devname, + pid, + ver, + ov772x_read(client, MIDH), + ov772x_read(client, MIDL)); + ret = v4l2_ctrl_handler_setup(&priv->hdl); + +done: + ov772x_s_power(&priv->subdev, 0); + return ret; +} + +static const struct v4l2_ctrl_ops ov772x_ctrl_ops = { + .s_ctrl = ov772x_s_ctrl, +}; + +static struct v4l2_subdev_core_ops ov772x_subdev_core_ops = { + .g_chip_ident = ov772x_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov772x_g_register, + .s_register = ov772x_s_register, +#endif + .s_power = ov772x_s_power, +}; + +static int ov772x_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(ov772x_cfmts)) + return -EINVAL; + + *code = ov772x_cfmts[index].code; + return 0; +} + +static int ov772x_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static struct v4l2_subdev_video_ops ov772x_subdev_video_ops = { + .s_stream = ov772x_s_stream, + .g_mbus_fmt = ov772x_g_fmt, + .s_mbus_fmt = ov772x_s_fmt, + .try_mbus_fmt = ov772x_try_fmt, + .cropcap = ov772x_cropcap, + .g_crop = ov772x_g_crop, + .enum_mbus_fmt = ov772x_enum_fmt, + .g_mbus_config = ov772x_g_mbus_config, +}; + +static struct v4l2_subdev_ops ov772x_subdev_ops = { + .core = &ov772x_subdev_core_ops, + .video = &ov772x_subdev_video_ops, +}; + +/* + * i2c_driver function + */ + +static int ov772x_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov772x_priv *priv; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret; + + if (!icl || !icl->priv) { + dev_err(&client->dev, "OV772X: missing platform data!\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&adapter->dev, + "I2C-Adapter doesn't support " + "I2C_FUNC_SMBUS_BYTE_DATA\n"); + return -EIO; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = icl->priv; + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov772x_subdev_ops); + v4l2_ctrl_handler_init(&priv->hdl, 3); + v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops, + V4L2_CID_BAND_STOP_FILTER, 0, 256, 1, 0); + priv->subdev.ctrl_handler = &priv->hdl; + if (priv->hdl.error) { + ret = priv->hdl.error; + goto done; + } + + ret = ov772x_video_probe(priv); + if (ret < 0) + goto done; + + priv->cfmt = &ov772x_cfmts[0]; + priv->win = &ov772x_win_sizes[0]; + +done: + if (ret) { + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + } + return ret; +} + +static int ov772x_remove(struct i2c_client *client) +{ + struct ov772x_priv *priv = to_ov772x(i2c_get_clientdata(client)); + + v4l2_device_unregister_subdev(&priv->subdev); + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + return 0; +} + +static const struct i2c_device_id ov772x_id[] = { + { "ov772x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov772x_id); + +static struct i2c_driver ov772x_i2c_driver = { + .driver = { + .name = "ov772x", + }, + .probe = ov772x_probe, + .remove = ov772x_remove, + .id_table = ov772x_id, +}; + +module_i2c_driver(ov772x_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for ov772x"); +MODULE_AUTHOR("Kuninori Morimoto"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/soc_camera/ov9640.c b/drivers/media/i2c/soc_camera/ov9640.c new file mode 100644 index 000000000000..b323684eaf77 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov9640.c @@ -0,0 +1,763 @@ +/* + * OmniVision OV96xx Camera Driver + * + * Copyright (C) 2009 Marek Vasut + * + * Based on ov772x camera driver: + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto + * + * Based on ov7670 and soc_camera_platform driver, + * + * Copyright 2006-7 Jonathan Corbet + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ov9640.h" + +#define to_ov9640_sensor(sd) container_of(sd, struct ov9640_priv, subdev) + +/* default register setup */ +static const struct ov9640_reg ov9640_regs_dflt[] = { + { OV9640_COM5, OV9640_COM5_SYSCLK | OV9640_COM5_LONGEXP }, + { OV9640_COM6, OV9640_COM6_OPT_BLC | OV9640_COM6_ADBLC_BIAS | + OV9640_COM6_FMT_RST | OV9640_COM6_ADBLC_OPTEN }, + { OV9640_PSHFT, OV9640_PSHFT_VAL(0x01) }, + { OV9640_ACOM, OV9640_ACOM_2X_ANALOG | OV9640_ACOM_RSVD }, + { OV9640_TSLB, OV9640_TSLB_YUYV_UYVY }, + { OV9640_COM16, OV9640_COM16_RB_AVG }, + + /* Gamma curve P */ + { 0x6c, 0x40 }, { 0x6d, 0x30 }, { 0x6e, 0x4b }, { 0x6f, 0x60 }, + { 0x70, 0x70 }, { 0x71, 0x70 }, { 0x72, 0x70 }, { 0x73, 0x70 }, + { 0x74, 0x60 }, { 0x75, 0x60 }, { 0x76, 0x50 }, { 0x77, 0x48 }, + { 0x78, 0x3a }, { 0x79, 0x2e }, { 0x7a, 0x28 }, { 0x7b, 0x22 }, + + /* Gamma curve T */ + { 0x7c, 0x04 }, { 0x7d, 0x07 }, { 0x7e, 0x10 }, { 0x7f, 0x28 }, + { 0x80, 0x36 }, { 0x81, 0x44 }, { 0x82, 0x52 }, { 0x83, 0x60 }, + { 0x84, 0x6c }, { 0x85, 0x78 }, { 0x86, 0x8c }, { 0x87, 0x9e }, + { 0x88, 0xbb }, { 0x89, 0xd2 }, { 0x8a, 0xe6 }, +}; + +/* Configurations + * NOTE: for YUV, alter the following registers: + * COM12 |= OV9640_COM12_YUV_AVG + * + * for RGB, alter the following registers: + * COM7 |= OV9640_COM7_RGB + * COM13 |= OV9640_COM13_RGB_AVG + * COM15 |= proper RGB color encoding mode + */ +static const struct ov9640_reg ov9640_regs_qqcif[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x0f) }, + { OV9640_COM1, OV9640_COM1_QQFMT | OV9640_COM1_HREF_2SKIP }, + { OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD }, + { OV9640_COM7, OV9640_COM7_QCIF }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_qqvga[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x07) }, + { OV9640_COM1, OV9640_COM1_QQFMT | OV9640_COM1_HREF_2SKIP }, + { OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD }, + { OV9640_COM7, OV9640_COM7_QVGA }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_qcif[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x07) }, + { OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD }, + { OV9640_COM7, OV9640_COM7_QCIF }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_qvga[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x03) }, + { OV9640_COM4, OV9640_COM4_QQ_VP | OV9640_COM4_RSVD }, + { OV9640_COM7, OV9640_COM7_QVGA }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_cif[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x03) }, + { OV9640_COM3, OV9640_COM3_VP }, + { OV9640_COM7, OV9640_COM7_CIF }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_vga[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x01) }, + { OV9640_COM3, OV9640_COM3_VP }, + { OV9640_COM7, OV9640_COM7_VGA }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_sxga[] = { + { OV9640_CLKRC, OV9640_CLKRC_DPLL_EN | OV9640_CLKRC_DIV(0x01) }, + { OV9640_COM3, OV9640_COM3_VP }, + { OV9640_COM7, 0 }, + { OV9640_COM12, OV9640_COM12_RSVD }, + { OV9640_COM13, OV9640_COM13_GAMMA_RAW | OV9640_COM13_MATRIX_EN }, + { OV9640_COM15, OV9640_COM15_OR_10F0 }, +}; + +static const struct ov9640_reg ov9640_regs_yuv[] = { + { OV9640_MTX1, 0x58 }, + { OV9640_MTX2, 0x48 }, + { OV9640_MTX3, 0x10 }, + { OV9640_MTX4, 0x28 }, + { OV9640_MTX5, 0x48 }, + { OV9640_MTX6, 0x70 }, + { OV9640_MTX7, 0x40 }, + { OV9640_MTX8, 0x40 }, + { OV9640_MTX9, 0x40 }, + { OV9640_MTXS, 0x0f }, +}; + +static const struct ov9640_reg ov9640_regs_rgb[] = { + { OV9640_MTX1, 0x71 }, + { OV9640_MTX2, 0x3e }, + { OV9640_MTX3, 0x0c }, + { OV9640_MTX4, 0x33 }, + { OV9640_MTX5, 0x72 }, + { OV9640_MTX6, 0x00 }, + { OV9640_MTX7, 0x2b }, + { OV9640_MTX8, 0x66 }, + { OV9640_MTX9, 0xd2 }, + { OV9640_MTXS, 0x65 }, +}; + +static enum v4l2_mbus_pixelcode ov9640_codes[] = { + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE, + V4L2_MBUS_FMT_RGB565_2X8_LE, +}; + +/* read a register */ +static int ov9640_reg_read(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret; + u8 data = reg; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + msg.flags = I2C_M_RD; + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + *val = data; + return 0; + +err: + dev_err(&client->dev, "Failed reading register 0x%02x!\n", reg); + return ret; +} + +/* write a register */ +static int ov9640_reg_write(struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + u8 _val; + unsigned char data[2] = { reg, val }; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg); + return ret; + } + + /* we have to read the register back ... no idea why, maybe HW bug */ + ret = ov9640_reg_read(client, reg, &_val); + if (ret) + dev_err(&client->dev, + "Failed reading back register 0x%02x!\n", reg); + + return 0; +} + + +/* Read a register, alter its bits, write it back */ +static int ov9640_reg_rmw(struct i2c_client *client, u8 reg, u8 set, u8 unset) +{ + u8 val; + int ret; + + ret = ov9640_reg_read(client, reg, &val); + if (ret) { + dev_err(&client->dev, + "[Read]-Modify-Write of register %02x failed!\n", reg); + return val; + } + + val |= set; + val &= ~unset; + + ret = ov9640_reg_write(client, reg, val); + if (ret) + dev_err(&client->dev, + "Read-Modify-[Write] of register %02x failed!\n", reg); + + return ret; +} + +/* Soft reset the camera. This has nothing to do with the RESET pin! */ +static int ov9640_reset(struct i2c_client *client) +{ + int ret; + + ret = ov9640_reg_write(client, OV9640_COM7, OV9640_COM7_SCCB_RESET); + if (ret) + dev_err(&client->dev, + "An error occurred while entering soft reset!\n"); + + return ret; +} + +/* Start/Stop streaming from the device */ +static int ov9640_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +/* Set status of additional camera capabilities */ +static int ov9640_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov9640_priv *priv = container_of(ctrl->handler, struct ov9640_priv, hdl); + struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev); + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->val) + return ov9640_reg_rmw(client, OV9640_MVFP, + OV9640_MVFP_V, 0); + return ov9640_reg_rmw(client, OV9640_MVFP, 0, OV9640_MVFP_V); + case V4L2_CID_HFLIP: + if (ctrl->val) + return ov9640_reg_rmw(client, OV9640_MVFP, + OV9640_MVFP_H, 0); + return ov9640_reg_rmw(client, OV9640_MVFP, 0, OV9640_MVFP_H); + } + return -EINVAL; +} + +/* Get chip identification */ +static int ov9640_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct ov9640_priv *priv = to_ov9640_sensor(sd); + + id->ident = priv->model; + id->revision = priv->revision; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov9640_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val; + + if (reg->reg & ~0xff) + return -EINVAL; + + reg->size = 1; + + ret = ov9640_reg_read(client, reg->reg, &val); + if (ret) + return ret; + + reg->val = (__u64)val; + + return 0; +} + +static int ov9640_set_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg & ~0xff || reg->val & ~0xff) + return -EINVAL; + + return ov9640_reg_write(client, reg->reg, reg->val); +} +#endif + +static int ov9640_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +/* select nearest higher resolution for capture */ +static void ov9640_res_roundup(u32 *width, u32 *height) +{ + int i; + enum { QQCIF, QQVGA, QCIF, QVGA, CIF, VGA, SXGA }; + int res_x[] = { 88, 160, 176, 320, 352, 640, 1280 }; + int res_y[] = { 72, 120, 144, 240, 288, 480, 960 }; + + for (i = 0; i < ARRAY_SIZE(res_x); i++) { + if (res_x[i] >= *width && res_y[i] >= *height) { + *width = res_x[i]; + *height = res_y[i]; + return; + } + } + + *width = res_x[SXGA]; + *height = res_y[SXGA]; +} + +/* Prepare necessary register changes depending on color encoding */ +static void ov9640_alter_regs(enum v4l2_mbus_pixelcode code, + struct ov9640_reg_alt *alt) +{ + switch (code) { + default: + case V4L2_MBUS_FMT_UYVY8_2X8: + alt->com12 = OV9640_COM12_YUV_AVG; + alt->com13 = OV9640_COM13_Y_DELAY_EN | + OV9640_COM13_YUV_DLY(0x01); + break; + case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE: + alt->com7 = OV9640_COM7_RGB; + alt->com13 = OV9640_COM13_RGB_AVG; + alt->com15 = OV9640_COM15_RGB_555; + break; + case V4L2_MBUS_FMT_RGB565_2X8_LE: + alt->com7 = OV9640_COM7_RGB; + alt->com13 = OV9640_COM13_RGB_AVG; + alt->com15 = OV9640_COM15_RGB_565; + break; + }; +} + +/* Setup registers according to resolution and color encoding */ +static int ov9640_write_regs(struct i2c_client *client, u32 width, + enum v4l2_mbus_pixelcode code, struct ov9640_reg_alt *alts) +{ + const struct ov9640_reg *ov9640_regs, *matrix_regs; + int ov9640_regs_len, matrix_regs_len; + int i, ret; + u8 val; + + /* select register configuration for given resolution */ + switch (width) { + case W_QQCIF: + ov9640_regs = ov9640_regs_qqcif; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qqcif); + break; + case W_QQVGA: + ov9640_regs = ov9640_regs_qqvga; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qqvga); + break; + case W_QCIF: + ov9640_regs = ov9640_regs_qcif; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qcif); + break; + case W_QVGA: + ov9640_regs = ov9640_regs_qvga; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_qvga); + break; + case W_CIF: + ov9640_regs = ov9640_regs_cif; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_cif); + break; + case W_VGA: + ov9640_regs = ov9640_regs_vga; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_vga); + break; + case W_SXGA: + ov9640_regs = ov9640_regs_sxga; + ov9640_regs_len = ARRAY_SIZE(ov9640_regs_sxga); + break; + default: + dev_err(&client->dev, "Failed to select resolution!\n"); + return -EINVAL; + } + + /* select color matrix configuration for given color encoding */ + if (code == V4L2_MBUS_FMT_UYVY8_2X8) { + matrix_regs = ov9640_regs_yuv; + matrix_regs_len = ARRAY_SIZE(ov9640_regs_yuv); + } else { + matrix_regs = ov9640_regs_rgb; + matrix_regs_len = ARRAY_SIZE(ov9640_regs_rgb); + } + + /* write register settings into the module */ + for (i = 0; i < ov9640_regs_len; i++) { + val = ov9640_regs[i].val; + + switch (ov9640_regs[i].reg) { + case OV9640_COM7: + val |= alts->com7; + break; + case OV9640_COM12: + val |= alts->com12; + break; + case OV9640_COM13: + val |= alts->com13; + break; + case OV9640_COM15: + val |= alts->com15; + break; + } + + ret = ov9640_reg_write(client, ov9640_regs[i].reg, val); + if (ret) + return ret; + } + + /* write color matrix configuration into the module */ + for (i = 0; i < matrix_regs_len; i++) { + ret = ov9640_reg_write(client, matrix_regs[i].reg, + matrix_regs[i].val); + if (ret) + return ret; + } + + return 0; +} + +/* program default register values */ +static int ov9640_prog_dflt(struct i2c_client *client) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(ov9640_regs_dflt); i++) { + ret = ov9640_reg_write(client, ov9640_regs_dflt[i].reg, + ov9640_regs_dflt[i].val); + if (ret) + return ret; + } + + /* wait for the changes to actually happen, 140ms are not enough yet */ + mdelay(150); + + return 0; +} + +/* set the format we will capture in */ +static int ov9640_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov9640_reg_alt alts = {0}; + enum v4l2_colorspace cspace; + enum v4l2_mbus_pixelcode code = mf->code; + int ret; + + ov9640_res_roundup(&mf->width, &mf->height); + ov9640_alter_regs(mf->code, &alts); + + ov9640_reset(client); + + ret = ov9640_prog_dflt(client); + if (ret) + return ret; + + switch (code) { + case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE: + case V4L2_MBUS_FMT_RGB565_2X8_LE: + cspace = V4L2_COLORSPACE_SRGB; + break; + default: + code = V4L2_MBUS_FMT_UYVY8_2X8; + case V4L2_MBUS_FMT_UYVY8_2X8: + cspace = V4L2_COLORSPACE_JPEG; + } + + ret = ov9640_write_regs(client, mf->width, code, &alts); + if (!ret) { + mf->code = code; + mf->colorspace = cspace; + } + + return ret; +} + +static int ov9640_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + ov9640_res_roundup(&mf->width, &mf->height); + + mf->field = V4L2_FIELD_NONE; + + switch (mf->code) { + case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE: + case V4L2_MBUS_FMT_RGB565_2X8_LE: + mf->colorspace = V4L2_COLORSPACE_SRGB; + break; + default: + mf->code = V4L2_MBUS_FMT_UYVY8_2X8; + case V4L2_MBUS_FMT_UYVY8_2X8: + mf->colorspace = V4L2_COLORSPACE_JPEG; + } + + return 0; +} + +static int ov9640_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(ov9640_codes)) + return -EINVAL; + + *code = ov9640_codes[index]; + return 0; +} + +static int ov9640_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + a->c.left = 0; + a->c.top = 0; + a->c.width = W_SXGA; + a->c.height = H_SXGA; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int ov9640_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = W_SXGA; + a->bounds.height = H_SXGA; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int ov9640_video_probe(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov9640_priv *priv = to_ov9640_sensor(sd); + u8 pid, ver, midh, midl; + const char *devname; + int ret; + + ret = ov9640_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * check and show product ID and manufacturer ID + */ + + ret = ov9640_reg_read(client, OV9640_PID, &pid); + if (!ret) + ret = ov9640_reg_read(client, OV9640_VER, &ver); + if (!ret) + ret = ov9640_reg_read(client, OV9640_MIDH, &midh); + if (!ret) + ret = ov9640_reg_read(client, OV9640_MIDL, &midl); + if (ret) + goto done; + + switch (VERSION(pid, ver)) { + case OV9640_V2: + devname = "ov9640"; + priv->model = V4L2_IDENT_OV9640; + priv->revision = 2; + break; + case OV9640_V3: + devname = "ov9640"; + priv->model = V4L2_IDENT_OV9640; + priv->revision = 3; + break; + default: + dev_err(&client->dev, "Product ID error %x:%x\n", pid, ver); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, "%s Product ID %0x:%0x Manufacturer ID %x:%x\n", + devname, pid, ver, midh, midl); + + ret = v4l2_ctrl_handler_setup(&priv->hdl); + +done: + ov9640_s_power(&priv->subdev, 0); + return ret; +} + +static const struct v4l2_ctrl_ops ov9640_ctrl_ops = { + .s_ctrl = ov9640_s_ctrl, +}; + +static struct v4l2_subdev_core_ops ov9640_core_ops = { + .g_chip_ident = ov9640_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov9640_get_register, + .s_register = ov9640_set_register, +#endif + .s_power = ov9640_s_power, +}; + +/* Request bus settings on camera side */ +static int ov9640_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static struct v4l2_subdev_video_ops ov9640_video_ops = { + .s_stream = ov9640_s_stream, + .s_mbus_fmt = ov9640_s_fmt, + .try_mbus_fmt = ov9640_try_fmt, + .enum_mbus_fmt = ov9640_enum_fmt, + .cropcap = ov9640_cropcap, + .g_crop = ov9640_g_crop, + .g_mbus_config = ov9640_g_mbus_config, +}; + +static struct v4l2_subdev_ops ov9640_subdev_ops = { + .core = &ov9640_core_ops, + .video = &ov9640_video_ops, +}; + +/* + * i2c_driver function + */ +static int ov9640_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov9640_priv *priv; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + if (!icl) { + dev_err(&client->dev, "Missing platform_data for driver\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(struct ov9640_priv), GFP_KERNEL); + if (!priv) { + dev_err(&client->dev, + "Failed to allocate memory for private data!\n"); + return -ENOMEM; + } + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov9640_subdev_ops); + + v4l2_ctrl_handler_init(&priv->hdl, 2); + v4l2_ctrl_new_std(&priv->hdl, &ov9640_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov9640_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + priv->subdev.ctrl_handler = &priv->hdl; + if (priv->hdl.error) { + int err = priv->hdl.error; + + kfree(priv); + return err; + } + + ret = ov9640_video_probe(client); + + if (ret) { + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + } + + return ret; +} + +static int ov9640_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov9640_priv *priv = to_ov9640_sensor(sd); + + v4l2_device_unregister_subdev(&priv->subdev); + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + return 0; +} + +static const struct i2c_device_id ov9640_id[] = { + { "ov9640", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov9640_id); + +static struct i2c_driver ov9640_i2c_driver = { + .driver = { + .name = "ov9640", + }, + .probe = ov9640_probe, + .remove = ov9640_remove, + .id_table = ov9640_id, +}; + +module_i2c_driver(ov9640_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV96xx"); +MODULE_AUTHOR("Marek Vasut "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/ov9640.h b/drivers/media/i2c/soc_camera/ov9640.h similarity index 100% rename from drivers/media/video/ov9640.h rename to drivers/media/i2c/soc_camera/ov9640.h diff --git a/drivers/media/i2c/soc_camera/ov9740.c b/drivers/media/i2c/soc_camera/ov9740.c new file mode 100644 index 000000000000..7a55889e397b --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov9740.c @@ -0,0 +1,1020 @@ +/* + * OmniVision OV9740 Camera Driver + * + * Copyright (C) 2011 NVIDIA Corporation + * + * Based on ov9640 camera driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define to_ov9740(sd) container_of(sd, struct ov9740_priv, subdev) + +/* General Status Registers */ +#define OV9740_MODEL_ID_HI 0x0000 +#define OV9740_MODEL_ID_LO 0x0001 +#define OV9740_REVISION_NUMBER 0x0002 +#define OV9740_MANUFACTURER_ID 0x0003 +#define OV9740_SMIA_VERSION 0x0004 + +/* General Setup Registers */ +#define OV9740_MODE_SELECT 0x0100 +#define OV9740_IMAGE_ORT 0x0101 +#define OV9740_SOFTWARE_RESET 0x0103 +#define OV9740_GRP_PARAM_HOLD 0x0104 +#define OV9740_MSK_CORRUP_FM 0x0105 + +/* Timing Setting */ +#define OV9740_FRM_LENGTH_LN_HI 0x0340 /* VTS */ +#define OV9740_FRM_LENGTH_LN_LO 0x0341 /* VTS */ +#define OV9740_LN_LENGTH_PCK_HI 0x0342 /* HTS */ +#define OV9740_LN_LENGTH_PCK_LO 0x0343 /* HTS */ +#define OV9740_X_ADDR_START_HI 0x0344 +#define OV9740_X_ADDR_START_LO 0x0345 +#define OV9740_Y_ADDR_START_HI 0x0346 +#define OV9740_Y_ADDR_START_LO 0x0347 +#define OV9740_X_ADDR_END_HI 0x0348 +#define OV9740_X_ADDR_END_LO 0x0349 +#define OV9740_Y_ADDR_END_HI 0x034a +#define OV9740_Y_ADDR_END_LO 0x034b +#define OV9740_X_OUTPUT_SIZE_HI 0x034c +#define OV9740_X_OUTPUT_SIZE_LO 0x034d +#define OV9740_Y_OUTPUT_SIZE_HI 0x034e +#define OV9740_Y_OUTPUT_SIZE_LO 0x034f + +/* IO Control Registers */ +#define OV9740_IO_CREL00 0x3002 +#define OV9740_IO_CREL01 0x3004 +#define OV9740_IO_CREL02 0x3005 +#define OV9740_IO_OUTPUT_SEL01 0x3026 +#define OV9740_IO_OUTPUT_SEL02 0x3027 + +/* AWB Registers */ +#define OV9740_AWB_MANUAL_CTRL 0x3406 + +/* Analog Control Registers */ +#define OV9740_ANALOG_CTRL01 0x3601 +#define OV9740_ANALOG_CTRL02 0x3602 +#define OV9740_ANALOG_CTRL03 0x3603 +#define OV9740_ANALOG_CTRL04 0x3604 +#define OV9740_ANALOG_CTRL10 0x3610 +#define OV9740_ANALOG_CTRL12 0x3612 +#define OV9740_ANALOG_CTRL15 0x3615 +#define OV9740_ANALOG_CTRL20 0x3620 +#define OV9740_ANALOG_CTRL21 0x3621 +#define OV9740_ANALOG_CTRL22 0x3622 +#define OV9740_ANALOG_CTRL30 0x3630 +#define OV9740_ANALOG_CTRL31 0x3631 +#define OV9740_ANALOG_CTRL32 0x3632 +#define OV9740_ANALOG_CTRL33 0x3633 + +/* Sensor Control */ +#define OV9740_SENSOR_CTRL03 0x3703 +#define OV9740_SENSOR_CTRL04 0x3704 +#define OV9740_SENSOR_CTRL05 0x3705 +#define OV9740_SENSOR_CTRL07 0x3707 + +/* Timing Control */ +#define OV9740_TIMING_CTRL17 0x3817 +#define OV9740_TIMING_CTRL19 0x3819 +#define OV9740_TIMING_CTRL33 0x3833 +#define OV9740_TIMING_CTRL35 0x3835 + +/* Banding Filter */ +#define OV9740_AEC_MAXEXPO_60_H 0x3a02 +#define OV9740_AEC_MAXEXPO_60_L 0x3a03 +#define OV9740_AEC_B50_STEP_HI 0x3a08 +#define OV9740_AEC_B50_STEP_LO 0x3a09 +#define OV9740_AEC_B60_STEP_HI 0x3a0a +#define OV9740_AEC_B60_STEP_LO 0x3a0b +#define OV9740_AEC_CTRL0D 0x3a0d +#define OV9740_AEC_CTRL0E 0x3a0e +#define OV9740_AEC_MAXEXPO_50_H 0x3a14 +#define OV9740_AEC_MAXEXPO_50_L 0x3a15 + +/* AEC/AGC Control */ +#define OV9740_AEC_ENABLE 0x3503 +#define OV9740_GAIN_CEILING_01 0x3a18 +#define OV9740_GAIN_CEILING_02 0x3a19 +#define OV9740_AEC_HI_THRESHOLD 0x3a11 +#define OV9740_AEC_3A1A 0x3a1a +#define OV9740_AEC_CTRL1B_WPT2 0x3a1b +#define OV9740_AEC_CTRL0F_WPT 0x3a0f +#define OV9740_AEC_CTRL10_BPT 0x3a10 +#define OV9740_AEC_CTRL1E_BPT2 0x3a1e +#define OV9740_AEC_LO_THRESHOLD 0x3a1f + +/* BLC Control */ +#define OV9740_BLC_AUTO_ENABLE 0x4002 +#define OV9740_BLC_MODE 0x4005 + +/* VFIFO */ +#define OV9740_VFIFO_READ_START_HI 0x4608 +#define OV9740_VFIFO_READ_START_LO 0x4609 + +/* DVP Control */ +#define OV9740_DVP_VSYNC_CTRL02 0x4702 +#define OV9740_DVP_VSYNC_MODE 0x4704 +#define OV9740_DVP_VSYNC_CTRL06 0x4706 + +/* PLL Setting */ +#define OV9740_PLL_MODE_CTRL01 0x3104 +#define OV9740_PRE_PLL_CLK_DIV 0x0305 +#define OV9740_PLL_MULTIPLIER 0x0307 +#define OV9740_VT_SYS_CLK_DIV 0x0303 +#define OV9740_VT_PIX_CLK_DIV 0x0301 +#define OV9740_PLL_CTRL3010 0x3010 +#define OV9740_VFIFO_CTRL00 0x460e + +/* ISP Control */ +#define OV9740_ISP_CTRL00 0x5000 +#define OV9740_ISP_CTRL01 0x5001 +#define OV9740_ISP_CTRL03 0x5003 +#define OV9740_ISP_CTRL05 0x5005 +#define OV9740_ISP_CTRL12 0x5012 +#define OV9740_ISP_CTRL19 0x5019 +#define OV9740_ISP_CTRL1A 0x501a +#define OV9740_ISP_CTRL1E 0x501e +#define OV9740_ISP_CTRL1F 0x501f +#define OV9740_ISP_CTRL20 0x5020 +#define OV9740_ISP_CTRL21 0x5021 + +/* AWB */ +#define OV9740_AWB_CTRL00 0x5180 +#define OV9740_AWB_CTRL01 0x5181 +#define OV9740_AWB_CTRL02 0x5182 +#define OV9740_AWB_CTRL03 0x5183 +#define OV9740_AWB_ADV_CTRL01 0x5184 +#define OV9740_AWB_ADV_CTRL02 0x5185 +#define OV9740_AWB_ADV_CTRL03 0x5186 +#define OV9740_AWB_ADV_CTRL04 0x5187 +#define OV9740_AWB_ADV_CTRL05 0x5188 +#define OV9740_AWB_ADV_CTRL06 0x5189 +#define OV9740_AWB_ADV_CTRL07 0x518a +#define OV9740_AWB_ADV_CTRL08 0x518b +#define OV9740_AWB_ADV_CTRL09 0x518c +#define OV9740_AWB_ADV_CTRL10 0x518d +#define OV9740_AWB_ADV_CTRL11 0x518e +#define OV9740_AWB_CTRL0F 0x518f +#define OV9740_AWB_CTRL10 0x5190 +#define OV9740_AWB_CTRL11 0x5191 +#define OV9740_AWB_CTRL12 0x5192 +#define OV9740_AWB_CTRL13 0x5193 +#define OV9740_AWB_CTRL14 0x5194 + +/* MIPI Control */ +#define OV9740_MIPI_CTRL00 0x4800 +#define OV9740_MIPI_3837 0x3837 +#define OV9740_MIPI_CTRL01 0x4801 +#define OV9740_MIPI_CTRL03 0x4803 +#define OV9740_MIPI_CTRL05 0x4805 +#define OV9740_VFIFO_RD_CTRL 0x4601 +#define OV9740_MIPI_CTRL_3012 0x3012 +#define OV9740_SC_CMMM_MIPI_CTR 0x3014 + +#define OV9740_MAX_WIDTH 1280 +#define OV9740_MAX_HEIGHT 720 + +/* Misc. structures */ +struct ov9740_reg { + u16 reg; + u8 val; +}; + +struct ov9740_priv { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + + int ident; + u16 model; + u8 revision; + u8 manid; + u8 smiaver; + + bool flag_vflip; + bool flag_hflip; + + /* For suspend/resume. */ + struct v4l2_mbus_framefmt current_mf; + bool current_enable; +}; + +static const struct ov9740_reg ov9740_defaults[] = { + /* Software Reset */ + { OV9740_SOFTWARE_RESET, 0x01 }, + + /* Banding Filter */ + { OV9740_AEC_B50_STEP_HI, 0x00 }, + { OV9740_AEC_B50_STEP_LO, 0xe8 }, + { OV9740_AEC_CTRL0E, 0x03 }, + { OV9740_AEC_MAXEXPO_50_H, 0x15 }, + { OV9740_AEC_MAXEXPO_50_L, 0xc6 }, + { OV9740_AEC_B60_STEP_HI, 0x00 }, + { OV9740_AEC_B60_STEP_LO, 0xc0 }, + { OV9740_AEC_CTRL0D, 0x04 }, + { OV9740_AEC_MAXEXPO_60_H, 0x18 }, + { OV9740_AEC_MAXEXPO_60_L, 0x20 }, + + /* LC */ + { 0x5842, 0x02 }, { 0x5843, 0x5e }, { 0x5844, 0x04 }, { 0x5845, 0x32 }, + { 0x5846, 0x03 }, { 0x5847, 0x29 }, { 0x5848, 0x02 }, { 0x5849, 0xcc }, + + /* Un-documented OV9740 registers */ + { 0x5800, 0x29 }, { 0x5801, 0x25 }, { 0x5802, 0x20 }, { 0x5803, 0x21 }, + { 0x5804, 0x26 }, { 0x5805, 0x2e }, { 0x5806, 0x11 }, { 0x5807, 0x0c }, + { 0x5808, 0x09 }, { 0x5809, 0x0a }, { 0x580a, 0x0e }, { 0x580b, 0x16 }, + { 0x580c, 0x06 }, { 0x580d, 0x02 }, { 0x580e, 0x00 }, { 0x580f, 0x00 }, + { 0x5810, 0x04 }, { 0x5811, 0x0a }, { 0x5812, 0x05 }, { 0x5813, 0x02 }, + { 0x5814, 0x00 }, { 0x5815, 0x00 }, { 0x5816, 0x03 }, { 0x5817, 0x09 }, + { 0x5818, 0x0f }, { 0x5819, 0x0a }, { 0x581a, 0x07 }, { 0x581b, 0x08 }, + { 0x581c, 0x0b }, { 0x581d, 0x14 }, { 0x581e, 0x28 }, { 0x581f, 0x23 }, + { 0x5820, 0x1d }, { 0x5821, 0x1e }, { 0x5822, 0x24 }, { 0x5823, 0x2a }, + { 0x5824, 0x4f }, { 0x5825, 0x6f }, { 0x5826, 0x5f }, { 0x5827, 0x7f }, + { 0x5828, 0x9f }, { 0x5829, 0x5f }, { 0x582a, 0x8f }, { 0x582b, 0x9e }, + { 0x582c, 0x8f }, { 0x582d, 0x9f }, { 0x582e, 0x4f }, { 0x582f, 0x87 }, + { 0x5830, 0x86 }, { 0x5831, 0x97 }, { 0x5832, 0xae }, { 0x5833, 0x3f }, + { 0x5834, 0x8e }, { 0x5835, 0x7c }, { 0x5836, 0x7e }, { 0x5837, 0xaf }, + { 0x5838, 0x8f }, { 0x5839, 0x8f }, { 0x583a, 0x9f }, { 0x583b, 0x7f }, + { 0x583c, 0x5f }, + + /* Y Gamma */ + { 0x5480, 0x07 }, { 0x5481, 0x18 }, { 0x5482, 0x2c }, { 0x5483, 0x4e }, + { 0x5484, 0x5e }, { 0x5485, 0x6b }, { 0x5486, 0x77 }, { 0x5487, 0x82 }, + { 0x5488, 0x8c }, { 0x5489, 0x95 }, { 0x548a, 0xa4 }, { 0x548b, 0xb1 }, + { 0x548c, 0xc6 }, { 0x548d, 0xd8 }, { 0x548e, 0xe9 }, + + /* UV Gamma */ + { 0x5490, 0x0f }, { 0x5491, 0xff }, { 0x5492, 0x0d }, { 0x5493, 0x05 }, + { 0x5494, 0x07 }, { 0x5495, 0x1a }, { 0x5496, 0x04 }, { 0x5497, 0x01 }, + { 0x5498, 0x03 }, { 0x5499, 0x53 }, { 0x549a, 0x02 }, { 0x549b, 0xeb }, + { 0x549c, 0x02 }, { 0x549d, 0xa0 }, { 0x549e, 0x02 }, { 0x549f, 0x67 }, + { 0x54a0, 0x02 }, { 0x54a1, 0x3b }, { 0x54a2, 0x02 }, { 0x54a3, 0x18 }, + { 0x54a4, 0x01 }, { 0x54a5, 0xe7 }, { 0x54a6, 0x01 }, { 0x54a7, 0xc3 }, + { 0x54a8, 0x01 }, { 0x54a9, 0x94 }, { 0x54aa, 0x01 }, { 0x54ab, 0x72 }, + { 0x54ac, 0x01 }, { 0x54ad, 0x57 }, + + /* AWB */ + { OV9740_AWB_CTRL00, 0xf0 }, + { OV9740_AWB_CTRL01, 0x00 }, + { OV9740_AWB_CTRL02, 0x41 }, + { OV9740_AWB_CTRL03, 0x42 }, + { OV9740_AWB_ADV_CTRL01, 0x8a }, + { OV9740_AWB_ADV_CTRL02, 0x61 }, + { OV9740_AWB_ADV_CTRL03, 0xce }, + { OV9740_AWB_ADV_CTRL04, 0xa8 }, + { OV9740_AWB_ADV_CTRL05, 0x17 }, + { OV9740_AWB_ADV_CTRL06, 0x1f }, + { OV9740_AWB_ADV_CTRL07, 0x27 }, + { OV9740_AWB_ADV_CTRL08, 0x41 }, + { OV9740_AWB_ADV_CTRL09, 0x34 }, + { OV9740_AWB_ADV_CTRL10, 0xf0 }, + { OV9740_AWB_ADV_CTRL11, 0x10 }, + { OV9740_AWB_CTRL0F, 0xff }, + { OV9740_AWB_CTRL10, 0x00 }, + { OV9740_AWB_CTRL11, 0xff }, + { OV9740_AWB_CTRL12, 0x00 }, + { OV9740_AWB_CTRL13, 0xff }, + { OV9740_AWB_CTRL14, 0x00 }, + + /* CIP */ + { 0x530d, 0x12 }, + + /* CMX */ + { 0x5380, 0x01 }, { 0x5381, 0x00 }, { 0x5382, 0x00 }, { 0x5383, 0x17 }, + { 0x5384, 0x00 }, { 0x5385, 0x01 }, { 0x5386, 0x00 }, { 0x5387, 0x00 }, + { 0x5388, 0x00 }, { 0x5389, 0xe0 }, { 0x538a, 0x00 }, { 0x538b, 0x20 }, + { 0x538c, 0x00 }, { 0x538d, 0x00 }, { 0x538e, 0x00 }, { 0x538f, 0x16 }, + { 0x5390, 0x00 }, { 0x5391, 0x9c }, { 0x5392, 0x00 }, { 0x5393, 0xa0 }, + { 0x5394, 0x18 }, + + /* 50/60 Detection */ + { 0x3c0a, 0x9c }, { 0x3c0b, 0x3f }, + + /* Output Select */ + { OV9740_IO_OUTPUT_SEL01, 0x00 }, + { OV9740_IO_OUTPUT_SEL02, 0x00 }, + { OV9740_IO_CREL00, 0x00 }, + { OV9740_IO_CREL01, 0x00 }, + { OV9740_IO_CREL02, 0x00 }, + + /* AWB Control */ + { OV9740_AWB_MANUAL_CTRL, 0x00 }, + + /* Analog Control */ + { OV9740_ANALOG_CTRL03, 0xaa }, + { OV9740_ANALOG_CTRL32, 0x2f }, + { OV9740_ANALOG_CTRL20, 0x66 }, + { OV9740_ANALOG_CTRL21, 0xc0 }, + { OV9740_ANALOG_CTRL31, 0x52 }, + { OV9740_ANALOG_CTRL33, 0x50 }, + { OV9740_ANALOG_CTRL30, 0xca }, + { OV9740_ANALOG_CTRL04, 0x0c }, + { OV9740_ANALOG_CTRL01, 0x40 }, + { OV9740_ANALOG_CTRL02, 0x16 }, + { OV9740_ANALOG_CTRL10, 0xa1 }, + { OV9740_ANALOG_CTRL12, 0x24 }, + { OV9740_ANALOG_CTRL22, 0x9f }, + { OV9740_ANALOG_CTRL15, 0xf0 }, + + /* Sensor Control */ + { OV9740_SENSOR_CTRL03, 0x42 }, + { OV9740_SENSOR_CTRL04, 0x10 }, + { OV9740_SENSOR_CTRL05, 0x45 }, + { OV9740_SENSOR_CTRL07, 0x14 }, + + /* Timing Control */ + { OV9740_TIMING_CTRL33, 0x04 }, + { OV9740_TIMING_CTRL35, 0x02 }, + { OV9740_TIMING_CTRL19, 0x6e }, + { OV9740_TIMING_CTRL17, 0x94 }, + + /* AEC/AGC Control */ + { OV9740_AEC_ENABLE, 0x10 }, + { OV9740_GAIN_CEILING_01, 0x00 }, + { OV9740_GAIN_CEILING_02, 0x7f }, + { OV9740_AEC_HI_THRESHOLD, 0xa0 }, + { OV9740_AEC_3A1A, 0x05 }, + { OV9740_AEC_CTRL1B_WPT2, 0x50 }, + { OV9740_AEC_CTRL0F_WPT, 0x50 }, + { OV9740_AEC_CTRL10_BPT, 0x4c }, + { OV9740_AEC_CTRL1E_BPT2, 0x4c }, + { OV9740_AEC_LO_THRESHOLD, 0x26 }, + + /* BLC Control */ + { OV9740_BLC_AUTO_ENABLE, 0x45 }, + { OV9740_BLC_MODE, 0x18 }, + + /* DVP Control */ + { OV9740_DVP_VSYNC_CTRL02, 0x04 }, + { OV9740_DVP_VSYNC_MODE, 0x00 }, + { OV9740_DVP_VSYNC_CTRL06, 0x08 }, + + /* PLL Setting */ + { OV9740_PLL_MODE_CTRL01, 0x20 }, + { OV9740_PRE_PLL_CLK_DIV, 0x03 }, + { OV9740_PLL_MULTIPLIER, 0x4c }, + { OV9740_VT_SYS_CLK_DIV, 0x01 }, + { OV9740_VT_PIX_CLK_DIV, 0x08 }, + { OV9740_PLL_CTRL3010, 0x01 }, + { OV9740_VFIFO_CTRL00, 0x82 }, + + /* Timing Setting */ + /* VTS */ + { OV9740_FRM_LENGTH_LN_HI, 0x03 }, + { OV9740_FRM_LENGTH_LN_LO, 0x07 }, + /* HTS */ + { OV9740_LN_LENGTH_PCK_HI, 0x06 }, + { OV9740_LN_LENGTH_PCK_LO, 0x62 }, + + /* MIPI Control */ + { OV9740_MIPI_CTRL00, 0x44 }, /* 0x64 for discontinuous clk */ + { OV9740_MIPI_3837, 0x01 }, + { OV9740_MIPI_CTRL01, 0x0f }, + { OV9740_MIPI_CTRL03, 0x05 }, + { OV9740_MIPI_CTRL05, 0x10 }, + { OV9740_VFIFO_RD_CTRL, 0x16 }, + { OV9740_MIPI_CTRL_3012, 0x70 }, + { OV9740_SC_CMMM_MIPI_CTR, 0x01 }, + + /* YUYV order */ + { OV9740_ISP_CTRL19, 0x02 }, +}; + +static enum v4l2_mbus_pixelcode ov9740_codes[] = { + V4L2_MBUS_FMT_YUYV8_2X8, +}; + +/* read a register */ +static int ov9740_reg_read(struct i2c_client *client, u16 reg, u8 *val) +{ + int ret; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = (u8 *)®, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = val, + }, + }; + + reg = swab16(reg); + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_err(&client->dev, "Failed reading register 0x%04x!\n", reg); + return ret; + } + + return 0; +} + +/* write a register */ +static int ov9740_reg_write(struct i2c_client *client, u16 reg, u8 val) +{ + struct i2c_msg msg; + struct { + u16 reg; + u8 val; + } __packed buf; + int ret; + + reg = swab16(reg); + + buf.reg = reg; + buf.val = val; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 3; + msg.buf = (u8 *)&buf; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_err(&client->dev, "Failed writing register 0x%04x!\n", reg); + return ret; + } + + return 0; +} + + +/* Read a register, alter its bits, write it back */ +static int ov9740_reg_rmw(struct i2c_client *client, u16 reg, u8 set, u8 unset) +{ + u8 val; + int ret; + + ret = ov9740_reg_read(client, reg, &val); + if (ret < 0) { + dev_err(&client->dev, + "[Read]-Modify-Write of register 0x%04x failed!\n", + reg); + return ret; + } + + val |= set; + val &= ~unset; + + ret = ov9740_reg_write(client, reg, val); + if (ret < 0) { + dev_err(&client->dev, + "Read-Modify-[Write] of register 0x%04x failed!\n", + reg); + return ret; + } + + return 0; +} + +static int ov9740_reg_write_array(struct i2c_client *client, + const struct ov9740_reg *regarray, + int regarraylen) +{ + int i; + int ret; + + for (i = 0; i < regarraylen; i++) { + ret = ov9740_reg_write(client, + regarray[i].reg, regarray[i].val); + if (ret < 0) + return ret; + } + + return 0; +} + +/* Start/Stop streaming from the device */ +static int ov9740_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov9740_priv *priv = to_ov9740(sd); + int ret; + + /* Program orientation register. */ + if (priv->flag_vflip) + ret = ov9740_reg_rmw(client, OV9740_IMAGE_ORT, 0x2, 0); + else + ret = ov9740_reg_rmw(client, OV9740_IMAGE_ORT, 0, 0x2); + if (ret < 0) + return ret; + + if (priv->flag_hflip) + ret = ov9740_reg_rmw(client, OV9740_IMAGE_ORT, 0x1, 0); + else + ret = ov9740_reg_rmw(client, OV9740_IMAGE_ORT, 0, 0x1); + if (ret < 0) + return ret; + + if (enable) { + dev_dbg(&client->dev, "Enabling Streaming\n"); + /* Start Streaming */ + ret = ov9740_reg_write(client, OV9740_MODE_SELECT, 0x01); + + } else { + dev_dbg(&client->dev, "Disabling Streaming\n"); + /* Software Reset */ + ret = ov9740_reg_write(client, OV9740_SOFTWARE_RESET, 0x01); + if (!ret) + /* Setting Streaming to Standby */ + ret = ov9740_reg_write(client, OV9740_MODE_SELECT, + 0x00); + } + + priv->current_enable = enable; + + return ret; +} + +/* select nearest higher resolution for capture */ +static void ov9740_res_roundup(u32 *width, u32 *height) +{ + /* Width must be a multiple of 4 pixels. */ + *width = ALIGN(*width, 4); + + /* Max resolution is 1280x720 (720p). */ + if (*width > OV9740_MAX_WIDTH) + *width = OV9740_MAX_WIDTH; + + if (*height > OV9740_MAX_HEIGHT) + *height = OV9740_MAX_HEIGHT; +} + +/* Setup registers according to resolution and color encoding */ +static int ov9740_set_res(struct i2c_client *client, u32 width, u32 height) +{ + u32 x_start; + u32 y_start; + u32 x_end; + u32 y_end; + bool scaling = 0; + u32 scale_input_x; + u32 scale_input_y; + int ret; + + if ((width != OV9740_MAX_WIDTH) || (height != OV9740_MAX_HEIGHT)) + scaling = 1; + + /* + * Try to use as much of the sensor area as possible when supporting + * smaller resolutions. Depending on the aspect ratio of the + * chosen resolution, we can either use the full width of the sensor, + * or the full height of the sensor (or both if the aspect ratio is + * the same as 1280x720. + */ + if ((OV9740_MAX_WIDTH * height) > (OV9740_MAX_HEIGHT * width)) { + scale_input_x = (OV9740_MAX_HEIGHT * width) / height; + scale_input_y = OV9740_MAX_HEIGHT; + } else { + scale_input_x = OV9740_MAX_WIDTH; + scale_input_y = (OV9740_MAX_WIDTH * height) / width; + } + + /* These describe the area of the sensor to use. */ + x_start = (OV9740_MAX_WIDTH - scale_input_x) / 2; + y_start = (OV9740_MAX_HEIGHT - scale_input_y) / 2; + x_end = x_start + scale_input_x - 1; + y_end = y_start + scale_input_y - 1; + + ret = ov9740_reg_write(client, OV9740_X_ADDR_START_HI, x_start >> 8); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_X_ADDR_START_LO, x_start & 0xff); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_Y_ADDR_START_HI, y_start >> 8); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_Y_ADDR_START_LO, y_start & 0xff); + if (ret) + goto done; + + ret = ov9740_reg_write(client, OV9740_X_ADDR_END_HI, x_end >> 8); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_X_ADDR_END_LO, x_end & 0xff); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_Y_ADDR_END_HI, y_end >> 8); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_Y_ADDR_END_LO, y_end & 0xff); + if (ret) + goto done; + + ret = ov9740_reg_write(client, OV9740_X_OUTPUT_SIZE_HI, width >> 8); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_X_OUTPUT_SIZE_LO, width & 0xff); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_Y_OUTPUT_SIZE_HI, height >> 8); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_Y_OUTPUT_SIZE_LO, height & 0xff); + if (ret) + goto done; + + ret = ov9740_reg_write(client, OV9740_ISP_CTRL1E, scale_input_x >> 8); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_ISP_CTRL1F, scale_input_x & 0xff); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_ISP_CTRL20, scale_input_y >> 8); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_ISP_CTRL21, scale_input_y & 0xff); + if (ret) + goto done; + + ret = ov9740_reg_write(client, OV9740_VFIFO_READ_START_HI, + (scale_input_x - width) >> 8); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_VFIFO_READ_START_LO, + (scale_input_x - width) & 0xff); + if (ret) + goto done; + + ret = ov9740_reg_write(client, OV9740_ISP_CTRL00, 0xff); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_ISP_CTRL01, 0xef | + (scaling << 4)); + if (ret) + goto done; + ret = ov9740_reg_write(client, OV9740_ISP_CTRL03, 0xff); + +done: + return ret; +} + +/* set the format we will capture in */ +static int ov9740_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov9740_priv *priv = to_ov9740(sd); + enum v4l2_colorspace cspace; + enum v4l2_mbus_pixelcode code = mf->code; + int ret; + + ov9740_res_roundup(&mf->width, &mf->height); + + switch (code) { + case V4L2_MBUS_FMT_YUYV8_2X8: + cspace = V4L2_COLORSPACE_SRGB; + break; + default: + return -EINVAL; + } + + ret = ov9740_reg_write_array(client, ov9740_defaults, + ARRAY_SIZE(ov9740_defaults)); + if (ret < 0) + return ret; + + ret = ov9740_set_res(client, mf->width, mf->height); + if (ret < 0) + return ret; + + mf->code = code; + mf->colorspace = cspace; + + memcpy(&priv->current_mf, mf, sizeof(struct v4l2_mbus_framefmt)); + + return ret; +} + +static int ov9740_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + ov9740_res_roundup(&mf->width, &mf->height); + + mf->field = V4L2_FIELD_NONE; + mf->code = V4L2_MBUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SRGB; + + return 0; +} + +static int ov9740_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(ov9740_codes)) + return -EINVAL; + + *code = ov9740_codes[index]; + + return 0; +} + +static int ov9740_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = OV9740_MAX_WIDTH; + a->bounds.height = OV9740_MAX_HEIGHT; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int ov9740_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + a->c.left = 0; + a->c.top = 0; + a->c.width = OV9740_MAX_WIDTH; + a->c.height = OV9740_MAX_HEIGHT; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +/* Set status of additional camera capabilities */ +static int ov9740_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov9740_priv *priv = + container_of(ctrl->handler, struct ov9740_priv, hdl); + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + priv->flag_vflip = ctrl->val; + break; + case V4L2_CID_HFLIP: + priv->flag_hflip = ctrl->val; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* Get chip identification */ +static int ov9740_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct ov9740_priv *priv = to_ov9740(sd); + + id->ident = priv->ident; + id->revision = priv->revision; + + return 0; +} + +static int ov9740_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct ov9740_priv *priv = to_ov9740(sd); + int ret; + + if (on) { + ret = soc_camera_power_on(&client->dev, icl); + if (ret < 0) + return ret; + + if (priv->current_enable) { + ov9740_s_fmt(sd, &priv->current_mf); + ov9740_s_stream(sd, 1); + } + } else { + if (priv->current_enable) { + ov9740_s_stream(sd, 0); + priv->current_enable = true; + } + + soc_camera_power_off(&client->dev, icl); + } + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov9740_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val; + + if (reg->reg & ~0xffff) + return -EINVAL; + + reg->size = 2; + + ret = ov9740_reg_read(client, reg->reg, &val); + if (ret) + return ret; + + reg->val = (__u64)val; + + return ret; +} + +static int ov9740_set_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg & ~0xffff || reg->val & ~0xff) + return -EINVAL; + + return ov9740_reg_write(client, reg->reg, reg->val); +} +#endif + +static int ov9740_video_probe(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov9740_priv *priv = to_ov9740(sd); + u8 modelhi, modello; + int ret; + + ret = ov9740_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * check and show product ID and manufacturer ID + */ + ret = ov9740_reg_read(client, OV9740_MODEL_ID_HI, &modelhi); + if (ret < 0) + goto done; + + ret = ov9740_reg_read(client, OV9740_MODEL_ID_LO, &modello); + if (ret < 0) + goto done; + + priv->model = (modelhi << 8) | modello; + + ret = ov9740_reg_read(client, OV9740_REVISION_NUMBER, &priv->revision); + if (ret < 0) + goto done; + + ret = ov9740_reg_read(client, OV9740_MANUFACTURER_ID, &priv->manid); + if (ret < 0) + goto done; + + ret = ov9740_reg_read(client, OV9740_SMIA_VERSION, &priv->smiaver); + if (ret < 0) + goto done; + + if (priv->model != 0x9740) { + ret = -ENODEV; + goto done; + } + + priv->ident = V4L2_IDENT_OV9740; + + dev_info(&client->dev, "ov9740 Model ID 0x%04x, Revision 0x%02x, " + "Manufacturer 0x%02x, SMIA Version 0x%02x\n", + priv->model, priv->revision, priv->manid, priv->smiaver); + + ret = v4l2_ctrl_handler_setup(&priv->hdl); + +done: + ov9740_s_power(&priv->subdev, 0); + return ret; +} + +/* Request bus settings on camera side */ +static int ov9740_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static struct v4l2_subdev_video_ops ov9740_video_ops = { + .s_stream = ov9740_s_stream, + .s_mbus_fmt = ov9740_s_fmt, + .try_mbus_fmt = ov9740_try_fmt, + .enum_mbus_fmt = ov9740_enum_fmt, + .cropcap = ov9740_cropcap, + .g_crop = ov9740_g_crop, + .g_mbus_config = ov9740_g_mbus_config, +}; + +static struct v4l2_subdev_core_ops ov9740_core_ops = { + .g_chip_ident = ov9740_g_chip_ident, + .s_power = ov9740_s_power, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov9740_get_register, + .s_register = ov9740_set_register, +#endif +}; + +static struct v4l2_subdev_ops ov9740_subdev_ops = { + .core = &ov9740_core_ops, + .video = &ov9740_video_ops, +}; + +static const struct v4l2_ctrl_ops ov9740_ctrl_ops = { + .s_ctrl = ov9740_s_ctrl, +}; + +/* + * i2c_driver function + */ +static int ov9740_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov9740_priv *priv; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + if (!icl) { + dev_err(&client->dev, "Missing platform_data for driver\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(struct ov9740_priv), GFP_KERNEL); + if (!priv) { + dev_err(&client->dev, "Failed to allocate private data!\n"); + return -ENOMEM; + } + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov9740_subdev_ops); + v4l2_ctrl_handler_init(&priv->hdl, 13); + v4l2_ctrl_new_std(&priv->hdl, &ov9740_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov9740_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + priv->subdev.ctrl_handler = &priv->hdl; + if (priv->hdl.error) { + int err = priv->hdl.error; + + kfree(priv); + return err; + } + + ret = ov9740_video_probe(client); + if (ret < 0) { + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + } + + return ret; +} + +static int ov9740_remove(struct i2c_client *client) +{ + struct ov9740_priv *priv = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(&priv->subdev); + v4l2_ctrl_handler_free(&priv->hdl); + kfree(priv); + return 0; +} + +static const struct i2c_device_id ov9740_id[] = { + { "ov9740", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov9740_id); + +static struct i2c_driver ov9740_i2c_driver = { + .driver = { + .name = "ov9740", + }, + .probe = ov9740_probe, + .remove = ov9740_remove, + .id_table = ov9740_id, +}; + +module_i2c_driver(ov9740_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV9740"); +MODULE_AUTHOR("Andrew Chew "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/soc_camera/rj54n1cb0c.c b/drivers/media/i2c/soc_camera/rj54n1cb0c.c new file mode 100644 index 000000000000..02f0400051d9 --- /dev/null +++ b/drivers/media/i2c/soc_camera/rj54n1cb0c.c @@ -0,0 +1,1431 @@ +/* + * Driver for RJ54N1CB0C CMOS Image Sensor from Sharp + * + * Copyright (C) 2009, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define RJ54N1_DEV_CODE 0x0400 +#define RJ54N1_DEV_CODE2 0x0401 +#define RJ54N1_OUT_SEL 0x0403 +#define RJ54N1_XY_OUTPUT_SIZE_S_H 0x0404 +#define RJ54N1_X_OUTPUT_SIZE_S_L 0x0405 +#define RJ54N1_Y_OUTPUT_SIZE_S_L 0x0406 +#define RJ54N1_XY_OUTPUT_SIZE_P_H 0x0407 +#define RJ54N1_X_OUTPUT_SIZE_P_L 0x0408 +#define RJ54N1_Y_OUTPUT_SIZE_P_L 0x0409 +#define RJ54N1_LINE_LENGTH_PCK_S_H 0x040a +#define RJ54N1_LINE_LENGTH_PCK_S_L 0x040b +#define RJ54N1_LINE_LENGTH_PCK_P_H 0x040c +#define RJ54N1_LINE_LENGTH_PCK_P_L 0x040d +#define RJ54N1_RESIZE_N 0x040e +#define RJ54N1_RESIZE_N_STEP 0x040f +#define RJ54N1_RESIZE_STEP 0x0410 +#define RJ54N1_RESIZE_HOLD_H 0x0411 +#define RJ54N1_RESIZE_HOLD_L 0x0412 +#define RJ54N1_H_OBEN_OFS 0x0413 +#define RJ54N1_V_OBEN_OFS 0x0414 +#define RJ54N1_RESIZE_CONTROL 0x0415 +#define RJ54N1_STILL_CONTROL 0x0417 +#define RJ54N1_INC_USE_SEL_H 0x0425 +#define RJ54N1_INC_USE_SEL_L 0x0426 +#define RJ54N1_MIRROR_STILL_MODE 0x0427 +#define RJ54N1_INIT_START 0x0428 +#define RJ54N1_SCALE_1_2_LEV 0x0429 +#define RJ54N1_SCALE_4_LEV 0x042a +#define RJ54N1_Y_GAIN 0x04d8 +#define RJ54N1_APT_GAIN_UP 0x04fa +#define RJ54N1_RA_SEL_UL 0x0530 +#define RJ54N1_BYTE_SWAP 0x0531 +#define RJ54N1_OUT_SIGPO 0x053b +#define RJ54N1_WB_SEL_WEIGHT_I 0x054e +#define RJ54N1_BIT8_WB 0x0569 +#define RJ54N1_HCAPS_WB 0x056a +#define RJ54N1_VCAPS_WB 0x056b +#define RJ54N1_HCAPE_WB 0x056c +#define RJ54N1_VCAPE_WB 0x056d +#define RJ54N1_EXPOSURE_CONTROL 0x058c +#define RJ54N1_FRAME_LENGTH_S_H 0x0595 +#define RJ54N1_FRAME_LENGTH_S_L 0x0596 +#define RJ54N1_FRAME_LENGTH_P_H 0x0597 +#define RJ54N1_FRAME_LENGTH_P_L 0x0598 +#define RJ54N1_PEAK_H 0x05b7 +#define RJ54N1_PEAK_50 0x05b8 +#define RJ54N1_PEAK_60 0x05b9 +#define RJ54N1_PEAK_DIFF 0x05ba +#define RJ54N1_IOC 0x05ef +#define RJ54N1_TG_BYPASS 0x0700 +#define RJ54N1_PLL_L 0x0701 +#define RJ54N1_PLL_N 0x0702 +#define RJ54N1_PLL_EN 0x0704 +#define RJ54N1_RATIO_TG 0x0706 +#define RJ54N1_RATIO_T 0x0707 +#define RJ54N1_RATIO_R 0x0708 +#define RJ54N1_RAMP_TGCLK_EN 0x0709 +#define RJ54N1_OCLK_DSP 0x0710 +#define RJ54N1_RATIO_OP 0x0711 +#define RJ54N1_RATIO_O 0x0712 +#define RJ54N1_OCLK_SEL_EN 0x0713 +#define RJ54N1_CLK_RST 0x0717 +#define RJ54N1_RESET_STANDBY 0x0718 +#define RJ54N1_FWFLG 0x07fe + +#define E_EXCLK (1 << 7) +#define SOFT_STDBY (1 << 4) +#define SEN_RSTX (1 << 2) +#define TG_RSTX (1 << 1) +#define DSP_RSTX (1 << 0) + +#define RESIZE_HOLD_SEL (1 << 2) +#define RESIZE_GO (1 << 1) + +/* + * When cropping, the camera automatically centers the cropped region, there + * doesn't seem to be a way to specify an explicit location of the rectangle. + */ +#define RJ54N1_COLUMN_SKIP 0 +#define RJ54N1_ROW_SKIP 0 +#define RJ54N1_MAX_WIDTH 1600 +#define RJ54N1_MAX_HEIGHT 1200 + +#define PLL_L 2 +#define PLL_N 0x31 + +/* I2C addresses: 0x50, 0x51, 0x60, 0x61 */ + +/* RJ54N1CB0C has only one fixed colorspace per pixelcode */ +struct rj54n1_datafmt { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; +}; + +/* Find a data format by a pixel code in an array */ +static const struct rj54n1_datafmt *rj54n1_find_datafmt( + enum v4l2_mbus_pixelcode code, const struct rj54n1_datafmt *fmt, + int n) +{ + int i; + for (i = 0; i < n; i++) + if (fmt[i].code == code) + return fmt + i; + + return NULL; +} + +static const struct rj54n1_datafmt rj54n1_colour_fmts[] = { + {V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG}, + {V4L2_MBUS_FMT_YVYU8_2X8, V4L2_COLORSPACE_JPEG}, + {V4L2_MBUS_FMT_RGB565_2X8_LE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_SBGGR10_2X8_PADLO_LE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_BE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_SBGGR10_2X8_PADLO_BE, V4L2_COLORSPACE_SRGB}, + {V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_SRGB}, +}; + +struct rj54n1_clock_div { + u8 ratio_tg; /* can be 0 or an odd number */ + u8 ratio_t; + u8 ratio_r; + u8 ratio_op; + u8 ratio_o; +}; + +struct rj54n1 { + struct v4l2_subdev subdev; + struct v4l2_ctrl_handler hdl; + struct rj54n1_clock_div clk_div; + const struct rj54n1_datafmt *fmt; + struct v4l2_rect rect; /* Sensor window */ + unsigned int tgclk_mhz; + bool auto_wb; + unsigned short width; /* Output window */ + unsigned short height; + unsigned short resize; /* Sensor * 1024 / resize = Output */ + unsigned short scale; + u8 bank; +}; + +struct rj54n1_reg_val { + u16 reg; + u8 val; +}; + +static const struct rj54n1_reg_val bank_4[] = { + {0x417, 0}, + {0x42c, 0}, + {0x42d, 0xf0}, + {0x42e, 0}, + {0x42f, 0x50}, + {0x430, 0xf5}, + {0x431, 0x16}, + {0x432, 0x20}, + {0x433, 0}, + {0x434, 0xc8}, + {0x43c, 8}, + {0x43e, 0x90}, + {0x445, 0x83}, + {0x4ba, 0x58}, + {0x4bb, 4}, + {0x4bc, 0x20}, + {0x4db, 4}, + {0x4fe, 2}, +}; + +static const struct rj54n1_reg_val bank_5[] = { + {0x514, 0}, + {0x516, 0}, + {0x518, 0}, + {0x51a, 0}, + {0x51d, 0xff}, + {0x56f, 0x28}, + {0x575, 0x40}, + {0x5bc, 0x48}, + {0x5c1, 6}, + {0x5e5, 0x11}, + {0x5e6, 0x43}, + {0x5e7, 0x33}, + {0x5e8, 0x21}, + {0x5e9, 0x30}, + {0x5ea, 0x0}, + {0x5eb, 0xa5}, + {0x5ec, 0xff}, + {0x5fe, 2}, +}; + +static const struct rj54n1_reg_val bank_7[] = { + {0x70a, 0}, + {0x714, 0xff}, + {0x715, 0xff}, + {0x716, 0x1f}, + {0x7FE, 2}, +}; + +static const struct rj54n1_reg_val bank_8[] = { + {0x800, 0x00}, + {0x801, 0x01}, + {0x802, 0x61}, + {0x805, 0x00}, + {0x806, 0x00}, + {0x807, 0x00}, + {0x808, 0x00}, + {0x809, 0x01}, + {0x80A, 0x61}, + {0x80B, 0x00}, + {0x80C, 0x01}, + {0x80D, 0x00}, + {0x80E, 0x00}, + {0x80F, 0x00}, + {0x810, 0x00}, + {0x811, 0x01}, + {0x812, 0x61}, + {0x813, 0x00}, + {0x814, 0x11}, + {0x815, 0x00}, + {0x816, 0x41}, + {0x817, 0x00}, + {0x818, 0x51}, + {0x819, 0x01}, + {0x81A, 0x1F}, + {0x81B, 0x00}, + {0x81C, 0x01}, + {0x81D, 0x00}, + {0x81E, 0x11}, + {0x81F, 0x00}, + {0x820, 0x41}, + {0x821, 0x00}, + {0x822, 0x51}, + {0x823, 0x00}, + {0x824, 0x00}, + {0x825, 0x00}, + {0x826, 0x47}, + {0x827, 0x01}, + {0x828, 0x4F}, + {0x829, 0x00}, + {0x82A, 0x00}, + {0x82B, 0x00}, + {0x82C, 0x30}, + {0x82D, 0x00}, + {0x82E, 0x40}, + {0x82F, 0x00}, + {0x830, 0xB3}, + {0x831, 0x00}, + {0x832, 0xE3}, + {0x833, 0x00}, + {0x834, 0x00}, + {0x835, 0x00}, + {0x836, 0x00}, + {0x837, 0x00}, + {0x838, 0x00}, + {0x839, 0x01}, + {0x83A, 0x61}, + {0x83B, 0x00}, + {0x83C, 0x01}, + {0x83D, 0x00}, + {0x83E, 0x00}, + {0x83F, 0x00}, + {0x840, 0x00}, + {0x841, 0x01}, + {0x842, 0x61}, + {0x843, 0x00}, + {0x844, 0x1D}, + {0x845, 0x00}, + {0x846, 0x00}, + {0x847, 0x00}, + {0x848, 0x00}, + {0x849, 0x01}, + {0x84A, 0x1F}, + {0x84B, 0x00}, + {0x84C, 0x05}, + {0x84D, 0x00}, + {0x84E, 0x19}, + {0x84F, 0x01}, + {0x850, 0x21}, + {0x851, 0x01}, + {0x852, 0x5D}, + {0x853, 0x00}, + {0x854, 0x00}, + {0x855, 0x00}, + {0x856, 0x19}, + {0x857, 0x01}, + {0x858, 0x21}, + {0x859, 0x00}, + {0x85A, 0x00}, + {0x85B, 0x00}, + {0x85C, 0x00}, + {0x85D, 0x00}, + {0x85E, 0x00}, + {0x85F, 0x00}, + {0x860, 0xB3}, + {0x861, 0x00}, + {0x862, 0xE3}, + {0x863, 0x00}, + {0x864, 0x00}, + {0x865, 0x00}, + {0x866, 0x00}, + {0x867, 0x00}, + {0x868, 0x00}, + {0x869, 0xE2}, + {0x86A, 0x00}, + {0x86B, 0x01}, + {0x86C, 0x06}, + {0x86D, 0x00}, + {0x86E, 0x00}, + {0x86F, 0x00}, + {0x870, 0x60}, + {0x871, 0x8C}, + {0x872, 0x10}, + {0x873, 0x00}, + {0x874, 0xE0}, + {0x875, 0x00}, + {0x876, 0x27}, + {0x877, 0x01}, + {0x878, 0x00}, + {0x879, 0x00}, + {0x87A, 0x00}, + {0x87B, 0x03}, + {0x87C, 0x00}, + {0x87D, 0x00}, + {0x87E, 0x00}, + {0x87F, 0x00}, + {0x880, 0x00}, + {0x881, 0x00}, + {0x882, 0x00}, + {0x883, 0x00}, + {0x884, 0x00}, + {0x885, 0x00}, + {0x886, 0xF8}, + {0x887, 0x00}, + {0x888, 0x03}, + {0x889, 0x00}, + {0x88A, 0x64}, + {0x88B, 0x00}, + {0x88C, 0x03}, + {0x88D, 0x00}, + {0x88E, 0xB1}, + {0x88F, 0x00}, + {0x890, 0x03}, + {0x891, 0x01}, + {0x892, 0x1D}, + {0x893, 0x00}, + {0x894, 0x03}, + {0x895, 0x01}, + {0x896, 0x4B}, + {0x897, 0x00}, + {0x898, 0xE5}, + {0x899, 0x00}, + {0x89A, 0x01}, + {0x89B, 0x00}, + {0x89C, 0x01}, + {0x89D, 0x04}, + {0x89E, 0xC8}, + {0x89F, 0x00}, + {0x8A0, 0x01}, + {0x8A1, 0x01}, + {0x8A2, 0x61}, + {0x8A3, 0x00}, + {0x8A4, 0x01}, + {0x8A5, 0x00}, + {0x8A6, 0x00}, + {0x8A7, 0x00}, + {0x8A8, 0x00}, + {0x8A9, 0x00}, + {0x8AA, 0x7F}, + {0x8AB, 0x03}, + {0x8AC, 0x00}, + {0x8AD, 0x00}, + {0x8AE, 0x00}, + {0x8AF, 0x00}, + {0x8B0, 0x00}, + {0x8B1, 0x00}, + {0x8B6, 0x00}, + {0x8B7, 0x01}, + {0x8B8, 0x00}, + {0x8B9, 0x00}, + {0x8BA, 0x02}, + {0x8BB, 0x00}, + {0x8BC, 0xFF}, + {0x8BD, 0x00}, + {0x8FE, 2}, +}; + +static const struct rj54n1_reg_val bank_10[] = { + {0x10bf, 0x69} +}; + +/* Clock dividers - these are default register values, divider = register + 1 */ +static const struct rj54n1_clock_div clk_div = { + .ratio_tg = 3 /* default: 5 */, + .ratio_t = 4 /* default: 1 */, + .ratio_r = 4 /* default: 0 */, + .ratio_op = 1 /* default: 5 */, + .ratio_o = 9 /* default: 0 */, +}; + +static struct rj54n1 *to_rj54n1(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct rj54n1, subdev); +} + +static int reg_read(struct i2c_client *client, const u16 reg) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int ret; + + /* set bank */ + if (rj54n1->bank != reg >> 8) { + dev_dbg(&client->dev, "[0x%x] = 0x%x\n", 0xff, reg >> 8); + ret = i2c_smbus_write_byte_data(client, 0xff, reg >> 8); + if (ret < 0) + return ret; + rj54n1->bank = reg >> 8; + } + return i2c_smbus_read_byte_data(client, reg & 0xff); +} + +static int reg_write(struct i2c_client *client, const u16 reg, + const u8 data) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int ret; + + /* set bank */ + if (rj54n1->bank != reg >> 8) { + dev_dbg(&client->dev, "[0x%x] = 0x%x\n", 0xff, reg >> 8); + ret = i2c_smbus_write_byte_data(client, 0xff, reg >> 8); + if (ret < 0) + return ret; + rj54n1->bank = reg >> 8; + } + dev_dbg(&client->dev, "[0x%x] = 0x%x\n", reg & 0xff, data); + return i2c_smbus_write_byte_data(client, reg & 0xff, data); +} + +static int reg_set(struct i2c_client *client, const u16 reg, + const u8 data, const u8 mask) +{ + int ret; + + ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, (ret & ~mask) | (data & mask)); +} + +static int reg_write_multiple(struct i2c_client *client, + const struct rj54n1_reg_val *rv, const int n) +{ + int i, ret; + + for (i = 0; i < n; i++) { + ret = reg_write(client, rv->reg, rv->val); + if (ret < 0) + return ret; + rv++; + } + + return 0; +} + +static int rj54n1_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(rj54n1_colour_fmts)) + return -EINVAL; + + *code = rj54n1_colour_fmts[index].code; + return 0; +} + +static int rj54n1_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* Switch between preview and still shot modes */ + return reg_set(client, RJ54N1_STILL_CONTROL, (!enable) << 7, 0x80); +} + +static int rj54n1_set_rect(struct i2c_client *client, + u16 reg_x, u16 reg_y, u16 reg_xy, + u32 width, u32 height) +{ + int ret; + + ret = reg_write(client, reg_xy, + ((width >> 4) & 0x70) | + ((height >> 8) & 7)); + + if (!ret) + ret = reg_write(client, reg_x, width & 0xff); + if (!ret) + ret = reg_write(client, reg_y, height & 0xff); + + return ret; +} + +/* + * Some commands, specifically certain initialisation sequences, require + * a commit operation. + */ +static int rj54n1_commit(struct i2c_client *client) +{ + int ret = reg_write(client, RJ54N1_INIT_START, 1); + msleep(10); + if (!ret) + ret = reg_write(client, RJ54N1_INIT_START, 0); + return ret; +} + +static int rj54n1_sensor_scale(struct v4l2_subdev *sd, s32 *in_w, s32 *in_h, + s32 *out_w, s32 *out_h); + +static int rj54n1_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + const struct v4l2_rect *rect = &a->c; + int dummy = 0, output_w, output_h, + input_w = rect->width, input_h = rect->height; + int ret; + + /* arbitrary minimum width and height, edges unimportant */ + soc_camera_limit_side(&dummy, &input_w, + RJ54N1_COLUMN_SKIP, 8, RJ54N1_MAX_WIDTH); + + soc_camera_limit_side(&dummy, &input_h, + RJ54N1_ROW_SKIP, 8, RJ54N1_MAX_HEIGHT); + + output_w = (input_w * 1024 + rj54n1->resize / 2) / rj54n1->resize; + output_h = (input_h * 1024 + rj54n1->resize / 2) / rj54n1->resize; + + dev_dbg(&client->dev, "Scaling for %dx%d : %u = %dx%d\n", + input_w, input_h, rj54n1->resize, output_w, output_h); + + ret = rj54n1_sensor_scale(sd, &input_w, &input_h, &output_w, &output_h); + if (ret < 0) + return ret; + + rj54n1->width = output_w; + rj54n1->height = output_h; + rj54n1->resize = ret; + rj54n1->rect.width = input_w; + rj54n1->rect.height = input_h; + + return 0; +} + +static int rj54n1_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + + a->c = rj54n1->rect; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int rj54n1_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = RJ54N1_COLUMN_SKIP; + a->bounds.top = RJ54N1_ROW_SKIP; + a->bounds.width = RJ54N1_MAX_WIDTH; + a->bounds.height = RJ54N1_MAX_HEIGHT; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int rj54n1_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + + mf->code = rj54n1->fmt->code; + mf->colorspace = rj54n1->fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + mf->width = rj54n1->width; + mf->height = rj54n1->height; + + return 0; +} + +/* + * The actual geometry configuration routine. It scales the input window into + * the output one, updates the window sizes and returns an error or the resize + * coefficient on success. Note: we only use the "Fixed Scaling" on this camera. + */ +static int rj54n1_sensor_scale(struct v4l2_subdev *sd, s32 *in_w, s32 *in_h, + s32 *out_w, s32 *out_h) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + unsigned int skip, resize, input_w = *in_w, input_h = *in_h, + output_w = *out_w, output_h = *out_h; + u16 inc_sel, wb_bit8, wb_left, wb_right, wb_top, wb_bottom; + unsigned int peak, peak_50, peak_60; + int ret; + + /* + * We have a problem with crops, where the window is larger than 512x384 + * and output window is larger than a half of the input one. In this + * case we have to either reduce the input window to equal or below + * 512x384 or the output window to equal or below 1/2 of the input. + */ + if (output_w > max(512U, input_w / 2)) { + if (2 * output_w > RJ54N1_MAX_WIDTH) { + input_w = RJ54N1_MAX_WIDTH; + output_w = RJ54N1_MAX_WIDTH / 2; + } else { + input_w = output_w * 2; + } + + dev_dbg(&client->dev, "Adjusted output width: in %u, out %u\n", + input_w, output_w); + } + + if (output_h > max(384U, input_h / 2)) { + if (2 * output_h > RJ54N1_MAX_HEIGHT) { + input_h = RJ54N1_MAX_HEIGHT; + output_h = RJ54N1_MAX_HEIGHT / 2; + } else { + input_h = output_h * 2; + } + + dev_dbg(&client->dev, "Adjusted output height: in %u, out %u\n", + input_h, output_h); + } + + /* Idea: use the read mode for snapshots, handle separate geometries */ + ret = rj54n1_set_rect(client, RJ54N1_X_OUTPUT_SIZE_S_L, + RJ54N1_Y_OUTPUT_SIZE_S_L, + RJ54N1_XY_OUTPUT_SIZE_S_H, output_w, output_h); + if (!ret) + ret = rj54n1_set_rect(client, RJ54N1_X_OUTPUT_SIZE_P_L, + RJ54N1_Y_OUTPUT_SIZE_P_L, + RJ54N1_XY_OUTPUT_SIZE_P_H, output_w, output_h); + + if (ret < 0) + return ret; + + if (output_w > input_w && output_h > input_h) { + input_w = output_w; + input_h = output_h; + + resize = 1024; + } else { + unsigned int resize_x, resize_y; + resize_x = (input_w * 1024 + output_w / 2) / output_w; + resize_y = (input_h * 1024 + output_h / 2) / output_h; + + /* We want max(resize_x, resize_y), check if it still fits */ + if (resize_x > resize_y && + (output_h * resize_x + 512) / 1024 > RJ54N1_MAX_HEIGHT) + resize = (RJ54N1_MAX_HEIGHT * 1024 + output_h / 2) / + output_h; + else if (resize_y > resize_x && + (output_w * resize_y + 512) / 1024 > RJ54N1_MAX_WIDTH) + resize = (RJ54N1_MAX_WIDTH * 1024 + output_w / 2) / + output_w; + else + resize = max(resize_x, resize_y); + + /* Prohibited value ranges */ + switch (resize) { + case 2040 ... 2047: + resize = 2039; + break; + case 4080 ... 4095: + resize = 4079; + break; + case 8160 ... 8191: + resize = 8159; + break; + case 16320 ... 16384: + resize = 16319; + } + } + + /* Set scaling */ + ret = reg_write(client, RJ54N1_RESIZE_HOLD_L, resize & 0xff); + if (!ret) + ret = reg_write(client, RJ54N1_RESIZE_HOLD_H, resize >> 8); + + if (ret < 0) + return ret; + + /* + * Configure a skipping bitmask. The sensor will select a skipping value + * among set bits automatically. This is very unclear in the datasheet + * too. I was told, in this register one enables all skipping values, + * that are required for a specific resize, and the camera selects + * automatically, which ones to use. But it is unclear how to identify, + * which cropping values are needed. Secondly, why don't we just set all + * bits and let the camera choose? Would it increase processing time and + * reduce the framerate? Using 0xfffc for INC_USE_SEL doesn't seem to + * improve the image quality or stability for larger frames (see comment + * above), but I didn't check the framerate. + */ + skip = min(resize / 1024, 15U); + + inc_sel = 1 << skip; + + if (inc_sel <= 2) + inc_sel = 0xc; + else if (resize & 1023 && skip < 15) + inc_sel |= 1 << (skip + 1); + + ret = reg_write(client, RJ54N1_INC_USE_SEL_L, inc_sel & 0xfc); + if (!ret) + ret = reg_write(client, RJ54N1_INC_USE_SEL_H, inc_sel >> 8); + + if (!rj54n1->auto_wb) { + /* Auto white balance window */ + wb_left = output_w / 16; + wb_right = (3 * output_w / 4 - 3) / 4; + wb_top = output_h / 16; + wb_bottom = (3 * output_h / 4 - 3) / 4; + wb_bit8 = ((wb_left >> 2) & 0x40) | ((wb_top >> 4) & 0x10) | + ((wb_right >> 6) & 4) | ((wb_bottom >> 8) & 1); + + if (!ret) + ret = reg_write(client, RJ54N1_BIT8_WB, wb_bit8); + if (!ret) + ret = reg_write(client, RJ54N1_HCAPS_WB, wb_left); + if (!ret) + ret = reg_write(client, RJ54N1_VCAPS_WB, wb_top); + if (!ret) + ret = reg_write(client, RJ54N1_HCAPE_WB, wb_right); + if (!ret) + ret = reg_write(client, RJ54N1_VCAPE_WB, wb_bottom); + } + + /* Antiflicker */ + peak = 12 * RJ54N1_MAX_WIDTH * (1 << 14) * resize / rj54n1->tgclk_mhz / + 10000; + peak_50 = peak / 6; + peak_60 = peak / 5; + + if (!ret) + ret = reg_write(client, RJ54N1_PEAK_H, + ((peak_50 >> 4) & 0xf0) | (peak_60 >> 8)); + if (!ret) + ret = reg_write(client, RJ54N1_PEAK_50, peak_50); + if (!ret) + ret = reg_write(client, RJ54N1_PEAK_60, peak_60); + if (!ret) + ret = reg_write(client, RJ54N1_PEAK_DIFF, peak / 150); + + /* Start resizing */ + if (!ret) + ret = reg_write(client, RJ54N1_RESIZE_CONTROL, + RESIZE_HOLD_SEL | RESIZE_GO | 1); + + if (ret < 0) + return ret; + + /* Constant taken from manufacturer's example */ + msleep(230); + + ret = reg_write(client, RJ54N1_RESIZE_CONTROL, RESIZE_HOLD_SEL | 1); + if (ret < 0) + return ret; + + *in_w = (output_w * resize + 512) / 1024; + *in_h = (output_h * resize + 512) / 1024; + *out_w = output_w; + *out_h = output_h; + + dev_dbg(&client->dev, "Scaled for %dx%d : %u = %ux%u, skip %u\n", + *in_w, *in_h, resize, output_w, output_h, skip); + + return resize; +} + +static int rj54n1_set_clock(struct i2c_client *client) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int ret; + + /* Enable external clock */ + ret = reg_write(client, RJ54N1_RESET_STANDBY, E_EXCLK | SOFT_STDBY); + /* Leave stand-by. Note: use this when implementing suspend / resume */ + if (!ret) + ret = reg_write(client, RJ54N1_RESET_STANDBY, E_EXCLK); + + if (!ret) + ret = reg_write(client, RJ54N1_PLL_L, PLL_L); + if (!ret) + ret = reg_write(client, RJ54N1_PLL_N, PLL_N); + + /* TGCLK dividers */ + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_TG, + rj54n1->clk_div.ratio_tg); + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_T, + rj54n1->clk_div.ratio_t); + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_R, + rj54n1->clk_div.ratio_r); + + /* Enable TGCLK & RAMP */ + if (!ret) + ret = reg_write(client, RJ54N1_RAMP_TGCLK_EN, 3); + + /* Disable clock output */ + if (!ret) + ret = reg_write(client, RJ54N1_OCLK_DSP, 0); + + /* Set divisors */ + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_OP, + rj54n1->clk_div.ratio_op); + if (!ret) + ret = reg_write(client, RJ54N1_RATIO_O, + rj54n1->clk_div.ratio_o); + + /* Enable OCLK */ + if (!ret) + ret = reg_write(client, RJ54N1_OCLK_SEL_EN, 1); + + /* Use PLL for Timing Generator, write 2 to reserved bits */ + if (!ret) + ret = reg_write(client, RJ54N1_TG_BYPASS, 2); + + /* Take sensor out of reset */ + if (!ret) + ret = reg_write(client, RJ54N1_RESET_STANDBY, + E_EXCLK | SEN_RSTX); + /* Enable PLL */ + if (!ret) + ret = reg_write(client, RJ54N1_PLL_EN, 1); + + /* Wait for PLL to stabilise */ + msleep(10); + + /* Enable clock to frequency divider */ + if (!ret) + ret = reg_write(client, RJ54N1_CLK_RST, 1); + + if (!ret) + ret = reg_read(client, RJ54N1_CLK_RST); + if (ret != 1) { + dev_err(&client->dev, + "Resetting RJ54N1CB0C clock failed: %d!\n", ret); + return -EIO; + } + + /* Start the PLL */ + ret = reg_set(client, RJ54N1_OCLK_DSP, 1, 1); + + /* Enable OCLK */ + if (!ret) + ret = reg_write(client, RJ54N1_OCLK_SEL_EN, 1); + + return ret; +} + +static int rj54n1_reg_init(struct i2c_client *client) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int ret = rj54n1_set_clock(client); + + if (!ret) + ret = reg_write_multiple(client, bank_7, ARRAY_SIZE(bank_7)); + if (!ret) + ret = reg_write_multiple(client, bank_10, ARRAY_SIZE(bank_10)); + + /* Set binning divisors */ + if (!ret) + ret = reg_write(client, RJ54N1_SCALE_1_2_LEV, 3 | (7 << 4)); + if (!ret) + ret = reg_write(client, RJ54N1_SCALE_4_LEV, 0xf); + + /* Switch to fixed resize mode */ + if (!ret) + ret = reg_write(client, RJ54N1_RESIZE_CONTROL, + RESIZE_HOLD_SEL | 1); + + /* Set gain */ + if (!ret) + ret = reg_write(client, RJ54N1_Y_GAIN, 0x84); + + /* + * Mirror the image back: default is upside down and left-to-right... + * Set manual preview / still shot switching + */ + if (!ret) + ret = reg_write(client, RJ54N1_MIRROR_STILL_MODE, 0x27); + + if (!ret) + ret = reg_write_multiple(client, bank_4, ARRAY_SIZE(bank_4)); + + /* Auto exposure area */ + if (!ret) + ret = reg_write(client, RJ54N1_EXPOSURE_CONTROL, 0x80); + /* Check current auto WB config */ + if (!ret) + ret = reg_read(client, RJ54N1_WB_SEL_WEIGHT_I); + if (ret >= 0) { + rj54n1->auto_wb = ret & 0x80; + ret = reg_write_multiple(client, bank_5, ARRAY_SIZE(bank_5)); + } + if (!ret) + ret = reg_write_multiple(client, bank_8, ARRAY_SIZE(bank_8)); + + if (!ret) + ret = reg_write(client, RJ54N1_RESET_STANDBY, + E_EXCLK | DSP_RSTX | SEN_RSTX); + + /* Commit init */ + if (!ret) + ret = rj54n1_commit(client); + + /* Take DSP, TG, sensor out of reset */ + if (!ret) + ret = reg_write(client, RJ54N1_RESET_STANDBY, + E_EXCLK | DSP_RSTX | TG_RSTX | SEN_RSTX); + + /* Start register update? Same register as 0x?FE in many bank_* sets */ + if (!ret) + ret = reg_write(client, RJ54N1_FWFLG, 2); + + /* Constant taken from manufacturer's example */ + msleep(700); + + return ret; +} + +static int rj54n1_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + const struct rj54n1_datafmt *fmt; + int align = mf->code == V4L2_MBUS_FMT_SBGGR10_1X10 || + mf->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_BE || + mf->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADLO_BE || + mf->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE || + mf->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADLO_LE; + + dev_dbg(&client->dev, "%s: code = %d, width = %u, height = %u\n", + __func__, mf->code, mf->width, mf->height); + + fmt = rj54n1_find_datafmt(mf->code, rj54n1_colour_fmts, + ARRAY_SIZE(rj54n1_colour_fmts)); + if (!fmt) { + fmt = rj54n1->fmt; + mf->code = fmt->code; + } + + mf->field = V4L2_FIELD_NONE; + mf->colorspace = fmt->colorspace; + + v4l_bound_align_image(&mf->width, 112, RJ54N1_MAX_WIDTH, align, + &mf->height, 84, RJ54N1_MAX_HEIGHT, align, 0); + + return 0; +} + +static int rj54n1_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct rj54n1 *rj54n1 = to_rj54n1(client); + const struct rj54n1_datafmt *fmt; + int output_w, output_h, max_w, max_h, + input_w = rj54n1->rect.width, input_h = rj54n1->rect.height; + int ret; + + /* + * The host driver can call us without .try_fmt(), so, we have to take + * care ourseleves + */ + rj54n1_try_fmt(sd, mf); + + /* + * Verify if the sensor has just been powered on. TODO: replace this + * with proper PM, when a suitable API is available. + */ + ret = reg_read(client, RJ54N1_RESET_STANDBY); + if (ret < 0) + return ret; + + if (!(ret & E_EXCLK)) { + ret = rj54n1_reg_init(client); + if (ret < 0) + return ret; + } + + dev_dbg(&client->dev, "%s: code = %d, width = %u, height = %u\n", + __func__, mf->code, mf->width, mf->height); + + /* RA_SEL_UL is only relevant for raw modes, ignored otherwise. */ + switch (mf->code) { + case V4L2_MBUS_FMT_YUYV8_2X8: + ret = reg_write(client, RJ54N1_OUT_SEL, 0); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 8, 8); + break; + case V4L2_MBUS_FMT_YVYU8_2X8: + ret = reg_write(client, RJ54N1_OUT_SEL, 0); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 0, 8); + break; + case V4L2_MBUS_FMT_RGB565_2X8_LE: + ret = reg_write(client, RJ54N1_OUT_SEL, 0x11); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 8, 8); + break; + case V4L2_MBUS_FMT_RGB565_2X8_BE: + ret = reg_write(client, RJ54N1_OUT_SEL, 0x11); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 0, 8); + break; + case V4L2_MBUS_FMT_SBGGR10_2X8_PADLO_LE: + ret = reg_write(client, RJ54N1_OUT_SEL, 4); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 8, 8); + if (!ret) + ret = reg_write(client, RJ54N1_RA_SEL_UL, 0); + break; + case V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE: + ret = reg_write(client, RJ54N1_OUT_SEL, 4); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 8, 8); + if (!ret) + ret = reg_write(client, RJ54N1_RA_SEL_UL, 8); + break; + case V4L2_MBUS_FMT_SBGGR10_2X8_PADLO_BE: + ret = reg_write(client, RJ54N1_OUT_SEL, 4); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 0, 8); + if (!ret) + ret = reg_write(client, RJ54N1_RA_SEL_UL, 0); + break; + case V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_BE: + ret = reg_write(client, RJ54N1_OUT_SEL, 4); + if (!ret) + ret = reg_set(client, RJ54N1_BYTE_SWAP, 0, 8); + if (!ret) + ret = reg_write(client, RJ54N1_RA_SEL_UL, 8); + break; + case V4L2_MBUS_FMT_SBGGR10_1X10: + ret = reg_write(client, RJ54N1_OUT_SEL, 5); + break; + default: + ret = -EINVAL; + } + + /* Special case: a raw mode with 10 bits of data per clock tick */ + if (!ret) + ret = reg_set(client, RJ54N1_OCLK_SEL_EN, + (mf->code == V4L2_MBUS_FMT_SBGGR10_1X10) << 1, 2); + + if (ret < 0) + return ret; + + /* Supported scales 1:1 >= scale > 1:16 */ + max_w = mf->width * (16 * 1024 - 1) / 1024; + if (input_w > max_w) + input_w = max_w; + max_h = mf->height * (16 * 1024 - 1) / 1024; + if (input_h > max_h) + input_h = max_h; + + output_w = mf->width; + output_h = mf->height; + + ret = rj54n1_sensor_scale(sd, &input_w, &input_h, &output_w, &output_h); + if (ret < 0) + return ret; + + fmt = rj54n1_find_datafmt(mf->code, rj54n1_colour_fmts, + ARRAY_SIZE(rj54n1_colour_fmts)); + + rj54n1->fmt = fmt; + rj54n1->resize = ret; + rj54n1->rect.width = input_w; + rj54n1->rect.height = input_h; + rj54n1->width = output_w; + rj54n1->height = output_h; + + mf->width = output_w; + mf->height = output_h; + mf->field = V4L2_FIELD_NONE; + mf->colorspace = fmt->colorspace; + + return 0; +} + +static int rj54n1_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + if (id->match.addr != client->addr) + return -ENODEV; + + id->ident = V4L2_IDENT_RJ54N1CB0C; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int rj54n1_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || + reg->reg < 0x400 || reg->reg > 0x1fff) + /* Registers > 0x0800 are only available from Sharp support */ + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + reg->size = 1; + reg->val = reg_read(client, reg->reg); + + if (reg->val > 0xff) + return -EIO; + + return 0; +} + +static int rj54n1_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || + reg->reg < 0x400 || reg->reg > 0x1fff) + /* Registers >= 0x0800 are only available from Sharp support */ + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + if (reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int rj54n1_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +static int rj54n1_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct rj54n1 *rj54n1 = container_of(ctrl->handler, struct rj54n1, hdl); + struct v4l2_subdev *sd = &rj54n1->subdev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int data; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->val) + data = reg_set(client, RJ54N1_MIRROR_STILL_MODE, 0, 1); + else + data = reg_set(client, RJ54N1_MIRROR_STILL_MODE, 1, 1); + if (data < 0) + return -EIO; + return 0; + case V4L2_CID_HFLIP: + if (ctrl->val) + data = reg_set(client, RJ54N1_MIRROR_STILL_MODE, 0, 2); + else + data = reg_set(client, RJ54N1_MIRROR_STILL_MODE, 2, 2); + if (data < 0) + return -EIO; + return 0; + case V4L2_CID_GAIN: + if (reg_write(client, RJ54N1_Y_GAIN, ctrl->val * 2) < 0) + return -EIO; + return 0; + case V4L2_CID_AUTO_WHITE_BALANCE: + /* Auto WB area - whole image */ + if (reg_set(client, RJ54N1_WB_SEL_WEIGHT_I, ctrl->val << 7, + 0x80) < 0) + return -EIO; + rj54n1->auto_wb = ctrl->val; + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops rj54n1_ctrl_ops = { + .s_ctrl = rj54n1_s_ctrl, +}; + +static struct v4l2_subdev_core_ops rj54n1_subdev_core_ops = { + .g_chip_ident = rj54n1_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = rj54n1_g_register, + .s_register = rj54n1_s_register, +#endif + .s_power = rj54n1_s_power, +}; + +static int rj54n1_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = + V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING | + V4L2_MBUS_MASTER | V4L2_MBUS_DATA_ACTIVE_HIGH | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static int rj54n1_s_mbus_config(struct v4l2_subdev *sd, + const struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + /* Figures 2.5-1 to 2.5-3 - default falling pixclk edge */ + if (soc_camera_apply_board_flags(icl, cfg) & + V4L2_MBUS_PCLK_SAMPLE_RISING) + return reg_write(client, RJ54N1_OUT_SIGPO, 1 << 4); + else + return reg_write(client, RJ54N1_OUT_SIGPO, 0); +} + +static struct v4l2_subdev_video_ops rj54n1_subdev_video_ops = { + .s_stream = rj54n1_s_stream, + .s_mbus_fmt = rj54n1_s_fmt, + .g_mbus_fmt = rj54n1_g_fmt, + .try_mbus_fmt = rj54n1_try_fmt, + .enum_mbus_fmt = rj54n1_enum_fmt, + .g_crop = rj54n1_g_crop, + .s_crop = rj54n1_s_crop, + .cropcap = rj54n1_cropcap, + .g_mbus_config = rj54n1_g_mbus_config, + .s_mbus_config = rj54n1_s_mbus_config, +}; + +static struct v4l2_subdev_ops rj54n1_subdev_ops = { + .core = &rj54n1_subdev_core_ops, + .video = &rj54n1_subdev_video_ops, +}; + +/* + * Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one + */ +static int rj54n1_video_probe(struct i2c_client *client, + struct rj54n1_pdata *priv) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + int data1, data2; + int ret; + + ret = rj54n1_s_power(&rj54n1->subdev, 1); + if (ret < 0) + return ret; + + /* Read out the chip version register */ + data1 = reg_read(client, RJ54N1_DEV_CODE); + data2 = reg_read(client, RJ54N1_DEV_CODE2); + + if (data1 != 0x51 || data2 != 0x10) { + ret = -ENODEV; + dev_info(&client->dev, "No RJ54N1CB0C found, read 0x%x:0x%x\n", + data1, data2); + goto done; + } + + /* Configure IOCTL polarity from the platform data: 0 or 1 << 7. */ + ret = reg_write(client, RJ54N1_IOC, priv->ioctl_high << 7); + if (ret < 0) + goto done; + + dev_info(&client->dev, "Detected a RJ54N1CB0C chip ID 0x%x:0x%x\n", + data1, data2); + + ret = v4l2_ctrl_handler_setup(&rj54n1->hdl); + +done: + rj54n1_s_power(&rj54n1->subdev, 0); + return ret; +} + +static int rj54n1_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct rj54n1 *rj54n1; + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct rj54n1_pdata *rj54n1_priv; + int ret; + + if (!icl || !icl->priv) { + dev_err(&client->dev, "RJ54N1CB0C: missing platform data!\n"); + return -EINVAL; + } + + rj54n1_priv = icl->priv; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE\n"); + return -EIO; + } + + rj54n1 = kzalloc(sizeof(struct rj54n1), GFP_KERNEL); + if (!rj54n1) + return -ENOMEM; + + v4l2_i2c_subdev_init(&rj54n1->subdev, client, &rj54n1_subdev_ops); + v4l2_ctrl_handler_init(&rj54n1->hdl, 4); + v4l2_ctrl_new_std(&rj54n1->hdl, &rj54n1_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&rj54n1->hdl, &rj54n1_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&rj54n1->hdl, &rj54n1_ctrl_ops, + V4L2_CID_GAIN, 0, 127, 1, 66); + v4l2_ctrl_new_std(&rj54n1->hdl, &rj54n1_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + rj54n1->subdev.ctrl_handler = &rj54n1->hdl; + if (rj54n1->hdl.error) { + int err = rj54n1->hdl.error; + + kfree(rj54n1); + return err; + } + + rj54n1->clk_div = clk_div; + rj54n1->rect.left = RJ54N1_COLUMN_SKIP; + rj54n1->rect.top = RJ54N1_ROW_SKIP; + rj54n1->rect.width = RJ54N1_MAX_WIDTH; + rj54n1->rect.height = RJ54N1_MAX_HEIGHT; + rj54n1->width = RJ54N1_MAX_WIDTH; + rj54n1->height = RJ54N1_MAX_HEIGHT; + rj54n1->fmt = &rj54n1_colour_fmts[0]; + rj54n1->resize = 1024; + rj54n1->tgclk_mhz = (rj54n1_priv->mclk_freq / PLL_L * PLL_N) / + (clk_div.ratio_tg + 1) / (clk_div.ratio_t + 1); + + ret = rj54n1_video_probe(client, rj54n1_priv); + if (ret < 0) { + v4l2_ctrl_handler_free(&rj54n1->hdl); + kfree(rj54n1); + } + + return ret; +} + +static int rj54n1_remove(struct i2c_client *client) +{ + struct rj54n1 *rj54n1 = to_rj54n1(client); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + v4l2_device_unregister_subdev(&rj54n1->subdev); + if (icl->free_bus) + icl->free_bus(icl); + v4l2_ctrl_handler_free(&rj54n1->hdl); + kfree(rj54n1); + + return 0; +} + +static const struct i2c_device_id rj54n1_id[] = { + { "rj54n1cb0c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rj54n1_id); + +static struct i2c_driver rj54n1_i2c_driver = { + .driver = { + .name = "rj54n1cb0c", + }, + .probe = rj54n1_probe, + .remove = rj54n1_remove, + .id_table = rj54n1_id, +}; + +module_i2c_driver(rj54n1_i2c_driver); + +MODULE_DESCRIPTION("Sharp RJ54N1CB0C Camera driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/soc_camera/tw9910.c b/drivers/media/i2c/soc_camera/tw9910.c new file mode 100644 index 000000000000..140716e71a15 --- /dev/null +++ b/drivers/media/i2c/soc_camera/tw9910.c @@ -0,0 +1,973 @@ +/* + * tw9910 Video Driver + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto + * + * Based on ov772x driver, + * + * Copyright (C) 2008 Kuninori Morimoto + * Copyright 2006-7 Jonathan Corbet + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define GET_ID(val) ((val & 0xF8) >> 3) +#define GET_REV(val) (val & 0x07) + +/* + * register offset + */ +#define ID 0x00 /* Product ID Code Register */ +#define STATUS1 0x01 /* Chip Status Register I */ +#define INFORM 0x02 /* Input Format */ +#define OPFORM 0x03 /* Output Format Control Register */ +#define DLYCTR 0x04 /* Hysteresis and HSYNC Delay Control */ +#define OUTCTR1 0x05 /* Output Control I */ +#define ACNTL1 0x06 /* Analog Control Register 1 */ +#define CROP_HI 0x07 /* Cropping Register, High */ +#define VDELAY_LO 0x08 /* Vertical Delay Register, Low */ +#define VACTIVE_LO 0x09 /* Vertical Active Register, Low */ +#define HDELAY_LO 0x0A /* Horizontal Delay Register, Low */ +#define HACTIVE_LO 0x0B /* Horizontal Active Register, Low */ +#define CNTRL1 0x0C /* Control Register I */ +#define VSCALE_LO 0x0D /* Vertical Scaling Register, Low */ +#define SCALE_HI 0x0E /* Scaling Register, High */ +#define HSCALE_LO 0x0F /* Horizontal Scaling Register, Low */ +#define BRIGHT 0x10 /* BRIGHTNESS Control Register */ +#define CONTRAST 0x11 /* CONTRAST Control Register */ +#define SHARPNESS 0x12 /* SHARPNESS Control Register I */ +#define SAT_U 0x13 /* Chroma (U) Gain Register */ +#define SAT_V 0x14 /* Chroma (V) Gain Register */ +#define HUE 0x15 /* Hue Control Register */ +#define CORING1 0x17 +#define CORING2 0x18 /* Coring and IF compensation */ +#define VBICNTL 0x19 /* VBI Control Register */ +#define ACNTL2 0x1A /* Analog Control 2 */ +#define OUTCTR2 0x1B /* Output Control 2 */ +#define SDT 0x1C /* Standard Selection */ +#define SDTR 0x1D /* Standard Recognition */ +#define TEST 0x1F /* Test Control Register */ +#define CLMPG 0x20 /* Clamping Gain */ +#define IAGC 0x21 /* Individual AGC Gain */ +#define AGCGAIN 0x22 /* AGC Gain */ +#define PEAKWT 0x23 /* White Peak Threshold */ +#define CLMPL 0x24 /* Clamp level */ +#define SYNCT 0x25 /* Sync Amplitude */ +#define MISSCNT 0x26 /* Sync Miss Count Register */ +#define PCLAMP 0x27 /* Clamp Position Register */ +#define VCNTL1 0x28 /* Vertical Control I */ +#define VCNTL2 0x29 /* Vertical Control II */ +#define CKILL 0x2A /* Color Killer Level Control */ +#define COMB 0x2B /* Comb Filter Control */ +#define LDLY 0x2C /* Luma Delay and H Filter Control */ +#define MISC1 0x2D /* Miscellaneous Control I */ +#define LOOP 0x2E /* LOOP Control Register */ +#define MISC2 0x2F /* Miscellaneous Control II */ +#define MVSN 0x30 /* Macrovision Detection */ +#define STATUS2 0x31 /* Chip STATUS II */ +#define HFREF 0x32 /* H monitor */ +#define CLMD 0x33 /* CLAMP MODE */ +#define IDCNTL 0x34 /* ID Detection Control */ +#define CLCNTL1 0x35 /* Clamp Control I */ +#define ANAPLLCTL 0x4C +#define VBIMIN 0x4D +#define HSLOWCTL 0x4E +#define WSS3 0x4F +#define FILLDATA 0x50 +#define SDID 0x51 +#define DID 0x52 +#define WSS1 0x53 +#define WSS2 0x54 +#define VVBI 0x55 +#define LCTL6 0x56 +#define LCTL7 0x57 +#define LCTL8 0x58 +#define LCTL9 0x59 +#define LCTL10 0x5A +#define LCTL11 0x5B +#define LCTL12 0x5C +#define LCTL13 0x5D +#define LCTL14 0x5E +#define LCTL15 0x5F +#define LCTL16 0x60 +#define LCTL17 0x61 +#define LCTL18 0x62 +#define LCTL19 0x63 +#define LCTL20 0x64 +#define LCTL21 0x65 +#define LCTL22 0x66 +#define LCTL23 0x67 +#define LCTL24 0x68 +#define LCTL25 0x69 +#define LCTL26 0x6A +#define HSBEGIN 0x6B +#define HSEND 0x6C +#define OVSDLY 0x6D +#define OVSEND 0x6E +#define VBIDELAY 0x6F + +/* + * register detail + */ + +/* INFORM */ +#define FC27_ON 0x40 /* 1 : Input crystal clock frequency is 27MHz */ +#define FC27_FF 0x00 /* 0 : Square pixel mode. */ + /* Must use 24.54MHz for 60Hz field rate */ + /* source or 29.5MHz for 50Hz field rate */ +#define IFSEL_S 0x10 /* 01 : S-video decoding */ +#define IFSEL_C 0x00 /* 00 : Composite video decoding */ + /* Y input video selection */ +#define YSEL_M0 0x00 /* 00 : Mux0 selected */ +#define YSEL_M1 0x04 /* 01 : Mux1 selected */ +#define YSEL_M2 0x08 /* 10 : Mux2 selected */ +#define YSEL_M3 0x10 /* 11 : Mux3 selected */ + +/* OPFORM */ +#define MODE 0x80 /* 0 : CCIR601 compatible YCrCb 4:2:2 format */ + /* 1 : ITU-R-656 compatible data sequence format */ +#define LEN 0x40 /* 0 : 8-bit YCrCb 4:2:2 output format */ + /* 1 : 16-bit YCrCb 4:2:2 output format.*/ +#define LLCMODE 0x20 /* 1 : LLC output mode. */ + /* 0 : free-run output mode */ +#define AINC 0x10 /* Serial interface auto-indexing control */ + /* 0 : auto-increment */ + /* 1 : non-auto */ +#define VSCTL 0x08 /* 1 : Vertical out ctrl by DVALID */ + /* 0 : Vertical out ctrl by HACTIVE and DVALID */ +#define OEN_TRI_SEL_MASK 0x07 +#define OEN_TRI_SEL_ALL_ON 0x00 /* Enable output for Rev0/Rev1 */ +#define OEN_TRI_SEL_ALL_OFF_r0 0x06 /* All tri-stated for Rev0 */ +#define OEN_TRI_SEL_ALL_OFF_r1 0x07 /* All tri-stated for Rev1 */ + +/* OUTCTR1 */ +#define VSP_LO 0x00 /* 0 : VS pin output polarity is active low */ +#define VSP_HI 0x80 /* 1 : VS pin output polarity is active high. */ + /* VS pin output control */ +#define VSSL_VSYNC 0x00 /* 0 : VSYNC */ +#define VSSL_VACT 0x10 /* 1 : VACT */ +#define VSSL_FIELD 0x20 /* 2 : FIELD */ +#define VSSL_VVALID 0x30 /* 3 : VVALID */ +#define VSSL_ZERO 0x70 /* 7 : 0 */ +#define HSP_LOW 0x00 /* 0 : HS pin output polarity is active low */ +#define HSP_HI 0x08 /* 1 : HS pin output polarity is active high.*/ + /* HS pin output control */ +#define HSSL_HACT 0x00 /* 0 : HACT */ +#define HSSL_HSYNC 0x01 /* 1 : HSYNC */ +#define HSSL_DVALID 0x02 /* 2 : DVALID */ +#define HSSL_HLOCK 0x03 /* 3 : HLOCK */ +#define HSSL_ASYNCW 0x04 /* 4 : ASYNCW */ +#define HSSL_ZERO 0x07 /* 7 : 0 */ + +/* ACNTL1 */ +#define SRESET 0x80 /* resets the device to its default state + * but all register content remain unchanged. + * This bit is self-resetting. + */ +#define ACNTL1_PDN_MASK 0x0e +#define CLK_PDN 0x08 /* system clock power down */ +#define Y_PDN 0x04 /* Luma ADC power down */ +#define C_PDN 0x02 /* Chroma ADC power down */ + +/* ACNTL2 */ +#define ACNTL2_PDN_MASK 0x40 +#define PLL_PDN 0x40 /* PLL power down */ + +/* VBICNTL */ + +/* RTSEL : control the real time signal output from the MPOUT pin */ +#define RTSEL_MASK 0x07 +#define RTSEL_VLOSS 0x00 /* 0000 = Video loss */ +#define RTSEL_HLOCK 0x01 /* 0001 = H-lock */ +#define RTSEL_SLOCK 0x02 /* 0010 = S-lock */ +#define RTSEL_VLOCK 0x03 /* 0011 = V-lock */ +#define RTSEL_MONO 0x04 /* 0100 = MONO */ +#define RTSEL_DET50 0x05 /* 0101 = DET50 */ +#define RTSEL_FIELD 0x06 /* 0110 = FIELD */ +#define RTSEL_RTCO 0x07 /* 0111 = RTCO ( Real Time Control ) */ + +/* HSYNC start and end are constant for now */ +#define HSYNC_START 0x0260 +#define HSYNC_END 0x0300 + +/* + * structure + */ + +struct regval_list { + unsigned char reg_num; + unsigned char value; +}; + +struct tw9910_scale_ctrl { + char *name; + unsigned short width; + unsigned short height; + u16 hscale; + u16 vscale; +}; + +struct tw9910_priv { + struct v4l2_subdev subdev; + struct tw9910_video_info *info; + const struct tw9910_scale_ctrl *scale; + v4l2_std_id norm; + u32 revision; +}; + +static const struct tw9910_scale_ctrl tw9910_ntsc_scales[] = { + { + .name = "NTSC SQ", + .width = 640, + .height = 480, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "NTSC CCIR601", + .width = 720, + .height = 480, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "NTSC SQ (CIF)", + .width = 320, + .height = 240, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "NTSC CCIR601 (CIF)", + .width = 360, + .height = 240, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "NTSC SQ (QCIF)", + .width = 160, + .height = 120, + .hscale = 0x0400, + .vscale = 0x0400, + }, + { + .name = "NTSC CCIR601 (QCIF)", + .width = 180, + .height = 120, + .hscale = 0x0400, + .vscale = 0x0400, + }, +}; + +static const struct tw9910_scale_ctrl tw9910_pal_scales[] = { + { + .name = "PAL SQ", + .width = 768, + .height = 576, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "PAL CCIR601", + .width = 720, + .height = 576, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "PAL SQ (CIF)", + .width = 384, + .height = 288, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "PAL CCIR601 (CIF)", + .width = 360, + .height = 288, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "PAL SQ (QCIF)", + .width = 192, + .height = 144, + .hscale = 0x0400, + .vscale = 0x0400, + }, + { + .name = "PAL CCIR601 (QCIF)", + .width = 180, + .height = 144, + .hscale = 0x0400, + .vscale = 0x0400, + }, +}; + +/* + * general function + */ +static struct tw9910_priv *to_tw9910(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct tw9910_priv, + subdev); +} + +static int tw9910_mask_set(struct i2c_client *client, u8 command, + u8 mask, u8 set) +{ + s32 val = i2c_smbus_read_byte_data(client, command); + if (val < 0) + return val; + + val &= ~mask; + val |= set & mask; + + return i2c_smbus_write_byte_data(client, command, val); +} + +static int tw9910_set_scale(struct i2c_client *client, + const struct tw9910_scale_ctrl *scale) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, SCALE_HI, + (scale->vscale & 0x0F00) >> 4 | + (scale->hscale & 0x0F00) >> 8); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, HSCALE_LO, + scale->hscale & 0x00FF); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, VSCALE_LO, + scale->vscale & 0x00FF); + + return ret; +} + +static int tw9910_set_hsync(struct i2c_client *client) +{ + struct tw9910_priv *priv = to_tw9910(client); + int ret; + + /* bit 10 - 3 */ + ret = i2c_smbus_write_byte_data(client, HSBEGIN, + (HSYNC_START & 0x07F8) >> 3); + if (ret < 0) + return ret; + + /* bit 10 - 3 */ + ret = i2c_smbus_write_byte_data(client, HSEND, + (HSYNC_END & 0x07F8) >> 3); + if (ret < 0) + return ret; + + /* So far only revisions 0 and 1 have been seen */ + /* bit 2 - 0 */ + if (1 == priv->revision) + ret = tw9910_mask_set(client, HSLOWCTL, 0x77, + (HSYNC_START & 0x0007) << 4 | + (HSYNC_END & 0x0007)); + + return ret; +} + +static void tw9910_reset(struct i2c_client *client) +{ + tw9910_mask_set(client, ACNTL1, SRESET, SRESET); + msleep(1); +} + +static int tw9910_power(struct i2c_client *client, int enable) +{ + int ret; + u8 acntl1; + u8 acntl2; + + if (enable) { + acntl1 = 0; + acntl2 = 0; + } else { + acntl1 = CLK_PDN | Y_PDN | C_PDN; + acntl2 = PLL_PDN; + } + + ret = tw9910_mask_set(client, ACNTL1, ACNTL1_PDN_MASK, acntl1); + if (ret < 0) + return ret; + + return tw9910_mask_set(client, ACNTL2, ACNTL2_PDN_MASK, acntl2); +} + +static const struct tw9910_scale_ctrl *tw9910_select_norm(v4l2_std_id norm, + u32 width, u32 height) +{ + const struct tw9910_scale_ctrl *scale; + const struct tw9910_scale_ctrl *ret = NULL; + __u32 diff = 0xffffffff, tmp; + int size, i; + + if (norm & V4L2_STD_NTSC) { + scale = tw9910_ntsc_scales; + size = ARRAY_SIZE(tw9910_ntsc_scales); + } else if (norm & V4L2_STD_PAL) { + scale = tw9910_pal_scales; + size = ARRAY_SIZE(tw9910_pal_scales); + } else { + return NULL; + } + + for (i = 0; i < size; i++) { + tmp = abs(width - scale[i].width) + + abs(height - scale[i].height); + if (tmp < diff) { + diff = tmp; + ret = scale + i; + } + } + + return ret; +} + +/* + * subdevice operations + */ +static int tw9910_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + u8 val; + int ret; + + if (!enable) { + switch (priv->revision) { + case 0: + val = OEN_TRI_SEL_ALL_OFF_r0; + break; + case 1: + val = OEN_TRI_SEL_ALL_OFF_r1; + break; + default: + dev_err(&client->dev, "un-supported revision\n"); + return -EINVAL; + } + } else { + val = OEN_TRI_SEL_ALL_ON; + + if (!priv->scale) { + dev_err(&client->dev, "norm select error\n"); + return -EPERM; + } + + dev_dbg(&client->dev, "%s %dx%d\n", + priv->scale->name, + priv->scale->width, + priv->scale->height); + } + + ret = tw9910_mask_set(client, OPFORM, OEN_TRI_SEL_MASK, val); + if (ret < 0) + return ret; + + return tw9910_power(client, enable); +} + +static int tw9910_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + *norm = priv->norm; + + return 0; +} + +static int tw9910_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + if (!(norm & (V4L2_STD_NTSC | V4L2_STD_PAL))) + return -EINVAL; + + priv->norm = norm; + + return 0; +} + +static int tw9910_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + id->ident = V4L2_IDENT_TW9910; + id->revision = priv->revision; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int tw9910_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + if (reg->reg > 0xff) + return -EINVAL; + + ret = i2c_smbus_read_byte_data(client, reg->reg); + if (ret < 0) + return ret; + + /* + * ret = int + * reg->val = __u64 + */ + reg->val = (__u64)ret; + + return 0; +} + +static int tw9910_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0xff || + reg->val > 0xff) + return -EINVAL; + + return i2c_smbus_write_byte_data(client, reg->reg, reg->val); +} +#endif + +static int tw9910_s_power(struct v4l2_subdev *sd, int on) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + return soc_camera_set_power(&client->dev, icl, on); +} + +static int tw9910_set_frame(struct v4l2_subdev *sd, u32 *width, u32 *height) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + int ret = -EINVAL; + u8 val; + + /* + * select suitable norm + */ + priv->scale = tw9910_select_norm(priv->norm, *width, *height); + if (!priv->scale) + goto tw9910_set_fmt_error; + + /* + * reset hardware + */ + tw9910_reset(client); + + /* + * set bus width + */ + val = 0x00; + if (SOCAM_DATAWIDTH_16 == priv->info->buswidth) + val = LEN; + + ret = tw9910_mask_set(client, OPFORM, LEN, val); + if (ret < 0) + goto tw9910_set_fmt_error; + + /* + * select MPOUT behavior + */ + switch (priv->info->mpout) { + case TW9910_MPO_VLOSS: + val = RTSEL_VLOSS; break; + case TW9910_MPO_HLOCK: + val = RTSEL_HLOCK; break; + case TW9910_MPO_SLOCK: + val = RTSEL_SLOCK; break; + case TW9910_MPO_VLOCK: + val = RTSEL_VLOCK; break; + case TW9910_MPO_MONO: + val = RTSEL_MONO; break; + case TW9910_MPO_DET50: + val = RTSEL_DET50; break; + case TW9910_MPO_FIELD: + val = RTSEL_FIELD; break; + case TW9910_MPO_RTCO: + val = RTSEL_RTCO; break; + default: + val = 0; + } + + ret = tw9910_mask_set(client, VBICNTL, RTSEL_MASK, val); + if (ret < 0) + goto tw9910_set_fmt_error; + + /* + * set scale + */ + ret = tw9910_set_scale(client, priv->scale); + if (ret < 0) + goto tw9910_set_fmt_error; + + /* + * set hsync + */ + ret = tw9910_set_hsync(client); + if (ret < 0) + goto tw9910_set_fmt_error; + + *width = priv->scale->width; + *height = priv->scale->height; + + return ret; + +tw9910_set_fmt_error: + + tw9910_reset(client); + priv->scale = NULL; + + return ret; +} + +static int tw9910_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + a->c.left = 0; + a->c.top = 0; + if (priv->norm & V4L2_STD_NTSC) { + a->c.width = 640; + a->c.height = 480; + } else { + a->c.width = 768; + a->c.height = 576; + } + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int tw9910_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + a->bounds.left = 0; + a->bounds.top = 0; + if (priv->norm & V4L2_STD_NTSC) { + a->bounds.width = 640; + a->bounds.height = 480; + } else { + a->bounds.width = 768; + a->bounds.height = 576; + } + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int tw9910_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + + if (!priv->scale) { + priv->scale = tw9910_select_norm(priv->norm, 640, 480); + if (!priv->scale) + return -EINVAL; + } + + mf->width = priv->scale->width; + mf->height = priv->scale->height; + mf->code = V4L2_MBUS_FMT_UYVY8_2X8; + mf->colorspace = V4L2_COLORSPACE_JPEG; + mf->field = V4L2_FIELD_INTERLACED_BT; + + return 0; +} + +static int tw9910_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + u32 width = mf->width, height = mf->height; + int ret; + + WARN_ON(mf->field != V4L2_FIELD_ANY && + mf->field != V4L2_FIELD_INTERLACED_BT); + + /* + * check color format + */ + if (mf->code != V4L2_MBUS_FMT_UYVY8_2X8) + return -EINVAL; + + mf->colorspace = V4L2_COLORSPACE_JPEG; + + ret = tw9910_set_frame(sd, &width, &height); + if (!ret) { + mf->width = width; + mf->height = height; + } + return ret; +} + +static int tw9910_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tw9910_priv *priv = to_tw9910(client); + const struct tw9910_scale_ctrl *scale; + + if (V4L2_FIELD_ANY == mf->field) { + mf->field = V4L2_FIELD_INTERLACED_BT; + } else if (V4L2_FIELD_INTERLACED_BT != mf->field) { + dev_err(&client->dev, "Field type %d invalid.\n", mf->field); + return -EINVAL; + } + + mf->code = V4L2_MBUS_FMT_UYVY8_2X8; + mf->colorspace = V4L2_COLORSPACE_JPEG; + + /* + * select suitable norm + */ + scale = tw9910_select_norm(priv->norm, mf->width, mf->height); + if (!scale) + return -EINVAL; + + mf->width = scale->width; + mf->height = scale->height; + + return 0; +} + +static int tw9910_video_probe(struct i2c_client *client) +{ + struct tw9910_priv *priv = to_tw9910(client); + s32 id; + int ret; + + /* + * tw9910 only use 8 or 16 bit bus width + */ + if (SOCAM_DATAWIDTH_16 != priv->info->buswidth && + SOCAM_DATAWIDTH_8 != priv->info->buswidth) { + dev_err(&client->dev, "bus width error\n"); + return -ENODEV; + } + + ret = tw9910_s_power(&priv->subdev, 1); + if (ret < 0) + return ret; + + /* + * check and show Product ID + * So far only revisions 0 and 1 have been seen + */ + id = i2c_smbus_read_byte_data(client, ID); + priv->revision = GET_REV(id); + id = GET_ID(id); + + if (0x0B != id || + 0x01 < priv->revision) { + dev_err(&client->dev, + "Product ID error %x:%x\n", + id, priv->revision); + ret = -ENODEV; + goto done; + } + + dev_info(&client->dev, + "tw9910 Product ID %0x:%0x\n", id, priv->revision); + + priv->norm = V4L2_STD_NTSC; + +done: + tw9910_s_power(&priv->subdev, 0); + return ret; +} + +static struct v4l2_subdev_core_ops tw9910_subdev_core_ops = { + .g_chip_ident = tw9910_g_chip_ident, + .s_std = tw9910_s_std, + .g_std = tw9910_g_std, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = tw9910_g_register, + .s_register = tw9910_s_register, +#endif + .s_power = tw9910_s_power, +}; + +static int tw9910_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index) + return -EINVAL; + + *code = V4L2_MBUS_FMT_UYVY8_2X8; + return 0; +} + +static int tw9910_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + + cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_LOW | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_LOW | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_PARALLEL; + cfg->flags = soc_camera_apply_board_flags(icl, cfg); + + return 0; +} + +static int tw9910_s_mbus_config(struct v4l2_subdev *sd, + const struct v4l2_mbus_config *cfg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + u8 val = VSSL_VVALID | HSSL_DVALID; + unsigned long flags = soc_camera_apply_board_flags(icl, cfg); + + /* + * set OUTCTR1 + * + * We use VVALID and DVALID signals to control VSYNC and HSYNC + * outputs, in this mode their polarity is inverted. + */ + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + val |= HSP_HI; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + val |= VSP_HI; + + return i2c_smbus_write_byte_data(client, OUTCTR1, val); +} + +static struct v4l2_subdev_video_ops tw9910_subdev_video_ops = { + .s_stream = tw9910_s_stream, + .g_mbus_fmt = tw9910_g_fmt, + .s_mbus_fmt = tw9910_s_fmt, + .try_mbus_fmt = tw9910_try_fmt, + .cropcap = tw9910_cropcap, + .g_crop = tw9910_g_crop, + .enum_mbus_fmt = tw9910_enum_fmt, + .g_mbus_config = tw9910_g_mbus_config, + .s_mbus_config = tw9910_s_mbus_config, +}; + +static struct v4l2_subdev_ops tw9910_subdev_ops = { + .core = &tw9910_subdev_core_ops, + .video = &tw9910_subdev_video_ops, +}; + +/* + * i2c_driver function + */ + +static int tw9910_probe(struct i2c_client *client, + const struct i2c_device_id *did) + +{ + struct tw9910_priv *priv; + struct tw9910_video_info *info; + struct i2c_adapter *adapter = + to_i2c_adapter(client->dev.parent); + struct soc_camera_link *icl = soc_camera_i2c_to_link(client); + int ret; + + if (!icl || !icl->priv) { + dev_err(&client->dev, "TW9910: missing platform data!\n"); + return -EINVAL; + } + + info = icl->priv; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "I2C-Adapter doesn't support " + "I2C_FUNC_SMBUS_BYTE_DATA\n"); + return -EIO; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = info; + + v4l2_i2c_subdev_init(&priv->subdev, client, &tw9910_subdev_ops); + + ret = tw9910_video_probe(client); + if (ret) + kfree(priv); + + return ret; +} + +static int tw9910_remove(struct i2c_client *client) +{ + struct tw9910_priv *priv = to_tw9910(client); + + kfree(priv); + return 0; +} + +static const struct i2c_device_id tw9910_id[] = { + { "tw9910", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tw9910_id); + +static struct i2c_driver tw9910_i2c_driver = { + .driver = { + .name = "tw9910", + }, + .probe = tw9910_probe, + .remove = tw9910_remove, + .id_table = tw9910_id, +}; + +module_i2c_driver(tw9910_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for tw9910"); +MODULE_AUTHOR("Kuninori Morimoto"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/sr030pc30.c b/drivers/media/i2c/sr030pc30.c similarity index 100% rename from drivers/media/video/sr030pc30.c rename to drivers/media/i2c/sr030pc30.c diff --git a/drivers/media/i2c/tcm825x.c b/drivers/media/i2c/tcm825x.c new file mode 100644 index 000000000000..9252529fc5dd --- /dev/null +++ b/drivers/media/i2c/tcm825x.c @@ -0,0 +1,937 @@ +/* + * drivers/media/i2c/tcm825x.c + * + * TCM825X camera sensor driver. + * + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus + * + * Based on code from David Cohen + * + * This driver was based on ov9640 sensor driver from MontaVista + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include + +#include "tcm825x.h" + +/* + * The sensor has two fps modes: the lower one just gives half the fps + * at the same xclk than the high one. + */ +#define MAX_FPS 30 +#define MIN_FPS 8 +#define MAX_HALF_FPS (MAX_FPS / 2) +#define HIGH_FPS_MODE_LOWER_LIMIT 14 +#define DEFAULT_FPS MAX_HALF_FPS + +struct tcm825x_sensor { + const struct tcm825x_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_fract timeperframe; +}; + +/* list of image formats supported by TCM825X sensor */ +static const struct v4l2_fmtdesc tcm825x_formats[] = { + { + .description = "YUYV (YUV 4:2:2), packed", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, { + /* Note: V4L2 defines RGB565 as: + * + * Byte 0 Byte 1 + * g2 g1 g0 r4 r3 r2 r1 r0 b4 b3 b2 b1 b0 g5 g4 g3 + * + * We interpret RGB565 as: + * + * Byte 0 Byte 1 + * g2 g1 g0 b4 b3 b2 b1 b0 r4 r3 r2 r1 r0 g5 g4 g3 + */ + .description = "RGB565, le", + .pixelformat = V4L2_PIX_FMT_RGB565, + }, +}; + +#define TCM825X_NUM_CAPTURE_FORMATS ARRAY_SIZE(tcm825x_formats) + +/* + * TCM825X register configuration for all combinations of pixel format and + * image size + */ +static const struct tcm825x_reg subqcif = { 0x20, TCM825X_PICSIZ }; +static const struct tcm825x_reg qcif = { 0x18, TCM825X_PICSIZ }; +static const struct tcm825x_reg cif = { 0x14, TCM825X_PICSIZ }; +static const struct tcm825x_reg qqvga = { 0x0c, TCM825X_PICSIZ }; +static const struct tcm825x_reg qvga = { 0x04, TCM825X_PICSIZ }; +static const struct tcm825x_reg vga = { 0x00, TCM825X_PICSIZ }; + +static const struct tcm825x_reg yuv422 = { 0x00, TCM825X_PICFMT }; +static const struct tcm825x_reg rgb565 = { 0x02, TCM825X_PICFMT }; + +/* Our own specific controls */ +#define V4L2_CID_ALC V4L2_CID_PRIVATE_BASE +#define V4L2_CID_H_EDGE_EN V4L2_CID_PRIVATE_BASE + 1 +#define V4L2_CID_V_EDGE_EN V4L2_CID_PRIVATE_BASE + 2 +#define V4L2_CID_LENS V4L2_CID_PRIVATE_BASE + 3 +#define V4L2_CID_MAX_EXPOSURE_TIME V4L2_CID_PRIVATE_BASE + 4 +#define V4L2_CID_LAST_PRIV V4L2_CID_MAX_EXPOSURE_TIME + +/* Video controls */ +static struct vcontrol { + struct v4l2_queryctrl qc; + u16 reg; + u16 start_bit; +} video_control[] = { + { + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain", + .minimum = 0, + .maximum = 63, + .step = 1, + }, + .reg = TCM825X_AG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Red Balance", + .minimum = 0, + .maximum = 255, + .step = 1, + }, + .reg = TCM825X_MRG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Blue Balance", + .minimum = 0, + .maximum = 255, + .step = 1, + }, + .reg = TCM825X_MBG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_AUTO_WHITE_BALANCE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto White Balance", + .minimum = 0, + .maximum = 1, + .step = 0, + }, + .reg = TCM825X_AWBSW, + .start_bit = 7, + }, + { + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Exposure Time", + .minimum = 0, + .maximum = 0x1fff, + .step = 1, + }, + .reg = TCM825X_ESRSPD_U, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mirror Image", + .minimum = 0, + .maximum = 1, + .step = 0, + }, + .reg = TCM825X_H_INV, + .start_bit = 6, + }, + { + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical Flip", + .minimum = 0, + .maximum = 1, + .step = 0, + }, + .reg = TCM825X_V_INV, + .start_bit = 7, + }, + /* Private controls */ + { + { + .id = V4L2_CID_ALC, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto Luminance Control", + .minimum = 0, + .maximum = 1, + .step = 0, + }, + .reg = TCM825X_ALCSW, + .start_bit = 7, + }, + { + { + .id = V4L2_CID_H_EDGE_EN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Horizontal Edge Enhancement", + .minimum = 0, + .maximum = 0xff, + .step = 1, + }, + .reg = TCM825X_HDTG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_V_EDGE_EN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Vertical Edge Enhancement", + .minimum = 0, + .maximum = 0xff, + .step = 1, + }, + .reg = TCM825X_VDTG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_LENS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Lens Shading Compensation", + .minimum = 0, + .maximum = 0x3f, + .step = 1, + }, + .reg = TCM825X_LENS, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_MAX_EXPOSURE_TIME, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Maximum Exposure Time", + .minimum = 0, + .maximum = 0x3, + .step = 1, + }, + .reg = TCM825X_ESRLIM, + .start_bit = 5, + }, +}; + + +static const struct tcm825x_reg *tcm825x_siz_reg[NUM_IMAGE_SIZES] = +{ &subqcif, &qqvga, &qcif, &qvga, &cif, &vga }; + +static const struct tcm825x_reg *tcm825x_fmt_reg[NUM_PIXEL_FORMATS] = +{ &yuv422, &rgb565 }; + +/* + * Read a value from a register in an TCM825X sensor device. The value is + * returned in 'val'. + * Returns zero if successful, or non-zero otherwise. + */ +static int tcm825x_read_reg(struct i2c_client *client, int reg) +{ + int err; + struct i2c_msg msg[2]; + u8 reg_buf, data_buf = 0; + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = ®_buf; + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = &data_buf; + + reg_buf = reg; + + err = i2c_transfer(client->adapter, msg, 2); + if (err < 0) + return err; + return data_buf; +} + +/* + * Write a value to a register in an TCM825X sensor device. + * Returns zero if successful, or non-zero otherwise. + */ +static int tcm825x_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + int err; + struct i2c_msg msg[1]; + unsigned char data[2]; + + if (!client->adapter) + return -ENODEV; + + msg->addr = client->addr; + msg->flags = 0; + msg->len = 2; + msg->buf = data; + data[0] = reg; + data[1] = val; + err = i2c_transfer(client->adapter, msg, 1); + if (err >= 0) + return 0; + return err; +} + +static int __tcm825x_write_reg_mask(struct i2c_client *client, + u8 reg, u8 val, u8 mask) +{ + int rc; + + /* need to do read - modify - write */ + rc = tcm825x_read_reg(client, reg); + if (rc < 0) + return rc; + + rc &= (~mask); /* Clear the masked bits */ + val &= mask; /* Enforce mask on value */ + val |= rc; + + /* write the new value to the register */ + rc = tcm825x_write_reg(client, reg, val); + if (rc) + return rc; + + return 0; +} + +#define tcm825x_write_reg_mask(client, regmask, val) \ + __tcm825x_write_reg_mask(client, TCM825X_ADDR((regmask)), val, \ + TCM825X_MASK((regmask))) + + +/* + * Initialize a list of TCM825X registers. + * The list of registers is terminated by the pair of values + * { TCM825X_REG_TERM, TCM825X_VAL_TERM }. + * Returns zero if successful, or non-zero otherwise. + */ +static int tcm825x_write_default_regs(struct i2c_client *client, + const struct tcm825x_reg *reglist) +{ + int err; + const struct tcm825x_reg *next = reglist; + + while (!((next->reg == TCM825X_REG_TERM) + && (next->val == TCM825X_VAL_TERM))) { + err = tcm825x_write_reg(client, next->reg, next->val); + if (err) { + dev_err(&client->dev, "register writing failed\n"); + return err; + } + next++; + } + + return 0; +} + +static struct vcontrol *find_vctrl(int id) +{ + int i; + + if (id < V4L2_CID_BASE) + return NULL; + + for (i = 0; i < ARRAY_SIZE(video_control); i++) + if (video_control[i].qc.id == id) + return &video_control[i]; + + return NULL; +} + +/* + * Find the best match for a requested image capture size. The best match + * is chosen as the nearest match that has the same number or fewer pixels + * as the requested size, or the smallest image size if the requested size + * has fewer pixels than the smallest image. + */ +static enum image_size tcm825x_find_size(struct v4l2_int_device *s, + unsigned int width, + unsigned int height) +{ + enum image_size isize; + unsigned long pixels = width * height; + struct tcm825x_sensor *sensor = s->priv; + + for (isize = subQCIF; isize < VGA; isize++) { + if (tcm825x_sizes[isize + 1].height + * tcm825x_sizes[isize + 1].width > pixels) { + dev_dbg(&sensor->i2c_client->dev, "size %d\n", isize); + + return isize; + } + } + + dev_dbg(&sensor->i2c_client->dev, "format default VGA\n"); + + return VGA; +} + +/* + * Configure the TCM825X for current image size, pixel format, and + * frame period. fper is the frame period (in seconds) expressed as a + * fraction. Returns zero if successful, or non-zero otherwise. The + * actual frame period is returned in fper. + */ +static int tcm825x_configure(struct v4l2_int_device *s) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_pix_format *pix = &sensor->pix; + enum image_size isize = tcm825x_find_size(s, pix->width, pix->height); + struct v4l2_fract *fper = &sensor->timeperframe; + enum pixel_format pfmt; + int err; + u32 tgt_fps; + u8 val; + + /* common register initialization */ + err = tcm825x_write_default_regs( + sensor->i2c_client, sensor->platform_data->default_regs()); + if (err) + return err; + + /* configure image size */ + val = tcm825x_siz_reg[isize]->val; + dev_dbg(&sensor->i2c_client->dev, + "configuring image size %d\n", isize); + err = tcm825x_write_reg_mask(sensor->i2c_client, + tcm825x_siz_reg[isize]->reg, val); + if (err) + return err; + + /* configure pixel format */ + switch (pix->pixelformat) { + default: + case V4L2_PIX_FMT_RGB565: + pfmt = RGB565; + break; + case V4L2_PIX_FMT_UYVY: + pfmt = YUV422; + break; + } + + dev_dbg(&sensor->i2c_client->dev, + "configuring pixel format %d\n", pfmt); + val = tcm825x_fmt_reg[pfmt]->val; + + err = tcm825x_write_reg_mask(sensor->i2c_client, + tcm825x_fmt_reg[pfmt]->reg, val); + if (err) + return err; + + /* + * For frame rate < 15, the FPS reg (addr 0x02, bit 7) must be + * set. Frame rate will be halved from the normal. + */ + tgt_fps = fper->denominator / fper->numerator; + if (tgt_fps <= HIGH_FPS_MODE_LOWER_LIMIT) { + val = tcm825x_read_reg(sensor->i2c_client, 0x02); + val |= 0x80; + tcm825x_write_reg(sensor->i2c_client, 0x02, val); + } + + return 0; +} + +static int ioctl_queryctrl(struct v4l2_int_device *s, + struct v4l2_queryctrl *qc) +{ + struct vcontrol *control; + + control = find_vctrl(qc->id); + + if (control == NULL) + return -EINVAL; + + *qc = control->qc; + + return 0; +} + +static int ioctl_g_ctrl(struct v4l2_int_device *s, + struct v4l2_control *vc) +{ + struct tcm825x_sensor *sensor = s->priv; + struct i2c_client *client = sensor->i2c_client; + int val, r; + struct vcontrol *lvc; + + /* exposure time is special, spread across 2 registers */ + if (vc->id == V4L2_CID_EXPOSURE) { + int val_lower, val_upper; + + val_upper = tcm825x_read_reg(client, + TCM825X_ADDR(TCM825X_ESRSPD_U)); + if (val_upper < 0) + return val_upper; + val_lower = tcm825x_read_reg(client, + TCM825X_ADDR(TCM825X_ESRSPD_L)); + if (val_lower < 0) + return val_lower; + + vc->value = ((val_upper & 0x1f) << 8) | (val_lower); + return 0; + } + + lvc = find_vctrl(vc->id); + if (lvc == NULL) + return -EINVAL; + + r = tcm825x_read_reg(client, TCM825X_ADDR(lvc->reg)); + if (r < 0) + return r; + val = r & TCM825X_MASK(lvc->reg); + val >>= lvc->start_bit; + + if (val < 0) + return val; + + if (vc->id == V4L2_CID_HFLIP || vc->id == V4L2_CID_VFLIP) + val ^= sensor->platform_data->is_upside_down(); + + vc->value = val; + return 0; +} + +static int ioctl_s_ctrl(struct v4l2_int_device *s, + struct v4l2_control *vc) +{ + struct tcm825x_sensor *sensor = s->priv; + struct i2c_client *client = sensor->i2c_client; + struct vcontrol *lvc; + int val = vc->value; + + /* exposure time is special, spread across 2 registers */ + if (vc->id == V4L2_CID_EXPOSURE) { + int val_lower, val_upper; + val_lower = val & TCM825X_MASK(TCM825X_ESRSPD_L); + val_upper = (val >> 8) & TCM825X_MASK(TCM825X_ESRSPD_U); + + if (tcm825x_write_reg_mask(client, + TCM825X_ESRSPD_U, val_upper)) + return -EIO; + + if (tcm825x_write_reg_mask(client, + TCM825X_ESRSPD_L, val_lower)) + return -EIO; + + return 0; + } + + lvc = find_vctrl(vc->id); + if (lvc == NULL) + return -EINVAL; + + if (vc->id == V4L2_CID_HFLIP || vc->id == V4L2_CID_VFLIP) + val ^= sensor->platform_data->is_upside_down(); + + val = val << lvc->start_bit; + if (tcm825x_write_reg_mask(client, lvc->reg, val)) + return -EIO; + + return 0; +} + +static int ioctl_enum_fmt_cap(struct v4l2_int_device *s, + struct v4l2_fmtdesc *fmt) +{ + int index = fmt->index; + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (index >= TCM825X_NUM_CAPTURE_FORMATS) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + fmt->flags = tcm825x_formats[index].flags; + strlcpy(fmt->description, tcm825x_formats[index].description, + sizeof(fmt->description)); + fmt->pixelformat = tcm825x_formats[index].pixelformat; + + return 0; +} + +static int ioctl_try_fmt_cap(struct v4l2_int_device *s, + struct v4l2_format *f) +{ + struct tcm825x_sensor *sensor = s->priv; + enum image_size isize; + int ifmt; + struct v4l2_pix_format *pix = &f->fmt.pix; + + isize = tcm825x_find_size(s, pix->width, pix->height); + dev_dbg(&sensor->i2c_client->dev, "isize = %d num_capture = %lu\n", + isize, (unsigned long)TCM825X_NUM_CAPTURE_FORMATS); + + pix->width = tcm825x_sizes[isize].width; + pix->height = tcm825x_sizes[isize].height; + + for (ifmt = 0; ifmt < TCM825X_NUM_CAPTURE_FORMATS; ifmt++) + if (pix->pixelformat == tcm825x_formats[ifmt].pixelformat) + break; + + if (ifmt == TCM825X_NUM_CAPTURE_FORMATS) + ifmt = 0; /* Default = YUV 4:2:2 */ + + pix->pixelformat = tcm825x_formats[ifmt].pixelformat; + pix->field = V4L2_FIELD_NONE; + pix->bytesperline = pix->width * TCM825X_BYTES_PER_PIXEL; + pix->sizeimage = pix->bytesperline * pix->height; + pix->priv = 0; + dev_dbg(&sensor->i2c_client->dev, "format = 0x%08x\n", + pix->pixelformat); + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_UYVY: + default: + pix->colorspace = V4L2_COLORSPACE_JPEG; + break; + case V4L2_PIX_FMT_RGB565: + pix->colorspace = V4L2_COLORSPACE_SRGB; + break; + } + + return 0; +} + +static int ioctl_s_fmt_cap(struct v4l2_int_device *s, + struct v4l2_format *f) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_pix_format *pix = &f->fmt.pix; + int rval; + + rval = ioctl_try_fmt_cap(s, f); + if (rval) + return rval; + + rval = tcm825x_configure(s); + + sensor->pix = *pix; + + return rval; +} + +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, + struct v4l2_format *f) +{ + struct tcm825x_sensor *sensor = s->priv; + + f->fmt.pix = sensor->pix; + + return 0; +} + +static int ioctl_g_parm(struct v4l2_int_device *s, + struct v4l2_streamparm *a) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + cparm->capability = V4L2_CAP_TIMEPERFRAME; + cparm->timeperframe = sensor->timeperframe; + + return 0; +} + +static int ioctl_s_parm(struct v4l2_int_device *s, + struct v4l2_streamparm *a) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + int rval; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if ((timeperframe->numerator == 0) + || (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + + tgt_fps = timeperframe->denominator / timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + + sensor->timeperframe = *timeperframe; + + rval = tcm825x_configure(s); + + return rval; +} + +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct tcm825x_sensor *sensor = s->priv; + + return sensor->platform_data->power_set(on); +} + +/* + * Given the image capture format in pix, the nominal frame period in + * timeperframe, calculate the required xclk frequency. + * + * TCM825X input frequency characteristics are: + * Minimum 11.9 MHz, Typical 24.57 MHz and maximum 25/27 MHz + */ + +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &sensor->timeperframe; + u32 tgt_xclk; /* target xclk */ + u32 tgt_fps; /* target frames per secound */ + int rval; + + rval = sensor->platform_data->ifparm(p); + if (rval) + return rval; + + tgt_fps = timeperframe->denominator / timeperframe->numerator; + + tgt_xclk = (tgt_fps <= HIGH_FPS_MODE_LOWER_LIMIT) ? + (2457 * tgt_fps) / MAX_HALF_FPS : + (2457 * tgt_fps) / MAX_FPS; + tgt_xclk *= 10000; + + tgt_xclk = min(tgt_xclk, (u32)TCM825X_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)TCM825X_XCLK_MIN); + + p->u.bt656.clock_curr = tgt_xclk; + + return 0; +} + +static int ioctl_g_needs_reset(struct v4l2_int_device *s, void *buf) +{ + struct tcm825x_sensor *sensor = s->priv; + + return sensor->platform_data->needs_reset(s, buf, &sensor->pix); +} + +static int ioctl_reset(struct v4l2_int_device *s) +{ + return -EBUSY; +} + +static int ioctl_init(struct v4l2_int_device *s) +{ + return tcm825x_configure(s); +} + +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + return 0; +} + +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct tcm825x_sensor *sensor = s->priv; + int r; + + r = tcm825x_read_reg(sensor->i2c_client, 0x01); + if (r < 0) + return r; + if (r == 0) { + dev_err(&sensor->i2c_client->dev, "device not detected\n"); + return -EIO; + } + return 0; +} + +static struct v4l2_int_ioctl_desc tcm825x_ioctl_desc[] = { + { vidioc_int_dev_init_num, + (v4l2_int_ioctl_func *)ioctl_dev_init }, + { vidioc_int_dev_exit_num, + (v4l2_int_ioctl_func *)ioctl_dev_exit }, + { vidioc_int_s_power_num, + (v4l2_int_ioctl_func *)ioctl_s_power }, + { vidioc_int_g_ifparm_num, + (v4l2_int_ioctl_func *)ioctl_g_ifparm }, + { vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset }, + { vidioc_int_reset_num, + (v4l2_int_ioctl_func *)ioctl_reset }, + { vidioc_int_init_num, + (v4l2_int_ioctl_func *)ioctl_init }, + { vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap }, + { vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap }, + { vidioc_int_g_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_g_fmt_cap }, + { vidioc_int_s_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_s_fmt_cap }, + { vidioc_int_g_parm_num, + (v4l2_int_ioctl_func *)ioctl_g_parm }, + { vidioc_int_s_parm_num, + (v4l2_int_ioctl_func *)ioctl_s_parm }, + { vidioc_int_queryctrl_num, + (v4l2_int_ioctl_func *)ioctl_queryctrl }, + { vidioc_int_g_ctrl_num, + (v4l2_int_ioctl_func *)ioctl_g_ctrl }, + { vidioc_int_s_ctrl_num, + (v4l2_int_ioctl_func *)ioctl_s_ctrl }, +}; + +static struct v4l2_int_slave tcm825x_slave = { + .ioctls = tcm825x_ioctl_desc, + .num_ioctls = ARRAY_SIZE(tcm825x_ioctl_desc), +}; + +static struct tcm825x_sensor tcm825x; + +static struct v4l2_int_device tcm825x_int_device = { + .module = THIS_MODULE, + .name = TCM825X_NAME, + .priv = &tcm825x, + .type = v4l2_int_type_slave, + .u = { + .slave = &tcm825x_slave, + }, +}; + +static int tcm825x_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct tcm825x_sensor *sensor = &tcm825x; + + if (i2c_get_clientdata(client)) + return -EBUSY; + + sensor->platform_data = client->dev.platform_data; + + if (sensor->platform_data == NULL + || !sensor->platform_data->is_okay()) + return -ENODEV; + + sensor->v4l2_int_device = &tcm825x_int_device; + + sensor->i2c_client = client; + i2c_set_clientdata(client, sensor); + + /* Make the default capture format QVGA RGB565 */ + sensor->pix.width = tcm825x_sizes[QVGA].width; + sensor->pix.height = tcm825x_sizes[QVGA].height; + sensor->pix.pixelformat = V4L2_PIX_FMT_RGB565; + + return v4l2_int_device_register(sensor->v4l2_int_device); +} + +static int tcm825x_remove(struct i2c_client *client) +{ + struct tcm825x_sensor *sensor = i2c_get_clientdata(client); + + if (!client->adapter) + return -ENODEV; /* our client isn't attached */ + + v4l2_int_device_unregister(sensor->v4l2_int_device); + + return 0; +} + +static const struct i2c_device_id tcm825x_id[] = { + { "tcm825x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcm825x_id); + +static struct i2c_driver tcm825x_i2c_driver = { + .driver = { + .name = TCM825X_NAME, + }, + .probe = tcm825x_probe, + .remove = tcm825x_remove, + .id_table = tcm825x_id, +}; + +static struct tcm825x_sensor tcm825x = { + .timeperframe = { + .numerator = 1, + .denominator = DEFAULT_FPS, + }, +}; + +static int __init tcm825x_init(void) +{ + int rval; + + rval = i2c_add_driver(&tcm825x_i2c_driver); + if (rval) + printk(KERN_INFO "%s: failed registering " TCM825X_NAME "\n", + __func__); + + return rval; +} + +static void __exit tcm825x_exit(void) +{ + i2c_del_driver(&tcm825x_i2c_driver); +} + +/* + * FIXME: Menelaus isn't ready (?) at module_init stage, so use + * late_initcall for now. + */ +late_initcall(tcm825x_init); +module_exit(tcm825x_exit); + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_DESCRIPTION("TCM825x camera sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/tcm825x.h b/drivers/media/i2c/tcm825x.h new file mode 100644 index 000000000000..8ebab953963f --- /dev/null +++ b/drivers/media/i2c/tcm825x.h @@ -0,0 +1,200 @@ +/* + * drivers/media/i2c/tcm825x.h + * + * Register definitions for the TCM825X CameraChip. + * + * Author: David Cohen (david.cohen@indt.org.br) + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * This file was based on ov9640.h from MontaVista + */ + +#ifndef TCM825X_H +#define TCM825X_H + +#include + +#include + +#define TCM825X_NAME "tcm825x" + +#define TCM825X_MASK(x) x & 0x00ff +#define TCM825X_ADDR(x) (x & 0xff00) >> 8 + +/* The TCM825X I2C sensor chip has a fixed slave address of 0x3d. */ +#define TCM825X_I2C_ADDR 0x3d + +/* + * define register offsets for the TCM825X sensor chip + * OFFSET(8 bits) + MASK(8 bits) + * MASK bit 4 and 3 are used when the register uses more than one address + */ +#define TCM825X_FPS 0x0280 +#define TCM825X_ACF 0x0240 +#define TCM825X_DOUTBUF 0x020C +#define TCM825X_DCLKP 0x0202 +#define TCM825X_ACFDET 0x0201 +#define TCM825X_DOUTSW 0x0380 +#define TCM825X_DATAHZ 0x0340 +#define TCM825X_PICSIZ 0x033c +#define TCM825X_PICFMT 0x0302 +#define TCM825X_V_INV 0x0480 +#define TCM825X_H_INV 0x0440 +#define TCM825X_ESRLSW 0x0430 +#define TCM825X_V_LENGTH 0x040F +#define TCM825X_ALCSW 0x0580 +#define TCM825X_ESRLIM 0x0560 +#define TCM825X_ESRSPD_U 0x051F +#define TCM825X_ESRSPD_L 0x06FF +#define TCM825X_AG 0x07FF +#define TCM825X_ESRSPD2 0x06FF +#define TCM825X_ALCMODE 0x0830 +#define TCM825X_ALCH 0x080F +#define TCM825X_ALCL 0x09FF +#define TCM825X_AWBSW 0x0A80 +#define TCM825X_MRG 0x0BFF +#define TCM825X_MBG 0x0CFF +#define TCM825X_GAMSW 0x0D80 +#define TCM825X_HDTG 0x0EFF +#define TCM825X_VDTG 0x0FFF +#define TCM825X_HDTCORE 0x10F0 +#define TCM825X_VDTCORE 0x100F +#define TCM825X_CONT 0x11FF +#define TCM825X_BRIGHT 0x12FF +#define TCM825X_VHUE 0x137F +#define TCM825X_UHUE 0x147F +#define TCM825X_VGAIN 0x153F +#define TCM825X_UGAIN 0x163F +#define TCM825X_UVCORE 0x170F +#define TCM825X_SATU 0x187F +#define TCM825X_MHMODE 0x1980 +#define TCM825X_MHLPFSEL 0x1940 +#define TCM825X_YMODE 0x1930 +#define TCM825X_MIXHG 0x1907 +#define TCM825X_LENS 0x1A3F +#define TCM825X_AGLIM 0x1BE0 +#define TCM825X_LENSRPOL 0x1B10 +#define TCM825X_LENSRGAIN 0x1B0F +#define TCM825X_ES100S 0x1CFF +#define TCM825X_ES120S 0x1DFF +#define TCM825X_DMASK 0x1EC0 +#define TCM825X_CODESW 0x1E20 +#define TCM825X_CODESEL 0x1E10 +#define TCM825X_TESPIC 0x1E04 +#define TCM825X_PICSEL 0x1E03 +#define TCM825X_HNUM 0x20FF +#define TCM825X_VOUTPH 0x287F +#define TCM825X_ESROUT 0x327F +#define TCM825X_ESROUT2 0x33FF +#define TCM825X_AGOUT 0x34FF +#define TCM825X_DGOUT 0x353F +#define TCM825X_AGSLOW1 0x39C0 +#define TCM825X_FLLSMODE 0x3930 +#define TCM825X_FLLSLIM 0x390F +#define TCM825X_DETSEL 0x3AF0 +#define TCM825X_ACDETNC 0x3A0F +#define TCM825X_AGSLOW2 0x3BC0 +#define TCM825X_DG 0x3B3F +#define TCM825X_REJHLEV 0x3CFF +#define TCM825X_ALCLOCK 0x3D80 +#define TCM825X_FPSLNKSW 0x3D40 +#define TCM825X_ALCSPD 0x3D30 +#define TCM825X_REJH 0x3D03 +#define TCM825X_SHESRSW 0x3E80 +#define TCM825X_ESLIMSEL 0x3E40 +#define TCM825X_SHESRSPD 0x3E30 +#define TCM825X_ELSTEP 0x3E0C +#define TCM825X_ELSTART 0x3E03 +#define TCM825X_AGMIN 0x3FFF +#define TCM825X_PREGRG 0x423F +#define TCM825X_PREGBG 0x433F +#define TCM825X_PRERG 0x443F +#define TCM825X_PREBG 0x453F +#define TCM825X_MSKBR 0x477F +#define TCM825X_MSKGR 0x487F +#define TCM825X_MSKRB 0x497F +#define TCM825X_MSKGB 0x4A7F +#define TCM825X_MSKRG 0x4B7F +#define TCM825X_MSKBG 0x4C7F +#define TCM825X_HDTCSW 0x4D80 +#define TCM825X_VDTCSW 0x4D40 +#define TCM825X_DTCYL 0x4D3F +#define TCM825X_HDTPSW 0x4E80 +#define TCM825X_VDTPSW 0x4E40 +#define TCM825X_DTCGAIN 0x4E3F +#define TCM825X_DTLLIMSW 0x4F10 +#define TCM825X_DTLYLIM 0x4F0F +#define TCM825X_YLCUTLMSK 0x5080 +#define TCM825X_YLCUTL 0x503F +#define TCM825X_YLCUTHMSK 0x5180 +#define TCM825X_YLCUTH 0x513F +#define TCM825X_UVSKNC 0x527F +#define TCM825X_UVLJ 0x537F +#define TCM825X_WBGMIN 0x54FF +#define TCM825X_WBGMAX 0x55FF +#define TCM825X_WBSPDUP 0x5603 +#define TCM825X_ALLAREA 0x5820 +#define TCM825X_WBLOCK 0x5810 +#define TCM825X_WB2SP 0x580F +#define TCM825X_KIZUSW 0x5920 +#define TCM825X_PBRSW 0x5910 +#define TCM825X_ABCSW 0x5903 +#define TCM825X_PBDLV 0x5AFF +#define TCM825X_PBC1LV 0x5BFF + +#define TCM825X_NUM_REGS (TCM825X_ADDR(TCM825X_PBC1LV) + 1) + +#define TCM825X_BYTES_PER_PIXEL 2 + +#define TCM825X_REG_TERM 0xff /* terminating list entry for reg */ +#define TCM825X_VAL_TERM 0xff /* terminating list entry for val */ + +/* define a structure for tcm825x register initialization values */ +struct tcm825x_reg { + u8 val; + u16 reg; +}; + +enum image_size { subQCIF = 0, QQVGA, QCIF, QVGA, CIF, VGA }; +enum pixel_format { YUV422 = 0, RGB565 }; +#define NUM_IMAGE_SIZES 6 +#define NUM_PIXEL_FORMATS 2 + +#define TCM825X_XCLK_MIN 11900000 +#define TCM825X_XCLK_MAX 25000000 + +struct capture_size { + unsigned long width; + unsigned long height; +}; + +struct tcm825x_platform_data { + /* Is the sensor usable? Doesn't yet mean it's there, but you + * can try! */ + int (*is_okay)(void); + /* Set power state, zero is off, non-zero is on. */ + int (*power_set)(int power); + /* Default registers written after power-on or reset. */ + const struct tcm825x_reg *(*default_regs)(void); + int (*needs_reset)(struct v4l2_int_device *s, void *buf, + struct v4l2_pix_format *fmt); + int (*ifparm)(struct v4l2_ifparm *p); + int (*is_upside_down)(void); +}; + +/* Array of image sizes supported by TCM825X. These must be ordered from + * smallest image size to largest. + */ +static const struct capture_size tcm825x_sizes[] = { + { 128, 96 }, /* subQCIF */ + { 160, 120 }, /* QQVGA */ + { 176, 144 }, /* QCIF */ + { 320, 240 }, /* QVGA */ + { 352, 288 }, /* CIF */ + { 640, 480 }, /* VGA */ +}; + +#endif /* ifndef TCM825X_H */ diff --git a/drivers/media/video/tda7432.c b/drivers/media/i2c/tda7432.c similarity index 100% rename from drivers/media/video/tda7432.c rename to drivers/media/i2c/tda7432.c diff --git a/drivers/media/video/tda9840.c b/drivers/media/i2c/tda9840.c similarity index 100% rename from drivers/media/video/tda9840.c rename to drivers/media/i2c/tda9840.c diff --git a/drivers/media/i2c/tea6415c.c b/drivers/media/i2c/tea6415c.c new file mode 100644 index 000000000000..3d5b06a5c308 --- /dev/null +++ b/drivers/media/i2c/tea6415c.c @@ -0,0 +1,187 @@ + /* + tea6415c - i2c-driver for the tea6415c by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold + Copyright (C) 2008 Hans Verkuil + + The tea6415c is a bus controlled video-matrix-switch + with 8 inputs and 6 outputs. + It is cascadable, i.e. it can be found at the addresses + 0x86 and 0x06 on the i2c-bus. + + For detailed informations download the specifications directly + from SGS Thomson at http://www.st.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License vs 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., 675 Mvss Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include +#include +#include "tea6415c.h" + +MODULE_AUTHOR("Michael Hunold "); +MODULE_DESCRIPTION("tea6415c driver"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* makes a connection between the input-pin 'i' and the output-pin 'o' */ +static int tea6415c_s_routing(struct v4l2_subdev *sd, + u32 i, u32 o, u32 config) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 byte = 0; + int ret; + + v4l2_dbg(1, debug, sd, "i=%d, o=%d\n", i, o); + + /* check if the pins are valid */ + if (0 == ((1 == i || 3 == i || 5 == i || 6 == i || 8 == i || 10 == i || 20 == i || 11 == i) + && (18 == o || 17 == o || 16 == o || 15 == o || 14 == o || 13 == o))) + return -EINVAL; + + /* to understand this, have a look at the tea6415c-specs (p.5) */ + switch (o) { + case 18: + byte = 0x00; + break; + case 14: + byte = 0x20; + break; + case 16: + byte = 0x10; + break; + case 17: + byte = 0x08; + break; + case 15: + byte = 0x18; + break; + case 13: + byte = 0x28; + break; + } + + switch (i) { + case 5: + byte |= 0x00; + break; + case 8: + byte |= 0x04; + break; + case 3: + byte |= 0x02; + break; + case 20: + byte |= 0x06; + break; + case 6: + byte |= 0x01; + break; + case 10: + byte |= 0x05; + break; + case 1: + byte |= 0x03; + break; + case 11: + byte |= 0x07; + break; + } + + ret = i2c_smbus_write_byte(client, byte); + if (ret) { + v4l2_dbg(1, debug, sd, + "i2c_smbus_write_byte() failed, ret:%d\n", ret); + return -EIO; + } + return ret; +} + +static int tea6415c_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_TEA6415C, 0); +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops tea6415c_core_ops = { + .g_chip_ident = tea6415c_g_chip_ident, +}; + +static const struct v4l2_subdev_video_ops tea6415c_video_ops = { + .s_routing = tea6415c_s_routing, +}; + +static const struct v4l2_subdev_ops tea6415c_ops = { + .core = &tea6415c_core_ops, + .video = &tea6415c_video_ops, +}; + +static int tea6415c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct v4l2_subdev *sd; + + /* let's see whether this adapter can support what we need */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + sd = kzalloc(sizeof(struct v4l2_subdev), GFP_KERNEL); + if (sd == NULL) + return -ENOMEM; + v4l2_i2c_subdev_init(sd, client, &tea6415c_ops); + return 0; +} + +static int tea6415c_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + kfree(sd); + return 0; +} + +static const struct i2c_device_id tea6415c_id[] = { + { "tea6415c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tea6415c_id); + +static struct i2c_driver tea6415c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "tea6415c", + }, + .probe = tea6415c_probe, + .remove = tea6415c_remove, + .id_table = tea6415c_id, +}; + +module_i2c_driver(tea6415c_driver); diff --git a/drivers/media/video/tea6415c.h b/drivers/media/i2c/tea6415c.h similarity index 100% rename from drivers/media/video/tea6415c.h rename to drivers/media/i2c/tea6415c.h diff --git a/drivers/media/video/tea6420.c b/drivers/media/i2c/tea6420.c similarity index 100% rename from drivers/media/video/tea6420.c rename to drivers/media/i2c/tea6420.c diff --git a/drivers/media/video/tea6420.h b/drivers/media/i2c/tea6420.h similarity index 100% rename from drivers/media/video/tea6420.h rename to drivers/media/i2c/tea6420.h diff --git a/drivers/media/video/ths7303.c b/drivers/media/i2c/ths7303.c similarity index 100% rename from drivers/media/video/ths7303.c rename to drivers/media/i2c/ths7303.c diff --git a/drivers/media/video/tlv320aic23b.c b/drivers/media/i2c/tlv320aic23b.c similarity index 100% rename from drivers/media/video/tlv320aic23b.c rename to drivers/media/i2c/tlv320aic23b.c diff --git a/drivers/media/i2c/tvaudio.c b/drivers/media/i2c/tvaudio.c new file mode 100644 index 000000000000..3b24d3fc1866 --- /dev/null +++ b/drivers/media/i2c/tvaudio.c @@ -0,0 +1,2131 @@ +/* + * Driver for simple i2c audio chips. + * + * Copyright (c) 2000 Gerd Knorr + * based on code by: + * Eric Sandeen (eric_sandeen@bigfoot.com) + * Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Greg Alexander (galexand@acm.org) + * + * For the TDA9875 part: + * Copyright (c) 2000 Guillaume Delvit based on Gerd Knorr source + * and Eric Sandeen + * + * Copyright(c) 2005-2008 Mauro Carvalho Chehab + * - Some cleanups, code fixes, etc + * - Convert it to V4L2 API + * + * This code is placed under the terms of the GNU General Public License + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* ---------------------------------------------------------------------- */ +/* insmod args */ + +static int debug; /* insmod parameter */ +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("device driver for various i2c TV sound decoder / audiomux chips"); +MODULE_AUTHOR("Eric Sandeen, Steve VanDeBogart, Greg Alexander, Gerd Knorr"); +MODULE_LICENSE("GPL"); + +#define UNSET (-1U) + +/* ---------------------------------------------------------------------- */ +/* our structs */ + +#define MAXREGS 256 + +struct CHIPSTATE; +typedef int (*getvalue)(int); +typedef int (*checkit)(struct CHIPSTATE*); +typedef int (*initialize)(struct CHIPSTATE*); +typedef int (*getrxsubchans)(struct CHIPSTATE *); +typedef void (*setaudmode)(struct CHIPSTATE*, int mode); + +/* i2c command */ +typedef struct AUDIOCMD { + int count; /* # of bytes to send */ + unsigned char bytes[MAXREGS+1]; /* addr, data, data, ... */ +} audiocmd; + +/* chip description */ +struct CHIPDESC { + char *name; /* chip name */ + int addr_lo, addr_hi; /* i2c address range */ + int registers; /* # of registers */ + + int *insmodopt; + checkit checkit; + initialize initialize; + int flags; +#define CHIP_HAS_VOLUME 1 +#define CHIP_HAS_BASSTREBLE 2 +#define CHIP_HAS_INPUTSEL 4 +#define CHIP_NEED_CHECKMODE 8 + + /* various i2c command sequences */ + audiocmd init; + + /* which register has which value */ + int leftreg,rightreg,treblereg,bassreg; + + /* initialize with (defaults to 65535/65535/32768/32768 */ + int leftinit,rightinit,trebleinit,bassinit; + + /* functions to convert the values (v4l -> chip) */ + getvalue volfunc,treblefunc,bassfunc; + + /* get/set mode */ + getrxsubchans getrxsubchans; + setaudmode setaudmode; + + /* input switch register + values for v4l inputs */ + int inputreg; + int inputmap[4]; + int inputmute; + int inputmask; +}; + +/* current state of the chip */ +struct CHIPSTATE { + struct v4l2_subdev sd; + + /* chip-specific description - should point to + an entry at CHIPDESC table */ + struct CHIPDESC *desc; + + /* shadow register set */ + audiocmd shadow; + + /* current settings */ + __u16 left, right, treble, bass, muted; + int prevmode; + int radio; + int input; + + /* thread */ + struct task_struct *thread; + struct timer_list wt; + int audmode; +}; + +static inline struct CHIPSTATE *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct CHIPSTATE, sd); +} + + +/* ---------------------------------------------------------------------- */ +/* i2c I/O functions */ + +static int chip_write(struct CHIPSTATE *chip, int subaddr, int val) +{ + struct v4l2_subdev *sd = &chip->sd; + struct i2c_client *c = v4l2_get_subdevdata(sd); + unsigned char buffer[2]; + + if (subaddr < 0) { + v4l2_dbg(1, debug, sd, "chip_write: 0x%x\n", val); + chip->shadow.bytes[1] = val; + buffer[0] = val; + if (1 != i2c_master_send(c, buffer, 1)) { + v4l2_warn(sd, "I/O error (write 0x%x)\n", val); + return -1; + } + } else { + if (subaddr + 1 >= ARRAY_SIZE(chip->shadow.bytes)) { + v4l2_info(sd, + "Tried to access a non-existent register: %d\n", + subaddr); + return -EINVAL; + } + + v4l2_dbg(1, debug, sd, "chip_write: reg%d=0x%x\n", + subaddr, val); + chip->shadow.bytes[subaddr+1] = val; + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(c, buffer, 2)) { + v4l2_warn(sd, "I/O error (write reg%d=0x%x)\n", + subaddr, val); + return -1; + } + } + return 0; +} + +static int chip_write_masked(struct CHIPSTATE *chip, + int subaddr, int val, int mask) +{ + struct v4l2_subdev *sd = &chip->sd; + + if (mask != 0) { + if (subaddr < 0) { + val = (chip->shadow.bytes[1] & ~mask) | (val & mask); + } else { + if (subaddr + 1 >= ARRAY_SIZE(chip->shadow.bytes)) { + v4l2_info(sd, + "Tried to access a non-existent register: %d\n", + subaddr); + return -EINVAL; + } + + val = (chip->shadow.bytes[subaddr+1] & ~mask) | (val & mask); + } + } + return chip_write(chip, subaddr, val); +} + +static int chip_read(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + struct i2c_client *c = v4l2_get_subdevdata(sd); + unsigned char buffer; + + if (1 != i2c_master_recv(c, &buffer, 1)) { + v4l2_warn(sd, "I/O error (read)\n"); + return -1; + } + v4l2_dbg(1, debug, sd, "chip_read: 0x%x\n", buffer); + return buffer; +} + +static int chip_read2(struct CHIPSTATE *chip, int subaddr) +{ + struct v4l2_subdev *sd = &chip->sd; + struct i2c_client *c = v4l2_get_subdevdata(sd); + unsigned char write[1]; + unsigned char read[1]; + struct i2c_msg msgs[2] = { + { + .addr = c->addr, + .len = 1, + .buf = write + }, + { + .addr = c->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = read + } + }; + + write[0] = subaddr; + + if (2 != i2c_transfer(c->adapter, msgs, 2)) { + v4l2_warn(sd, "I/O error (read2)\n"); + return -1; + } + v4l2_dbg(1, debug, sd, "chip_read2: reg%d=0x%x\n", + subaddr, read[0]); + return read[0]; +} + +static int chip_cmd(struct CHIPSTATE *chip, char *name, audiocmd *cmd) +{ + struct v4l2_subdev *sd = &chip->sd; + struct i2c_client *c = v4l2_get_subdevdata(sd); + int i; + + if (0 == cmd->count) + return 0; + + if (cmd->count + cmd->bytes[0] - 1 >= ARRAY_SIZE(chip->shadow.bytes)) { + v4l2_info(sd, + "Tried to access a non-existent register range: %d to %d\n", + cmd->bytes[0] + 1, cmd->bytes[0] + cmd->count - 1); + return -EINVAL; + } + + /* FIXME: it seems that the shadow bytes are wrong bellow !*/ + + /* update our shadow register set; print bytes if (debug > 0) */ + v4l2_dbg(1, debug, sd, "chip_cmd(%s): reg=%d, data:", + name, cmd->bytes[0]); + for (i = 1; i < cmd->count; i++) { + if (debug) + printk(KERN_CONT " 0x%x", cmd->bytes[i]); + chip->shadow.bytes[i+cmd->bytes[0]] = cmd->bytes[i]; + } + if (debug) + printk(KERN_CONT "\n"); + + /* send data to the chip */ + if (cmd->count != i2c_master_send(c, cmd->bytes, cmd->count)) { + v4l2_warn(sd, "I/O error (%s)\n", name); + return -1; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* kernel thread for doing i2c stuff asyncronly + * right now it is used only to check the audio mode (mono/stereo/whatever) + * some time after switching to another TV channel, then turn on stereo + * if available, ... + */ + +static void chip_thread_wake(unsigned long data) +{ + struct CHIPSTATE *chip = (struct CHIPSTATE*)data; + wake_up_process(chip->thread); +} + +static int chip_thread(void *data) +{ + struct CHIPSTATE *chip = data; + struct CHIPDESC *desc = chip->desc; + struct v4l2_subdev *sd = &chip->sd; + int mode, selected; + + v4l2_dbg(1, debug, sd, "thread started\n"); + set_freezable(); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (!kthread_should_stop()) + schedule(); + set_current_state(TASK_RUNNING); + try_to_freeze(); + if (kthread_should_stop()) + break; + v4l2_dbg(1, debug, sd, "thread wakeup\n"); + + /* don't do anything for radio */ + if (chip->radio) + continue; + + /* have a look what's going on */ + mode = desc->getrxsubchans(chip); + if (mode == chip->prevmode) + continue; + + /* chip detected a new audio mode - set it */ + v4l2_dbg(1, debug, sd, "thread checkmode\n"); + + chip->prevmode = mode; + + selected = V4L2_TUNER_MODE_MONO; + switch (chip->audmode) { + case V4L2_TUNER_MODE_MONO: + if (mode & V4L2_TUNER_SUB_LANG1) + selected = V4L2_TUNER_MODE_LANG1; + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1: + if (mode & V4L2_TUNER_SUB_LANG1) + selected = V4L2_TUNER_MODE_LANG1; + else if (mode & V4L2_TUNER_SUB_STEREO) + selected = V4L2_TUNER_MODE_STEREO; + break; + case V4L2_TUNER_MODE_LANG2: + if (mode & V4L2_TUNER_SUB_LANG2) + selected = V4L2_TUNER_MODE_LANG2; + else if (mode & V4L2_TUNER_SUB_STEREO) + selected = V4L2_TUNER_MODE_STEREO; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + if (mode & V4L2_TUNER_SUB_LANG2) + selected = V4L2_TUNER_MODE_LANG1_LANG2; + else if (mode & V4L2_TUNER_SUB_STEREO) + selected = V4L2_TUNER_MODE_STEREO; + } + desc->setaudmode(chip, selected); + + /* schedule next check */ + mod_timer(&chip->wt, jiffies+msecs_to_jiffies(2000)); + } + + v4l2_dbg(1, debug, sd, "thread exiting\n"); + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda9840 */ + +#define TDA9840_SW 0x00 +#define TDA9840_LVADJ 0x02 +#define TDA9840_STADJ 0x03 +#define TDA9840_TEST 0x04 + +#define TDA9840_MONO 0x10 +#define TDA9840_STEREO 0x2a +#define TDA9840_DUALA 0x12 +#define TDA9840_DUALB 0x1e +#define TDA9840_DUALAB 0x1a +#define TDA9840_DUALBA 0x16 +#define TDA9840_EXTERNAL 0x7a + +#define TDA9840_DS_DUAL 0x20 /* Dual sound identified */ +#define TDA9840_ST_STEREO 0x40 /* Stereo sound identified */ +#define TDA9840_PONRES 0x80 /* Power-on reset detected if = 1 */ + +#define TDA9840_TEST_INT1SN 0x1 /* Integration time 0.5s when set */ +#define TDA9840_TEST_INTFU 0x02 /* Disables integrator function */ + +static int tda9840_getrxsubchans(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int val, mode; + + val = chip_read(chip); + mode = V4L2_TUNER_SUB_MONO; + if (val & TDA9840_DS_DUAL) + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + if (val & TDA9840_ST_STEREO) + mode = V4L2_TUNER_SUB_STEREO; + + v4l2_dbg(1, debug, sd, + "tda9840_getrxsubchans(): raw chip read: %d, return: %d\n", + val, mode); + return mode; +} + +static void tda9840_setaudmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + int t = chip->shadow.bytes[TDA9840_SW + 1] & ~0x7e; + + switch (mode) { + case V4L2_TUNER_MODE_MONO: + t |= TDA9840_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + t |= TDA9840_STEREO; + break; + case V4L2_TUNER_MODE_LANG1: + t |= TDA9840_DUALA; + break; + case V4L2_TUNER_MODE_LANG2: + t |= TDA9840_DUALB; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + t |= TDA9840_DUALAB; + break; + default: + update = 0; + } + + if (update) + chip_write(chip, TDA9840_SW, t); +} + +static int tda9840_checkit(struct CHIPSTATE *chip) +{ + int rc; + rc = chip_read(chip); + /* lower 5 bits should be 0 */ + return ((rc & 0x1f) == 0) ? 1 : 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda985x */ + +/* subaddresses for TDA9855 */ +#define TDA9855_VR 0x00 /* Volume, right */ +#define TDA9855_VL 0x01 /* Volume, left */ +#define TDA9855_BA 0x02 /* Bass */ +#define TDA9855_TR 0x03 /* Treble */ +#define TDA9855_SW 0x04 /* Subwoofer - not connected on DTV2000 */ + +/* subaddresses for TDA9850 */ +#define TDA9850_C4 0x04 /* Control 1 for TDA9850 */ + +/* subaddesses for both chips */ +#define TDA985x_C5 0x05 /* Control 2 for TDA9850, Control 1 for TDA9855 */ +#define TDA985x_C6 0x06 /* Control 3 for TDA9850, Control 2 for TDA9855 */ +#define TDA985x_C7 0x07 /* Control 4 for TDA9850, Control 3 for TDA9855 */ +#define TDA985x_A1 0x08 /* Alignment 1 for both chips */ +#define TDA985x_A2 0x09 /* Alignment 2 for both chips */ +#define TDA985x_A3 0x0a /* Alignment 3 for both chips */ + +/* Masks for bits in TDA9855 subaddresses */ +/* 0x00 - VR in TDA9855 */ +/* 0x01 - VL in TDA9855 */ +/* lower 7 bits control gain from -71dB (0x28) to 16dB (0x7f) + * in 1dB steps - mute is 0x27 */ + + +/* 0x02 - BA in TDA9855 */ +/* lower 5 bits control bass gain from -12dB (0x06) to 16.5dB (0x19) + * in .5dB steps - 0 is 0x0E */ + + +/* 0x03 - TR in TDA9855 */ +/* 4 bits << 1 control treble gain from -12dB (0x3) to 12dB (0xb) + * in 3dB steps - 0 is 0x7 */ + +/* Masks for bits in both chips' subaddresses */ +/* 0x04 - SW in TDA9855, C4/Control 1 in TDA9850 */ +/* Unique to TDA9855: */ +/* 4 bits << 2 control subwoofer/surround gain from -14db (0x1) to 14db (0xf) + * in 3dB steps - mute is 0x0 */ + +/* Unique to TDA9850: */ +/* lower 4 bits control stereo noise threshold, over which stereo turns off + * set to values of 0x00 through 0x0f for Ster1 through Ster16 */ + + +/* 0x05 - C5 - Control 1 in TDA9855 , Control 2 in TDA9850*/ +/* Unique to TDA9855: */ +#define TDA9855_MUTE 1<<7 /* GMU, Mute at outputs */ +#define TDA9855_AVL 1<<6 /* AVL, Automatic Volume Level */ +#define TDA9855_LOUD 1<<5 /* Loudness, 1==off */ +#define TDA9855_SUR 1<<3 /* Surround / Subwoofer 1==.5(L-R) 0==.5(L+R) */ + /* Bits 0 to 3 select various combinations + * of line in and line out, only the + * interesting ones are defined */ +#define TDA9855_EXT 1<<2 /* Selects inputs LIR and LIL. Pins 41 & 12 */ +#define TDA9855_INT 0 /* Selects inputs LOR and LOL. (internal) */ + +/* Unique to TDA9850: */ +/* lower 4 bits contol SAP noise threshold, over which SAP turns off + * set to values of 0x00 through 0x0f for SAP1 through SAP16 */ + + +/* 0x06 - C6 - Control 2 in TDA9855, Control 3 in TDA9850 */ +/* Common to TDA9855 and TDA9850: */ +#define TDA985x_SAP 3<<6 /* Selects SAP output, mute if not received */ +#define TDA985x_MONOSAP 2<<6 /* Selects Mono on left, SAP on right */ +#define TDA985x_STEREO 1<<6 /* Selects Stereo ouput, mono if not received */ +#define TDA985x_MONO 0 /* Forces Mono output */ +#define TDA985x_LMU 1<<3 /* Mute (LOR/LOL for 9855, OUTL/OUTR for 9850) */ + +/* Unique to TDA9855: */ +#define TDA9855_TZCM 1<<5 /* If set, don't mute till zero crossing */ +#define TDA9855_VZCM 1<<4 /* If set, don't change volume till zero crossing*/ +#define TDA9855_LINEAR 0 /* Linear Stereo */ +#define TDA9855_PSEUDO 1 /* Pseudo Stereo */ +#define TDA9855_SPAT_30 2 /* Spatial Stereo, 30% anti-phase crosstalk */ +#define TDA9855_SPAT_50 3 /* Spatial Stereo, 52% anti-phase crosstalk */ +#define TDA9855_E_MONO 7 /* Forced mono - mono select elseware, so useless*/ + +/* 0x07 - C7 - Control 3 in TDA9855, Control 4 in TDA9850 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 4 bits control input gain from -3.5dB (0x0) to 4dB (0xF) + * in .5dB steps - 0dB is 0x7 */ + +/* 0x08, 0x09 - A1 and A2 (read/write) */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 5 bites are wideband and spectral expander alignment + * from 0x00 to 0x1f - nominal at 0x0f and 0x10 (read/write) */ +#define TDA985x_STP 1<<5 /* Stereo Pilot/detect (read-only) */ +#define TDA985x_SAPP 1<<6 /* SAP Pilot/detect (read-only) */ +#define TDA985x_STS 1<<7 /* Stereo trigger 1= <35mV 0= <30mV (write-only)*/ + +/* 0x0a - A3 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 3 bits control timing current for alignment: -30% (0x0), -20% (0x1), + * -10% (0x2), nominal (0x3), +10% (0x6), +20% (0x5), +30% (0x4) */ +#define TDA985x_ADJ 1<<7 /* Stereo adjust on/off (wideband and spectral */ + +static int tda9855_volume(int val) { return val/0x2e8+0x27; } +static int tda9855_bass(int val) { return val/0xccc+0x06; } +static int tda9855_treble(int val) { return (val/0x1c71+0x3)<<1; } + +static int tda985x_getrxsubchans(struct CHIPSTATE *chip) +{ + int mode, val; + + /* Add mono mode regardless of SAP and stereo */ + /* Allows forced mono */ + mode = V4L2_TUNER_SUB_MONO; + val = chip_read(chip); + if (val & TDA985x_STP) + mode = V4L2_TUNER_SUB_STEREO; + if (val & TDA985x_SAPP) + mode |= V4L2_TUNER_SUB_SAP; + return mode; +} + +static void tda985x_setaudmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + int c6 = chip->shadow.bytes[TDA985x_C6+1] & 0x3f; + + switch (mode) { + case V4L2_TUNER_MODE_MONO: + c6 |= TDA985x_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1: + c6 |= TDA985x_STEREO; + break; + case V4L2_TUNER_MODE_SAP: + c6 |= TDA985x_SAP; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + c6 |= TDA985x_MONOSAP; + break; + default: + update = 0; + } + if (update) + chip_write(chip,TDA985x_C6,c6); +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda9873h */ + +/* Subaddresses for TDA9873H */ + +#define TDA9873_SW 0x00 /* Switching */ +#define TDA9873_AD 0x01 /* Adjust */ +#define TDA9873_PT 0x02 /* Port */ + +/* Subaddress 0x00: Switching Data + * B7..B0: + * + * B1, B0: Input source selection + * 0, 0 internal + * 1, 0 external stereo + * 0, 1 external mono + */ +#define TDA9873_INP_MASK 3 +#define TDA9873_INTERNAL 0 +#define TDA9873_EXT_STEREO 2 +#define TDA9873_EXT_MONO 1 + +/* B3, B2: output signal select + * B4 : transmission mode + * 0, 0, 1 Mono + * 1, 0, 0 Stereo + * 1, 1, 1 Stereo (reversed channel) + * 0, 0, 0 Dual AB + * 0, 0, 1 Dual AA + * 0, 1, 0 Dual BB + * 0, 1, 1 Dual BA + */ + +#define TDA9873_TR_MASK (7 << 2) +#define TDA9873_TR_MONO 4 +#define TDA9873_TR_STEREO 1 << 4 +#define TDA9873_TR_REVERSE ((1 << 3) | (1 << 2)) +#define TDA9873_TR_DUALA 1 << 2 +#define TDA9873_TR_DUALB 1 << 3 +#define TDA9873_TR_DUALAB 0 + +/* output level controls + * B5: output level switch (0 = reduced gain, 1 = normal gain) + * B6: mute (1 = muted) + * B7: auto-mute (1 = auto-mute enabled) + */ + +#define TDA9873_GAIN_NORMAL 1 << 5 +#define TDA9873_MUTE 1 << 6 +#define TDA9873_AUTOMUTE 1 << 7 + +/* Subaddress 0x01: Adjust/standard */ + +/* Lower 4 bits (C3..C0) control stereo adjustment on R channel (-0.6 - +0.7 dB) + * Recommended value is +0 dB + */ + +#define TDA9873_STEREO_ADJ 0x06 /* 0dB gain */ + +/* Bits C6..C4 control FM stantard + * C6, C5, C4 + * 0, 0, 0 B/G (PAL FM) + * 0, 0, 1 M + * 0, 1, 0 D/K(1) + * 0, 1, 1 D/K(2) + * 1, 0, 0 D/K(3) + * 1, 0, 1 I + */ +#define TDA9873_BG 0 +#define TDA9873_M 1 +#define TDA9873_DK1 2 +#define TDA9873_DK2 3 +#define TDA9873_DK3 4 +#define TDA9873_I 5 + +/* C7 controls identification response time (1=fast/0=normal) + */ +#define TDA9873_IDR_NORM 0 +#define TDA9873_IDR_FAST 1 << 7 + + +/* Subaddress 0x02: Port data */ + +/* E1, E0 free programmable ports P1/P2 + 0, 0 both ports low + 0, 1 P1 high + 1, 0 P2 high + 1, 1 both ports high +*/ + +#define TDA9873_PORTS 3 + +/* E2: test port */ +#define TDA9873_TST_PORT 1 << 2 + +/* E5..E3 control mono output channel (together with transmission mode bit B4) + * + * E5 E4 E3 B4 OUTM + * 0 0 0 0 mono + * 0 0 1 0 DUAL B + * 0 1 0 1 mono (from stereo decoder) + */ +#define TDA9873_MOUT_MONO 0 +#define TDA9873_MOUT_FMONO 0 +#define TDA9873_MOUT_DUALA 0 +#define TDA9873_MOUT_DUALB 1 << 3 +#define TDA9873_MOUT_ST 1 << 4 +#define TDA9873_MOUT_EXTM ((1 << 4) | (1 << 3)) +#define TDA9873_MOUT_EXTL 1 << 5 +#define TDA9873_MOUT_EXTR ((1 << 5) | (1 << 3)) +#define TDA9873_MOUT_EXTLR ((1 << 5) | (1 << 4)) +#define TDA9873_MOUT_MUTE ((1 << 5) | (1 << 4) | (1 << 3)) + +/* Status bits: (chip read) */ +#define TDA9873_PONR 0 /* Power-on reset detected if = 1 */ +#define TDA9873_STEREO 2 /* Stereo sound is identified */ +#define TDA9873_DUAL 4 /* Dual sound is identified */ + +static int tda9873_getrxsubchans(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int val,mode; + + val = chip_read(chip); + mode = V4L2_TUNER_SUB_MONO; + if (val & TDA9873_STEREO) + mode = V4L2_TUNER_SUB_STEREO; + if (val & TDA9873_DUAL) + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + v4l2_dbg(1, debug, sd, + "tda9873_getrxsubchans(): raw chip read: %d, return: %d\n", + val, mode); + return mode; +} + +static void tda9873_setaudmode(struct CHIPSTATE *chip, int mode) +{ + struct v4l2_subdev *sd = &chip->sd; + int sw_data = chip->shadow.bytes[TDA9873_SW+1] & ~ TDA9873_TR_MASK; + /* int adj_data = chip->shadow.bytes[TDA9873_AD+1] ; */ + + if ((sw_data & TDA9873_INP_MASK) != TDA9873_INTERNAL) { + v4l2_dbg(1, debug, sd, + "tda9873_setaudmode(): external input\n"); + return; + } + + v4l2_dbg(1, debug, sd, + "tda9873_setaudmode(): chip->shadow.bytes[%d] = %d\n", + TDA9873_SW+1, chip->shadow.bytes[TDA9873_SW+1]); + v4l2_dbg(1, debug, sd, "tda9873_setaudmode(): sw_data = %d\n", + sw_data); + + switch (mode) { + case V4L2_TUNER_MODE_MONO: + sw_data |= TDA9873_TR_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + sw_data |= TDA9873_TR_STEREO; + break; + case V4L2_TUNER_MODE_LANG1: + sw_data |= TDA9873_TR_DUALA; + break; + case V4L2_TUNER_MODE_LANG2: + sw_data |= TDA9873_TR_DUALB; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + sw_data |= TDA9873_TR_DUALAB; + break; + default: + return; + } + + chip_write(chip, TDA9873_SW, sw_data); + v4l2_dbg(1, debug, sd, + "tda9873_setaudmode(): req. mode %d; chip_write: %d\n", + mode, sw_data); +} + +static int tda9873_checkit(struct CHIPSTATE *chip) +{ + int rc; + + if (-1 == (rc = chip_read2(chip,254))) + return 0; + return (rc & ~0x1f) == 0x80; +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip description - defines+functions for tda9874h and tda9874a */ +/* Dariusz Kowalewski */ + +/* Subaddresses for TDA9874H and TDA9874A (slave rx) */ +#define TDA9874A_AGCGR 0x00 /* AGC gain */ +#define TDA9874A_GCONR 0x01 /* general config */ +#define TDA9874A_MSR 0x02 /* monitor select */ +#define TDA9874A_C1FRA 0x03 /* carrier 1 freq. */ +#define TDA9874A_C1FRB 0x04 /* carrier 1 freq. */ +#define TDA9874A_C1FRC 0x05 /* carrier 1 freq. */ +#define TDA9874A_C2FRA 0x06 /* carrier 2 freq. */ +#define TDA9874A_C2FRB 0x07 /* carrier 2 freq. */ +#define TDA9874A_C2FRC 0x08 /* carrier 2 freq. */ +#define TDA9874A_DCR 0x09 /* demodulator config */ +#define TDA9874A_FMER 0x0a /* FM de-emphasis */ +#define TDA9874A_FMMR 0x0b /* FM dematrix */ +#define TDA9874A_C1OLAR 0x0c /* ch.1 output level adj. */ +#define TDA9874A_C2OLAR 0x0d /* ch.2 output level adj. */ +#define TDA9874A_NCONR 0x0e /* NICAM config */ +#define TDA9874A_NOLAR 0x0f /* NICAM output level adj. */ +#define TDA9874A_NLELR 0x10 /* NICAM lower error limit */ +#define TDA9874A_NUELR 0x11 /* NICAM upper error limit */ +#define TDA9874A_AMCONR 0x12 /* audio mute control */ +#define TDA9874A_SDACOSR 0x13 /* stereo DAC output select */ +#define TDA9874A_AOSR 0x14 /* analog output select */ +#define TDA9874A_DAICONR 0x15 /* digital audio interface config */ +#define TDA9874A_I2SOSR 0x16 /* I2S-bus output select */ +#define TDA9874A_I2SOLAR 0x17 /* I2S-bus output level adj. */ +#define TDA9874A_MDACOSR 0x18 /* mono DAC output select (tda9874a) */ +#define TDA9874A_ESP 0xFF /* easy standard progr. (tda9874a) */ + +/* Subaddresses for TDA9874H and TDA9874A (slave tx) */ +#define TDA9874A_DSR 0x00 /* device status */ +#define TDA9874A_NSR 0x01 /* NICAM status */ +#define TDA9874A_NECR 0x02 /* NICAM error count */ +#define TDA9874A_DR1 0x03 /* add. data LSB */ +#define TDA9874A_DR2 0x04 /* add. data MSB */ +#define TDA9874A_LLRA 0x05 /* monitor level read-out LSB */ +#define TDA9874A_LLRB 0x06 /* monitor level read-out MSB */ +#define TDA9874A_SIFLR 0x07 /* SIF level */ +#define TDA9874A_TR2 252 /* test reg. 2 */ +#define TDA9874A_TR1 253 /* test reg. 1 */ +#define TDA9874A_DIC 254 /* device id. code */ +#define TDA9874A_SIC 255 /* software id. code */ + + +static int tda9874a_mode = 1; /* 0: A2, 1: NICAM */ +static int tda9874a_GCONR = 0xc0; /* default config. input pin: SIFSEL=0 */ +static int tda9874a_NCONR = 0x01; /* default NICAM config.: AMSEL=0,AMUTE=1 */ +static int tda9874a_ESP = 0x07; /* default standard: NICAM D/K */ +static int tda9874a_dic = -1; /* device id. code */ + +/* insmod options for tda9874a */ +static unsigned int tda9874a_SIF = UNSET; +static unsigned int tda9874a_AMSEL = UNSET; +static unsigned int tda9874a_STD = UNSET; +module_param(tda9874a_SIF, int, 0444); +module_param(tda9874a_AMSEL, int, 0444); +module_param(tda9874a_STD, int, 0444); + +/* + * initialization table for tda9874 decoder: + * - carrier 1 freq. registers (3 bytes) + * - carrier 2 freq. registers (3 bytes) + * - demudulator config register + * - FM de-emphasis register (slow identification mode) + * Note: frequency registers must be written in single i2c transfer. + */ +static struct tda9874a_MODES { + char *name; + audiocmd cmd; +} tda9874a_modelist[9] = { + { "A2, B/G", /* default */ + { 9, { TDA9874A_C1FRA, 0x72,0x95,0x55, 0x77,0xA0,0x00, 0x00,0x00 }} }, + { "A2, M (Korea)", + { 9, { TDA9874A_C1FRA, 0x5D,0xC0,0x00, 0x62,0x6A,0xAA, 0x20,0x22 }} }, + { "A2, D/K (1)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x82,0x60,0x00, 0x00,0x00 }} }, + { "A2, D/K (2)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x8C,0x75,0x55, 0x00,0x00 }} }, + { "A2, D/K (3)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x77,0xA0,0x00, 0x00,0x00 }} }, + { "NICAM, I", + { 9, { TDA9874A_C1FRA, 0x7D,0x00,0x00, 0x88,0x8A,0xAA, 0x08,0x33 }} }, + { "NICAM, B/G", + { 9, { TDA9874A_C1FRA, 0x72,0x95,0x55, 0x79,0xEA,0xAA, 0x08,0x33 }} }, + { "NICAM, D/K", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x79,0xEA,0xAA, 0x08,0x33 }} }, + { "NICAM, L", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x79,0xEA,0xAA, 0x09,0x33 }} } +}; + +static int tda9874a_setup(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + + chip_write(chip, TDA9874A_AGCGR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_GCONR, tda9874a_GCONR); + chip_write(chip, TDA9874A_MSR, (tda9874a_mode) ? 0x03:0x02); + if(tda9874a_dic == 0x11) { + chip_write(chip, TDA9874A_FMMR, 0x80); + } else { /* dic == 0x07 */ + chip_cmd(chip,"tda9874_modelist",&tda9874a_modelist[tda9874a_STD].cmd); + chip_write(chip, TDA9874A_FMMR, 0x00); + } + chip_write(chip, TDA9874A_C1OLAR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_C2OLAR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_NCONR, tda9874a_NCONR); + chip_write(chip, TDA9874A_NOLAR, 0x00); /* 0 dB */ + /* Note: If signal quality is poor you may want to change NICAM */ + /* error limit registers (NLELR and NUELR) to some greater values. */ + /* Then the sound would remain stereo, but won't be so clear. */ + chip_write(chip, TDA9874A_NLELR, 0x14); /* default */ + chip_write(chip, TDA9874A_NUELR, 0x50); /* default */ + + if(tda9874a_dic == 0x11) { + chip_write(chip, TDA9874A_AMCONR, 0xf9); + chip_write(chip, TDA9874A_SDACOSR, (tda9874a_mode) ? 0x81:0x80); + chip_write(chip, TDA9874A_AOSR, 0x80); + chip_write(chip, TDA9874A_MDACOSR, (tda9874a_mode) ? 0x82:0x80); + chip_write(chip, TDA9874A_ESP, tda9874a_ESP); + } else { /* dic == 0x07 */ + chip_write(chip, TDA9874A_AMCONR, 0xfb); + chip_write(chip, TDA9874A_SDACOSR, (tda9874a_mode) ? 0x81:0x80); + chip_write(chip, TDA9874A_AOSR, 0x00); /* or 0x10 */ + } + v4l2_dbg(1, debug, sd, "tda9874a_setup(): %s [0x%02X].\n", + tda9874a_modelist[tda9874a_STD].name,tda9874a_STD); + return 1; +} + +static int tda9874a_getrxsubchans(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int dsr,nsr,mode; + int necr; /* just for debugging */ + + mode = V4L2_TUNER_SUB_MONO; + + if(-1 == (dsr = chip_read2(chip,TDA9874A_DSR))) + return mode; + if(-1 == (nsr = chip_read2(chip,TDA9874A_NSR))) + return mode; + if(-1 == (necr = chip_read2(chip,TDA9874A_NECR))) + return mode; + + /* need to store dsr/nsr somewhere */ + chip->shadow.bytes[MAXREGS-2] = dsr; + chip->shadow.bytes[MAXREGS-1] = nsr; + + if(tda9874a_mode) { + /* Note: DSR.RSSF and DSR.AMSTAT bits are also checked. + * If NICAM auto-muting is enabled, DSR.AMSTAT=1 indicates + * that sound has (temporarily) switched from NICAM to + * mono FM (or AM) on 1st sound carrier due to high NICAM bit + * error count. So in fact there is no stereo in this case :-( + * But changing the mode to V4L2_TUNER_MODE_MONO would switch + * external 4052 multiplexer in audio_hook(). + */ + if(nsr & 0x02) /* NSR.S/MB=1 */ + mode = V4L2_TUNER_SUB_STEREO; + if(nsr & 0x01) /* NSR.D/SB=1 */ + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + } else { + if(dsr & 0x02) /* DSR.IDSTE=1 */ + mode = V4L2_TUNER_SUB_STEREO; + if(dsr & 0x04) /* DSR.IDDUA=1 */ + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + } + + v4l2_dbg(1, debug, sd, + "tda9874a_getrxsubchans(): DSR=0x%X, NSR=0x%X, NECR=0x%X, return: %d.\n", + dsr, nsr, necr, mode); + return mode; +} + +static void tda9874a_setaudmode(struct CHIPSTATE *chip, int mode) +{ + struct v4l2_subdev *sd = &chip->sd; + + /* Disable/enable NICAM auto-muting (based on DSR.RSSF status bit). */ + /* If auto-muting is disabled, we can hear a signal of degrading quality. */ + if (tda9874a_mode) { + if(chip->shadow.bytes[MAXREGS-2] & 0x20) /* DSR.RSSF=1 */ + tda9874a_NCONR &= 0xfe; /* enable */ + else + tda9874a_NCONR |= 0x01; /* disable */ + chip_write(chip, TDA9874A_NCONR, tda9874a_NCONR); + } + + /* Note: TDA9874A supports automatic FM dematrixing (FMMR register) + * and has auto-select function for audio output (AOSR register). + * Old TDA9874H doesn't support these features. + * TDA9874A also has additional mono output pin (OUTM), which + * on same (all?) tv-cards is not used, anyway (as well as MONOIN). + */ + if(tda9874a_dic == 0x11) { + int aosr = 0x80; + int mdacosr = (tda9874a_mode) ? 0x82:0x80; + + switch(mode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_STEREO: + break; + case V4L2_TUNER_MODE_LANG1: + aosr = 0x80; /* auto-select, dual A/A */ + mdacosr = (tda9874a_mode) ? 0x82:0x80; + break; + case V4L2_TUNER_MODE_LANG2: + aosr = 0xa0; /* auto-select, dual B/B */ + mdacosr = (tda9874a_mode) ? 0x83:0x81; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + aosr = 0x00; /* always route L to L and R to R */ + mdacosr = (tda9874a_mode) ? 0x82:0x80; + break; + default: + return; + } + chip_write(chip, TDA9874A_AOSR, aosr); + chip_write(chip, TDA9874A_MDACOSR, mdacosr); + + v4l2_dbg(1, debug, sd, + "tda9874a_setaudmode(): req. mode %d; AOSR=0x%X, MDACOSR=0x%X.\n", + mode, aosr, mdacosr); + + } else { /* dic == 0x07 */ + int fmmr,aosr; + + switch(mode) { + case V4L2_TUNER_MODE_MONO: + fmmr = 0x00; /* mono */ + aosr = 0x10; /* A/A */ + break; + case V4L2_TUNER_MODE_STEREO: + if(tda9874a_mode) { + fmmr = 0x00; + aosr = 0x00; /* handled by NICAM auto-mute */ + } else { + fmmr = (tda9874a_ESP == 1) ? 0x05 : 0x04; /* stereo */ + aosr = 0x00; + } + break; + case V4L2_TUNER_MODE_LANG1: + fmmr = 0x02; /* dual */ + aosr = 0x10; /* dual A/A */ + break; + case V4L2_TUNER_MODE_LANG2: + fmmr = 0x02; /* dual */ + aosr = 0x20; /* dual B/B */ + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + fmmr = 0x02; /* dual */ + aosr = 0x00; /* dual A/B */ + break; + default: + return; + } + chip_write(chip, TDA9874A_FMMR, fmmr); + chip_write(chip, TDA9874A_AOSR, aosr); + + v4l2_dbg(1, debug, sd, + "tda9874a_setaudmode(): req. mode %d; FMMR=0x%X, AOSR=0x%X.\n", + mode, fmmr, aosr); + } +} + +static int tda9874a_checkit(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int dic,sic; /* device id. and software id. codes */ + + if(-1 == (dic = chip_read2(chip,TDA9874A_DIC))) + return 0; + if(-1 == (sic = chip_read2(chip,TDA9874A_SIC))) + return 0; + + v4l2_dbg(1, debug, sd, "tda9874a_checkit(): DIC=0x%X, SIC=0x%X.\n", dic, sic); + + if((dic == 0x11)||(dic == 0x07)) { + v4l2_info(sd, "found tda9874%s.\n", (dic == 0x11) ? "a" : "h"); + tda9874a_dic = dic; /* remember device id. */ + return 1; + } + return 0; /* not found */ +} + +static int tda9874a_initialize(struct CHIPSTATE *chip) +{ + if (tda9874a_SIF > 2) + tda9874a_SIF = 1; + if (tda9874a_STD >= ARRAY_SIZE(tda9874a_modelist)) + tda9874a_STD = 0; + if(tda9874a_AMSEL > 1) + tda9874a_AMSEL = 0; + + if(tda9874a_SIF == 1) + tda9874a_GCONR = 0xc0; /* sound IF input 1 */ + else + tda9874a_GCONR = 0xc1; /* sound IF input 2 */ + + tda9874a_ESP = tda9874a_STD; + tda9874a_mode = (tda9874a_STD < 5) ? 0 : 1; + + if(tda9874a_AMSEL == 0) + tda9874a_NCONR = 0x01; /* auto-mute: analog mono input */ + else + tda9874a_NCONR = 0x05; /* auto-mute: 1st carrier FM or AM */ + + tda9874a_setup(chip); + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip description - defines+functions for tda9875 */ +/* The TDA9875 is made by Philips Semiconductor + * http://www.semiconductors.philips.com + * TDA9875: I2C-bus controlled DSP audio processor, FM demodulator + * + */ + +/* subaddresses for TDA9875 */ +#define TDA9875_MUT 0x12 /*General mute (value --> 0b11001100*/ +#define TDA9875_CFG 0x01 /* Config register (value --> 0b00000000 */ +#define TDA9875_DACOS 0x13 /*DAC i/o select (ADC) 0b0000100*/ +#define TDA9875_LOSR 0x16 /*Line output select regirter 0b0100 0001*/ + +#define TDA9875_CH1V 0x0c /*Channel 1 volume (mute)*/ +#define TDA9875_CH2V 0x0d /*Channel 2 volume (mute)*/ +#define TDA9875_SC1 0x14 /*SCART 1 in (mono)*/ +#define TDA9875_SC2 0x15 /*SCART 2 in (mono)*/ + +#define TDA9875_ADCIS 0x17 /*ADC input select (mono) 0b0110 000*/ +#define TDA9875_AER 0x19 /*Audio effect (AVL+Pseudo) 0b0000 0110*/ +#define TDA9875_MCS 0x18 /*Main channel select (DAC) 0b0000100*/ +#define TDA9875_MVL 0x1a /* Main volume gauche */ +#define TDA9875_MVR 0x1b /* Main volume droite */ +#define TDA9875_MBA 0x1d /* Main Basse */ +#define TDA9875_MTR 0x1e /* Main treble */ +#define TDA9875_ACS 0x1f /* Auxiliary channel select (FM) 0b0000000*/ +#define TDA9875_AVL 0x20 /* Auxiliary volume gauche */ +#define TDA9875_AVR 0x21 /* Auxiliary volume droite */ +#define TDA9875_ABA 0x22 /* Auxiliary Basse */ +#define TDA9875_ATR 0x23 /* Auxiliary treble */ + +#define TDA9875_MSR 0x02 /* Monitor select register */ +#define TDA9875_C1MSB 0x03 /* Carrier 1 (FM) frequency register MSB */ +#define TDA9875_C1MIB 0x04 /* Carrier 1 (FM) frequency register (16-8]b */ +#define TDA9875_C1LSB 0x05 /* Carrier 1 (FM) frequency register LSB */ +#define TDA9875_C2MSB 0x06 /* Carrier 2 (nicam) frequency register MSB */ +#define TDA9875_C2MIB 0x07 /* Carrier 2 (nicam) frequency register (16-8]b */ +#define TDA9875_C2LSB 0x08 /* Carrier 2 (nicam) frequency register LSB */ +#define TDA9875_DCR 0x09 /* Demodulateur configuration regirter*/ +#define TDA9875_DEEM 0x0a /* FM de-emphasis regirter*/ +#define TDA9875_FMAT 0x0b /* FM Matrix regirter*/ + +/* values */ +#define TDA9875_MUTE_ON 0xff /* general mute */ +#define TDA9875_MUTE_OFF 0xcc /* general no mute */ + +static int tda9875_initialize(struct CHIPSTATE *chip) +{ + chip_write(chip, TDA9875_CFG, 0xd0); /*reg de config 0 (reset)*/ + chip_write(chip, TDA9875_MSR, 0x03); /* Monitor 0b00000XXX*/ + chip_write(chip, TDA9875_C1MSB, 0x00); /*Car1(FM) MSB XMHz*/ + chip_write(chip, TDA9875_C1MIB, 0x00); /*Car1(FM) MIB XMHz*/ + chip_write(chip, TDA9875_C1LSB, 0x00); /*Car1(FM) LSB XMHz*/ + chip_write(chip, TDA9875_C2MSB, 0x00); /*Car2(NICAM) MSB XMHz*/ + chip_write(chip, TDA9875_C2MIB, 0x00); /*Car2(NICAM) MIB XMHz*/ + chip_write(chip, TDA9875_C2LSB, 0x00); /*Car2(NICAM) LSB XMHz*/ + chip_write(chip, TDA9875_DCR, 0x00); /*Demod config 0x00*/ + chip_write(chip, TDA9875_DEEM, 0x44); /*DE-Emph 0b0100 0100*/ + chip_write(chip, TDA9875_FMAT, 0x00); /*FM Matrix reg 0x00*/ + chip_write(chip, TDA9875_SC1, 0x00); /* SCART 1 (SC1)*/ + chip_write(chip, TDA9875_SC2, 0x01); /* SCART 2 (sc2)*/ + + chip_write(chip, TDA9875_CH1V, 0x10); /* Channel volume 1 mute*/ + chip_write(chip, TDA9875_CH2V, 0x10); /* Channel volume 2 mute */ + chip_write(chip, TDA9875_DACOS, 0x02); /* sig DAC i/o(in:nicam)*/ + chip_write(chip, TDA9875_ADCIS, 0x6f); /* sig ADC input(in:mono)*/ + chip_write(chip, TDA9875_LOSR, 0x00); /* line out (in:mono)*/ + chip_write(chip, TDA9875_AER, 0x00); /*06 Effect (AVL+PSEUDO) */ + chip_write(chip, TDA9875_MCS, 0x44); /* Main ch select (DAC) */ + chip_write(chip, TDA9875_MVL, 0x03); /* Vol Main left 10dB */ + chip_write(chip, TDA9875_MVR, 0x03); /* Vol Main right 10dB*/ + chip_write(chip, TDA9875_MBA, 0x00); /* Main Bass Main 0dB*/ + chip_write(chip, TDA9875_MTR, 0x00); /* Main Treble Main 0dB*/ + chip_write(chip, TDA9875_ACS, 0x44); /* Aux chan select (dac)*/ + chip_write(chip, TDA9875_AVL, 0x00); /* Vol Aux left 0dB*/ + chip_write(chip, TDA9875_AVR, 0x00); /* Vol Aux right 0dB*/ + chip_write(chip, TDA9875_ABA, 0x00); /* Aux Bass Main 0dB*/ + chip_write(chip, TDA9875_ATR, 0x00); /* Aux Aigus Main 0dB*/ + + chip_write(chip, TDA9875_MUT, 0xcc); /* General mute */ + return 0; +} + +static int tda9875_volume(int val) { return (unsigned char)(val / 602 - 84); } +static int tda9875_bass(int val) { return (unsigned char)(max(-12, val / 2115 - 15)); } +static int tda9875_treble(int val) { return (unsigned char)(val / 2622 - 12); } + +/* ----------------------------------------------------------------------- */ + + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda9875_checkit(struct CHIPSTATE *chip) +{ + struct v4l2_subdev *sd = &chip->sd; + int dic, rev; + + dic = chip_read2(chip, 254); + rev = chip_read2(chip, 255); + + if (dic == 0 || dic == 2) { /* tda9875 and tda9875A */ + v4l2_info(sd, "found tda9875%s rev. %d.\n", + dic == 0 ? "" : "A", rev); + return 1; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tea6420 */ + +#define TEA6300_VL 0x00 /* volume left */ +#define TEA6300_VR 0x01 /* volume right */ +#define TEA6300_BA 0x02 /* bass */ +#define TEA6300_TR 0x03 /* treble */ +#define TEA6300_FA 0x04 /* fader control */ +#define TEA6300_S 0x05 /* switch register */ + /* values for those registers: */ +#define TEA6300_S_SA 0x01 /* stereo A input */ +#define TEA6300_S_SB 0x02 /* stereo B */ +#define TEA6300_S_SC 0x04 /* stereo C */ +#define TEA6300_S_GMU 0x80 /* general mute */ + +#define TEA6320_V 0x00 /* volume (0-5)/loudness off (6)/zero crossing mute(7) */ +#define TEA6320_FFR 0x01 /* fader front right (0-5) */ +#define TEA6320_FFL 0x02 /* fader front left (0-5) */ +#define TEA6320_FRR 0x03 /* fader rear right (0-5) */ +#define TEA6320_FRL 0x04 /* fader rear left (0-5) */ +#define TEA6320_BA 0x05 /* bass (0-4) */ +#define TEA6320_TR 0x06 /* treble (0-4) */ +#define TEA6320_S 0x07 /* switch register */ + /* values for those registers: */ +#define TEA6320_S_SA 0x07 /* stereo A input */ +#define TEA6320_S_SB 0x06 /* stereo B */ +#define TEA6320_S_SC 0x05 /* stereo C */ +#define TEA6320_S_SD 0x04 /* stereo D */ +#define TEA6320_S_GMU 0x80 /* general mute */ + +#define TEA6420_S_SA 0x00 /* stereo A input */ +#define TEA6420_S_SB 0x01 /* stereo B */ +#define TEA6420_S_SC 0x02 /* stereo C */ +#define TEA6420_S_SD 0x03 /* stereo D */ +#define TEA6420_S_SE 0x04 /* stereo E */ +#define TEA6420_S_GMU 0x05 /* general mute */ + +static int tea6300_shift10(int val) { return val >> 10; } +static int tea6300_shift12(int val) { return val >> 12; } + +/* Assumes 16bit input (values 0x3f to 0x0c are unique, values less than */ +/* 0x0c mirror those immediately higher) */ +static int tea6320_volume(int val) { return (val / (65535/(63-12)) + 12) & 0x3f; } +static int tea6320_shift11(int val) { return val >> 11; } +static int tea6320_initialize(struct CHIPSTATE * chip) +{ + chip_write(chip, TEA6320_FFR, 0x3f); + chip_write(chip, TEA6320_FFL, 0x3f); + chip_write(chip, TEA6320_FRR, 0x3f); + chip_write(chip, TEA6320_FRL, 0x3f); + + return 0; +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda8425 */ + +#define TDA8425_VL 0x00 /* volume left */ +#define TDA8425_VR 0x01 /* volume right */ +#define TDA8425_BA 0x02 /* bass */ +#define TDA8425_TR 0x03 /* treble */ +#define TDA8425_S1 0x08 /* switch functions */ + /* values for those registers: */ +#define TDA8425_S1_OFF 0xEE /* audio off (mute on) */ +#define TDA8425_S1_CH1 0xCE /* audio channel 1 (mute off) - "linear stereo" mode */ +#define TDA8425_S1_CH2 0xCF /* audio channel 2 (mute off) - "linear stereo" mode */ +#define TDA8425_S1_MU 0x20 /* mute bit */ +#define TDA8425_S1_STEREO 0x18 /* stereo bits */ +#define TDA8425_S1_STEREO_SPATIAL 0x18 /* spatial stereo */ +#define TDA8425_S1_STEREO_LINEAR 0x08 /* linear stereo */ +#define TDA8425_S1_STEREO_PSEUDO 0x10 /* pseudo stereo */ +#define TDA8425_S1_STEREO_MONO 0x00 /* forced mono */ +#define TDA8425_S1_ML 0x06 /* language selector */ +#define TDA8425_S1_ML_SOUND_A 0x02 /* sound a */ +#define TDA8425_S1_ML_SOUND_B 0x04 /* sound b */ +#define TDA8425_S1_ML_STEREO 0x06 /* stereo */ +#define TDA8425_S1_IS 0x01 /* channel selector */ + + +static int tda8425_shift10(int val) { return (val >> 10) | 0xc0; } +static int tda8425_shift12(int val) { return (val >> 12) | 0xf0; } + +static void tda8425_setaudmode(struct CHIPSTATE *chip, int mode) +{ + int s1 = chip->shadow.bytes[TDA8425_S1+1] & 0xe1; + + switch (mode) { + case V4L2_TUNER_MODE_LANG1: + s1 |= TDA8425_S1_ML_SOUND_A; + s1 |= TDA8425_S1_STEREO_PSEUDO; + break; + case V4L2_TUNER_MODE_LANG2: + s1 |= TDA8425_S1_ML_SOUND_B; + s1 |= TDA8425_S1_STEREO_PSEUDO; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + s1 |= TDA8425_S1_ML_STEREO; + s1 |= TDA8425_S1_STEREO_LINEAR; + break; + case V4L2_TUNER_MODE_MONO: + s1 |= TDA8425_S1_ML_STEREO; + s1 |= TDA8425_S1_STEREO_MONO; + break; + case V4L2_TUNER_MODE_STEREO: + s1 |= TDA8425_S1_ML_STEREO; + s1 |= TDA8425_S1_STEREO_SPATIAL; + break; + default: + return; + } + chip_write(chip,TDA8425_S1,s1); +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for pic16c54 (PV951) */ + +/* the registers of 16C54, I2C sub address. */ +#define PIC16C54_REG_KEY_CODE 0x01 /* Not use. */ +#define PIC16C54_REG_MISC 0x02 + +/* bit definition of the RESET register, I2C data. */ +#define PIC16C54_MISC_RESET_REMOTE_CTL 0x01 /* bit 0, Reset to receive the key */ + /* code of remote controller */ +#define PIC16C54_MISC_MTS_MAIN 0x02 /* bit 1 */ +#define PIC16C54_MISC_MTS_SAP 0x04 /* bit 2 */ +#define PIC16C54_MISC_MTS_BOTH 0x08 /* bit 3 */ +#define PIC16C54_MISC_SND_MUTE 0x10 /* bit 4, Mute Audio(Line-in and Tuner) */ +#define PIC16C54_MISC_SND_NOTMUTE 0x20 /* bit 5 */ +#define PIC16C54_MISC_SWITCH_TUNER 0x40 /* bit 6 , Switch to Line-in */ +#define PIC16C54_MISC_SWITCH_LINE 0x80 /* bit 7 , Switch to Tuner */ + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for TA8874Z */ + +/* write 1st byte */ +#define TA8874Z_LED_STE 0x80 +#define TA8874Z_LED_BIL 0x40 +#define TA8874Z_LED_EXT 0x20 +#define TA8874Z_MONO_SET 0x10 +#define TA8874Z_MUTE 0x08 +#define TA8874Z_F_MONO 0x04 +#define TA8874Z_MODE_SUB 0x02 +#define TA8874Z_MODE_MAIN 0x01 + +/* write 2nd byte */ +/*#define TA8874Z_TI 0x80 */ /* test mode */ +#define TA8874Z_SEPARATION 0x3f +#define TA8874Z_SEPARATION_DEFAULT 0x10 + +/* read */ +#define TA8874Z_B1 0x80 +#define TA8874Z_B0 0x40 +#define TA8874Z_CHAG_FLAG 0x20 + +/* + * B1 B0 + * mono L H + * stereo L L + * BIL H L + */ +static int ta8874z_getrxsubchans(struct CHIPSTATE *chip) +{ + int val, mode; + + val = chip_read(chip); + mode = V4L2_TUNER_SUB_MONO; + if (val & TA8874Z_B1){ + mode |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + }else if (!(val & TA8874Z_B0)){ + mode = V4L2_TUNER_SUB_STEREO; + } + /* v4l2_dbg(1, debug, &chip->sd, + "ta8874z_getrxsubchans(): raw chip read: 0x%02x, return: 0x%02x\n", + val, mode); */ + return mode; +} + +static audiocmd ta8874z_stereo = { 2, {0, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_mono = {2, { TA8874Z_MONO_SET, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_main = {2, { 0, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_sub = {2, { TA8874Z_MODE_SUB, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_both = {2, { TA8874Z_MODE_MAIN | TA8874Z_MODE_SUB, TA8874Z_SEPARATION_DEFAULT}}; + +static void ta8874z_setaudmode(struct CHIPSTATE *chip, int mode) +{ + struct v4l2_subdev *sd = &chip->sd; + int update = 1; + audiocmd *t = NULL; + + v4l2_dbg(1, debug, sd, "ta8874z_setaudmode(): mode: 0x%02x\n", mode); + + switch(mode){ + case V4L2_TUNER_MODE_MONO: + t = &ta8874z_mono; + break; + case V4L2_TUNER_MODE_STEREO: + t = &ta8874z_stereo; + break; + case V4L2_TUNER_MODE_LANG1: + t = &ta8874z_main; + break; + case V4L2_TUNER_MODE_LANG2: + t = &ta8874z_sub; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + t = &ta8874z_both; + break; + default: + update = 0; + } + + if(update) + chip_cmd(chip, "TA8874Z", t); +} + +static int ta8874z_checkit(struct CHIPSTATE *chip) +{ + int rc; + rc = chip_read(chip); + return ((rc & 0x1f) == 0x1f) ? 1 : 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - struct CHIPDESC */ + +/* insmod options to enable/disable individual audio chips */ +static int tda8425 = 1; +static int tda9840 = 1; +static int tda9850 = 1; +static int tda9855 = 1; +static int tda9873 = 1; +static int tda9874a = 1; +static int tda9875 = 1; +static int tea6300; /* default 0 - address clash with msp34xx */ +static int tea6320; /* default 0 - address clash with msp34xx */ +static int tea6420 = 1; +static int pic16c54 = 1; +static int ta8874z; /* default 0 - address clash with tda9840 */ + +module_param(tda8425, int, 0444); +module_param(tda9840, int, 0444); +module_param(tda9850, int, 0444); +module_param(tda9855, int, 0444); +module_param(tda9873, int, 0444); +module_param(tda9874a, int, 0444); +module_param(tda9875, int, 0444); +module_param(tea6300, int, 0444); +module_param(tea6320, int, 0444); +module_param(tea6420, int, 0444); +module_param(pic16c54, int, 0444); +module_param(ta8874z, int, 0444); + +static struct CHIPDESC chiplist[] = { + { + .name = "tda9840", + .insmodopt = &tda9840, + .addr_lo = I2C_ADDR_TDA9840 >> 1, + .addr_hi = I2C_ADDR_TDA9840 >> 1, + .registers = 5, + .flags = CHIP_NEED_CHECKMODE, + + /* callbacks */ + .checkit = tda9840_checkit, + .getrxsubchans = tda9840_getrxsubchans, + .setaudmode = tda9840_setaudmode, + + .init = { 2, { TDA9840_TEST, TDA9840_TEST_INT1SN + /* ,TDA9840_SW, TDA9840_MONO */} } + }, + { + .name = "tda9873h", + .insmodopt = &tda9873, + .addr_lo = I2C_ADDR_TDA985x_L >> 1, + .addr_hi = I2C_ADDR_TDA985x_H >> 1, + .registers = 3, + .flags = CHIP_HAS_INPUTSEL | CHIP_NEED_CHECKMODE, + + /* callbacks */ + .checkit = tda9873_checkit, + .getrxsubchans = tda9873_getrxsubchans, + .setaudmode = tda9873_setaudmode, + + .init = { 4, { TDA9873_SW, 0xa4, 0x06, 0x03 } }, + .inputreg = TDA9873_SW, + .inputmute = TDA9873_MUTE | TDA9873_AUTOMUTE, + .inputmap = {0xa0, 0xa2, 0xa0, 0xa0}, + .inputmask = TDA9873_INP_MASK|TDA9873_MUTE|TDA9873_AUTOMUTE, + + }, + { + .name = "tda9874h/a", + .insmodopt = &tda9874a, + .addr_lo = I2C_ADDR_TDA9874 >> 1, + .addr_hi = I2C_ADDR_TDA9874 >> 1, + .flags = CHIP_NEED_CHECKMODE, + + /* callbacks */ + .initialize = tda9874a_initialize, + .checkit = tda9874a_checkit, + .getrxsubchans = tda9874a_getrxsubchans, + .setaudmode = tda9874a_setaudmode, + }, + { + .name = "tda9875", + .insmodopt = &tda9875, + .addr_lo = I2C_ADDR_TDA9875 >> 1, + .addr_hi = I2C_ADDR_TDA9875 >> 1, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE, + + /* callbacks */ + .initialize = tda9875_initialize, + .checkit = tda9875_checkit, + .volfunc = tda9875_volume, + .bassfunc = tda9875_bass, + .treblefunc = tda9875_treble, + .leftreg = TDA9875_MVL, + .rightreg = TDA9875_MVR, + .bassreg = TDA9875_MBA, + .treblereg = TDA9875_MTR, + .leftinit = 58880, + .rightinit = 58880, + }, + { + .name = "tda9850", + .insmodopt = &tda9850, + .addr_lo = I2C_ADDR_TDA985x_L >> 1, + .addr_hi = I2C_ADDR_TDA985x_H >> 1, + .registers = 11, + + .getrxsubchans = tda985x_getrxsubchans, + .setaudmode = tda985x_setaudmode, + + .init = { 8, { TDA9850_C4, 0x08, 0x08, TDA985x_STEREO, 0x07, 0x10, 0x10, 0x03 } } + }, + { + .name = "tda9855", + .insmodopt = &tda9855, + .addr_lo = I2C_ADDR_TDA985x_L >> 1, + .addr_hi = I2C_ADDR_TDA985x_H >> 1, + .registers = 11, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE, + + .leftreg = TDA9855_VL, + .rightreg = TDA9855_VR, + .bassreg = TDA9855_BA, + .treblereg = TDA9855_TR, + + /* callbacks */ + .volfunc = tda9855_volume, + .bassfunc = tda9855_bass, + .treblefunc = tda9855_treble, + .getrxsubchans = tda985x_getrxsubchans, + .setaudmode = tda985x_setaudmode, + + .init = { 12, { 0, 0x6f, 0x6f, 0x0e, 0x07<<1, 0x8<<2, + TDA9855_MUTE | TDA9855_AVL | TDA9855_LOUD | TDA9855_INT, + TDA985x_STEREO | TDA9855_LINEAR | TDA9855_TZCM | TDA9855_VZCM, + 0x07, 0x10, 0x10, 0x03 }} + }, + { + .name = "tea6300", + .insmodopt = &tea6300, + .addr_lo = I2C_ADDR_TEA6300 >> 1, + .addr_hi = I2C_ADDR_TEA6300 >> 1, + .registers = 6, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TEA6300_VR, + .rightreg = TEA6300_VL, + .bassreg = TEA6300_BA, + .treblereg = TEA6300_TR, + + /* callbacks */ + .volfunc = tea6300_shift10, + .bassfunc = tea6300_shift12, + .treblefunc = tea6300_shift12, + + .inputreg = TEA6300_S, + .inputmap = { TEA6300_S_SA, TEA6300_S_SB, TEA6300_S_SC }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tea6320", + .insmodopt = &tea6320, + .addr_lo = I2C_ADDR_TEA6300 >> 1, + .addr_hi = I2C_ADDR_TEA6300 >> 1, + .registers = 8, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TEA6320_V, + .rightreg = TEA6320_V, + .bassreg = TEA6320_BA, + .treblereg = TEA6320_TR, + + /* callbacks */ + .initialize = tea6320_initialize, + .volfunc = tea6320_volume, + .bassfunc = tea6320_shift11, + .treblefunc = tea6320_shift11, + + .inputreg = TEA6320_S, + .inputmap = { TEA6320_S_SA, TEA6420_S_SB, TEA6300_S_SC, TEA6320_S_SD }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tea6420", + .insmodopt = &tea6420, + .addr_lo = I2C_ADDR_TEA6420 >> 1, + .addr_hi = I2C_ADDR_TEA6420 >> 1, + .registers = 1, + .flags = CHIP_HAS_INPUTSEL, + + .inputreg = -1, + .inputmap = { TEA6420_S_SA, TEA6420_S_SB, TEA6420_S_SC }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tda8425", + .insmodopt = &tda8425, + .addr_lo = I2C_ADDR_TDA8425 >> 1, + .addr_hi = I2C_ADDR_TDA8425 >> 1, + .registers = 9, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TDA8425_VL, + .rightreg = TDA8425_VR, + .bassreg = TDA8425_BA, + .treblereg = TDA8425_TR, + + /* callbacks */ + .volfunc = tda8425_shift10, + .bassfunc = tda8425_shift12, + .treblefunc = tda8425_shift12, + .setaudmode = tda8425_setaudmode, + + .inputreg = TDA8425_S1, + .inputmap = { TDA8425_S1_CH1, TDA8425_S1_CH1, TDA8425_S1_CH1 }, + .inputmute = TDA8425_S1_OFF, + + }, + { + .name = "pic16c54 (PV951)", + .insmodopt = &pic16c54, + .addr_lo = I2C_ADDR_PIC16C54 >> 1, + .addr_hi = I2C_ADDR_PIC16C54>> 1, + .registers = 2, + .flags = CHIP_HAS_INPUTSEL, + + .inputreg = PIC16C54_REG_MISC, + .inputmap = {PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_TUNER, + PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_LINE, + PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_LINE, + PIC16C54_MISC_SND_MUTE}, + .inputmute = PIC16C54_MISC_SND_MUTE, + }, + { + .name = "ta8874z", + .checkit = ta8874z_checkit, + .insmodopt = &ta8874z, + .addr_lo = I2C_ADDR_TDA9840 >> 1, + .addr_hi = I2C_ADDR_TDA9840 >> 1, + .registers = 2, + + /* callbacks */ + .getrxsubchans = ta8874z_getrxsubchans, + .setaudmode = ta8874z_setaudmode, + + .init = {2, { TA8874Z_MONO_SET, TA8874Z_SEPARATION_DEFAULT}}, + }, + { .name = NULL } /* EOF */ +}; + + +/* ---------------------------------------------------------------------- */ + +static int tvaudio_g_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + if (!(desc->flags & CHIP_HAS_INPUTSEL)) + break; + ctrl->value=chip->muted; + return 0; + case V4L2_CID_AUDIO_VOLUME: + if (!(desc->flags & CHIP_HAS_VOLUME)) + break; + ctrl->value = max(chip->left,chip->right); + return 0; + case V4L2_CID_AUDIO_BALANCE: + { + int volume; + if (!(desc->flags & CHIP_HAS_VOLUME)) + break; + volume = max(chip->left,chip->right); + if (volume) + ctrl->value=(32768*min(chip->left,chip->right))/volume; + else + ctrl->value=32768; + return 0; + } + case V4L2_CID_AUDIO_BASS: + if (!(desc->flags & CHIP_HAS_BASSTREBLE)) + break; + ctrl->value = chip->bass; + return 0; + case V4L2_CID_AUDIO_TREBLE: + if (!(desc->flags & CHIP_HAS_BASSTREBLE)) + break; + ctrl->value = chip->treble; + return 0; + } + return -EINVAL; +} + +static int tvaudio_s_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + if (!(desc->flags & CHIP_HAS_INPUTSEL)) + break; + + if (ctrl->value < 0 || ctrl->value >= 2) + return -ERANGE; + chip->muted = ctrl->value; + if (chip->muted) + chip_write_masked(chip,desc->inputreg,desc->inputmute,desc->inputmask); + else + chip_write_masked(chip,desc->inputreg, + desc->inputmap[chip->input],desc->inputmask); + return 0; + case V4L2_CID_AUDIO_VOLUME: + { + int volume,balance; + + if (!(desc->flags & CHIP_HAS_VOLUME)) + break; + + volume = max(chip->left,chip->right); + if (volume) + balance=(32768*min(chip->left,chip->right))/volume; + else + balance=32768; + + volume=ctrl->value; + chip->left = (min(65536 - balance,32768) * volume) / 32768; + chip->right = (min(balance,volume *(__u16)32768)) / 32768; + + chip_write(chip,desc->leftreg,desc->volfunc(chip->left)); + chip_write(chip,desc->rightreg,desc->volfunc(chip->right)); + + return 0; + } + case V4L2_CID_AUDIO_BALANCE: + { + int volume, balance; + + if (!(desc->flags & CHIP_HAS_VOLUME)) + break; + + volume = max(chip->left, chip->right); + balance = ctrl->value; + chip->left = (min(65536 - balance, 32768) * volume) / 32768; + chip->right = (min(balance, volume * (__u16)32768)) / 32768; + + chip_write(chip, desc->leftreg, desc->volfunc(chip->left)); + chip_write(chip, desc->rightreg, desc->volfunc(chip->right)); + + return 0; + } + case V4L2_CID_AUDIO_BASS: + if (!(desc->flags & CHIP_HAS_BASSTREBLE)) + break; + chip->bass = ctrl->value; + chip_write(chip,desc->bassreg,desc->bassfunc(chip->bass)); + + return 0; + case V4L2_CID_AUDIO_TREBLE: + if (!(desc->flags & CHIP_HAS_BASSTREBLE)) + break; + chip->treble = ctrl->value; + chip_write(chip,desc->treblereg,desc->treblefunc(chip->treble)); + + return 0; + } + return -EINVAL; +} + + +/* ---------------------------------------------------------------------- */ +/* video4linux interface */ + +static int tvaudio_s_radio(struct v4l2_subdev *sd) +{ + struct CHIPSTATE *chip = to_state(sd); + + chip->radio = 1; + /* del_timer(&chip->wt); */ + return 0; +} + +static int tvaudio_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + switch (qc->id) { + case V4L2_CID_AUDIO_MUTE: + if (desc->flags & CHIP_HAS_INPUTSEL) + return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0); + break; + case V4L2_CID_AUDIO_VOLUME: + if (desc->flags & CHIP_HAS_VOLUME) + return v4l2_ctrl_query_fill(qc, 0, 65535, 65535 / 100, 58880); + break; + case V4L2_CID_AUDIO_BALANCE: + if (desc->flags & CHIP_HAS_VOLUME) + return v4l2_ctrl_query_fill(qc, 0, 65535, 65535 / 100, 32768); + break; + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + if (desc->flags & CHIP_HAS_BASSTREBLE) + return v4l2_ctrl_query_fill(qc, 0, 65535, 65535 / 100, 32768); + break; + default: + break; + } + return -EINVAL; +} + +static int tvaudio_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + if (!(desc->flags & CHIP_HAS_INPUTSEL)) + return 0; + if (input >= 4) + return -EINVAL; + /* There are four inputs: tuner, radio, extern and intern. */ + chip->input = input; + if (chip->muted) + return 0; + chip_write_masked(chip, desc->inputreg, + desc->inputmap[chip->input], desc->inputmask); + return 0; +} + +static int tvaudio_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + if (!desc->setaudmode) + return 0; + if (chip->radio) + return 0; + + switch (vt->audmode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1: + case V4L2_TUNER_MODE_LANG2: + case V4L2_TUNER_MODE_LANG1_LANG2: + break; + default: + return -EINVAL; + } + chip->audmode = vt->audmode; + + if (chip->thread) + wake_up_process(chip->thread); + else + desc->setaudmode(chip, vt->audmode); + + return 0; +} + +static int tvaudio_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + if (!desc->getrxsubchans) + return 0; + if (chip->radio) + return 0; + + vt->audmode = chip->audmode; + vt->rxsubchans = desc->getrxsubchans(chip); + vt->capability = V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; + + return 0; +} + +static int tvaudio_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct CHIPSTATE *chip = to_state(sd); + + chip->radio = 0; + return 0; +} + +static int tvaudio_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *freq) +{ + struct CHIPSTATE *chip = to_state(sd); + struct CHIPDESC *desc = chip->desc; + + /* For chips that provide getrxsubchans and setaudmode, and doesn't + automatically follows the stereo carrier, a kthread is + created to set the audio standard. In this case, when then + the video channel is changed, tvaudio starts on MONO mode. + After waiting for 2 seconds, the kernel thread is called, + to follow whatever audio standard is pointed by the + audio carrier. + */ + if (chip->thread) { + desc->setaudmode(chip, V4L2_TUNER_MODE_MONO); + chip->prevmode = -1; /* reset previous mode */ + mod_timer(&chip->wt, jiffies+msecs_to_jiffies(2000)); + } + return 0; +} + +static int tvaudio_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_TVAUDIO, 0); +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops tvaudio_core_ops = { + .g_chip_ident = tvaudio_g_chip_ident, + .queryctrl = tvaudio_queryctrl, + .g_ctrl = tvaudio_g_ctrl, + .s_ctrl = tvaudio_s_ctrl, + .s_std = tvaudio_s_std, +}; + +static const struct v4l2_subdev_tuner_ops tvaudio_tuner_ops = { + .s_radio = tvaudio_s_radio, + .s_frequency = tvaudio_s_frequency, + .s_tuner = tvaudio_s_tuner, + .g_tuner = tvaudio_g_tuner, +}; + +static const struct v4l2_subdev_audio_ops tvaudio_audio_ops = { + .s_routing = tvaudio_s_routing, +}; + +static const struct v4l2_subdev_ops tvaudio_ops = { + .core = &tvaudio_core_ops, + .tuner = &tvaudio_tuner_ops, + .audio = &tvaudio_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ + + +/* i2c registration */ + +static int tvaudio_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct CHIPSTATE *chip; + struct CHIPDESC *desc; + struct v4l2_subdev *sd; + + if (debug) { + printk(KERN_INFO "tvaudio: TV audio decoder + audio/video mux driver\n"); + printk(KERN_INFO "tvaudio: known chips: "); + for (desc = chiplist; desc->name != NULL; desc++) + printk("%s%s", (desc == chiplist) ? "" : ", ", desc->name); + printk("\n"); + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + sd = &chip->sd; + v4l2_i2c_subdev_init(sd, client, &tvaudio_ops); + + /* find description for the chip */ + v4l2_dbg(1, debug, sd, "chip found @ 0x%x\n", client->addr<<1); + for (desc = chiplist; desc->name != NULL; desc++) { + if (0 == *(desc->insmodopt)) + continue; + if (client->addr < desc->addr_lo || + client->addr > desc->addr_hi) + continue; + if (desc->checkit && !desc->checkit(chip)) + continue; + break; + } + if (desc->name == NULL) { + v4l2_dbg(1, debug, sd, "no matching chip description found\n"); + kfree(chip); + return -EIO; + } + v4l2_info(sd, "%s found @ 0x%x (%s)\n", desc->name, client->addr<<1, client->adapter->name); + if (desc->flags) { + v4l2_dbg(1, debug, sd, "matches:%s%s%s.\n", + (desc->flags & CHIP_HAS_VOLUME) ? " volume" : "", + (desc->flags & CHIP_HAS_BASSTREBLE) ? " bass/treble" : "", + (desc->flags & CHIP_HAS_INPUTSEL) ? " audiomux" : ""); + } + + /* fill required data structures */ + if (!id) + strlcpy(client->name, desc->name, I2C_NAME_SIZE); + chip->desc = desc; + chip->shadow.count = desc->registers+1; + chip->prevmode = -1; + chip->audmode = V4L2_TUNER_MODE_LANG1; + + /* initialization */ + if (desc->initialize != NULL) + desc->initialize(chip); + else + chip_cmd(chip, "init", &desc->init); + + if (desc->flags & CHIP_HAS_VOLUME) { + if (!desc->volfunc) { + /* This shouldn't be happen. Warn user, but keep working + without volume controls + */ + v4l2_info(sd, "volume callback undefined!\n"); + desc->flags &= ~CHIP_HAS_VOLUME; + } else { + chip->left = desc->leftinit ? desc->leftinit : 65535; + chip->right = desc->rightinit ? desc->rightinit : 65535; + chip_write(chip, desc->leftreg, + desc->volfunc(chip->left)); + chip_write(chip, desc->rightreg, + desc->volfunc(chip->right)); + } + } + if (desc->flags & CHIP_HAS_BASSTREBLE) { + if (!desc->bassfunc || !desc->treblefunc) { + /* This shouldn't be happen. Warn user, but keep working + without bass/treble controls + */ + v4l2_info(sd, "bass/treble callbacks undefined!\n"); + desc->flags &= ~CHIP_HAS_BASSTREBLE; + } else { + chip->treble = desc->trebleinit ? + desc->trebleinit : 32768; + chip->bass = desc->bassinit ? + desc->bassinit : 32768; + chip_write(chip, desc->bassreg, + desc->bassfunc(chip->bass)); + chip_write(chip, desc->treblereg, + desc->treblefunc(chip->treble)); + } + } + + chip->thread = NULL; + init_timer(&chip->wt); + if (desc->flags & CHIP_NEED_CHECKMODE) { + if (!desc->getrxsubchans || !desc->setaudmode) { + /* This shouldn't be happen. Warn user, but keep working + without kthread + */ + v4l2_info(sd, "set/get mode callbacks undefined!\n"); + return 0; + } + /* start async thread */ + chip->wt.function = chip_thread_wake; + chip->wt.data = (unsigned long)chip; + chip->thread = kthread_run(chip_thread, chip, client->name); + if (IS_ERR(chip->thread)) { + v4l2_warn(sd, "failed to create kthread\n"); + chip->thread = NULL; + } + } + return 0; +} + +static int tvaudio_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct CHIPSTATE *chip = to_state(sd); + + del_timer_sync(&chip->wt); + if (chip->thread) { + /* shutdown async thread */ + kthread_stop(chip->thread); + chip->thread = NULL; + } + + v4l2_device_unregister_subdev(sd); + kfree(chip); + return 0; +} + +/* This driver supports many devices and the idea is to let the driver + detect which device is present. So rather than listing all supported + devices here, we pretend to support a single, fake device type. */ +static const struct i2c_device_id tvaudio_id[] = { + { "tvaudio", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tvaudio_id); + +static struct i2c_driver tvaudio_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "tvaudio", + }, + .probe = tvaudio_probe, + .remove = tvaudio_remove, + .id_table = tvaudio_id, +}; + +module_i2c_driver(tvaudio_driver); diff --git a/drivers/media/video/tveeprom.c b/drivers/media/i2c/tveeprom.c similarity index 100% rename from drivers/media/video/tveeprom.c rename to drivers/media/i2c/tveeprom.c diff --git a/drivers/media/i2c/tvp514x.c b/drivers/media/i2c/tvp514x.c new file mode 100644 index 000000000000..1f3943bb87d5 --- /dev/null +++ b/drivers/media/i2c/tvp514x.c @@ -0,0 +1,1166 @@ +/* + * drivers/media/i2c/tvp514x.c + * + * TI TVP5146/47 decoder driver + * + * Copyright (C) 2008 Texas Instruments Inc + * Author: Vaibhav Hiremath + * + * Contributors: + * Sivaraj R + * Brijesh R Jadav + * Hardik Shah + * Manjunath Hadli + * Karicheri Muralidharan + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "tvp514x_regs.h" + +/* Module Name */ +#define TVP514X_MODULE_NAME "tvp514x" + +/* Private macros for TVP */ +#define I2C_RETRY_COUNT (5) +#define LOCK_RETRY_COUNT (5) +#define LOCK_RETRY_DELAY (200) + +/* Debug functions */ +static bool debug; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TVP514X linux decoder driver"); +MODULE_LICENSE("GPL"); + +/* enum tvp514x_std - enum for supported standards */ +enum tvp514x_std { + STD_NTSC_MJ = 0, + STD_PAL_BDGHIN, + STD_INVALID +}; + +/** + * struct tvp514x_std_info - Structure to store standard informations + * @width: Line width in pixels + * @height:Number of active lines + * @video_std: Value to write in REG_VIDEO_STD register + * @standard: v4l2 standard structure information + */ +struct tvp514x_std_info { + unsigned long width; + unsigned long height; + u8 video_std; + struct v4l2_standard standard; +}; + +static struct tvp514x_reg tvp514x_reg_list_default[0x40]; + +static int tvp514x_s_stream(struct v4l2_subdev *sd, int enable); +/** + * struct tvp514x_decoder - TVP5146/47 decoder object + * @sd: Subdevice Slave handle + * @tvp514x_regs: copy of hw's regs with preset values. + * @pdata: Board specific + * @ver: Chip version + * @streaming: TVP5146/47 decoder streaming - enabled or disabled. + * @current_std: Current standard + * @num_stds: Number of standards + * @std_list: Standards list + * @input: Input routing at chip level + * @output: Output routing at chip level + */ +struct tvp514x_decoder { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct tvp514x_reg tvp514x_regs[ARRAY_SIZE(tvp514x_reg_list_default)]; + const struct tvp514x_platform_data *pdata; + + int ver; + int streaming; + + enum tvp514x_std current_std; + int num_stds; + const struct tvp514x_std_info *std_list; + /* Input and Output Routing parameters */ + u32 input; + u32 output; +}; + +/* TVP514x default register values */ +static struct tvp514x_reg tvp514x_reg_list_default[] = { + /* Composite selected */ + {TOK_WRITE, REG_INPUT_SEL, 0x05}, + {TOK_WRITE, REG_AFE_GAIN_CTRL, 0x0F}, + /* Auto mode */ + {TOK_WRITE, REG_VIDEO_STD, 0x00}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, + {TOK_SKIP, REG_AUTOSWITCH_MASK, 0x3F}, + {TOK_WRITE, REG_COLOR_KILLER, 0x10}, + {TOK_WRITE, REG_LUMA_CONTROL1, 0x00}, + {TOK_WRITE, REG_LUMA_CONTROL2, 0x00}, + {TOK_WRITE, REG_LUMA_CONTROL3, 0x02}, + {TOK_WRITE, REG_BRIGHTNESS, 0x80}, + {TOK_WRITE, REG_CONTRAST, 0x80}, + {TOK_WRITE, REG_SATURATION, 0x80}, + {TOK_WRITE, REG_HUE, 0x00}, + {TOK_WRITE, REG_CHROMA_CONTROL1, 0x00}, + {TOK_WRITE, REG_CHROMA_CONTROL2, 0x0E}, + /* Reserved */ + {TOK_SKIP, 0x0F, 0x00}, + {TOK_WRITE, REG_COMP_PR_SATURATION, 0x80}, + {TOK_WRITE, REG_COMP_Y_CONTRAST, 0x80}, + {TOK_WRITE, REG_COMP_PB_SATURATION, 0x80}, + /* Reserved */ + {TOK_SKIP, 0x13, 0x00}, + {TOK_WRITE, REG_COMP_Y_BRIGHTNESS, 0x80}, + /* Reserved */ + {TOK_SKIP, 0x15, 0x00}, + /* NTSC timing */ + {TOK_SKIP, REG_AVID_START_PIXEL_LSB, 0x55}, + {TOK_SKIP, REG_AVID_START_PIXEL_MSB, 0x00}, + {TOK_SKIP, REG_AVID_STOP_PIXEL_LSB, 0x25}, + {TOK_SKIP, REG_AVID_STOP_PIXEL_MSB, 0x03}, + /* NTSC timing */ + {TOK_SKIP, REG_HSYNC_START_PIXEL_LSB, 0x00}, + {TOK_SKIP, REG_HSYNC_START_PIXEL_MSB, 0x00}, + {TOK_SKIP, REG_HSYNC_STOP_PIXEL_LSB, 0x40}, + {TOK_SKIP, REG_HSYNC_STOP_PIXEL_MSB, 0x00}, + /* NTSC timing */ + {TOK_SKIP, REG_VSYNC_START_LINE_LSB, 0x04}, + {TOK_SKIP, REG_VSYNC_START_LINE_MSB, 0x00}, + {TOK_SKIP, REG_VSYNC_STOP_LINE_LSB, 0x07}, + {TOK_SKIP, REG_VSYNC_STOP_LINE_MSB, 0x00}, + /* NTSC timing */ + {TOK_SKIP, REG_VBLK_START_LINE_LSB, 0x01}, + {TOK_SKIP, REG_VBLK_START_LINE_MSB, 0x00}, + {TOK_SKIP, REG_VBLK_STOP_LINE_LSB, 0x15}, + {TOK_SKIP, REG_VBLK_STOP_LINE_MSB, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x26, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x27, 0x00}, + {TOK_SKIP, REG_FAST_SWTICH_CONTROL, 0xCC}, + /* Reserved */ + {TOK_SKIP, 0x29, 0x00}, + {TOK_SKIP, REG_FAST_SWTICH_SCART_DELAY, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x2B, 0x00}, + {TOK_SKIP, REG_SCART_DELAY, 0x00}, + {TOK_SKIP, REG_CTI_DELAY, 0x00}, + {TOK_SKIP, REG_CTI_CONTROL, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x2F, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x30, 0x00}, + /* Reserved */ + {TOK_SKIP, 0x31, 0x00}, + /* HS, VS active high */ + {TOK_WRITE, REG_SYNC_CONTROL, 0x00}, + /* 10-bit BT.656 */ + {TOK_WRITE, REG_OUTPUT_FORMATTER1, 0x00}, + /* Enable clk & data */ + {TOK_WRITE, REG_OUTPUT_FORMATTER2, 0x11}, + /* Enable AVID & FLD */ + {TOK_WRITE, REG_OUTPUT_FORMATTER3, 0xEE}, + /* Enable VS & HS */ + {TOK_WRITE, REG_OUTPUT_FORMATTER4, 0xAF}, + {TOK_WRITE, REG_OUTPUT_FORMATTER5, 0xFF}, + {TOK_WRITE, REG_OUTPUT_FORMATTER6, 0xFF}, + /* Clear status */ + {TOK_WRITE, REG_CLEAR_LOST_LOCK, 0x01}, + {TOK_TERM, 0, 0}, +}; + +/** + * Supported standards - + * + * Currently supports two standards only, need to add support for rest of the + * modes, like SECAM, etc... + */ +static const struct tvp514x_std_info tvp514x_std_list[] = { + /* Standard: STD_NTSC_MJ */ + [STD_NTSC_MJ] = { + .width = NTSC_NUM_ACTIVE_PIXELS, + .height = NTSC_NUM_ACTIVE_LINES, + .video_std = VIDEO_STD_NTSC_MJ_BIT, + .standard = { + .index = 0, + .id = V4L2_STD_NTSC, + .name = "NTSC", + .frameperiod = {1001, 30000}, + .framelines = 525 + }, + /* Standard: STD_PAL_BDGHIN */ + }, + [STD_PAL_BDGHIN] = { + .width = PAL_NUM_ACTIVE_PIXELS, + .height = PAL_NUM_ACTIVE_LINES, + .video_std = VIDEO_STD_PAL_BDGHIN_BIT, + .standard = { + .index = 1, + .id = V4L2_STD_PAL, + .name = "PAL", + .frameperiod = {1, 25}, + .framelines = 625 + }, + }, + /* Standard: need to add for additional standard */ +}; + + +static inline struct tvp514x_decoder *to_decoder(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tvp514x_decoder, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tvp514x_decoder, hdl)->sd; +} + + +/** + * tvp514x_read_reg() - Read a value from a register in an TVP5146/47. + * @sd: ptr to v4l2_subdev struct + * @reg: TVP5146/47 register address + * + * Returns value read if successful, or non-zero (-1) otherwise. + */ +static int tvp514x_read_reg(struct v4l2_subdev *sd, u8 reg) +{ + int err, retry = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + +read_again: + + err = i2c_smbus_read_byte_data(client, reg); + if (err < 0) { + if (retry <= I2C_RETRY_COUNT) { + v4l2_warn(sd, "Read: retry ... %d\n", retry); + retry++; + msleep_interruptible(10); + goto read_again; + } + } + + return err; +} + +/** + * dump_reg() - dump the register content of TVP5146/47. + * @sd: ptr to v4l2_subdev struct + * @reg: TVP5146/47 register address + */ +static void dump_reg(struct v4l2_subdev *sd, u8 reg) +{ + u32 val; + + val = tvp514x_read_reg(sd, reg); + v4l2_info(sd, "Reg(0x%.2X): 0x%.2X\n", reg, val); +} + +/** + * tvp514x_write_reg() - Write a value to a register in TVP5146/47 + * @sd: ptr to v4l2_subdev struct + * @reg: TVP5146/47 register address + * @val: value to be written to the register + * + * Write a value to a register in an TVP5146/47 decoder device. + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp514x_write_reg(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + int err, retry = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + +write_again: + + err = i2c_smbus_write_byte_data(client, reg, val); + if (err) { + if (retry <= I2C_RETRY_COUNT) { + v4l2_warn(sd, "Write: retry ... %d\n", retry); + retry++; + msleep_interruptible(10); + goto write_again; + } + } + + return err; +} + +/** + * tvp514x_write_regs() : Initializes a list of TVP5146/47 registers + * @sd: ptr to v4l2_subdev struct + * @reglist: list of TVP5146/47 registers and values + * + * Initializes a list of TVP5146/47 registers:- + * if token is TOK_TERM, then entire write operation terminates + * if token is TOK_DELAY, then a delay of 'val' msec is introduced + * if token is TOK_SKIP, then the register write is skipped + * if token is TOK_WRITE, then the register write is performed + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp514x_write_regs(struct v4l2_subdev *sd, + const struct tvp514x_reg reglist[]) +{ + int err; + const struct tvp514x_reg *next = reglist; + + for (; next->token != TOK_TERM; next++) { + if (next->token == TOK_DELAY) { + msleep(next->val); + continue; + } + + if (next->token == TOK_SKIP) + continue; + + err = tvp514x_write_reg(sd, next->reg, (u8) next->val); + if (err) { + v4l2_err(sd, "Write failed. Err[%d]\n", err); + return err; + } + } + return 0; +} + +/** + * tvp514x_query_current_std() : Query the current standard detected by TVP5146/47 + * @sd: ptr to v4l2_subdev struct + * + * Returns the current standard detected by TVP5146/47, STD_INVALID if there is no + * standard detected. + */ +static enum tvp514x_std tvp514x_query_current_std(struct v4l2_subdev *sd) +{ + u8 std, std_status; + + std = tvp514x_read_reg(sd, REG_VIDEO_STD); + if ((std & VIDEO_STD_MASK) == VIDEO_STD_AUTO_SWITCH_BIT) + /* use the standard status register */ + std_status = tvp514x_read_reg(sd, REG_VIDEO_STD_STATUS); + else + /* use the standard register itself */ + std_status = std; + + switch (std_status & VIDEO_STD_MASK) { + case VIDEO_STD_NTSC_MJ_BIT: + return STD_NTSC_MJ; + + case VIDEO_STD_PAL_BDGHIN_BIT: + return STD_PAL_BDGHIN; + + default: + return STD_INVALID; + } + + return STD_INVALID; +} + +/* TVP5146/47 register dump function */ +static void tvp514x_reg_dump(struct v4l2_subdev *sd) +{ + dump_reg(sd, REG_INPUT_SEL); + dump_reg(sd, REG_AFE_GAIN_CTRL); + dump_reg(sd, REG_VIDEO_STD); + dump_reg(sd, REG_OPERATION_MODE); + dump_reg(sd, REG_COLOR_KILLER); + dump_reg(sd, REG_LUMA_CONTROL1); + dump_reg(sd, REG_LUMA_CONTROL2); + dump_reg(sd, REG_LUMA_CONTROL3); + dump_reg(sd, REG_BRIGHTNESS); + dump_reg(sd, REG_CONTRAST); + dump_reg(sd, REG_SATURATION); + dump_reg(sd, REG_HUE); + dump_reg(sd, REG_CHROMA_CONTROL1); + dump_reg(sd, REG_CHROMA_CONTROL2); + dump_reg(sd, REG_COMP_PR_SATURATION); + dump_reg(sd, REG_COMP_Y_CONTRAST); + dump_reg(sd, REG_COMP_PB_SATURATION); + dump_reg(sd, REG_COMP_Y_BRIGHTNESS); + dump_reg(sd, REG_AVID_START_PIXEL_LSB); + dump_reg(sd, REG_AVID_START_PIXEL_MSB); + dump_reg(sd, REG_AVID_STOP_PIXEL_LSB); + dump_reg(sd, REG_AVID_STOP_PIXEL_MSB); + dump_reg(sd, REG_HSYNC_START_PIXEL_LSB); + dump_reg(sd, REG_HSYNC_START_PIXEL_MSB); + dump_reg(sd, REG_HSYNC_STOP_PIXEL_LSB); + dump_reg(sd, REG_HSYNC_STOP_PIXEL_MSB); + dump_reg(sd, REG_VSYNC_START_LINE_LSB); + dump_reg(sd, REG_VSYNC_START_LINE_MSB); + dump_reg(sd, REG_VSYNC_STOP_LINE_LSB); + dump_reg(sd, REG_VSYNC_STOP_LINE_MSB); + dump_reg(sd, REG_VBLK_START_LINE_LSB); + dump_reg(sd, REG_VBLK_START_LINE_MSB); + dump_reg(sd, REG_VBLK_STOP_LINE_LSB); + dump_reg(sd, REG_VBLK_STOP_LINE_MSB); + dump_reg(sd, REG_SYNC_CONTROL); + dump_reg(sd, REG_OUTPUT_FORMATTER1); + dump_reg(sd, REG_OUTPUT_FORMATTER2); + dump_reg(sd, REG_OUTPUT_FORMATTER3); + dump_reg(sd, REG_OUTPUT_FORMATTER4); + dump_reg(sd, REG_OUTPUT_FORMATTER5); + dump_reg(sd, REG_OUTPUT_FORMATTER6); + dump_reg(sd, REG_CLEAR_LOST_LOCK); +} + +/** + * tvp514x_configure() - Configure the TVP5146/47 registers + * @sd: ptr to v4l2_subdev struct + * @decoder: ptr to tvp514x_decoder structure + * + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp514x_configure(struct v4l2_subdev *sd, + struct tvp514x_decoder *decoder) +{ + int err; + + /* common register initialization */ + err = + tvp514x_write_regs(sd, decoder->tvp514x_regs); + if (err) + return err; + + if (debug) + tvp514x_reg_dump(sd); + + return 0; +} + +/** + * tvp514x_detect() - Detect if an tvp514x is present, and if so which revision. + * @sd: pointer to standard V4L2 sub-device structure + * @decoder: pointer to tvp514x_decoder structure + * + * A device is considered to be detected if the chip ID (LSB and MSB) + * registers match the expected values. + * Any value of the rom version register is accepted. + * Returns ENODEV error number if no device is detected, or zero + * if a device is detected. + */ +static int tvp514x_detect(struct v4l2_subdev *sd, + struct tvp514x_decoder *decoder) +{ + u8 chip_id_msb, chip_id_lsb, rom_ver; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + chip_id_msb = tvp514x_read_reg(sd, REG_CHIP_ID_MSB); + chip_id_lsb = tvp514x_read_reg(sd, REG_CHIP_ID_LSB); + rom_ver = tvp514x_read_reg(sd, REG_ROM_VERSION); + + v4l2_dbg(1, debug, sd, + "chip id detected msb:0x%x lsb:0x%x rom version:0x%x\n", + chip_id_msb, chip_id_lsb, rom_ver); + if ((chip_id_msb != TVP514X_CHIP_ID_MSB) + || ((chip_id_lsb != TVP5146_CHIP_ID_LSB) + && (chip_id_lsb != TVP5147_CHIP_ID_LSB))) { + /* We didn't read the values we expected, so this must not be + * an TVP5146/47. + */ + v4l2_err(sd, "chip id mismatch msb:0x%x lsb:0x%x\n", + chip_id_msb, chip_id_lsb); + return -ENODEV; + } + + decoder->ver = rom_ver; + + v4l2_info(sd, "%s (Version - 0x%.2x) found at 0x%x (%s)\n", + client->name, decoder->ver, + client->addr << 1, client->adapter->name); + return 0; +} + +/** + * tvp514x_querystd() - V4L2 decoder interface handler for querystd + * @sd: pointer to standard V4L2 sub-device structure + * @std_id: standard V4L2 std_id ioctl enum + * + * Returns the current standard detected by TVP5146/47. If no active input is + * detected then *std_id is set to 0 and the function returns 0. + */ +static int tvp514x_querystd(struct v4l2_subdev *sd, v4l2_std_id *std_id) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + enum tvp514x_std current_std; + enum tvp514x_input input_sel; + u8 sync_lock_status, lock_mask; + + if (std_id == NULL) + return -EINVAL; + + *std_id = V4L2_STD_UNKNOWN; + + /* query the current standard */ + current_std = tvp514x_query_current_std(sd); + if (current_std == STD_INVALID) + return 0; + + input_sel = decoder->input; + + switch (input_sel) { + case INPUT_CVBS_VI1A: + case INPUT_CVBS_VI1B: + case INPUT_CVBS_VI1C: + case INPUT_CVBS_VI2A: + case INPUT_CVBS_VI2B: + case INPUT_CVBS_VI2C: + case INPUT_CVBS_VI3A: + case INPUT_CVBS_VI3B: + case INPUT_CVBS_VI3C: + case INPUT_CVBS_VI4A: + lock_mask = STATUS_CLR_SUBCAR_LOCK_BIT | + STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + + case INPUT_SVIDEO_VI2A_VI1A: + case INPUT_SVIDEO_VI2B_VI1B: + case INPUT_SVIDEO_VI2C_VI1C: + case INPUT_SVIDEO_VI2A_VI3A: + case INPUT_SVIDEO_VI2B_VI3B: + case INPUT_SVIDEO_VI2C_VI3C: + case INPUT_SVIDEO_VI4A_VI1A: + case INPUT_SVIDEO_VI4A_VI1B: + case INPUT_SVIDEO_VI4A_VI1C: + case INPUT_SVIDEO_VI4A_VI3A: + case INPUT_SVIDEO_VI4A_VI3B: + case INPUT_SVIDEO_VI4A_VI3C: + lock_mask = STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + /*Need to add other interfaces*/ + default: + return -EINVAL; + } + /* check whether signal is locked */ + sync_lock_status = tvp514x_read_reg(sd, REG_STATUS1); + if (lock_mask != (sync_lock_status & lock_mask)) + return 0; /* No input detected */ + + *std_id = decoder->std_list[current_std].standard.id; + + v4l2_dbg(1, debug, sd, "Current STD: %s\n", + decoder->std_list[current_std].standard.name); + return 0; +} + +/** + * tvp514x_s_std() - V4L2 decoder interface handler for s_std + * @sd: pointer to standard V4L2 sub-device structure + * @std_id: standard V4L2 v4l2_std_id ioctl enum + * + * If std_id is supported, sets the requested standard. Otherwise, returns + * -EINVAL + */ +static int tvp514x_s_std(struct v4l2_subdev *sd, v4l2_std_id std_id) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + int err, i; + + for (i = 0; i < decoder->num_stds; i++) + if (std_id & decoder->std_list[i].standard.id) + break; + + if ((i == decoder->num_stds) || (i == STD_INVALID)) + return -EINVAL; + + err = tvp514x_write_reg(sd, REG_VIDEO_STD, + decoder->std_list[i].video_std); + if (err) + return err; + + decoder->current_std = i; + decoder->tvp514x_regs[REG_VIDEO_STD].val = + decoder->std_list[i].video_std; + + v4l2_dbg(1, debug, sd, "Standard set to: %s\n", + decoder->std_list[i].standard.name); + return 0; +} + +/** + * tvp514x_s_routing() - V4L2 decoder interface handler for s_routing + * @sd: pointer to standard V4L2 sub-device structure + * @input: input selector for routing the signal + * @output: output selector for routing the signal + * @config: config value. Not used + * + * If index is valid, selects the requested input. Otherwise, returns -EINVAL if + * the input is not supported or there is no active signal present in the + * selected input. + */ +static int tvp514x_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + int err; + enum tvp514x_input input_sel; + enum tvp514x_output output_sel; + u8 sync_lock_status, lock_mask; + int try_count = LOCK_RETRY_COUNT; + + if ((input >= INPUT_INVALID) || + (output >= OUTPUT_INVALID)) + /* Index out of bound */ + return -EINVAL; + + /* + * For the sequence streamon -> streamoff and again s_input + * it fails to lock the signal, since streamoff puts TVP514x + * into power off state which leads to failure in sub-sequent s_input. + * + * So power up the TVP514x device here, since it is important to lock + * the signal at this stage. + */ + if (!decoder->streaming) + tvp514x_s_stream(sd, 1); + + input_sel = input; + output_sel = output; + + err = tvp514x_write_reg(sd, REG_INPUT_SEL, input_sel); + if (err) + return err; + + output_sel |= tvp514x_read_reg(sd, + REG_OUTPUT_FORMATTER1) & 0x7; + err = tvp514x_write_reg(sd, REG_OUTPUT_FORMATTER1, + output_sel); + if (err) + return err; + + decoder->tvp514x_regs[REG_INPUT_SEL].val = input_sel; + decoder->tvp514x_regs[REG_OUTPUT_FORMATTER1].val = output_sel; + + /* Clear status */ + msleep(LOCK_RETRY_DELAY); + err = + tvp514x_write_reg(sd, REG_CLEAR_LOST_LOCK, 0x01); + if (err) + return err; + + switch (input_sel) { + case INPUT_CVBS_VI1A: + case INPUT_CVBS_VI1B: + case INPUT_CVBS_VI1C: + case INPUT_CVBS_VI2A: + case INPUT_CVBS_VI2B: + case INPUT_CVBS_VI2C: + case INPUT_CVBS_VI3A: + case INPUT_CVBS_VI3B: + case INPUT_CVBS_VI3C: + case INPUT_CVBS_VI4A: + lock_mask = STATUS_CLR_SUBCAR_LOCK_BIT | + STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + + case INPUT_SVIDEO_VI2A_VI1A: + case INPUT_SVIDEO_VI2B_VI1B: + case INPUT_SVIDEO_VI2C_VI1C: + case INPUT_SVIDEO_VI2A_VI3A: + case INPUT_SVIDEO_VI2B_VI3B: + case INPUT_SVIDEO_VI2C_VI3C: + case INPUT_SVIDEO_VI4A_VI1A: + case INPUT_SVIDEO_VI4A_VI1B: + case INPUT_SVIDEO_VI4A_VI1C: + case INPUT_SVIDEO_VI4A_VI3A: + case INPUT_SVIDEO_VI4A_VI3B: + case INPUT_SVIDEO_VI4A_VI3C: + lock_mask = STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + /* Need to add other interfaces*/ + default: + return -EINVAL; + } + + while (try_count-- > 0) { + /* Allow decoder to sync up with new input */ + msleep(LOCK_RETRY_DELAY); + + sync_lock_status = tvp514x_read_reg(sd, + REG_STATUS1); + if (lock_mask == (sync_lock_status & lock_mask)) + /* Input detected */ + break; + } + + if (try_count < 0) + return -EINVAL; + + decoder->input = input; + decoder->output = output; + + v4l2_dbg(1, debug, sd, "Input set to: %d\n", input_sel); + + return 0; +} + +/** + * tvp514x_s_ctrl() - V4L2 decoder interface handler for s_ctrl + * @ctrl: pointer to v4l2_ctrl structure + * + * If the requested control is supported, sets the control's current + * value in HW. Otherwise, returns -EINVAL if the control is not supported. + */ +static int tvp514x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct tvp514x_decoder *decoder = to_decoder(sd); + int err = -EINVAL, value; + + value = ctrl->val; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + err = tvp514x_write_reg(sd, REG_BRIGHTNESS, value); + if (!err) + decoder->tvp514x_regs[REG_BRIGHTNESS].val = value; + break; + case V4L2_CID_CONTRAST: + err = tvp514x_write_reg(sd, REG_CONTRAST, value); + if (!err) + decoder->tvp514x_regs[REG_CONTRAST].val = value; + break; + case V4L2_CID_SATURATION: + err = tvp514x_write_reg(sd, REG_SATURATION, value); + if (!err) + decoder->tvp514x_regs[REG_SATURATION].val = value; + break; + case V4L2_CID_HUE: + if (value == 180) + value = 0x7F; + else if (value == -180) + value = 0x80; + err = tvp514x_write_reg(sd, REG_HUE, value); + if (!err) + decoder->tvp514x_regs[REG_HUE].val = value; + break; + case V4L2_CID_AUTOGAIN: + err = tvp514x_write_reg(sd, REG_AFE_GAIN_CTRL, value ? 0x0f : 0x0c); + if (!err) + decoder->tvp514x_regs[REG_AFE_GAIN_CTRL].val = value; + break; + } + + v4l2_dbg(1, debug, sd, "Set Control: ID - %d - %d\n", + ctrl->id, ctrl->val); + return err; +} + +/** + * tvp514x_enum_mbus_fmt() - V4L2 decoder interface handler for enum_mbus_fmt + * @sd: pointer to standard V4L2 sub-device structure + * @index: index of pixelcode to retrieve + * @code: receives the pixelcode + * + * Enumerates supported mediabus formats + */ +static int +tvp514x_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned index, + enum v4l2_mbus_pixelcode *code) +{ + if (index) + return -EINVAL; + + *code = V4L2_MBUS_FMT_YUYV10_2X10; + return 0; +} + +/** + * tvp514x_mbus_fmt_cap() - V4L2 decoder interface handler for try/s/g_mbus_fmt + * @sd: pointer to standard V4L2 sub-device structure + * @f: pointer to the mediabus format structure + * + * Negotiates the image capture size and mediabus format. + */ +static int +tvp514x_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *f) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + enum tvp514x_std current_std; + + if (f == NULL) + return -EINVAL; + + /* Calculate height and width based on current standard */ + current_std = decoder->current_std; + + f->code = V4L2_MBUS_FMT_YUYV10_2X10; + f->width = decoder->std_list[current_std].width; + f->height = decoder->std_list[current_std].height; + f->field = V4L2_FIELD_INTERLACED; + f->colorspace = V4L2_COLORSPACE_SMPTE170M; + + v4l2_dbg(1, debug, sd, "MBUS_FMT: Width - %d, Height - %d\n", + f->width, f->height); + return 0; +} + +/** + * tvp514x_g_parm() - V4L2 decoder interface handler for g_parm + * @sd: pointer to standard V4L2 sub-device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the decoder's video CAPTURE parameters. + */ +static int +tvp514x_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + struct v4l2_captureparm *cparm; + enum tvp514x_std current_std; + + if (a == NULL) + return -EINVAL; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + /* only capture is supported */ + return -EINVAL; + + /* get the current standard */ + current_std = decoder->current_std; + + cparm = &a->parm.capture; + cparm->capability = V4L2_CAP_TIMEPERFRAME; + cparm->timeperframe = + decoder->std_list[current_std].standard.frameperiod; + + return 0; +} + +/** + * tvp514x_s_parm() - V4L2 decoder interface handler for s_parm + * @sd: pointer to standard V4L2 sub-device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the decoder to use the input parameters, if possible. If + * not possible, returns the appropriate error code. + */ +static int +tvp514x_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a) +{ + struct tvp514x_decoder *decoder = to_decoder(sd); + struct v4l2_fract *timeperframe; + enum tvp514x_std current_std; + + if (a == NULL) + return -EINVAL; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + /* only capture is supported */ + return -EINVAL; + + timeperframe = &a->parm.capture.timeperframe; + + /* get the current standard */ + current_std = decoder->current_std; + + *timeperframe = + decoder->std_list[current_std].standard.frameperiod; + + return 0; +} + +/** + * tvp514x_s_stream() - V4L2 decoder i/f handler for s_stream + * @sd: pointer to standard V4L2 sub-device structure + * @enable: streaming enable or disable + * + * Sets streaming to enable or disable, if possible. + */ +static int tvp514x_s_stream(struct v4l2_subdev *sd, int enable) +{ + int err = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct tvp514x_decoder *decoder = to_decoder(sd); + + if (decoder->streaming == enable) + return 0; + + switch (enable) { + case 0: + { + /* Power Down Sequence */ + err = tvp514x_write_reg(sd, REG_OPERATION_MODE, 0x01); + if (err) { + v4l2_err(sd, "Unable to turn off decoder\n"); + return err; + } + decoder->streaming = enable; + break; + } + case 1: + { + struct tvp514x_reg *int_seq = (struct tvp514x_reg *) + client->driver->id_table->driver_data; + + /* Power Up Sequence */ + err = tvp514x_write_regs(sd, int_seq); + if (err) { + v4l2_err(sd, "Unable to turn on decoder\n"); + return err; + } + /* Detect if not already detected */ + err = tvp514x_detect(sd, decoder); + if (err) { + v4l2_err(sd, "Unable to detect decoder\n"); + return err; + } + err = tvp514x_configure(sd, decoder); + if (err) { + v4l2_err(sd, "Unable to configure decoder\n"); + return err; + } + decoder->streaming = enable; + break; + } + default: + err = -ENODEV; + break; + } + + return err; +} + +static const struct v4l2_ctrl_ops tvp514x_ctrl_ops = { + .s_ctrl = tvp514x_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops tvp514x_core_ops = { + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, + .s_std = tvp514x_s_std, +}; + +static const struct v4l2_subdev_video_ops tvp514x_video_ops = { + .s_routing = tvp514x_s_routing, + .querystd = tvp514x_querystd, + .enum_mbus_fmt = tvp514x_enum_mbus_fmt, + .g_mbus_fmt = tvp514x_mbus_fmt, + .try_mbus_fmt = tvp514x_mbus_fmt, + .s_mbus_fmt = tvp514x_mbus_fmt, + .g_parm = tvp514x_g_parm, + .s_parm = tvp514x_s_parm, + .s_stream = tvp514x_s_stream, +}; + +static const struct v4l2_subdev_ops tvp514x_ops = { + .core = &tvp514x_core_ops, + .video = &tvp514x_video_ops, +}; + +static struct tvp514x_decoder tvp514x_dev = { + .streaming = 0, + .current_std = STD_NTSC_MJ, + .std_list = tvp514x_std_list, + .num_stds = ARRAY_SIZE(tvp514x_std_list), + +}; + +/** + * tvp514x_probe() - decoder driver i2c probe handler + * @client: i2c driver client device structure + * @id: i2c driver id table + * + * Register decoder as an i2c client device and V4L2 + * device. + */ +static int +tvp514x_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct tvp514x_decoder *decoder; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + if (!client->dev.platform_data) { + v4l2_err(client, "No platform data!!\n"); + return -ENODEV; + } + + decoder = kzalloc(sizeof(*decoder), GFP_KERNEL); + if (!decoder) + return -ENOMEM; + + /* Initialize the tvp514x_decoder with default configuration */ + *decoder = tvp514x_dev; + /* Copy default register configuration */ + memcpy(decoder->tvp514x_regs, tvp514x_reg_list_default, + sizeof(tvp514x_reg_list_default)); + + /* Copy board specific information here */ + decoder->pdata = client->dev.platform_data; + + /** + * Fetch platform specific data, and configure the + * tvp514x_reg_list[] accordingly. Since this is one + * time configuration, no need to preserve. + */ + decoder->tvp514x_regs[REG_OUTPUT_FORMATTER2].val |= + (decoder->pdata->clk_polarity << 1); + decoder->tvp514x_regs[REG_SYNC_CONTROL].val |= + ((decoder->pdata->hs_polarity << 2) | + (decoder->pdata->vs_polarity << 3)); + /* Set default standard to auto */ + decoder->tvp514x_regs[REG_VIDEO_STD].val = + VIDEO_STD_AUTO_SWITCH_BIT; + + /* Register with V4L2 layer as slave device */ + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &tvp514x_ops); + + v4l2_ctrl_handler_init(&decoder->hdl, 5); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_HUE, -180, 180, 180, 0); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + int err = decoder->hdl.error; + + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(decoder); + return err; + } + v4l2_ctrl_handler_setup(&decoder->hdl); + + v4l2_info(sd, "%s decoder driver registered !!\n", sd->name); + + return 0; + +} + +/** + * tvp514x_remove() - decoder driver i2c remove handler + * @client: i2c driver client device structure + * + * Unregister decoder as an i2c client device and V4L2 + * device. Complement of tvp514x_probe(). + */ +static int tvp514x_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tvp514x_decoder *decoder = to_decoder(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(decoder); + return 0; +} +/* TVP5146 Init/Power on Sequence */ +static const struct tvp514x_reg tvp5146_init_reg_seq[] = { + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x02}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0x80}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x60}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xB0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x00}, + {TOK_WRITE, REG_OPERATION_MODE, 0x01}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, + {TOK_TERM, 0, 0}, +}; + +/* TVP5147 Init/Power on Sequence */ +static const struct tvp514x_reg tvp5147_init_reg_seq[] = { + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x02}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0x80}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x60}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xB0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x16}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xA0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x16}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x60}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xB0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x00}, + {TOK_WRITE, REG_OPERATION_MODE, 0x01}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, + {TOK_TERM, 0, 0}, +}; + +/* TVP5146M2/TVP5147M1 Init/Power on Sequence */ +static const struct tvp514x_reg tvp514xm_init_reg_seq[] = { + {TOK_WRITE, REG_OPERATION_MODE, 0x01}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, + {TOK_TERM, 0, 0}, +}; + +/** + * I2C Device Table - + * + * name - Name of the actual device/chip. + * driver_data - Driver data + */ +static const struct i2c_device_id tvp514x_id[] = { + {"tvp5146", (unsigned long)tvp5146_init_reg_seq}, + {"tvp5146m2", (unsigned long)tvp514xm_init_reg_seq}, + {"tvp5147", (unsigned long)tvp5147_init_reg_seq}, + {"tvp5147m1", (unsigned long)tvp514xm_init_reg_seq}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, tvp514x_id); + +static struct i2c_driver tvp514x_driver = { + .driver = { + .owner = THIS_MODULE, + .name = TVP514X_MODULE_NAME, + }, + .probe = tvp514x_probe, + .remove = tvp514x_remove, + .id_table = tvp514x_id, +}; + +module_i2c_driver(tvp514x_driver); diff --git a/drivers/media/i2c/tvp514x_regs.h b/drivers/media/i2c/tvp514x_regs.h new file mode 100644 index 000000000000..d23aa2fbb9b1 --- /dev/null +++ b/drivers/media/i2c/tvp514x_regs.h @@ -0,0 +1,287 @@ +/* + * drivers/media/i2c/tvp514x_regs.h + * + * Copyright (C) 2008 Texas Instruments Inc + * Author: Vaibhav Hiremath + * + * Contributors: + * Sivaraj R + * Brijesh R Jadav + * Hardik Shah + * Manjunath Hadli + * Karicheri Muralidharan + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _TVP514X_REGS_H +#define _TVP514X_REGS_H + +/* + * TVP5146/47 registers + */ +#define REG_INPUT_SEL (0x00) +#define REG_AFE_GAIN_CTRL (0x01) +#define REG_VIDEO_STD (0x02) +#define REG_OPERATION_MODE (0x03) +#define REG_AUTOSWITCH_MASK (0x04) + +#define REG_COLOR_KILLER (0x05) +#define REG_LUMA_CONTROL1 (0x06) +#define REG_LUMA_CONTROL2 (0x07) +#define REG_LUMA_CONTROL3 (0x08) + +#define REG_BRIGHTNESS (0x09) +#define REG_CONTRAST (0x0A) +#define REG_SATURATION (0x0B) +#define REG_HUE (0x0C) + +#define REG_CHROMA_CONTROL1 (0x0D) +#define REG_CHROMA_CONTROL2 (0x0E) + +/* 0x0F Reserved */ + +#define REG_COMP_PR_SATURATION (0x10) +#define REG_COMP_Y_CONTRAST (0x11) +#define REG_COMP_PB_SATURATION (0x12) + +/* 0x13 Reserved */ + +#define REG_COMP_Y_BRIGHTNESS (0x14) + +/* 0x15 Reserved */ + +#define REG_AVID_START_PIXEL_LSB (0x16) +#define REG_AVID_START_PIXEL_MSB (0x17) +#define REG_AVID_STOP_PIXEL_LSB (0x18) +#define REG_AVID_STOP_PIXEL_MSB (0x19) + +#define REG_HSYNC_START_PIXEL_LSB (0x1A) +#define REG_HSYNC_START_PIXEL_MSB (0x1B) +#define REG_HSYNC_STOP_PIXEL_LSB (0x1C) +#define REG_HSYNC_STOP_PIXEL_MSB (0x1D) + +#define REG_VSYNC_START_LINE_LSB (0x1E) +#define REG_VSYNC_START_LINE_MSB (0x1F) +#define REG_VSYNC_STOP_LINE_LSB (0x20) +#define REG_VSYNC_STOP_LINE_MSB (0x21) + +#define REG_VBLK_START_LINE_LSB (0x22) +#define REG_VBLK_START_LINE_MSB (0x23) +#define REG_VBLK_STOP_LINE_LSB (0x24) +#define REG_VBLK_STOP_LINE_MSB (0x25) + +/* 0x26 - 0x27 Reserved */ + +#define REG_FAST_SWTICH_CONTROL (0x28) + +/* 0x29 Reserved */ + +#define REG_FAST_SWTICH_SCART_DELAY (0x2A) + +/* 0x2B Reserved */ + +#define REG_SCART_DELAY (0x2C) +#define REG_CTI_DELAY (0x2D) +#define REG_CTI_CONTROL (0x2E) + +/* 0x2F - 0x31 Reserved */ + +#define REG_SYNC_CONTROL (0x32) +#define REG_OUTPUT_FORMATTER1 (0x33) +#define REG_OUTPUT_FORMATTER2 (0x34) +#define REG_OUTPUT_FORMATTER3 (0x35) +#define REG_OUTPUT_FORMATTER4 (0x36) +#define REG_OUTPUT_FORMATTER5 (0x37) +#define REG_OUTPUT_FORMATTER6 (0x38) +#define REG_CLEAR_LOST_LOCK (0x39) + +#define REG_STATUS1 (0x3A) +#define REG_STATUS2 (0x3B) + +#define REG_AGC_GAIN_STATUS_LSB (0x3C) +#define REG_AGC_GAIN_STATUS_MSB (0x3D) + +/* 0x3E Reserved */ + +#define REG_VIDEO_STD_STATUS (0x3F) +#define REG_GPIO_INPUT1 (0x40) +#define REG_GPIO_INPUT2 (0x41) + +/* 0x42 - 0x45 Reserved */ + +#define REG_AFE_COARSE_GAIN_CH1 (0x46) +#define REG_AFE_COARSE_GAIN_CH2 (0x47) +#define REG_AFE_COARSE_GAIN_CH3 (0x48) +#define REG_AFE_COARSE_GAIN_CH4 (0x49) + +#define REG_AFE_FINE_GAIN_PB_B_LSB (0x4A) +#define REG_AFE_FINE_GAIN_PB_B_MSB (0x4B) +#define REG_AFE_FINE_GAIN_Y_G_CHROMA_LSB (0x4C) +#define REG_AFE_FINE_GAIN_Y_G_CHROMA_MSB (0x4D) +#define REG_AFE_FINE_GAIN_PR_R_LSB (0x4E) +#define REG_AFE_FINE_GAIN_PR_R_MSB (0x4F) +#define REG_AFE_FINE_GAIN_CVBS_LUMA_LSB (0x50) +#define REG_AFE_FINE_GAIN_CVBS_LUMA_MSB (0x51) + +/* 0x52 - 0x68 Reserved */ + +#define REG_FBIT_VBIT_CONTROL1 (0x69) + +/* 0x6A - 0x6B Reserved */ + +#define REG_BACKEND_AGC_CONTROL (0x6C) + +/* 0x6D - 0x6E Reserved */ + +#define REG_AGC_DECREMENT_SPEED_CONTROL (0x6F) +#define REG_ROM_VERSION (0x70) + +/* 0x71 - 0x73 Reserved */ + +#define REG_AGC_WHITE_PEAK_PROCESSING (0x74) +#define REG_FBIT_VBIT_CONTROL2 (0x75) +#define REG_VCR_TRICK_MODE_CONTROL (0x76) +#define REG_HORIZONTAL_SHAKE_INCREMENT (0x77) +#define REG_AGC_INCREMENT_SPEED (0x78) +#define REG_AGC_INCREMENT_DELAY (0x79) + +/* 0x7A - 0x7F Reserved */ + +#define REG_CHIP_ID_MSB (0x80) +#define REG_CHIP_ID_LSB (0x81) + +/* 0x82 Reserved */ + +#define REG_CPLL_SPEED_CONTROL (0x83) + +/* 0x84 - 0x96 Reserved */ + +#define REG_STATUS_REQUEST (0x97) + +/* 0x98 - 0x99 Reserved */ + +#define REG_VERTICAL_LINE_COUNT_LSB (0x9A) +#define REG_VERTICAL_LINE_COUNT_MSB (0x9B) + +/* 0x9C - 0x9D Reserved */ + +#define REG_AGC_DECREMENT_DELAY (0x9E) + +/* 0x9F - 0xB0 Reserved */ + +#define REG_VDP_TTX_FILTER_1_MASK1 (0xB1) +#define REG_VDP_TTX_FILTER_1_MASK2 (0xB2) +#define REG_VDP_TTX_FILTER_1_MASK3 (0xB3) +#define REG_VDP_TTX_FILTER_1_MASK4 (0xB4) +#define REG_VDP_TTX_FILTER_1_MASK5 (0xB5) +#define REG_VDP_TTX_FILTER_2_MASK1 (0xB6) +#define REG_VDP_TTX_FILTER_2_MASK2 (0xB7) +#define REG_VDP_TTX_FILTER_2_MASK3 (0xB8) +#define REG_VDP_TTX_FILTER_2_MASK4 (0xB9) +#define REG_VDP_TTX_FILTER_2_MASK5 (0xBA) +#define REG_VDP_TTX_FILTER_CONTROL (0xBB) +#define REG_VDP_FIFO_WORD_COUNT (0xBC) +#define REG_VDP_FIFO_INTERRUPT_THRLD (0xBD) + +/* 0xBE Reserved */ + +#define REG_VDP_FIFO_RESET (0xBF) +#define REG_VDP_FIFO_OUTPUT_CONTROL (0xC0) +#define REG_VDP_LINE_NUMBER_INTERRUPT (0xC1) +#define REG_VDP_PIXEL_ALIGNMENT_LSB (0xC2) +#define REG_VDP_PIXEL_ALIGNMENT_MSB (0xC3) + +/* 0xC4 - 0xD5 Reserved */ + +#define REG_VDP_LINE_START (0xD6) +#define REG_VDP_LINE_STOP (0xD7) +#define REG_VDP_GLOBAL_LINE_MODE (0xD8) +#define REG_VDP_FULL_FIELD_ENABLE (0xD9) +#define REG_VDP_FULL_FIELD_MODE (0xDA) + +/* 0xDB - 0xDF Reserved */ + +#define REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR (0xE0) +#define REG_VBUS_DATA_ACCESS_VBUS_ADDR_INCR (0xE1) +#define REG_FIFO_READ_DATA (0xE2) + +/* 0xE3 - 0xE7 Reserved */ + +#define REG_VBUS_ADDRESS_ACCESS1 (0xE8) +#define REG_VBUS_ADDRESS_ACCESS2 (0xE9) +#define REG_VBUS_ADDRESS_ACCESS3 (0xEA) + +/* 0xEB - 0xEF Reserved */ + +#define REG_INTERRUPT_RAW_STATUS0 (0xF0) +#define REG_INTERRUPT_RAW_STATUS1 (0xF1) +#define REG_INTERRUPT_STATUS0 (0xF2) +#define REG_INTERRUPT_STATUS1 (0xF3) +#define REG_INTERRUPT_MASK0 (0xF4) +#define REG_INTERRUPT_MASK1 (0xF5) +#define REG_INTERRUPT_CLEAR0 (0xF6) +#define REG_INTERRUPT_CLEAR1 (0xF7) + +/* 0xF8 - 0xFF Reserved */ + +/* + * Mask and bit definitions of TVP5146/47 registers + */ +/* The ID values we are looking for */ +#define TVP514X_CHIP_ID_MSB (0x51) +#define TVP5146_CHIP_ID_LSB (0x46) +#define TVP5147_CHIP_ID_LSB (0x47) + +#define VIDEO_STD_MASK (0x07) +#define VIDEO_STD_AUTO_SWITCH_BIT (0x00) +#define VIDEO_STD_NTSC_MJ_BIT (0x01) +#define VIDEO_STD_PAL_BDGHIN_BIT (0x02) +#define VIDEO_STD_PAL_M_BIT (0x03) +#define VIDEO_STD_PAL_COMBINATION_N_BIT (0x04) +#define VIDEO_STD_NTSC_4_43_BIT (0x05) +#define VIDEO_STD_SECAM_BIT (0x06) +#define VIDEO_STD_PAL_60_BIT (0x07) + +/* + * Status bit + */ +#define STATUS_TV_VCR_BIT (1<<0) +#define STATUS_HORZ_SYNC_LOCK_BIT (1<<1) +#define STATUS_VIRT_SYNC_LOCK_BIT (1<<2) +#define STATUS_CLR_SUBCAR_LOCK_BIT (1<<3) +#define STATUS_LOST_LOCK_DETECT_BIT (1<<4) +#define STATUS_FEILD_RATE_BIT (1<<5) +#define STATUS_LINE_ALTERNATING_BIT (1<<6) +#define STATUS_PEAK_WHITE_DETECT_BIT (1<<7) + +/* Tokens for register write */ +#define TOK_WRITE (0) /* token for write operation */ +#define TOK_TERM (1) /* terminating token */ +#define TOK_DELAY (2) /* delay token for reg list */ +#define TOK_SKIP (3) /* token to skip a register */ +/** + * struct tvp514x_reg - Structure for TVP5146/47 register initialization values + * @token - Token: TOK_WRITE, TOK_TERM etc.. + * @reg - Register offset + * @val - Register Value for TOK_WRITE or delay in ms for TOK_DELAY + */ +struct tvp514x_reg { + u8 token; + u8 reg; + u32 val; +}; + +#endif /* ifndef _TVP514X_REGS_H */ diff --git a/drivers/media/i2c/tvp5150.c b/drivers/media/i2c/tvp5150.c new file mode 100644 index 000000000000..31104a960652 --- /dev/null +++ b/drivers/media/i2c/tvp5150.c @@ -0,0 +1,1274 @@ +/* + * tvp5150 - Texas Instruments TVP5150A/AM1 video decoder driver + * + * Copyright (c) 2005,2006 Mauro Carvalho Chehab (mchehab@infradead.org) + * This code is placed under the terms of the GNU General Public License v2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tvp5150_reg.h" + +#define TVP5150_H_MAX 720 +#define TVP5150_V_MAX_525_60 480 +#define TVP5150_V_MAX_OTHERS 576 +#define TVP5150_MAX_CROP_LEFT 511 +#define TVP5150_MAX_CROP_TOP 127 +#define TVP5150_CROP_SHIFT 2 + +MODULE_DESCRIPTION("Texas Instruments TVP5150A video decoder driver"); +MODULE_AUTHOR("Mauro Carvalho Chehab"); +MODULE_LICENSE("GPL"); + + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +struct tvp5150 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct v4l2_rect rect; + + v4l2_std_id norm; /* Current set standard */ + u32 input; + u32 output; + int enable; +}; + +static inline struct tvp5150 *to_tvp5150(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tvp5150, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tvp5150, hdl)->sd; +} + +static int tvp5150_read(struct v4l2_subdev *sd, unsigned char addr) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + unsigned char buffer[1]; + int rc; + + buffer[0] = addr; + + rc = i2c_master_send(c, buffer, 1); + if (rc < 0) { + v4l2_err(sd, "i2c i/o error: rc == %d (should be 1)\n", rc); + return rc; + } + + msleep(10); + + rc = i2c_master_recv(c, buffer, 1); + if (rc < 0) { + v4l2_err(sd, "i2c i/o error: rc == %d (should be 1)\n", rc); + return rc; + } + + v4l2_dbg(2, debug, sd, "tvp5150: read 0x%02x = 0x%02x\n", addr, buffer[0]); + + return (buffer[0]); +} + +static inline void tvp5150_write(struct v4l2_subdev *sd, unsigned char addr, + unsigned char value) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + unsigned char buffer[2]; + int rc; + + buffer[0] = addr; + buffer[1] = value; + v4l2_dbg(2, debug, sd, "tvp5150: writing 0x%02x 0x%02x\n", buffer[0], buffer[1]); + if (2 != (rc = i2c_master_send(c, buffer, 2))) + v4l2_dbg(0, debug, sd, "i2c i/o error: rc == %d (should be 2)\n", rc); +} + +static void dump_reg_range(struct v4l2_subdev *sd, char *s, u8 init, + const u8 end, int max_line) +{ + int i = 0; + + while (init != (u8)(end + 1)) { + if ((i % max_line) == 0) { + if (i > 0) + printk("\n"); + printk("tvp5150: %s reg 0x%02x = ", s, init); + } + printk("%02x ", tvp5150_read(sd, init)); + + init++; + i++; + } + printk("\n"); +} + +static int tvp5150_log_status(struct v4l2_subdev *sd) +{ + printk("tvp5150: Video input source selection #1 = 0x%02x\n", + tvp5150_read(sd, TVP5150_VD_IN_SRC_SEL_1)); + printk("tvp5150: Analog channel controls = 0x%02x\n", + tvp5150_read(sd, TVP5150_ANAL_CHL_CTL)); + printk("tvp5150: Operation mode controls = 0x%02x\n", + tvp5150_read(sd, TVP5150_OP_MODE_CTL)); + printk("tvp5150: Miscellaneous controls = 0x%02x\n", + tvp5150_read(sd, TVP5150_MISC_CTL)); + printk("tvp5150: Autoswitch mask= 0x%02x\n", + tvp5150_read(sd, TVP5150_AUTOSW_MSK)); + printk("tvp5150: Color killer threshold control = 0x%02x\n", + tvp5150_read(sd, TVP5150_COLOR_KIL_THSH_CTL)); + printk("tvp5150: Luminance processing controls #1 #2 and #3 = %02x %02x %02x\n", + tvp5150_read(sd, TVP5150_LUMA_PROC_CTL_1), + tvp5150_read(sd, TVP5150_LUMA_PROC_CTL_2), + tvp5150_read(sd, TVP5150_LUMA_PROC_CTL_3)); + printk("tvp5150: Brightness control = 0x%02x\n", + tvp5150_read(sd, TVP5150_BRIGHT_CTL)); + printk("tvp5150: Color saturation control = 0x%02x\n", + tvp5150_read(sd, TVP5150_SATURATION_CTL)); + printk("tvp5150: Hue control = 0x%02x\n", + tvp5150_read(sd, TVP5150_HUE_CTL)); + printk("tvp5150: Contrast control = 0x%02x\n", + tvp5150_read(sd, TVP5150_CONTRAST_CTL)); + printk("tvp5150: Outputs and data rates select = 0x%02x\n", + tvp5150_read(sd, TVP5150_DATA_RATE_SEL)); + printk("tvp5150: Configuration shared pins = 0x%02x\n", + tvp5150_read(sd, TVP5150_CONF_SHARED_PIN)); + printk("tvp5150: Active video cropping start = 0x%02x%02x\n", + tvp5150_read(sd, TVP5150_ACT_VD_CROP_ST_MSB), + tvp5150_read(sd, TVP5150_ACT_VD_CROP_ST_LSB)); + printk("tvp5150: Active video cropping stop = 0x%02x%02x\n", + tvp5150_read(sd, TVP5150_ACT_VD_CROP_STP_MSB), + tvp5150_read(sd, TVP5150_ACT_VD_CROP_STP_LSB)); + printk("tvp5150: Genlock/RTC = 0x%02x\n", + tvp5150_read(sd, TVP5150_GENLOCK)); + printk("tvp5150: Horizontal sync start = 0x%02x\n", + tvp5150_read(sd, TVP5150_HORIZ_SYNC_START)); + printk("tvp5150: Vertical blanking start = 0x%02x\n", + tvp5150_read(sd, TVP5150_VERT_BLANKING_START)); + printk("tvp5150: Vertical blanking stop = 0x%02x\n", + tvp5150_read(sd, TVP5150_VERT_BLANKING_STOP)); + printk("tvp5150: Chrominance processing control #1 and #2 = %02x %02x\n", + tvp5150_read(sd, TVP5150_CHROMA_PROC_CTL_1), + tvp5150_read(sd, TVP5150_CHROMA_PROC_CTL_2)); + printk("tvp5150: Interrupt reset register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_RESET_REG_B)); + printk("tvp5150: Interrupt enable register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_ENABLE_REG_B)); + printk("tvp5150: Interrupt configuration register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INTT_CONFIG_REG_B)); + printk("tvp5150: Video standard = 0x%02x\n", + tvp5150_read(sd, TVP5150_VIDEO_STD)); + printk("tvp5150: Chroma gain factor: Cb=0x%02x Cr=0x%02x\n", + tvp5150_read(sd, TVP5150_CB_GAIN_FACT), + tvp5150_read(sd, TVP5150_CR_GAIN_FACTOR)); + printk("tvp5150: Macrovision on counter = 0x%02x\n", + tvp5150_read(sd, TVP5150_MACROVISION_ON_CTR)); + printk("tvp5150: Macrovision off counter = 0x%02x\n", + tvp5150_read(sd, TVP5150_MACROVISION_OFF_CTR)); + printk("tvp5150: ITU-R BT.656.%d timing(TVP5150AM1 only)\n", + (tvp5150_read(sd, TVP5150_REV_SELECT) & 1) ? 3 : 4); + printk("tvp5150: Device ID = %02x%02x\n", + tvp5150_read(sd, TVP5150_MSB_DEV_ID), + tvp5150_read(sd, TVP5150_LSB_DEV_ID)); + printk("tvp5150: ROM version = (hex) %02x.%02x\n", + tvp5150_read(sd, TVP5150_ROM_MAJOR_VER), + tvp5150_read(sd, TVP5150_ROM_MINOR_VER)); + printk("tvp5150: Vertical line count = 0x%02x%02x\n", + tvp5150_read(sd, TVP5150_VERT_LN_COUNT_MSB), + tvp5150_read(sd, TVP5150_VERT_LN_COUNT_LSB)); + printk("tvp5150: Interrupt status register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_STATUS_REG_B)); + printk("tvp5150: Interrupt active register B = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_ACTIVE_REG_B)); + printk("tvp5150: Status regs #1 to #5 = %02x %02x %02x %02x %02x\n", + tvp5150_read(sd, TVP5150_STATUS_REG_1), + tvp5150_read(sd, TVP5150_STATUS_REG_2), + tvp5150_read(sd, TVP5150_STATUS_REG_3), + tvp5150_read(sd, TVP5150_STATUS_REG_4), + tvp5150_read(sd, TVP5150_STATUS_REG_5)); + + dump_reg_range(sd, "Teletext filter 1", TVP5150_TELETEXT_FIL1_INI, + TVP5150_TELETEXT_FIL1_END, 8); + dump_reg_range(sd, "Teletext filter 2", TVP5150_TELETEXT_FIL2_INI, + TVP5150_TELETEXT_FIL2_END, 8); + + printk("tvp5150: Teletext filter enable = 0x%02x\n", + tvp5150_read(sd, TVP5150_TELETEXT_FIL_ENA)); + printk("tvp5150: Interrupt status register A = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_STATUS_REG_A)); + printk("tvp5150: Interrupt enable register A = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_ENABLE_REG_A)); + printk("tvp5150: Interrupt configuration = 0x%02x\n", + tvp5150_read(sd, TVP5150_INT_CONF)); + printk("tvp5150: VDP status register = 0x%02x\n", + tvp5150_read(sd, TVP5150_VDP_STATUS_REG)); + printk("tvp5150: FIFO word count = 0x%02x\n", + tvp5150_read(sd, TVP5150_FIFO_WORD_COUNT)); + printk("tvp5150: FIFO interrupt threshold = 0x%02x\n", + tvp5150_read(sd, TVP5150_FIFO_INT_THRESHOLD)); + printk("tvp5150: FIFO reset = 0x%02x\n", + tvp5150_read(sd, TVP5150_FIFO_RESET)); + printk("tvp5150: Line number interrupt = 0x%02x\n", + tvp5150_read(sd, TVP5150_LINE_NUMBER_INT)); + printk("tvp5150: Pixel alignment register = 0x%02x%02x\n", + tvp5150_read(sd, TVP5150_PIX_ALIGN_REG_HIGH), + tvp5150_read(sd, TVP5150_PIX_ALIGN_REG_LOW)); + printk("tvp5150: FIFO output control = 0x%02x\n", + tvp5150_read(sd, TVP5150_FIFO_OUT_CTRL)); + printk("tvp5150: Full field enable = 0x%02x\n", + tvp5150_read(sd, TVP5150_FULL_FIELD_ENA)); + printk("tvp5150: Full field mode register = 0x%02x\n", + tvp5150_read(sd, TVP5150_FULL_FIELD_MODE_REG)); + + dump_reg_range(sd, "CC data", TVP5150_CC_DATA_INI, + TVP5150_CC_DATA_END, 8); + + dump_reg_range(sd, "WSS data", TVP5150_WSS_DATA_INI, + TVP5150_WSS_DATA_END, 8); + + dump_reg_range(sd, "VPS data", TVP5150_VPS_DATA_INI, + TVP5150_VPS_DATA_END, 8); + + dump_reg_range(sd, "VITC data", TVP5150_VITC_DATA_INI, + TVP5150_VITC_DATA_END, 10); + + dump_reg_range(sd, "Line mode", TVP5150_LINE_MODE_INI, + TVP5150_LINE_MODE_END, 8); + return 0; +} + +/**************************************************************************** + Basic functions + ****************************************************************************/ + +static inline void tvp5150_selmux(struct v4l2_subdev *sd) +{ + int opmode = 0; + struct tvp5150 *decoder = to_tvp5150(sd); + int input = 0; + int val; + + if ((decoder->output & TVP5150_BLACK_SCREEN) || !decoder->enable) + input = 8; + + switch (decoder->input) { + case TVP5150_COMPOSITE1: + input |= 2; + /* fall through */ + case TVP5150_COMPOSITE0: + break; + case TVP5150_SVIDEO: + default: + input |= 1; + break; + } + + v4l2_dbg(1, debug, sd, "Selecting video route: route input=%i, output=%i " + "=> tvp5150 input=%i, opmode=%i\n", + decoder->input, decoder->output, + input, opmode); + + tvp5150_write(sd, TVP5150_OP_MODE_CTL, opmode); + tvp5150_write(sd, TVP5150_VD_IN_SRC_SEL_1, input); + + /* Svideo should enable YCrCb output and disable GPCL output + * For Composite and TV, it should be the reverse + */ + val = tvp5150_read(sd, TVP5150_MISC_CTL); + if (val < 0) { + v4l2_err(sd, "%s: failed with error = %d\n", __func__, val); + return; + } + + if (decoder->input == TVP5150_SVIDEO) + val = (val & ~0x40) | 0x10; + else + val = (val & ~0x10) | 0x40; + tvp5150_write(sd, TVP5150_MISC_CTL, val); +}; + +struct i2c_reg_value { + unsigned char reg; + unsigned char value; +}; + +/* Default values as sugested at TVP5150AM1 datasheet */ +static const struct i2c_reg_value tvp5150_init_default[] = { + { /* 0x00 */ + TVP5150_VD_IN_SRC_SEL_1,0x00 + }, + { /* 0x01 */ + TVP5150_ANAL_CHL_CTL,0x15 + }, + { /* 0x02 */ + TVP5150_OP_MODE_CTL,0x00 + }, + { /* 0x03 */ + TVP5150_MISC_CTL,0x01 + }, + { /* 0x06 */ + TVP5150_COLOR_KIL_THSH_CTL,0x10 + }, + { /* 0x07 */ + TVP5150_LUMA_PROC_CTL_1,0x60 + }, + { /* 0x08 */ + TVP5150_LUMA_PROC_CTL_2,0x00 + }, + { /* 0x09 */ + TVP5150_BRIGHT_CTL,0x80 + }, + { /* 0x0a */ + TVP5150_SATURATION_CTL,0x80 + }, + { /* 0x0b */ + TVP5150_HUE_CTL,0x00 + }, + { /* 0x0c */ + TVP5150_CONTRAST_CTL,0x80 + }, + { /* 0x0d */ + TVP5150_DATA_RATE_SEL,0x47 + }, + { /* 0x0e */ + TVP5150_LUMA_PROC_CTL_3,0x00 + }, + { /* 0x0f */ + TVP5150_CONF_SHARED_PIN,0x08 + }, + { /* 0x11 */ + TVP5150_ACT_VD_CROP_ST_MSB,0x00 + }, + { /* 0x12 */ + TVP5150_ACT_VD_CROP_ST_LSB,0x00 + }, + { /* 0x13 */ + TVP5150_ACT_VD_CROP_STP_MSB,0x00 + }, + { /* 0x14 */ + TVP5150_ACT_VD_CROP_STP_LSB,0x00 + }, + { /* 0x15 */ + TVP5150_GENLOCK,0x01 + }, + { /* 0x16 */ + TVP5150_HORIZ_SYNC_START,0x80 + }, + { /* 0x18 */ + TVP5150_VERT_BLANKING_START,0x00 + }, + { /* 0x19 */ + TVP5150_VERT_BLANKING_STOP,0x00 + }, + { /* 0x1a */ + TVP5150_CHROMA_PROC_CTL_1,0x0c + }, + { /* 0x1b */ + TVP5150_CHROMA_PROC_CTL_2,0x14 + }, + { /* 0x1c */ + TVP5150_INT_RESET_REG_B,0x00 + }, + { /* 0x1d */ + TVP5150_INT_ENABLE_REG_B,0x00 + }, + { /* 0x1e */ + TVP5150_INTT_CONFIG_REG_B,0x00 + }, + { /* 0x28 */ + TVP5150_VIDEO_STD,0x00 + }, + { /* 0x2e */ + TVP5150_MACROVISION_ON_CTR,0x0f + }, + { /* 0x2f */ + TVP5150_MACROVISION_OFF_CTR,0x01 + }, + { /* 0xbb */ + TVP5150_TELETEXT_FIL_ENA,0x00 + }, + { /* 0xc0 */ + TVP5150_INT_STATUS_REG_A,0x00 + }, + { /* 0xc1 */ + TVP5150_INT_ENABLE_REG_A,0x00 + }, + { /* 0xc2 */ + TVP5150_INT_CONF,0x04 + }, + { /* 0xc8 */ + TVP5150_FIFO_INT_THRESHOLD,0x80 + }, + { /* 0xc9 */ + TVP5150_FIFO_RESET,0x00 + }, + { /* 0xca */ + TVP5150_LINE_NUMBER_INT,0x00 + }, + { /* 0xcb */ + TVP5150_PIX_ALIGN_REG_LOW,0x4e + }, + { /* 0xcc */ + TVP5150_PIX_ALIGN_REG_HIGH,0x00 + }, + { /* 0xcd */ + TVP5150_FIFO_OUT_CTRL,0x01 + }, + { /* 0xcf */ + TVP5150_FULL_FIELD_ENA,0x00 + }, + { /* 0xd0 */ + TVP5150_LINE_MODE_INI,0x00 + }, + { /* 0xfc */ + TVP5150_FULL_FIELD_MODE_REG,0x7f + }, + { /* end of data */ + 0xff,0xff + } +}; + +/* Default values as sugested at TVP5150AM1 datasheet */ +static const struct i2c_reg_value tvp5150_init_enable[] = { + { + TVP5150_CONF_SHARED_PIN, 2 + },{ /* Automatic offset and AGC enabled */ + TVP5150_ANAL_CHL_CTL, 0x15 + },{ /* Activate YCrCb output 0x9 or 0xd ? */ + TVP5150_MISC_CTL, 0x6f + },{ /* Activates video std autodetection for all standards */ + TVP5150_AUTOSW_MSK, 0x0 + },{ /* Default format: 0x47. For 4:2:2: 0x40 */ + TVP5150_DATA_RATE_SEL, 0x47 + },{ + TVP5150_CHROMA_PROC_CTL_1, 0x0c + },{ + TVP5150_CHROMA_PROC_CTL_2, 0x54 + },{ /* Non documented, but initialized on WinTV USB2 */ + 0x27, 0x20 + },{ + 0xff,0xff + } +}; + +struct tvp5150_vbi_type { + unsigned int vbi_type; + unsigned int ini_line; + unsigned int end_line; + unsigned int by_field :1; +}; + +struct i2c_vbi_ram_value { + u16 reg; + struct tvp5150_vbi_type type; + unsigned char values[16]; +}; + +/* This struct have the values for each supported VBI Standard + * by + tvp5150_vbi_types should follow the same order as vbi_ram_default + * value 0 means rom position 0x10, value 1 means rom position 0x30 + * and so on. There are 16 possible locations from 0 to 15. + */ + +static struct i2c_vbi_ram_value vbi_ram_default[] = +{ + /* FIXME: Current api doesn't handle all VBI types, those not + yet supported are placed under #if 0 */ +#if 0 + {0x010, /* Teletext, SECAM, WST System A */ + {V4L2_SLICED_TELETEXT_SECAM,6,23,1}, + { 0xaa, 0xaa, 0xff, 0xff, 0xe7, 0x2e, 0x20, 0x26, + 0xe6, 0xb4, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, +#endif + {0x030, /* Teletext, PAL, WST System B */ + {V4L2_SLICED_TELETEXT_B,6,22,1}, + { 0xaa, 0xaa, 0xff, 0xff, 0x27, 0x2e, 0x20, 0x2b, + 0xa6, 0x72, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, +#if 0 + {0x050, /* Teletext, PAL, WST System C */ + {V4L2_SLICED_TELETEXT_PAL_C,6,22,1}, + { 0xaa, 0xaa, 0xff, 0xff, 0xe7, 0x2e, 0x20, 0x22, + 0xa6, 0x98, 0x0d, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, + {0x070, /* Teletext, NTSC, WST System B */ + {V4L2_SLICED_TELETEXT_NTSC_B,10,21,1}, + { 0xaa, 0xaa, 0xff, 0xff, 0x27, 0x2e, 0x20, 0x23, + 0x69, 0x93, 0x0d, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, + {0x090, /* Tetetext, NTSC NABTS System C */ + {V4L2_SLICED_TELETEXT_NTSC_C,10,21,1}, + { 0xaa, 0xaa, 0xff, 0xff, 0xe7, 0x2e, 0x20, 0x22, + 0x69, 0x93, 0x0d, 0x00, 0x00, 0x00, 0x15, 0x00 } + }, + {0x0b0, /* Teletext, NTSC-J, NABTS System D */ + {V4L2_SLICED_TELETEXT_NTSC_D,10,21,1}, + { 0xaa, 0xaa, 0xff, 0xff, 0xa7, 0x2e, 0x20, 0x23, + 0x69, 0x93, 0x0d, 0x00, 0x00, 0x00, 0x10, 0x00 } + }, + {0x0d0, /* Closed Caption, PAL/SECAM */ + {V4L2_SLICED_CAPTION_625,22,22,1}, + { 0xaa, 0x2a, 0xff, 0x3f, 0x04, 0x51, 0x6e, 0x02, + 0xa6, 0x7b, 0x09, 0x00, 0x00, 0x00, 0x27, 0x00 } + }, +#endif + {0x0f0, /* Closed Caption, NTSC */ + {V4L2_SLICED_CAPTION_525,21,21,1}, + { 0xaa, 0x2a, 0xff, 0x3f, 0x04, 0x51, 0x6e, 0x02, + 0x69, 0x8c, 0x09, 0x00, 0x00, 0x00, 0x27, 0x00 } + }, + {0x110, /* Wide Screen Signal, PAL/SECAM */ + {V4L2_SLICED_WSS_625,23,23,1}, + { 0x5b, 0x55, 0xc5, 0xff, 0x00, 0x71, 0x6e, 0x42, + 0xa6, 0xcd, 0x0f, 0x00, 0x00, 0x00, 0x3a, 0x00 } + }, +#if 0 + {0x130, /* Wide Screen Signal, NTSC C */ + {V4L2_SLICED_WSS_525,20,20,1}, + { 0x38, 0x00, 0x3f, 0x00, 0x00, 0x71, 0x6e, 0x43, + 0x69, 0x7c, 0x08, 0x00, 0x00, 0x00, 0x39, 0x00 } + }, + {0x150, /* Vertical Interval Timecode (VITC), PAL/SECAM */ + {V4l2_SLICED_VITC_625,6,22,0}, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x6d, 0x49, + 0xa6, 0x85, 0x08, 0x00, 0x00, 0x00, 0x4c, 0x00 } + }, + {0x170, /* Vertical Interval Timecode (VITC), NTSC */ + {V4l2_SLICED_VITC_525,10,20,0}, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x6d, 0x49, + 0x69, 0x94, 0x08, 0x00, 0x00, 0x00, 0x4c, 0x00 } + }, +#endif + {0x190, /* Video Program System (VPS), PAL */ + {V4L2_SLICED_VPS,16,16,0}, + { 0xaa, 0xaa, 0xff, 0xff, 0xba, 0xce, 0x2b, 0x0d, + 0xa6, 0xda, 0x0b, 0x00, 0x00, 0x00, 0x60, 0x00 } + }, + /* 0x1d0 User programmable */ + + /* End of struct */ + { (u16)-1 } +}; + +static int tvp5150_write_inittab(struct v4l2_subdev *sd, + const struct i2c_reg_value *regs) +{ + while (regs->reg != 0xff) { + tvp5150_write(sd, regs->reg, regs->value); + regs++; + } + return 0; +} + +static int tvp5150_vdp_init(struct v4l2_subdev *sd, + const struct i2c_vbi_ram_value *regs) +{ + unsigned int i; + + /* Disable Full Field */ + tvp5150_write(sd, TVP5150_FULL_FIELD_ENA, 0); + + /* Before programming, Line mode should be at 0xff */ + for (i = TVP5150_LINE_MODE_INI; i <= TVP5150_LINE_MODE_END; i++) + tvp5150_write(sd, i, 0xff); + + /* Load Ram Table */ + while (regs->reg != (u16)-1) { + tvp5150_write(sd, TVP5150_CONF_RAM_ADDR_HIGH, regs->reg >> 8); + tvp5150_write(sd, TVP5150_CONF_RAM_ADDR_LOW, regs->reg); + + for (i = 0; i < 16; i++) + tvp5150_write(sd, TVP5150_VDP_CONF_RAM_DATA, regs->values[i]); + + regs++; + } + return 0; +} + +/* Fills VBI capabilities based on i2c_vbi_ram_value struct */ +static int tvp5150_g_sliced_vbi_cap(struct v4l2_subdev *sd, + struct v4l2_sliced_vbi_cap *cap) +{ + const struct i2c_vbi_ram_value *regs = vbi_ram_default; + int line; + + v4l2_dbg(1, debug, sd, "g_sliced_vbi_cap\n"); + memset(cap, 0, sizeof *cap); + + while (regs->reg != (u16)-1 ) { + for (line=regs->type.ini_line;line<=regs->type.end_line;line++) { + cap->service_lines[0][line] |= regs->type.vbi_type; + } + cap->service_set |= regs->type.vbi_type; + + regs++; + } + return 0; +} + +/* Set vbi processing + * type - one of tvp5150_vbi_types + * line - line to gather data + * fields: bit 0 field1, bit 1, field2 + * flags (default=0xf0) is a bitmask, were set means: + * bit 7: enable filtering null bytes on CC + * bit 6: send data also to FIFO + * bit 5: don't allow data with errors on FIFO + * bit 4: enable ECC when possible + * pix_align = pix alignment: + * LSB = field1 + * MSB = field2 + */ +static int tvp5150_set_vbi(struct v4l2_subdev *sd, + const struct i2c_vbi_ram_value *regs, + unsigned int type,u8 flags, int line, + const int fields) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + v4l2_std_id std = decoder->norm; + u8 reg; + int pos=0; + + if (std == V4L2_STD_ALL) { + v4l2_err(sd, "VBI can't be configured without knowing number of lines\n"); + return 0; + } else if (std & V4L2_STD_625_50) { + /* Don't follow NTSC Line number convension */ + line += 3; + } + + if (line<6||line>27) + return 0; + + while (regs->reg != (u16)-1 ) { + if ((type & regs->type.vbi_type) && + (line>=regs->type.ini_line) && + (line<=regs->type.end_line)) { + type=regs->type.vbi_type; + break; + } + + regs++; + pos++; + } + if (regs->reg == (u16)-1) + return 0; + + type=pos | (flags & 0xf0); + reg=((line-6)<<1)+TVP5150_LINE_MODE_INI; + + if (fields&1) { + tvp5150_write(sd, reg, type); + } + + if (fields&2) { + tvp5150_write(sd, reg+1, type); + } + + return type; +} + +static int tvp5150_get_vbi(struct v4l2_subdev *sd, + const struct i2c_vbi_ram_value *regs, int line) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + v4l2_std_id std = decoder->norm; + u8 reg; + int pos, type = 0; + int i, ret = 0; + + if (std == V4L2_STD_ALL) { + v4l2_err(sd, "VBI can't be configured without knowing number of lines\n"); + return 0; + } else if (std & V4L2_STD_625_50) { + /* Don't follow NTSC Line number convension */ + line += 3; + } + + if (line < 6 || line > 27) + return 0; + + reg = ((line - 6) << 1) + TVP5150_LINE_MODE_INI; + + for (i = 0; i <= 1; i++) { + ret = tvp5150_read(sd, reg + i); + if (ret < 0) { + v4l2_err(sd, "%s: failed with error = %d\n", + __func__, ret); + return 0; + } + pos = ret & 0x0f; + if (pos < 0x0f) + type |= regs[pos].type.vbi_type; + } + + return type; +} + +static int tvp5150_set_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + int fmt = 0; + + decoder->norm = std; + + /* First tests should be against specific std */ + + if (std == V4L2_STD_ALL) { + fmt = VIDEO_STD_AUTO_SWITCH_BIT; /* Autodetect mode */ + } else if (std & V4L2_STD_NTSC_443) { + fmt = VIDEO_STD_NTSC_4_43_BIT; + } else if (std & V4L2_STD_PAL_M) { + fmt = VIDEO_STD_PAL_M_BIT; + } else if (std & (V4L2_STD_PAL_N | V4L2_STD_PAL_Nc)) { + fmt = VIDEO_STD_PAL_COMBINATION_N_BIT; + } else { + /* Then, test against generic ones */ + if (std & V4L2_STD_NTSC) + fmt = VIDEO_STD_NTSC_MJ_BIT; + else if (std & V4L2_STD_PAL) + fmt = VIDEO_STD_PAL_BDGHIN_BIT; + else if (std & V4L2_STD_SECAM) + fmt = VIDEO_STD_SECAM_BIT; + } + + v4l2_dbg(1, debug, sd, "Set video std register to %d.\n", fmt); + tvp5150_write(sd, TVP5150_VIDEO_STD, fmt); + return 0; +} + +static int tvp5150_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + if (decoder->norm == std) + return 0; + + /* Change cropping height limits */ + if (std & V4L2_STD_525_60) + decoder->rect.height = TVP5150_V_MAX_525_60; + else + decoder->rect.height = TVP5150_V_MAX_OTHERS; + + + return tvp5150_set_std(sd, std); +} + +static int tvp5150_reset(struct v4l2_subdev *sd, u32 val) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + /* Initializes TVP5150 to its default values */ + tvp5150_write_inittab(sd, tvp5150_init_default); + + /* Initializes VDP registers */ + tvp5150_vdp_init(sd, vbi_ram_default); + + /* Selects decoder input */ + tvp5150_selmux(sd); + + /* Initializes TVP5150 to stream enabled values */ + tvp5150_write_inittab(sd, tvp5150_init_enable); + + /* Initialize image preferences */ + v4l2_ctrl_handler_setup(&decoder->hdl); + + tvp5150_set_std(sd, decoder->norm); + return 0; +}; + +static int tvp5150_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + tvp5150_write(sd, TVP5150_BRIGHT_CTL, ctrl->val); + return 0; + case V4L2_CID_CONTRAST: + tvp5150_write(sd, TVP5150_CONTRAST_CTL, ctrl->val); + return 0; + case V4L2_CID_SATURATION: + tvp5150_write(sd, TVP5150_SATURATION_CTL, ctrl->val); + return 0; + case V4L2_CID_HUE: + tvp5150_write(sd, TVP5150_HUE_CTL, ctrl->val); + return 0; + } + return -EINVAL; +} + +static v4l2_std_id tvp5150_read_std(struct v4l2_subdev *sd) +{ + int val = tvp5150_read(sd, TVP5150_STATUS_REG_5); + + switch (val & 0x0F) { + case 0x01: + return V4L2_STD_NTSC; + case 0x03: + return V4L2_STD_PAL; + case 0x05: + return V4L2_STD_PAL_M; + case 0x07: + return V4L2_STD_PAL_N | V4L2_STD_PAL_Nc; + case 0x09: + return V4L2_STD_NTSC_443; + case 0xb: + return V4L2_STD_SECAM; + default: + return V4L2_STD_UNKNOWN; + } +} + +static int tvp5150_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned index, + enum v4l2_mbus_pixelcode *code) +{ + if (index) + return -EINVAL; + + *code = V4L2_MBUS_FMT_UYVY8_2X8; + return 0; +} + +static int tvp5150_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *f) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + if (f == NULL) + return -EINVAL; + + tvp5150_reset(sd, 0); + + f->width = decoder->rect.width; + f->height = decoder->rect.height; + + f->code = V4L2_MBUS_FMT_UYVY8_2X8; + f->field = V4L2_FIELD_SEQ_TB; + f->colorspace = V4L2_COLORSPACE_SMPTE170M; + + v4l2_dbg(1, debug, sd, "width = %d, height = %d\n", f->width, + f->height); + return 0; +} + +static int tvp5150_s_crop(struct v4l2_subdev *sd, const struct v4l2_crop *a) +{ + struct v4l2_rect rect = a->c; + struct tvp5150 *decoder = to_tvp5150(sd); + v4l2_std_id std; + int hmax; + + v4l2_dbg(1, debug, sd, "%s left=%d, top=%d, width=%d, height=%d\n", + __func__, rect.left, rect.top, rect.width, rect.height); + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* tvp5150 has some special limits */ + rect.left = clamp(rect.left, 0, TVP5150_MAX_CROP_LEFT); + rect.width = clamp(rect.width, + TVP5150_H_MAX - TVP5150_MAX_CROP_LEFT - rect.left, + TVP5150_H_MAX - rect.left); + rect.top = clamp(rect.top, 0, TVP5150_MAX_CROP_TOP); + + /* Calculate height based on current standard */ + if (decoder->norm == V4L2_STD_ALL) + std = tvp5150_read_std(sd); + else + std = decoder->norm; + + if (std & V4L2_STD_525_60) + hmax = TVP5150_V_MAX_525_60; + else + hmax = TVP5150_V_MAX_OTHERS; + + rect.height = clamp(rect.height, + hmax - TVP5150_MAX_CROP_TOP - rect.top, + hmax - rect.top); + + tvp5150_write(sd, TVP5150_VERT_BLANKING_START, rect.top); + tvp5150_write(sd, TVP5150_VERT_BLANKING_STOP, + rect.top + rect.height - hmax); + tvp5150_write(sd, TVP5150_ACT_VD_CROP_ST_MSB, + rect.left >> TVP5150_CROP_SHIFT); + tvp5150_write(sd, TVP5150_ACT_VD_CROP_ST_LSB, + rect.left | (1 << TVP5150_CROP_SHIFT)); + tvp5150_write(sd, TVP5150_ACT_VD_CROP_STP_MSB, + (rect.left + rect.width - TVP5150_MAX_CROP_LEFT) >> + TVP5150_CROP_SHIFT); + tvp5150_write(sd, TVP5150_ACT_VD_CROP_STP_LSB, + rect.left + rect.width - TVP5150_MAX_CROP_LEFT); + + decoder->rect = rect; + + return 0; +} + +static int tvp5150_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct tvp5150 *decoder = container_of(sd, struct tvp5150, sd); + + a->c = decoder->rect; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int tvp5150_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + struct tvp5150 *decoder = container_of(sd, struct tvp5150, sd); + v4l2_std_id std; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = TVP5150_H_MAX; + + /* Calculate height based on current standard */ + if (decoder->norm == V4L2_STD_ALL) + std = tvp5150_read_std(sd); + else + std = decoder->norm; + + if (std & V4L2_STD_525_60) + a->bounds.height = TVP5150_V_MAX_525_60; + else + a->bounds.height = TVP5150_V_MAX_OTHERS; + + a->defrect = a->bounds; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +/**************************************************************************** + I2C Command + ****************************************************************************/ + +static int tvp5150_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct tvp5150 *decoder = to_tvp5150(sd); + + decoder->input = input; + decoder->output = output; + tvp5150_selmux(sd); + return 0; +} + +static int tvp5150_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt) +{ + /* this is for capturing 36 raw vbi lines + if there's a way to cut off the beginning 2 vbi lines + with the tvp5150 then the vbi line count could be lowered + to 17 lines/field again, although I couldn't find a register + which could do that cropping */ + if (fmt->sample_format == V4L2_PIX_FMT_GREY) + tvp5150_write(sd, TVP5150_LUMA_PROC_CTL_1, 0x70); + if (fmt->count[0] == 18 && fmt->count[1] == 18) { + tvp5150_write(sd, TVP5150_VERT_BLANKING_START, 0x00); + tvp5150_write(sd, TVP5150_VERT_BLANKING_STOP, 0x01); + } + return 0; +} + +static int tvp5150_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + int i; + + if (svbi->service_set != 0) { + for (i = 0; i <= 23; i++) { + svbi->service_lines[1][i] = 0; + svbi->service_lines[0][i] = + tvp5150_set_vbi(sd, vbi_ram_default, + svbi->service_lines[0][i], 0xf0, i, 3); + } + /* Enables FIFO */ + tvp5150_write(sd, TVP5150_FIFO_OUT_CTRL, 1); + } else { + /* Disables FIFO*/ + tvp5150_write(sd, TVP5150_FIFO_OUT_CTRL, 0); + + /* Disable Full Field */ + tvp5150_write(sd, TVP5150_FULL_FIELD_ENA, 0); + + /* Disable Line modes */ + for (i = TVP5150_LINE_MODE_INI; i <= TVP5150_LINE_MODE_END; i++) + tvp5150_write(sd, i, 0xff); + } + return 0; +} + +static int tvp5150_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + int i, mask = 0; + + memset(svbi->service_lines, 0, sizeof(svbi->service_lines)); + + for (i = 0; i <= 23; i++) { + svbi->service_lines[0][i] = + tvp5150_get_vbi(sd, vbi_ram_default, i); + mask |= svbi->service_lines[0][i]; + } + svbi->service_set = mask; + return 0; +} + +static int tvp5150_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + int rev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + rev = tvp5150_read(sd, TVP5150_ROM_MAJOR_VER) << 8 | + tvp5150_read(sd, TVP5150_ROM_MINOR_VER); + + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_TVP5150, + rev); +} + + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int tvp5150_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + int res; + + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + res = tvp5150_read(sd, reg->reg & 0xff); + if (res < 0) { + v4l2_err(sd, "%s: failed with error = %d\n", __func__, res); + return res; + } + + reg->val = res; + reg->size = 1; + return 0; +} + +static int tvp5150_s_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + tvp5150_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static int tvp5150_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + int status = tvp5150_read(sd, 0x88); + + vt->signal = ((status & 0x04) && (status & 0x02)) ? 0xffff : 0x0; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops tvp5150_ctrl_ops = { + .s_ctrl = tvp5150_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops tvp5150_core_ops = { + .log_status = tvp5150_log_status, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, + .s_std = tvp5150_s_std, + .reset = tvp5150_reset, + .g_chip_ident = tvp5150_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = tvp5150_g_register, + .s_register = tvp5150_s_register, +#endif +}; + +static const struct v4l2_subdev_tuner_ops tvp5150_tuner_ops = { + .g_tuner = tvp5150_g_tuner, +}; + +static const struct v4l2_subdev_video_ops tvp5150_video_ops = { + .s_routing = tvp5150_s_routing, + .enum_mbus_fmt = tvp5150_enum_mbus_fmt, + .s_mbus_fmt = tvp5150_mbus_fmt, + .try_mbus_fmt = tvp5150_mbus_fmt, + .g_mbus_fmt = tvp5150_mbus_fmt, + .s_crop = tvp5150_s_crop, + .g_crop = tvp5150_g_crop, + .cropcap = tvp5150_cropcap, +}; + +static const struct v4l2_subdev_vbi_ops tvp5150_vbi_ops = { + .g_sliced_vbi_cap = tvp5150_g_sliced_vbi_cap, + .g_sliced_fmt = tvp5150_g_sliced_fmt, + .s_sliced_fmt = tvp5150_s_sliced_fmt, + .s_raw_fmt = tvp5150_s_raw_fmt, +}; + +static const struct v4l2_subdev_ops tvp5150_ops = { + .core = &tvp5150_core_ops, + .tuner = &tvp5150_tuner_ops, + .video = &tvp5150_video_ops, + .vbi = &tvp5150_vbi_ops, +}; + + +/**************************************************************************** + I2C Client & Driver + ****************************************************************************/ + +static int tvp5150_probe(struct i2c_client *c, + const struct i2c_device_id *id) +{ + struct tvp5150 *core; + struct v4l2_subdev *sd; + int tvp5150_id[4]; + int i, res; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(c->adapter, + I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -EIO; + + core = kzalloc(sizeof(struct tvp5150), GFP_KERNEL); + if (!core) { + return -ENOMEM; + } + sd = &core->sd; + v4l2_i2c_subdev_init(sd, c, &tvp5150_ops); + + /* + * Read consequent registers - TVP5150_MSB_DEV_ID, TVP5150_LSB_DEV_ID, + * TVP5150_ROM_MAJOR_VER, TVP5150_ROM_MINOR_VER + */ + for (i = 0; i < 4; i++) { + res = tvp5150_read(sd, TVP5150_MSB_DEV_ID + i); + if (res < 0) + goto free_core; + tvp5150_id[i] = res; + } + + v4l_info(c, "chip found @ 0x%02x (%s)\n", + c->addr << 1, c->adapter->name); + + if (tvp5150_id[2] == 4 && tvp5150_id[3] == 0) { /* Is TVP5150AM1 */ + v4l2_info(sd, "tvp%02x%02xam1 detected.\n", + tvp5150_id[0], tvp5150_id[1]); + + /* ITU-T BT.656.4 timing */ + tvp5150_write(sd, TVP5150_REV_SELECT, 0); + } else { + /* Is TVP5150A */ + if (tvp5150_id[2] == 3 || tvp5150_id[3] == 0x21) { + v4l2_info(sd, "tvp%02x%02xa detected.\n", + tvp5150_id[2], tvp5150_id[3]); + } else { + v4l2_info(sd, "*** unknown tvp%02x%02x chip detected.\n", + tvp5150_id[2], tvp5150_id[3]); + v4l2_info(sd, "*** Rom ver is %d.%d\n", + tvp5150_id[2], tvp5150_id[3]); + } + } + + core->norm = V4L2_STD_ALL; /* Default is autodetect */ + core->input = TVP5150_COMPOSITE1; + core->enable = 1; + + v4l2_ctrl_handler_init(&core->hdl, 4); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + sd->ctrl_handler = &core->hdl; + if (core->hdl.error) { + res = core->hdl.error; + v4l2_ctrl_handler_free(&core->hdl); + goto free_core; + } + v4l2_ctrl_handler_setup(&core->hdl); + + /* Default is no cropping */ + core->rect.top = 0; + if (tvp5150_read_std(sd) & V4L2_STD_525_60) + core->rect.height = TVP5150_V_MAX_525_60; + else + core->rect.height = TVP5150_V_MAX_OTHERS; + core->rect.left = 0; + core->rect.width = TVP5150_H_MAX; + + if (debug > 1) + tvp5150_log_status(sd); + return 0; + +free_core: + kfree(core); + return res; +} + +static int tvp5150_remove(struct i2c_client *c) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct tvp5150 *decoder = to_tvp5150(sd); + + v4l2_dbg(1, debug, sd, + "tvp5150.c: removing tvp5150 adapter on address 0x%x\n", + c->addr << 1); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(to_tvp5150(sd)); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id tvp5150_id[] = { + { "tvp5150", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tvp5150_id); + +static struct i2c_driver tvp5150_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "tvp5150", + }, + .probe = tvp5150_probe, + .remove = tvp5150_remove, + .id_table = tvp5150_id, +}; + +module_i2c_driver(tvp5150_driver); diff --git a/drivers/media/video/tvp5150_reg.h b/drivers/media/i2c/tvp5150_reg.h similarity index 100% rename from drivers/media/video/tvp5150_reg.h rename to drivers/media/i2c/tvp5150_reg.h diff --git a/drivers/media/video/tvp7002.c b/drivers/media/i2c/tvp7002.c similarity index 100% rename from drivers/media/video/tvp7002.c rename to drivers/media/i2c/tvp7002.c diff --git a/drivers/media/video/tvp7002_reg.h b/drivers/media/i2c/tvp7002_reg.h similarity index 100% rename from drivers/media/video/tvp7002_reg.h rename to drivers/media/i2c/tvp7002_reg.h diff --git a/drivers/media/video/upd64031a.c b/drivers/media/i2c/upd64031a.c similarity index 100% rename from drivers/media/video/upd64031a.c rename to drivers/media/i2c/upd64031a.c diff --git a/drivers/media/video/upd64083.c b/drivers/media/i2c/upd64083.c similarity index 100% rename from drivers/media/video/upd64083.c rename to drivers/media/i2c/upd64083.c diff --git a/drivers/media/video/vp27smpx.c b/drivers/media/i2c/vp27smpx.c similarity index 100% rename from drivers/media/video/vp27smpx.c rename to drivers/media/i2c/vp27smpx.c diff --git a/drivers/media/video/vpx3220.c b/drivers/media/i2c/vpx3220.c similarity index 100% rename from drivers/media/video/vpx3220.c rename to drivers/media/i2c/vpx3220.c diff --git a/drivers/media/video/vs6624.c b/drivers/media/i2c/vs6624.c similarity index 100% rename from drivers/media/video/vs6624.c rename to drivers/media/i2c/vs6624.c diff --git a/drivers/media/video/vs6624_regs.h b/drivers/media/i2c/vs6624_regs.h similarity index 100% rename from drivers/media/video/vs6624_regs.h rename to drivers/media/i2c/vs6624_regs.h diff --git a/drivers/media/video/wm8739.c b/drivers/media/i2c/wm8739.c similarity index 100% rename from drivers/media/video/wm8739.c rename to drivers/media/i2c/wm8739.c diff --git a/drivers/media/video/wm8775.c b/drivers/media/i2c/wm8775.c similarity index 100% rename from drivers/media/video/wm8775.c rename to drivers/media/i2c/wm8775.c diff --git a/drivers/media/media-device.c b/drivers/media/media-device.c index 6f9eb94e85b3..d01fcb7e87c2 100644 --- a/drivers/media/media-device.c +++ b/drivers/media/media-device.c @@ -59,7 +59,9 @@ static int media_device_get_info(struct media_device *dev, info.hw_revision = dev->hw_revision; info.driver_version = dev->driver_version; - return copy_to_user(__info, &info, sizeof(*__info)); + if (copy_to_user(__info, &info, sizeof(*__info))) + return -EFAULT; + return 0; } static struct media_entity *find_entity(struct media_device *mdev, u32 id) diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c index f6b52d549430..023b2a1cbb9b 100644 --- a/drivers/media/media-devnode.c +++ b/drivers/media/media-devnode.c @@ -30,6 +30,8 @@ * counting. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include #include #include @@ -215,7 +217,7 @@ int __must_check media_devnode_register(struct media_devnode *mdev) minor = find_next_zero_bit(media_devnode_nums, MEDIA_NUM_DEVICES, 0); if (minor == MEDIA_NUM_DEVICES) { mutex_unlock(&media_devnode_lock); - printk(KERN_ERR "could not get a free minor\n"); + pr_err("could not get a free minor\n"); return -ENFILE; } @@ -230,7 +232,7 @@ int __must_check media_devnode_register(struct media_devnode *mdev) ret = cdev_add(&mdev->cdev, MKDEV(MAJOR(media_dev_t), mdev->minor), 1); if (ret < 0) { - printk(KERN_ERR "%s: cdev_add failed\n", __func__); + pr_err("%s: cdev_add failed\n", __func__); goto error; } @@ -243,7 +245,7 @@ int __must_check media_devnode_register(struct media_devnode *mdev) dev_set_name(&mdev->dev, "media%d", mdev->minor); ret = device_register(&mdev->dev); if (ret < 0) { - printk(KERN_ERR "%s: device_register failed\n", __func__); + pr_err("%s: device_register failed\n", __func__); goto error; } @@ -287,18 +289,18 @@ static int __init media_devnode_init(void) { int ret; - printk(KERN_INFO "Linux media interface: v0.10\n"); + pr_info("Linux media interface: v0.10\n"); ret = alloc_chrdev_region(&media_dev_t, 0, MEDIA_NUM_DEVICES, MEDIA_NAME); if (ret < 0) { - printk(KERN_WARNING "media: unable to allocate major\n"); + pr_warn("unable to allocate major\n"); return ret; } ret = bus_register(&media_bus_type); if (ret < 0) { unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES); - printk(KERN_WARNING "media: bus_register failed\n"); + pr_warn("bus_register failed\n"); return -EIO; } diff --git a/drivers/media/mmc/Kconfig b/drivers/media/mmc/Kconfig new file mode 100644 index 000000000000..8c30ada27c79 --- /dev/null +++ b/drivers/media/mmc/Kconfig @@ -0,0 +1,2 @@ +comment "Supported MMC/SDIO adapters" +source "drivers/media/mmc/siano/Kconfig" diff --git a/drivers/media/mmc/Makefile b/drivers/media/mmc/Makefile new file mode 100644 index 000000000000..31e297a202fb --- /dev/null +++ b/drivers/media/mmc/Makefile @@ -0,0 +1 @@ +obj-y += siano/ diff --git a/drivers/media/mmc/siano/Kconfig b/drivers/media/mmc/siano/Kconfig new file mode 100644 index 000000000000..fa62475be3bf --- /dev/null +++ b/drivers/media/mmc/siano/Kconfig @@ -0,0 +1,10 @@ +# +# Siano Mobile Silicon Digital TV device configuration +# + +config SMS_SDIO_DRV + tristate "Siano SMS1xxx based MDTV via SDIO interface" + depends on DVB_CORE && RC_CORE && HAS_DMA + depends on MMC + ---help--- + Choose if you would like to have Siano's support for SDIO interface diff --git a/drivers/media/mmc/siano/Makefile b/drivers/media/mmc/siano/Makefile new file mode 100644 index 000000000000..0e01f973db6b --- /dev/null +++ b/drivers/media/mmc/siano/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_SMS_SDIO_DRV) += smssdio.o + +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += -Idrivers/media/common/siano +ccflags-y += $(extra-cflags-y) $(extra-cflags-m) + diff --git a/drivers/media/dvb/siano/smssdio.c b/drivers/media/mmc/siano/smssdio.c similarity index 100% rename from drivers/media/dvb/siano/smssdio.c rename to drivers/media/mmc/siano/smssdio.c diff --git a/drivers/media/parport/Kconfig b/drivers/media/parport/Kconfig new file mode 100644 index 000000000000..ece13dcff07d --- /dev/null +++ b/drivers/media/parport/Kconfig @@ -0,0 +1,52 @@ +menuconfig MEDIA_PARPORT_SUPPORT + bool "ISA and parallel port devices" + depends on (ISA || PARPORT) && MEDIA_CAMERA_SUPPORT + help + Enables drivers for ISA and parallel port bus. If you + need media drivers using those legacy buses, say Y. + +if MEDIA_PARPORT_SUPPORT +config VIDEO_BWQCAM + tristate "Quickcam BW Video For Linux" + depends on PARPORT && VIDEO_V4L2 + help + Say Y have if you the black and white version of the QuickCam + camera. See the next option for the color version. + + To compile this driver as a module, choose M here: the + module will be called bw-qcam. + +config VIDEO_CQCAM + tristate "QuickCam Colour Video For Linux" + depends on PARPORT && VIDEO_V4L2 + help + This is the video4linux driver for the colour version of the + Connectix QuickCam. If you have one of these cameras, say Y here, + otherwise say N. This driver does not work with the original + monochrome QuickCam, QuickCam VC or QuickClip. It is also available + as a module (c-qcam). + Read for more information. + +config VIDEO_PMS + tristate "Mediavision Pro Movie Studio Video For Linux" + depends on ISA && VIDEO_V4L2 + help + Say Y if you have the ISA Mediavision Pro Movie Studio + capture card. + + To compile this driver as a module, choose M here: the + module will be called pms. + +config VIDEO_W9966 + tristate "W9966CF Webcam (FlyCam Supra and others) Video For Linux" + depends on PARPORT_1284 && PARPORT && VIDEO_V4L2 + help + Video4linux driver for Winbond's w9966 based Webcams. + Currently tested with the LifeView FlyCam Supra. + If you have one of these cameras, say Y here + otherwise say N. + This driver is also available as a module (w9966). + + Check out for more + information. +endif diff --git a/drivers/media/parport/Makefile b/drivers/media/parport/Makefile new file mode 100644 index 000000000000..4eea06d7af5b --- /dev/null +++ b/drivers/media/parport/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_VIDEO_CQCAM) += c-qcam.o +obj-$(CONFIG_VIDEO_BWQCAM) += bw-qcam.o +obj-$(CONFIG_VIDEO_W9966) += w9966.o +obj-$(CONFIG_VIDEO_PMS) += pms.o diff --git a/drivers/media/video/bw-qcam.c b/drivers/media/parport/bw-qcam.c similarity index 100% rename from drivers/media/video/bw-qcam.c rename to drivers/media/parport/bw-qcam.c diff --git a/drivers/media/video/c-qcam.c b/drivers/media/parport/c-qcam.c similarity index 100% rename from drivers/media/video/c-qcam.c rename to drivers/media/parport/c-qcam.c diff --git a/drivers/media/video/pms.c b/drivers/media/parport/pms.c similarity index 100% rename from drivers/media/video/pms.c rename to drivers/media/parport/pms.c diff --git a/drivers/media/video/w9966.c b/drivers/media/parport/w9966.c similarity index 100% rename from drivers/media/video/w9966.c rename to drivers/media/parport/w9966.c diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig new file mode 100644 index 000000000000..d4e2ed3f27e5 --- /dev/null +++ b/drivers/media/pci/Kconfig @@ -0,0 +1,47 @@ +menuconfig MEDIA_PCI_SUPPORT + bool "Media PCI Adapters" + depends on PCI && MEDIA_SUPPORT + help + Enable media drivers for PCI/PCIe bus. + If you have such devices, say Y. + +if MEDIA_PCI_SUPPORT + +if MEDIA_CAMERA_SUPPORT + comment "Media capture support" +source "drivers/media/pci/meye/Kconfig" +source "drivers/media/pci/sta2x11/Kconfig" +endif + +if MEDIA_ANALOG_TV_SUPPORT + comment "Media capture/analog TV support" +source "drivers/media/pci/ivtv/Kconfig" +source "drivers/media/pci/zoran/Kconfig" +source "drivers/media/pci/saa7146/Kconfig" +endif + +if MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT + comment "Media capture/analog/hybrid TV support" +source "drivers/media/pci/cx18/Kconfig" +source "drivers/media/pci/cx23885/Kconfig" +source "drivers/media/pci/cx25821/Kconfig" +source "drivers/media/pci/cx88/Kconfig" +source "drivers/media/pci/bt8xx/Kconfig" +source "drivers/media/pci/saa7134/Kconfig" +source "drivers/media/pci/saa7164/Kconfig" + +endif + +if MEDIA_DIGITAL_TV_SUPPORT + comment "Media digital TV PCI Adapters" +source "drivers/media/pci/ttpci/Kconfig" +source "drivers/media/pci/b2c2/Kconfig" +source "drivers/media/pci/pluto2/Kconfig" +source "drivers/media/pci/dm1105/Kconfig" +source "drivers/media/pci/pt1/Kconfig" +source "drivers/media/pci/mantis/Kconfig" +source "drivers/media/pci/ngene/Kconfig" +source "drivers/media/pci/ddbridge/Kconfig" +endif + +endif #MEDIA_PCI_SUPPORT diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile new file mode 100644 index 000000000000..35cc57862c01 --- /dev/null +++ b/drivers/media/pci/Makefile @@ -0,0 +1,26 @@ +# +# Makefile for the kernel multimedia device drivers. +# + +obj-y += ttpci/ \ + b2c2/ \ + pluto2/ \ + dm1105/ \ + pt1/ \ + mantis/ \ + ngene/ \ + ddbridge/ \ + b2c2/ \ + saa7146/ + +obj-$(CONFIG_VIDEO_IVTV) += ivtv/ +obj-$(CONFIG_VIDEO_ZORAN) += zoran/ +obj-$(CONFIG_VIDEO_CX18) += cx18/ +obj-$(CONFIG_VIDEO_CX23885) += cx23885/ +obj-$(CONFIG_VIDEO_CX25821) += cx25821/ +obj-$(CONFIG_VIDEO_CX88) += cx88/ +obj-$(CONFIG_VIDEO_BT848) += bt8xx/ +obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ +obj-$(CONFIG_VIDEO_SAA7164) += saa7164/ +obj-$(CONFIG_VIDEO_MEYE) += meye/ +obj-$(CONFIG_STA2X11_VIP) += sta2x11/ diff --git a/drivers/media/pci/b2c2/Kconfig b/drivers/media/pci/b2c2/Kconfig new file mode 100644 index 000000000000..58761a21caa0 --- /dev/null +++ b/drivers/media/pci/b2c2/Kconfig @@ -0,0 +1,15 @@ +config DVB_B2C2_FLEXCOP_PCI + tristate "Technisat/B2C2 Air/Sky/Cable2PC PCI" + depends on DVB_CORE && I2C + help + Support for the Air/Sky/CableStar2 PCI card (DVB/ATSC) by Technisat/B2C2. + + Say Y if you own such a device and want to use it. + +config DVB_B2C2_FLEXCOP_PCI_DEBUG + bool "Enable debug for the B2C2 FlexCop drivers" + depends on DVB_B2C2_FLEXCOP_PCI + select DVB_B2C2_FLEXCOP_DEBUG + help + Say Y if you want to enable the module option to control debug messages + of all B2C2 FlexCop drivers. diff --git a/drivers/media/pci/b2c2/Makefile b/drivers/media/pci/b2c2/Makefile new file mode 100644 index 000000000000..b894320a5f21 --- /dev/null +++ b/drivers/media/pci/b2c2/Makefile @@ -0,0 +1,9 @@ +ifneq ($(CONFIG_DVB_B2C2_FLEXCOP_PCI),) +b2c2-flexcop-pci-objs += flexcop-dma.o +endif + +b2c2-flexcop-pci-objs += flexcop-pci.o +obj-$(CONFIG_DVB_B2C2_FLEXCOP_PCI) += b2c2-flexcop-pci.o + +ccflags-y += -Idrivers/media/dvb-core/ +ccflags-y += -Idrivers/media/common/b2c2/ diff --git a/drivers/media/dvb/b2c2/flexcop-dma.c b/drivers/media/pci/b2c2/flexcop-dma.c similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-dma.c rename to drivers/media/pci/b2c2/flexcop-dma.c diff --git a/drivers/media/dvb/b2c2/flexcop-pci.c b/drivers/media/pci/b2c2/flexcop-pci.c similarity index 100% rename from drivers/media/dvb/b2c2/flexcop-pci.c rename to drivers/media/pci/b2c2/flexcop-pci.c diff --git a/drivers/media/pci/bt8xx/Kconfig b/drivers/media/pci/bt8xx/Kconfig new file mode 100644 index 000000000000..61d09e010814 --- /dev/null +++ b/drivers/media/pci/bt8xx/Kconfig @@ -0,0 +1,43 @@ +config VIDEO_BT848 + tristate "BT848 Video For Linux" + depends on VIDEO_DEV && PCI && I2C && VIDEO_V4L2 + select I2C_ALGOBIT + select VIDEO_BTCX + select VIDEOBUF_DMA_SG + depends on RC_CORE + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_MSP3400 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TVAUDIO if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TDA7432 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_SAA6588 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + Support for BT848 based frame grabber/overlay boards. This includes + the Miro, Hauppauge and STB boards. Please read the material in + for more information. + + To compile this driver as a module, choose M here: the + module will be called bttv. + +config DVB_BT8XX + tristate "DVB/ATSC Support for bt878 based TV cards" + depends on DVB_CORE && PCI && I2C && VIDEO_BT848 + select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT + select DVB_SP887X if MEDIA_SUBDRV_AUTOSELECT + select DVB_NXT6000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24110 if MEDIA_SUBDRV_AUTOSELECT + select DVB_OR51211 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT + help + Support for PCI cards based on the Bt8xx PCI bridge. Examples are + the Nebula cards, the Pinnacle PCTV cards, the Twinhan DST cards, + the pcHDTV HD2000 cards, the DViCO FusionHDTV Lite cards, and + some AVerMedia cards. + + Since these cards have no MPEG decoder onboard, they transmit + only compressed MPEG data over the PCI bus, so you need + an external software decoder to watch TV on your computer. + + Say Y if you own such a device and want to use it. diff --git a/drivers/media/pci/bt8xx/Makefile b/drivers/media/pci/bt8xx/Makefile new file mode 100644 index 000000000000..5f06597c6a6e --- /dev/null +++ b/drivers/media/pci/bt8xx/Makefile @@ -0,0 +1,11 @@ +bttv-objs := bttv-driver.o bttv-cards.o bttv-if.o \ + bttv-risc.o bttv-vbi.o bttv-i2c.o bttv-gpio.o \ + bttv-input.o bttv-audio-hook.o + +obj-$(CONFIG_VIDEO_BT848) += bttv.o +obj-$(CONFIG_DVB_BT8XX) += bt878.o dvb-bt8xx.o dst.o dst_ca.o + +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += -Idrivers/media/dvb-frontends +ccflags-y += -Idrivers/media/i2c +ccflags-y += -Idrivers/media/tuners diff --git a/drivers/media/video/bt8xx/bt848.h b/drivers/media/pci/bt8xx/bt848.h similarity index 100% rename from drivers/media/video/bt8xx/bt848.h rename to drivers/media/pci/bt8xx/bt848.h diff --git a/drivers/media/dvb/bt8xx/bt878.c b/drivers/media/pci/bt8xx/bt878.c similarity index 100% rename from drivers/media/dvb/bt8xx/bt878.c rename to drivers/media/pci/bt8xx/bt878.c diff --git a/drivers/media/dvb/bt8xx/bt878.h b/drivers/media/pci/bt8xx/bt878.h similarity index 100% rename from drivers/media/dvb/bt8xx/bt878.h rename to drivers/media/pci/bt8xx/bt878.h diff --git a/drivers/media/video/bt8xx/bttv-audio-hook.c b/drivers/media/pci/bt8xx/bttv-audio-hook.c similarity index 100% rename from drivers/media/video/bt8xx/bttv-audio-hook.c rename to drivers/media/pci/bt8xx/bttv-audio-hook.c diff --git a/drivers/media/video/bt8xx/bttv-audio-hook.h b/drivers/media/pci/bt8xx/bttv-audio-hook.h similarity index 100% rename from drivers/media/video/bt8xx/bttv-audio-hook.h rename to drivers/media/pci/bt8xx/bttv-audio-hook.h diff --git a/drivers/media/video/bt8xx/bttv-cards.c b/drivers/media/pci/bt8xx/bttv-cards.c similarity index 100% rename from drivers/media/video/bt8xx/bttv-cards.c rename to drivers/media/pci/bt8xx/bttv-cards.c diff --git a/drivers/media/pci/bt8xx/bttv-driver.c b/drivers/media/pci/bt8xx/bttv-driver.c new file mode 100644 index 000000000000..b68918c97f66 --- /dev/null +++ b/drivers/media/pci/bt8xx/bttv-driver.c @@ -0,0 +1,4630 @@ +/* + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler + & Marcus Metzler + (c) 1999-2002 Gerd Knorr + + some v4l2 code lines are taken from Justin's bttv2 driver which is + (c) 2000 Justin Schoeman + + V4L1 removal from: + (c) 2005-2006 Nickolay V. Shmyrev + + Fixes to be fully V4L2 compliant by + (c) 2006 Mauro Carvalho Chehab + + Cropping and overscan support + Copyright (C) 2005, 2006 Michael H. Schimek + Sponsored by OPQ Systems AB + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bttvp.h" +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#define BTTV_VERSION "0.9.19" + +unsigned int bttv_num; /* number of Bt848s in use */ +struct bttv *bttvs[BTTV_MAX]; + +unsigned int bttv_debug; +unsigned int bttv_verbose = 1; +unsigned int bttv_gpio; + +/* config variables */ +#ifdef __BIG_ENDIAN +static unsigned int bigendian=1; +#else +static unsigned int bigendian; +#endif +static unsigned int radio[BTTV_MAX]; +static unsigned int irq_debug; +static unsigned int gbuffers = 8; +static unsigned int gbufsize = 0x208000; +static unsigned int reset_crop = 1; + +static int video_nr[BTTV_MAX] = { [0 ... (BTTV_MAX-1)] = -1 }; +static int radio_nr[BTTV_MAX] = { [0 ... (BTTV_MAX-1)] = -1 }; +static int vbi_nr[BTTV_MAX] = { [0 ... (BTTV_MAX-1)] = -1 }; +static int debug_latency; +static int disable_ir; + +static unsigned int fdsr; + +/* options */ +static unsigned int combfilter; +static unsigned int lumafilter; +static unsigned int automute = 1; +static unsigned int chroma_agc; +static unsigned int adc_crush = 1; +static unsigned int whitecrush_upper = 0xCF; +static unsigned int whitecrush_lower = 0x7F; +static unsigned int vcr_hack; +static unsigned int irq_iswitch; +static unsigned int uv_ratio = 50; +static unsigned int full_luma_range; +static unsigned int coring; + +/* API features (turn on/off stuff for testing) */ +static unsigned int v4l2 = 1; + +/* insmod args */ +module_param(bttv_verbose, int, 0644); +module_param(bttv_gpio, int, 0644); +module_param(bttv_debug, int, 0644); +module_param(irq_debug, int, 0644); +module_param(debug_latency, int, 0644); +module_param(disable_ir, int, 0444); + +module_param(fdsr, int, 0444); +module_param(gbuffers, int, 0444); +module_param(gbufsize, int, 0444); +module_param(reset_crop, int, 0444); + +module_param(v4l2, int, 0644); +module_param(bigendian, int, 0644); +module_param(irq_iswitch, int, 0644); +module_param(combfilter, int, 0444); +module_param(lumafilter, int, 0444); +module_param(automute, int, 0444); +module_param(chroma_agc, int, 0444); +module_param(adc_crush, int, 0444); +module_param(whitecrush_upper, int, 0444); +module_param(whitecrush_lower, int, 0444); +module_param(vcr_hack, int, 0444); +module_param(uv_ratio, int, 0444); +module_param(full_luma_range, int, 0444); +module_param(coring, int, 0444); + +module_param_array(radio, int, NULL, 0444); +module_param_array(video_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); + +MODULE_PARM_DESC(radio,"The TV card supports radio, default is 0 (no)"); +MODULE_PARM_DESC(bigendian,"byte order of the framebuffer, default is native endian"); +MODULE_PARM_DESC(bttv_verbose,"verbose startup messages, default is 1 (yes)"); +MODULE_PARM_DESC(bttv_gpio,"log gpio changes, default is 0 (no)"); +MODULE_PARM_DESC(bttv_debug,"debug messages, default is 0 (no)"); +MODULE_PARM_DESC(irq_debug,"irq handler debug messages, default is 0 (no)"); +MODULE_PARM_DESC(disable_ir, "disable infrared remote support"); +MODULE_PARM_DESC(gbuffers,"number of capture buffers. range 2-32, default 8"); +MODULE_PARM_DESC(gbufsize,"size of the capture buffers, default is 0x208000"); +MODULE_PARM_DESC(reset_crop,"reset cropping parameters at open(), default " + "is 1 (yes) for compatibility with older applications"); +MODULE_PARM_DESC(automute,"mute audio on bad/missing video signal, default is 1 (yes)"); +MODULE_PARM_DESC(chroma_agc,"enables the AGC of chroma signal, default is 0 (no)"); +MODULE_PARM_DESC(adc_crush,"enables the luminance ADC crush, default is 1 (yes)"); +MODULE_PARM_DESC(whitecrush_upper,"sets the white crush upper value, default is 207"); +MODULE_PARM_DESC(whitecrush_lower,"sets the white crush lower value, default is 127"); +MODULE_PARM_DESC(vcr_hack,"enables the VCR hack (improves synch on poor VCR tapes), default is 0 (no)"); +MODULE_PARM_DESC(irq_iswitch,"switch inputs in irq handler"); +MODULE_PARM_DESC(uv_ratio,"ratio between u and v gains, default is 50"); +MODULE_PARM_DESC(full_luma_range,"use the full luma range, default is 0 (no)"); +MODULE_PARM_DESC(coring,"set the luma coring level, default is 0 (no)"); +MODULE_PARM_DESC(video_nr, "video device numbers"); +MODULE_PARM_DESC(vbi_nr, "vbi device numbers"); +MODULE_PARM_DESC(radio_nr, "radio device numbers"); + +MODULE_DESCRIPTION("bttv - v4l/v4l2 driver module for bt848/878 based cards"); +MODULE_AUTHOR("Ralph Metzler & Marcus Metzler & Gerd Knorr"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(BTTV_VERSION); + +/* ----------------------------------------------------------------------- */ +/* sysfs */ + +static ssize_t show_card(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vfd = container_of(cd, struct video_device, dev); + struct bttv *btv = video_get_drvdata(vfd); + return sprintf(buf, "%d\n", btv ? btv->c.type : UNSET); +} +static DEVICE_ATTR(card, S_IRUGO, show_card, NULL); + +/* ----------------------------------------------------------------------- */ +/* dvb auto-load setup */ +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + request_module("dvb-bt8xx"); +} + +static void request_modules(struct bttv *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct bttv *dev) +{ + flush_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) +#endif /* CONFIG_MODULES */ + + +/* ----------------------------------------------------------------------- */ +/* static data */ + +/* special timing tables from conexant... */ +static u8 SRAM_Table[][60] = +{ + /* PAL digital input over GPIO[7:0] */ + { + 45, // 45 bytes following + 0x36,0x11,0x01,0x00,0x90,0x02,0x05,0x10,0x04,0x16, + 0x12,0x05,0x11,0x00,0x04,0x12,0xC0,0x00,0x31,0x00, + 0x06,0x51,0x08,0x03,0x89,0x08,0x07,0xC0,0x44,0x00, + 0x81,0x01,0x01,0xA9,0x0D,0x02,0x02,0x50,0x03,0x37, + 0x37,0x00,0xAF,0x21,0x00 + }, + /* NTSC digital input over GPIO[7:0] */ + { + 51, // 51 bytes following + 0x0C,0xC0,0x00,0x00,0x90,0x02,0x03,0x10,0x03,0x06, + 0x10,0x04,0x12,0x12,0x05,0x02,0x13,0x04,0x19,0x00, + 0x04,0x39,0x00,0x06,0x59,0x08,0x03,0x83,0x08,0x07, + 0x03,0x50,0x00,0xC0,0x40,0x00,0x86,0x01,0x01,0xA6, + 0x0D,0x02,0x03,0x11,0x01,0x05,0x37,0x00,0xAC,0x21, + 0x00, + }, + // TGB_NTSC392 // quartzsight + // This table has been modified to be used for Fusion Rev D + { + 0x2A, // size of table = 42 + 0x06, 0x08, 0x04, 0x0a, 0xc0, 0x00, 0x18, 0x08, 0x03, 0x24, + 0x08, 0x07, 0x02, 0x90, 0x02, 0x08, 0x10, 0x04, 0x0c, 0x10, + 0x05, 0x2c, 0x11, 0x04, 0x55, 0x48, 0x00, 0x05, 0x50, 0x00, + 0xbf, 0x0c, 0x02, 0x2f, 0x3d, 0x00, 0x2f, 0x3f, 0x00, 0xc3, + 0x20, 0x00 + } +}; + +/* minhdelayx1 first video pixel we can capture on a line and + hdelayx1 start of active video, both relative to rising edge of + /HRESET pulse (0H) in 1 / fCLKx1. + swidth width of active video and + totalwidth total line width, both in 1 / fCLKx1. + sqwidth total line width in square pixels. + vdelay start of active video in 2 * field lines relative to + trailing edge of /VRESET pulse (VDELAY register). + sheight height of active video in 2 * field lines. + videostart0 ITU-R frame line number of the line corresponding + to vdelay in the first field. */ +#define CROPCAP(minhdelayx1, hdelayx1, swidth, totalwidth, sqwidth, \ + vdelay, sheight, videostart0) \ + .cropcap.bounds.left = minhdelayx1, \ + /* * 2 because vertically we count field lines times two, */ \ + /* e.g. 23 * 2 to 23 * 2 + 576 in PAL-BGHI defrect. */ \ + .cropcap.bounds.top = (videostart0) * 2 - (vdelay) + MIN_VDELAY, \ + /* 4 is a safety margin at the end of the line. */ \ + .cropcap.bounds.width = (totalwidth) - (minhdelayx1) - 4, \ + .cropcap.bounds.height = (sheight) + (vdelay) - MIN_VDELAY, \ + .cropcap.defrect.left = hdelayx1, \ + .cropcap.defrect.top = (videostart0) * 2, \ + .cropcap.defrect.width = swidth, \ + .cropcap.defrect.height = sheight, \ + .cropcap.pixelaspect.numerator = totalwidth, \ + .cropcap.pixelaspect.denominator = sqwidth, + +const struct bttv_tvnorm bttv_tvnorms[] = { + /* PAL-BDGHI */ + /* max. active video is actually 922, but 924 is divisible by 4 and 3! */ + /* actually, max active PAL with HSCALE=0 is 948, NTSC is 768 - nil */ + { + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .Fsc = 35468950, + .swidth = 924, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 924, + .vdelay = 0x20, + .vbipack = 255, /* min (2048 / 4, 0x1ff) & 0xff */ + .sram = 0, + /* ITU-R frame line number of the first VBI line + we can capture, of the first and second field. + The last line is determined by cropcap.bounds. */ + .vbistart = { 7, 320 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 186, + /* Should be (768 * 1135 + 944 / 2) / 944. + cropcap.defrect is used for image width + checks, so we keep the old value 924. */ + /* swidth */ 924, + /* totalwidth */ 1135, + /* sqwidth */ 944, + /* vdelay */ 0x20, + /* sheight */ 576, + /* videostart0 */ 23) + /* bt878 (and bt848?) can capture another + line below active video. */ + .cropcap.bounds.height = (576 + 2) + 0x20 - 2, + },{ + .v4l2_id = V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR, + .name = "NTSC", + .Fsc = 28636363, + .swidth = 768, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_NTSC|BT848_IFORM_XT0), + .scaledtwidth = 910, + .hdelayx1 = 128, + .hactivex1 = 910, + .vdelay = 0x1a, + .vbipack = 144, /* min (1600 / 4, 0x1ff) & 0xff */ + .sram = 1, + .vbistart = { 10, 273 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 128, + /* Should be (640 * 910 + 780 / 2) / 780? */ + /* swidth */ 768, + /* totalwidth */ 910, + /* sqwidth */ 780, + /* vdelay */ 0x1a, + /* sheight */ 480, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_SECAM, + .name = "SECAM", + .Fsc = 35468950, + .swidth = 924, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0xb0, + .iform = (BT848_IFORM_SECAM|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 922, + .vdelay = 0x20, + .vbipack = 255, + .sram = 0, /* like PAL, correct? */ + .vbistart = { 7, 320 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 186, + /* swidth */ 924, + /* totalwidth */ 1135, + /* sqwidth */ 944, + /* vdelay */ 0x20, + /* sheight */ 576, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_PAL_Nc, + .name = "PAL-Nc", + .Fsc = 28636363, + .swidth = 640, + .sheight = 576, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_PAL_NC|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 130, + .hactivex1 = 734, + .vdelay = 0x1a, + .vbipack = 144, + .sram = -1, + .vbistart = { 7, 320 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 130, + /* swidth */ (640 * 910 + 780 / 2) / 780, + /* totalwidth */ 910, + /* sqwidth */ 780, + /* vdelay */ 0x1a, + /* sheight */ 576, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_PAL_M, + .name = "PAL-M", + .Fsc = 28636363, + .swidth = 640, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_PAL_M|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 135, + .hactivex1 = 754, + .vdelay = 0x1a, + .vbipack = 144, + .sram = -1, + .vbistart = { 10, 273 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 135, + /* swidth */ (640 * 910 + 780 / 2) / 780, + /* totalwidth */ 910, + /* sqwidth */ 780, + /* vdelay */ 0x1a, + /* sheight */ 480, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_PAL_N, + .name = "PAL-N", + .Fsc = 35468950, + .swidth = 768, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_N|BT848_IFORM_XT1), + .scaledtwidth = 944, + .hdelayx1 = 186, + .hactivex1 = 922, + .vdelay = 0x20, + .vbipack = 144, + .sram = -1, + .vbistart = { 7, 320 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 186, + /* swidth */ (768 * 1135 + 944 / 2) / 944, + /* totalwidth */ 1135, + /* sqwidth */ 944, + /* vdelay */ 0x20, + /* sheight */ 576, + /* videostart0 */ 23) + },{ + .v4l2_id = V4L2_STD_NTSC_M_JP, + .name = "NTSC-JP", + .Fsc = 28636363, + .swidth = 640, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_NTSC_J|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 135, + .hactivex1 = 754, + .vdelay = 0x16, + .vbipack = 144, + .sram = -1, + .vbistart = { 10, 273 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 135, + /* swidth */ (640 * 910 + 780 / 2) / 780, + /* totalwidth */ 910, + /* sqwidth */ 780, + /* vdelay */ 0x16, + /* sheight */ 480, + /* videostart0 */ 23) + },{ + /* that one hopefully works with the strange timing + * which video recorders produce when playing a NTSC + * tape on a PAL TV ... */ + .v4l2_id = V4L2_STD_PAL_60, + .name = "PAL-60", + .Fsc = 35468950, + .swidth = 924, + .sheight = 480, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 924, + .vdelay = 0x1a, + .vbipack = 255, + .vtotal = 524, + .sram = -1, + .vbistart = { 10, 273 }, + CROPCAP(/* minhdelayx1 */ 68, + /* hdelayx1 */ 186, + /* swidth */ 924, + /* totalwidth */ 1135, + /* sqwidth */ 944, + /* vdelay */ 0x1a, + /* sheight */ 480, + /* videostart0 */ 23) + } +}; +static const unsigned int BTTV_TVNORMS = ARRAY_SIZE(bttv_tvnorms); + +/* ----------------------------------------------------------------------- */ +/* bttv format list + packed pixel formats must come first */ +static const struct bttv_format formats[] = { + { + .name = "8 bpp, gray", + .fourcc = V4L2_PIX_FMT_GREY, + .btformat = BT848_COLOR_FMT_Y8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "8 bpp, dithered color", + .fourcc = V4L2_PIX_FMT_HI240, + .btformat = BT848_COLOR_FMT_RGB8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED | FORMAT_FLAGS_DITHER, + },{ + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .btformat = BT848_COLOR_FMT_RGB15, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .btformat = BT848_COLOR_FMT_RGB15, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .btformat = BT848_COLOR_FMT_RGB16, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .btformat = BT848_COLOR_FMT_RGB16, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .btformat = BT848_COLOR_FMT_RGB24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .btformat = BT848_COLOR_FMT_RGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .btformat = BT848_COLOR_FMT_RGB32, + .btswap = 0x0f, /* byte+word swap */ + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .btformat = BT848_COLOR_FMT_YUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .btformat = BT848_COLOR_FMT_YUY2, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV422P, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 16, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 1, + .vshift = 0, + },{ + .name = "4:2:0, planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV420, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 1, + .vshift = 1, + },{ + .name = "4:2:0, planar, Y-Cr-Cb", + .fourcc = V4L2_PIX_FMT_YVU420, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb, + .hshift = 1, + .vshift = 1, + },{ + .name = "4:1:1, planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV411P, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 2, + .vshift = 0, + },{ + .name = "4:1:0, planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV410, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 2, + .vshift = 2, + },{ + .name = "4:1:0, planar, Y-Cr-Cb", + .fourcc = V4L2_PIX_FMT_YVU410, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb, + .hshift = 2, + .vshift = 2, + },{ + .name = "raw scanlines", + .fourcc = -1, + .btformat = BT848_COLOR_FMT_RAW, + .depth = 8, + .flags = FORMAT_FLAGS_RAW, + } +}; +static const unsigned int FORMATS = ARRAY_SIZE(formats); + +/* ----------------------------------------------------------------------- */ + +#define V4L2_CID_PRIVATE_CHROMA_AGC (V4L2_CID_PRIVATE_BASE + 0) +#define V4L2_CID_PRIVATE_COMBFILTER (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_PRIVATE_LUMAFILTER (V4L2_CID_PRIVATE_BASE + 3) +#define V4L2_CID_PRIVATE_AGC_CRUSH (V4L2_CID_PRIVATE_BASE + 4) +#define V4L2_CID_PRIVATE_VCR_HACK (V4L2_CID_PRIVATE_BASE + 5) +#define V4L2_CID_PRIVATE_WHITECRUSH_UPPER (V4L2_CID_PRIVATE_BASE + 6) +#define V4L2_CID_PRIVATE_WHITECRUSH_LOWER (V4L2_CID_PRIVATE_BASE + 7) +#define V4L2_CID_PRIVATE_UV_RATIO (V4L2_CID_PRIVATE_BASE + 8) +#define V4L2_CID_PRIVATE_FULL_LUMA_RANGE (V4L2_CID_PRIVATE_BASE + 9) +#define V4L2_CID_PRIVATE_CORING (V4L2_CID_PRIVATE_BASE + 10) +#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 11) + +static const struct v4l2_queryctrl no_ctl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; +static const struct v4l2_queryctrl bttv_ctls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0, + .maximum = 65535, + .step = 256, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 65535, + .step = 128, + .default_value = 27648, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 65535, + .step = 128, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = 0, + .maximum = 65535, + .step = 256, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + /* --- audio --- */ + { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 65535, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_AUDIO_BALANCE, + .name = "Balance", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_AUDIO_BASS, + .name = "Bass", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_AUDIO_TREBLE, + .name = "Treble", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + /* --- private --- */ + { + .id = V4L2_CID_PRIVATE_CHROMA_AGC, + .name = "chroma agc", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_COMBFILTER, + .name = "combfilter", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_AUTOMUTE, + .name = "automute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_LUMAFILTER, + .name = "luma decimation filter", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_AGC_CRUSH, + .name = "agc crush", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_VCR_HACK, + .name = "vcr hack", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_WHITECRUSH_UPPER, + .name = "whitecrush upper", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 0xCF, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_WHITECRUSH_LOWER, + .name = "whitecrush lower", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 0x7F, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_UV_RATIO, + .name = "uv ratio", + .minimum = 0, + .maximum = 100, + .step = 1, + .default_value = 50, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_FULL_LUMA_RANGE, + .name = "full luma range", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_CORING, + .name = "coring", + .minimum = 0, + .maximum = 3, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + } + + + +}; + +static const struct v4l2_queryctrl *ctrl_by_id(int id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bttv_ctls); i++) + if (bttv_ctls[i].id == id) + return bttv_ctls+i; + + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* resource management */ + +/* + RESOURCE_ allocated by freed by + + VIDEO_READ bttv_read 1) bttv_read 2) + + VIDEO_STREAM VIDIOC_STREAMON VIDIOC_STREAMOFF + VIDIOC_QBUF 1) bttv_release + VIDIOCMCAPTURE 1) + + OVERLAY VIDIOCCAPTURE on VIDIOCCAPTURE off + VIDIOC_OVERLAY on VIDIOC_OVERLAY off + 3) bttv_release + + VBI VIDIOC_STREAMON VIDIOC_STREAMOFF + VIDIOC_QBUF 1) bttv_release + bttv_read, bttv_poll 1) 4) + + 1) The resource must be allocated when we enter buffer prepare functions + and remain allocated while buffers are in the DMA queue. + 2) This is a single frame read. + 3) VIDIOC_S_FBUF and VIDIOC_S_FMT (OVERLAY) still work when + RESOURCE_OVERLAY is allocated. + 4) This is a continuous read, implies VIDIOC_STREAMON. + + Note this driver permits video input and standard changes regardless if + resources are allocated. +*/ + +#define VBI_RESOURCES (RESOURCE_VBI) +#define VIDEO_RESOURCES (RESOURCE_VIDEO_READ | \ + RESOURCE_VIDEO_STREAM | \ + RESOURCE_OVERLAY) + +static +int check_alloc_btres_lock(struct bttv *btv, struct bttv_fh *fh, int bit) +{ + int xbits; /* mutual exclusive resources */ + + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + xbits = bit; + if (bit & (RESOURCE_VIDEO_READ | RESOURCE_VIDEO_STREAM)) + xbits |= RESOURCE_VIDEO_READ | RESOURCE_VIDEO_STREAM; + + /* is it free? */ + if (btv->resources & xbits) { + /* no, someone else uses it */ + goto fail; + } + + if ((bit & VIDEO_RESOURCES) + && 0 == (btv->resources & VIDEO_RESOURCES)) { + /* Do crop - use current, don't - use default parameters. */ + __s32 top = btv->crop[!!fh->do_crop].rect.top; + + if (btv->vbi_end > top) + goto fail; + + /* We cannot capture the same line as video and VBI data. + Claim scan lines crop[].rect.top to bottom. */ + btv->crop_start = top; + } else if (bit & VBI_RESOURCES) { + __s32 end = fh->vbi_fmt.end; + + if (end > btv->crop_start) + goto fail; + + /* Claim scan lines above fh->vbi_fmt.end. */ + btv->vbi_end = end; + } + + /* it's free, grab it */ + fh->resources |= bit; + btv->resources |= bit; + return 1; + + fail: + return 0; +} + +static +int check_btres(struct bttv_fh *fh, int bit) +{ + return (fh->resources & bit); +} + +static +int locked_btres(struct bttv *btv, int bit) +{ + return (btv->resources & bit); +} + +/* Call with btv->lock down. */ +static void +disclaim_vbi_lines(struct bttv *btv) +{ + btv->vbi_end = 0; +} + +/* Call with btv->lock down. */ +static void +disclaim_video_lines(struct bttv *btv) +{ + const struct bttv_tvnorm *tvnorm; + u8 crop; + + tvnorm = &bttv_tvnorms[btv->tvnorm]; + btv->crop_start = tvnorm->cropcap.bounds.top + + tvnorm->cropcap.bounds.height; + + /* VBI capturing ends at VDELAY, start of video capturing, no + matter how many lines the VBI RISC program expects. When video + capturing is off, it shall no longer "preempt" VBI capturing, + so we set VDELAY to maximum. */ + crop = btread(BT848_E_CROP) | 0xc0; + btwrite(crop, BT848_E_CROP); + btwrite(0xfe, BT848_E_VDELAY_LO); + btwrite(crop, BT848_O_CROP); + btwrite(0xfe, BT848_O_VDELAY_LO); +} + +static +void free_btres_lock(struct bttv *btv, struct bttv_fh *fh, int bits) +{ + if ((fh->resources & bits) != bits) { + /* trying to free resources not allocated by us ... */ + pr_err("BUG! (btres)\n"); + } + fh->resources &= ~bits; + btv->resources &= ~bits; + + bits = btv->resources; + + if (0 == (bits & VIDEO_RESOURCES)) + disclaim_video_lines(btv); + + if (0 == (bits & VBI_RESOURCES)) + disclaim_vbi_lines(btv); +} + +/* ----------------------------------------------------------------------- */ +/* If Bt848a or Bt849, use PLL for PAL/SECAM and crystal for NTSC */ + +/* Frequency = (F_input / PLL_X) * PLL_I.PLL_F/PLL_C + PLL_X = Reference pre-divider (0=1, 1=2) + PLL_C = Post divider (0=6, 1=4) + PLL_I = Integer input + PLL_F = Fractional input + + F_input = 28.636363 MHz: + PAL (CLKx2 = 35.46895 MHz): PLL_X = 1, PLL_I = 0x0E, PLL_F = 0xDCF9, PLL_C = 0 +*/ + +static void set_pll_freq(struct bttv *btv, unsigned int fin, unsigned int fout) +{ + unsigned char fl, fh, fi; + + /* prevent overflows */ + fin/=4; + fout/=4; + + fout*=12; + fi=fout/fin; + + fout=(fout%fin)*256; + fh=fout/fin; + + fout=(fout%fin)*256; + fl=fout/fin; + + btwrite(fl, BT848_PLL_F_LO); + btwrite(fh, BT848_PLL_F_HI); + btwrite(fi|BT848_PLL_X, BT848_PLL_XCI); +} + +static void set_pll(struct bttv *btv) +{ + int i; + + if (!btv->pll.pll_crystal) + return; + + if (btv->pll.pll_ofreq == btv->pll.pll_current) { + dprintk("%d: PLL: no change required\n", btv->c.nr); + return; + } + + if (btv->pll.pll_ifreq == btv->pll.pll_ofreq) { + /* no PLL needed */ + if (btv->pll.pll_current == 0) + return; + if (bttv_verbose) + pr_info("%d: PLL can sleep, using XTAL (%d)\n", + btv->c.nr, btv->pll.pll_ifreq); + btwrite(0x00,BT848_TGCTRL); + btwrite(0x00,BT848_PLL_XCI); + btv->pll.pll_current = 0; + return; + } + + if (bttv_verbose) + pr_info("%d: Setting PLL: %d => %d (needs up to 100ms)\n", + btv->c.nr, + btv->pll.pll_ifreq, btv->pll.pll_ofreq); + set_pll_freq(btv, btv->pll.pll_ifreq, btv->pll.pll_ofreq); + + for (i=0; i<10; i++) { + /* Let other people run while the PLL stabilizes */ + msleep(10); + + if (btread(BT848_DSTATUS) & BT848_DSTATUS_PLOCK) { + btwrite(0,BT848_DSTATUS); + } else { + btwrite(0x08,BT848_TGCTRL); + btv->pll.pll_current = btv->pll.pll_ofreq; + if (bttv_verbose) + pr_info("PLL set ok\n"); + return; + } + } + btv->pll.pll_current = -1; + if (bttv_verbose) + pr_info("Setting PLL failed\n"); + return; +} + +/* used to switch between the bt848's analog/digital video capture modes */ +static void bt848A_set_timing(struct bttv *btv) +{ + int i, len; + int table_idx = bttv_tvnorms[btv->tvnorm].sram; + int fsc = bttv_tvnorms[btv->tvnorm].Fsc; + + if (btv->input == btv->dig) { + dprintk("%d: load digital timing table (table_idx=%d)\n", + btv->c.nr,table_idx); + + /* timing change...reset timing generator address */ + btwrite(0x00, BT848_TGCTRL); + btwrite(0x02, BT848_TGCTRL); + btwrite(0x00, BT848_TGCTRL); + + len=SRAM_Table[table_idx][0]; + for(i = 1; i <= len; i++) + btwrite(SRAM_Table[table_idx][i],BT848_TGLB); + btv->pll.pll_ofreq = 27000000; + + set_pll(btv); + btwrite(0x11, BT848_TGCTRL); + btwrite(0x41, BT848_DVSIF); + } else { + btv->pll.pll_ofreq = fsc; + set_pll(btv); + btwrite(0x0, BT848_DVSIF); + } +} + +/* ----------------------------------------------------------------------- */ + +static void bt848_bright(struct bttv *btv, int bright) +{ + int value; + + // printk("set bright: %d\n", bright); // DEBUG + btv->bright = bright; + + /* We want -128 to 127 we get 0-65535 */ + value = (bright >> 8) - 128; + btwrite(value & 0xff, BT848_BRIGHT); +} + +static void bt848_hue(struct bttv *btv, int hue) +{ + int value; + + btv->hue = hue; + + /* -128 to 127 */ + value = (hue >> 8) - 128; + btwrite(value & 0xff, BT848_HUE); +} + +static void bt848_contrast(struct bttv *btv, int cont) +{ + int value,hibit; + + btv->contrast = cont; + + /* 0-511 */ + value = (cont >> 7); + hibit = (value >> 6) & 4; + btwrite(value & 0xff, BT848_CONTRAST_LO); + btaor(hibit, ~4, BT848_E_CONTROL); + btaor(hibit, ~4, BT848_O_CONTROL); +} + +static void bt848_sat(struct bttv *btv, int color) +{ + int val_u,val_v,hibits; + + btv->saturation = color; + + /* 0-511 for the color */ + val_u = ((color * btv->opt_uv_ratio) / 50) >> 7; + val_v = (((color * (100 - btv->opt_uv_ratio) / 50) >>7)*180L)/254; + hibits = (val_u >> 7) & 2; + hibits |= (val_v >> 8) & 1; + btwrite(val_u & 0xff, BT848_SAT_U_LO); + btwrite(val_v & 0xff, BT848_SAT_V_LO); + btaor(hibits, ~3, BT848_E_CONTROL); + btaor(hibits, ~3, BT848_O_CONTROL); +} + +/* ----------------------------------------------------------------------- */ + +static int +video_mux(struct bttv *btv, unsigned int input) +{ + int mux,mask2; + + if (input >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + + /* needed by RemoteVideo MX */ + mask2 = bttv_tvcards[btv->c.type].gpiomask2; + if (mask2) + gpio_inout(mask2,mask2); + + if (input == btv->svhs) { + btor(BT848_CONTROL_COMP, BT848_E_CONTROL); + btor(BT848_CONTROL_COMP, BT848_O_CONTROL); + } else { + btand(~BT848_CONTROL_COMP, BT848_E_CONTROL); + btand(~BT848_CONTROL_COMP, BT848_O_CONTROL); + } + mux = bttv_muxsel(btv, input); + btaor(mux<<5, ~(3<<5), BT848_IFORM); + dprintk("%d: video mux: input=%d mux=%d\n", btv->c.nr, input, mux); + + /* card specific hook */ + if(bttv_tvcards[btv->c.type].muxsel_hook) + bttv_tvcards[btv->c.type].muxsel_hook (btv, input); + return 0; +} + +static char *audio_modes[] = { + "audio: tuner", "audio: radio", "audio: extern", + "audio: intern", "audio: mute" +}; + +static int +audio_mux(struct bttv *btv, int input, int mute) +{ + int gpio_val, signal; + struct v4l2_control ctrl; + + gpio_inout(bttv_tvcards[btv->c.type].gpiomask, + bttv_tvcards[btv->c.type].gpiomask); + signal = btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC; + + btv->mute = mute; + btv->audio = input; + + /* automute */ + mute = mute || (btv->opt_automute && !signal && !btv->radio_user); + + if (mute) + gpio_val = bttv_tvcards[btv->c.type].gpiomute; + else + gpio_val = bttv_tvcards[btv->c.type].gpiomux[input]; + + switch (btv->c.type) { + case BTTV_BOARD_VOODOOTV_FM: + case BTTV_BOARD_VOODOOTV_200: + gpio_val = bttv_tda9880_setnorm(btv, gpio_val); + break; + + default: + gpio_bits(bttv_tvcards[btv->c.type].gpiomask, gpio_val); + } + + if (bttv_gpio) + bttv_gpio_tracking(btv, audio_modes[mute ? 4 : input]); + if (in_interrupt()) + return 0; + + ctrl.id = V4L2_CID_AUDIO_MUTE; + ctrl.value = btv->mute; + bttv_call_all(btv, core, s_ctrl, &ctrl); + if (btv->sd_msp34xx) { + u32 in; + + /* Note: the inputs tuner/radio/extern/intern are translated + to msp routings. This assumes common behavior for all msp3400 + based TV cards. When this assumption fails, then the + specific MSP routing must be added to the card table. + For now this is sufficient. */ + switch (input) { + case TVAUDIO_INPUT_RADIO: + /* Some boards need the msp do to the radio demod */ + if (btv->radio_uses_msp_demodulator) { + in = MSP_INPUT_DEFAULT; + break; + } + in = MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1, + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART); + break; + case TVAUDIO_INPUT_EXTERN: + in = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1, + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART); + break; + case TVAUDIO_INPUT_INTERN: + /* Yes, this is the same input as for RADIO. I doubt + if this is ever used. The only board with an INTERN + input is the BTTV_BOARD_AVERMEDIA98. I wonder how + that was tested. My guess is that the whole INTERN + input does not work. */ + in = MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1, + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART); + break; + case TVAUDIO_INPUT_TUNER: + default: + /* This is the only card that uses TUNER2, and afaik, + is the only difference between the VOODOOTV_FM + and VOODOOTV_200 */ + if (btv->c.type == BTTV_BOARD_VOODOOTV_200) + in = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER2, \ + MSP_DSP_IN_TUNER, MSP_DSP_IN_TUNER); + else + in = MSP_INPUT_DEFAULT; + break; + } + v4l2_subdev_call(btv->sd_msp34xx, audio, s_routing, + in, MSP_OUTPUT_DEFAULT, 0); + } + if (btv->sd_tvaudio) { + v4l2_subdev_call(btv->sd_tvaudio, audio, s_routing, + input, 0, 0); + } + return 0; +} + +static inline int +audio_mute(struct bttv *btv, int mute) +{ + return audio_mux(btv, btv->audio, mute); +} + +static inline int +audio_input(struct bttv *btv, int input) +{ + return audio_mux(btv, input, btv->mute); +} + +static void +bttv_crop_calc_limits(struct bttv_crop *c) +{ + /* Scale factor min. 1:1, max. 16:1. Min. image size + 48 x 32. Scaled width must be a multiple of 4. */ + + if (1) { + /* For bug compatibility with VIDIOCGCAP and image + size checks in earlier driver versions. */ + c->min_scaled_width = 48; + c->min_scaled_height = 32; + } else { + c->min_scaled_width = + (max(48, c->rect.width >> 4) + 3) & ~3; + c->min_scaled_height = + max(32, c->rect.height >> 4); + } + + c->max_scaled_width = c->rect.width & ~3; + c->max_scaled_height = c->rect.height; +} + +static void +bttv_crop_reset(struct bttv_crop *c, unsigned int norm) +{ + c->rect = bttv_tvnorms[norm].cropcap.defrect; + bttv_crop_calc_limits(c); +} + +/* Call with btv->lock down. */ +static int +set_tvnorm(struct bttv *btv, unsigned int norm) +{ + const struct bttv_tvnorm *tvnorm; + v4l2_std_id id; + + BUG_ON(norm >= BTTV_TVNORMS); + BUG_ON(btv->tvnorm >= BTTV_TVNORMS); + + tvnorm = &bttv_tvnorms[norm]; + + if (memcmp(&bttv_tvnorms[btv->tvnorm].cropcap, &tvnorm->cropcap, + sizeof (tvnorm->cropcap))) { + bttv_crop_reset(&btv->crop[0], norm); + btv->crop[1] = btv->crop[0]; /* current = default */ + + if (0 == (btv->resources & VIDEO_RESOURCES)) { + btv->crop_start = tvnorm->cropcap.bounds.top + + tvnorm->cropcap.bounds.height; + } + } + + btv->tvnorm = norm; + + btwrite(tvnorm->adelay, BT848_ADELAY); + btwrite(tvnorm->bdelay, BT848_BDELAY); + btaor(tvnorm->iform,~(BT848_IFORM_NORM|BT848_IFORM_XTBOTH), + BT848_IFORM); + btwrite(tvnorm->vbipack, BT848_VBI_PACK_SIZE); + btwrite(1, BT848_VBI_PACK_DEL); + bt848A_set_timing(btv); + + switch (btv->c.type) { + case BTTV_BOARD_VOODOOTV_FM: + case BTTV_BOARD_VOODOOTV_200: + bttv_tda9880_setnorm(btv, gpio_read()); + break; + } + id = tvnorm->v4l2_id; + bttv_call_all(btv, core, s_std, id); + + return 0; +} + +/* Call with btv->lock down. */ +static void +set_input(struct bttv *btv, unsigned int input, unsigned int norm) +{ + unsigned long flags; + + btv->input = input; + if (irq_iswitch) { + spin_lock_irqsave(&btv->s_lock,flags); + if (btv->curr.frame_irq) { + /* active capture -> delayed input switch */ + btv->new_input = input; + } else { + video_mux(btv,input); + } + spin_unlock_irqrestore(&btv->s_lock,flags); + } else { + video_mux(btv,input); + } + audio_input(btv, (btv->tuner_type != TUNER_ABSENT && input == 0) ? + TVAUDIO_INPUT_TUNER : TVAUDIO_INPUT_EXTERN); + set_tvnorm(btv, norm); +} + +static void init_irqreg(struct bttv *btv) +{ + /* clear status */ + btwrite(0xfffffUL, BT848_INT_STAT); + + if (bttv_tvcards[btv->c.type].no_video) { + /* i2c only */ + btwrite(BT848_INT_I2CDONE, + BT848_INT_MASK); + } else { + /* full video */ + btwrite((btv->triton1) | + (btv->gpioirq ? BT848_INT_GPINT : 0) | + BT848_INT_SCERR | + (fdsr ? BT848_INT_FDSR : 0) | + BT848_INT_RISCI | BT848_INT_OCERR | + BT848_INT_FMTCHG|BT848_INT_HLOCK| + BT848_INT_I2CDONE, + BT848_INT_MASK); + } +} + +static void init_bt848(struct bttv *btv) +{ + int val; + + if (bttv_tvcards[btv->c.type].no_video) { + /* very basic init only */ + init_irqreg(btv); + return; + } + + btwrite(0x00, BT848_CAP_CTL); + btwrite(BT848_COLOR_CTL_GAMMA, BT848_COLOR_CTL); + btwrite(BT848_IFORM_XTAUTO | BT848_IFORM_AUTO, BT848_IFORM); + + /* set planar and packed mode trigger points and */ + /* set rising edge of inverted GPINTR pin as irq trigger */ + btwrite(BT848_GPIO_DMA_CTL_PKTP_32| + BT848_GPIO_DMA_CTL_PLTP1_16| + BT848_GPIO_DMA_CTL_PLTP23_16| + BT848_GPIO_DMA_CTL_GPINTC| + BT848_GPIO_DMA_CTL_GPINTI, + BT848_GPIO_DMA_CTL); + + val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0; + btwrite(val, BT848_E_SCLOOP); + btwrite(val, BT848_O_SCLOOP); + + btwrite(0x20, BT848_E_VSCALE_HI); + btwrite(0x20, BT848_O_VSCALE_HI); + btwrite(BT848_ADC_RESERVED | (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0), + BT848_ADC); + + btwrite(whitecrush_upper, BT848_WC_UP); + btwrite(whitecrush_lower, BT848_WC_DOWN); + + if (btv->opt_lumafilter) { + btwrite(0, BT848_E_CONTROL); + btwrite(0, BT848_O_CONTROL); + } else { + btwrite(BT848_CONTROL_LDEC, BT848_E_CONTROL); + btwrite(BT848_CONTROL_LDEC, BT848_O_CONTROL); + } + + bt848_bright(btv, btv->bright); + bt848_hue(btv, btv->hue); + bt848_contrast(btv, btv->contrast); + bt848_sat(btv, btv->saturation); + + /* interrupt */ + init_irqreg(btv); +} + +static void bttv_reinit_bt848(struct bttv *btv) +{ + unsigned long flags; + + if (bttv_verbose) + pr_info("%d: reset, reinitialize\n", btv->c.nr); + spin_lock_irqsave(&btv->s_lock,flags); + btv->errors=0; + bttv_set_dma(btv,0); + spin_unlock_irqrestore(&btv->s_lock,flags); + + init_bt848(btv); + btv->pll.pll_current = -1; + set_input(btv, btv->input, btv->tvnorm); +} + +static int bttv_g_ctrl(struct file *file, void *priv, + struct v4l2_control *c) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = btv->bright; + break; + case V4L2_CID_HUE: + c->value = btv->hue; + break; + case V4L2_CID_CONTRAST: + c->value = btv->contrast; + break; + case V4L2_CID_SATURATION: + c->value = btv->saturation; + break; + + case V4L2_CID_AUDIO_MUTE: + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + bttv_call_all(btv, core, g_ctrl, c); + break; + + case V4L2_CID_PRIVATE_CHROMA_AGC: + c->value = btv->opt_chroma_agc; + break; + case V4L2_CID_PRIVATE_COMBFILTER: + c->value = btv->opt_combfilter; + break; + case V4L2_CID_PRIVATE_LUMAFILTER: + c->value = btv->opt_lumafilter; + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + c->value = btv->opt_automute; + break; + case V4L2_CID_PRIVATE_AGC_CRUSH: + c->value = btv->opt_adc_crush; + break; + case V4L2_CID_PRIVATE_VCR_HACK: + c->value = btv->opt_vcr_hack; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_UPPER: + c->value = btv->opt_whitecrush_upper; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_LOWER: + c->value = btv->opt_whitecrush_lower; + break; + case V4L2_CID_PRIVATE_UV_RATIO: + c->value = btv->opt_uv_ratio; + break; + case V4L2_CID_PRIVATE_FULL_LUMA_RANGE: + c->value = btv->opt_full_luma_range; + break; + case V4L2_CID_PRIVATE_CORING: + c->value = btv->opt_coring; + break; + default: + return -EINVAL; + } + return 0; +} + +static int bttv_s_ctrl(struct file *file, void *f, + struct v4l2_control *c) +{ + int err; + int val; + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + + err = v4l2_prio_check(&btv->prio, fh->prio); + if (0 != err) + return err; + + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + bt848_bright(btv, c->value); + break; + case V4L2_CID_HUE: + bt848_hue(btv, c->value); + break; + case V4L2_CID_CONTRAST: + bt848_contrast(btv, c->value); + break; + case V4L2_CID_SATURATION: + bt848_sat(btv, c->value); + break; + case V4L2_CID_AUDIO_MUTE: + audio_mute(btv, c->value); + /* fall through */ + case V4L2_CID_AUDIO_VOLUME: + if (btv->volume_gpio) + btv->volume_gpio(btv, c->value); + + bttv_call_all(btv, core, s_ctrl, c); + break; + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + bttv_call_all(btv, core, s_ctrl, c); + break; + + case V4L2_CID_PRIVATE_CHROMA_AGC: + btv->opt_chroma_agc = c->value; + val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0; + btwrite(val, BT848_E_SCLOOP); + btwrite(val, BT848_O_SCLOOP); + break; + case V4L2_CID_PRIVATE_COMBFILTER: + btv->opt_combfilter = c->value; + break; + case V4L2_CID_PRIVATE_LUMAFILTER: + btv->opt_lumafilter = c->value; + if (btv->opt_lumafilter) { + btand(~BT848_CONTROL_LDEC, BT848_E_CONTROL); + btand(~BT848_CONTROL_LDEC, BT848_O_CONTROL); + } else { + btor(BT848_CONTROL_LDEC, BT848_E_CONTROL); + btor(BT848_CONTROL_LDEC, BT848_O_CONTROL); + } + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + btv->opt_automute = c->value; + break; + case V4L2_CID_PRIVATE_AGC_CRUSH: + btv->opt_adc_crush = c->value; + btwrite(BT848_ADC_RESERVED | + (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0), + BT848_ADC); + break; + case V4L2_CID_PRIVATE_VCR_HACK: + btv->opt_vcr_hack = c->value; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_UPPER: + btv->opt_whitecrush_upper = c->value; + btwrite(c->value, BT848_WC_UP); + break; + case V4L2_CID_PRIVATE_WHITECRUSH_LOWER: + btv->opt_whitecrush_lower = c->value; + btwrite(c->value, BT848_WC_DOWN); + break; + case V4L2_CID_PRIVATE_UV_RATIO: + btv->opt_uv_ratio = c->value; + bt848_sat(btv, btv->saturation); + break; + case V4L2_CID_PRIVATE_FULL_LUMA_RANGE: + btv->opt_full_luma_range = c->value; + btaor((c->value<<7), ~BT848_OFORM_RANGE, BT848_OFORM); + break; + case V4L2_CID_PRIVATE_CORING: + btv->opt_coring = c->value; + btaor((c->value<<5), ~BT848_OFORM_CORE32, BT848_OFORM); + break; + default: + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +void bttv_gpio_tracking(struct bttv *btv, char *comment) +{ + unsigned int outbits, data; + outbits = btread(BT848_GPIO_OUT_EN); + data = btread(BT848_GPIO_DATA); + pr_debug("%d: gpio: en=%08x, out=%08x in=%08x [%s]\n", + btv->c.nr, outbits, data & outbits, data & ~outbits, comment); +} + +static void bttv_field_count(struct bttv *btv) +{ + int need_count = 0; + + if (btv->users) + need_count++; + + if (need_count) { + /* start field counter */ + btor(BT848_INT_VSYNC,BT848_INT_MASK); + } else { + /* stop field counter */ + btand(~BT848_INT_VSYNC,BT848_INT_MASK); + btv->field_count = 0; + } +} + +static const struct bttv_format* +format_by_fourcc(int fourcc) +{ + unsigned int i; + + for (i = 0; i < FORMATS; i++) { + if (-1 == formats[i].fourcc) + continue; + if (formats[i].fourcc == fourcc) + return formats+i; + } + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* misc helpers */ + +static int +bttv_switch_overlay(struct bttv *btv, struct bttv_fh *fh, + struct bttv_buffer *new) +{ + struct bttv_buffer *old; + unsigned long flags; + int retval = 0; + + dprintk("switch_overlay: enter [new=%p]\n", new); + if (new) + new->vb.state = VIDEOBUF_DONE; + spin_lock_irqsave(&btv->s_lock,flags); + old = btv->screen; + btv->screen = new; + btv->loop_irq |= 1; + bttv_set_dma(btv, 0x03); + spin_unlock_irqrestore(&btv->s_lock,flags); + if (NULL != old) { + dprintk("switch_overlay: old=%p state is %d\n", + old, old->vb.state); + bttv_dma_free(&fh->cap,btv, old); + kfree(old); + } + if (NULL == new) + free_btres_lock(btv,fh,RESOURCE_OVERLAY); + dprintk("switch_overlay: done\n"); + return retval; +} + +/* ----------------------------------------------------------------------- */ +/* video4linux (1) interface */ + +static int bttv_prepare_buffer(struct videobuf_queue *q,struct bttv *btv, + struct bttv_buffer *buf, + const struct bttv_format *fmt, + unsigned int width, unsigned int height, + enum v4l2_field field) +{ + struct bttv_fh *fh = q->priv_data; + int redo_dma_risc = 0; + struct bttv_crop c; + int norm; + int rc; + + /* check settings */ + if (NULL == fmt) + return -EINVAL; + if (fmt->btformat == BT848_COLOR_FMT_RAW) { + width = RAW_BPL; + height = RAW_LINES*2; + if (width*height > buf->vb.bsize) + return -EINVAL; + buf->vb.size = buf->vb.bsize; + + /* Make sure tvnorm and vbi_end remain consistent + until we're done. */ + + norm = btv->tvnorm; + + /* In this mode capturing always starts at defrect.top + (default VDELAY), ignoring cropping parameters. */ + if (btv->vbi_end > bttv_tvnorms[norm].cropcap.defrect.top) { + return -EINVAL; + } + + c.rect = bttv_tvnorms[norm].cropcap.defrect; + } else { + norm = btv->tvnorm; + c = btv->crop[!!fh->do_crop]; + + if (width < c.min_scaled_width || + width > c.max_scaled_width || + height < c.min_scaled_height) + return -EINVAL; + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_ALTERNATE: + /* btv->crop counts frame lines. Max. scale + factor is 16:1 for frames, 8:1 for fields. */ + if (height * 2 > c.max_scaled_height) + return -EINVAL; + break; + + default: + if (height > c.max_scaled_height) + return -EINVAL; + break; + } + + buf->vb.size = (width * height * fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + } + + /* alloc + fill struct bttv_buffer (if changed) */ + if (buf->vb.width != width || buf->vb.height != height || + buf->vb.field != field || + buf->tvnorm != norm || buf->fmt != fmt || + buf->crop.top != c.rect.top || + buf->crop.left != c.rect.left || + buf->crop.width != c.rect.width || + buf->crop.height != c.rect.height) { + buf->vb.width = width; + buf->vb.height = height; + buf->vb.field = field; + buf->tvnorm = norm; + buf->fmt = fmt; + buf->crop = c.rect; + redo_dma_risc = 1; + } + + /* alloc risc memory */ + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + redo_dma_risc = 1; + if (0 != (rc = videobuf_iolock(q,&buf->vb,&btv->fbuf))) + goto fail; + } + + if (redo_dma_risc) + if (0 != (rc = bttv_buffer_risc(btv,buf))) + goto fail; + + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + + fail: + bttv_dma_free(q,btv,buf); + return rc; +} + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct bttv_fh *fh = q->priv_data; + + *size = fh->fmt->depth*fh->width*fh->height >> 3; + if (0 == *count) + *count = gbuffers; + if (*size * *count > gbuffers * gbufsize) + *count = (gbuffers * gbufsize) / *size; + return 0; +} + +static int +buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); + struct bttv_fh *fh = q->priv_data; + + return bttv_prepare_buffer(q,fh->btv, buf, fh->fmt, + fh->width, fh->height, field); +} + +static void +buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); + struct bttv_fh *fh = q->priv_data; + struct bttv *btv = fh->btv; + + buf->vb.state = VIDEOBUF_QUEUED; + list_add_tail(&buf->vb.queue,&btv->capture); + if (!btv->curr.frame_irq) { + btv->loop_irq |= 1; + bttv_set_dma(btv, 0x03); + } +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); + struct bttv_fh *fh = q->priv_data; + + bttv_dma_free(q,fh->btv,buf); +} + +static struct videobuf_queue_ops bttv_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +static int bttv_s_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + unsigned int i; + int err; + + err = v4l2_prio_check(&btv->prio, fh->prio); + if (err) + goto err; + + for (i = 0; i < BTTV_TVNORMS; i++) + if (*id & bttv_tvnorms[i].v4l2_id) + break; + if (i == BTTV_TVNORMS) { + err = -EINVAL; + goto err; + } + + set_tvnorm(btv, i); + +err: + + return err; +} + +static int bttv_querystd(struct file *file, void *f, v4l2_std_id *id) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + + if (btread(BT848_DSTATUS) & BT848_DSTATUS_NUML) + *id = V4L2_STD_625_50; + else + *id = V4L2_STD_525_60; + return 0; +} + +static int bttv_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + int rc = 0; + + if (i->index >= bttv_tvcards[btv->c.type].video_inputs) { + rc = -EINVAL; + goto err; + } + + i->type = V4L2_INPUT_TYPE_CAMERA; + i->audioset = 1; + + if (btv->tuner_type != TUNER_ABSENT && i->index == 0) { + sprintf(i->name, "Television"); + i->type = V4L2_INPUT_TYPE_TUNER; + i->tuner = 0; + } else if (i->index == btv->svhs) { + sprintf(i->name, "S-Video"); + } else { + sprintf(i->name, "Composite%d", i->index); + } + + if (i->index == btv->input) { + __u32 dstatus = btread(BT848_DSTATUS); + if (0 == (dstatus & BT848_DSTATUS_PRES)) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (0 == (dstatus & BT848_DSTATUS_HLOC)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + } + + i->std = BTTV_NORMS; + +err: + + return rc; +} + +static int bttv_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + *i = btv->input; + + return 0; +} + +static int bttv_s_input(struct file *file, void *priv, unsigned int i) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + int err; + + err = v4l2_prio_check(&btv->prio, fh->prio); + if (unlikely(err)) + goto err; + + if (i > bttv_tvcards[btv->c.type].video_inputs) { + err = -EINVAL; + goto err; + } + + set_input(btv, i, btv->tvnorm); + +err: + return 0; +} + +static int bttv_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + int err; + + if (unlikely(0 != t->index)) + return -EINVAL; + + if (unlikely(btv->tuner_type == TUNER_ABSENT)) { + err = -EINVAL; + goto err; + } + + err = v4l2_prio_check(&btv->prio, fh->prio); + if (unlikely(err)) + goto err; + + bttv_call_all(btv, tuner, s_tuner, t); + + if (btv->audio_mode_gpio) + btv->audio_mode_gpio(btv, t, 1); + +err: + + return 0; +} + +static int bttv_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + f->type = btv->radio_user ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + f->frequency = btv->freq; + + return 0; +} + +static int bttv_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + int err; + + if (unlikely(f->tuner != 0)) + return -EINVAL; + + err = v4l2_prio_check(&btv->prio, fh->prio); + if (unlikely(err)) + goto err; + + if (unlikely(f->type != (btv->radio_user + ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV))) { + err = -EINVAL; + goto err; + } + btv->freq = f->frequency; + bttv_call_all(btv, tuner, s_frequency, f); + if (btv->has_matchbox && btv->radio_user) + tea5757_set_freq(btv, btv->freq); +err: + + return 0; +} + +static int bttv_log_status(struct file *file, void *f) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + + bttv_call_all(btv, core, log_status); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int bttv_g_register(struct file *file, void *f, + struct v4l2_dbg_register *reg) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + + /* bt848 has a 12-bit register space */ + reg->reg &= 0xfff; + reg->val = btread(reg->reg); + reg->size = 1; + + return 0; +} + +static int bttv_s_register(struct file *file, void *f, + struct v4l2_dbg_register *reg) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + + /* bt848 has a 12-bit register space */ + reg->reg &= 0xfff; + btwrite(reg->val, reg->reg); + + return 0; +} +#endif + +/* Given cropping boundaries b and the scaled width and height of a + single field or frame, which must not exceed hardware limits, this + function adjusts the cropping parameters c. */ +static void +bttv_crop_adjust (struct bttv_crop * c, + const struct v4l2_rect * b, + __s32 width, + __s32 height, + enum v4l2_field field) +{ + __s32 frame_height = height << !V4L2_FIELD_HAS_BOTH(field); + __s32 max_left; + __s32 max_top; + + if (width < c->min_scaled_width) { + /* Max. hor. scale factor 16:1. */ + c->rect.width = width * 16; + } else if (width > c->max_scaled_width) { + /* Min. hor. scale factor 1:1. */ + c->rect.width = width; + + max_left = b->left + b->width - width; + max_left = min(max_left, (__s32) MAX_HDELAY); + if (c->rect.left > max_left) + c->rect.left = max_left; + } + + if (height < c->min_scaled_height) { + /* Max. vert. scale factor 16:1, single fields 8:1. */ + c->rect.height = height * 16; + } else if (frame_height > c->max_scaled_height) { + /* Min. vert. scale factor 1:1. + Top and height count field lines times two. */ + c->rect.height = (frame_height + 1) & ~1; + + max_top = b->top + b->height - c->rect.height; + if (c->rect.top > max_top) + c->rect.top = max_top; + } + + bttv_crop_calc_limits(c); +} + +/* Returns an error if scaling to a frame or single field with the given + width and height is not possible with the current cropping parameters + and width aligned according to width_mask. If adjust_size is TRUE the + function may adjust the width and/or height instead, rounding width + to (width + width_bias) & width_mask. If adjust_crop is TRUE it may + also adjust the current cropping parameters to get closer to the + desired image size. */ +static int +limit_scaled_size_lock (struct bttv_fh * fh, + __s32 * width, + __s32 * height, + enum v4l2_field field, + unsigned int width_mask, + unsigned int width_bias, + int adjust_size, + int adjust_crop) +{ + struct bttv *btv = fh->btv; + const struct v4l2_rect *b; + struct bttv_crop *c; + __s32 min_width; + __s32 min_height; + __s32 max_width; + __s32 max_height; + int rc; + + BUG_ON((int) width_mask >= 0 || + width_bias >= (unsigned int) -width_mask); + + /* Make sure tvnorm, vbi_end and the current cropping parameters + remain consistent until we're done. */ + + b = &bttv_tvnorms[btv->tvnorm].cropcap.bounds; + + /* Do crop - use current, don't - use default parameters. */ + c = &btv->crop[!!fh->do_crop]; + + if (fh->do_crop + && adjust_size + && adjust_crop + && !locked_btres(btv, VIDEO_RESOURCES)) { + min_width = 48; + min_height = 32; + + /* We cannot scale up. When the scaled image is larger + than crop.rect we adjust the crop.rect as required + by the V4L2 spec, hence cropcap.bounds are our limit. */ + max_width = min(b->width, (__s32) MAX_HACTIVE); + max_height = b->height; + + /* We cannot capture the same line as video and VBI data. + Note btv->vbi_end is really a minimum, see + bttv_vbi_try_fmt(). */ + if (btv->vbi_end > b->top) { + max_height -= btv->vbi_end - b->top; + rc = -EBUSY; + if (min_height > max_height) + goto fail; + } + } else { + rc = -EBUSY; + if (btv->vbi_end > c->rect.top) + goto fail; + + min_width = c->min_scaled_width; + min_height = c->min_scaled_height; + max_width = c->max_scaled_width; + max_height = c->max_scaled_height; + + adjust_crop = 0; + } + + min_width = (min_width - width_mask - 1) & width_mask; + max_width = max_width & width_mask; + + /* Max. scale factor is 16:1 for frames, 8:1 for fields. */ + min_height = min_height; + /* Min. scale factor is 1:1. */ + max_height >>= !V4L2_FIELD_HAS_BOTH(field); + + if (adjust_size) { + *width = clamp(*width, min_width, max_width); + *height = clamp(*height, min_height, max_height); + + /* Round after clamping to avoid overflow. */ + *width = (*width + width_bias) & width_mask; + + if (adjust_crop) { + bttv_crop_adjust(c, b, *width, *height, field); + + if (btv->vbi_end > c->rect.top) { + /* Move the crop window out of the way. */ + c->rect.top = btv->vbi_end; + } + } + } else { + rc = -EINVAL; + if (*width < min_width || + *height < min_height || + *width > max_width || + *height > max_height || + 0 != (*width & ~width_mask)) + goto fail; + } + + rc = 0; /* success */ + + fail: + + return rc; +} + +/* Returns an error if the given overlay window dimensions are not + possible with the current cropping parameters. If adjust_size is + TRUE the function may adjust the window width and/or height + instead, however it always rounds the horizontal position and + width as btcx_align() does. If adjust_crop is TRUE the function + may also adjust the current cropping parameters to get closer + to the desired window size. */ +static int +verify_window_lock (struct bttv_fh * fh, + struct v4l2_window * win, + int adjust_size, + int adjust_crop) +{ + enum v4l2_field field; + unsigned int width_mask; + int rc; + + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + if (win->clipcount > 2048) + return -EINVAL; + + field = win->field; + + if (V4L2_FIELD_ANY == field) { + __s32 height2; + + height2 = fh->btv->crop[!!fh->do_crop].rect.height >> 1; + field = (win->w.height > height2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + /* 4-byte alignment. */ + if (NULL == fh->ovfmt) + return -EINVAL; + width_mask = ~0; + switch (fh->ovfmt->depth) { + case 8: + case 24: + width_mask = ~3; + break; + case 16: + width_mask = ~1; + break; + case 32: + break; + default: + BUG(); + } + + win->w.width -= win->w.left & ~width_mask; + win->w.left = (win->w.left - width_mask - 1) & width_mask; + + rc = limit_scaled_size_lock(fh, &win->w.width, &win->w.height, + field, width_mask, + /* width_bias: round down */ 0, + adjust_size, adjust_crop); + if (0 != rc) + return rc; + + win->field = field; + return 0; +} + +static int setup_window_lock(struct bttv_fh *fh, struct bttv *btv, + struct v4l2_window *win, int fixup) +{ + struct v4l2_clip *clips = NULL; + int n,size,retval = 0; + + if (NULL == fh->ovfmt) + return -EINVAL; + if (!(fh->ovfmt->flags & FORMAT_FLAGS_PACKED)) + return -EINVAL; + retval = verify_window_lock(fh, win, + /* adjust_size */ fixup, + /* adjust_crop */ fixup); + if (0 != retval) + return retval; + + /* copy clips -- luckily v4l1 + v4l2 are binary + compatible here ...*/ + n = win->clipcount; + size = sizeof(*clips)*(n+4); + clips = kmalloc(size,GFP_KERNEL); + if (NULL == clips) + return -ENOMEM; + if (n > 0) { + if (copy_from_user(clips,win->clips,sizeof(struct v4l2_clip)*n)) { + kfree(clips); + return -EFAULT; + } + } + + /* clip against screen */ + if (NULL != btv->fbuf.base) + n = btcx_screen_clips(btv->fbuf.fmt.width, btv->fbuf.fmt.height, + &win->w, clips, n); + btcx_sort_clips(clips,n); + + /* 4-byte alignments */ + switch (fh->ovfmt->depth) { + case 8: + case 24: + btcx_align(&win->w, clips, n, 3); + break; + case 16: + btcx_align(&win->w, clips, n, 1); + break; + case 32: + /* no alignment fixups needed */ + break; + default: + BUG(); + } + + kfree(fh->ov.clips); + fh->ov.clips = clips; + fh->ov.nclips = n; + + fh->ov.w = win->w; + fh->ov.field = win->field; + fh->ov.setup_ok = 1; + + btv->init.ov.w.width = win->w.width; + btv->init.ov.w.height = win->w.height; + btv->init.ov.field = win->field; + + /* update overlay if needed */ + retval = 0; + if (check_btres(fh, RESOURCE_OVERLAY)) { + struct bttv_buffer *new; + + new = videobuf_sg_alloc(sizeof(*new)); + new->crop = btv->crop[!!fh->do_crop].rect; + bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new); + retval = bttv_switch_overlay(btv,fh,new); + } + return retval; +} + +/* ----------------------------------------------------------------------- */ + +static struct videobuf_queue* bttv_queue(struct bttv_fh *fh) +{ + struct videobuf_queue* q = NULL; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + q = &fh->cap; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + q = &fh->vbi; + break; + default: + BUG(); + } + return q; +} + +static int bttv_resource(struct bttv_fh *fh) +{ + int res = 0; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + res = RESOURCE_VIDEO_STREAM; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + res = RESOURCE_VBI; + break; + default: + BUG(); + } + return res; +} + +static int bttv_switch_type(struct bttv_fh *fh, enum v4l2_buf_type type) +{ + struct videobuf_queue *q = bttv_queue(fh); + int res = bttv_resource(fh); + + if (check_btres(fh,res)) + return -EBUSY; + if (videobuf_queue_is_busy(q)) + return -EBUSY; + fh->type = type; + return 0; +} + +static void +pix_format_set_size (struct v4l2_pix_format * f, + const struct bttv_format * fmt, + unsigned int width, + unsigned int height) +{ + f->width = width; + f->height = height; + + if (fmt->flags & FORMAT_FLAGS_PLANAR) { + f->bytesperline = width; /* Y plane */ + f->sizeimage = (width * height * fmt->depth) >> 3; + } else { + f->bytesperline = (width * fmt->depth) >> 3; + f->sizeimage = height * f->bytesperline; + } +} + +static int bttv_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bttv_fh *fh = priv; + + pix_format_set_size(&f->fmt.pix, fh->fmt, + fh->width, fh->height); + f->fmt.pix.field = fh->cap.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + + return 0; +} + +static int bttv_g_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bttv_fh *fh = priv; + + f->fmt.win.w = fh->ov.w; + f->fmt.win.field = fh->ov.field; + + return 0; +} + +static int bttv_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + const struct bttv_format *fmt; + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + enum v4l2_field field; + __s32 width, height; + int rc; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + + if (V4L2_FIELD_ANY == field) { + __s32 height2; + + height2 = btv->crop[!!fh->do_crop].rect.height >> 1; + field = (f->fmt.pix.height > height2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + + if (V4L2_FIELD_SEQ_BT == field) + field = V4L2_FIELD_SEQ_TB; + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_ALTERNATE: + case V4L2_FIELD_INTERLACED: + break; + case V4L2_FIELD_SEQ_TB: + if (fmt->flags & FORMAT_FLAGS_PLANAR) + return -EINVAL; + break; + default: + return -EINVAL; + } + + width = f->fmt.pix.width; + height = f->fmt.pix.height; + + rc = limit_scaled_size_lock(fh, &width, &height, field, + /* width_mask: 4 pixels */ ~3, + /* width_bias: nearest */ 2, + /* adjust_size */ 1, + /* adjust_crop */ 0); + if (0 != rc) + return rc; + + /* update data for the application */ + f->fmt.pix.field = field; + pix_format_set_size(&f->fmt.pix, fmt, width, height); + + return 0; +} + +static int bttv_try_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bttv_fh *fh = priv; + + return verify_window_lock(fh, &f->fmt.win, + /* adjust_size */ 1, + /* adjust_crop */ 0); +} + +static int bttv_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int retval; + const struct bttv_format *fmt; + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + __s32 width, height; + enum v4l2_field field; + + retval = bttv_switch_type(fh, f->type); + if (0 != retval) + return retval; + + retval = bttv_try_fmt_vid_cap(file, priv, f); + if (0 != retval) + return retval; + + width = f->fmt.pix.width; + height = f->fmt.pix.height; + field = f->fmt.pix.field; + + retval = limit_scaled_size_lock(fh, &width, &height, f->fmt.pix.field, + /* width_mask: 4 pixels */ ~3, + /* width_bias: nearest */ 2, + /* adjust_size */ 1, + /* adjust_crop */ 1); + if (0 != retval) + return retval; + + f->fmt.pix.field = field; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + + /* update our state informations */ + fh->fmt = fmt; + fh->cap.field = f->fmt.pix.field; + fh->cap.last = V4L2_FIELD_NONE; + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + btv->init.fmt = fmt; + btv->init.width = f->fmt.pix.width; + btv->init.height = f->fmt.pix.height; + + return 0; +} + +static int bttv_s_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + if (no_overlay > 0) { + pr_err("V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n"); + return -EINVAL; + } + + return setup_window_lock(fh, btv, &f->fmt.win, 1); +} + +static int bttv_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + if (0 == v4l2) + return -EINVAL; + + strlcpy(cap->driver, "bttv", sizeof(cap->driver)); + strlcpy(cap->card, btv->video_dev->name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "PCI:%s", pci_name(btv->c.pci)); + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + if (no_overlay <= 0) + cap->capabilities |= V4L2_CAP_VIDEO_OVERLAY; + + /* + * No need to lock here: those vars are initialized during board + * probe and remains untouched during the rest of the driver lifecycle + */ + if (btv->has_saa6588) + cap->capabilities |= V4L2_CAP_RDS_CAPTURE; + if (btv->tuner_type != TUNER_ABSENT) + cap->capabilities |= V4L2_CAP_TUNER; + return 0; +} + +static int bttv_enum_fmt_cap_ovr(struct v4l2_fmtdesc *f) +{ + int index = -1, i; + + for (i = 0; i < FORMATS; i++) { + if (formats[i].fourcc != -1) + index++; + if ((unsigned int)index == f->index) + break; + } + if (FORMATS == i) + return -EINVAL; + + f->pixelformat = formats[i].fourcc; + strlcpy(f->description, formats[i].name, sizeof(f->description)); + + return i; +} + +static int bttv_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int rc = bttv_enum_fmt_cap_ovr(f); + + if (rc < 0) + return rc; + + return 0; +} + +static int bttv_enum_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int rc; + + if (no_overlay > 0) { + pr_err("V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n"); + return -EINVAL; + } + + rc = bttv_enum_fmt_cap_ovr(f); + + if (rc < 0) + return rc; + + if (!(formats[rc].flags & FORMAT_FLAGS_PACKED)) + return -EINVAL; + + return 0; +} + +static int bttv_g_fbuf(struct file *file, void *f, + struct v4l2_framebuffer *fb) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + + *fb = btv->fbuf; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + if (fh->ovfmt) + fb->fmt.pixelformat = fh->ovfmt->fourcc; + return 0; +} + +static int bttv_overlay(struct file *file, void *f, unsigned int on) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + struct bttv_buffer *new; + int retval = 0; + + if (on) { + /* verify args */ + if (unlikely(!btv->fbuf.base)) { + return -EINVAL; + } + if (unlikely(!fh->ov.setup_ok)) { + dprintk("%d: overlay: !setup_ok\n", btv->c.nr); + retval = -EINVAL; + } + if (retval) + return retval; + } + + if (!check_alloc_btres_lock(btv, fh, RESOURCE_OVERLAY)) + return -EBUSY; + + if (on) { + fh->ov.tvnorm = btv->tvnorm; + new = videobuf_sg_alloc(sizeof(*new)); + new->crop = btv->crop[!!fh->do_crop].rect; + bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new); + } else { + new = NULL; + } + + /* switch over */ + retval = bttv_switch_overlay(btv, fh, new); + return retval; +} + +static int bttv_s_fbuf(struct file *file, void *f, + const struct v4l2_framebuffer *fb) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + const struct bttv_format *fmt; + int retval; + + if (!capable(CAP_SYS_ADMIN) && + !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = format_by_fourcc(fb->fmt.pixelformat); + if (NULL == fmt) + return -EINVAL; + if (0 == (fmt->flags & FORMAT_FLAGS_PACKED)) + return -EINVAL; + + retval = -EINVAL; + if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) { + __s32 width = fb->fmt.width; + __s32 height = fb->fmt.height; + + retval = limit_scaled_size_lock(fh, &width, &height, + V4L2_FIELD_INTERLACED, + /* width_mask */ ~3, + /* width_bias */ 2, + /* adjust_size */ 0, + /* adjust_crop */ 0); + if (0 != retval) + return retval; + } + + /* ok, accept it */ + btv->fbuf.base = fb->base; + btv->fbuf.fmt.width = fb->fmt.width; + btv->fbuf.fmt.height = fb->fmt.height; + if (0 != fb->fmt.bytesperline) + btv->fbuf.fmt.bytesperline = fb->fmt.bytesperline; + else + btv->fbuf.fmt.bytesperline = btv->fbuf.fmt.width*fmt->depth/8; + + retval = 0; + fh->ovfmt = fmt; + btv->init.ovfmt = fmt; + if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) { + fh->ov.w.left = 0; + fh->ov.w.top = 0; + fh->ov.w.width = fb->fmt.width; + fh->ov.w.height = fb->fmt.height; + btv->init.ov.w.width = fb->fmt.width; + btv->init.ov.w.height = fb->fmt.height; + kfree(fh->ov.clips); + fh->ov.clips = NULL; + fh->ov.nclips = 0; + + if (check_btres(fh, RESOURCE_OVERLAY)) { + struct bttv_buffer *new; + + new = videobuf_sg_alloc(sizeof(*new)); + new->crop = btv->crop[!!fh->do_crop].rect; + bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new); + retval = bttv_switch_overlay(btv, fh, new); + } + } + return retval; +} + +static int bttv_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct bttv_fh *fh = priv; + return videobuf_reqbufs(bttv_queue(fh), p); +} + +static int bttv_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct bttv_fh *fh = priv; + return videobuf_querybuf(bttv_queue(fh), b); +} + +static int bttv_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + int res = bttv_resource(fh); + + if (!check_alloc_btres_lock(btv, fh, res)) + return -EBUSY; + + return videobuf_qbuf(bttv_queue(fh), b); +} + +static int bttv_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct bttv_fh *fh = priv; + return videobuf_dqbuf(bttv_queue(fh), b, + file->f_flags & O_NONBLOCK); +} + +static int bttv_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + int res = bttv_resource(fh); + + if (!check_alloc_btres_lock(btv, fh, res)) + return -EBUSY; + return videobuf_streamon(bttv_queue(fh)); +} + + +static int bttv_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + int retval; + int res = bttv_resource(fh); + + + retval = videobuf_streamoff(bttv_queue(fh)); + if (retval < 0) + return retval; + free_btres_lock(btv, fh, res); + return 0; +} + +static int bttv_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *c) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + const struct v4l2_queryctrl *ctrl; + + if ((c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) && + (c->id < V4L2_CID_PRIVATE_BASE || + c->id >= V4L2_CID_PRIVATE_LASTP1)) + return -EINVAL; + + if (!btv->volume_gpio && (c->id == V4L2_CID_AUDIO_VOLUME)) + *c = no_ctl; + else { + ctrl = ctrl_by_id(c->id); + + *c = (NULL != ctrl) ? *ctrl : no_ctl; + } + + return 0; +} + +static int bttv_g_parm(struct file *file, void *f, + struct v4l2_streamparm *parm) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + + v4l2_video_std_frame_period(bttv_tvnorms[btv->tvnorm].v4l2_id, + &parm->parm.capture.timeperframe); + + return 0; +} + +static int bttv_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + if (btv->tuner_type == TUNER_ABSENT) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + t->rxsubchans = V4L2_TUNER_SUB_MONO; + bttv_call_all(btv, tuner, g_tuner, t); + strcpy(t->name, "Television"); + t->capability = V4L2_TUNER_CAP_NORM; + t->type = V4L2_TUNER_ANALOG_TV; + if (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC) + t->signal = 0xffff; + + if (btv->audio_mode_gpio) + btv->audio_mode_gpio(btv, t, 0); + + return 0; +} + +static int bttv_g_priority(struct file *file, void *f, enum v4l2_priority *p) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + + *p = v4l2_prio_max(&btv->prio); + + return 0; +} + +static int bttv_s_priority(struct file *file, void *f, + enum v4l2_priority prio) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + int rc; + + rc = v4l2_prio_change(&btv->prio, &fh->prio, prio); + + return rc; +} + +static int bttv_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *cap) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + + *cap = bttv_tvnorms[btv->tvnorm].cropcap; + + return 0; +} + +static int bttv_g_crop(struct file *file, void *f, struct v4l2_crop *crop) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + + /* No fh->do_crop = 1; because btv->crop[1] may be + inconsistent with fh->width or fh->height and apps + do not expect a change here. */ + + crop->c = btv->crop[!!fh->do_crop].rect; + + return 0; +} + +static int bttv_s_crop(struct file *file, void *f, const struct v4l2_crop *crop) +{ + struct bttv_fh *fh = f; + struct bttv *btv = fh->btv; + const struct v4l2_rect *b; + int retval; + struct bttv_crop c; + __s32 b_left; + __s32 b_top; + __s32 b_right; + __s32 b_bottom; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + + /* Make sure tvnorm, vbi_end and the current cropping + parameters remain consistent until we're done. Note + read() may change vbi_end in check_alloc_btres_lock(). */ + retval = v4l2_prio_check(&btv->prio, fh->prio); + if (0 != retval) { + return retval; + } + + retval = -EBUSY; + + if (locked_btres(fh->btv, VIDEO_RESOURCES)) { + return retval; + } + + b = &bttv_tvnorms[btv->tvnorm].cropcap.bounds; + + b_left = b->left; + b_right = b_left + b->width; + b_bottom = b->top + b->height; + + b_top = max(b->top, btv->vbi_end); + if (b_top + 32 >= b_bottom) { + return retval; + } + + /* Min. scaled size 48 x 32. */ + c.rect.left = clamp_t(s32, crop->c.left, b_left, b_right - 48); + c.rect.left = min(c.rect.left, (__s32) MAX_HDELAY); + + c.rect.width = clamp_t(s32, crop->c.width, + 48, b_right - c.rect.left); + + c.rect.top = clamp_t(s32, crop->c.top, b_top, b_bottom - 32); + /* Top and height must be a multiple of two. */ + c.rect.top = (c.rect.top + 1) & ~1; + + c.rect.height = clamp_t(s32, crop->c.height, + 32, b_bottom - c.rect.top); + c.rect.height = (c.rect.height + 1) & ~1; + + bttv_crop_calc_limits(&c); + + btv->crop[1] = c; + + fh->do_crop = 1; + + if (fh->width < c.min_scaled_width) { + fh->width = c.min_scaled_width; + btv->init.width = c.min_scaled_width; + } else if (fh->width > c.max_scaled_width) { + fh->width = c.max_scaled_width; + btv->init.width = c.max_scaled_width; + } + + if (fh->height < c.min_scaled_height) { + fh->height = c.min_scaled_height; + btv->init.height = c.min_scaled_height; + } else if (fh->height > c.max_scaled_height) { + fh->height = c.max_scaled_height; + btv->init.height = c.max_scaled_height; + } + + return 0; +} + +static int bttv_g_audio(struct file *file, void *priv, struct v4l2_audio *a) +{ + if (unlikely(a->index)) + return -EINVAL; + + strcpy(a->name, "audio"); + return 0; +} + +static int bttv_s_audio(struct file *file, void *priv, const struct v4l2_audio *a) +{ + if (unlikely(a->index)) + return -EINVAL; + + return 0; +} + +static ssize_t bttv_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct bttv_fh *fh = file->private_data; + int retval = 0; + + if (fh->btv->errors) + bttv_reinit_bt848(fh->btv); + dprintk("%d: read count=%d type=%s\n", + fh->btv->c.nr, (int)count, v4l2_type_names[fh->type]); + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (!check_alloc_btres_lock(fh->btv, fh, RESOURCE_VIDEO_READ)) { + /* VIDEO_READ in use by another fh, + or VIDEO_STREAM by any fh. */ + return -EBUSY; + } + retval = videobuf_read_one(&fh->cap, data, count, ppos, + file->f_flags & O_NONBLOCK); + free_btres_lock(fh->btv, fh, RESOURCE_VIDEO_READ); + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!check_alloc_btres_lock(fh->btv,fh,RESOURCE_VBI)) + return -EBUSY; + retval = videobuf_read_stream(&fh->vbi, data, count, ppos, 1, + file->f_flags & O_NONBLOCK); + break; + default: + BUG(); + } + return retval; +} + +static unsigned int bttv_poll(struct file *file, poll_table *wait) +{ + struct bttv_fh *fh = file->private_data; + struct bttv_buffer *buf; + enum v4l2_field field; + unsigned int rc = POLLERR; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { + if (!check_alloc_btres_lock(fh->btv,fh,RESOURCE_VBI)) + return POLLERR; + return videobuf_poll_stream(file, &fh->vbi, wait); + } + + if (check_btres(fh,RESOURCE_VIDEO_STREAM)) { + /* streaming capture */ + if (list_empty(&fh->cap.stream)) + goto err; + buf = list_entry(fh->cap.stream.next,struct bttv_buffer,vb.stream); + } else { + /* read() capture */ + if (NULL == fh->cap.read_buf) { + /* need to capture a new frame */ + if (locked_btres(fh->btv,RESOURCE_VIDEO_STREAM)) + goto err; + fh->cap.read_buf = videobuf_sg_alloc(fh->cap.msize); + if (NULL == fh->cap.read_buf) + goto err; + fh->cap.read_buf->memory = V4L2_MEMORY_USERPTR; + field = videobuf_next_field(&fh->cap); + if (0 != fh->cap.ops->buf_prepare(&fh->cap,fh->cap.read_buf,field)) { + kfree (fh->cap.read_buf); + fh->cap.read_buf = NULL; + goto err; + } + fh->cap.ops->buf_queue(&fh->cap,fh->cap.read_buf); + fh->cap.read_off = 0; + } + buf = (struct bttv_buffer*)fh->cap.read_buf; + } + + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == VIDEOBUF_DONE || + buf->vb.state == VIDEOBUF_ERROR) + rc = POLLIN|POLLRDNORM; + else + rc = 0; +err: + return rc; +} + +static int bttv_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct bttv *btv = video_drvdata(file); + struct bttv_fh *fh; + enum v4l2_buf_type type = 0; + + dprintk("open dev=%s\n", video_device_node_name(vdev)); + + if (vdev->vfl_type == VFL_TYPE_GRABBER) { + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + } else if (vdev->vfl_type == VFL_TYPE_VBI) { + type = V4L2_BUF_TYPE_VBI_CAPTURE; + } else { + WARN_ON(1); + return -ENODEV; + } + + dprintk("%d: open called (type=%s)\n", + btv->c.nr, v4l2_type_names[type]); + + /* allocate per filehandle data */ + fh = kmalloc(sizeof(*fh), GFP_KERNEL); + if (unlikely(!fh)) + return -ENOMEM; + file->private_data = fh; + + *fh = btv->init; + + fh->type = type; + fh->ov.setup_ok = 0; + + v4l2_prio_open(&btv->prio, &fh->prio); + + videobuf_queue_sg_init(&fh->cap, &bttv_video_qops, + &btv->c.pci->dev, &btv->s_lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct bttv_buffer), + fh, &btv->lock); + videobuf_queue_sg_init(&fh->vbi, &bttv_vbi_qops, + &btv->c.pci->dev, &btv->s_lock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct bttv_buffer), + fh, &btv->lock); + set_tvnorm(btv,btv->tvnorm); + set_input(btv, btv->input, btv->tvnorm); + + btv->users++; + + /* The V4L2 spec requires one global set of cropping parameters + which only change on request. These are stored in btv->crop[1]. + However for compatibility with V4L apps and cropping unaware + V4L2 apps we now reset the cropping parameters as seen through + this fh, which is to say VIDIOC_G_CROP and scaling limit checks + will use btv->crop[0], the default cropping parameters for the + current video standard, and VIDIOC_S_FMT will not implicitely + change the cropping parameters until VIDIOC_S_CROP has been + called. */ + fh->do_crop = !reset_crop; /* module parameter */ + + /* Likewise there should be one global set of VBI capture + parameters, but for compatibility with V4L apps and earlier + driver versions each fh has its own parameters. */ + bttv_vbi_fmt_reset(&fh->vbi_fmt, btv->tvnorm); + + bttv_field_count(btv); + return 0; +} + +static int bttv_release(struct file *file) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + + /* turn off overlay */ + if (check_btres(fh, RESOURCE_OVERLAY)) + bttv_switch_overlay(btv,fh,NULL); + + /* stop video capture */ + if (check_btres(fh, RESOURCE_VIDEO_STREAM)) { + videobuf_streamoff(&fh->cap); + free_btres_lock(btv,fh,RESOURCE_VIDEO_STREAM); + } + if (fh->cap.read_buf) { + buffer_release(&fh->cap,fh->cap.read_buf); + kfree(fh->cap.read_buf); + } + if (check_btres(fh, RESOURCE_VIDEO_READ)) { + free_btres_lock(btv, fh, RESOURCE_VIDEO_READ); + } + + /* stop vbi capture */ + if (check_btres(fh, RESOURCE_VBI)) { + videobuf_stop(&fh->vbi); + free_btres_lock(btv,fh,RESOURCE_VBI); + } + + /* free stuff */ + + videobuf_mmap_free(&fh->cap); + videobuf_mmap_free(&fh->vbi); + v4l2_prio_close(&btv->prio, fh->prio); + file->private_data = NULL; + kfree(fh); + + btv->users--; + bttv_field_count(btv); + + if (!btv->users) + audio_mute(btv, 1); + + return 0; +} + +static int +bttv_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct bttv_fh *fh = file->private_data; + + dprintk("%d: mmap type=%s 0x%lx+%ld\n", + fh->btv->c.nr, v4l2_type_names[fh->type], + vma->vm_start, vma->vm_end - vma->vm_start); + return videobuf_mmap_mapper(bttv_queue(fh),vma); +} + +static const struct v4l2_file_operations bttv_fops = +{ + .owner = THIS_MODULE, + .open = bttv_open, + .release = bttv_release, + .unlocked_ioctl = video_ioctl2, + .read = bttv_read, + .mmap = bttv_mmap, + .poll = bttv_poll, +}; + +static const struct v4l2_ioctl_ops bttv_ioctl_ops = { + .vidioc_querycap = bttv_querycap, + .vidioc_enum_fmt_vid_cap = bttv_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = bttv_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = bttv_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = bttv_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_overlay = bttv_enum_fmt_vid_overlay, + .vidioc_g_fmt_vid_overlay = bttv_g_fmt_vid_overlay, + .vidioc_try_fmt_vid_overlay = bttv_try_fmt_vid_overlay, + .vidioc_s_fmt_vid_overlay = bttv_s_fmt_vid_overlay, + .vidioc_g_fmt_vbi_cap = bttv_g_fmt_vbi_cap, + .vidioc_try_fmt_vbi_cap = bttv_try_fmt_vbi_cap, + .vidioc_s_fmt_vbi_cap = bttv_s_fmt_vbi_cap, + .vidioc_g_audio = bttv_g_audio, + .vidioc_s_audio = bttv_s_audio, + .vidioc_cropcap = bttv_cropcap, + .vidioc_reqbufs = bttv_reqbufs, + .vidioc_querybuf = bttv_querybuf, + .vidioc_qbuf = bttv_qbuf, + .vidioc_dqbuf = bttv_dqbuf, + .vidioc_s_std = bttv_s_std, + .vidioc_enum_input = bttv_enum_input, + .vidioc_g_input = bttv_g_input, + .vidioc_s_input = bttv_s_input, + .vidioc_queryctrl = bttv_queryctrl, + .vidioc_g_ctrl = bttv_g_ctrl, + .vidioc_s_ctrl = bttv_s_ctrl, + .vidioc_streamon = bttv_streamon, + .vidioc_streamoff = bttv_streamoff, + .vidioc_g_tuner = bttv_g_tuner, + .vidioc_s_tuner = bttv_s_tuner, + .vidioc_g_crop = bttv_g_crop, + .vidioc_s_crop = bttv_s_crop, + .vidioc_g_fbuf = bttv_g_fbuf, + .vidioc_s_fbuf = bttv_s_fbuf, + .vidioc_overlay = bttv_overlay, + .vidioc_g_priority = bttv_g_priority, + .vidioc_s_priority = bttv_s_priority, + .vidioc_g_parm = bttv_g_parm, + .vidioc_g_frequency = bttv_g_frequency, + .vidioc_s_frequency = bttv_s_frequency, + .vidioc_log_status = bttv_log_status, + .vidioc_querystd = bttv_querystd, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = bttv_g_register, + .vidioc_s_register = bttv_s_register, +#endif +}; + +static struct video_device bttv_video_template = { + .fops = &bttv_fops, + .ioctl_ops = &bttv_ioctl_ops, + .tvnorms = BTTV_NORMS, + .current_norm = V4L2_STD_PAL, +}; + +/* ----------------------------------------------------------------------- */ +/* radio interface */ + +static int radio_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct bttv *btv = video_drvdata(file); + struct bttv_fh *fh; + + dprintk("open dev=%s\n", video_device_node_name(vdev)); + + dprintk("%d: open called (radio)\n", btv->c.nr); + + /* allocate per filehandle data */ + fh = kmalloc(sizeof(*fh), GFP_KERNEL); + if (unlikely(!fh)) + return -ENOMEM; + file->private_data = fh; + *fh = btv->init; + + v4l2_prio_open(&btv->prio, &fh->prio); + + btv->radio_user++; + + bttv_call_all(btv, tuner, s_radio); + audio_input(btv,TVAUDIO_INPUT_RADIO); + + return 0; +} + +static int radio_release(struct file *file) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + struct saa6588_command cmd; + + v4l2_prio_close(&btv->prio, fh->prio); + file->private_data = NULL; + kfree(fh); + + btv->radio_user--; + + bttv_call_all(btv, core, ioctl, SAA6588_CMD_CLOSE, &cmd); + + return 0; +} + +static int radio_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + strcpy(cap->driver, "bttv"); + strlcpy(cap->card, btv->radio_dev->name, sizeof(cap->card)); + sprintf(cap->bus_info, "PCI:%s", pci_name(btv->c.pci)); + cap->capabilities = V4L2_CAP_TUNER; + + return 0; +} + +static int radio_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + if (btv->tuner_type == TUNER_ABSENT) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + strcpy(t->name, "Radio"); + t->type = V4L2_TUNER_RADIO; + + bttv_call_all(btv, tuner, g_tuner, t); + + if (btv->audio_mode_gpio) + btv->audio_mode_gpio(btv, t, 0); + + return 0; +} + +static int radio_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + + strcpy(i->name, "Radio"); + i->type = V4L2_INPUT_TYPE_TUNER; + + return 0; +} + +static int radio_g_audio(struct file *file, void *priv, + struct v4l2_audio *a) +{ + if (unlikely(a->index)) + return -EINVAL; + + strcpy(a->name, "Radio"); + + return 0; +} + +static int radio_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct bttv_fh *fh = priv; + struct bttv *btv = fh->btv; + + if (0 != t->index) + return -EINVAL; + + bttv_call_all(btv, tuner, s_tuner, t); + return 0; +} + +static int radio_s_audio(struct file *file, void *priv, + const struct v4l2_audio *a) +{ + if (unlikely(a->index)) + return -EINVAL; + + return 0; +} + +static int radio_s_input(struct file *filp, void *priv, unsigned int i) +{ + if (unlikely(i)) + return -EINVAL; + + return 0; +} + +static int radio_s_std(struct file *file, void *fh, v4l2_std_id *norm) +{ + return 0; +} + +static int radio_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *c) +{ + const struct v4l2_queryctrl *ctrl; + + if (c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) + return -EINVAL; + + if (c->id == V4L2_CID_AUDIO_MUTE) { + ctrl = ctrl_by_id(c->id); + *c = *ctrl; + } else + *c = no_ctl; + + return 0; +} + +static int radio_g_input(struct file *filp, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static ssize_t radio_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + struct saa6588_command cmd; + cmd.block_count = count/3; + cmd.buffer = data; + cmd.instance = file; + cmd.result = -ENODEV; + + bttv_call_all(btv, core, ioctl, SAA6588_CMD_READ, &cmd); + + return cmd.result; +} + +static unsigned int radio_poll(struct file *file, poll_table *wait) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + struct saa6588_command cmd; + cmd.instance = file; + cmd.event_list = wait; + cmd.result = -ENODEV; + bttv_call_all(btv, core, ioctl, SAA6588_CMD_POLL, &cmd); + + return cmd.result; +} + +static const struct v4l2_file_operations radio_fops = +{ + .owner = THIS_MODULE, + .open = radio_open, + .read = radio_read, + .release = radio_release, + .unlocked_ioctl = video_ioctl2, + .poll = radio_poll, +}; + +static const struct v4l2_ioctl_ops radio_ioctl_ops = { + .vidioc_querycap = radio_querycap, + .vidioc_g_tuner = radio_g_tuner, + .vidioc_enum_input = radio_enum_input, + .vidioc_g_audio = radio_g_audio, + .vidioc_s_tuner = radio_s_tuner, + .vidioc_s_audio = radio_s_audio, + .vidioc_s_input = radio_s_input, + .vidioc_s_std = radio_s_std, + .vidioc_queryctrl = radio_queryctrl, + .vidioc_g_input = radio_g_input, + .vidioc_g_ctrl = bttv_g_ctrl, + .vidioc_s_ctrl = bttv_s_ctrl, + .vidioc_g_frequency = bttv_g_frequency, + .vidioc_s_frequency = bttv_s_frequency, +}; + +static struct video_device radio_template = { + .fops = &radio_fops, + .ioctl_ops = &radio_ioctl_ops, +}; + +/* ----------------------------------------------------------------------- */ +/* some debug code */ + +static int bttv_risc_decode(u32 risc) +{ + static char *instr[16] = { + [ BT848_RISC_WRITE >> 28 ] = "write", + [ BT848_RISC_SKIP >> 28 ] = "skip", + [ BT848_RISC_WRITEC >> 28 ] = "writec", + [ BT848_RISC_JUMP >> 28 ] = "jump", + [ BT848_RISC_SYNC >> 28 ] = "sync", + [ BT848_RISC_WRITE123 >> 28 ] = "write123", + [ BT848_RISC_SKIP123 >> 28 ] = "skip123", + [ BT848_RISC_WRITE1S23 >> 28 ] = "write1s23", + }; + static int incr[16] = { + [ BT848_RISC_WRITE >> 28 ] = 2, + [ BT848_RISC_JUMP >> 28 ] = 2, + [ BT848_RISC_SYNC >> 28 ] = 2, + [ BT848_RISC_WRITE123 >> 28 ] = 5, + [ BT848_RISC_SKIP123 >> 28 ] = 2, + [ BT848_RISC_WRITE1S23 >> 28 ] = 3, + }; + static char *bits[] = { + "be0", "be1", "be2", "be3/resync", + "set0", "set1", "set2", "set3", + "clr0", "clr1", "clr2", "clr3", + "irq", "res", "eol", "sol", + }; + int i; + + pr_cont("0x%08x [ %s", risc, + instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); + for (i = ARRAY_SIZE(bits)-1; i >= 0; i--) + if (risc & (1 << (i + 12))) + pr_cont(" %s", bits[i]); + pr_cont(" count=%d ]\n", risc & 0xfff); + return incr[risc >> 28] ? incr[risc >> 28] : 1; +} + +static void bttv_risc_disasm(struct bttv *btv, + struct btcx_riscmem *risc) +{ + unsigned int i,j,n; + + pr_info("%s: risc disasm: %p [dma=0x%08lx]\n", + btv->c.v4l2_dev.name, risc->cpu, (unsigned long)risc->dma); + for (i = 0; i < (risc->size >> 2); i += n) { + pr_info("%s: 0x%lx: ", + btv->c.v4l2_dev.name, + (unsigned long)(risc->dma + (i<<2))); + n = bttv_risc_decode(le32_to_cpu(risc->cpu[i])); + for (j = 1; j < n; j++) + pr_info("%s: 0x%lx: 0x%08x [ arg #%d ]\n", + btv->c.v4l2_dev.name, + (unsigned long)(risc->dma + ((i+j)<<2)), + risc->cpu[i+j], j); + if (0 == risc->cpu[i]) + break; + } +} + +static void bttv_print_riscaddr(struct bttv *btv) +{ + pr_info(" main: %08llx\n", (unsigned long long)btv->main.dma); + pr_info(" vbi : o=%08llx e=%08llx\n", + btv->cvbi ? (unsigned long long)btv->cvbi->top.dma : 0, + btv->cvbi ? (unsigned long long)btv->cvbi->bottom.dma : 0); + pr_info(" cap : o=%08llx e=%08llx\n", + btv->curr.top + ? (unsigned long long)btv->curr.top->top.dma : 0, + btv->curr.bottom + ? (unsigned long long)btv->curr.bottom->bottom.dma : 0); + pr_info(" scr : o=%08llx e=%08llx\n", + btv->screen ? (unsigned long long)btv->screen->top.dma : 0, + btv->screen ? (unsigned long long)btv->screen->bottom.dma : 0); + bttv_risc_disasm(btv, &btv->main); +} + +/* ----------------------------------------------------------------------- */ +/* irq handler */ + +static char *irq_name[] = { + "FMTCHG", // format change detected (525 vs. 625) + "VSYNC", // vertical sync (new field) + "HSYNC", // horizontal sync + "OFLOW", // chroma/luma AGC overflow + "HLOCK", // horizontal lock changed + "VPRES", // video presence changed + "6", "7", + "I2CDONE", // hw irc operation finished + "GPINT", // gpio port triggered irq + "10", + "RISCI", // risc instruction triggered irq + "FBUS", // pixel data fifo dropped data (high pci bus latencies) + "FTRGT", // pixel data fifo overrun + "FDSR", // fifo data stream resyncronisation + "PPERR", // parity error (data transfer) + "RIPERR", // parity error (read risc instructions) + "PABORT", // pci abort + "OCERR", // risc instruction error + "SCERR", // syncronisation error +}; + +static void bttv_print_irqbits(u32 print, u32 mark) +{ + unsigned int i; + + pr_cont("bits:"); + for (i = 0; i < ARRAY_SIZE(irq_name); i++) { + if (print & (1 << i)) + pr_cont(" %s", irq_name[i]); + if (mark & (1 << i)) + pr_cont("*"); + } +} + +static void bttv_irq_debug_low_latency(struct bttv *btv, u32 rc) +{ + pr_warn("%d: irq: skipped frame [main=%lx,o_vbi=%lx,o_field=%lx,rc=%lx]\n", + btv->c.nr, + (unsigned long)btv->main.dma, + (unsigned long)le32_to_cpu(btv->main.cpu[RISC_SLOT_O_VBI+1]), + (unsigned long)le32_to_cpu(btv->main.cpu[RISC_SLOT_O_FIELD+1]), + (unsigned long)rc); + + if (0 == (btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC)) { + pr_notice("%d: Oh, there (temporarily?) is no input signal. " + "Ok, then this is harmless, don't worry ;)\n", + btv->c.nr); + return; + } + pr_notice("%d: Uhm. Looks like we have unusual high IRQ latencies\n", + btv->c.nr); + pr_notice("%d: Lets try to catch the culpit red-handed ...\n", + btv->c.nr); + dump_stack(); +} + +static int +bttv_irq_next_video(struct bttv *btv, struct bttv_buffer_set *set) +{ + struct bttv_buffer *item; + + memset(set,0,sizeof(*set)); + + /* capture request ? */ + if (!list_empty(&btv->capture)) { + set->frame_irq = 1; + item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue); + if (V4L2_FIELD_HAS_TOP(item->vb.field)) + set->top = item; + if (V4L2_FIELD_HAS_BOTTOM(item->vb.field)) + set->bottom = item; + + /* capture request for other field ? */ + if (!V4L2_FIELD_HAS_BOTH(item->vb.field) && + (item->vb.queue.next != &btv->capture)) { + item = list_entry(item->vb.queue.next, struct bttv_buffer, vb.queue); + /* Mike Isely - Only check + * and set up the bottom field in the logic + * below. Don't ever do the top field. This + * of course means that if we set up the + * bottom field in the above code that we'll + * actually skip a field. But that's OK. + * Having processed only a single buffer this + * time, then the next time around the first + * available buffer should be for a top field. + * That will then cause us here to set up a + * top then a bottom field in the normal way. + * The alternative to this understanding is + * that we set up the second available buffer + * as a top field, but that's out of order + * since this driver always processes the top + * field first - the effect will be the two + * buffers being returned in the wrong order, + * with the second buffer also being delayed + * by one field time (owing to the fifo nature + * of videobuf). Worse still, we'll be stuck + * doing fields out of order now every time + * until something else causes a field to be + * dropped. By effectively forcing a field to + * drop this way then we always get back into + * sync within a single frame time. (Out of + * order fields can screw up deinterlacing + * algorithms.) */ + if (!V4L2_FIELD_HAS_BOTH(item->vb.field)) { + if (NULL == set->bottom && + V4L2_FIELD_BOTTOM == item->vb.field) { + set->bottom = item; + } + if (NULL != set->top && NULL != set->bottom) + set->top_irq = 2; + } + } + } + + /* screen overlay ? */ + if (NULL != btv->screen) { + if (V4L2_FIELD_HAS_BOTH(btv->screen->vb.field)) { + if (NULL == set->top && NULL == set->bottom) { + set->top = btv->screen; + set->bottom = btv->screen; + } + } else { + if (V4L2_FIELD_TOP == btv->screen->vb.field && + NULL == set->top) { + set->top = btv->screen; + } + if (V4L2_FIELD_BOTTOM == btv->screen->vb.field && + NULL == set->bottom) { + set->bottom = btv->screen; + } + } + } + + dprintk("%d: next set: top=%p bottom=%p [screen=%p,irq=%d,%d]\n", + btv->c.nr, set->top, set->bottom, + btv->screen, set->frame_irq, set->top_irq); + return 0; +} + +static void +bttv_irq_wakeup_video(struct bttv *btv, struct bttv_buffer_set *wakeup, + struct bttv_buffer_set *curr, unsigned int state) +{ + struct timeval ts; + + do_gettimeofday(&ts); + + if (wakeup->top == wakeup->bottom) { + if (NULL != wakeup->top && curr->top != wakeup->top) { + if (irq_debug > 1) + pr_debug("%d: wakeup: both=%p\n", + btv->c.nr, wakeup->top); + wakeup->top->vb.ts = ts; + wakeup->top->vb.field_count = btv->field_count; + wakeup->top->vb.state = state; + wake_up(&wakeup->top->vb.done); + } + } else { + if (NULL != wakeup->top && curr->top != wakeup->top) { + if (irq_debug > 1) + pr_debug("%d: wakeup: top=%p\n", + btv->c.nr, wakeup->top); + wakeup->top->vb.ts = ts; + wakeup->top->vb.field_count = btv->field_count; + wakeup->top->vb.state = state; + wake_up(&wakeup->top->vb.done); + } + if (NULL != wakeup->bottom && curr->bottom != wakeup->bottom) { + if (irq_debug > 1) + pr_debug("%d: wakeup: bottom=%p\n", + btv->c.nr, wakeup->bottom); + wakeup->bottom->vb.ts = ts; + wakeup->bottom->vb.field_count = btv->field_count; + wakeup->bottom->vb.state = state; + wake_up(&wakeup->bottom->vb.done); + } + } +} + +static void +bttv_irq_wakeup_vbi(struct bttv *btv, struct bttv_buffer *wakeup, + unsigned int state) +{ + struct timeval ts; + + if (NULL == wakeup) + return; + + do_gettimeofday(&ts); + wakeup->vb.ts = ts; + wakeup->vb.field_count = btv->field_count; + wakeup->vb.state = state; + wake_up(&wakeup->vb.done); +} + +static void bttv_irq_timeout(unsigned long data) +{ + struct bttv *btv = (struct bttv *)data; + struct bttv_buffer_set old,new; + struct bttv_buffer *ovbi; + struct bttv_buffer *item; + unsigned long flags; + + if (bttv_verbose) { + pr_info("%d: timeout: drop=%d irq=%d/%d, risc=%08x, ", + btv->c.nr, btv->framedrop, btv->irq_me, btv->irq_total, + btread(BT848_RISC_COUNT)); + bttv_print_irqbits(btread(BT848_INT_STAT),0); + pr_cont("\n"); + } + + spin_lock_irqsave(&btv->s_lock,flags); + + /* deactivate stuff */ + memset(&new,0,sizeof(new)); + old = btv->curr; + ovbi = btv->cvbi; + btv->curr = new; + btv->cvbi = NULL; + btv->loop_irq = 0; + bttv_buffer_activate_video(btv, &new); + bttv_buffer_activate_vbi(btv, NULL); + bttv_set_dma(btv, 0); + + /* wake up */ + bttv_irq_wakeup_video(btv, &old, &new, VIDEOBUF_ERROR); + bttv_irq_wakeup_vbi(btv, ovbi, VIDEOBUF_ERROR); + + /* cancel all outstanding capture / vbi requests */ + while (!list_empty(&btv->capture)) { + item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue); + list_del(&item->vb.queue); + item->vb.state = VIDEOBUF_ERROR; + wake_up(&item->vb.done); + } + while (!list_empty(&btv->vcapture)) { + item = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue); + list_del(&item->vb.queue); + item->vb.state = VIDEOBUF_ERROR; + wake_up(&item->vb.done); + } + + btv->errors++; + spin_unlock_irqrestore(&btv->s_lock,flags); +} + +static void +bttv_irq_wakeup_top(struct bttv *btv) +{ + struct bttv_buffer *wakeup = btv->curr.top; + + if (NULL == wakeup) + return; + + spin_lock(&btv->s_lock); + btv->curr.top_irq = 0; + btv->curr.top = NULL; + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + + do_gettimeofday(&wakeup->vb.ts); + wakeup->vb.field_count = btv->field_count; + wakeup->vb.state = VIDEOBUF_DONE; + wake_up(&wakeup->vb.done); + spin_unlock(&btv->s_lock); +} + +static inline int is_active(struct btcx_riscmem *risc, u32 rc) +{ + if (rc < risc->dma) + return 0; + if (rc > risc->dma + risc->size) + return 0; + return 1; +} + +static void +bttv_irq_switch_video(struct bttv *btv) +{ + struct bttv_buffer_set new; + struct bttv_buffer_set old; + dma_addr_t rc; + + spin_lock(&btv->s_lock); + + /* new buffer set */ + bttv_irq_next_video(btv, &new); + rc = btread(BT848_RISC_COUNT); + if ((btv->curr.top && is_active(&btv->curr.top->top, rc)) || + (btv->curr.bottom && is_active(&btv->curr.bottom->bottom, rc))) { + btv->framedrop++; + if (debug_latency) + bttv_irq_debug_low_latency(btv, rc); + spin_unlock(&btv->s_lock); + return; + } + + /* switch over */ + old = btv->curr; + btv->curr = new; + btv->loop_irq &= ~1; + bttv_buffer_activate_video(btv, &new); + bttv_set_dma(btv, 0); + + /* switch input */ + if (UNSET != btv->new_input) { + video_mux(btv,btv->new_input); + btv->new_input = UNSET; + } + + /* wake up finished buffers */ + bttv_irq_wakeup_video(btv, &old, &new, VIDEOBUF_DONE); + spin_unlock(&btv->s_lock); +} + +static void +bttv_irq_switch_vbi(struct bttv *btv) +{ + struct bttv_buffer *new = NULL; + struct bttv_buffer *old; + u32 rc; + + spin_lock(&btv->s_lock); + + if (!list_empty(&btv->vcapture)) + new = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue); + old = btv->cvbi; + + rc = btread(BT848_RISC_COUNT); + if (NULL != old && (is_active(&old->top, rc) || + is_active(&old->bottom, rc))) { + btv->framedrop++; + if (debug_latency) + bttv_irq_debug_low_latency(btv, rc); + spin_unlock(&btv->s_lock); + return; + } + + /* switch */ + btv->cvbi = new; + btv->loop_irq &= ~4; + bttv_buffer_activate_vbi(btv, new); + bttv_set_dma(btv, 0); + + bttv_irq_wakeup_vbi(btv, old, VIDEOBUF_DONE); + spin_unlock(&btv->s_lock); +} + +static irqreturn_t bttv_irq(int irq, void *dev_id) +{ + u32 stat,astat; + u32 dstat; + int count; + struct bttv *btv; + int handled = 0; + + btv=(struct bttv *)dev_id; + + count=0; + while (1) { + /* get/clear interrupt status bits */ + stat=btread(BT848_INT_STAT); + astat=stat&btread(BT848_INT_MASK); + if (!astat) + break; + handled = 1; + btwrite(stat,BT848_INT_STAT); + + /* get device status bits */ + dstat=btread(BT848_DSTATUS); + + if (irq_debug) { + pr_debug("%d: irq loop=%d fc=%d riscs=%x, riscc=%08x, ", + btv->c.nr, count, btv->field_count, + stat>>28, btread(BT848_RISC_COUNT)); + bttv_print_irqbits(stat,astat); + if (stat & BT848_INT_HLOCK) + pr_cont(" HLOC => %s", + dstat & BT848_DSTATUS_HLOC + ? "yes" : "no"); + if (stat & BT848_INT_VPRES) + pr_cont(" PRES => %s", + dstat & BT848_DSTATUS_PRES + ? "yes" : "no"); + if (stat & BT848_INT_FMTCHG) + pr_cont(" NUML => %s", + dstat & BT848_DSTATUS_NUML + ? "625" : "525"); + pr_cont("\n"); + } + + if (astat&BT848_INT_VSYNC) + btv->field_count++; + + if ((astat & BT848_INT_GPINT) && btv->remote) { + bttv_input_irq(btv); + } + + if (astat & BT848_INT_I2CDONE) { + btv->i2c_done = stat; + wake_up(&btv->i2c_queue); + } + + if ((astat & BT848_INT_RISCI) && (stat & (4<<28))) + bttv_irq_switch_vbi(btv); + + if ((astat & BT848_INT_RISCI) && (stat & (2<<28))) + bttv_irq_wakeup_top(btv); + + if ((astat & BT848_INT_RISCI) && (stat & (1<<28))) + bttv_irq_switch_video(btv); + + if ((astat & BT848_INT_HLOCK) && btv->opt_automute) + audio_mute(btv, btv->mute); /* trigger automute */ + + if (astat & (BT848_INT_SCERR|BT848_INT_OCERR)) { + pr_info("%d: %s%s @ %08x,", + btv->c.nr, + (astat & BT848_INT_SCERR) ? "SCERR" : "", + (astat & BT848_INT_OCERR) ? "OCERR" : "", + btread(BT848_RISC_COUNT)); + bttv_print_irqbits(stat,astat); + pr_cont("\n"); + if (bttv_debug) + bttv_print_riscaddr(btv); + } + if (fdsr && astat & BT848_INT_FDSR) { + pr_info("%d: FDSR @ %08x\n", + btv->c.nr, btread(BT848_RISC_COUNT)); + if (bttv_debug) + bttv_print_riscaddr(btv); + } + + count++; + if (count > 4) { + + if (count > 8 || !(astat & BT848_INT_GPINT)) { + btwrite(0, BT848_INT_MASK); + + pr_err("%d: IRQ lockup, cleared int mask [", + btv->c.nr); + } else { + pr_err("%d: IRQ lockup, clearing GPINT from int mask [", + btv->c.nr); + + btwrite(btread(BT848_INT_MASK) & (-1 ^ BT848_INT_GPINT), + BT848_INT_MASK); + }; + + bttv_print_irqbits(stat,astat); + + pr_cont("]\n"); + } + } + btv->irq_total++; + if (handled) + btv->irq_me++; + return IRQ_RETVAL(handled); +} + + +/* ----------------------------------------------------------------------- */ +/* initialitation */ + +static struct video_device *vdev_init(struct bttv *btv, + const struct video_device *template, + const char *type_name) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->v4l2_dev = &btv->c.v4l2_dev; + vfd->release = video_device_release; + vfd->debug = bttv_debug; + video_set_drvdata(vfd, btv); + snprintf(vfd->name, sizeof(vfd->name), "BT%d%s %s (%s)", + btv->id, (btv->id==848 && btv->revision==0x12) ? "A" : "", + type_name, bttv_tvcards[btv->c.type].name); + return vfd; +} + +static void bttv_unregister_video(struct bttv *btv) +{ + if (btv->video_dev) { + if (video_is_registered(btv->video_dev)) + video_unregister_device(btv->video_dev); + else + video_device_release(btv->video_dev); + btv->video_dev = NULL; + } + if (btv->vbi_dev) { + if (video_is_registered(btv->vbi_dev)) + video_unregister_device(btv->vbi_dev); + else + video_device_release(btv->vbi_dev); + btv->vbi_dev = NULL; + } + if (btv->radio_dev) { + if (video_is_registered(btv->radio_dev)) + video_unregister_device(btv->radio_dev); + else + video_device_release(btv->radio_dev); + btv->radio_dev = NULL; + } +} + +/* register video4linux devices */ +static int __devinit bttv_register_video(struct bttv *btv) +{ + if (no_overlay > 0) + pr_notice("Overlay support disabled\n"); + + /* video */ + btv->video_dev = vdev_init(btv, &bttv_video_template, "video"); + + if (NULL == btv->video_dev) + goto err; + if (video_register_device(btv->video_dev, VFL_TYPE_GRABBER, + video_nr[btv->c.nr]) < 0) + goto err; + pr_info("%d: registered device %s\n", + btv->c.nr, video_device_node_name(btv->video_dev)); + if (device_create_file(&btv->video_dev->dev, + &dev_attr_card)<0) { + pr_err("%d: device_create_file 'card' failed\n", btv->c.nr); + goto err; + } + + /* vbi */ + btv->vbi_dev = vdev_init(btv, &bttv_video_template, "vbi"); + + if (NULL == btv->vbi_dev) + goto err; + if (video_register_device(btv->vbi_dev, VFL_TYPE_VBI, + vbi_nr[btv->c.nr]) < 0) + goto err; + pr_info("%d: registered device %s\n", + btv->c.nr, video_device_node_name(btv->vbi_dev)); + + if (!btv->has_radio) + return 0; + /* radio */ + btv->radio_dev = vdev_init(btv, &radio_template, "radio"); + if (NULL == btv->radio_dev) + goto err; + if (video_register_device(btv->radio_dev, VFL_TYPE_RADIO, + radio_nr[btv->c.nr]) < 0) + goto err; + pr_info("%d: registered device %s\n", + btv->c.nr, video_device_node_name(btv->radio_dev)); + + /* all done */ + return 0; + + err: + bttv_unregister_video(btv); + return -1; +} + + +/* on OpenFirmware machines (PowerMac at least), PCI memory cycle */ +/* response on cards with no firmware is not enabled by OF */ +static void pci_set_command(struct pci_dev *dev) +{ +#if defined(__powerpc__) + unsigned int cmd; + + pci_read_config_dword(dev, PCI_COMMAND, &cmd); + cmd = (cmd | PCI_COMMAND_MEMORY ); + pci_write_config_dword(dev, PCI_COMMAND, cmd); +#endif +} + +static int __devinit bttv_probe(struct pci_dev *dev, + const struct pci_device_id *pci_id) +{ + int result; + unsigned char lat; + struct bttv *btv; + + if (bttv_num == BTTV_MAX) + return -ENOMEM; + pr_info("Bt8xx card found (%d)\n", bttv_num); + bttvs[bttv_num] = btv = kzalloc(sizeof(*btv), GFP_KERNEL); + if (btv == NULL) { + pr_err("out of memory\n"); + return -ENOMEM; + } + btv->c.nr = bttv_num; + snprintf(btv->c.v4l2_dev.name, sizeof(btv->c.v4l2_dev.name), + "bttv%d", btv->c.nr); + + /* initialize structs / fill in defaults */ + mutex_init(&btv->lock); + spin_lock_init(&btv->s_lock); + spin_lock_init(&btv->gpio_lock); + init_waitqueue_head(&btv->i2c_queue); + INIT_LIST_HEAD(&btv->c.subs); + INIT_LIST_HEAD(&btv->capture); + INIT_LIST_HEAD(&btv->vcapture); + v4l2_prio_init(&btv->prio); + + init_timer(&btv->timeout); + btv->timeout.function = bttv_irq_timeout; + btv->timeout.data = (unsigned long)btv; + + btv->i2c_rc = -1; + btv->tuner_type = UNSET; + btv->new_input = UNSET; + btv->has_radio=radio[btv->c.nr]; + + /* pci stuff (init, get irq/mmio, ... */ + btv->c.pci = dev; + btv->id = dev->device; + if (pci_enable_device(dev)) { + pr_warn("%d: Can't enable device\n", btv->c.nr); + return -EIO; + } + if (pci_set_dma_mask(dev, DMA_BIT_MASK(32))) { + pr_warn("%d: No suitable DMA available\n", btv->c.nr); + return -EIO; + } + if (!request_mem_region(pci_resource_start(dev,0), + pci_resource_len(dev,0), + btv->c.v4l2_dev.name)) { + pr_warn("%d: can't request iomem (0x%llx)\n", + btv->c.nr, + (unsigned long long)pci_resource_start(dev, 0)); + return -EBUSY; + } + pci_set_master(dev); + pci_set_command(dev); + + result = v4l2_device_register(&dev->dev, &btv->c.v4l2_dev); + if (result < 0) { + pr_warn("%d: v4l2_device_register() failed\n", btv->c.nr); + goto fail0; + } + + btv->revision = dev->revision; + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat); + pr_info("%d: Bt%d (rev %d) at %s, irq: %d, latency: %d, mmio: 0x%llx\n", + bttv_num, btv->id, btv->revision, pci_name(dev), + btv->c.pci->irq, lat, + (unsigned long long)pci_resource_start(dev, 0)); + schedule(); + + btv->bt848_mmio = ioremap(pci_resource_start(dev, 0), 0x1000); + if (NULL == btv->bt848_mmio) { + pr_err("%d: ioremap() failed\n", btv->c.nr); + result = -EIO; + goto fail1; + } + + /* identify card */ + bttv_idcard(btv); + + /* disable irqs, register irq handler */ + btwrite(0, BT848_INT_MASK); + result = request_irq(btv->c.pci->irq, bttv_irq, + IRQF_SHARED | IRQF_DISABLED, btv->c.v4l2_dev.name, (void *)btv); + if (result < 0) { + pr_err("%d: can't get IRQ %d\n", + bttv_num, btv->c.pci->irq); + goto fail1; + } + + if (0 != bttv_handle_chipset(btv)) { + result = -EIO; + goto fail2; + } + + /* init options from insmod args */ + btv->opt_combfilter = combfilter; + btv->opt_lumafilter = lumafilter; + btv->opt_automute = automute; + btv->opt_chroma_agc = chroma_agc; + btv->opt_adc_crush = adc_crush; + btv->opt_vcr_hack = vcr_hack; + btv->opt_whitecrush_upper = whitecrush_upper; + btv->opt_whitecrush_lower = whitecrush_lower; + btv->opt_uv_ratio = uv_ratio; + btv->opt_full_luma_range = full_luma_range; + btv->opt_coring = coring; + + /* fill struct bttv with some useful defaults */ + btv->init.btv = btv; + btv->init.ov.w.width = 320; + btv->init.ov.w.height = 240; + btv->init.fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + btv->init.width = 320; + btv->init.height = 240; + btv->input = 0; + + /* initialize hardware */ + if (bttv_gpio) + bttv_gpio_tracking(btv,"pre-init"); + + bttv_risc_init_main(btv); + init_bt848(btv); + + /* gpio */ + btwrite(0x00, BT848_GPIO_REG_INP); + btwrite(0x00, BT848_GPIO_OUT_EN); + if (bttv_verbose) + bttv_gpio_tracking(btv,"init"); + + /* needs to be done before i2c is registered */ + bttv_init_card1(btv); + + /* register i2c + gpio */ + init_bttv_i2c(btv); + + /* some card-specific stuff (needs working i2c) */ + bttv_init_card2(btv); + bttv_init_tuner(btv); + init_irqreg(btv); + + /* register video4linux + input */ + if (!bttv_tvcards[btv->c.type].no_video) { + bttv_register_video(btv); + bt848_bright(btv,32768); + bt848_contrast(btv, 27648); + bt848_hue(btv,32768); + bt848_sat(btv,32768); + audio_mute(btv, 1); + set_input(btv, 0, btv->tvnorm); + bttv_crop_reset(&btv->crop[0], btv->tvnorm); + btv->crop[1] = btv->crop[0]; /* current = default */ + disclaim_vbi_lines(btv); + disclaim_video_lines(btv); + } + + /* add subdevices and autoload dvb-bt8xx if needed */ + if (bttv_tvcards[btv->c.type].has_dvb) { + bttv_sub_add_device(&btv->c, "dvb"); + request_modules(btv); + } + + if (!disable_ir) { + init_bttv_i2c_ir(btv); + bttv_input_init(btv); + } + + /* everything is fine */ + bttv_num++; + return 0; + +fail2: + free_irq(btv->c.pci->irq,btv); + +fail1: + v4l2_device_unregister(&btv->c.v4l2_dev); + +fail0: + if (btv->bt848_mmio) + iounmap(btv->bt848_mmio); + release_mem_region(pci_resource_start(btv->c.pci,0), + pci_resource_len(btv->c.pci,0)); + return result; +} + +static void __devexit bttv_remove(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct bttv *btv = to_bttv(v4l2_dev); + + if (bttv_verbose) + pr_info("%d: unloading\n", btv->c.nr); + + if (bttv_tvcards[btv->c.type].has_dvb) + flush_request_modules(btv); + + /* shutdown everything (DMA+IRQs) */ + btand(~15, BT848_GPIO_DMA_CTL); + btwrite(0, BT848_INT_MASK); + btwrite(~0x0, BT848_INT_STAT); + btwrite(0x0, BT848_GPIO_OUT_EN); + if (bttv_gpio) + bttv_gpio_tracking(btv,"cleanup"); + + /* tell gpio modules we are leaving ... */ + btv->shutdown=1; + bttv_input_fini(btv); + bttv_sub_del_devices(&btv->c); + + /* unregister i2c_bus + input */ + fini_bttv_i2c(btv); + + /* unregister video4linux */ + bttv_unregister_video(btv); + + /* free allocated memory */ + btcx_riscmem_free(btv->c.pci,&btv->main); + + /* free ressources */ + free_irq(btv->c.pci->irq,btv); + iounmap(btv->bt848_mmio); + release_mem_region(pci_resource_start(btv->c.pci,0), + pci_resource_len(btv->c.pci,0)); + + v4l2_device_unregister(&btv->c.v4l2_dev); + bttvs[btv->c.nr] = NULL; + kfree(btv); + + return; +} + +#ifdef CONFIG_PM +static int bttv_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct bttv *btv = to_bttv(v4l2_dev); + struct bttv_buffer_set idle; + unsigned long flags; + + dprintk("%d: suspend %d\n", btv->c.nr, state.event); + + /* stop dma + irqs */ + spin_lock_irqsave(&btv->s_lock,flags); + memset(&idle, 0, sizeof(idle)); + btv->state.video = btv->curr; + btv->state.vbi = btv->cvbi; + btv->state.loop_irq = btv->loop_irq; + btv->curr = idle; + btv->loop_irq = 0; + bttv_buffer_activate_video(btv, &idle); + bttv_buffer_activate_vbi(btv, NULL); + bttv_set_dma(btv, 0); + btwrite(0, BT848_INT_MASK); + spin_unlock_irqrestore(&btv->s_lock,flags); + + /* save bt878 state */ + btv->state.gpio_enable = btread(BT848_GPIO_OUT_EN); + btv->state.gpio_data = gpio_read(); + + /* save pci state */ + pci_save_state(pci_dev); + if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) { + pci_disable_device(pci_dev); + btv->state.disabled = 1; + } + return 0; +} + +static int bttv_resume(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct bttv *btv = to_bttv(v4l2_dev); + unsigned long flags; + int err; + + dprintk("%d: resume\n", btv->c.nr); + + /* restore pci state */ + if (btv->state.disabled) { + err=pci_enable_device(pci_dev); + if (err) { + pr_warn("%d: Can't enable device\n", btv->c.nr); + return err; + } + btv->state.disabled = 0; + } + err=pci_set_power_state(pci_dev, PCI_D0); + if (err) { + pci_disable_device(pci_dev); + pr_warn("%d: Can't enable device\n", btv->c.nr); + btv->state.disabled = 1; + return err; + } + + pci_restore_state(pci_dev); + + /* restore bt878 state */ + bttv_reinit_bt848(btv); + gpio_inout(0xffffff, btv->state.gpio_enable); + gpio_write(btv->state.gpio_data); + + /* restart dma */ + spin_lock_irqsave(&btv->s_lock,flags); + btv->curr = btv->state.video; + btv->cvbi = btv->state.vbi; + btv->loop_irq = btv->state.loop_irq; + bttv_buffer_activate_video(btv, &btv->curr); + bttv_buffer_activate_vbi(btv, btv->cvbi); + bttv_set_dma(btv, 0); + spin_unlock_irqrestore(&btv->s_lock,flags); + return 0; +} +#endif + +static struct pci_device_id bttv_pci_tbl[] = { + {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT848), 0}, + {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT849), 0}, + {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT878), 0}, + {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT879), 0}, + {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_FUSION879), 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, bttv_pci_tbl); + +static struct pci_driver bttv_pci_driver = { + .name = "bttv", + .id_table = bttv_pci_tbl, + .probe = bttv_probe, + .remove = __devexit_p(bttv_remove), +#ifdef CONFIG_PM + .suspend = bttv_suspend, + .resume = bttv_resume, +#endif +}; + +static int __init bttv_init_module(void) +{ + int ret; + + bttv_num = 0; + + pr_info("driver version %s loaded\n", BTTV_VERSION); + if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) + gbuffers = 2; + if (gbufsize > BTTV_MAX_FBUF) + gbufsize = BTTV_MAX_FBUF; + gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; + if (bttv_verbose) + pr_info("using %d buffers with %dk (%d pages) each for capture\n", + gbuffers, gbufsize >> 10, gbufsize >> PAGE_SHIFT); + + bttv_check_chipset(); + + ret = bus_register(&bttv_sub_bus_type); + if (ret < 0) { + pr_warn("bus_register error: %d\n", ret); + return ret; + } + ret = pci_register_driver(&bttv_pci_driver); + if (ret < 0) + bus_unregister(&bttv_sub_bus_type); + + return ret; +} + +static void __exit bttv_cleanup_module(void) +{ + pci_unregister_driver(&bttv_pci_driver); + bus_unregister(&bttv_sub_bus_type); +} + +module_init(bttv_init_module); +module_exit(bttv_cleanup_module); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bt8xx/bttv-gpio.c b/drivers/media/pci/bt8xx/bttv-gpio.c similarity index 100% rename from drivers/media/video/bt8xx/bttv-gpio.c rename to drivers/media/pci/bt8xx/bttv-gpio.c diff --git a/drivers/media/video/bt8xx/bttv-i2c.c b/drivers/media/pci/bt8xx/bttv-i2c.c similarity index 100% rename from drivers/media/video/bt8xx/bttv-i2c.c rename to drivers/media/pci/bt8xx/bttv-i2c.c diff --git a/drivers/media/video/bt8xx/bttv-if.c b/drivers/media/pci/bt8xx/bttv-if.c similarity index 100% rename from drivers/media/video/bt8xx/bttv-if.c rename to drivers/media/pci/bt8xx/bttv-if.c diff --git a/drivers/media/video/bt8xx/bttv-input.c b/drivers/media/pci/bt8xx/bttv-input.c similarity index 100% rename from drivers/media/video/bt8xx/bttv-input.c rename to drivers/media/pci/bt8xx/bttv-input.c diff --git a/drivers/media/video/bt8xx/bttv-risc.c b/drivers/media/pci/bt8xx/bttv-risc.c similarity index 100% rename from drivers/media/video/bt8xx/bttv-risc.c rename to drivers/media/pci/bt8xx/bttv-risc.c diff --git a/drivers/media/video/bt8xx/bttv-vbi.c b/drivers/media/pci/bt8xx/bttv-vbi.c similarity index 100% rename from drivers/media/video/bt8xx/bttv-vbi.c rename to drivers/media/pci/bt8xx/bttv-vbi.c diff --git a/drivers/media/video/bt8xx/bttv.h b/drivers/media/pci/bt8xx/bttv.h similarity index 100% rename from drivers/media/video/bt8xx/bttv.h rename to drivers/media/pci/bt8xx/bttv.h diff --git a/drivers/media/video/bt8xx/bttvp.h b/drivers/media/pci/bt8xx/bttvp.h similarity index 100% rename from drivers/media/video/bt8xx/bttvp.h rename to drivers/media/pci/bt8xx/bttvp.h diff --git a/drivers/media/dvb/bt8xx/dst.c b/drivers/media/pci/bt8xx/dst.c similarity index 100% rename from drivers/media/dvb/bt8xx/dst.c rename to drivers/media/pci/bt8xx/dst.c diff --git a/drivers/media/pci/bt8xx/dst_ca.c b/drivers/media/pci/bt8xx/dst_ca.c new file mode 100644 index 000000000000..ee3884fbc9ce --- /dev/null +++ b/drivers/media/pci/bt8xx/dst_ca.c @@ -0,0 +1,726 @@ +/* + CA-driver for TwinHan DST Frontend/Card + + Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "dvbdev.h" +#include "dvb_frontend.h" +#include "dst_ca.h" +#include "dst_common.h" + +#define DST_CA_ERROR 0 +#define DST_CA_NOTICE 1 +#define DST_CA_INFO 2 +#define DST_CA_DEBUG 3 + +#define dprintk(x, y, z, format, arg...) do { \ + if (z) { \ + if ((x > DST_CA_ERROR) && (x > y)) \ + printk(KERN_ERR "%s: " format "\n", __func__ , ##arg); \ + else if ((x > DST_CA_NOTICE) && (x > y)) \ + printk(KERN_NOTICE "%s: " format "\n", __func__ , ##arg); \ + else if ((x > DST_CA_INFO) && (x > y)) \ + printk(KERN_INFO "%s: " format "\n", __func__ , ##arg); \ + else if ((x > DST_CA_DEBUG) && (x > y)) \ + printk(KERN_DEBUG "%s: " format "\n", __func__ , ##arg); \ + } else { \ + if (x > y) \ + printk(format, ## arg); \ + } \ +} while(0) + + +static DEFINE_MUTEX(dst_ca_mutex); +static unsigned int verbose = 5; +module_param(verbose, int, 0644); +MODULE_PARM_DESC(verbose, "verbose startup messages, default is 1 (yes)"); + +/* Need some more work */ +static int ca_set_slot_descr(void) +{ + /* We could make this more graceful ? */ + return -EOPNOTSUPP; +} + +/* Need some more work */ +static int ca_set_pid(void) +{ + /* We could make this more graceful ? */ + return -EOPNOTSUPP; +} + +static void put_command_and_length(u8 *data, int command, int length) +{ + data[0] = (command >> 16) & 0xff; + data[1] = (command >> 8) & 0xff; + data[2] = command & 0xff; + data[3] = length; +} + +static void put_checksum(u8 *check_string, int length) +{ + dprintk(verbose, DST_CA_DEBUG, 1, " Computing string checksum."); + dprintk(verbose, DST_CA_DEBUG, 1, " -> string length : 0x%02x", length); + check_string[length] = dst_check_sum (check_string, length); + dprintk(verbose, DST_CA_DEBUG, 1, " -> checksum : 0x%02x", check_string[length]); +} + +static int dst_ci_command(struct dst_state* state, u8 * data, u8 *ca_string, u8 len, int read) +{ + u8 reply; + + mutex_lock(&state->dst_mutex); + dst_comm_init(state); + msleep(65); + + if (write_dst(state, data, len)) { + dprintk(verbose, DST_CA_INFO, 1, " Write not successful, trying to recover"); + dst_error_recovery(state); + goto error; + } + if ((dst_pio_disable(state)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " DST PIO disable failed."); + goto error; + } + if (read_dst(state, &reply, GET_ACK) < 0) { + dprintk(verbose, DST_CA_INFO, 1, " Read not successful, trying to recover"); + dst_error_recovery(state); + goto error; + } + if (read) { + if (! dst_wait_dst_ready(state, LONG_DELAY)) { + dprintk(verbose, DST_CA_NOTICE, 1, " 8820 not ready"); + goto error; + } + if (read_dst(state, ca_string, 128) < 0) { /* Try to make this dynamic */ + dprintk(verbose, DST_CA_INFO, 1, " Read not successful, trying to recover"); + dst_error_recovery(state); + goto error; + } + } + mutex_unlock(&state->dst_mutex); + return 0; + +error: + mutex_unlock(&state->dst_mutex); + return -EIO; +} + + +static int dst_put_ci(struct dst_state *state, u8 *data, int len, u8 *ca_string, int read) +{ + u8 dst_ca_comm_err = 0; + + while (dst_ca_comm_err < RETRIES) { + dprintk(verbose, DST_CA_NOTICE, 1, " Put Command"); + if (dst_ci_command(state, data, ca_string, len, read)) { // If error + dst_error_recovery(state); + dst_ca_comm_err++; // work required here. + } else { + break; + } + } + + if(dst_ca_comm_err == RETRIES) + return -1; + + return 0; +} + + + +static int ca_get_app_info(struct dst_state *state) +{ + int length, str_length; + static u8 command[8] = {0x07, 0x40, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff}; + + put_checksum(&command[0], command[0]); + if ((dst_put_ci(state, command, sizeof(command), state->messages, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); + return -1; + } + dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !"); + dprintk(verbose, DST_CA_INFO, 1, " ================================ CI Module Application Info ======================================"); + dprintk(verbose, DST_CA_INFO, 1, " Application Type=[%d], Application Vendor=[%d], Vendor Code=[%d]\n%s: Application info=[%s]", + state->messages[7], (state->messages[8] << 8) | state->messages[9], + (state->messages[10] << 8) | state->messages[11], __func__, (char *)(&state->messages[12])); + dprintk(verbose, DST_CA_INFO, 1, " =================================================================================================="); + + // Transform dst message to correct application_info message + length = state->messages[5]; + str_length = length - 6; + if (str_length < 0) { + str_length = 0; + dprintk(verbose, DST_CA_ERROR, 1, "Invalid string length returned in ca_get_app_info(). Recovering."); + } + + // First, the command and length fields + put_command_and_length(&state->messages[0], CA_APP_INFO, length); + + // Copy application_type, application_manufacturer and manufacturer_code + memcpy(&state->messages[4], &state->messages[7], 5); + + // Set string length and copy string + state->messages[9] = str_length; + memcpy(&state->messages[10], &state->messages[12], str_length); + + return 0; +} + +static int ca_get_ca_info(struct dst_state *state) +{ + int srcPtr, dstPtr, i, num_ids; + static u8 slot_command[8] = {0x07, 0x40, 0x00, 0x00, 0x02, 0x00, 0x00, 0xff}; + const int in_system_id_pos = 8, out_system_id_pos = 4, in_num_ids_pos = 7; + + put_checksum(&slot_command[0], slot_command[0]); + if ((dst_put_ci(state, slot_command, sizeof (slot_command), state->messages, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); + return -1; + } + dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !"); + + // Print raw data + dprintk(verbose, DST_CA_INFO, 0, " DST data = ["); + for (i = 0; i < state->messages[0] + 1; i++) { + dprintk(verbose, DST_CA_INFO, 0, " 0x%02x", state->messages[i]); + } + dprintk(verbose, DST_CA_INFO, 0, "]\n"); + + // Set the command and length of the output + num_ids = state->messages[in_num_ids_pos]; + if (num_ids >= 100) { + num_ids = 100; + dprintk(verbose, DST_CA_ERROR, 1, "Invalid number of ids (>100). Recovering."); + } + put_command_and_length(&state->messages[0], CA_INFO, num_ids * 2); + + dprintk(verbose, DST_CA_INFO, 0, " CA_INFO = ["); + srcPtr = in_system_id_pos; + dstPtr = out_system_id_pos; + for(i = 0; i < num_ids; i++) { + dprintk(verbose, DST_CA_INFO, 0, " 0x%02x%02x", state->messages[srcPtr + 0], state->messages[srcPtr + 1]); + // Append to output + state->messages[dstPtr + 0] = state->messages[srcPtr + 0]; + state->messages[dstPtr + 1] = state->messages[srcPtr + 1]; + srcPtr += 2; + dstPtr += 2; + } + dprintk(verbose, DST_CA_INFO, 0, "]\n"); + + return 0; +} + +static int ca_get_slot_caps(struct dst_state *state, struct ca_caps *p_ca_caps, void __user *arg) +{ + int i; + u8 slot_cap[256]; + static u8 slot_command[8] = {0x07, 0x40, 0x02, 0x00, 0x02, 0x00, 0x00, 0xff}; + + put_checksum(&slot_command[0], slot_command[0]); + if ((dst_put_ci(state, slot_command, sizeof (slot_command), slot_cap, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); + return -1; + } + dprintk(verbose, DST_CA_NOTICE, 1, " -->dst_put_ci SUCCESS !"); + + /* Will implement the rest soon */ + + dprintk(verbose, DST_CA_INFO, 1, " Slot cap = [%d]", slot_cap[7]); + dprintk(verbose, DST_CA_INFO, 0, "===================================\n"); + for (i = 0; i < slot_cap[0] + 1; i++) + dprintk(verbose, DST_CA_INFO, 0, " %d", slot_cap[i]); + dprintk(verbose, DST_CA_INFO, 0, "\n"); + + p_ca_caps->slot_num = 1; + p_ca_caps->slot_type = 1; + p_ca_caps->descr_num = slot_cap[7]; + p_ca_caps->descr_type = 1; + + if (copy_to_user(arg, p_ca_caps, sizeof (struct ca_caps))) + return -EFAULT; + + return 0; +} + +/* Need some more work */ +static int ca_get_slot_descr(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg) +{ + return -EOPNOTSUPP; +} + + +static int ca_get_slot_info(struct dst_state *state, struct ca_slot_info *p_ca_slot_info, void __user *arg) +{ + int i; + static u8 slot_command[8] = {0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}; + + u8 *slot_info = state->messages; + + put_checksum(&slot_command[0], 7); + if ((dst_put_ci(state, slot_command, sizeof (slot_command), slot_info, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !"); + return -1; + } + dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !"); + + /* Will implement the rest soon */ + + dprintk(verbose, DST_CA_INFO, 1, " Slot info = [%d]", slot_info[3]); + dprintk(verbose, DST_CA_INFO, 0, "===================================\n"); + for (i = 0; i < 8; i++) + dprintk(verbose, DST_CA_INFO, 0, " %d", slot_info[i]); + dprintk(verbose, DST_CA_INFO, 0, "\n"); + + if (slot_info[4] & 0x80) { + p_ca_slot_info->flags = CA_CI_MODULE_PRESENT; + p_ca_slot_info->num = 1; + p_ca_slot_info->type = CA_CI; + } else if (slot_info[4] & 0x40) { + p_ca_slot_info->flags = CA_CI_MODULE_READY; + p_ca_slot_info->num = 1; + p_ca_slot_info->type = CA_CI; + } else + p_ca_slot_info->flags = 0; + + if (copy_to_user(arg, p_ca_slot_info, sizeof (struct ca_slot_info))) + return -EFAULT; + + return 0; +} + + +static int ca_get_message(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg) +{ + u8 i = 0; + u32 command = 0; + + if (copy_from_user(p_ca_message, arg, sizeof (struct ca_msg))) + return -EFAULT; + + if (p_ca_message->msg) { + dprintk(verbose, DST_CA_NOTICE, 1, " Message = [%*ph]", + 3, p_ca_message->msg); + + for (i = 0; i < 3; i++) { + command = command | p_ca_message->msg[i]; + if (i < 2) + command = command << 8; + } + dprintk(verbose, DST_CA_NOTICE, 1, " Command=[0x%x]", command); + + switch (command) { + case CA_APP_INFO: + memcpy(p_ca_message->msg, state->messages, 128); + if (copy_to_user(arg, p_ca_message, sizeof (struct ca_msg)) ) + return -EFAULT; + break; + case CA_INFO: + memcpy(p_ca_message->msg, state->messages, 128); + if (copy_to_user(arg, p_ca_message, sizeof (struct ca_msg)) ) + return -EFAULT; + break; + } + } + + return 0; +} + +static int handle_dst_tag(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer, u32 length) +{ + if (state->dst_hw_cap & DST_TYPE_HAS_SESSION) { + hw_buffer->msg[2] = p_ca_message->msg[1]; /* MSB */ + hw_buffer->msg[3] = p_ca_message->msg[2]; /* LSB */ + } else { + if (length > 247) { + dprintk(verbose, DST_CA_ERROR, 1, " Message too long ! *** Bailing Out *** !"); + return -1; + } + hw_buffer->msg[0] = (length & 0xff) + 7; + hw_buffer->msg[1] = 0x40; + hw_buffer->msg[2] = 0x03; + hw_buffer->msg[3] = 0x00; + hw_buffer->msg[4] = 0x03; + hw_buffer->msg[5] = length & 0xff; + hw_buffer->msg[6] = 0x00; + + /* + * Need to compute length for EN50221 section 8.3.2, for the time being + * assuming 8.3.2 is not applicable + */ + memcpy(&hw_buffer->msg[7], &p_ca_message->msg[4], length); + } + + return 0; +} + +static int write_to_8820(struct dst_state *state, struct ca_msg *hw_buffer, u8 length, u8 reply) +{ + if ((dst_put_ci(state, hw_buffer->msg, length, hw_buffer->msg, reply)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " DST-CI Command failed."); + dprintk(verbose, DST_CA_NOTICE, 1, " Resetting DST."); + rdc_reset_state(state); + return -1; + } + dprintk(verbose, DST_CA_NOTICE, 1, " DST-CI Command success."); + + return 0; +} + +static u32 asn_1_decode(u8 *asn_1_array) +{ + u8 length_field = 0, word_count = 0, count = 0; + u32 length = 0; + + length_field = asn_1_array[0]; + dprintk(verbose, DST_CA_DEBUG, 1, " Length field=[%02x]", length_field); + if (length_field < 0x80) { + length = length_field & 0x7f; + dprintk(verbose, DST_CA_DEBUG, 1, " Length=[%02x]\n", length); + } else { + word_count = length_field & 0x7f; + for (count = 0; count < word_count; count++) { + length = length << 8; + length += asn_1_array[count + 1]; + dprintk(verbose, DST_CA_DEBUG, 1, " Length=[%04x]", length); + } + } + return length; +} + +static int debug_string(u8 *msg, u32 length, u32 offset) +{ + u32 i; + + dprintk(verbose, DST_CA_DEBUG, 0, " String=[ "); + for (i = offset; i < length; i++) + dprintk(verbose, DST_CA_DEBUG, 0, "%02x ", msg[i]); + dprintk(verbose, DST_CA_DEBUG, 0, "]\n"); + + return 0; +} + + +static int ca_set_pmt(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer, u8 reply, u8 query) +{ + u32 length = 0; + u8 tag_length = 8; + + length = asn_1_decode(&p_ca_message->msg[3]); + dprintk(verbose, DST_CA_DEBUG, 1, " CA Message length=[%d]", length); + debug_string(&p_ca_message->msg[4], length, 0); /* length is excluding tag & length */ + + memset(hw_buffer->msg, '\0', length); + handle_dst_tag(state, p_ca_message, hw_buffer, length); + put_checksum(hw_buffer->msg, hw_buffer->msg[0]); + + debug_string(hw_buffer->msg, (length + tag_length), 0); /* tags too */ + write_to_8820(state, hw_buffer, (length + tag_length), reply); + + return 0; +} + + +/* Board supports CA PMT reply ? */ +static int dst_check_ca_pmt(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer) +{ + int ca_pmt_reply_test = 0; + + /* Do test board */ + /* Not there yet but soon */ + + /* CA PMT Reply capable */ + if (ca_pmt_reply_test) { + if ((ca_set_pmt(state, p_ca_message, hw_buffer, 1, GET_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " ca_set_pmt.. failed !"); + return -1; + } + + /* Process CA PMT Reply */ + /* will implement soon */ + dprintk(verbose, DST_CA_ERROR, 1, " Not there yet"); + } + /* CA PMT Reply not capable */ + if (!ca_pmt_reply_test) { + if ((ca_set_pmt(state, p_ca_message, hw_buffer, 0, NO_REPLY)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " ca_set_pmt.. failed !"); + return -1; + } + dprintk(verbose, DST_CA_NOTICE, 1, " ca_set_pmt.. success !"); + /* put a dummy message */ + + } + return 0; +} + +static int ca_send_message(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg) +{ + int i = 0; + + u32 command = 0; + struct ca_msg *hw_buffer; + int result = 0; + + if ((hw_buffer = kmalloc(sizeof (struct ca_msg), GFP_KERNEL)) == NULL) { + dprintk(verbose, DST_CA_ERROR, 1, " Memory allocation failure"); + return -ENOMEM; + } + dprintk(verbose, DST_CA_DEBUG, 1, " "); + + if (copy_from_user(p_ca_message, arg, sizeof (struct ca_msg))) { + result = -EFAULT; + goto free_mem_and_exit; + } + + + if (p_ca_message->msg) { + /* EN50221 tag */ + command = 0; + + for (i = 0; i < 3; i++) { + command = command | p_ca_message->msg[i]; + if (i < 2) + command = command << 8; + } + dprintk(verbose, DST_CA_DEBUG, 1, " Command=[0x%x]\n", command); + + switch (command) { + case CA_PMT: + dprintk(verbose, DST_CA_DEBUG, 1, "Command = SEND_CA_PMT"); + if ((ca_set_pmt(state, p_ca_message, hw_buffer, 0, 0)) < 0) { // code simplification started + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_PMT Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_PMT Success !"); + break; + case CA_PMT_REPLY: + dprintk(verbose, DST_CA_INFO, 1, "Command = CA_PMT_REPLY"); + /* Have to handle the 2 basic types of cards here */ + if ((dst_check_ca_pmt(state, p_ca_message, hw_buffer)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_PMT_REPLY Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_PMT_REPLY Success !"); + break; + case CA_APP_INFO_ENQUIRY: // only for debugging + dprintk(verbose, DST_CA_INFO, 1, " Getting Cam Application information"); + + if ((ca_get_app_info(state)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_APP_INFO_ENQUIRY Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_APP_INFO_ENQUIRY Success !"); + break; + case CA_INFO_ENQUIRY: + dprintk(verbose, DST_CA_INFO, 1, " Getting CA Information"); + + if ((ca_get_ca_info(state)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_INFO_ENQUIRY Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_INFO_ENQUIRY Success !"); + break; + } + } +free_mem_and_exit: + kfree (hw_buffer); + + return result; +} + +static long dst_ca_ioctl(struct file *file, unsigned int cmd, unsigned long ioctl_arg) +{ + struct dvb_device *dvbdev; + struct dst_state *state; + struct ca_slot_info *p_ca_slot_info; + struct ca_caps *p_ca_caps; + struct ca_msg *p_ca_message; + void __user *arg = (void __user *)ioctl_arg; + int result = 0; + + mutex_lock(&dst_ca_mutex); + dvbdev = file->private_data; + state = (struct dst_state *)dvbdev->priv; + p_ca_message = kmalloc(sizeof (struct ca_msg), GFP_KERNEL); + p_ca_slot_info = kmalloc(sizeof (struct ca_slot_info), GFP_KERNEL); + p_ca_caps = kmalloc(sizeof (struct ca_caps), GFP_KERNEL); + if (!p_ca_message || !p_ca_slot_info || !p_ca_caps) { + dprintk(verbose, DST_CA_ERROR, 1, " Memory allocation failure"); + result = -ENOMEM; + goto free_mem_and_exit; + } + + /* We have now only the standard ioctl's, the driver is upposed to handle internals. */ + switch (cmd) { + case CA_SEND_MSG: + dprintk(verbose, DST_CA_INFO, 1, " Sending message"); + if ((ca_send_message(state, p_ca_message, arg)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SEND_MSG Failed !"); + result = -1; + goto free_mem_and_exit; + } + break; + case CA_GET_MSG: + dprintk(verbose, DST_CA_INFO, 1, " Getting message"); + if ((ca_get_message(state, p_ca_message, arg)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_MSG Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_MSG Success !"); + break; + case CA_RESET: + dprintk(verbose, DST_CA_ERROR, 1, " Resetting DST"); + dst_error_bailout(state); + msleep(4000); + break; + case CA_GET_SLOT_INFO: + dprintk(verbose, DST_CA_INFO, 1, " Getting Slot info"); + if ((ca_get_slot_info(state, p_ca_slot_info, arg)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_SLOT_INFO Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_SLOT_INFO Success !"); + break; + case CA_GET_CAP: + dprintk(verbose, DST_CA_INFO, 1, " Getting Slot capabilities"); + if ((ca_get_slot_caps(state, p_ca_caps, arg)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_CAP Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_CAP Success !"); + break; + case CA_GET_DESCR_INFO: + dprintk(verbose, DST_CA_INFO, 1, " Getting descrambler description"); + if ((ca_get_slot_descr(state, p_ca_message, arg)) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_DESCR_INFO Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_DESCR_INFO Success !"); + break; + case CA_SET_DESCR: + dprintk(verbose, DST_CA_INFO, 1, " Setting descrambler"); + if ((ca_set_slot_descr()) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SET_DESCR Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_SET_DESCR Success !"); + break; + case CA_SET_PID: + dprintk(verbose, DST_CA_INFO, 1, " Setting PID"); + if ((ca_set_pid()) < 0) { + dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SET_PID Failed !"); + result = -1; + goto free_mem_and_exit; + } + dprintk(verbose, DST_CA_INFO, 1, " -->CA_SET_PID Success !"); + default: + result = -EOPNOTSUPP; + }; + free_mem_and_exit: + kfree (p_ca_message); + kfree (p_ca_slot_info); + kfree (p_ca_caps); + + mutex_unlock(&dst_ca_mutex); + return result; +} + +static int dst_ca_open(struct inode *inode, struct file *file) +{ + dprintk(verbose, DST_CA_DEBUG, 1, " Device opened [%p] ", file); + try_module_get(THIS_MODULE); + + return 0; +} + +static int dst_ca_release(struct inode *inode, struct file *file) +{ + dprintk(verbose, DST_CA_DEBUG, 1, " Device closed."); + module_put(THIS_MODULE); + + return 0; +} + +static ssize_t dst_ca_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) +{ + ssize_t bytes_read = 0; + + dprintk(verbose, DST_CA_DEBUG, 1, " Device read."); + + return bytes_read; +} + +static ssize_t dst_ca_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) +{ + dprintk(verbose, DST_CA_DEBUG, 1, " Device write."); + + return 0; +} + +static const struct file_operations dst_ca_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dst_ca_ioctl, + .open = dst_ca_open, + .release = dst_ca_release, + .read = dst_ca_read, + .write = dst_ca_write, + .llseek = noop_llseek, +}; + +static struct dvb_device dvbdev_ca = { + .priv = NULL, + .users = 1, + .readers = 1, + .writers = 1, + .fops = &dst_ca_fops +}; + +struct dvb_device *dst_ca_attach(struct dst_state *dst, struct dvb_adapter *dvb_adapter) +{ + struct dvb_device *dvbdev; + + dprintk(verbose, DST_CA_ERROR, 1, "registering DST-CA device"); + if (dvb_register_device(dvb_adapter, &dvbdev, &dvbdev_ca, dst, DVB_DEVICE_CA) == 0) { + dst->dst_ca = dvbdev; + return dst->dst_ca; + } + + return NULL; +} + +EXPORT_SYMBOL(dst_ca_attach); + +MODULE_DESCRIPTION("DST DVB-S/T/C Combo CA driver"); +MODULE_AUTHOR("Manu Abraham"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/bt8xx/dst_ca.h b/drivers/media/pci/bt8xx/dst_ca.h similarity index 100% rename from drivers/media/dvb/bt8xx/dst_ca.h rename to drivers/media/pci/bt8xx/dst_ca.h diff --git a/drivers/media/dvb/bt8xx/dst_common.h b/drivers/media/pci/bt8xx/dst_common.h similarity index 100% rename from drivers/media/dvb/bt8xx/dst_common.h rename to drivers/media/pci/bt8xx/dst_common.h diff --git a/drivers/media/dvb/bt8xx/dst_priv.h b/drivers/media/pci/bt8xx/dst_priv.h similarity index 100% rename from drivers/media/dvb/bt8xx/dst_priv.h rename to drivers/media/pci/bt8xx/dst_priv.h diff --git a/drivers/media/dvb/bt8xx/dvb-bt8xx.c b/drivers/media/pci/bt8xx/dvb-bt8xx.c similarity index 100% rename from drivers/media/dvb/bt8xx/dvb-bt8xx.c rename to drivers/media/pci/bt8xx/dvb-bt8xx.c diff --git a/drivers/media/dvb/bt8xx/dvb-bt8xx.h b/drivers/media/pci/bt8xx/dvb-bt8xx.h similarity index 100% rename from drivers/media/dvb/bt8xx/dvb-bt8xx.h rename to drivers/media/pci/bt8xx/dvb-bt8xx.h diff --git a/drivers/media/pci/cx18/Kconfig b/drivers/media/pci/cx18/Kconfig new file mode 100644 index 000000000000..c675b83c43a9 --- /dev/null +++ b/drivers/media/pci/cx18/Kconfig @@ -0,0 +1,35 @@ +config VIDEO_CX18 + tristate "Conexant cx23418 MPEG encoder support" + depends on VIDEO_V4L2 && DVB_CORE && PCI && I2C + select I2C_ALGOBIT + select VIDEOBUF_VMALLOC + depends on RC_CORE + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_CX2341X + select VIDEO_CS5345 + select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + This is a video4linux driver for Conexant cx23418 based + PCI combo video recorder devices. + + This is used in devices such as the Hauppauge HVR-1600 + cards. + + To compile this driver as a module, choose M here: the + module will be called cx18. + +config VIDEO_CX18_ALSA + tristate "Conexant 23418 DMA audio support" + depends on VIDEO_CX18 && SND + select SND_PCM + ---help--- + This is a video4linux driver for direct (DMA) audio on + Conexant 23418 based TV cards using ALSA. + + To compile this driver as a module, choose M here: the + module will be called cx18-alsa. diff --git a/drivers/media/pci/cx18/Makefile b/drivers/media/pci/cx18/Makefile new file mode 100644 index 000000000000..d3ff1545c2c5 --- /dev/null +++ b/drivers/media/pci/cx18/Makefile @@ -0,0 +1,13 @@ +cx18-objs := cx18-driver.o cx18-cards.o cx18-i2c.o cx18-firmware.o cx18-gpio.o \ + cx18-queue.o cx18-streams.o cx18-fileops.o cx18-ioctl.o cx18-controls.o \ + cx18-mailbox.o cx18-vbi.o cx18-audio.o cx18-video.o cx18-irq.o \ + cx18-av-core.o cx18-av-audio.o cx18-av-firmware.o cx18-av-vbi.o cx18-scb.o \ + cx18-dvb.o cx18-io.o +cx18-alsa-objs := cx18-alsa-main.o cx18-alsa-pcm.o + +obj-$(CONFIG_VIDEO_CX18) += cx18.o +obj-$(CONFIG_VIDEO_CX18_ALSA) += cx18-alsa.o + +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += -Idrivers/media/dvb-frontends +ccflags-y += -Idrivers/media/tuners diff --git a/drivers/media/video/cx18/cx18-alsa-main.c b/drivers/media/pci/cx18/cx18-alsa-main.c similarity index 100% rename from drivers/media/video/cx18/cx18-alsa-main.c rename to drivers/media/pci/cx18/cx18-alsa-main.c diff --git a/drivers/media/video/cx18/cx18-alsa-mixer.c b/drivers/media/pci/cx18/cx18-alsa-mixer.c similarity index 100% rename from drivers/media/video/cx18/cx18-alsa-mixer.c rename to drivers/media/pci/cx18/cx18-alsa-mixer.c diff --git a/drivers/media/video/cx18/cx18-alsa-mixer.h b/drivers/media/pci/cx18/cx18-alsa-mixer.h similarity index 100% rename from drivers/media/video/cx18/cx18-alsa-mixer.h rename to drivers/media/pci/cx18/cx18-alsa-mixer.h diff --git a/drivers/media/video/cx18/cx18-alsa-pcm.c b/drivers/media/pci/cx18/cx18-alsa-pcm.c similarity index 100% rename from drivers/media/video/cx18/cx18-alsa-pcm.c rename to drivers/media/pci/cx18/cx18-alsa-pcm.c diff --git a/drivers/media/video/cx18/cx18-alsa-pcm.h b/drivers/media/pci/cx18/cx18-alsa-pcm.h similarity index 100% rename from drivers/media/video/cx18/cx18-alsa-pcm.h rename to drivers/media/pci/cx18/cx18-alsa-pcm.h diff --git a/drivers/media/video/cx18/cx18-alsa.h b/drivers/media/pci/cx18/cx18-alsa.h similarity index 100% rename from drivers/media/video/cx18/cx18-alsa.h rename to drivers/media/pci/cx18/cx18-alsa.h diff --git a/drivers/media/video/cx18/cx18-audio.c b/drivers/media/pci/cx18/cx18-audio.c similarity index 100% rename from drivers/media/video/cx18/cx18-audio.c rename to drivers/media/pci/cx18/cx18-audio.c diff --git a/drivers/media/video/cx18/cx18-audio.h b/drivers/media/pci/cx18/cx18-audio.h similarity index 100% rename from drivers/media/video/cx18/cx18-audio.h rename to drivers/media/pci/cx18/cx18-audio.h diff --git a/drivers/media/video/cx18/cx18-av-audio.c b/drivers/media/pci/cx18/cx18-av-audio.c similarity index 100% rename from drivers/media/video/cx18/cx18-av-audio.c rename to drivers/media/pci/cx18/cx18-av-audio.c diff --git a/drivers/media/video/cx18/cx18-av-core.c b/drivers/media/pci/cx18/cx18-av-core.c similarity index 100% rename from drivers/media/video/cx18/cx18-av-core.c rename to drivers/media/pci/cx18/cx18-av-core.c diff --git a/drivers/media/video/cx18/cx18-av-core.h b/drivers/media/pci/cx18/cx18-av-core.h similarity index 100% rename from drivers/media/video/cx18/cx18-av-core.h rename to drivers/media/pci/cx18/cx18-av-core.h diff --git a/drivers/media/pci/cx18/cx18-av-firmware.c b/drivers/media/pci/cx18/cx18-av-firmware.c new file mode 100644 index 000000000000..a34fd082b76e --- /dev/null +++ b/drivers/media/pci/cx18/cx18-av-firmware.c @@ -0,0 +1,225 @@ +/* + * cx18 ADEC firmware functions + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + * + * 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. + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include + +#define CX18_AUDIO_ENABLE 0xc72014 +#define CX18_AI1_MUX_MASK 0x30 +#define CX18_AI1_MUX_I2S1 0x00 +#define CX18_AI1_MUX_I2S2 0x10 +#define CX18_AI1_MUX_843_I2S 0x20 +#define CX18_AI1_MUX_INVALID 0x30 + +#define FWFILE "v4l-cx23418-dig.fw" + +static int cx18_av_verifyfw(struct cx18 *cx, const struct firmware *fw) +{ + struct v4l2_subdev *sd = &cx->av_state.sd; + int ret = 0; + const u8 *data; + u32 size; + int addr; + u32 expected, dl_control; + + /* Ensure we put the 8051 in reset and enable firmware upload mode */ + dl_control = cx18_av_read4(cx, CXADEC_DL_CTL); + do { + dl_control &= 0x00ffffff; + dl_control |= 0x0f000000; + cx18_av_write4_noretry(cx, CXADEC_DL_CTL, dl_control); + dl_control = cx18_av_read4(cx, CXADEC_DL_CTL); + } while ((dl_control & 0xff000000) != 0x0f000000); + + /* Read and auto increment until at address 0x0000 */ + while (dl_control & 0x3fff) + dl_control = cx18_av_read4(cx, CXADEC_DL_CTL); + + data = fw->data; + size = fw->size; + for (addr = 0; addr < size; addr++) { + dl_control &= 0xffff3fff; /* ignore top 2 bits of address */ + expected = 0x0f000000 | ((u32)data[addr] << 16) | addr; + if (expected != dl_control) { + CX18_ERR_DEV(sd, "verification of %s firmware load " + "failed: expected %#010x got %#010x\n", + FWFILE, expected, dl_control); + ret = -EIO; + break; + } + dl_control = cx18_av_read4(cx, CXADEC_DL_CTL); + } + if (ret == 0) + CX18_INFO_DEV(sd, "verified load of %s firmware (%d bytes)\n", + FWFILE, size); + return ret; +} + +int cx18_av_loadfw(struct cx18 *cx) +{ + struct v4l2_subdev *sd = &cx->av_state.sd; + const struct firmware *fw = NULL; + u32 size; + u32 u, v; + const u8 *ptr; + int i; + int retries1 = 0; + + if (request_firmware(&fw, FWFILE, &cx->pci_dev->dev) != 0) { + CX18_ERR_DEV(sd, "unable to open firmware %s\n", FWFILE); + return -EINVAL; + } + + /* The firmware load often has byte errors, so allow for several + retries, both at byte level and at the firmware load level. */ + while (retries1 < 5) { + cx18_av_write4_expect(cx, CXADEC_CHIP_CTRL, 0x00010000, + 0x00008430, 0xffffffff); /* cx25843 */ + cx18_av_write_expect(cx, CXADEC_STD_DET_CTL, 0xf6, 0xf6, 0xff); + + /* Reset the Mako core, Register is alias of CXADEC_CHIP_CTRL */ + cx18_av_write4_expect(cx, 0x8100, 0x00010000, + 0x00008430, 0xffffffff); /* cx25843 */ + + /* Put the 8051 in reset and enable firmware upload */ + cx18_av_write4_noretry(cx, CXADEC_DL_CTL, 0x0F000000); + + ptr = fw->data; + size = fw->size; + + for (i = 0; i < size; i++) { + u32 dl_control = 0x0F000000 | i | ((u32)ptr[i] << 16); + u32 value = 0; + int retries2; + int unrec_err = 0; + + for (retries2 = 0; retries2 < CX18_MAX_MMIO_WR_RETRIES; + retries2++) { + cx18_av_write4_noretry(cx, CXADEC_DL_CTL, + dl_control); + udelay(10); + value = cx18_av_read4(cx, CXADEC_DL_CTL); + if (value == dl_control) + break; + /* Check if we can correct the byte by changing + the address. We can only write the lower + address byte of the address. */ + if ((value & 0x3F00) != (dl_control & 0x3F00)) { + unrec_err = 1; + break; + } + } + if (unrec_err || retries2 >= CX18_MAX_MMIO_WR_RETRIES) + break; + } + if (i == size) + break; + retries1++; + } + if (retries1 >= 5) { + CX18_ERR_DEV(sd, "unable to load firmware %s\n", FWFILE); + release_firmware(fw); + return -EIO; + } + + cx18_av_write4_expect(cx, CXADEC_DL_CTL, + 0x03000000 | fw->size, 0x03000000, 0x13000000); + + CX18_INFO_DEV(sd, "loaded %s firmware (%d bytes)\n", FWFILE, size); + + if (cx18_av_verifyfw(cx, fw) == 0) + cx18_av_write4_expect(cx, CXADEC_DL_CTL, + 0x13000000 | fw->size, 0x13000000, 0x13000000); + + /* Output to the 416 */ + cx18_av_and_or4(cx, CXADEC_PIN_CTRL1, ~0, 0x78000); + + /* Audio input control 1 set to Sony mode */ + /* Audio output input 2 is 0 for slave operation input */ + /* 0xC4000914[5]: 0 = left sample on WS=0, 1 = left sample on WS=1 */ + /* 0xC4000914[7]: 0 = Philips mode, 1 = Sony mode (1st SCK rising edge + after WS transition for first bit of audio word. */ + cx18_av_write4(cx, CXADEC_I2S_IN_CTL, 0x000000A0); + + /* Audio output control 1 is set to Sony mode */ + /* Audio output control 2 is set to 1 for master mode */ + /* 0xC4000918[5]: 0 = left sample on WS=0, 1 = left sample on WS=1 */ + /* 0xC4000918[7]: 0 = Philips mode, 1 = Sony mode (1st SCK rising edge + after WS transition for first bit of audio word. */ + /* 0xC4000918[8]: 0 = slave operation, 1 = master (SCK_OUT and WS_OUT + are generated) */ + cx18_av_write4(cx, CXADEC_I2S_OUT_CTL, 0x000001A0); + + /* set alt I2s master clock to /0x16 and enable alt divider i2s + passthrough */ + cx18_av_write4(cx, CXADEC_PIN_CFG3, 0x5600B687); + + cx18_av_write4_expect(cx, CXADEC_STD_DET_CTL, 0x000000F6, 0x000000F6, + 0x3F00FFFF); + /* CxDevWrReg(CXADEC_STD_DET_CTL, 0x000000FF); */ + + /* Set bit 0 in register 0x9CC to signify that this is MiniMe. */ + /* Register 0x09CC is defined by the Merlin firmware, and doesn't + have a name in the spec. */ + cx18_av_write4(cx, 0x09CC, 1); + + v = cx18_read_reg(cx, CX18_AUDIO_ENABLE); + /* If bit 11 is 1, clear bit 10 */ + if (v & 0x800) + cx18_write_reg_expect(cx, v & 0xFFFFFBFF, CX18_AUDIO_ENABLE, + 0, 0x400); + + /* Toggle the AI1 MUX */ + v = cx18_read_reg(cx, CX18_AUDIO_ENABLE); + u = v & CX18_AI1_MUX_MASK; + v &= ~CX18_AI1_MUX_MASK; + if (u == CX18_AI1_MUX_843_I2S || u == CX18_AI1_MUX_INVALID) { + /* Switch to I2S1 */ + v |= CX18_AI1_MUX_I2S1; + cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE, + v, CX18_AI1_MUX_MASK); + /* Switch back to the A/V decoder core I2S output */ + v = (v & ~CX18_AI1_MUX_MASK) | CX18_AI1_MUX_843_I2S; + } else { + /* Switch to the A/V decoder core I2S output */ + v |= CX18_AI1_MUX_843_I2S; + cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE, + v, CX18_AI1_MUX_MASK); + /* Switch back to I2S1 or I2S2 */ + v = (v & ~CX18_AI1_MUX_MASK) | u; + } + cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE, + v, CX18_AI1_MUX_MASK); + + /* Enable WW auto audio standard detection */ + v = cx18_av_read4(cx, CXADEC_STD_DET_CTL); + v |= 0xFF; /* Auto by default */ + v |= 0x400; /* Stereo by default */ + v |= 0x14000000; + cx18_av_write4_expect(cx, CXADEC_STD_DET_CTL, v, v, 0x3F00FFFF); + + release_firmware(fw); + return 0; +} + +MODULE_FIRMWARE(FWFILE); diff --git a/drivers/media/pci/cx18/cx18-av-vbi.c b/drivers/media/pci/cx18/cx18-av-vbi.c new file mode 100644 index 000000000000..246982841fec --- /dev/null +++ b/drivers/media/pci/cx18/cx18-av-vbi.c @@ -0,0 +1,313 @@ +/* + * cx18 ADEC VBI functions + * + * Derived from cx25840-vbi.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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. + */ + + +#include "cx18-driver.h" + +/* + * For sliced VBI output, we set up to use VIP-1.1, 8-bit mode, + * NN counts 1 byte Dwords, an IDID with the VBI line # in it. + * Thus, according to the VIP-2 Spec, our VBI ancillary data lines + * (should!) look like: + * 4 byte EAV code: 0xff 0x00 0x00 0xRP + * unknown number of possible idle bytes + * 3 byte Anc data preamble: 0x00 0xff 0xff + * 1 byte data identifier: ne010iii (parity bits, 010, DID bits) + * 1 byte secondary data id: nessssss (parity bits, SDID bits) + * 1 byte data word count: necccccc (parity bits, NN Dword count) + * 2 byte Internal DID: VBI-line-# 0x80 + * NN data bytes + * 1 byte checksum + * Fill bytes needed to fil out to 4*NN bytes of payload + * + * The RP codes for EAVs when in VIP-1.1 mode, not in raw mode, & + * in the vertical blanking interval are: + * 0xb0 (Task 0 VerticalBlank HorizontalBlank 0 0 0 0) + * 0xf0 (Task EvenField VerticalBlank HorizontalBlank 0 0 0 0) + * + * Since the V bit is only allowed to toggle in the EAV RP code, just + * before the first active region line and for active lines, they are: + * 0x90 (Task 0 0 HorizontalBlank 0 0 0 0) + * 0xd0 (Task EvenField 0 HorizontalBlank 0 0 0 0) + * + * The user application DID bytes we care about are: + * 0x91 (1 0 010 0 !ActiveLine AncDataPresent) + * 0x55 (0 1 010 2ndField !ActiveLine AncDataPresent) + * + */ +static const u8 sliced_vbi_did[2] = { 0x91, 0x55 }; + +struct vbi_anc_data { + /* u8 eav[4]; */ + /* u8 idle[]; Variable number of idle bytes */ + u8 preamble[3]; + u8 did; + u8 sdid; + u8 data_count; + u8 idid[2]; + u8 payload[1]; /* data_count of payload */ + /* u8 checksum; */ + /* u8 fill[]; Variable number of fill bytes */ +}; + +static int odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static int decode_vps(u8 *dst, u8 *p) +{ + static const u8 biphase_tbl[] = { + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87, + 0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3, + 0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85, + 0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86, + 0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2, + 0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84, + 0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + }; + + u8 c, err = 0; + int i; + + for (i = 0; i < 2 * 13; i += 2) { + err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]]; + c = (biphase_tbl[p[i + 1]] & 0xf) | + ((biphase_tbl[p[i]] & 0xf) << 4); + dst[i / 2] = c; + } + + return err & 0xf0; +} + +int cx18_av_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + struct cx18_av_state *state = &cx->av_state; + static const u16 lcr2vbi[] = { + 0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */ + 0, V4L2_SLICED_WSS_625, 0, /* 4 */ + V4L2_SLICED_CAPTION_525, /* 6 */ + 0, 0, V4L2_SLICED_VPS, 0, 0, /* 9 */ + 0, 0, 0, 0 + }; + int is_pal = !(state->std & V4L2_STD_525_60); + int i; + + memset(svbi->service_lines, 0, sizeof(svbi->service_lines)); + svbi->service_set = 0; + + /* we're done if raw VBI is active */ + if ((cx18_av_read(cx, 0x404) & 0x10) == 0) + return 0; + + if (is_pal) { + for (i = 7; i <= 23; i++) { + u8 v = cx18_av_read(cx, 0x424 + i - 7); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } else { + for (i = 10; i <= 21; i++) { + u8 v = cx18_av_read(cx, 0x424 + i - 10); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } + return 0; +} + +int cx18_av_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + struct cx18_av_state *state = &cx->av_state; + + /* Setup standard */ + cx18_av_std_setup(cx); + + /* VBI Offset */ + cx18_av_write(cx, 0x47f, state->slicer_line_delay); + cx18_av_write(cx, 0x404, 0x2e); + return 0; +} + +int cx18_av_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + struct cx18_av_state *state = &cx->av_state; + int is_pal = !(state->std & V4L2_STD_525_60); + int i, x; + u8 lcr[24]; + + for (x = 0; x <= 23; x++) + lcr[x] = 0x00; + + /* Setup standard */ + cx18_av_std_setup(cx); + + /* Sliced VBI */ + cx18_av_write(cx, 0x404, 0x32); /* Ancillary data */ + cx18_av_write(cx, 0x406, 0x13); + cx18_av_write(cx, 0x47f, state->slicer_line_delay); + + /* Force impossible lines to 0 */ + if (is_pal) { + for (i = 0; i <= 6; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } else { + for (i = 0; i <= 9; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + + for (i = 22; i <= 23; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } + + /* Build register values for requested service lines */ + for (i = 7; i <= 23; i++) { + for (x = 0; x <= 1; x++) { + switch (svbi->service_lines[1-x][i]) { + case V4L2_SLICED_TELETEXT_B: + lcr[i] |= 1 << (4 * x); + break; + case V4L2_SLICED_WSS_625: + lcr[i] |= 4 << (4 * x); + break; + case V4L2_SLICED_CAPTION_525: + lcr[i] |= 6 << (4 * x); + break; + case V4L2_SLICED_VPS: + lcr[i] |= 9 << (4 * x); + break; + } + } + } + + if (is_pal) { + for (x = 1, i = 0x424; i <= 0x434; i++, x++) + cx18_av_write(cx, i, lcr[6 + x]); + } else { + for (x = 1, i = 0x424; i <= 0x430; i++, x++) + cx18_av_write(cx, i, lcr[9 + x]); + for (i = 0x431; i <= 0x434; i++) + cx18_av_write(cx, i, 0); + } + + cx18_av_write(cx, 0x43c, 0x16); + /* Should match vblank set in cx18_av_std_setup() */ + cx18_av_write(cx, 0x474, is_pal ? 38 : 26); + return 0; +} + +int cx18_av_decode_vbi_line(struct v4l2_subdev *sd, + struct v4l2_decode_vbi_line *vbi) +{ + struct cx18 *cx = v4l2_get_subdevdata(sd); + struct cx18_av_state *state = &cx->av_state; + struct vbi_anc_data *anc = (struct vbi_anc_data *)vbi->p; + u8 *p; + int did, sdid, l, err = 0; + + /* + * Check for the ancillary data header for sliced VBI + */ + if (anc->preamble[0] || + anc->preamble[1] != 0xff || anc->preamble[2] != 0xff || + (anc->did != sliced_vbi_did[0] && + anc->did != sliced_vbi_did[1])) { + vbi->line = vbi->type = 0; + return 0; + } + + did = anc->did; + sdid = anc->sdid & 0xf; + l = anc->idid[0] & 0x3f; + l += state->slicer_line_offset; + p = anc->payload; + + /* Decode the SDID set by the slicer */ + switch (sdid) { + case 1: + sdid = V4L2_SLICED_TELETEXT_B; + break; + case 4: + sdid = V4L2_SLICED_WSS_625; + break; + case 6: + sdid = V4L2_SLICED_CAPTION_525; + err = !odd_parity(p[0]) || !odd_parity(p[1]); + break; + case 9: + sdid = V4L2_SLICED_VPS; + if (decode_vps(p, p) != 0) + err = 1; + break; + default: + sdid = 0; + err = 1; + break; + } + + vbi->type = err ? 0 : sdid; + vbi->line = err ? 0 : l; + vbi->is_second_field = err ? 0 : (did == sliced_vbi_did[1]); + vbi->p = p; + return 0; +} diff --git a/drivers/media/video/cx18/cx18-cards.c b/drivers/media/pci/cx18/cx18-cards.c similarity index 100% rename from drivers/media/video/cx18/cx18-cards.c rename to drivers/media/pci/cx18/cx18-cards.c diff --git a/drivers/media/video/cx18/cx18-cards.h b/drivers/media/pci/cx18/cx18-cards.h similarity index 100% rename from drivers/media/video/cx18/cx18-cards.h rename to drivers/media/pci/cx18/cx18-cards.h diff --git a/drivers/media/video/cx18/cx18-controls.c b/drivers/media/pci/cx18/cx18-controls.c similarity index 100% rename from drivers/media/video/cx18/cx18-controls.c rename to drivers/media/pci/cx18/cx18-controls.c diff --git a/drivers/media/video/cx18/cx18-controls.h b/drivers/media/pci/cx18/cx18-controls.h similarity index 100% rename from drivers/media/video/cx18/cx18-controls.h rename to drivers/media/pci/cx18/cx18-controls.h diff --git a/drivers/media/pci/cx18/cx18-driver.c b/drivers/media/pci/cx18/cx18-driver.c new file mode 100644 index 000000000000..039133d692e3 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-driver.c @@ -0,0 +1,1360 @@ +/* + * cx18 driver initialization and card probing + * + * Derived from ivtv-driver.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-version.h" +#include "cx18-cards.h" +#include "cx18-i2c.h" +#include "cx18-irq.h" +#include "cx18-gpio.h" +#include "cx18-firmware.h" +#include "cx18-queue.h" +#include "cx18-streams.h" +#include "cx18-av-core.h" +#include "cx18-scb.h" +#include "cx18-mailbox.h" +#include "cx18-ioctl.h" +#include "cx18-controls.h" +#include "tuner-xc2028.h" +#include +#include + +/* If you have already X v4l cards, then set this to X. This way + the device numbers stay matched. Example: you have a WinTV card + without radio and a Compro H900 with. Normally this would give a + video1 device together with a radio0 device for the Compro. By + setting this to 1 you ensure that radio0 is now also radio1. */ +int cx18_first_minor; + +/* Callback for registering extensions */ +int (*cx18_ext_init)(struct cx18 *); +EXPORT_SYMBOL(cx18_ext_init); + +/* add your revision and whatnot here */ +static struct pci_device_id cx18_pci_tbl[] __devinitdata = { + {PCI_VENDOR_ID_CX, PCI_DEVICE_ID_CX23418, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, cx18_pci_tbl); + +static atomic_t cx18_instance = ATOMIC_INIT(0); + +/* Parameter declarations */ +static int cardtype[CX18_MAX_CARDS]; +static int tuner[CX18_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int radio[CX18_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static unsigned cardtype_c = 1; +static unsigned tuner_c = 1; +static unsigned radio_c = 1; +static char pal[] = "--"; +static char secam[] = "--"; +static char ntsc[] = "-"; + +/* Buffers */ +static int enc_ts_buffers = CX18_DEFAULT_ENC_TS_BUFFERS; +static int enc_mpg_buffers = CX18_DEFAULT_ENC_MPG_BUFFERS; +static int enc_idx_buffers = CX18_DEFAULT_ENC_IDX_BUFFERS; +static int enc_yuv_buffers = CX18_DEFAULT_ENC_YUV_BUFFERS; +static int enc_vbi_buffers = CX18_DEFAULT_ENC_VBI_BUFFERS; +static int enc_pcm_buffers = CX18_DEFAULT_ENC_PCM_BUFFERS; + +static int enc_ts_bufsize = CX18_DEFAULT_ENC_TS_BUFSIZE; +static int enc_mpg_bufsize = CX18_DEFAULT_ENC_MPG_BUFSIZE; +static int enc_idx_bufsize = CX18_DEFAULT_ENC_IDX_BUFSIZE; +static int enc_yuv_bufsize = CX18_DEFAULT_ENC_YUV_BUFSIZE; +static int enc_pcm_bufsize = CX18_DEFAULT_ENC_PCM_BUFSIZE; + +static int enc_ts_bufs = -1; +static int enc_mpg_bufs = -1; +static int enc_idx_bufs = CX18_MAX_FW_MDLS_PER_STREAM; +static int enc_yuv_bufs = -1; +static int enc_vbi_bufs = -1; +static int enc_pcm_bufs = -1; + + +static int cx18_pci_latency = 1; + +static int mmio_ndelay; +static int retry_mmio = 1; + +int cx18_debug; + +module_param_array(tuner, int, &tuner_c, 0644); +module_param_array(radio, int, &radio_c, 0644); +module_param_array(cardtype, int, &cardtype_c, 0644); +module_param_string(pal, pal, sizeof(pal), 0644); +module_param_string(secam, secam, sizeof(secam), 0644); +module_param_string(ntsc, ntsc, sizeof(ntsc), 0644); +module_param_named(debug, cx18_debug, int, 0644); +module_param(mmio_ndelay, int, 0644); +module_param(retry_mmio, int, 0644); +module_param(cx18_pci_latency, int, 0644); +module_param(cx18_first_minor, int, 0644); + +module_param(enc_ts_buffers, int, 0644); +module_param(enc_mpg_buffers, int, 0644); +module_param(enc_idx_buffers, int, 0644); +module_param(enc_yuv_buffers, int, 0644); +module_param(enc_vbi_buffers, int, 0644); +module_param(enc_pcm_buffers, int, 0644); + +module_param(enc_ts_bufsize, int, 0644); +module_param(enc_mpg_bufsize, int, 0644); +module_param(enc_idx_bufsize, int, 0644); +module_param(enc_yuv_bufsize, int, 0644); +module_param(enc_pcm_bufsize, int, 0644); + +module_param(enc_ts_bufs, int, 0644); +module_param(enc_mpg_bufs, int, 0644); +module_param(enc_idx_bufs, int, 0644); +module_param(enc_yuv_bufs, int, 0644); +module_param(enc_vbi_bufs, int, 0644); +module_param(enc_pcm_bufs, int, 0644); + +MODULE_PARM_DESC(tuner, "Tuner type selection,\n" + "\t\t\tsee tuner.h for values"); +MODULE_PARM_DESC(radio, + "Enable or disable the radio. Use only if autodetection\n" + "\t\t\tfails. 0 = disable, 1 = enable"); +MODULE_PARM_DESC(cardtype, + "Only use this option if your card is not detected properly.\n" + "\t\tSpecify card type:\n" + "\t\t\t 1 = Hauppauge HVR 1600 (ESMT memory)\n" + "\t\t\t 2 = Hauppauge HVR 1600 (Samsung memory)\n" + "\t\t\t 3 = Compro VideoMate H900\n" + "\t\t\t 4 = Yuan MPC718\n" + "\t\t\t 5 = Conexant Raptor PAL/SECAM\n" + "\t\t\t 6 = Toshiba Qosmio DVB-T/Analog\n" + "\t\t\t 7 = Leadtek WinFast PVR2100\n" + "\t\t\t 8 = Leadtek WinFast DVR3100 H\n" + "\t\t\t 9 = GoTView PCI DVD3 Hybrid\n" + "\t\t\t 10 = Hauppauge HVR 1600 (S5H1411)\n" + "\t\t\t 0 = Autodetect (default)\n" + "\t\t\t-1 = Ignore this card\n\t\t"); +MODULE_PARM_DESC(pal, "Set PAL standard: B, G, H, D, K, I, M, N, Nc, 60"); +MODULE_PARM_DESC(secam, "Set SECAM standard: B, G, H, D, K, L, LC"); +MODULE_PARM_DESC(ntsc, "Set NTSC standard: M, J, K"); +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: 0\n" + "\t\t\t 1/0x0001: warning\n" + "\t\t\t 2/0x0002: info\n" + "\t\t\t 4/0x0004: mailbox\n" + "\t\t\t 8/0x0008: dma\n" + "\t\t\t 16/0x0010: ioctl\n" + "\t\t\t 32/0x0020: file\n" + "\t\t\t 64/0x0040: i2c\n" + "\t\t\t128/0x0080: irq\n" + "\t\t\t256/0x0100: high volume\n"); +MODULE_PARM_DESC(cx18_pci_latency, + "Change the PCI latency to 64 if lower: 0 = No, 1 = Yes,\n" + "\t\t\tDefault: Yes"); +MODULE_PARM_DESC(retry_mmio, + "(Deprecated) MMIO writes are now always checked and retried\n" + "\t\t\tEffectively: 1 [Yes]"); +MODULE_PARM_DESC(mmio_ndelay, + "(Deprecated) MMIO accesses are now never purposely delayed\n" + "\t\t\tEffectively: 0 ns"); +MODULE_PARM_DESC(enc_ts_buffers, + "Encoder TS buffer memory (MB). (enc_ts_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_TS_BUFFERS)); +MODULE_PARM_DESC(enc_ts_bufsize, + "Size of an encoder TS buffer (kB)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_TS_BUFSIZE)); +MODULE_PARM_DESC(enc_ts_bufs, + "Number of encoder TS buffers\n" + "\t\t\tDefault is computed from other enc_ts_* parameters"); +MODULE_PARM_DESC(enc_mpg_buffers, + "Encoder MPG buffer memory (MB). (enc_mpg_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_MPG_BUFFERS)); +MODULE_PARM_DESC(enc_mpg_bufsize, + "Size of an encoder MPG buffer (kB)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_MPG_BUFSIZE)); +MODULE_PARM_DESC(enc_mpg_bufs, + "Number of encoder MPG buffers\n" + "\t\t\tDefault is computed from other enc_mpg_* parameters"); +MODULE_PARM_DESC(enc_idx_buffers, + "(Deprecated) Encoder IDX buffer memory (MB)\n" + "\t\t\tIgnored, except 0 disables IDX buffer allocations\n" + "\t\t\tDefault: 1 [Enabled]"); +MODULE_PARM_DESC(enc_idx_bufsize, + "Size of an encoder IDX buffer (kB)\n" + "\t\t\tAllowed values are multiples of 1.5 kB rounded up\n" + "\t\t\t(multiples of size required for 64 index entries)\n" + "\t\t\tDefault: 2"); +MODULE_PARM_DESC(enc_idx_bufs, + "Number of encoder IDX buffers\n" + "\t\t\tDefault: " __stringify(CX18_MAX_FW_MDLS_PER_STREAM)); +MODULE_PARM_DESC(enc_yuv_buffers, + "Encoder YUV buffer memory (MB). (enc_yuv_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_YUV_BUFFERS)); +MODULE_PARM_DESC(enc_yuv_bufsize, + "Size of an encoder YUV buffer (kB)\n" + "\t\t\tAllowed values are multiples of 33.75 kB rounded up\n" + "\t\t\t(multiples of size required for 32 screen lines)\n" + "\t\t\tDefault: 102"); +MODULE_PARM_DESC(enc_yuv_bufs, + "Number of encoder YUV buffers\n" + "\t\t\tDefault is computed from other enc_yuv_* parameters"); +MODULE_PARM_DESC(enc_vbi_buffers, + "Encoder VBI buffer memory (MB). (enc_vbi_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_VBI_BUFFERS)); +MODULE_PARM_DESC(enc_vbi_bufs, + "Number of encoder VBI buffers\n" + "\t\t\tDefault is computed from enc_vbi_buffers"); +MODULE_PARM_DESC(enc_pcm_buffers, + "Encoder PCM buffer memory (MB). (enc_pcm_bufs can override)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_PCM_BUFFERS)); +MODULE_PARM_DESC(enc_pcm_bufsize, + "Size of an encoder PCM buffer (kB)\n" + "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_PCM_BUFSIZE)); +MODULE_PARM_DESC(enc_pcm_bufs, + "Number of encoder PCM buffers\n" + "\t\t\tDefault is computed from other enc_pcm_* parameters"); + +MODULE_PARM_DESC(cx18_first_minor, + "Set device node number assigned to first card"); + +MODULE_AUTHOR("Hans Verkuil"); +MODULE_DESCRIPTION("CX23418 driver"); +MODULE_SUPPORTED_DEVICE("CX23418 MPEG2 encoder"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(CX18_VERSION); + +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct cx18 *dev = container_of(work, struct cx18, request_module_wk); + + /* Make sure cx18-alsa module is loaded */ + request_module("cx18-alsa"); + + /* Initialize cx18-alsa for this instance of the cx18 device */ + if (cx18_ext_init != NULL) + cx18_ext_init(dev); +} + +static void request_modules(struct cx18 *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct cx18 *dev) +{ + flush_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) +#endif /* CONFIG_MODULES */ + +/* Generic utility functions */ +int cx18_msleep_timeout(unsigned int msecs, int intr) +{ + long int timeout = msecs_to_jiffies(msecs); + int sig; + + do { + set_current_state(intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + timeout = schedule_timeout(timeout); + sig = intr ? signal_pending(current) : 0; + } while (!sig && timeout); + return sig; +} + +/* Release ioremapped memory */ +static void cx18_iounmap(struct cx18 *cx) +{ + if (cx == NULL) + return; + + /* Release io memory */ + if (cx->enc_mem != NULL) { + CX18_DEBUG_INFO("releasing enc_mem\n"); + iounmap(cx->enc_mem); + cx->enc_mem = NULL; + } +} + +static void cx18_eeprom_dump(struct cx18 *cx, unsigned char *eedata, int len) +{ + int i; + + CX18_INFO("eeprom dump:\n"); + for (i = 0; i < len; i++) { + if (0 == (i % 16)) + CX18_INFO("eeprom %02x:", i); + printk(KERN_CONT " %02x", eedata[i]); + if (15 == (i % 16)) + printk(KERN_CONT "\n"); + } +} + +/* Hauppauge card? get values from tveeprom */ +void cx18_read_eeprom(struct cx18 *cx, struct tveeprom *tv) +{ + struct i2c_client c; + u8 eedata[256]; + + memset(&c, 0, sizeof(c)); + strlcpy(c.name, "cx18 tveeprom tmp", sizeof(c.name)); + c.adapter = &cx->i2c_adap[0]; + c.addr = 0xA0 >> 1; + + memset(tv, 0, sizeof(*tv)); + if (tveeprom_read(&c, eedata, sizeof(eedata))) + return; + + switch (cx->card->type) { + case CX18_CARD_HVR_1600_ESMT: + case CX18_CARD_HVR_1600_SAMSUNG: + case CX18_CARD_HVR_1600_S5H1411: + tveeprom_hauppauge_analog(&c, tv, eedata); + break; + case CX18_CARD_YUAN_MPC718: + case CX18_CARD_GOTVIEW_PCI_DVD3: + tv->model = 0x718; + cx18_eeprom_dump(cx, eedata, sizeof(eedata)); + CX18_INFO("eeprom PCI ID: %02x%02x:%02x%02x\n", + eedata[2], eedata[1], eedata[4], eedata[3]); + break; + default: + tv->model = 0xffffffff; + cx18_eeprom_dump(cx, eedata, sizeof(eedata)); + break; + } +} + +static void cx18_process_eeprom(struct cx18 *cx) +{ + struct tveeprom tv; + + cx18_read_eeprom(cx, &tv); + + /* Many thanks to Steven Toth from Hauppauge for providing the + model numbers */ + /* Note: the Samsung memory models cannot be reliably determined + from the model number. Use the cardtype module option if you + have one of these preproduction models. */ + switch (tv.model) { + case 74301: /* Retail models */ + case 74321: + case 74351: /* OEM models */ + case 74361: + /* Digital side is s5h1411/tda18271 */ + cx->card = cx18_get_card(CX18_CARD_HVR_1600_S5H1411); + break; + case 74021: /* Retail models */ + case 74031: + case 74041: + case 74141: + case 74541: /* OEM models */ + case 74551: + case 74591: + case 74651: + case 74691: + case 74751: + case 74891: + /* Digital side is s5h1409/mxl5005s */ + cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT); + break; + case 0x718: + return; + case 0xffffffff: + CX18_INFO("Unknown EEPROM encoding\n"); + return; + case 0: + CX18_ERR("Invalid EEPROM\n"); + return; + default: + CX18_ERR("Unknown model %d, defaulting to original HVR-1600 " + "(cardtype=1)\n", tv.model); + cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT); + break; + } + + cx->v4l2_cap = cx->card->v4l2_capabilities; + cx->card_name = cx->card->name; + cx->card_i2c = cx->card->i2c; + + CX18_INFO("Autodetected %s\n", cx->card_name); + + if (tv.tuner_type == TUNER_ABSENT) + CX18_ERR("tveeprom cannot autodetect tuner!\n"); + + if (cx->options.tuner == -1) + cx->options.tuner = tv.tuner_type; + if (cx->options.radio == -1) + cx->options.radio = (tv.has_radio != 0); + + if (cx->std != 0) + /* user specified tuner standard */ + return; + + /* autodetect tuner standard */ +#define TVEEPROM_TUNER_FORMAT_ALL (V4L2_STD_B | V4L2_STD_GH | \ + V4L2_STD_MN | \ + V4L2_STD_PAL_I | \ + V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC | \ + V4L2_STD_DK) + if ((tv.tuner_formats & TVEEPROM_TUNER_FORMAT_ALL) + == TVEEPROM_TUNER_FORMAT_ALL) { + CX18_DEBUG_INFO("Worldwide tuner detected\n"); + cx->std = V4L2_STD_ALL; + } else if (tv.tuner_formats & V4L2_STD_PAL) { + CX18_DEBUG_INFO("PAL tuner detected\n"); + cx->std |= V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + } else if (tv.tuner_formats & V4L2_STD_NTSC) { + CX18_DEBUG_INFO("NTSC tuner detected\n"); + cx->std |= V4L2_STD_NTSC_M; + } else if (tv.tuner_formats & V4L2_STD_SECAM) { + CX18_DEBUG_INFO("SECAM tuner detected\n"); + cx->std |= V4L2_STD_SECAM_L; + } else { + CX18_INFO("No tuner detected, default to NTSC-M\n"); + cx->std |= V4L2_STD_NTSC_M; + } +} + +static v4l2_std_id cx18_parse_std(struct cx18 *cx) +{ + switch (pal[0]) { + case '6': + return V4L2_STD_PAL_60; + case 'b': + case 'B': + case 'g': + case 'G': + return V4L2_STD_PAL_BG; + case 'h': + case 'H': + return V4L2_STD_PAL_H; + case 'n': + case 'N': + if (pal[1] == 'c' || pal[1] == 'C') + return V4L2_STD_PAL_Nc; + return V4L2_STD_PAL_N; + case 'i': + case 'I': + return V4L2_STD_PAL_I; + case 'd': + case 'D': + case 'k': + case 'K': + return V4L2_STD_PAL_DK; + case 'M': + case 'm': + return V4L2_STD_PAL_M; + case '-': + break; + default: + CX18_WARN("pal= argument not recognised\n"); + return 0; + } + + switch (secam[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + case 'h': + case 'H': + return V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H; + case 'd': + case 'D': + case 'k': + case 'K': + return V4L2_STD_SECAM_DK; + case 'l': + case 'L': + if (secam[1] == 'C' || secam[1] == 'c') + return V4L2_STD_SECAM_LC; + return V4L2_STD_SECAM_L; + case '-': + break; + default: + CX18_WARN("secam= argument not recognised\n"); + return 0; + } + + switch (ntsc[0]) { + case 'm': + case 'M': + return V4L2_STD_NTSC_M; + case 'j': + case 'J': + return V4L2_STD_NTSC_M_JP; + case 'k': + case 'K': + return V4L2_STD_NTSC_M_KR; + case '-': + break; + default: + CX18_WARN("ntsc= argument not recognised\n"); + return 0; + } + + /* no match found */ + return 0; +} + +static void cx18_process_options(struct cx18 *cx) +{ + int i, j; + + cx->options.megabytes[CX18_ENC_STREAM_TYPE_TS] = enc_ts_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_VBI] = enc_vbi_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_buffers; + cx->options.megabytes[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control only */ + + cx->stream_buffers[CX18_ENC_STREAM_TYPE_TS] = enc_ts_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_VBI] = enc_vbi_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_bufs; + cx->stream_buffers[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control, no data */ + + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_TS] = enc_ts_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_VBI] = vbi_active_samples * 36; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_bufsize; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control no data */ + + /* Ensure stream_buffers & stream_buf_size are valid */ + for (i = 0; i < CX18_MAX_STREAMS; i++) { + if (cx->stream_buffers[i] == 0 || /* User said 0 buffers */ + cx->options.megabytes[i] <= 0 || /* User said 0 MB total */ + cx->stream_buf_size[i] <= 0) { /* User said buf size 0 */ + cx->options.megabytes[i] = 0; + cx->stream_buffers[i] = 0; + cx->stream_buf_size[i] = 0; + continue; + } + /* + * YUV is a special case where the stream_buf_size needs to be + * an integral multiple of 33.75 kB (storage for 32 screens + * lines to maintain alignment in case of lost buffers). + * + * IDX is a special case where the stream_buf_size should be + * an integral multiple of 1.5 kB (storage for 64 index entries + * to maintain alignment in case of lost buffers). + * + */ + if (i == CX18_ENC_STREAM_TYPE_YUV) { + cx->stream_buf_size[i] *= 1024; + cx->stream_buf_size[i] -= + (cx->stream_buf_size[i] % CX18_UNIT_ENC_YUV_BUFSIZE); + + if (cx->stream_buf_size[i] < CX18_UNIT_ENC_YUV_BUFSIZE) + cx->stream_buf_size[i] = + CX18_UNIT_ENC_YUV_BUFSIZE; + } else if (i == CX18_ENC_STREAM_TYPE_IDX) { + cx->stream_buf_size[i] *= 1024; + cx->stream_buf_size[i] -= + (cx->stream_buf_size[i] % CX18_UNIT_ENC_IDX_BUFSIZE); + + if (cx->stream_buf_size[i] < CX18_UNIT_ENC_IDX_BUFSIZE) + cx->stream_buf_size[i] = + CX18_UNIT_ENC_IDX_BUFSIZE; + } + /* + * YUV and IDX are special cases where the stream_buf_size is + * now in bytes. + * VBI is a special case where the stream_buf_size is fixed + * and already in bytes + */ + if (i == CX18_ENC_STREAM_TYPE_VBI || + i == CX18_ENC_STREAM_TYPE_YUV || + i == CX18_ENC_STREAM_TYPE_IDX) { + if (cx->stream_buffers[i] < 0) { + cx->stream_buffers[i] = + cx->options.megabytes[i] * 1024 * 1024 + / cx->stream_buf_size[i]; + } else { + /* N.B. This might round down to 0 */ + cx->options.megabytes[i] = + cx->stream_buffers[i] + * cx->stream_buf_size[i]/(1024 * 1024); + } + } else { + /* All other streams have stream_buf_size in kB here */ + if (cx->stream_buffers[i] < 0) { + cx->stream_buffers[i] = + cx->options.megabytes[i] * 1024 + / cx->stream_buf_size[i]; + } else { + /* N.B. This might round down to 0 */ + cx->options.megabytes[i] = + cx->stream_buffers[i] + * cx->stream_buf_size[i] / 1024; + } + /* convert from kB to bytes */ + cx->stream_buf_size[i] *= 1024; + } + CX18_DEBUG_INFO("Stream type %d options: %d MB, %d buffers, " + "%d bytes\n", i, cx->options.megabytes[i], + cx->stream_buffers[i], cx->stream_buf_size[i]); + } + + cx->options.cardtype = cardtype[cx->instance]; + cx->options.tuner = tuner[cx->instance]; + cx->options.radio = radio[cx->instance]; + + cx->std = cx18_parse_std(cx); + if (cx->options.cardtype == -1) { + CX18_INFO("Ignore card\n"); + return; + } + cx->card = cx18_get_card(cx->options.cardtype - 1); + if (cx->card) + CX18_INFO("User specified %s card\n", cx->card->name); + else if (cx->options.cardtype != 0) + CX18_ERR("Unknown user specified type, trying to autodetect card\n"); + if (cx->card == NULL) { + if (cx->pci_dev->subsystem_vendor == CX18_PCI_ID_HAUPPAUGE) { + cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT); + CX18_INFO("Autodetected Hauppauge card\n"); + } + } + if (cx->card == NULL) { + for (i = 0; (cx->card = cx18_get_card(i)); i++) { + if (cx->card->pci_list == NULL) + continue; + for (j = 0; cx->card->pci_list[j].device; j++) { + if (cx->pci_dev->device != + cx->card->pci_list[j].device) + continue; + if (cx->pci_dev->subsystem_vendor != + cx->card->pci_list[j].subsystem_vendor) + continue; + if (cx->pci_dev->subsystem_device != + cx->card->pci_list[j].subsystem_device) + continue; + CX18_INFO("Autodetected %s card\n", cx->card->name); + goto done; + } + } + } +done: + + if (cx->card == NULL) { + cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT); + CX18_ERR("Unknown card: vendor/device: [%04x:%04x]\n", + cx->pci_dev->vendor, cx->pci_dev->device); + CX18_ERR(" subsystem vendor/device: [%04x:%04x]\n", + cx->pci_dev->subsystem_vendor, + cx->pci_dev->subsystem_device); + CX18_ERR("Defaulting to %s card\n", cx->card->name); + CX18_ERR("Please mail the vendor/device and subsystem vendor/device IDs and what kind of\n"); + CX18_ERR("card you have to the ivtv-devel mailinglist (www.ivtvdriver.org)\n"); + CX18_ERR("Prefix your subject line with [UNKNOWN CX18 CARD].\n"); + } + cx->v4l2_cap = cx->card->v4l2_capabilities; + cx->card_name = cx->card->name; + cx->card_i2c = cx->card->i2c; +} + +static int __devinit cx18_create_in_workq(struct cx18 *cx) +{ + snprintf(cx->in_workq_name, sizeof(cx->in_workq_name), "%s-in", + cx->v4l2_dev.name); + cx->in_work_queue = alloc_ordered_workqueue(cx->in_workq_name, 0); + if (cx->in_work_queue == NULL) { + CX18_ERR("Unable to create incoming mailbox handler thread\n"); + return -ENOMEM; + } + return 0; +} + +static void __devinit cx18_init_in_work_orders(struct cx18 *cx) +{ + int i; + for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++) { + cx->in_work_order[i].cx = cx; + cx->in_work_order[i].str = cx->epu_debug_str; + INIT_WORK(&cx->in_work_order[i].work, cx18_in_work_handler); + } +} + +/* Precondition: the cx18 structure has been memset to 0. Only + the dev and instance fields have been filled in. + No assumptions on the card type may be made here (see cx18_init_struct2 + for that). + */ +static int __devinit cx18_init_struct1(struct cx18 *cx) +{ + int ret; + + cx->base_addr = pci_resource_start(cx->pci_dev, 0); + + mutex_init(&cx->serialize_lock); + mutex_init(&cx->gpio_lock); + mutex_init(&cx->epu2apu_mb_lock); + mutex_init(&cx->epu2cpu_mb_lock); + + ret = cx18_create_in_workq(cx); + if (ret) + return ret; + + cx18_init_in_work_orders(cx); + + /* start counting open_id at 1 */ + cx->open_id = 1; + + /* Initial settings */ + cx->cxhdl.port = CX2341X_PORT_MEMORY; + cx->cxhdl.capabilities = CX2341X_CAP_HAS_TS | CX2341X_CAP_HAS_SLICED_VBI; + cx->cxhdl.ops = &cx18_cxhdl_ops; + cx->cxhdl.func = cx18_api_func; + cx->cxhdl.priv = &cx->streams[CX18_ENC_STREAM_TYPE_MPG]; + ret = cx2341x_handler_init(&cx->cxhdl, 50); + if (ret) + return ret; + cx->v4l2_dev.ctrl_handler = &cx->cxhdl.hdl; + + cx->temporal_strength = cx->cxhdl.video_temporal_filter->cur.val; + cx->spatial_strength = cx->cxhdl.video_spatial_filter->cur.val; + cx->filter_mode = cx->cxhdl.video_spatial_filter_mode->cur.val | + (cx->cxhdl.video_temporal_filter_mode->cur.val << 1) | + (cx->cxhdl.video_median_filter_type->cur.val << 2); + + init_waitqueue_head(&cx->cap_w); + init_waitqueue_head(&cx->mb_apu_waitq); + init_waitqueue_head(&cx->mb_cpu_waitq); + init_waitqueue_head(&cx->dma_waitq); + + /* VBI */ + cx->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE; + cx->vbi.sliced_in = &cx->vbi.in.fmt.sliced; + + /* IVTV style VBI insertion into MPEG streams */ + INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_buf.list); + INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_mdl.list); + INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_mdl.buf_list); + list_add(&cx->vbi.sliced_mpeg_buf.list, + &cx->vbi.sliced_mpeg_mdl.buf_list); + return 0; +} + +/* Second initialization part. Here the card type has been + autodetected. */ +static void __devinit cx18_init_struct2(struct cx18 *cx) +{ + int i; + + for (i = 0; i < CX18_CARD_MAX_VIDEO_INPUTS; i++) + if (cx->card->video_inputs[i].video_type == 0) + break; + cx->nof_inputs = i; + for (i = 0; i < CX18_CARD_MAX_AUDIO_INPUTS; i++) + if (cx->card->audio_inputs[i].audio_type == 0) + break; + cx->nof_audio_inputs = i; + + /* Find tuner input */ + for (i = 0; i < cx->nof_inputs; i++) { + if (cx->card->video_inputs[i].video_type == + CX18_CARD_INPUT_VID_TUNER) + break; + } + if (i == cx->nof_inputs) + i = 0; + cx->active_input = i; + cx->audio_input = cx->card->video_inputs[i].audio_index; +} + +static int cx18_setup_pci(struct cx18 *cx, struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + u16 cmd; + unsigned char pci_latency; + + CX18_DEBUG_INFO("Enabling pci device\n"); + + if (pci_enable_device(pci_dev)) { + CX18_ERR("Can't enable device %d!\n", cx->instance); + return -EIO; + } + if (pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32))) { + CX18_ERR("No suitable DMA available, card %d\n", cx->instance); + return -EIO; + } + if (!request_mem_region(cx->base_addr, CX18_MEM_SIZE, "cx18 encoder")) { + CX18_ERR("Cannot request encoder memory region, card %d\n", + cx->instance); + return -EIO; + } + + /* Enable bus mastering and memory mapped IO for the CX23418 */ + pci_read_config_word(pci_dev, PCI_COMMAND, &cmd); + cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; + pci_write_config_word(pci_dev, PCI_COMMAND, cmd); + + cx->card_rev = pci_dev->revision; + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &pci_latency); + + if (pci_latency < 64 && cx18_pci_latency) { + CX18_INFO("Unreasonably low latency timer, " + "setting to 64 (was %d)\n", pci_latency); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, 64); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &pci_latency); + } + + CX18_DEBUG_INFO("cx%d (rev %d) at %02x:%02x.%x, " + "irq: %d, latency: %d, memory: 0x%llx\n", + cx->pci_dev->device, cx->card_rev, pci_dev->bus->number, + PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn), + cx->pci_dev->irq, pci_latency, (u64)cx->base_addr); + + return 0; +} + +static void cx18_init_subdevs(struct cx18 *cx) +{ + u32 hw = cx->card->hw_all; + u32 device; + int i; + + for (i = 0, device = 1; i < 32; i++, device <<= 1) { + + if (!(device & hw)) + continue; + + switch (device) { + case CX18_HW_DVB: + case CX18_HW_TVEEPROM: + /* These subordinate devices do not use probing */ + cx->hw_flags |= device; + break; + case CX18_HW_418_AV: + /* The A/V decoder gets probed earlier to set PLLs */ + /* Just note that the card uses it (i.e. has analog) */ + cx->hw_flags |= device; + break; + case CX18_HW_GPIO_RESET_CTRL: + /* + * The Reset Controller gets probed and added to + * hw_flags earlier for i2c adapter/bus initialization + */ + break; + case CX18_HW_GPIO_MUX: + if (cx18_gpio_register(cx, device) == 0) + cx->hw_flags |= device; + break; + default: + if (cx18_i2c_register(cx, i) == 0) + cx->hw_flags |= device; + break; + } + } + + if (cx->hw_flags & CX18_HW_418_AV) + cx->sd_av = cx18_find_hw(cx, CX18_HW_418_AV); + + if (cx->card->hw_muxer != 0) + cx->sd_extmux = cx18_find_hw(cx, cx->card->hw_muxer); +} + +static int __devinit cx18_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + int retval = 0; + int i; + u32 devtype; + struct cx18 *cx; + + /* FIXME - module parameter arrays constrain max instances */ + i = atomic_inc_return(&cx18_instance) - 1; + if (i >= CX18_MAX_CARDS) { + printk(KERN_ERR "cx18: cannot manage card %d, driver has a " + "limit of 0 - %d\n", i, CX18_MAX_CARDS - 1); + return -ENOMEM; + } + + cx = kzalloc(sizeof(struct cx18), GFP_ATOMIC); + if (cx == NULL) { + printk(KERN_ERR "cx18: cannot manage card %d, out of memory\n", + i); + return -ENOMEM; + } + cx->pci_dev = pci_dev; + cx->instance = i; + + retval = v4l2_device_register(&pci_dev->dev, &cx->v4l2_dev); + if (retval) { + printk(KERN_ERR "cx18: v4l2_device_register of card %d failed" + "\n", cx->instance); + kfree(cx); + return retval; + } + snprintf(cx->v4l2_dev.name, sizeof(cx->v4l2_dev.name), "cx18-%d", + cx->instance); + CX18_INFO("Initializing card %d\n", cx->instance); + + cx18_process_options(cx); + if (cx->options.cardtype == -1) { + retval = -ENODEV; + goto err; + } + + retval = cx18_init_struct1(cx); + if (retval) + goto err; + + CX18_DEBUG_INFO("base addr: 0x%llx\n", (u64)cx->base_addr); + + /* PCI Device Setup */ + retval = cx18_setup_pci(cx, pci_dev, pci_id); + if (retval != 0) + goto free_workqueues; + + /* map io memory */ + CX18_DEBUG_INFO("attempting ioremap at 0x%llx len 0x%08x\n", + (u64)cx->base_addr + CX18_MEM_OFFSET, CX18_MEM_SIZE); + cx->enc_mem = ioremap_nocache(cx->base_addr + CX18_MEM_OFFSET, + CX18_MEM_SIZE); + if (!cx->enc_mem) { + CX18_ERR("ioremap failed. Can't get a window into CX23418 " + "memory and register space\n"); + CX18_ERR("Each capture card with a CX23418 needs 64 MB of " + "vmalloc address space for the window\n"); + CX18_ERR("Check the output of 'grep Vmalloc /proc/meminfo'\n"); + CX18_ERR("Use the vmalloc= kernel command line option to set " + "VmallocTotal to a larger value\n"); + retval = -ENOMEM; + goto free_mem; + } + cx->reg_mem = cx->enc_mem + CX18_REG_OFFSET; + devtype = cx18_read_reg(cx, 0xC72028); + switch (devtype & 0xff000000) { + case 0xff000000: + CX18_INFO("cx23418 revision %08x (A)\n", devtype); + break; + case 0x01000000: + CX18_INFO("cx23418 revision %08x (B)\n", devtype); + break; + default: + CX18_INFO("cx23418 revision %08x (Unknown)\n", devtype); + break; + } + + cx18_init_power(cx, 1); + cx18_init_memory(cx); + + cx->scb = (struct cx18_scb __iomem *)(cx->enc_mem + SCB_OFFSET); + cx18_init_scb(cx); + + cx18_gpio_init(cx); + + /* Initialize integrated A/V decoder early to set PLLs, just in case */ + retval = cx18_av_probe(cx); + if (retval) { + CX18_ERR("Could not register A/V decoder subdevice\n"); + goto free_map; + } + + /* Initialize GPIO Reset Controller to do chip resets during i2c init */ + if (cx->card->hw_all & CX18_HW_GPIO_RESET_CTRL) { + if (cx18_gpio_register(cx, CX18_HW_GPIO_RESET_CTRL) != 0) + CX18_WARN("Could not register GPIO reset controller" + "subdevice; proceeding anyway.\n"); + else + cx->hw_flags |= CX18_HW_GPIO_RESET_CTRL; + } + + /* active i2c */ + CX18_DEBUG_INFO("activating i2c...\n"); + retval = init_cx18_i2c(cx); + if (retval) { + CX18_ERR("Could not initialize i2c\n"); + goto free_map; + } + + if (cx->card->hw_all & CX18_HW_TVEEPROM) { + /* Based on the model number the cardtype may be changed. + The PCI IDs are not always reliable. */ + const struct cx18_card *orig_card = cx->card; + cx18_process_eeprom(cx); + + if (cx->card != orig_card) { + /* Changed the cardtype; re-reset the I2C chips */ + cx18_gpio_init(cx); + cx18_call_hw(cx, CX18_HW_GPIO_RESET_CTRL, + core, reset, (u32) CX18_GPIO_RESET_I2C); + } + } + if (cx->card->comment) + CX18_INFO("%s", cx->card->comment); + if (cx->card->v4l2_capabilities == 0) { + retval = -ENODEV; + goto free_i2c; + } + cx18_init_memory(cx); + cx18_init_scb(cx); + + /* Register IRQ */ + retval = request_irq(cx->pci_dev->irq, cx18_irq_handler, + IRQF_SHARED | IRQF_DISABLED, + cx->v4l2_dev.name, (void *)cx); + if (retval) { + CX18_ERR("Failed to register irq %d\n", retval); + goto free_i2c; + } + + if (cx->std == 0) + cx->std = V4L2_STD_NTSC_M; + + if (cx->options.tuner == -1) { + for (i = 0; i < CX18_CARD_MAX_TUNERS; i++) { + if ((cx->std & cx->card->tuners[i].std) == 0) + continue; + cx->options.tuner = cx->card->tuners[i].tuner; + break; + } + } + /* if no tuner was found, then pick the first tuner in the card list */ + if (cx->options.tuner == -1 && cx->card->tuners[0].std) { + cx->std = cx->card->tuners[0].std; + if (cx->std & V4L2_STD_PAL) + cx->std = V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + else if (cx->std & V4L2_STD_NTSC) + cx->std = V4L2_STD_NTSC_M; + else if (cx->std & V4L2_STD_SECAM) + cx->std = V4L2_STD_SECAM_L; + cx->options.tuner = cx->card->tuners[0].tuner; + } + if (cx->options.radio == -1) + cx->options.radio = (cx->card->radio_input.audio_type != 0); + + /* The card is now fully identified, continue with card-specific + initialization. */ + cx18_init_struct2(cx); + + cx18_init_subdevs(cx); + + if (cx->std & V4L2_STD_525_60) + cx->is_60hz = 1; + else + cx->is_50hz = 1; + + cx2341x_handler_set_50hz(&cx->cxhdl, !cx->is_60hz); + + if (cx->options.radio > 0) + cx->v4l2_cap |= V4L2_CAP_RADIO; + + if (cx->options.tuner > -1) { + struct tuner_setup setup; + + setup.addr = ADDR_UNSET; + setup.type = cx->options.tuner; + setup.mode_mask = T_ANALOG_TV; /* matches TV tuners */ + if (cx->options.radio > 0) + setup.mode_mask |= T_RADIO; + setup.tuner_callback = (setup.type == TUNER_XC2028) ? + cx18_reset_tuner_gpio : NULL; + cx18_call_all(cx, tuner, s_type_addr, &setup); + if (setup.type == TUNER_XC2028) { + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + }; + struct v4l2_priv_tun_config cfg = { + .tuner = cx->options.tuner, + .priv = &ctrl, + }; + cx18_call_all(cx, tuner, s_config, &cfg); + } + } + + /* The tuner is fixed to the standard. The other inputs (e.g. S-Video) + are not. */ + cx->tuner_std = cx->std; + if (cx->std == V4L2_STD_ALL) + cx->std = V4L2_STD_NTSC_M; + + retval = cx18_streams_setup(cx); + if (retval) { + CX18_ERR("Error %d setting up streams\n", retval); + goto free_irq; + } + retval = cx18_streams_register(cx); + if (retval) { + CX18_ERR("Error %d registering devices\n", retval); + goto free_streams; + } + + CX18_INFO("Initialized card: %s\n", cx->card_name); + + /* Load cx18 submodules (cx18-alsa) */ + request_modules(cx); + return 0; + +free_streams: + cx18_streams_cleanup(cx, 1); +free_irq: + free_irq(cx->pci_dev->irq, (void *)cx); +free_i2c: + exit_cx18_i2c(cx); +free_map: + cx18_iounmap(cx); +free_mem: + release_mem_region(cx->base_addr, CX18_MEM_SIZE); +free_workqueues: + destroy_workqueue(cx->in_work_queue); +err: + if (retval == 0) + retval = -ENODEV; + CX18_ERR("Error %d on initialization\n", retval); + + v4l2_device_unregister(&cx->v4l2_dev); + kfree(cx); + return retval; +} + +int cx18_init_on_first_open(struct cx18 *cx) +{ + int video_input; + int fw_retry_count = 3; + struct v4l2_frequency vf; + struct cx18_open_id fh; + v4l2_std_id std; + + fh.cx = cx; + + if (test_bit(CX18_F_I_FAILED, &cx->i_flags)) + return -ENXIO; + + if (test_and_set_bit(CX18_F_I_INITED, &cx->i_flags)) + return 0; + + while (--fw_retry_count > 0) { + /* load firmware */ + if (cx18_firmware_init(cx) == 0) + break; + if (fw_retry_count > 1) + CX18_WARN("Retry loading firmware\n"); + } + + if (fw_retry_count == 0) { + set_bit(CX18_F_I_FAILED, &cx->i_flags); + return -ENXIO; + } + set_bit(CX18_F_I_LOADED_FW, &cx->i_flags); + + /* + * Init the firmware twice to work around a silicon bug + * with the digital TS. + * + * The second firmware load requires us to normalize the APU state, + * or the audio for the first analog capture will be badly incorrect. + * + * I can't seem to call APU_RESETAI and have it succeed without the + * APU capturing audio, so we start and stop it here to do the reset + */ + + /* MPEG Encoding, 224 kbps, MPEG Layer II, 48 ksps */ + cx18_vapi(cx, CX18_APU_START, 2, CX18_APU_ENCODING_METHOD_MPEG|0xb9, 0); + cx18_vapi(cx, CX18_APU_RESETAI, 0); + cx18_vapi(cx, CX18_APU_STOP, 1, CX18_APU_ENCODING_METHOD_MPEG); + + fw_retry_count = 3; + while (--fw_retry_count > 0) { + /* load firmware */ + if (cx18_firmware_init(cx) == 0) + break; + if (fw_retry_count > 1) + CX18_WARN("Retry loading firmware\n"); + } + + if (fw_retry_count == 0) { + set_bit(CX18_F_I_FAILED, &cx->i_flags); + return -ENXIO; + } + + /* + * The second firmware load requires us to normalize the APU state, + * or the audio for the first analog capture will be badly incorrect. + * + * I can't seem to call APU_RESETAI and have it succeed without the + * APU capturing audio, so we start and stop it here to do the reset + */ + + /* MPEG Encoding, 224 kbps, MPEG Layer II, 48 ksps */ + cx18_vapi(cx, CX18_APU_START, 2, CX18_APU_ENCODING_METHOD_MPEG|0xb9, 0); + cx18_vapi(cx, CX18_APU_RESETAI, 0); + cx18_vapi(cx, CX18_APU_STOP, 1, CX18_APU_ENCODING_METHOD_MPEG); + + /* Init the A/V decoder, if it hasn't been already */ + v4l2_subdev_call(cx->sd_av, core, load_fw); + + vf.tuner = 0; + vf.type = V4L2_TUNER_ANALOG_TV; + vf.frequency = 6400; /* the tuner 'baseline' frequency */ + + /* Set initial frequency. For PAL/SECAM broadcasts no + 'default' channel exists AFAIK. */ + if (cx->std == V4L2_STD_NTSC_M_JP) + vf.frequency = 1460; /* ch. 1 91250*16/1000 */ + else if (cx->std & V4L2_STD_NTSC_M) + vf.frequency = 1076; /* ch. 4 67250*16/1000 */ + + video_input = cx->active_input; + cx->active_input++; /* Force update of input */ + cx18_s_input(NULL, &fh, video_input); + + /* Let the VIDIOC_S_STD ioctl do all the work, keeps the code + in one place. */ + cx->std++; /* Force full standard initialization */ + std = (cx->tuner_std == V4L2_STD_ALL) ? V4L2_STD_NTSC_M : cx->tuner_std; + cx18_s_std(NULL, &fh, &std); + cx18_s_frequency(NULL, &fh, &vf); + return 0; +} + +static void cx18_cancel_in_work_orders(struct cx18 *cx) +{ + int i; + for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++) + cancel_work_sync(&cx->in_work_order[i].work); +} + +static void cx18_cancel_out_work_orders(struct cx18 *cx) +{ + int i; + for (i = 0; i < CX18_MAX_STREAMS; i++) + if (&cx->streams[i].video_dev != NULL) + cancel_work_sync(&cx->streams[i].out_work_order); +} + +static void cx18_remove(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct cx18 *cx = to_cx18(v4l2_dev); + int i; + + CX18_DEBUG_INFO("Removing Card\n"); + + flush_request_modules(cx); + + /* Stop all captures */ + CX18_DEBUG_INFO("Stopping all streams\n"); + if (atomic_read(&cx->tot_capturing) > 0) + cx18_stop_all_captures(cx); + + /* Stop interrupts that cause incoming work to be queued */ + cx18_sw1_irq_disable(cx, IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU); + + /* Incoming work can cause outgoing work, so clean up incoming first */ + cx18_cancel_in_work_orders(cx); + cx18_cancel_out_work_orders(cx); + + /* Stop ack interrupts that may have been needed for work to finish */ + cx18_sw2_irq_disable(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK); + + cx18_halt_firmware(cx); + + destroy_workqueue(cx->in_work_queue); + + cx18_streams_cleanup(cx, 1); + + exit_cx18_i2c(cx); + + free_irq(cx->pci_dev->irq, (void *)cx); + + cx18_iounmap(cx); + + release_mem_region(cx->base_addr, CX18_MEM_SIZE); + + pci_disable_device(cx->pci_dev); + + if (cx->vbi.sliced_mpeg_data[0] != NULL) + for (i = 0; i < CX18_VBI_FRAMES; i++) + kfree(cx->vbi.sliced_mpeg_data[i]); + + v4l2_ctrl_handler_free(&cx->av_state.hdl); + + CX18_INFO("Removed %s\n", cx->card_name); + + v4l2_device_unregister(v4l2_dev); + kfree(cx); +} + + +/* define a pci_driver for card detection */ +static struct pci_driver cx18_pci_driver = { + .name = "cx18", + .id_table = cx18_pci_tbl, + .probe = cx18_probe, + .remove = cx18_remove, +}; + +static int __init module_start(void) +{ + printk(KERN_INFO "cx18: Start initialization, version %s\n", + CX18_VERSION); + + /* Validate parameters */ + if (cx18_first_minor < 0 || cx18_first_minor >= CX18_MAX_CARDS) { + printk(KERN_ERR "cx18: Exiting, cx18_first_minor must be between 0 and %d\n", + CX18_MAX_CARDS - 1); + return -1; + } + + if (cx18_debug < 0 || cx18_debug > 511) { + cx18_debug = 0; + printk(KERN_INFO "cx18: Debug value must be >= 0 and <= 511!\n"); + } + + if (pci_register_driver(&cx18_pci_driver)) { + printk(KERN_ERR "cx18: Error detecting PCI card\n"); + return -ENODEV; + } + printk(KERN_INFO "cx18: End initialization\n"); + return 0; +} + +static void __exit module_cleanup(void) +{ + pci_unregister_driver(&cx18_pci_driver); +} + +module_init(module_start); +module_exit(module_cleanup); +MODULE_FIRMWARE(XC2028_DEFAULT_FIRMWARE); diff --git a/drivers/media/video/cx18/cx18-driver.h b/drivers/media/pci/cx18/cx18-driver.h similarity index 100% rename from drivers/media/video/cx18/cx18-driver.h rename to drivers/media/pci/cx18/cx18-driver.h diff --git a/drivers/media/pci/cx18/cx18-dvb.c b/drivers/media/pci/cx18/cx18-dvb.c new file mode 100644 index 000000000000..3eac59c51231 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-dvb.c @@ -0,0 +1,609 @@ +/* + * cx18 functions for DVB support + * + * Copyright (c) 2008 Steven Toth + * Copyright (C) 2008 Andy Walls + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "cx18-version.h" +#include "cx18-dvb.h" +#include "cx18-io.h" +#include "cx18-queue.h" +#include "cx18-streams.h" +#include "cx18-cards.h" +#include "cx18-gpio.h" +#include "s5h1409.h" +#include "mxl5005s.h" +#include "s5h1411.h" +#include "tda18271.h" +#include "zl10353.h" + +#include +#include "mt352.h" +#include "mt352_priv.h" +#include "tuner-xc2028.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define FWFILE "dvb-cx18-mpc718-mt352.fw" + +#define CX18_REG_DMUX_NUM_PORT_0_CONTROL 0xd5a000 +#define CX18_CLOCK_ENABLE2 0xc71024 +#define CX18_DMUX_CLK_MASK 0x0080 + +/* + * CX18_CARD_HVR_1600_ESMT + * CX18_CARD_HVR_1600_SAMSUNG + */ + +static struct mxl5005s_config hauppauge_hvr1600_tuner = { + .i2c_address = 0xC6 >> 1, + .if_freq = IF_FREQ_5380000HZ, + .xtal_freq = CRYSTAL_FREQ_16000000HZ, + .agc_mode = MXL_SINGLE_AGC, + .tracking_filter = MXL_TF_C_H, + .rssi_enable = MXL_RSSI_ENABLE, + .cap_select = MXL_CAP_SEL_ENABLE, + .div_out = MXL_DIV_OUT_4, + .clock_out = MXL_CLOCK_OUT_DISABLE, + .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, + .top = MXL5005S_TOP_25P2, + .mod_mode = MXL_DIGITAL_MODE, + .if_mode = MXL_ZERO_IF, + .qam_gain = 0x02, + .AgcMasterByte = 0x00, +}; + +static struct s5h1409_config hauppauge_hvr1600_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, + .hvr1600_opt = S5H1409_HVR1600_OPTIMIZE +}; + +/* + * CX18_CARD_HVR_1600_S5H1411 + */ +static struct s5h1411_config hcw_s5h1411_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .vsb_if = S5H1411_IF_44000, + .qam_if = S5H1411_IF_4000, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static struct tda18271_std_map hauppauge_tda18271_std_map = { + .atsc_6 = { .if_freq = 5380, .agc_mode = 3, .std = 3, + .if_lvl = 6, .rfagc_top = 0x37 }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0, + .if_lvl = 6, .rfagc_top = 0x37 }, +}; + +static struct tda18271_config hauppauge_tda18271_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_DIGITAL, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +/* + * CX18_CARD_LEADTEK_DVR3100H + */ +/* Information/confirmation of proper config values provided by Terry Wu */ +static struct zl10353_config leadtek_dvr3100h_demod = { + .demod_address = 0x1e >> 1, /* Datasheet suggested straps */ + .if2 = 45600, /* 4.560 MHz IF from the XC3028 */ + .parallel_ts = 1, /* Not a serial TS */ + .no_tuner = 1, /* XC3028 is not behind the gate */ + .disable_i2c_gate_ctrl = 1, /* Disable the I2C gate */ +}; + +/* + * CX18_CARD_YUAN_MPC718 + */ +/* + * Due to + * + * 1. an absence of information on how to prgram the MT352 + * 2. the Linux mt352 module pushing MT352 initialzation off onto us here + * + * We have to use an init sequence that *you* must extract from the Windows + * driver (yuanrap.sys) and which we load as a firmware. + * + * If someone can provide me with a Zarlink MT352 (Intel CE6352?) Design Manual + * with chip programming details, then I can remove this annoyance. + */ +static int yuan_mpc718_mt352_reqfw(struct cx18_stream *stream, + const struct firmware **fw) +{ + struct cx18 *cx = stream->cx; + const char *fn = FWFILE; + int ret; + + ret = request_firmware(fw, fn, &cx->pci_dev->dev); + if (ret) + CX18_ERR("Unable to open firmware file %s\n", fn); + else { + size_t sz = (*fw)->size; + if (sz < 2 || sz > 64 || (sz % 2) != 0) { + CX18_ERR("Firmware %s has a bad size: %lu bytes\n", + fn, (unsigned long) sz); + ret = -EILSEQ; + release_firmware(*fw); + *fw = NULL; + } + } + + if (ret) { + CX18_ERR("The MPC718 board variant with the MT352 DVB-T" + "demodualtor will not work without it\n"); + CX18_ERR("Run 'linux/Documentation/dvb/get_dvb_firmware " + "mpc718' if you need the firmware\n"); + } + return ret; +} + +static int yuan_mpc718_mt352_init(struct dvb_frontend *fe) +{ + struct cx18_dvb *dvb = container_of(fe->dvb, + struct cx18_dvb, dvb_adapter); + struct cx18_stream *stream = dvb->stream; + const struct firmware *fw = NULL; + int ret; + int i; + u8 buf[3]; + + ret = yuan_mpc718_mt352_reqfw(stream, &fw); + if (ret) + return ret; + + /* Loop through all the register-value pairs in the firmware file */ + for (i = 0; i < fw->size; i += 2) { + buf[0] = fw->data[i]; + /* Intercept a few registers we want to set ourselves */ + switch (buf[0]) { + case TRL_NOMINAL_RATE_0: + /* Set our custom OFDM bandwidth in the case below */ + break; + case TRL_NOMINAL_RATE_1: + /* 6 MHz: 64/7 * 6/8 / 20.48 * 2^16 = 0x55b6.db6 */ + /* 7 MHz: 64/7 * 7/8 / 20.48 * 2^16 = 0x6400 */ + /* 8 MHz: 64/7 * 8/8 / 20.48 * 2^16 = 0x7249.249 */ + buf[1] = 0x72; + buf[2] = 0x49; + mt352_write(fe, buf, 3); + break; + case INPUT_FREQ_0: + /* Set our custom IF in the case below */ + break; + case INPUT_FREQ_1: + /* 4.56 MHz IF: (20.48 - 4.56)/20.48 * 2^14 = 0x31c0 */ + buf[1] = 0x31; + buf[2] = 0xc0; + mt352_write(fe, buf, 3); + break; + default: + /* Pass through the register-value pair from the fw */ + buf[1] = fw->data[i+1]; + mt352_write(fe, buf, 2); + break; + } + } + + buf[0] = (u8) TUNER_GO; + buf[1] = 0x01; /* Go */ + mt352_write(fe, buf, 2); + release_firmware(fw); + return 0; +} + +static struct mt352_config yuan_mpc718_mt352_demod = { + .demod_address = 0x1e >> 1, + .adc_clock = 20480, /* 20.480 MHz */ + .if2 = 4560, /* 4.560 MHz */ + .no_tuner = 1, /* XC3028 is not behind the gate */ + .demod_init = yuan_mpc718_mt352_init, +}; + +static struct zl10353_config yuan_mpc718_zl10353_demod = { + .demod_address = 0x1e >> 1, /* Datasheet suggested straps */ + .if2 = 45600, /* 4.560 MHz IF from the XC3028 */ + .parallel_ts = 1, /* Not a serial TS */ + .no_tuner = 1, /* XC3028 is not behind the gate */ + .disable_i2c_gate_ctrl = 1, /* Disable the I2C gate */ +}; + +static struct zl10353_config gotview_dvd3_zl10353_demod = { + .demod_address = 0x1e >> 1, /* Datasheet suggested straps */ + .if2 = 45600, /* 4.560 MHz IF from the XC3028 */ + .parallel_ts = 1, /* Not a serial TS */ + .no_tuner = 1, /* XC3028 is not behind the gate */ + .disable_i2c_gate_ctrl = 1, /* Disable the I2C gate */ +}; + +static int dvb_register(struct cx18_stream *stream); + +/* Kernel DVB framework calls this when the feed needs to start. + * The CX18 framework should enable the transport DMA handling + * and queue processing. + */ +static int cx18_dvb_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct cx18_stream *stream = (struct cx18_stream *) demux->priv; + struct cx18 *cx; + int ret; + u32 v; + + if (!stream) + return -EINVAL; + + cx = stream->cx; + CX18_DEBUG_INFO("Start feed: pid = 0x%x index = %d\n", + feed->pid, feed->index); + + mutex_lock(&cx->serialize_lock); + ret = cx18_init_on_first_open(cx); + mutex_unlock(&cx->serialize_lock); + if (ret) { + CX18_ERR("Failed to initialize firmware starting DVB feed\n"); + return ret; + } + ret = -EINVAL; + + switch (cx->card->type) { + case CX18_CARD_HVR_1600_ESMT: + case CX18_CARD_HVR_1600_SAMSUNG: + case CX18_CARD_HVR_1600_S5H1411: + v = cx18_read_reg(cx, CX18_REG_DMUX_NUM_PORT_0_CONTROL); + v |= 0x00400000; /* Serial Mode */ + v |= 0x00002000; /* Data Length - Byte */ + v |= 0x00010000; /* Error - Polarity */ + v |= 0x00020000; /* Error - Passthru */ + v |= 0x000c0000; /* Error - Ignore */ + cx18_write_reg(cx, v, CX18_REG_DMUX_NUM_PORT_0_CONTROL); + break; + + case CX18_CARD_LEADTEK_DVR3100H: + case CX18_CARD_YUAN_MPC718: + case CX18_CARD_GOTVIEW_PCI_DVD3: + default: + /* Assumption - Parallel transport - Signalling + * undefined or default. + */ + break; + } + + if (!demux->dmx.frontend) + return -EINVAL; + + mutex_lock(&stream->dvb->feedlock); + if (stream->dvb->feeding++ == 0) { + CX18_DEBUG_INFO("Starting Transport DMA\n"); + mutex_lock(&cx->serialize_lock); + set_bit(CX18_F_S_STREAMING, &stream->s_flags); + ret = cx18_start_v4l2_encode_stream(stream); + if (ret < 0) { + CX18_DEBUG_INFO("Failed to start Transport DMA\n"); + stream->dvb->feeding--; + if (stream->dvb->feeding == 0) + clear_bit(CX18_F_S_STREAMING, &stream->s_flags); + } + mutex_unlock(&cx->serialize_lock); + } else + ret = 0; + mutex_unlock(&stream->dvb->feedlock); + + return ret; +} + +/* Kernel DVB framework calls this when the feed needs to stop. */ +static int cx18_dvb_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct cx18_stream *stream = (struct cx18_stream *)demux->priv; + struct cx18 *cx; + int ret = -EINVAL; + + if (stream) { + cx = stream->cx; + CX18_DEBUG_INFO("Stop feed: pid = 0x%x index = %d\n", + feed->pid, feed->index); + + mutex_lock(&stream->dvb->feedlock); + if (--stream->dvb->feeding == 0) { + CX18_DEBUG_INFO("Stopping Transport DMA\n"); + mutex_lock(&cx->serialize_lock); + ret = cx18_stop_v4l2_encode_stream(stream, 0); + mutex_unlock(&cx->serialize_lock); + } else + ret = 0; + mutex_unlock(&stream->dvb->feedlock); + } + + return ret; +} + +int cx18_dvb_register(struct cx18_stream *stream) +{ + struct cx18 *cx = stream->cx; + struct cx18_dvb *dvb = stream->dvb; + struct dvb_adapter *dvb_adapter; + struct dvb_demux *dvbdemux; + struct dmx_demux *dmx; + int ret; + + if (!dvb) + return -EINVAL; + + dvb->enabled = 0; + dvb->stream = stream; + + ret = dvb_register_adapter(&dvb->dvb_adapter, + CX18_DRIVER_NAME, + THIS_MODULE, &cx->pci_dev->dev, adapter_nr); + if (ret < 0) + goto err_out; + + dvb_adapter = &dvb->dvb_adapter; + + dvbdemux = &dvb->demux; + + dvbdemux->priv = (void *)stream; + + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = cx18_dvb_start_feed; + dvbdemux->stop_feed = cx18_dvb_stop_feed; + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | + DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING); + ret = dvb_dmx_init(dvbdemux); + if (ret < 0) + goto err_dvb_unregister_adapter; + + dmx = &dvbdemux->dmx; + + dvb->hw_frontend.source = DMX_FRONTEND_0; + dvb->mem_frontend.source = DMX_MEMORY_FE; + dvb->dmxdev.filternum = 256; + dvb->dmxdev.demux = dmx; + + ret = dvb_dmxdev_init(&dvb->dmxdev, dvb_adapter); + if (ret < 0) + goto err_dvb_dmx_release; + + ret = dmx->add_frontend(dmx, &dvb->hw_frontend); + if (ret < 0) + goto err_dvb_dmxdev_release; + + ret = dmx->add_frontend(dmx, &dvb->mem_frontend); + if (ret < 0) + goto err_remove_hw_frontend; + + ret = dmx->connect_frontend(dmx, &dvb->hw_frontend); + if (ret < 0) + goto err_remove_mem_frontend; + + ret = dvb_register(stream); + if (ret < 0) + goto err_disconnect_frontend; + + dvb_net_init(dvb_adapter, &dvb->dvbnet, dmx); + + CX18_INFO("DVB Frontend registered\n"); + CX18_INFO("Registered DVB adapter%d for %s (%d x %d.%02d kB)\n", + stream->dvb->dvb_adapter.num, stream->name, + stream->buffers, stream->buf_size/1024, + (stream->buf_size * 100 / 1024) % 100); + + mutex_init(&dvb->feedlock); + dvb->enabled = 1; + return ret; + +err_disconnect_frontend: + dmx->disconnect_frontend(dmx); +err_remove_mem_frontend: + dmx->remove_frontend(dmx, &dvb->mem_frontend); +err_remove_hw_frontend: + dmx->remove_frontend(dmx, &dvb->hw_frontend); +err_dvb_dmxdev_release: + dvb_dmxdev_release(&dvb->dmxdev); +err_dvb_dmx_release: + dvb_dmx_release(dvbdemux); +err_dvb_unregister_adapter: + dvb_unregister_adapter(dvb_adapter); +err_out: + return ret; +} + +void cx18_dvb_unregister(struct cx18_stream *stream) +{ + struct cx18 *cx = stream->cx; + struct cx18_dvb *dvb = stream->dvb; + struct dvb_adapter *dvb_adapter; + struct dvb_demux *dvbdemux; + struct dmx_demux *dmx; + + CX18_INFO("unregister DVB\n"); + + if (dvb == NULL || !dvb->enabled) + return; + + dvb_adapter = &dvb->dvb_adapter; + dvbdemux = &dvb->demux; + dmx = &dvbdemux->dmx; + + dmx->close(dmx); + dvb_net_release(&dvb->dvbnet); + dmx->remove_frontend(dmx, &dvb->mem_frontend); + dmx->remove_frontend(dmx, &dvb->hw_frontend); + dvb_dmxdev_release(&dvb->dmxdev); + dvb_dmx_release(dvbdemux); + dvb_unregister_frontend(dvb->fe); + dvb_frontend_detach(dvb->fe); + dvb_unregister_adapter(dvb_adapter); +} + +/* All the DVB attach calls go here, this function get's modified + * for each new card. cx18_dvb_start_feed() will also need changes. + */ +static int dvb_register(struct cx18_stream *stream) +{ + struct cx18_dvb *dvb = stream->dvb; + struct cx18 *cx = stream->cx; + int ret = 0; + + switch (cx->card->type) { + case CX18_CARD_HVR_1600_ESMT: + case CX18_CARD_HVR_1600_SAMSUNG: + dvb->fe = dvb_attach(s5h1409_attach, + &hauppauge_hvr1600_config, + &cx->i2c_adap[0]); + if (dvb->fe != NULL) { + dvb_attach(mxl5005s_attach, dvb->fe, + &cx->i2c_adap[0], + &hauppauge_hvr1600_tuner); + ret = 0; + } + break; + case CX18_CARD_HVR_1600_S5H1411: + dvb->fe = dvb_attach(s5h1411_attach, + &hcw_s5h1411_config, + &cx->i2c_adap[0]); + if (dvb->fe != NULL) + dvb_attach(tda18271_attach, dvb->fe, + 0x60, &cx->i2c_adap[0], + &hauppauge_tda18271_config); + break; + case CX18_CARD_LEADTEK_DVR3100H: + dvb->fe = dvb_attach(zl10353_attach, + &leadtek_dvr3100h_demod, + &cx->i2c_adap[1]); + if (dvb->fe != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &cx->i2c_adap[1], + .i2c_addr = 0xc2 >> 1, + .ctrl = NULL, + }; + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + .type = XC2028_AUTO, + }; + + fe = dvb_attach(xc2028_attach, dvb->fe, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctrl); + } + break; + case CX18_CARD_YUAN_MPC718: + /* + * TODO + * Apparently, these cards also could instead have a + * DiBcom demod supported by one of the db7000 drivers + */ + dvb->fe = dvb_attach(mt352_attach, + &yuan_mpc718_mt352_demod, + &cx->i2c_adap[1]); + if (dvb->fe == NULL) + dvb->fe = dvb_attach(zl10353_attach, + &yuan_mpc718_zl10353_demod, + &cx->i2c_adap[1]); + if (dvb->fe != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &cx->i2c_adap[1], + .i2c_addr = 0xc2 >> 1, + .ctrl = NULL, + }; + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + .type = XC2028_AUTO, + }; + + fe = dvb_attach(xc2028_attach, dvb->fe, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctrl); + } + break; + case CX18_CARD_GOTVIEW_PCI_DVD3: + dvb->fe = dvb_attach(zl10353_attach, + &gotview_dvd3_zl10353_demod, + &cx->i2c_adap[1]); + if (dvb->fe != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &cx->i2c_adap[1], + .i2c_addr = 0xc2 >> 1, + .ctrl = NULL, + }; + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + .type = XC2028_AUTO, + }; + + fe = dvb_attach(xc2028_attach, dvb->fe, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctrl); + } + break; + default: + /* No Digital Tv Support */ + break; + } + + if (dvb->fe == NULL) { + CX18_ERR("frontend initialization failed\n"); + return -1; + } + + dvb->fe->callback = cx18_reset_tuner_gpio; + + ret = dvb_register_frontend(&dvb->dvb_adapter, dvb->fe); + if (ret < 0) { + if (dvb->fe->ops.release) + dvb->fe->ops.release(dvb->fe); + return ret; + } + + /* + * The firmware seems to enable the TS DMUX clock + * under various circumstances. However, since we know we + * might use it, let's just turn it on ourselves here. + */ + cx18_write_reg_expect(cx, + (CX18_DMUX_CLK_MASK << 16) | CX18_DMUX_CLK_MASK, + CX18_CLOCK_ENABLE2, + CX18_DMUX_CLK_MASK, + (CX18_DMUX_CLK_MASK << 16) | CX18_DMUX_CLK_MASK); + + return ret; +} + +MODULE_FIRMWARE(FWFILE); diff --git a/drivers/media/video/cx18/cx18-dvb.h b/drivers/media/pci/cx18/cx18-dvb.h similarity index 100% rename from drivers/media/video/cx18/cx18-dvb.h rename to drivers/media/pci/cx18/cx18-dvb.h diff --git a/drivers/media/video/cx18/cx18-fileops.c b/drivers/media/pci/cx18/cx18-fileops.c similarity index 100% rename from drivers/media/video/cx18/cx18-fileops.c rename to drivers/media/pci/cx18/cx18-fileops.c diff --git a/drivers/media/video/cx18/cx18-fileops.h b/drivers/media/pci/cx18/cx18-fileops.h similarity index 100% rename from drivers/media/video/cx18/cx18-fileops.h rename to drivers/media/pci/cx18/cx18-fileops.h diff --git a/drivers/media/pci/cx18/cx18-firmware.c b/drivers/media/pci/cx18/cx18-firmware.c new file mode 100644 index 000000000000..a1c1cec05f98 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-firmware.c @@ -0,0 +1,459 @@ +/* + * cx18 firmware functions + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-scb.h" +#include "cx18-irq.h" +#include "cx18-firmware.h" +#include "cx18-cards.h" +#include + +#define CX18_PROC_SOFT_RESET 0xc70010 +#define CX18_DDR_SOFT_RESET 0xc70014 +#define CX18_CLOCK_SELECT1 0xc71000 +#define CX18_CLOCK_SELECT2 0xc71004 +#define CX18_HALF_CLOCK_SELECT1 0xc71008 +#define CX18_HALF_CLOCK_SELECT2 0xc7100C +#define CX18_CLOCK_POLARITY1 0xc71010 +#define CX18_CLOCK_POLARITY2 0xc71014 +#define CX18_ADD_DELAY_ENABLE1 0xc71018 +#define CX18_ADD_DELAY_ENABLE2 0xc7101C +#define CX18_CLOCK_ENABLE1 0xc71020 +#define CX18_CLOCK_ENABLE2 0xc71024 + +#define CX18_REG_BUS_TIMEOUT_EN 0xc72024 + +#define CX18_FAST_CLOCK_PLL_INT 0xc78000 +#define CX18_FAST_CLOCK_PLL_FRAC 0xc78004 +#define CX18_FAST_CLOCK_PLL_POST 0xc78008 +#define CX18_FAST_CLOCK_PLL_PRESCALE 0xc7800C +#define CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH 0xc78010 + +#define CX18_SLOW_CLOCK_PLL_INT 0xc78014 +#define CX18_SLOW_CLOCK_PLL_FRAC 0xc78018 +#define CX18_SLOW_CLOCK_PLL_POST 0xc7801C +#define CX18_MPEG_CLOCK_PLL_INT 0xc78040 +#define CX18_MPEG_CLOCK_PLL_FRAC 0xc78044 +#define CX18_MPEG_CLOCK_PLL_POST 0xc78048 +#define CX18_PLL_POWER_DOWN 0xc78088 +#define CX18_SW1_INT_STATUS 0xc73104 +#define CX18_SW1_INT_ENABLE_PCI 0xc7311C +#define CX18_SW2_INT_SET 0xc73140 +#define CX18_SW2_INT_STATUS 0xc73144 +#define CX18_ADEC_CONTROL 0xc78120 + +#define CX18_DDR_REQUEST_ENABLE 0xc80000 +#define CX18_DDR_CHIP_CONFIG 0xc80004 +#define CX18_DDR_REFRESH 0xc80008 +#define CX18_DDR_TIMING1 0xc8000C +#define CX18_DDR_TIMING2 0xc80010 +#define CX18_DDR_POWER_REG 0xc8001C + +#define CX18_DDR_TUNE_LANE 0xc80048 +#define CX18_DDR_INITIAL_EMRS 0xc80054 +#define CX18_DDR_MB_PER_ROW_7 0xc8009C +#define CX18_DDR_BASE_63_ADDR 0xc804FC + +#define CX18_WMB_CLIENT02 0xc90108 +#define CX18_WMB_CLIENT05 0xc90114 +#define CX18_WMB_CLIENT06 0xc90118 +#define CX18_WMB_CLIENT07 0xc9011C +#define CX18_WMB_CLIENT08 0xc90120 +#define CX18_WMB_CLIENT09 0xc90124 +#define CX18_WMB_CLIENT10 0xc90128 +#define CX18_WMB_CLIENT11 0xc9012C +#define CX18_WMB_CLIENT12 0xc90130 +#define CX18_WMB_CLIENT13 0xc90134 +#define CX18_WMB_CLIENT14 0xc90138 + +#define CX18_DSP0_INTERRUPT_MASK 0xd0004C + +#define APU_ROM_SYNC1 0x6D676553 /* "mgeS" */ +#define APU_ROM_SYNC2 0x72646548 /* "rdeH" */ + +struct cx18_apu_rom_seghdr { + u32 sync1; + u32 sync2; + u32 addr; + u32 size; +}; + +static int load_cpu_fw_direct(const char *fn, u8 __iomem *mem, struct cx18 *cx) +{ + const struct firmware *fw = NULL; + int i, j; + unsigned size; + u32 __iomem *dst = (u32 __iomem *)mem; + const u32 *src; + + if (request_firmware(&fw, fn, &cx->pci_dev->dev)) { + CX18_ERR("Unable to open firmware %s\n", fn); + CX18_ERR("Did you put the firmware in the hotplug firmware directory?\n"); + return -ENOMEM; + } + + src = (const u32 *)fw->data; + + for (i = 0; i < fw->size; i += 4096) { + cx18_setup_page(cx, i); + for (j = i; j < fw->size && j < i + 4096; j += 4) { + /* no need for endianness conversion on the ppc */ + cx18_raw_writel(cx, *src, dst); + if (cx18_raw_readl(cx, dst) != *src) { + CX18_ERR("Mismatch at offset %x\n", i); + release_firmware(fw); + cx18_setup_page(cx, 0); + return -EIO; + } + dst++; + src++; + } + } + if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags)) + CX18_INFO("loaded %s firmware (%zd bytes)\n", fn, fw->size); + size = fw->size; + release_firmware(fw); + cx18_setup_page(cx, SCB_OFFSET); + return size; +} + +static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx, + u32 *entry_addr) +{ + const struct firmware *fw = NULL; + int i, j; + unsigned size; + const u32 *src; + struct cx18_apu_rom_seghdr seghdr; + const u8 *vers; + u32 offset = 0; + u32 apu_version = 0; + int sz; + + if (request_firmware(&fw, fn, &cx->pci_dev->dev)) { + CX18_ERR("unable to open firmware %s\n", fn); + CX18_ERR("did you put the firmware in the hotplug firmware directory?\n"); + cx18_setup_page(cx, 0); + return -ENOMEM; + } + + *entry_addr = 0; + src = (const u32 *)fw->data; + vers = fw->data + sizeof(seghdr); + sz = fw->size; + + apu_version = (vers[0] << 24) | (vers[4] << 16) | vers[32]; + while (offset + sizeof(seghdr) < fw->size) { + const u32 *shptr = src + offset / 4; + + seghdr.sync1 = le32_to_cpu(shptr[0]); + seghdr.sync2 = le32_to_cpu(shptr[1]); + seghdr.addr = le32_to_cpu(shptr[2]); + seghdr.size = le32_to_cpu(shptr[3]); + + offset += sizeof(seghdr); + if (seghdr.sync1 != APU_ROM_SYNC1 || + seghdr.sync2 != APU_ROM_SYNC2) { + offset += seghdr.size; + continue; + } + CX18_DEBUG_INFO("load segment %x-%x\n", seghdr.addr, + seghdr.addr + seghdr.size - 1); + if (*entry_addr == 0) + *entry_addr = seghdr.addr; + if (offset + seghdr.size > sz) + break; + for (i = 0; i < seghdr.size; i += 4096) { + cx18_setup_page(cx, seghdr.addr + i); + for (j = i; j < seghdr.size && j < i + 4096; j += 4) { + /* no need for endianness conversion on the ppc */ + cx18_raw_writel(cx, src[(offset + j) / 4], + dst + seghdr.addr + j); + if (cx18_raw_readl(cx, dst + seghdr.addr + j) + != src[(offset + j) / 4]) { + CX18_ERR("Mismatch at offset %x\n", + offset + j); + release_firmware(fw); + cx18_setup_page(cx, 0); + return -EIO; + } + } + } + offset += seghdr.size; + } + if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags)) + CX18_INFO("loaded %s firmware V%08x (%zd bytes)\n", + fn, apu_version, fw->size); + size = fw->size; + release_firmware(fw); + cx18_setup_page(cx, 0); + return size; +} + +void cx18_halt_firmware(struct cx18 *cx) +{ + CX18_DEBUG_INFO("Preparing for firmware halt.\n"); + cx18_write_reg_expect(cx, 0x000F000F, CX18_PROC_SOFT_RESET, + 0x0000000F, 0x000F000F); + cx18_write_reg_expect(cx, 0x00020002, CX18_ADEC_CONTROL, + 0x00000002, 0x00020002); +} + +void cx18_init_power(struct cx18 *cx, int lowpwr) +{ + /* power-down Spare and AOM PLLs */ + /* power-up fast, slow and mpeg PLLs */ + cx18_write_reg(cx, 0x00000008, CX18_PLL_POWER_DOWN); + + /* ADEC out of sleep */ + cx18_write_reg_expect(cx, 0x00020000, CX18_ADEC_CONTROL, + 0x00000000, 0x00020002); + + /* + * The PLL parameters are based on the external crystal frequency that + * would ideally be: + * + * NTSC Color subcarrier freq * 8 = + * 4.5 MHz/286 * 455/2 * 8 = 28.63636363... MHz + * + * The accidents of history and rationale that explain from where this + * combination of magic numbers originate can be found in: + * + * [1] Abrahams, I. C., "Choice of Chrominance Subcarrier Frequency in + * the NTSC Standards", Proceedings of the I-R-E, January 1954, pp 79-80 + * + * [2] Abrahams, I. C., "The 'Frequency Interleaving' Principle in the + * NTSC Standards", Proceedings of the I-R-E, January 1954, pp 81-83 + * + * As Mike Bradley has rightly pointed out, it's not the exact crystal + * frequency that matters, only that all parts of the driver and + * firmware are using the same value (close to the ideal value). + * + * Since I have a strong suspicion that, if the firmware ever assumes a + * crystal value at all, it will assume 28.636360 MHz, the crystal + * freq used in calculations in this driver will be: + * + * xtal_freq = 28.636360 MHz + * + * an error of less than 0.13 ppm which is way, way better than any off + * the shelf crystal will have for accuracy anyway. + * + * Below I aim to run the PLLs' VCOs near 400 MHz to minimze errors. + * + * Many thanks to Jeff Campbell and Mike Bradley for their extensive + * investigation, experimentation, testing, and suggested solutions of + * of audio/video sync problems with SVideo and CVBS captures. + */ + + /* the fast clock is at 200/245 MHz */ + /* 1 * xtal_freq * 0x0d.f7df9b8 / 2 = 200 MHz: 400 MHz pre post-divide*/ + /* 1 * xtal_freq * 0x11.1c71eb8 / 2 = 245 MHz: 490 MHz pre post-divide*/ + cx18_write_reg(cx, lowpwr ? 0xD : 0x11, CX18_FAST_CLOCK_PLL_INT); + cx18_write_reg(cx, lowpwr ? 0x1EFBF37 : 0x038E3D7, + CX18_FAST_CLOCK_PLL_FRAC); + + cx18_write_reg(cx, 2, CX18_FAST_CLOCK_PLL_POST); + cx18_write_reg(cx, 1, CX18_FAST_CLOCK_PLL_PRESCALE); + cx18_write_reg(cx, 4, CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH); + + /* set slow clock to 125/120 MHz */ + /* xtal_freq * 0x0d.1861a20 / 3 = 125 MHz: 375 MHz before post-divide */ + /* xtal_freq * 0x0c.92493f8 / 3 = 120 MHz: 360 MHz before post-divide */ + cx18_write_reg(cx, lowpwr ? 0xD : 0xC, CX18_SLOW_CLOCK_PLL_INT); + cx18_write_reg(cx, lowpwr ? 0x30C344 : 0x124927F, + CX18_SLOW_CLOCK_PLL_FRAC); + cx18_write_reg(cx, 3, CX18_SLOW_CLOCK_PLL_POST); + + /* mpeg clock pll 54MHz */ + /* xtal_freq * 0xf.15f17f0 / 8 = 54 MHz: 432 MHz before post-divide */ + cx18_write_reg(cx, 0xF, CX18_MPEG_CLOCK_PLL_INT); + cx18_write_reg(cx, 0x2BE2FE, CX18_MPEG_CLOCK_PLL_FRAC); + cx18_write_reg(cx, 8, CX18_MPEG_CLOCK_PLL_POST); + + /* Defaults */ + /* APU = SC or SC/2 = 125/62.5 */ + /* EPU = SC = 125 */ + /* DDR = FC = 180 */ + /* ENC = SC = 125 */ + /* AI1 = SC = 125 */ + /* VIM2 = disabled */ + /* PCI = FC/2 = 90 */ + /* AI2 = disabled */ + /* DEMUX = disabled */ + /* AO = SC/2 = 62.5 */ + /* SER = 54MHz */ + /* VFC = disabled */ + /* USB = disabled */ + + if (lowpwr) { + cx18_write_reg_expect(cx, 0xFFFF0020, CX18_CLOCK_SELECT1, + 0x00000020, 0xFFFFFFFF); + cx18_write_reg_expect(cx, 0xFFFF0004, CX18_CLOCK_SELECT2, + 0x00000004, 0xFFFFFFFF); + } else { + /* This doesn't explicitly set every clock select */ + cx18_write_reg_expect(cx, 0x00060004, CX18_CLOCK_SELECT1, + 0x00000004, 0x00060006); + cx18_write_reg_expect(cx, 0x00060006, CX18_CLOCK_SELECT2, + 0x00000006, 0x00060006); + } + + cx18_write_reg_expect(cx, 0xFFFF0002, CX18_HALF_CLOCK_SELECT1, + 0x00000002, 0xFFFFFFFF); + cx18_write_reg_expect(cx, 0xFFFF0104, CX18_HALF_CLOCK_SELECT2, + 0x00000104, 0xFFFFFFFF); + cx18_write_reg_expect(cx, 0xFFFF9026, CX18_CLOCK_ENABLE1, + 0x00009026, 0xFFFFFFFF); + cx18_write_reg_expect(cx, 0xFFFF3105, CX18_CLOCK_ENABLE2, + 0x00003105, 0xFFFFFFFF); +} + +void cx18_init_memory(struct cx18 *cx) +{ + cx18_msleep_timeout(10, 0); + cx18_write_reg_expect(cx, 0x00010000, CX18_DDR_SOFT_RESET, + 0x00000000, 0x00010001); + cx18_msleep_timeout(10, 0); + + cx18_write_reg(cx, cx->card->ddr.chip_config, CX18_DDR_CHIP_CONFIG); + + cx18_msleep_timeout(10, 0); + + cx18_write_reg(cx, cx->card->ddr.refresh, CX18_DDR_REFRESH); + cx18_write_reg(cx, cx->card->ddr.timing1, CX18_DDR_TIMING1); + cx18_write_reg(cx, cx->card->ddr.timing2, CX18_DDR_TIMING2); + + cx18_msleep_timeout(10, 0); + + /* Initialize DQS pad time */ + cx18_write_reg(cx, cx->card->ddr.tune_lane, CX18_DDR_TUNE_LANE); + cx18_write_reg(cx, cx->card->ddr.initial_emrs, CX18_DDR_INITIAL_EMRS); + + cx18_msleep_timeout(10, 0); + + cx18_write_reg_expect(cx, 0x00020000, CX18_DDR_SOFT_RESET, + 0x00000000, 0x00020002); + cx18_msleep_timeout(10, 0); + + /* use power-down mode when idle */ + cx18_write_reg(cx, 0x00000010, CX18_DDR_POWER_REG); + + cx18_write_reg_expect(cx, 0x00010001, CX18_REG_BUS_TIMEOUT_EN, + 0x00000001, 0x00010001); + + cx18_write_reg(cx, 0x48, CX18_DDR_MB_PER_ROW_7); + cx18_write_reg(cx, 0xE0000, CX18_DDR_BASE_63_ADDR); + + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT02); /* AO */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT09); /* AI2 */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT05); /* VIM1 */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT06); /* AI1 */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT07); /* 3D comb */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT10); /* ME */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT12); /* ENC */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT13); /* PK */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT11); /* RC */ + cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT14); /* AVO */ +} + +#define CX18_CPU_FIRMWARE "v4l-cx23418-cpu.fw" +#define CX18_APU_FIRMWARE "v4l-cx23418-apu.fw" + +int cx18_firmware_init(struct cx18 *cx) +{ + u32 fw_entry_addr; + int sz, retries; + u32 api_args[MAX_MB_ARGUMENTS]; + + /* Allow chip to control CLKRUN */ + cx18_write_reg(cx, 0x5, CX18_DSP0_INTERRUPT_MASK); + + /* Stop the firmware */ + cx18_write_reg_expect(cx, 0x000F000F, CX18_PROC_SOFT_RESET, + 0x0000000F, 0x000F000F); + + cx18_msleep_timeout(1, 0); + + /* If the CPU is still running */ + if ((cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 8) == 0) { + CX18_ERR("%s: couldn't stop CPU to load firmware\n", __func__); + return -EIO; + } + + cx18_sw1_irq_enable(cx, IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU); + cx18_sw2_irq_enable(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK); + + sz = load_cpu_fw_direct(CX18_CPU_FIRMWARE, cx->enc_mem, cx); + if (sz <= 0) + return sz; + + /* The SCB & IPC area *must* be correct before starting the firmwares */ + cx18_init_scb(cx); + + fw_entry_addr = 0; + sz = load_apu_fw_direct(CX18_APU_FIRMWARE, cx->enc_mem, cx, + &fw_entry_addr); + if (sz <= 0) + return sz; + + /* Start the CPU. The CPU will take care of the APU for us. */ + cx18_write_reg_expect(cx, 0x00080000, CX18_PROC_SOFT_RESET, + 0x00000000, 0x00080008); + + /* Wait up to 500 ms for the APU to come out of reset */ + for (retries = 0; + retries < 50 && (cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 1) == 1; + retries++) + cx18_msleep_timeout(10, 0); + + cx18_msleep_timeout(200, 0); + + if (retries == 50 && + (cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 1) == 1) { + CX18_ERR("Could not start the CPU\n"); + return -EIO; + } + + /* + * The CPU had once before set up to receive an interrupt for it's + * outgoing IRQ_CPU_TO_EPU_ACK to us. If it ever does this, we get an + * interrupt when it sends us an ack, but by the time we process it, + * that flag in the SW2 status register has been cleared by the CPU + * firmware. We'll prevent that not so useful condition from happening + * by clearing the CPU's interrupt enables for Ack IRQ's we want to + * process. + */ + cx18_sw2_irq_disable_cpu(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK); + + /* Try a benign command to see if the CPU is alive and well */ + sz = cx18_vapi_result(cx, api_args, CX18_CPU_DEBUG_PEEK32, 1, 0); + if (sz < 0) + return sz; + + /* initialize GPIO */ + cx18_write_reg_expect(cx, 0x14001400, 0xc78110, 0x00001400, 0x14001400); + return 0; +} + +MODULE_FIRMWARE(CX18_CPU_FIRMWARE); +MODULE_FIRMWARE(CX18_APU_FIRMWARE); diff --git a/drivers/media/video/cx18/cx18-firmware.h b/drivers/media/pci/cx18/cx18-firmware.h similarity index 100% rename from drivers/media/video/cx18/cx18-firmware.h rename to drivers/media/pci/cx18/cx18-firmware.h diff --git a/drivers/media/video/cx18/cx18-gpio.c b/drivers/media/pci/cx18/cx18-gpio.c similarity index 100% rename from drivers/media/video/cx18/cx18-gpio.c rename to drivers/media/pci/cx18/cx18-gpio.c diff --git a/drivers/media/video/cx18/cx18-gpio.h b/drivers/media/pci/cx18/cx18-gpio.h similarity index 100% rename from drivers/media/video/cx18/cx18-gpio.h rename to drivers/media/pci/cx18/cx18-gpio.h diff --git a/drivers/media/video/cx18/cx18-i2c.c b/drivers/media/pci/cx18/cx18-i2c.c similarity index 100% rename from drivers/media/video/cx18/cx18-i2c.c rename to drivers/media/pci/cx18/cx18-i2c.c diff --git a/drivers/media/video/cx18/cx18-i2c.h b/drivers/media/pci/cx18/cx18-i2c.h similarity index 100% rename from drivers/media/video/cx18/cx18-i2c.h rename to drivers/media/pci/cx18/cx18-i2c.h diff --git a/drivers/media/video/cx18/cx18-io.c b/drivers/media/pci/cx18/cx18-io.c similarity index 100% rename from drivers/media/video/cx18/cx18-io.c rename to drivers/media/pci/cx18/cx18-io.c diff --git a/drivers/media/video/cx18/cx18-io.h b/drivers/media/pci/cx18/cx18-io.h similarity index 100% rename from drivers/media/video/cx18/cx18-io.h rename to drivers/media/pci/cx18/cx18-io.h diff --git a/drivers/media/pci/cx18/cx18-ioctl.c b/drivers/media/pci/cx18/cx18-ioctl.c new file mode 100644 index 000000000000..cd8d2c2b1624 --- /dev/null +++ b/drivers/media/pci/cx18/cx18-ioctl.c @@ -0,0 +1,1190 @@ +/* + * cx18 ioctl system call + * + * Derived from ivtv-ioctl.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-version.h" +#include "cx18-mailbox.h" +#include "cx18-i2c.h" +#include "cx18-queue.h" +#include "cx18-fileops.h" +#include "cx18-vbi.h" +#include "cx18-audio.h" +#include "cx18-video.h" +#include "cx18-streams.h" +#include "cx18-ioctl.h" +#include "cx18-gpio.h" +#include "cx18-controls.h" +#include "cx18-cards.h" +#include "cx18-av-core.h" +#include +#include + +u16 cx18_service2vbi(int type) +{ + switch (type) { + case V4L2_SLICED_TELETEXT_B: + return CX18_SLICED_TYPE_TELETEXT_B; + case V4L2_SLICED_CAPTION_525: + return CX18_SLICED_TYPE_CAPTION_525; + case V4L2_SLICED_WSS_625: + return CX18_SLICED_TYPE_WSS_625; + case V4L2_SLICED_VPS: + return CX18_SLICED_TYPE_VPS; + default: + return 0; + } +} + +/* Check if VBI services are allowed on the (field, line) for the video std */ +static int valid_service_line(int field, int line, int is_pal) +{ + return (is_pal && line >= 6 && + ((field == 0 && line <= 23) || (field == 1 && line <= 22))) || + (!is_pal && line >= 10 && line < 22); +} + +/* + * For a (field, line, std) and inbound potential set of services for that line, + * return the first valid service of those passed in the incoming set for that + * line in priority order: + * CC, VPS, or WSS over TELETEXT for well known lines + * TELETEXT, before VPS, before CC, before WSS, for other lines + */ +static u16 select_service_from_set(int field, int line, u16 set, int is_pal) +{ + u16 valid_set = (is_pal ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525); + int i; + + set = set & valid_set; + if (set == 0 || !valid_service_line(field, line, is_pal)) + return 0; + if (!is_pal) { + if (line == 21 && (set & V4L2_SLICED_CAPTION_525)) + return V4L2_SLICED_CAPTION_525; + } else { + if (line == 16 && field == 0 && (set & V4L2_SLICED_VPS)) + return V4L2_SLICED_VPS; + if (line == 23 && field == 0 && (set & V4L2_SLICED_WSS_625)) + return V4L2_SLICED_WSS_625; + if (line == 23) + return 0; + } + for (i = 0; i < 32; i++) { + if ((1 << i) & set) + return 1 << i; + } + return 0; +} + +/* + * Expand the service_set of *fmt into valid service_lines for the std, + * and clear the passed in fmt->service_set + */ +void cx18_expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + u16 set = fmt->service_set; + int f, l; + + fmt->service_set = 0; + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) + fmt->service_lines[f][l] = select_service_from_set(f, l, set, is_pal); + } +} + +/* + * Sanitize the service_lines in *fmt per the video std, and return 1 + * if any service_line is left as valid after santization + */ +static int check_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + int f, l; + u16 set = 0; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + fmt->service_lines[f][l] = select_service_from_set(f, l, fmt->service_lines[f][l], is_pal); + set |= fmt->service_lines[f][l]; + } + } + return set != 0; +} + +/* Compute the service_set from the assumed valid service_lines of *fmt */ +u16 cx18_get_service_set(struct v4l2_sliced_vbi_format *fmt) +{ + int f, l; + u16 set = 0; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) + set |= fmt->service_lines[f][l]; + } + return set; +} + +static int cx18_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + pixfmt->width = cx->cxhdl.width; + pixfmt->height = cx->cxhdl.height; + pixfmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + pixfmt->field = V4L2_FIELD_INTERLACED; + pixfmt->priv = 0; + if (id->type == CX18_ENC_STREAM_TYPE_YUV) { + pixfmt->pixelformat = s->pixelformat; + pixfmt->sizeimage = s->vb_bytes_per_frame; + pixfmt->bytesperline = 720; + } else { + pixfmt->pixelformat = V4L2_PIX_FMT_MPEG; + pixfmt->sizeimage = 128 * 1024; + pixfmt->bytesperline = 0; + } + return 0; +} + +static int cx18_g_fmt_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18 *cx = fh2id(fh)->cx; + struct v4l2_vbi_format *vbifmt = &fmt->fmt.vbi; + + vbifmt->sampling_rate = 27000000; + vbifmt->offset = 248; /* FIXME - slightly wrong for both 50 & 60 Hz */ + vbifmt->samples_per_line = vbi_active_samples - 4; + vbifmt->sample_format = V4L2_PIX_FMT_GREY; + vbifmt->start[0] = cx->vbi.start[0]; + vbifmt->start[1] = cx->vbi.start[1]; + vbifmt->count[0] = vbifmt->count[1] = cx->vbi.count; + vbifmt->flags = 0; + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + return 0; +} + +static int cx18_g_fmt_sliced_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18 *cx = fh2id(fh)->cx; + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + /* sane, V4L2 spec compliant, defaults */ + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + memset(vbifmt->service_lines, 0, sizeof(vbifmt->service_lines)); + vbifmt->service_set = 0; + + /* + * Fetch the configured service_lines and total service_set from the + * digitizer/slicer. Note, cx18_av_vbi() wipes the passed in + * fmt->fmt.sliced under valid calling conditions + */ + if (v4l2_subdev_call(cx->sd_av, vbi, g_sliced_fmt, &fmt->fmt.sliced)) + return -EINVAL; + + vbifmt->service_set = cx18_get_service_set(vbifmt); + return 0; +} + +static int cx18_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + int w = fmt->fmt.pix.width; + int h = fmt->fmt.pix.height; + int min_h = 2; + + w = min(w, 720); + w = max(w, 2); + if (id->type == CX18_ENC_STREAM_TYPE_YUV) { + /* YUV height must be a multiple of 32 */ + h &= ~0x1f; + min_h = 32; + } + h = min(h, cx->is_50hz ? 576 : 480); + h = max(h, min_h); + + fmt->fmt.pix.width = w; + fmt->fmt.pix.height = h; + return 0; +} + +static int cx18_try_fmt_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + return cx18_g_fmt_vbi_cap(file, fh, fmt); +} + +static int cx18_try_fmt_sliced_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18 *cx = fh2id(fh)->cx; + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + + /* If given a service set, expand it validly & clear passed in set */ + if (vbifmt->service_set) + cx18_expand_service_set(vbifmt, cx->is_50hz); + /* Sanitize the service_lines, and compute the new set if any valid */ + if (check_service_set(vbifmt, cx->is_50hz)) + vbifmt->service_set = cx18_get_service_set(vbifmt); + return 0; +} + +static int cx18_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + struct v4l2_mbus_framefmt mbus_fmt; + struct cx18_stream *s = &cx->streams[id->type]; + int ret; + int w, h; + + ret = cx18_try_fmt_vid_cap(file, fh, fmt); + if (ret) + return ret; + w = fmt->fmt.pix.width; + h = fmt->fmt.pix.height; + + if (cx->cxhdl.width == w && cx->cxhdl.height == h && + s->pixelformat == fmt->fmt.pix.pixelformat) + return 0; + + if (atomic_read(&cx->ana_capturing) > 0) + return -EBUSY; + + s->pixelformat = fmt->fmt.pix.pixelformat; + /* HM12 YUV size is (Y=(h*720) + UV=(h*(720/2))) + UYUV YUV size is (Y=(h*720) + UV=(h*(720))) */ + if (s->pixelformat == V4L2_PIX_FMT_HM12) + s->vb_bytes_per_frame = h * 720 * 3 / 2; + else + s->vb_bytes_per_frame = h * 720 * 2; + + mbus_fmt.width = cx->cxhdl.width = w; + mbus_fmt.height = cx->cxhdl.height = h; + mbus_fmt.code = V4L2_MBUS_FMT_FIXED; + v4l2_subdev_call(cx->sd_av, video, s_mbus_fmt, &mbus_fmt); + return cx18_g_fmt_vid_cap(file, fh, fmt); +} + +static int cx18_s_fmt_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + int ret; + + /* + * Changing the Encoder's Raw VBI parameters won't have any effect + * if any analog capture is ongoing + */ + if (!cx18_raw_vbi(cx) && atomic_read(&cx->ana_capturing) > 0) + return -EBUSY; + + /* + * Set the digitizer registers for raw active VBI. + * Note cx18_av_vbi_wipes out a lot of the passed in fmt under valid + * calling conditions + */ + ret = v4l2_subdev_call(cx->sd_av, vbi, s_raw_fmt, &fmt->fmt.vbi); + if (ret) + return ret; + + /* Store our new v4l2 (non-)sliced VBI state */ + cx->vbi.sliced_in->service_set = 0; + cx->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE; + + return cx18_g_fmt_vbi_cap(file, fh, fmt); +} + +static int cx18_s_fmt_sliced_vbi_cap(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + int ret; + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + cx18_try_fmt_sliced_vbi_cap(file, fh, fmt); + + /* + * Changing the Encoder's Raw VBI parameters won't have any effect + * if any analog capture is ongoing + */ + if (cx18_raw_vbi(cx) && atomic_read(&cx->ana_capturing) > 0) + return -EBUSY; + + /* + * Set the service_lines requested in the digitizer/slicer registers. + * Note, cx18_av_vbi() wipes some "impossible" service lines in the + * passed in fmt->fmt.sliced under valid calling conditions + */ + ret = v4l2_subdev_call(cx->sd_av, vbi, s_sliced_fmt, &fmt->fmt.sliced); + if (ret) + return ret; + /* Store our current v4l2 sliced VBI settings */ + cx->vbi.in.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE; + memcpy(cx->vbi.sliced_in, vbifmt, sizeof(*cx->vbi.sliced_in)); + return 0; +} + +static int cx18_g_chip_ident(struct file *file, void *fh, + struct v4l2_dbg_chip_ident *chip) +{ + struct cx18 *cx = fh2id(fh)->cx; + int err = 0; + + chip->ident = V4L2_IDENT_NONE; + chip->revision = 0; + switch (chip->match.type) { + case V4L2_CHIP_MATCH_HOST: + switch (chip->match.addr) { + case 0: + chip->ident = V4L2_IDENT_CX23418; + chip->revision = cx18_read_reg(cx, 0xC72028); + break; + case 1: + /* + * The A/V decoder is always present, but in the rare + * case that the card doesn't have analog, we don't + * use it. We find it w/o using the cx->sd_av pointer + */ + cx18_call_hw(cx, CX18_HW_418_AV, + core, g_chip_ident, chip); + break; + default: + /* + * Could return ident = V4L2_IDENT_UNKNOWN if we had + * other host chips at higher addresses, but we don't + */ + err = -EINVAL; /* per V4L2 spec */ + break; + } + break; + case V4L2_CHIP_MATCH_I2C_DRIVER: + /* If needed, returns V4L2_IDENT_AMBIGUOUS without extra work */ + cx18_call_all(cx, core, g_chip_ident, chip); + break; + case V4L2_CHIP_MATCH_I2C_ADDR: + /* + * We could return V4L2_IDENT_UNKNOWN, but we don't do the work + * to look if a chip is at the address with no driver. That's a + * dangerous thing to do with EEPROMs anyway. + */ + cx18_call_all(cx, core, g_chip_ident, chip); + break; + default: + err = -EINVAL; + break; + } + return err; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cx18_cxc(struct cx18 *cx, unsigned int cmd, void *arg) +{ + struct v4l2_dbg_register *regs = arg; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (regs->reg >= CX18_MEM_OFFSET + CX18_MEM_SIZE) + return -EINVAL; + + regs->size = 4; + if (cmd == VIDIOC_DBG_S_REGISTER) + cx18_write_enc(cx, regs->val, regs->reg); + else + regs->val = cx18_read_enc(cx, regs->reg); + return 0; +} + +static int cx18_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx18 *cx = fh2id(fh)->cx; + + if (v4l2_chip_match_host(®->match)) + return cx18_cxc(cx, VIDIOC_DBG_G_REGISTER, reg); + /* FIXME - errors shouldn't be ignored */ + cx18_call_all(cx, core, g_register, reg); + return 0; +} + +static int cx18_s_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx18 *cx = fh2id(fh)->cx; + + if (v4l2_chip_match_host(®->match)) + return cx18_cxc(cx, VIDIOC_DBG_S_REGISTER, reg); + /* FIXME - errors shouldn't be ignored */ + cx18_call_all(cx, core, s_register, reg); + return 0; +} +#endif + +static int cx18_querycap(struct file *file, void *fh, + struct v4l2_capability *vcap) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + + strlcpy(vcap->driver, CX18_DRIVER_NAME, sizeof(vcap->driver)); + strlcpy(vcap->card, cx->card_name, sizeof(vcap->card)); + snprintf(vcap->bus_info, sizeof(vcap->bus_info), + "PCI:%s", pci_name(cx->pci_dev)); + vcap->capabilities = cx->v4l2_cap; /* capabilities */ + if (id->type == CX18_ENC_STREAM_TYPE_YUV) + vcap->capabilities |= V4L2_CAP_STREAMING; + return 0; +} + +static int cx18_enumaudio(struct file *file, void *fh, struct v4l2_audio *vin) +{ + struct cx18 *cx = fh2id(fh)->cx; + + return cx18_get_audio_input(cx, vin->index, vin); +} + +static int cx18_g_audio(struct file *file, void *fh, struct v4l2_audio *vin) +{ + struct cx18 *cx = fh2id(fh)->cx; + + vin->index = cx->audio_input; + return cx18_get_audio_input(cx, vin->index, vin); +} + +static int cx18_s_audio(struct file *file, void *fh, const struct v4l2_audio *vout) +{ + struct cx18 *cx = fh2id(fh)->cx; + + if (vout->index >= cx->nof_audio_inputs) + return -EINVAL; + cx->audio_input = vout->index; + cx18_audio_set_io(cx); + return 0; +} + +static int cx18_enum_input(struct file *file, void *fh, struct v4l2_input *vin) +{ + struct cx18 *cx = fh2id(fh)->cx; + + /* set it to defaults from our table */ + return cx18_get_input(cx, vin->index, vin); +} + +static int cx18_cropcap(struct file *file, void *fh, + struct v4l2_cropcap *cropcap) +{ + struct cx18 *cx = fh2id(fh)->cx; + + if (cropcap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + cropcap->bounds.top = cropcap->bounds.left = 0; + cropcap->bounds.width = 720; + cropcap->bounds.height = cx->is_50hz ? 576 : 480; + cropcap->pixelaspect.numerator = cx->is_50hz ? 59 : 10; + cropcap->pixelaspect.denominator = cx->is_50hz ? 54 : 11; + cropcap->defrect = cropcap->bounds; + return 0; +} + +static int cx18_s_crop(struct file *file, void *fh, const struct v4l2_crop *crop) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + CX18_DEBUG_WARN("VIDIOC_S_CROP not implemented\n"); + return -EINVAL; +} + +static int cx18_g_crop(struct file *file, void *fh, struct v4l2_crop *crop) +{ + struct cx18 *cx = fh2id(fh)->cx; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + CX18_DEBUG_WARN("VIDIOC_G_CROP not implemented\n"); + return -EINVAL; +} + +static int cx18_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *fmt) +{ + static const struct v4l2_fmtdesc formats[] = { + { 0, V4L2_BUF_TYPE_VIDEO_CAPTURE, 0, + "HM12 (YUV 4:1:1)", V4L2_PIX_FMT_HM12, { 0, 0, 0, 0 } + }, + { 1, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FMT_FLAG_COMPRESSED, + "MPEG", V4L2_PIX_FMT_MPEG, { 0, 0, 0, 0 } + }, + { 2, V4L2_BUF_TYPE_VIDEO_CAPTURE, 0, + "UYVY 4:2:2", V4L2_PIX_FMT_UYVY, { 0, 0, 0, 0 } + }, + }; + + if (fmt->index > ARRAY_SIZE(formats) - 1) + return -EINVAL; + *fmt = formats[fmt->index]; + return 0; +} + +static int cx18_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct cx18 *cx = fh2id(fh)->cx; + + *i = cx->active_input; + return 0; +} + +int cx18_s_input(struct file *file, void *fh, unsigned int inp) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + + if (inp >= cx->nof_inputs) + return -EINVAL; + + if (inp == cx->active_input) { + CX18_DEBUG_INFO("Input unchanged\n"); + return 0; + } + + CX18_DEBUG_INFO("Changing input from %d to %d\n", + cx->active_input, inp); + + cx->active_input = inp; + /* Set the audio input to whatever is appropriate for the input type. */ + cx->audio_input = cx->card->video_inputs[inp].audio_index; + + /* prevent others from messing with the streams until + we're finished changing inputs. */ + cx18_mute(cx); + cx18_video_set_io(cx); + cx18_audio_set_io(cx); + cx18_unmute(cx); + return 0; +} + +static int cx18_g_frequency(struct file *file, void *fh, + struct v4l2_frequency *vf) +{ + struct cx18 *cx = fh2id(fh)->cx; + + if (vf->tuner != 0) + return -EINVAL; + + cx18_call_all(cx, tuner, g_frequency, vf); + return 0; +} + +int cx18_s_frequency(struct file *file, void *fh, struct v4l2_frequency *vf) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + + if (vf->tuner != 0) + return -EINVAL; + + cx18_mute(cx); + CX18_DEBUG_INFO("v4l2 ioctl: set frequency %d\n", vf->frequency); + cx18_call_all(cx, tuner, s_frequency, vf); + cx18_unmute(cx); + return 0; +} + +static int cx18_g_std(struct file *file, void *fh, v4l2_std_id *std) +{ + struct cx18 *cx = fh2id(fh)->cx; + + *std = cx->std; + return 0; +} + +int cx18_s_std(struct file *file, void *fh, v4l2_std_id *std) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + + if ((*std & V4L2_STD_ALL) == 0) + return -EINVAL; + + if (*std == cx->std) + return 0; + + if (test_bit(CX18_F_I_RADIO_USER, &cx->i_flags) || + atomic_read(&cx->ana_capturing) > 0) { + /* Switching standard would turn off the radio or mess + with already running streams, prevent that by + returning EBUSY. */ + return -EBUSY; + } + + cx->std = *std; + cx->is_60hz = (*std & V4L2_STD_525_60) ? 1 : 0; + cx->is_50hz = !cx->is_60hz; + cx2341x_handler_set_50hz(&cx->cxhdl, cx->is_50hz); + cx->cxhdl.width = 720; + cx->cxhdl.height = cx->is_50hz ? 576 : 480; + cx->vbi.count = cx->is_50hz ? 18 : 12; + cx->vbi.start[0] = cx->is_50hz ? 6 : 10; + cx->vbi.start[1] = cx->is_50hz ? 318 : 273; + CX18_DEBUG_INFO("Switching standard to %llx.\n", + (unsigned long long) cx->std); + + /* Tuner */ + cx18_call_all(cx, core, s_std, cx->std); + return 0; +} + +static int cx18_s_tuner(struct file *file, void *fh, struct v4l2_tuner *vt) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + + if (vt->index != 0) + return -EINVAL; + + cx18_call_all(cx, tuner, s_tuner, vt); + return 0; +} + +static int cx18_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt) +{ + struct cx18 *cx = fh2id(fh)->cx; + + if (vt->index != 0) + return -EINVAL; + + cx18_call_all(cx, tuner, g_tuner, vt); + + if (vt->type == V4L2_TUNER_RADIO) + strlcpy(vt->name, "cx18 Radio Tuner", sizeof(vt->name)); + else + strlcpy(vt->name, "cx18 TV Tuner", sizeof(vt->name)); + return 0; +} + +static int cx18_g_sliced_vbi_cap(struct file *file, void *fh, + struct v4l2_sliced_vbi_cap *cap) +{ + struct cx18 *cx = fh2id(fh)->cx; + int set = cx->is_50hz ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525; + int f, l; + + if (cap->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) + return -EINVAL; + + cap->service_set = 0; + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + if (valid_service_line(f, l, cx->is_50hz)) { + /* + * We can find all v4l2 supported vbi services + * for the standard, on a valid line for the std + */ + cap->service_lines[f][l] = set; + cap->service_set |= set; + } else + cap->service_lines[f][l] = 0; + } + } + for (f = 0; f < 3; f++) + cap->reserved[f] = 0; + return 0; +} + +static int _cx18_process_idx_data(struct cx18_buffer *buf, + struct v4l2_enc_idx *idx) +{ + int consumed, remaining; + struct v4l2_enc_idx_entry *e_idx; + struct cx18_enc_idx_entry *e_buf; + + /* Frame type lookup: 1=I, 2=P, 4=B */ + const int mapping[8] = { + -1, V4L2_ENC_IDX_FRAME_I, V4L2_ENC_IDX_FRAME_P, + -1, V4L2_ENC_IDX_FRAME_B, -1, -1, -1 + }; + + /* + * Assumption here is that a buf holds an integral number of + * struct cx18_enc_idx_entry objects and is properly aligned. + * This is enforced by the module options on IDX buffer sizes. + */ + remaining = buf->bytesused - buf->readpos; + consumed = 0; + e_idx = &idx->entry[idx->entries]; + e_buf = (struct cx18_enc_idx_entry *) &buf->buf[buf->readpos]; + + while (remaining >= sizeof(struct cx18_enc_idx_entry) && + idx->entries < V4L2_ENC_IDX_ENTRIES) { + + e_idx->offset = (((u64) le32_to_cpu(e_buf->offset_high)) << 32) + | le32_to_cpu(e_buf->offset_low); + + e_idx->pts = (((u64) (le32_to_cpu(e_buf->pts_high) & 1)) << 32) + | le32_to_cpu(e_buf->pts_low); + + e_idx->length = le32_to_cpu(e_buf->length); + + e_idx->flags = mapping[le32_to_cpu(e_buf->flags) & 0x7]; + + e_idx->reserved[0] = 0; + e_idx->reserved[1] = 0; + + idx->entries++; + e_idx = &idx->entry[idx->entries]; + e_buf++; + + remaining -= sizeof(struct cx18_enc_idx_entry); + consumed += sizeof(struct cx18_enc_idx_entry); + } + + /* Swallow any partial entries at the end, if there are any */ + if (remaining > 0 && remaining < sizeof(struct cx18_enc_idx_entry)) + consumed += remaining; + + buf->readpos += consumed; + return consumed; +} + +static int cx18_process_idx_data(struct cx18_stream *s, struct cx18_mdl *mdl, + struct v4l2_enc_idx *idx) +{ + if (s->type != CX18_ENC_STREAM_TYPE_IDX) + return -EINVAL; + + if (mdl->curr_buf == NULL) + mdl->curr_buf = list_first_entry(&mdl->buf_list, + struct cx18_buffer, list); + + if (list_entry_is_past_end(mdl->curr_buf, &mdl->buf_list, list)) { + /* + * For some reason we've exhausted the buffers, but the MDL + * object still said some data was unread. + * Fix that and bail out. + */ + mdl->readpos = mdl->bytesused; + return 0; + } + + list_for_each_entry_from(mdl->curr_buf, &mdl->buf_list, list) { + + /* Skip any empty buffers in the MDL */ + if (mdl->curr_buf->readpos >= mdl->curr_buf->bytesused) + continue; + + mdl->readpos += _cx18_process_idx_data(mdl->curr_buf, idx); + + /* exit when MDL drained or request satisfied */ + if (idx->entries >= V4L2_ENC_IDX_ENTRIES || + mdl->curr_buf->readpos < mdl->curr_buf->bytesused || + mdl->readpos >= mdl->bytesused) + break; + } + return 0; +} + +static int cx18_g_enc_index(struct file *file, void *fh, + struct v4l2_enc_idx *idx) +{ + struct cx18 *cx = fh2id(fh)->cx; + struct cx18_stream *s = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + s32 tmp; + struct cx18_mdl *mdl; + + if (!cx18_stream_enabled(s)) /* Module options inhibited IDX stream */ + return -EINVAL; + + /* Compute the best case number of entries we can buffer */ + tmp = s->buffers - + s->bufs_per_mdl * CX18_ENC_STREAM_TYPE_IDX_FW_MDL_MIN; + if (tmp <= 0) + tmp = 1; + tmp = tmp * s->buf_size / sizeof(struct cx18_enc_idx_entry); + + /* Fill out the header of the return structure */ + idx->entries = 0; + idx->entries_cap = tmp; + memset(idx->reserved, 0, sizeof(idx->reserved)); + + /* Pull IDX MDLs and buffers from q_full and populate the entries */ + do { + mdl = cx18_dequeue(s, &s->q_full); + if (mdl == NULL) /* No more IDX data right now */ + break; + + /* Extract the Index entry data from the MDL and buffers */ + cx18_process_idx_data(s, mdl, idx); + if (mdl->readpos < mdl->bytesused) { + /* We finished with data remaining, push the MDL back */ + cx18_push(s, mdl, &s->q_full); + break; + } + + /* We drained this MDL, schedule it to go to the firmware */ + cx18_enqueue(s, mdl, &s->q_free); + + } while (idx->entries < V4L2_ENC_IDX_ENTRIES); + + /* Tell the work handler to send free IDX MDLs to the firmware */ + cx18_stream_load_fw_queue(s); + return 0; +} + +static struct videobuf_queue *cx18_vb_queue(struct cx18_open_id *id) +{ + struct videobuf_queue *q = NULL; + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + + switch (s->vb_type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + q = &s->vbuf_q; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + break; + default: + break; + } + return q; +} + +static int cx18_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct cx18_open_id *id = file->private_data; + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + + /* Start the hardware only if we're the video device */ + if ((s->vb_type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->vb_type != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + if (id->type != CX18_ENC_STREAM_TYPE_YUV) + return -EINVAL; + + /* Establish a buffer timeout */ + mod_timer(&s->vb_timeout, msecs_to_jiffies(2000) + jiffies); + + return videobuf_streamon(cx18_vb_queue(id)); +} + +static int cx18_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct cx18_open_id *id = file->private_data; + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + + /* Start the hardware only if we're the video device */ + if ((s->vb_type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->vb_type != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + if (id->type != CX18_ENC_STREAM_TYPE_YUV) + return -EINVAL; + + return videobuf_streamoff(cx18_vb_queue(id)); +} + +static int cx18_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + struct cx18_open_id *id = file->private_data; + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + + if ((s->vb_type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->vb_type != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + return videobuf_reqbufs(cx18_vb_queue(id), rb); +} + +static int cx18_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct cx18_open_id *id = file->private_data; + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + + if ((s->vb_type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->vb_type != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + return videobuf_querybuf(cx18_vb_queue(id), b); +} + +static int cx18_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct cx18_open_id *id = file->private_data; + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + + if ((s->vb_type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->vb_type != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + return videobuf_qbuf(cx18_vb_queue(id), b); +} + +static int cx18_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct cx18_open_id *id = file->private_data; + struct cx18 *cx = id->cx; + struct cx18_stream *s = &cx->streams[id->type]; + + if ((s->vb_type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->vb_type != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + return videobuf_dqbuf(cx18_vb_queue(id), b, file->f_flags & O_NONBLOCK); +} + +static int cx18_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *enc) +{ + struct cx18_open_id *id = fh2id(fh); + struct cx18 *cx = id->cx; + u32 h; + + switch (enc->cmd) { + case V4L2_ENC_CMD_START: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_START\n"); + enc->flags = 0; + return cx18_start_capture(id); + + case V4L2_ENC_CMD_STOP: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_STOP\n"); + enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END; + cx18_stop_capture(id, + enc->flags & V4L2_ENC_CMD_STOP_AT_GOP_END); + break; + + case V4L2_ENC_CMD_PAUSE: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_PAUSE\n"); + enc->flags = 0; + if (!atomic_read(&cx->ana_capturing)) + return -EPERM; + if (test_and_set_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags)) + return 0; + h = cx18_find_handle(cx); + if (h == CX18_INVALID_TASK_HANDLE) { + CX18_ERR("Can't find valid task handle for " + "V4L2_ENC_CMD_PAUSE\n"); + return -EBADFD; + } + cx18_mute(cx); + cx18_vapi(cx, CX18_CPU_CAPTURE_PAUSE, 1, h); + break; + + case V4L2_ENC_CMD_RESUME: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_RESUME\n"); + enc->flags = 0; + if (!atomic_read(&cx->ana_capturing)) + return -EPERM; + if (!test_and_clear_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags)) + return 0; + h = cx18_find_handle(cx); + if (h == CX18_INVALID_TASK_HANDLE) { + CX18_ERR("Can't find valid task handle for " + "V4L2_ENC_CMD_RESUME\n"); + return -EBADFD; + } + cx18_vapi(cx, CX18_CPU_CAPTURE_RESUME, 1, h); + cx18_unmute(cx); + break; + + default: + CX18_DEBUG_IOCTL("Unknown cmd %d\n", enc->cmd); + return -EINVAL; + } + return 0; +} + +static int cx18_try_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *enc) +{ + struct cx18 *cx = fh2id(fh)->cx; + + switch (enc->cmd) { + case V4L2_ENC_CMD_START: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_START\n"); + enc->flags = 0; + break; + + case V4L2_ENC_CMD_STOP: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_STOP\n"); + enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END; + break; + + case V4L2_ENC_CMD_PAUSE: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_PAUSE\n"); + enc->flags = 0; + break; + + case V4L2_ENC_CMD_RESUME: + CX18_DEBUG_IOCTL("V4L2_ENC_CMD_RESUME\n"); + enc->flags = 0; + break; + + default: + CX18_DEBUG_IOCTL("Unknown cmd %d\n", enc->cmd); + return -EINVAL; + } + return 0; +} + +static int cx18_log_status(struct file *file, void *fh) +{ + struct cx18 *cx = fh2id(fh)->cx; + struct v4l2_input vidin; + struct v4l2_audio audin; + int i; + + CX18_INFO("Version: %s Card: %s\n", CX18_VERSION, cx->card_name); + if (cx->hw_flags & CX18_HW_TVEEPROM) { + struct tveeprom tv; + + cx18_read_eeprom(cx, &tv); + } + cx18_call_all(cx, core, log_status); + cx18_get_input(cx, cx->active_input, &vidin); + cx18_get_audio_input(cx, cx->audio_input, &audin); + CX18_INFO("Video Input: %s\n", vidin.name); + CX18_INFO("Audio Input: %s\n", audin.name); + mutex_lock(&cx->gpio_lock); + CX18_INFO("GPIO: direction 0x%08x, value 0x%08x\n", + cx->gpio_dir, cx->gpio_val); + mutex_unlock(&cx->gpio_lock); + CX18_INFO("Tuner: %s\n", + test_bit(CX18_F_I_RADIO_USER, &cx->i_flags) ? "Radio" : "TV"); + v4l2_ctrl_handler_log_status(&cx->cxhdl.hdl, cx->v4l2_dev.name); + CX18_INFO("Status flags: 0x%08lx\n", cx->i_flags); + for (i = 0; i < CX18_MAX_STREAMS; i++) { + struct cx18_stream *s = &cx->streams[i]; + + if (s->video_dev == NULL || s->buffers == 0) + continue; + CX18_INFO("Stream %s: status 0x%04lx, %d%% of %d KiB (%d buffers) in use\n", + s->name, s->s_flags, + atomic_read(&s->q_full.depth) * s->bufs_per_mdl * 100 + / s->buffers, + (s->buffers * s->buf_size) / 1024, s->buffers); + } + CX18_INFO("Read MPEG/VBI: %lld/%lld bytes\n", + (long long)cx->mpg_data_received, + (long long)cx->vbi_data_inserted); + return 0; +} + +static long cx18_default(struct file *file, void *fh, bool valid_prio, + int cmd, void *arg) +{ + struct cx18 *cx = fh2id(fh)->cx; + + switch (cmd) { + case VIDIOC_INT_RESET: { + u32 val = *(u32 *)arg; + + if ((val == 0) || (val & 0x01)) + cx18_call_hw(cx, CX18_HW_GPIO_RESET_CTRL, core, reset, + (u32) CX18_GPIO_RESET_Z8F0811); + break; + } + + default: + return -ENOTTY; + } + return 0; +} + +static const struct v4l2_ioctl_ops cx18_ioctl_ops = { + .vidioc_querycap = cx18_querycap, + .vidioc_s_audio = cx18_s_audio, + .vidioc_g_audio = cx18_g_audio, + .vidioc_enumaudio = cx18_enumaudio, + .vidioc_enum_input = cx18_enum_input, + .vidioc_cropcap = cx18_cropcap, + .vidioc_s_crop = cx18_s_crop, + .vidioc_g_crop = cx18_g_crop, + .vidioc_g_input = cx18_g_input, + .vidioc_s_input = cx18_s_input, + .vidioc_g_frequency = cx18_g_frequency, + .vidioc_s_frequency = cx18_s_frequency, + .vidioc_s_tuner = cx18_s_tuner, + .vidioc_g_tuner = cx18_g_tuner, + .vidioc_g_enc_index = cx18_g_enc_index, + .vidioc_g_std = cx18_g_std, + .vidioc_s_std = cx18_s_std, + .vidioc_log_status = cx18_log_status, + .vidioc_enum_fmt_vid_cap = cx18_enum_fmt_vid_cap, + .vidioc_encoder_cmd = cx18_encoder_cmd, + .vidioc_try_encoder_cmd = cx18_try_encoder_cmd, + .vidioc_g_fmt_vid_cap = cx18_g_fmt_vid_cap, + .vidioc_g_fmt_vbi_cap = cx18_g_fmt_vbi_cap, + .vidioc_g_fmt_sliced_vbi_cap = cx18_g_fmt_sliced_vbi_cap, + .vidioc_s_fmt_vid_cap = cx18_s_fmt_vid_cap, + .vidioc_s_fmt_vbi_cap = cx18_s_fmt_vbi_cap, + .vidioc_s_fmt_sliced_vbi_cap = cx18_s_fmt_sliced_vbi_cap, + .vidioc_try_fmt_vid_cap = cx18_try_fmt_vid_cap, + .vidioc_try_fmt_vbi_cap = cx18_try_fmt_vbi_cap, + .vidioc_try_fmt_sliced_vbi_cap = cx18_try_fmt_sliced_vbi_cap, + .vidioc_g_sliced_vbi_cap = cx18_g_sliced_vbi_cap, + .vidioc_g_chip_ident = cx18_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = cx18_g_register, + .vidioc_s_register = cx18_s_register, +#endif + .vidioc_default = cx18_default, + .vidioc_streamon = cx18_streamon, + .vidioc_streamoff = cx18_streamoff, + .vidioc_reqbufs = cx18_reqbufs, + .vidioc_querybuf = cx18_querybuf, + .vidioc_qbuf = cx18_qbuf, + .vidioc_dqbuf = cx18_dqbuf, +}; + +void cx18_set_funcs(struct video_device *vdev) +{ + vdev->ioctl_ops = &cx18_ioctl_ops; +} diff --git a/drivers/media/video/cx18/cx18-ioctl.h b/drivers/media/pci/cx18/cx18-ioctl.h similarity index 100% rename from drivers/media/video/cx18/cx18-ioctl.h rename to drivers/media/pci/cx18/cx18-ioctl.h diff --git a/drivers/media/video/cx18/cx18-irq.c b/drivers/media/pci/cx18/cx18-irq.c similarity index 100% rename from drivers/media/video/cx18/cx18-irq.c rename to drivers/media/pci/cx18/cx18-irq.c diff --git a/drivers/media/video/cx18/cx18-irq.h b/drivers/media/pci/cx18/cx18-irq.h similarity index 100% rename from drivers/media/video/cx18/cx18-irq.h rename to drivers/media/pci/cx18/cx18-irq.h diff --git a/drivers/media/video/cx18/cx18-mailbox.c b/drivers/media/pci/cx18/cx18-mailbox.c similarity index 100% rename from drivers/media/video/cx18/cx18-mailbox.c rename to drivers/media/pci/cx18/cx18-mailbox.c diff --git a/drivers/media/video/cx18/cx18-mailbox.h b/drivers/media/pci/cx18/cx18-mailbox.h similarity index 100% rename from drivers/media/video/cx18/cx18-mailbox.h rename to drivers/media/pci/cx18/cx18-mailbox.h diff --git a/drivers/media/video/cx18/cx18-queue.c b/drivers/media/pci/cx18/cx18-queue.c similarity index 100% rename from drivers/media/video/cx18/cx18-queue.c rename to drivers/media/pci/cx18/cx18-queue.c diff --git a/drivers/media/video/cx18/cx18-queue.h b/drivers/media/pci/cx18/cx18-queue.h similarity index 100% rename from drivers/media/video/cx18/cx18-queue.h rename to drivers/media/pci/cx18/cx18-queue.h diff --git a/drivers/media/video/cx18/cx18-scb.c b/drivers/media/pci/cx18/cx18-scb.c similarity index 100% rename from drivers/media/video/cx18/cx18-scb.c rename to drivers/media/pci/cx18/cx18-scb.c diff --git a/drivers/media/video/cx18/cx18-scb.h b/drivers/media/pci/cx18/cx18-scb.h similarity index 100% rename from drivers/media/video/cx18/cx18-scb.h rename to drivers/media/pci/cx18/cx18-scb.h diff --git a/drivers/media/pci/cx18/cx18-streams.c b/drivers/media/pci/cx18/cx18-streams.c new file mode 100644 index 000000000000..72af9b5c2d7d --- /dev/null +++ b/drivers/media/pci/cx18/cx18-streams.c @@ -0,0 +1,1059 @@ +/* + * cx18 init/start/stop/exit stream functions + * + * Derived from ivtv-streams.c + * + * Copyright (C) 2007 Hans Verkuil + * Copyright (C) 2008 Andy Walls + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include "cx18-driver.h" +#include "cx18-io.h" +#include "cx18-fileops.h" +#include "cx18-mailbox.h" +#include "cx18-i2c.h" +#include "cx18-queue.h" +#include "cx18-ioctl.h" +#include "cx18-streams.h" +#include "cx18-cards.h" +#include "cx18-scb.h" +#include "cx18-dvb.h" + +#define CX18_DSP0_INTERRUPT_MASK 0xd0004C + +static struct v4l2_file_operations cx18_v4l2_enc_fops = { + .owner = THIS_MODULE, + .read = cx18_v4l2_read, + .open = cx18_v4l2_open, + .unlocked_ioctl = video_ioctl2, + .release = cx18_v4l2_close, + .poll = cx18_v4l2_enc_poll, + .mmap = cx18_v4l2_mmap, +}; + +/* offset from 0 to register ts v4l2 minors on */ +#define CX18_V4L2_ENC_TS_OFFSET 16 +/* offset from 0 to register pcm v4l2 minors on */ +#define CX18_V4L2_ENC_PCM_OFFSET 24 +/* offset from 0 to register yuv v4l2 minors on */ +#define CX18_V4L2_ENC_YUV_OFFSET 32 + +static struct { + const char *name; + int vfl_type; + int num_offset; + int dma; +} cx18_stream_info[] = { + { /* CX18_ENC_STREAM_TYPE_MPG */ + "encoder MPEG", + VFL_TYPE_GRABBER, 0, + PCI_DMA_FROMDEVICE, + }, + { /* CX18_ENC_STREAM_TYPE_TS */ + "TS", + VFL_TYPE_GRABBER, -1, + PCI_DMA_FROMDEVICE, + }, + { /* CX18_ENC_STREAM_TYPE_YUV */ + "encoder YUV", + VFL_TYPE_GRABBER, CX18_V4L2_ENC_YUV_OFFSET, + PCI_DMA_FROMDEVICE, + }, + { /* CX18_ENC_STREAM_TYPE_VBI */ + "encoder VBI", + VFL_TYPE_VBI, 0, + PCI_DMA_FROMDEVICE, + }, + { /* CX18_ENC_STREAM_TYPE_PCM */ + "encoder PCM audio", + VFL_TYPE_GRABBER, CX18_V4L2_ENC_PCM_OFFSET, + PCI_DMA_FROMDEVICE, + }, + { /* CX18_ENC_STREAM_TYPE_IDX */ + "encoder IDX", + VFL_TYPE_GRABBER, -1, + PCI_DMA_FROMDEVICE, + }, + { /* CX18_ENC_STREAM_TYPE_RAD */ + "encoder radio", + VFL_TYPE_RADIO, 0, + PCI_DMA_NONE, + }, +}; + + +void cx18_dma_free(struct videobuf_queue *q, + struct cx18_stream *s, struct cx18_videobuf_buffer *buf) +{ + videobuf_waiton(q, &buf->vb, 0, 0); + videobuf_vmalloc_free(&buf->vb); + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +static int cx18_prepare_buffer(struct videobuf_queue *q, + struct cx18_stream *s, + struct cx18_videobuf_buffer *buf, + u32 pixelformat, + unsigned int width, unsigned int height, + enum v4l2_field field) +{ + struct cx18 *cx = s->cx; + int rc = 0; + + /* check settings */ + buf->bytes_used = 0; + + if ((width < 48) || (height < 32)) + return -EINVAL; + + buf->vb.size = (width * height * 2); + if ((buf->vb.baddr != 0) && (buf->vb.bsize < buf->vb.size)) + return -EINVAL; + + /* alloc + fill struct (if changed) */ + if (buf->vb.width != width || buf->vb.height != height || + buf->vb.field != field || s->pixelformat != pixelformat || + buf->tvnorm != cx->std) { + + buf->vb.width = width; + buf->vb.height = height; + buf->vb.field = field; + buf->tvnorm = cx->std; + s->pixelformat = pixelformat; + + /* HM12 YUV size is (Y=(h*720) + UV=(h*(720/2))) + UYUV YUV size is (Y=(h*720) + UV=(h*(720))) */ + if (s->pixelformat == V4L2_PIX_FMT_HM12) + s->vb_bytes_per_frame = height * 720 * 3 / 2; + else + s->vb_bytes_per_frame = height * 720 * 2; + cx18_dma_free(q, s, buf); + } + + if ((buf->vb.baddr != 0) && (buf->vb.bsize < buf->vb.size)) + return -EINVAL; + + if (buf->vb.field == 0) + buf->vb.field = V4L2_FIELD_INTERLACED; + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + buf->vb.width = width; + buf->vb.height = height; + buf->vb.field = field; + buf->tvnorm = cx->std; + s->pixelformat = pixelformat; + + /* HM12 YUV size is (Y=(h*720) + UV=(h*(720/2))) + UYUV YUV size is (Y=(h*720) + UV=(h*(720))) */ + if (s->pixelformat == V4L2_PIX_FMT_HM12) + s->vb_bytes_per_frame = height * 720 * 3 / 2; + else + s->vb_bytes_per_frame = height * 720 * 2; + rc = videobuf_iolock(q, &buf->vb, NULL); + if (rc != 0) + goto fail; + } + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + +fail: + cx18_dma_free(q, s, buf); + return rc; + +} + +/* VB_MIN_BUFSIZE is lcm(1440 * 480, 1440 * 576) + 1440 is a single line of 4:2:2 YUV at 720 luma samples wide +*/ +#define VB_MIN_BUFFERS 32 +#define VB_MIN_BUFSIZE 4147200 + +static int buffer_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct cx18_stream *s = q->priv_data; + struct cx18 *cx = s->cx; + + *size = 2 * cx->cxhdl.width * cx->cxhdl.height; + if (*count == 0) + *count = VB_MIN_BUFFERS; + + while (*size * *count > VB_MIN_BUFFERS * VB_MIN_BUFSIZE) + (*count)--; + + q->field = V4L2_FIELD_INTERLACED; + q->last = V4L2_FIELD_INTERLACED; + + return 0; +} + +static int buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx18_videobuf_buffer *buf = + container_of(vb, struct cx18_videobuf_buffer, vb); + struct cx18_stream *s = q->priv_data; + struct cx18 *cx = s->cx; + + return cx18_prepare_buffer(q, s, buf, s->pixelformat, + cx->cxhdl.width, cx->cxhdl.height, field); +} + +static void buffer_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct cx18_videobuf_buffer *buf = + container_of(vb, struct cx18_videobuf_buffer, vb); + struct cx18_stream *s = q->priv_data; + + cx18_dma_free(q, s, buf); +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx18_videobuf_buffer *buf = + container_of(vb, struct cx18_videobuf_buffer, vb); + struct cx18_stream *s = q->priv_data; + + buf->vb.state = VIDEOBUF_QUEUED; + + list_add_tail(&buf->vb.queue, &s->vb_capture); +} + +static struct videobuf_queue_ops cx18_videobuf_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +static void cx18_stream_init(struct cx18 *cx, int type) +{ + struct cx18_stream *s = &cx->streams[type]; + struct video_device *video_dev = s->video_dev; + + /* we need to keep video_dev, so restore it afterwards */ + memset(s, 0, sizeof(*s)); + s->video_dev = video_dev; + + /* initialize cx18_stream fields */ + s->dvb = NULL; + s->cx = cx; + s->type = type; + s->name = cx18_stream_info[type].name; + s->handle = CX18_INVALID_TASK_HANDLE; + + s->dma = cx18_stream_info[type].dma; + s->buffers = cx->stream_buffers[type]; + s->buf_size = cx->stream_buf_size[type]; + INIT_LIST_HEAD(&s->buf_pool); + s->bufs_per_mdl = 1; + s->mdl_size = s->buf_size * s->bufs_per_mdl; + + init_waitqueue_head(&s->waitq); + s->id = -1; + spin_lock_init(&s->q_free.lock); + cx18_queue_init(&s->q_free); + spin_lock_init(&s->q_busy.lock); + cx18_queue_init(&s->q_busy); + spin_lock_init(&s->q_full.lock); + cx18_queue_init(&s->q_full); + spin_lock_init(&s->q_idle.lock); + cx18_queue_init(&s->q_idle); + + INIT_WORK(&s->out_work_order, cx18_out_work_handler); + + INIT_LIST_HEAD(&s->vb_capture); + s->vb_timeout.function = cx18_vb_timeout; + s->vb_timeout.data = (unsigned long)s; + init_timer(&s->vb_timeout); + spin_lock_init(&s->vb_lock); + if (type == CX18_ENC_STREAM_TYPE_YUV) { + spin_lock_init(&s->vbuf_q_lock); + + s->vb_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + videobuf_queue_vmalloc_init(&s->vbuf_q, &cx18_videobuf_qops, + &cx->pci_dev->dev, &s->vbuf_q_lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx18_videobuf_buffer), + s, &cx->serialize_lock); + + /* Assume the previous pixel default */ + s->pixelformat = V4L2_PIX_FMT_HM12; + s->vb_bytes_per_frame = cx->cxhdl.height * 720 * 3 / 2; + } +} + +static int cx18_prep_dev(struct cx18 *cx, int type) +{ + struct cx18_stream *s = &cx->streams[type]; + u32 cap = cx->v4l2_cap; + int num_offset = cx18_stream_info[type].num_offset; + int num = cx->instance + cx18_first_minor + num_offset; + + /* + * These five fields are always initialized. + * For analog capture related streams, if video_dev == NULL then the + * stream is not in use. + * For the TS stream, if dvb == NULL then the stream is not in use. + * In those cases no other fields but these four can be used. + */ + s->video_dev = NULL; + s->dvb = NULL; + s->cx = cx; + s->type = type; + s->name = cx18_stream_info[type].name; + + /* Check whether the radio is supported */ + if (type == CX18_ENC_STREAM_TYPE_RAD && !(cap & V4L2_CAP_RADIO)) + return 0; + + /* Check whether VBI is supported */ + if (type == CX18_ENC_STREAM_TYPE_VBI && + !(cap & (V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE))) + return 0; + + /* User explicitly selected 0 buffers for these streams, so don't + create them. */ + if (cx18_stream_info[type].dma != PCI_DMA_NONE && + cx->stream_buffers[type] == 0) { + CX18_INFO("Disabled %s device\n", cx18_stream_info[type].name); + return 0; + } + + cx18_stream_init(cx, type); + + /* Allocate the cx18_dvb struct only for the TS on cards with DTV */ + if (type == CX18_ENC_STREAM_TYPE_TS) { + if (cx->card->hw_all & CX18_HW_DVB) { + s->dvb = kzalloc(sizeof(struct cx18_dvb), GFP_KERNEL); + if (s->dvb == NULL) { + CX18_ERR("Couldn't allocate cx18_dvb structure" + " for %s\n", s->name); + return -ENOMEM; + } + } else { + /* Don't need buffers for the TS, if there is no DVB */ + s->buffers = 0; + } + } + + if (num_offset == -1) + return 0; + + /* allocate and initialize the v4l2 video device structure */ + s->video_dev = video_device_alloc(); + if (s->video_dev == NULL) { + CX18_ERR("Couldn't allocate v4l2 video_device for %s\n", + s->name); + return -ENOMEM; + } + + snprintf(s->video_dev->name, sizeof(s->video_dev->name), "%s %s", + cx->v4l2_dev.name, s->name); + + s->video_dev->num = num; + s->video_dev->v4l2_dev = &cx->v4l2_dev; + s->video_dev->fops = &cx18_v4l2_enc_fops; + s->video_dev->release = video_device_release; + s->video_dev->tvnorms = V4L2_STD_ALL; + s->video_dev->lock = &cx->serialize_lock; + set_bit(V4L2_FL_USE_FH_PRIO, &s->video_dev->flags); + cx18_set_funcs(s->video_dev); + return 0; +} + +/* Initialize v4l2 variables and register v4l2 devices */ +int cx18_streams_setup(struct cx18 *cx) +{ + int type, ret; + + /* Setup V4L2 Devices */ + for (type = 0; type < CX18_MAX_STREAMS; type++) { + /* Prepare device */ + ret = cx18_prep_dev(cx, type); + if (ret < 0) + break; + + /* Allocate Stream */ + ret = cx18_stream_alloc(&cx->streams[type]); + if (ret < 0) + break; + } + if (type == CX18_MAX_STREAMS) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + cx18_streams_cleanup(cx, 0); + return ret; +} + +static int cx18_reg_dev(struct cx18 *cx, int type) +{ + struct cx18_stream *s = &cx->streams[type]; + int vfl_type = cx18_stream_info[type].vfl_type; + const char *name; + int num, ret; + + if (type == CX18_ENC_STREAM_TYPE_TS && s->dvb != NULL) { + ret = cx18_dvb_register(s); + if (ret < 0) { + CX18_ERR("DVB failed to register\n"); + return ret; + } + } + + if (s->video_dev == NULL) + return 0; + + num = s->video_dev->num; + /* card number + user defined offset + device offset */ + if (type != CX18_ENC_STREAM_TYPE_MPG) { + struct cx18_stream *s_mpg = &cx->streams[CX18_ENC_STREAM_TYPE_MPG]; + + if (s_mpg->video_dev) + num = s_mpg->video_dev->num + + cx18_stream_info[type].num_offset; + } + video_set_drvdata(s->video_dev, s); + + /* Register device. First try the desired minor, then any free one. */ + ret = video_register_device_no_warn(s->video_dev, vfl_type, num); + if (ret < 0) { + CX18_ERR("Couldn't register v4l2 device for %s (device node number %d)\n", + s->name, num); + video_device_release(s->video_dev); + s->video_dev = NULL; + return ret; + } + + name = video_device_node_name(s->video_dev); + + switch (vfl_type) { + case VFL_TYPE_GRABBER: + CX18_INFO("Registered device %s for %s (%d x %d.%02d kB)\n", + name, s->name, cx->stream_buffers[type], + cx->stream_buf_size[type] / 1024, + (cx->stream_buf_size[type] * 100 / 1024) % 100); + break; + + case VFL_TYPE_RADIO: + CX18_INFO("Registered device %s for %s\n", name, s->name); + break; + + case VFL_TYPE_VBI: + if (cx->stream_buffers[type]) + CX18_INFO("Registered device %s for %s " + "(%d x %d bytes)\n", + name, s->name, cx->stream_buffers[type], + cx->stream_buf_size[type]); + else + CX18_INFO("Registered device %s for %s\n", + name, s->name); + break; + } + + return 0; +} + +/* Register v4l2 devices */ +int cx18_streams_register(struct cx18 *cx) +{ + int type; + int err; + int ret = 0; + + /* Register V4L2 devices */ + for (type = 0; type < CX18_MAX_STREAMS; type++) { + err = cx18_reg_dev(cx, type); + if (err && ret == 0) + ret = err; + } + + if (ret == 0) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + cx18_streams_cleanup(cx, 1); + return ret; +} + +/* Unregister v4l2 devices */ +void cx18_streams_cleanup(struct cx18 *cx, int unregister) +{ + struct video_device *vdev; + int type; + + /* Teardown all streams */ + for (type = 0; type < CX18_MAX_STREAMS; type++) { + + /* The TS has a cx18_dvb structure, not a video_device */ + if (type == CX18_ENC_STREAM_TYPE_TS) { + if (cx->streams[type].dvb != NULL) { + if (unregister) + cx18_dvb_unregister(&cx->streams[type]); + kfree(cx->streams[type].dvb); + cx->streams[type].dvb = NULL; + cx18_stream_free(&cx->streams[type]); + } + continue; + } + + /* No struct video_device, but can have buffers allocated */ + if (type == CX18_ENC_STREAM_TYPE_IDX) { + /* If the module params didn't inhibit IDX ... */ + if (cx->stream_buffers[type] != 0) { + cx->stream_buffers[type] = 0; + /* + * Before calling cx18_stream_free(), + * check if the IDX stream was actually set up. + * Needed, since the cx18_probe() error path + * exits through here as well as normal clean up + */ + if (cx->streams[type].buffers != 0) + cx18_stream_free(&cx->streams[type]); + } + continue; + } + + /* If struct video_device exists, can have buffers allocated */ + vdev = cx->streams[type].video_dev; + + cx->streams[type].video_dev = NULL; + if (vdev == NULL) + continue; + + if (type == CX18_ENC_STREAM_TYPE_YUV) + videobuf_mmap_free(&cx->streams[type].vbuf_q); + + cx18_stream_free(&cx->streams[type]); + + /* Unregister or release device */ + if (unregister) + video_unregister_device(vdev); + else + video_device_release(vdev); + } +} + +static void cx18_vbi_setup(struct cx18_stream *s) +{ + struct cx18 *cx = s->cx; + int raw = cx18_raw_vbi(cx); + u32 data[CX2341X_MBOX_MAX_DATA]; + int lines; + + if (cx->is_60hz) { + cx->vbi.count = 12; + cx->vbi.start[0] = 10; + cx->vbi.start[1] = 273; + } else { /* PAL/SECAM */ + cx->vbi.count = 18; + cx->vbi.start[0] = 6; + cx->vbi.start[1] = 318; + } + + /* setup VBI registers */ + if (raw) + v4l2_subdev_call(cx->sd_av, vbi, s_raw_fmt, &cx->vbi.in.fmt.vbi); + else + v4l2_subdev_call(cx->sd_av, vbi, s_sliced_fmt, &cx->vbi.in.fmt.sliced); + + /* + * Send the CX18_CPU_SET_RAW_VBI_PARAM API command to setup Encoder Raw + * VBI when the first analog capture channel starts, as once it starts + * (e.g. MPEG), we can't effect any change in the Encoder Raw VBI setup + * (i.e. for the VBI capture channels). We also send it for each + * analog capture channel anyway just to make sure we get the proper + * behavior + */ + if (raw) { + lines = cx->vbi.count * 2; + } else { + /* + * For 525/60 systems, according to the VIP 2 & BT.656 std: + * The EAV RP code's Field bit toggles on line 4, a few lines + * after the Vertcal Blank bit has already toggled. + * Tell the encoder to capture 21-4+1=18 lines per field, + * since we want lines 10 through 21. + * + * For 625/50 systems, according to the VIP 2 & BT.656 std: + * The EAV RP code's Field bit toggles on line 1, a few lines + * after the Vertcal Blank bit has already toggled. + * (We've actually set the digitizer so that the Field bit + * toggles on line 2.) Tell the encoder to capture 23-2+1=22 + * lines per field, since we want lines 6 through 23. + */ + lines = cx->is_60hz ? (21 - 4 + 1) * 2 : (23 - 2 + 1) * 2; + } + + data[0] = s->handle; + /* Lines per field */ + data[1] = (lines / 2) | ((lines / 2) << 16); + /* bytes per line */ + data[2] = (raw ? vbi_active_samples + : (cx->is_60hz ? vbi_hblank_samples_60Hz + : vbi_hblank_samples_50Hz)); + /* Every X number of frames a VBI interrupt arrives + (frames as in 25 or 30 fps) */ + data[3] = 1; + /* + * Set the SAV/EAV RP codes to look for as start/stop points + * when in VIP-1.1 mode + */ + if (raw) { + /* + * Start codes for beginning of "active" line in vertical blank + * 0x20 ( VerticalBlank ) + * 0x60 ( EvenField VerticalBlank ) + */ + data[4] = 0x20602060; + /* + * End codes for end of "active" raw lines and regular lines + * 0x30 ( VerticalBlank HorizontalBlank) + * 0x70 ( EvenField VerticalBlank HorizontalBlank) + * 0x90 (Task HorizontalBlank) + * 0xd0 (Task EvenField HorizontalBlank) + */ + data[5] = 0x307090d0; + } else { + /* + * End codes for active video, we want data in the hblank region + * 0xb0 (Task 0 VerticalBlank HorizontalBlank) + * 0xf0 (Task EvenField VerticalBlank HorizontalBlank) + * + * Since the V bit is only allowed to toggle in the EAV RP code, + * just before the first active region line, these two + * are problematic: + * 0x90 (Task HorizontalBlank) + * 0xd0 (Task EvenField HorizontalBlank) + * + * We have set the digitzer such that we don't have to worry + * about these problem codes. + */ + data[4] = 0xB0F0B0F0; + /* + * Start codes for beginning of active line in vertical blank + * 0xa0 (Task VerticalBlank ) + * 0xe0 (Task EvenField VerticalBlank ) + */ + data[5] = 0xA0E0A0E0; + } + + CX18_DEBUG_INFO("Setup VBI h: %d lines %x bpl %d fr %d %x %x\n", + data[0], data[1], data[2], data[3], data[4], data[5]); + + cx18_api(cx, CX18_CPU_SET_RAW_VBI_PARAM, 6, data); +} + +void cx18_stream_rotate_idx_mdls(struct cx18 *cx) +{ + struct cx18_stream *s = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + struct cx18_mdl *mdl; + + if (!cx18_stream_enabled(s)) + return; + + /* Return if the firmware is not running low on MDLs */ + if ((atomic_read(&s->q_free.depth) + atomic_read(&s->q_busy.depth)) >= + CX18_ENC_STREAM_TYPE_IDX_FW_MDL_MIN) + return; + + /* Return if there are no MDLs to rotate back to the firmware */ + if (atomic_read(&s->q_full.depth) < 2) + return; + + /* + * Take the oldest IDX MDL still holding data, and discard its index + * entries by scheduling the MDL to go back to the firmware + */ + mdl = cx18_dequeue(s, &s->q_full); + if (mdl != NULL) + cx18_enqueue(s, mdl, &s->q_free); +} + +static +struct cx18_queue *_cx18_stream_put_mdl_fw(struct cx18_stream *s, + struct cx18_mdl *mdl) +{ + struct cx18 *cx = s->cx; + struct cx18_queue *q; + + /* Don't give it to the firmware, if we're not running a capture */ + if (s->handle == CX18_INVALID_TASK_HANDLE || + test_bit(CX18_F_S_STOPPING, &s->s_flags) || + !test_bit(CX18_F_S_STREAMING, &s->s_flags)) + return cx18_enqueue(s, mdl, &s->q_free); + + q = cx18_enqueue(s, mdl, &s->q_busy); + if (q != &s->q_busy) + return q; /* The firmware has the max MDLs it can handle */ + + cx18_mdl_sync_for_device(s, mdl); + cx18_vapi(cx, CX18_CPU_DE_SET_MDL, 5, s->handle, + (void __iomem *) &cx->scb->cpu_mdl[mdl->id] - cx->enc_mem, + s->bufs_per_mdl, mdl->id, s->mdl_size); + return q; +} + +static +void _cx18_stream_load_fw_queue(struct cx18_stream *s) +{ + struct cx18_queue *q; + struct cx18_mdl *mdl; + + if (atomic_read(&s->q_free.depth) == 0 || + atomic_read(&s->q_busy.depth) >= CX18_MAX_FW_MDLS_PER_STREAM) + return; + + /* Move from q_free to q_busy notifying the firmware, until the limit */ + do { + mdl = cx18_dequeue(s, &s->q_free); + if (mdl == NULL) + break; + q = _cx18_stream_put_mdl_fw(s, mdl); + } while (atomic_read(&s->q_busy.depth) < CX18_MAX_FW_MDLS_PER_STREAM + && q == &s->q_busy); +} + +void cx18_out_work_handler(struct work_struct *work) +{ + struct cx18_stream *s = + container_of(work, struct cx18_stream, out_work_order); + + _cx18_stream_load_fw_queue(s); +} + +static void cx18_stream_configure_mdls(struct cx18_stream *s) +{ + cx18_unload_queues(s); + + switch (s->type) { + case CX18_ENC_STREAM_TYPE_YUV: + /* + * Height should be a multiple of 32 lines. + * Set the MDL size to the exact size needed for one frame. + * Use enough buffers per MDL to cover the MDL size + */ + if (s->pixelformat == V4L2_PIX_FMT_HM12) + s->mdl_size = 720 * s->cx->cxhdl.height * 3 / 2; + else + s->mdl_size = 720 * s->cx->cxhdl.height * 2; + s->bufs_per_mdl = s->mdl_size / s->buf_size; + if (s->mdl_size % s->buf_size) + s->bufs_per_mdl++; + break; + case CX18_ENC_STREAM_TYPE_VBI: + s->bufs_per_mdl = 1; + if (cx18_raw_vbi(s->cx)) { + s->mdl_size = (s->cx->is_60hz ? 12 : 18) + * 2 * vbi_active_samples; + } else { + /* + * See comment in cx18_vbi_setup() below about the + * extra lines we capture in sliced VBI mode due to + * the lines on which EAV RP codes toggle. + */ + s->mdl_size = s->cx->is_60hz + ? (21 - 4 + 1) * 2 * vbi_hblank_samples_60Hz + : (23 - 2 + 1) * 2 * vbi_hblank_samples_50Hz; + } + break; + default: + s->bufs_per_mdl = 1; + s->mdl_size = s->buf_size * s->bufs_per_mdl; + break; + } + + cx18_load_queues(s); +} + +int cx18_start_v4l2_encode_stream(struct cx18_stream *s) +{ + u32 data[MAX_MB_ARGUMENTS]; + struct cx18 *cx = s->cx; + int captype = 0; + struct cx18_stream *s_idx; + + if (!cx18_stream_enabled(s)) + return -EINVAL; + + CX18_DEBUG_INFO("Start encoder stream %s\n", s->name); + + switch (s->type) { + case CX18_ENC_STREAM_TYPE_MPG: + captype = CAPTURE_CHANNEL_TYPE_MPEG; + cx->mpg_data_received = cx->vbi_data_inserted = 0; + cx->dualwatch_jiffies = jiffies; + cx->dualwatch_stereo_mode = v4l2_ctrl_g_ctrl(cx->cxhdl.audio_mode); + cx->search_pack_header = 0; + break; + + case CX18_ENC_STREAM_TYPE_IDX: + captype = CAPTURE_CHANNEL_TYPE_INDEX; + break; + case CX18_ENC_STREAM_TYPE_TS: + captype = CAPTURE_CHANNEL_TYPE_TS; + break; + case CX18_ENC_STREAM_TYPE_YUV: + captype = CAPTURE_CHANNEL_TYPE_YUV; + break; + case CX18_ENC_STREAM_TYPE_PCM: + captype = CAPTURE_CHANNEL_TYPE_PCM; + break; + case CX18_ENC_STREAM_TYPE_VBI: +#ifdef CX18_ENCODER_PARSES_SLICED + captype = cx18_raw_vbi(cx) ? + CAPTURE_CHANNEL_TYPE_VBI : CAPTURE_CHANNEL_TYPE_SLICED_VBI; +#else + /* + * Currently we set things up so that Sliced VBI from the + * digitizer is handled as Raw VBI by the encoder + */ + captype = CAPTURE_CHANNEL_TYPE_VBI; +#endif + cx->vbi.frame = 0; + cx->vbi.inserted_frame = 0; + memset(cx->vbi.sliced_mpeg_size, + 0, sizeof(cx->vbi.sliced_mpeg_size)); + break; + default: + return -EINVAL; + } + + /* Clear Streamoff flags in case left from last capture */ + clear_bit(CX18_F_S_STREAMOFF, &s->s_flags); + + cx18_vapi_result(cx, data, CX18_CREATE_TASK, 1, CPU_CMD_MASK_CAPTURE); + s->handle = data[0]; + cx18_vapi(cx, CX18_CPU_SET_CHANNEL_TYPE, 2, s->handle, captype); + + /* + * For everything but CAPTURE_CHANNEL_TYPE_TS, play it safe and + * set up all the parameters, as it is not obvious which parameters the + * firmware shares across capture channel types and which it does not. + * + * Some of the cx18_vapi() calls below apply to only certain capture + * channel types. We're hoping there's no harm in calling most of them + * anyway, as long as the values are all consistent. Setting some + * shared parameters will have no effect once an analog capture channel + * has started streaming. + */ + if (captype != CAPTURE_CHANNEL_TYPE_TS) { + cx18_vapi(cx, CX18_CPU_SET_VER_CROP_LINE, 2, s->handle, 0); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 3, s->handle, 3, 1); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 3, s->handle, 8, 0); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 3, s->handle, 4, 1); + + /* + * Audio related reset according to + * Documentation/video4linux/cx2341x/fw-encoder-api.txt + */ + if (atomic_read(&cx->ana_capturing) == 0) + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 2, + s->handle, 12); + + /* + * Number of lines for Field 1 & Field 2 according to + * Documentation/video4linux/cx2341x/fw-encoder-api.txt + * Field 1 is 312 for 625 line systems in BT.656 + * Field 2 is 313 for 625 line systems in BT.656 + */ + cx18_vapi(cx, CX18_CPU_SET_CAPTURE_LINE_NO, 3, + s->handle, 312, 313); + + if (cx->v4l2_cap & V4L2_CAP_VBI_CAPTURE) + cx18_vbi_setup(s); + + /* + * Select to receive I, P, and B frame index entries, if the + * index stream is enabled. Otherwise disable index entry + * generation. + */ + s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; + cx18_vapi_result(cx, data, CX18_CPU_SET_INDEXTABLE, 2, + s->handle, cx18_stream_enabled(s_idx) ? 7 : 0); + + /* Call out to the common CX2341x API setup for user controls */ + cx->cxhdl.priv = s; + cx2341x_handler_setup(&cx->cxhdl); + + /* + * When starting a capture and we're set for radio, + * ensure the video is muted, despite the user control. + */ + if (!cx->cxhdl.video_mute && + test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) + cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, s->handle, + (v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute_yuv) << 8) | 1); + + /* Enable the Video Format Converter for UYVY 4:2:2 support, + * rather than the default HM12 Macroblovk 4:2:0 support. + */ + if (captype == CAPTURE_CHANNEL_TYPE_YUV) { + if (s->pixelformat == V4L2_PIX_FMT_UYVY) + cx18_vapi(cx, CX18_CPU_SET_VFC_PARAM, 2, + s->handle, 1); + else + /* If in doubt, default to HM12 */ + cx18_vapi(cx, CX18_CPU_SET_VFC_PARAM, 2, + s->handle, 0); + } + } + + if (atomic_read(&cx->tot_capturing) == 0) { + cx2341x_handler_set_busy(&cx->cxhdl, 1); + clear_bit(CX18_F_I_EOS, &cx->i_flags); + cx18_write_reg(cx, 7, CX18_DSP0_INTERRUPT_MASK); + } + + cx18_vapi(cx, CX18_CPU_DE_SET_MDL_ACK, 3, s->handle, + (void __iomem *)&cx->scb->cpu_mdl_ack[s->type][0] - cx->enc_mem, + (void __iomem *)&cx->scb->cpu_mdl_ack[s->type][1] - cx->enc_mem); + + /* Init all the cpu_mdls for this stream */ + cx18_stream_configure_mdls(s); + _cx18_stream_load_fw_queue(s); + + /* begin_capture */ + if (cx18_vapi(cx, CX18_CPU_CAPTURE_START, 1, s->handle)) { + CX18_DEBUG_WARN("Error starting capture!\n"); + /* Ensure we're really not capturing before releasing MDLs */ + set_bit(CX18_F_S_STOPPING, &s->s_flags); + if (s->type == CX18_ENC_STREAM_TYPE_MPG) + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 2, s->handle, 1); + else + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 1, s->handle); + clear_bit(CX18_F_S_STREAMING, &s->s_flags); + /* FIXME - CX18_F_S_STREAMOFF as well? */ + cx18_vapi(cx, CX18_CPU_DE_RELEASE_MDL, 1, s->handle); + cx18_vapi(cx, CX18_DESTROY_TASK, 1, s->handle); + s->handle = CX18_INVALID_TASK_HANDLE; + clear_bit(CX18_F_S_STOPPING, &s->s_flags); + if (atomic_read(&cx->tot_capturing) == 0) { + set_bit(CX18_F_I_EOS, &cx->i_flags); + cx18_write_reg(cx, 5, CX18_DSP0_INTERRUPT_MASK); + } + return -EINVAL; + } + + /* you're live! sit back and await interrupts :) */ + if (captype != CAPTURE_CHANNEL_TYPE_TS) + atomic_inc(&cx->ana_capturing); + atomic_inc(&cx->tot_capturing); + return 0; +} +EXPORT_SYMBOL(cx18_start_v4l2_encode_stream); + +void cx18_stop_all_captures(struct cx18 *cx) +{ + int i; + + for (i = CX18_MAX_STREAMS - 1; i >= 0; i--) { + struct cx18_stream *s = &cx->streams[i]; + + if (!cx18_stream_enabled(s)) + continue; + if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) + cx18_stop_v4l2_encode_stream(s, 0); + } +} + +int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end) +{ + struct cx18 *cx = s->cx; + + if (!cx18_stream_enabled(s)) + return -EINVAL; + + /* This function assumes that you are allowed to stop the capture + and that we are actually capturing */ + + CX18_DEBUG_INFO("Stop Capture\n"); + + if (atomic_read(&cx->tot_capturing) == 0) + return 0; + + set_bit(CX18_F_S_STOPPING, &s->s_flags); + if (s->type == CX18_ENC_STREAM_TYPE_MPG) + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 2, s->handle, !gop_end); + else + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 1, s->handle); + + if (s->type == CX18_ENC_STREAM_TYPE_MPG && gop_end) { + CX18_INFO("ignoring gop_end: not (yet?) supported by the firmware\n"); + } + + if (s->type != CX18_ENC_STREAM_TYPE_TS) + atomic_dec(&cx->ana_capturing); + atomic_dec(&cx->tot_capturing); + + /* Clear capture and no-read bits */ + clear_bit(CX18_F_S_STREAMING, &s->s_flags); + + /* Tell the CX23418 it can't use our buffers anymore */ + cx18_vapi(cx, CX18_CPU_DE_RELEASE_MDL, 1, s->handle); + + cx18_vapi(cx, CX18_DESTROY_TASK, 1, s->handle); + s->handle = CX18_INVALID_TASK_HANDLE; + clear_bit(CX18_F_S_STOPPING, &s->s_flags); + + if (atomic_read(&cx->tot_capturing) > 0) + return 0; + + cx2341x_handler_set_busy(&cx->cxhdl, 0); + cx18_write_reg(cx, 5, CX18_DSP0_INTERRUPT_MASK); + wake_up(&s->waitq); + + return 0; +} +EXPORT_SYMBOL(cx18_stop_v4l2_encode_stream); + +u32 cx18_find_handle(struct cx18 *cx) +{ + int i; + + /* find first available handle to be used for global settings */ + for (i = 0; i < CX18_MAX_STREAMS; i++) { + struct cx18_stream *s = &cx->streams[i]; + + if (s->video_dev && (s->handle != CX18_INVALID_TASK_HANDLE)) + return s->handle; + } + return CX18_INVALID_TASK_HANDLE; +} + +struct cx18_stream *cx18_handle_to_stream(struct cx18 *cx, u32 handle) +{ + int i; + struct cx18_stream *s; + + if (handle == CX18_INVALID_TASK_HANDLE) + return NULL; + + for (i = 0; i < CX18_MAX_STREAMS; i++) { + s = &cx->streams[i]; + if (s->handle != handle) + continue; + if (cx18_stream_enabled(s)) + return s; + } + return NULL; +} diff --git a/drivers/media/video/cx18/cx18-streams.h b/drivers/media/pci/cx18/cx18-streams.h similarity index 100% rename from drivers/media/video/cx18/cx18-streams.h rename to drivers/media/pci/cx18/cx18-streams.h diff --git a/drivers/media/video/cx18/cx18-vbi.c b/drivers/media/pci/cx18/cx18-vbi.c similarity index 100% rename from drivers/media/video/cx18/cx18-vbi.c rename to drivers/media/pci/cx18/cx18-vbi.c diff --git a/drivers/media/video/cx18/cx18-vbi.h b/drivers/media/pci/cx18/cx18-vbi.h similarity index 100% rename from drivers/media/video/cx18/cx18-vbi.h rename to drivers/media/pci/cx18/cx18-vbi.h diff --git a/drivers/media/video/cx18/cx18-version.h b/drivers/media/pci/cx18/cx18-version.h similarity index 100% rename from drivers/media/video/cx18/cx18-version.h rename to drivers/media/pci/cx18/cx18-version.h diff --git a/drivers/media/video/cx18/cx18-video.c b/drivers/media/pci/cx18/cx18-video.c similarity index 100% rename from drivers/media/video/cx18/cx18-video.c rename to drivers/media/pci/cx18/cx18-video.c diff --git a/drivers/media/video/cx18/cx18-video.h b/drivers/media/pci/cx18/cx18-video.h similarity index 100% rename from drivers/media/video/cx18/cx18-video.h rename to drivers/media/pci/cx18/cx18-video.h diff --git a/drivers/media/video/cx18/cx23418.h b/drivers/media/pci/cx18/cx23418.h similarity index 100% rename from drivers/media/video/cx18/cx23418.h rename to drivers/media/pci/cx18/cx23418.h diff --git a/drivers/media/pci/cx23885/Kconfig b/drivers/media/pci/cx23885/Kconfig new file mode 100644 index 000000000000..eafa1144b17d --- /dev/null +++ b/drivers/media/pci/cx23885/Kconfig @@ -0,0 +1,50 @@ +config VIDEO_CX23885 + tristate "Conexant cx23885 (2388x successor) support" + depends on DVB_CORE && VIDEO_DEV && PCI && I2C && INPUT && SND + select SND_PCM + select I2C_ALGOBIT + select VIDEO_BTCX + select VIDEO_TUNER + select VIDEO_TVEEPROM + depends on RC_CORE + select VIDEOBUF_DVB + select VIDEOBUF_DMA_SG + select VIDEO_CX25840 + select VIDEO_CX2341X + select DVB_DIB7000P if MEDIA_SUBDRV_AUTOSELECT + select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT + select DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0367 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2063 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2131 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_XC2028 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_XC5000 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + This is a video4linux driver for Conexant 23885 based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called cx23885 + +config MEDIA_ALTERA_CI + tristate "Altera FPGA based CI module" + depends on VIDEO_CX23885 && DVB_CORE + select ALTERA_STAPL + ---help--- + An Altera FPGA CI module for NetUP Dual DVB-T/C RF CI card. + + To compile this driver as a module, choose M here: the + module will be called altera-ci diff --git a/drivers/media/pci/cx23885/Makefile b/drivers/media/pci/cx23885/Makefile new file mode 100644 index 000000000000..a2cbdcf15a8c --- /dev/null +++ b/drivers/media/pci/cx23885/Makefile @@ -0,0 +1,15 @@ +cx23885-objs := cx23885-cards.o cx23885-video.o cx23885-vbi.o \ + cx23885-core.o cx23885-i2c.o cx23885-dvb.o cx23885-417.o \ + cx23885-ioctl.o cx23885-ir.o cx23885-av.o cx23885-input.o \ + cx23888-ir.o netup-init.o cimax2.o netup-eeprom.o \ + cx23885-f300.o cx23885-alsa.o + +obj-$(CONFIG_VIDEO_CX23885) += cx23885.o +obj-$(CONFIG_MEDIA_ALTERA_CI) += altera-ci.o + +ccflags-y += -Idrivers/media/i2c +ccflags-y += -Idrivers/media/tuners +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += -Idrivers/media/dvb-frontends + +ccflags-y += $(extra-cflags-y) $(extra-cflags-m) diff --git a/drivers/media/pci/cx23885/altera-ci.c b/drivers/media/pci/cx23885/altera-ci.c new file mode 100644 index 000000000000..aee7f0dacff1 --- /dev/null +++ b/drivers/media/pci/cx23885/altera-ci.c @@ -0,0 +1,839 @@ +/* + * altera-ci.c + * + * CI driver in conjunction with NetUp Dual DVB-T/C RF CI card + * + * Copyright (C) 2010,2011 NetUP Inc. + * Copyright (C) 2010,2011 Igor M. Liplianin + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * currently cx23885 GPIO's used. + * GPIO-0 ~INT in + * GPIO-1 TMS out + * GPIO-2 ~reset chips out + * GPIO-3 to GPIO-10 data/addr for CA in/out + * GPIO-11 ~CS out + * GPIO-12 AD_RG out + * GPIO-13 ~WR out + * GPIO-14 ~RD out + * GPIO-15 ~RDY in + * GPIO-16 TCK out + * GPIO-17 TDO in + * GPIO-18 TDI out + */ +/* + * Bit definitions for MC417_RWD and MC417_OEN registers + * bits 31-16 + * +-----------+ + * | Reserved | + * +-----------+ + * bit 15 bit 14 bit 13 bit 12 bit 11 bit 10 bit 9 bit 8 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | TDI | TDO | TCK | RDY# | #RD | #WR | AD_RG | #CS | + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | DATA7| DATA6| DATA5| DATA4| DATA3| DATA2| DATA1| DATA0| + * +-------+-------+-------+-------+-------+-------+-------+-------+ + */ +#include +#include +#include "altera-ci.h" +#include "dvb_ca_en50221.h" + +/* FPGA regs */ +#define NETUP_CI_INT_CTRL 0x00 +#define NETUP_CI_BUSCTRL2 0x01 +#define NETUP_CI_ADDR0 0x04 +#define NETUP_CI_ADDR1 0x05 +#define NETUP_CI_DATA 0x06 +#define NETUP_CI_BUSCTRL 0x07 +#define NETUP_CI_PID_ADDR0 0x08 +#define NETUP_CI_PID_ADDR1 0x09 +#define NETUP_CI_PID_DATA 0x0a +#define NETUP_CI_TSA_DIV 0x0c +#define NETUP_CI_TSB_DIV 0x0d +#define NETUP_CI_REVISION 0x0f + +/* const for ci op */ +#define NETUP_CI_FLG_CTL 1 +#define NETUP_CI_FLG_RD 1 +#define NETUP_CI_FLG_AD 1 + +static unsigned int ci_dbg; +module_param(ci_dbg, int, 0644); +MODULE_PARM_DESC(ci_dbg, "Enable CI debugging"); + +static unsigned int pid_dbg; +module_param(pid_dbg, int, 0644); +MODULE_PARM_DESC(pid_dbg, "Enable PID filtering debugging"); + +MODULE_DESCRIPTION("altera FPGA CI module"); +MODULE_AUTHOR("Igor M. Liplianin "); +MODULE_LICENSE("GPL"); + +#define ci_dbg_print(args...) \ + do { \ + if (ci_dbg) \ + printk(KERN_DEBUG args); \ + } while (0) + +#define pid_dbg_print(args...) \ + do { \ + if (pid_dbg) \ + printk(KERN_DEBUG args); \ + } while (0) + +struct altera_ci_state; +struct netup_hw_pid_filter; + +struct fpga_internal { + void *dev; + struct mutex fpga_mutex;/* two CI's on the same fpga */ + struct netup_hw_pid_filter *pid_filt[2]; + struct altera_ci_state *state[2]; + struct work_struct work; + int (*fpga_rw) (void *dev, int flag, int data, int rw); + int cis_used; + int filts_used; + int strt_wrk; +}; + +/* stores all private variables for communication with CI */ +struct altera_ci_state { + struct fpga_internal *internal; + struct dvb_ca_en50221 ca; + int status; + int nr; +}; + +/* stores all private variables for hardware pid filtering */ +struct netup_hw_pid_filter { + struct fpga_internal *internal; + struct dvb_demux *demux; + /* save old functions */ + int (*start_feed)(struct dvb_demux_feed *feed); + int (*stop_feed)(struct dvb_demux_feed *feed); + + int status; + int nr; +}; + +/* internal params node */ +struct fpga_inode { + /* pointer for internal params, one for each pair of CI's */ + struct fpga_internal *internal; + struct fpga_inode *next_inode; +}; + +/* first internal params */ +static struct fpga_inode *fpga_first_inode; + +/* find chip by dev */ +static struct fpga_inode *find_inode(void *dev) +{ + struct fpga_inode *temp_chip = fpga_first_inode; + + if (temp_chip == NULL) + return temp_chip; + + /* + Search for the last fpga CI chip or + find it by dev */ + while ((temp_chip != NULL) && + (temp_chip->internal->dev != dev)) + temp_chip = temp_chip->next_inode; + + return temp_chip; +} +/* check demux */ +static struct fpga_internal *check_filter(struct fpga_internal *temp_int, + void *demux_dev, int filt_nr) +{ + if (temp_int == NULL) + return NULL; + + if ((temp_int->pid_filt[filt_nr]) == NULL) + return NULL; + + if (temp_int->pid_filt[filt_nr]->demux == demux_dev) + return temp_int; + + return NULL; +} + +/* find chip by demux */ +static struct fpga_inode *find_dinode(void *demux_dev) +{ + struct fpga_inode *temp_chip = fpga_first_inode; + struct fpga_internal *temp_int; + + /* + * Search of the last fpga CI chip or + * find it by demux + */ + while (temp_chip != NULL) { + if (temp_chip->internal != NULL) { + temp_int = temp_chip->internal; + if (check_filter(temp_int, demux_dev, 0)) + break; + if (check_filter(temp_int, demux_dev, 1)) + break; + } + + temp_chip = temp_chip->next_inode; + } + + return temp_chip; +} + +/* deallocating chip */ +static void remove_inode(struct fpga_internal *internal) +{ + struct fpga_inode *prev_node = fpga_first_inode; + struct fpga_inode *del_node = find_inode(internal->dev); + + if (del_node != NULL) { + if (del_node == fpga_first_inode) { + fpga_first_inode = del_node->next_inode; + } else { + while (prev_node->next_inode != del_node) + prev_node = prev_node->next_inode; + + if (del_node->next_inode == NULL) + prev_node->next_inode = NULL; + else + prev_node->next_inode = + prev_node->next_inode->next_inode; + } + + kfree(del_node); + } +} + +/* allocating new chip */ +static struct fpga_inode *append_internal(struct fpga_internal *internal) +{ + struct fpga_inode *new_node = fpga_first_inode; + + if (new_node == NULL) { + new_node = kmalloc(sizeof(struct fpga_inode), GFP_KERNEL); + fpga_first_inode = new_node; + } else { + while (new_node->next_inode != NULL) + new_node = new_node->next_inode; + + new_node->next_inode = + kmalloc(sizeof(struct fpga_inode), GFP_KERNEL); + if (new_node->next_inode != NULL) + new_node = new_node->next_inode; + else + new_node = NULL; + } + + if (new_node != NULL) { + new_node->internal = internal; + new_node->next_inode = NULL; + } + + return new_node; +} + +static int netup_fpga_op_rw(struct fpga_internal *inter, int addr, + u8 val, u8 read) +{ + inter->fpga_rw(inter->dev, NETUP_CI_FLG_AD, addr, 0); + return inter->fpga_rw(inter->dev, 0, val, read); +} + +/* flag - mem/io, read - read/write */ +int altera_ci_op_cam(struct dvb_ca_en50221 *en50221, int slot, + u8 flag, u8 read, int addr, u8 val) +{ + + struct altera_ci_state *state = en50221->data; + struct fpga_internal *inter = state->internal; + + u8 store; + int mem = 0; + + if (0 != slot) + return -EINVAL; + + mutex_lock(&inter->fpga_mutex); + + netup_fpga_op_rw(inter, NETUP_CI_ADDR0, ((addr << 1) & 0xfe), 0); + netup_fpga_op_rw(inter, NETUP_CI_ADDR1, ((addr >> 7) & 0x7f), 0); + store = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD); + + store &= 0x0f; + store |= ((state->nr << 7) | (flag << 6)); + + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, store, 0); + mem = netup_fpga_op_rw(inter, NETUP_CI_DATA, val, read); + + mutex_unlock(&inter->fpga_mutex); + + ci_dbg_print("%s: %s: addr=[0x%02x], %s=%x\n", __func__, + (read) ? "read" : "write", addr, + (flag == NETUP_CI_FLG_CTL) ? "ctl" : "mem", + (read) ? mem : val); + + return mem; +} + +int altera_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr) +{ + return altera_ci_op_cam(en50221, slot, 0, NETUP_CI_FLG_RD, addr, 0); +} + +int altera_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, + int slot, int addr, u8 data) +{ + return altera_ci_op_cam(en50221, slot, 0, 0, addr, data); +} + +int altera_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221, int slot, u8 addr) +{ + return altera_ci_op_cam(en50221, slot, NETUP_CI_FLG_CTL, + NETUP_CI_FLG_RD, addr, 0); +} + +int altera_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221, int slot, + u8 addr, u8 data) +{ + return altera_ci_op_cam(en50221, slot, NETUP_CI_FLG_CTL, 0, addr, data); +} + +int altera_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot) +{ + struct altera_ci_state *state = en50221->data; + struct fpga_internal *inter = state->internal; + /* reasonable timeout for CI reset is 10 seconds */ + unsigned long t_out = jiffies + msecs_to_jiffies(9999); + int ret; + + ci_dbg_print("%s\n", __func__); + + if (0 != slot) + return -EINVAL; + + mutex_lock(&inter->fpga_mutex); + + ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, + (ret & 0xcf) | (1 << (5 - state->nr)), 0); + + mutex_unlock(&inter->fpga_mutex); + + for (;;) { + mdelay(50); + + mutex_lock(&inter->fpga_mutex); + + ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, + 0, NETUP_CI_FLG_RD); + mutex_unlock(&inter->fpga_mutex); + + if ((ret & (1 << (5 - state->nr))) == 0) + break; + if (time_after(jiffies, t_out)) + break; + } + + + ci_dbg_print("%s: %d msecs\n", __func__, + jiffies_to_msecs(jiffies + msecs_to_jiffies(9999) - t_out)); + + return 0; +} + +int altera_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot) +{ + /* not implemented */ + return 0; +} + +int altera_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221, int slot) +{ + struct altera_ci_state *state = en50221->data; + struct fpga_internal *inter = state->internal; + int ret; + + ci_dbg_print("%s\n", __func__); + + if (0 != slot) + return -EINVAL; + + mutex_lock(&inter->fpga_mutex); + + ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, + (ret & 0x0f) | (1 << (3 - state->nr)), 0); + + mutex_unlock(&inter->fpga_mutex); + + return 0; +} + +/* work handler */ +static void netup_read_ci_status(struct work_struct *work) +{ + struct fpga_internal *inter = + container_of(work, struct fpga_internal, work); + int ret; + + ci_dbg_print("%s\n", __func__); + + mutex_lock(&inter->fpga_mutex); + /* ack' irq */ + ret = netup_fpga_op_rw(inter, NETUP_CI_INT_CTRL, 0, NETUP_CI_FLG_RD); + ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD); + + mutex_unlock(&inter->fpga_mutex); + + if (inter->state[1] != NULL) { + inter->state[1]->status = + ((ret & 1) == 0 ? + DVB_CA_EN50221_POLL_CAM_PRESENT | + DVB_CA_EN50221_POLL_CAM_READY : 0); + ci_dbg_print("%s: setting CI[1] status = 0x%x\n", + __func__, inter->state[1]->status); + }; + + if (inter->state[0] != NULL) { + inter->state[0]->status = + ((ret & 2) == 0 ? + DVB_CA_EN50221_POLL_CAM_PRESENT | + DVB_CA_EN50221_POLL_CAM_READY : 0); + ci_dbg_print("%s: setting CI[0] status = 0x%x\n", + __func__, inter->state[0]->status); + }; +} + +/* CI irq handler */ +int altera_ci_irq(void *dev) +{ + struct fpga_inode *temp_int = NULL; + struct fpga_internal *inter = NULL; + + ci_dbg_print("%s\n", __func__); + + if (dev != NULL) { + temp_int = find_inode(dev); + if (temp_int != NULL) { + inter = temp_int->internal; + schedule_work(&inter->work); + } + } + + return 1; +} +EXPORT_SYMBOL(altera_ci_irq); + +int altera_poll_ci_slot_status(struct dvb_ca_en50221 *en50221, int slot, + int open) +{ + struct altera_ci_state *state = en50221->data; + + if (0 != slot) + return -EINVAL; + + return state->status; +} + +void altera_hw_filt_release(void *main_dev, int filt_nr) +{ + struct fpga_inode *temp_int = find_inode(main_dev); + struct netup_hw_pid_filter *pid_filt = NULL; + + ci_dbg_print("%s\n", __func__); + + if (temp_int != NULL) { + pid_filt = temp_int->internal->pid_filt[filt_nr - 1]; + /* stored old feed controls */ + pid_filt->demux->start_feed = pid_filt->start_feed; + pid_filt->demux->stop_feed = pid_filt->stop_feed; + + if (((--(temp_int->internal->filts_used)) <= 0) && + ((temp_int->internal->cis_used) <= 0)) { + + ci_dbg_print("%s: Actually removing\n", __func__); + + remove_inode(temp_int->internal); + kfree(pid_filt->internal); + } + + kfree(pid_filt); + + } + +} +EXPORT_SYMBOL(altera_hw_filt_release); + +void altera_ci_release(void *dev, int ci_nr) +{ + struct fpga_inode *temp_int = find_inode(dev); + struct altera_ci_state *state = NULL; + + ci_dbg_print("%s\n", __func__); + + if (temp_int != NULL) { + state = temp_int->internal->state[ci_nr - 1]; + altera_hw_filt_release(dev, ci_nr); + + + if (((temp_int->internal->filts_used) <= 0) && + ((--(temp_int->internal->cis_used)) <= 0)) { + + ci_dbg_print("%s: Actually removing\n", __func__); + + remove_inode(temp_int->internal); + kfree(state->internal); + } + + if (state != NULL) { + if (state->ca.data != NULL) + dvb_ca_en50221_release(&state->ca); + + kfree(state); + } + } + +} +EXPORT_SYMBOL(altera_ci_release); + +static void altera_pid_control(struct netup_hw_pid_filter *pid_filt, + u16 pid, int onoff) +{ + struct fpga_internal *inter = pid_filt->internal; + u8 store = 0; + + /* pid 0-0x1f always enabled, don't touch them */ + if ((pid == 0x2000) || (pid < 0x20)) + return; + + mutex_lock(&inter->fpga_mutex); + + netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR0, (pid >> 3) & 0xff, 0); + netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR1, + ((pid >> 11) & 0x03) | (pid_filt->nr << 2), 0); + + store = netup_fpga_op_rw(inter, NETUP_CI_PID_DATA, 0, NETUP_CI_FLG_RD); + + if (onoff)/* 0 - on, 1 - off */ + store |= (1 << (pid & 7)); + else + store &= ~(1 << (pid & 7)); + + netup_fpga_op_rw(inter, NETUP_CI_PID_DATA, store, 0); + + mutex_unlock(&inter->fpga_mutex); + + pid_dbg_print("%s: (%d) set pid: %5d 0x%04x '%s'\n", __func__, + pid_filt->nr, pid, pid, onoff ? "off" : "on"); +} + +static void altera_toggle_fullts_streaming(struct netup_hw_pid_filter *pid_filt, + int filt_nr, int onoff) +{ + struct fpga_internal *inter = pid_filt->internal; + u8 store = 0; + int i; + + pid_dbg_print("%s: pid_filt->nr[%d] now %s\n", __func__, pid_filt->nr, + onoff ? "off" : "on"); + + if (onoff)/* 0 - on, 1 - off */ + store = 0xff;/* ignore pid */ + else + store = 0;/* enable pid */ + + mutex_lock(&inter->fpga_mutex); + + for (i = 0; i < 1024; i++) { + netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR0, i & 0xff, 0); + + netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR1, + ((i >> 8) & 0x03) | (pid_filt->nr << 2), 0); + /* pid 0-0x1f always enabled */ + netup_fpga_op_rw(inter, NETUP_CI_PID_DATA, + (i > 3 ? store : 0), 0); + } + + mutex_unlock(&inter->fpga_mutex); +} + +int altera_pid_feed_control(void *demux_dev, int filt_nr, + struct dvb_demux_feed *feed, int onoff) +{ + struct fpga_inode *temp_int = find_dinode(demux_dev); + struct fpga_internal *inter = temp_int->internal; + struct netup_hw_pid_filter *pid_filt = inter->pid_filt[filt_nr - 1]; + + altera_pid_control(pid_filt, feed->pid, onoff ? 0 : 1); + /* call old feed proc's */ + if (onoff) + pid_filt->start_feed(feed); + else + pid_filt->stop_feed(feed); + + if (feed->pid == 0x2000) + altera_toggle_fullts_streaming(pid_filt, filt_nr, + onoff ? 0 : 1); + + return 0; +} +EXPORT_SYMBOL(altera_pid_feed_control); + +int altera_ci_start_feed(struct dvb_demux_feed *feed, int num) +{ + altera_pid_feed_control(feed->demux, num, feed, 1); + + return 0; +} + +int altera_ci_stop_feed(struct dvb_demux_feed *feed, int num) +{ + altera_pid_feed_control(feed->demux, num, feed, 0); + + return 0; +} + +int altera_ci_start_feed_1(struct dvb_demux_feed *feed) +{ + return altera_ci_start_feed(feed, 1); +} + +int altera_ci_stop_feed_1(struct dvb_demux_feed *feed) +{ + return altera_ci_stop_feed(feed, 1); +} + +int altera_ci_start_feed_2(struct dvb_demux_feed *feed) +{ + return altera_ci_start_feed(feed, 2); +} + +int altera_ci_stop_feed_2(struct dvb_demux_feed *feed) +{ + return altera_ci_stop_feed(feed, 2); +} + +int altera_hw_filt_init(struct altera_ci_config *config, int hw_filt_nr) +{ + struct netup_hw_pid_filter *pid_filt = NULL; + struct fpga_inode *temp_int = find_inode(config->dev); + struct fpga_internal *inter = NULL; + int ret = 0; + + pid_filt = kzalloc(sizeof(struct netup_hw_pid_filter), GFP_KERNEL); + + ci_dbg_print("%s\n", __func__); + + if (!pid_filt) { + ret = -ENOMEM; + goto err; + } + + if (temp_int != NULL) { + inter = temp_int->internal; + (inter->filts_used)++; + ci_dbg_print("%s: Find Internal Structure!\n", __func__); + } else { + inter = kzalloc(sizeof(struct fpga_internal), GFP_KERNEL); + if (!inter) { + ret = -ENOMEM; + goto err; + } + + temp_int = append_internal(inter); + inter->filts_used = 1; + inter->dev = config->dev; + inter->fpga_rw = config->fpga_rw; + mutex_init(&inter->fpga_mutex); + inter->strt_wrk = 1; + ci_dbg_print("%s: Create New Internal Structure!\n", __func__); + } + + ci_dbg_print("%s: setting hw pid filter = %p for ci = %d\n", __func__, + pid_filt, hw_filt_nr - 1); + inter->pid_filt[hw_filt_nr - 1] = pid_filt; + pid_filt->demux = config->demux; + pid_filt->internal = inter; + pid_filt->nr = hw_filt_nr - 1; + /* store old feed controls */ + pid_filt->start_feed = config->demux->start_feed; + pid_filt->stop_feed = config->demux->stop_feed; + /* replace with new feed controls */ + if (hw_filt_nr == 1) { + pid_filt->demux->start_feed = altera_ci_start_feed_1; + pid_filt->demux->stop_feed = altera_ci_stop_feed_1; + } else if (hw_filt_nr == 2) { + pid_filt->demux->start_feed = altera_ci_start_feed_2; + pid_filt->demux->stop_feed = altera_ci_stop_feed_2; + } + + altera_toggle_fullts_streaming(pid_filt, 0, 1); + + return 0; +err: + ci_dbg_print("%s: Can't init hardware filter: Error %d\n", + __func__, ret); + + kfree(pid_filt); + + return ret; +} +EXPORT_SYMBOL(altera_hw_filt_init); + +int altera_ci_init(struct altera_ci_config *config, int ci_nr) +{ + struct altera_ci_state *state; + struct fpga_inode *temp_int = find_inode(config->dev); + struct fpga_internal *inter = NULL; + int ret = 0; + u8 store = 0; + + state = kzalloc(sizeof(struct altera_ci_state), GFP_KERNEL); + + ci_dbg_print("%s\n", __func__); + + if (!state) { + ret = -ENOMEM; + goto err; + } + + if (temp_int != NULL) { + inter = temp_int->internal; + (inter->cis_used)++; + inter->fpga_rw = config->fpga_rw; + ci_dbg_print("%s: Find Internal Structure!\n", __func__); + } else { + inter = kzalloc(sizeof(struct fpga_internal), GFP_KERNEL); + if (!inter) { + ret = -ENOMEM; + goto err; + } + + temp_int = append_internal(inter); + inter->cis_used = 1; + inter->dev = config->dev; + inter->fpga_rw = config->fpga_rw; + mutex_init(&inter->fpga_mutex); + inter->strt_wrk = 1; + ci_dbg_print("%s: Create New Internal Structure!\n", __func__); + } + + ci_dbg_print("%s: setting state = %p for ci = %d\n", __func__, + state, ci_nr - 1); + state->internal = inter; + state->nr = ci_nr - 1; + + state->ca.owner = THIS_MODULE; + state->ca.read_attribute_mem = altera_ci_read_attribute_mem; + state->ca.write_attribute_mem = altera_ci_write_attribute_mem; + state->ca.read_cam_control = altera_ci_read_cam_ctl; + state->ca.write_cam_control = altera_ci_write_cam_ctl; + state->ca.slot_reset = altera_ci_slot_reset; + state->ca.slot_shutdown = altera_ci_slot_shutdown; + state->ca.slot_ts_enable = altera_ci_slot_ts_ctl; + state->ca.poll_slot_status = altera_poll_ci_slot_status; + state->ca.data = state; + + ret = dvb_ca_en50221_init(config->adapter, + &state->ca, + /* flags */ 0, + /* n_slots */ 1); + if (0 != ret) + goto err; + + inter->state[ci_nr - 1] = state; + + altera_hw_filt_init(config, ci_nr); + + if (inter->strt_wrk) { + INIT_WORK(&inter->work, netup_read_ci_status); + inter->strt_wrk = 0; + } + + ci_dbg_print("%s: CI initialized!\n", __func__); + + mutex_lock(&inter->fpga_mutex); + + /* Enable div */ + netup_fpga_op_rw(inter, NETUP_CI_TSA_DIV, 0x0, 0); + netup_fpga_op_rw(inter, NETUP_CI_TSB_DIV, 0x0, 0); + + /* enable TS out */ + store = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, 0, NETUP_CI_FLG_RD); + store |= (3 << 4); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, store, 0); + + ret = netup_fpga_op_rw(inter, NETUP_CI_REVISION, 0, NETUP_CI_FLG_RD); + /* enable irq */ + netup_fpga_op_rw(inter, NETUP_CI_INT_CTRL, 0x44, 0); + + mutex_unlock(&inter->fpga_mutex); + + ci_dbg_print("%s: NetUP CI Revision = 0x%x\n", __func__, ret); + + schedule_work(&inter->work); + + return 0; +err: + ci_dbg_print("%s: Cannot initialize CI: Error %d.\n", __func__, ret); + + kfree(state); + + return ret; +} +EXPORT_SYMBOL(altera_ci_init); + +int altera_ci_tuner_reset(void *dev, int ci_nr) +{ + struct fpga_inode *temp_int = find_inode(dev); + struct fpga_internal *inter = NULL; + u8 store; + + ci_dbg_print("%s\n", __func__); + + if (temp_int == NULL) + return -1; + + if (temp_int->internal == NULL) + return -1; + + inter = temp_int->internal; + + mutex_lock(&inter->fpga_mutex); + + store = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, 0, NETUP_CI_FLG_RD); + store &= ~(4 << (2 - ci_nr)); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, store, 0); + msleep(100); + store |= (4 << (2 - ci_nr)); + netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, store, 0); + + mutex_unlock(&inter->fpga_mutex); + + return 0; +} +EXPORT_SYMBOL(altera_ci_tuner_reset); diff --git a/drivers/media/video/cx23885/altera-ci.h b/drivers/media/pci/cx23885/altera-ci.h similarity index 100% rename from drivers/media/video/cx23885/altera-ci.h rename to drivers/media/pci/cx23885/altera-ci.h diff --git a/drivers/media/video/cx23885/cimax2.c b/drivers/media/pci/cx23885/cimax2.c similarity index 100% rename from drivers/media/video/cx23885/cimax2.c rename to drivers/media/pci/cx23885/cimax2.c diff --git a/drivers/media/video/cx23885/cimax2.h b/drivers/media/pci/cx23885/cimax2.h similarity index 100% rename from drivers/media/video/cx23885/cimax2.h rename to drivers/media/pci/cx23885/cimax2.h diff --git a/drivers/media/pci/cx23885/cx23885-417.c b/drivers/media/pci/cx23885/cx23885-417.c new file mode 100644 index 000000000000..5d5052d0253f --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-417.c @@ -0,0 +1,1790 @@ +/* + * + * Support for a cx23417 mpeg encoder via cx23885 host port. + * + * (c) 2004 Jelle Foks + * (c) 2004 Gerd Knorr + * (c) 2008 Steven Toth + * - CX23885/7/8 support + * + * Includes parts from the ivtv driver + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx23885.h" +#include "cx23885-ioctl.h" + +#define CX23885_FIRM_IMAGE_SIZE 376836 +#define CX23885_FIRM_IMAGE_NAME "v4l-cx23885-enc.fw" + +static unsigned int mpegbufs = 32; +module_param(mpegbufs, int, 0644); +MODULE_PARM_DESC(mpegbufs, "number of mpeg buffers, range 2-32"); +static unsigned int mpeglines = 32; +module_param(mpeglines, int, 0644); +MODULE_PARM_DESC(mpeglines, "number of lines in an MPEG buffer, range 2-32"); +static unsigned int mpeglinesize = 512; +module_param(mpeglinesize, int, 0644); +MODULE_PARM_DESC(mpeglinesize, + "number of bytes in each line of an MPEG buffer, range 512-1024"); + +static unsigned int v4l_debug; +module_param(v4l_debug, int, 0644); +MODULE_PARM_DESC(v4l_debug, "enable V4L debug messages"); + +#define dprintk(level, fmt, arg...)\ + do { if (v4l_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, \ + (dev) ? dev->name : "cx23885[?]", ## arg); \ + } while (0) + +static struct cx23885_tvnorm cx23885_tvnorms[] = { + { + .name = "NTSC-M", + .id = V4L2_STD_NTSC_M, + }, { + .name = "NTSC-JP", + .id = V4L2_STD_NTSC_M_JP, + }, { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + }, { + .name = "PAL-N", + .id = V4L2_STD_PAL_N, + }, { + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + }, { + .name = "PAL-60", + .id = V4L2_STD_PAL_60, + }, { + .name = "SECAM-L", + .id = V4L2_STD_SECAM_L, + }, { + .name = "SECAM-DK", + .id = V4L2_STD_SECAM_DK, + } +}; + +/* ------------------------------------------------------------------ */ +enum cx23885_capture_type { + CX23885_MPEG_CAPTURE, + CX23885_RAW_CAPTURE, + CX23885_RAW_PASSTHRU_CAPTURE +}; +enum cx23885_capture_bits { + CX23885_RAW_BITS_NONE = 0x00, + CX23885_RAW_BITS_YUV_CAPTURE = 0x01, + CX23885_RAW_BITS_PCM_CAPTURE = 0x02, + CX23885_RAW_BITS_VBI_CAPTURE = 0x04, + CX23885_RAW_BITS_PASSTHRU_CAPTURE = 0x08, + CX23885_RAW_BITS_TO_HOST_CAPTURE = 0x10 +}; +enum cx23885_capture_end { + CX23885_END_AT_GOP, /* stop at the end of gop, generate irq */ + CX23885_END_NOW, /* stop immediately, no irq */ +}; +enum cx23885_framerate { + CX23885_FRAMERATE_NTSC_30, /* NTSC: 30fps */ + CX23885_FRAMERATE_PAL_25 /* PAL: 25fps */ +}; +enum cx23885_stream_port { + CX23885_OUTPUT_PORT_MEMORY, + CX23885_OUTPUT_PORT_STREAMING, + CX23885_OUTPUT_PORT_SERIAL +}; +enum cx23885_data_xfer_status { + CX23885_MORE_BUFFERS_FOLLOW, + CX23885_LAST_BUFFER, +}; +enum cx23885_picture_mask { + CX23885_PICTURE_MASK_NONE, + CX23885_PICTURE_MASK_I_FRAMES, + CX23885_PICTURE_MASK_I_P_FRAMES = 0x3, + CX23885_PICTURE_MASK_ALL_FRAMES = 0x7, +}; +enum cx23885_vbi_mode_bits { + CX23885_VBI_BITS_SLICED, + CX23885_VBI_BITS_RAW, +}; +enum cx23885_vbi_insertion_bits { + CX23885_VBI_BITS_INSERT_IN_XTENSION_USR_DATA, + CX23885_VBI_BITS_INSERT_IN_PRIVATE_PACKETS = 0x1 << 1, + CX23885_VBI_BITS_SEPARATE_STREAM = 0x2 << 1, + CX23885_VBI_BITS_SEPARATE_STREAM_USR_DATA = 0x4 << 1, + CX23885_VBI_BITS_SEPARATE_STREAM_PRV_DATA = 0x5 << 1, +}; +enum cx23885_dma_unit { + CX23885_DMA_BYTES, + CX23885_DMA_FRAMES, +}; +enum cx23885_dma_transfer_status_bits { + CX23885_DMA_TRANSFER_BITS_DONE = 0x01, + CX23885_DMA_TRANSFER_BITS_ERROR = 0x04, + CX23885_DMA_TRANSFER_BITS_LL_ERROR = 0x10, +}; +enum cx23885_pause { + CX23885_PAUSE_ENCODING, + CX23885_RESUME_ENCODING, +}; +enum cx23885_copyright { + CX23885_COPYRIGHT_OFF, + CX23885_COPYRIGHT_ON, +}; +enum cx23885_notification_type { + CX23885_NOTIFICATION_REFRESH, +}; +enum cx23885_notification_status { + CX23885_NOTIFICATION_OFF, + CX23885_NOTIFICATION_ON, +}; +enum cx23885_notification_mailbox { + CX23885_NOTIFICATION_NO_MAILBOX = -1, +}; +enum cx23885_field1_lines { + CX23885_FIELD1_SAA7114 = 0x00EF, /* 239 */ + CX23885_FIELD1_SAA7115 = 0x00F0, /* 240 */ + CX23885_FIELD1_MICRONAS = 0x0105, /* 261 */ +}; +enum cx23885_field2_lines { + CX23885_FIELD2_SAA7114 = 0x00EF, /* 239 */ + CX23885_FIELD2_SAA7115 = 0x00F0, /* 240 */ + CX23885_FIELD2_MICRONAS = 0x0106, /* 262 */ +}; +enum cx23885_custom_data_type { + CX23885_CUSTOM_EXTENSION_USR_DATA, + CX23885_CUSTOM_PRIVATE_PACKET, +}; +enum cx23885_mute { + CX23885_UNMUTE, + CX23885_MUTE, +}; +enum cx23885_mute_video_mask { + CX23885_MUTE_VIDEO_V_MASK = 0x0000FF00, + CX23885_MUTE_VIDEO_U_MASK = 0x00FF0000, + CX23885_MUTE_VIDEO_Y_MASK = 0xFF000000, +}; +enum cx23885_mute_video_shift { + CX23885_MUTE_VIDEO_V_SHIFT = 8, + CX23885_MUTE_VIDEO_U_SHIFT = 16, + CX23885_MUTE_VIDEO_Y_SHIFT = 24, +}; + +/* defines below are from ivtv-driver.h */ +#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF + +/* Firmware API commands */ +#define IVTV_API_STD_TIMEOUT 500 + +/* Registers */ +/* IVTV_REG_OFFSET */ +#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8) +#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC) +#define IVTV_REG_SPU (0x9050) +#define IVTV_REG_HW_BLOCKS (0x9054) +#define IVTV_REG_VPU (0x9058) +#define IVTV_REG_APU (0xA064) + +/**** Bit definitions for MC417_RWD and MC417_OEN registers *** + bits 31-16 ++-----------+ +| Reserved | ++-----------+ + bit 15 bit 14 bit 13 bit 12 bit 11 bit 10 bit 9 bit 8 ++-------+-------+-------+-------+-------+-------+-------+-------+ +| MIWR# | MIRD# | MICS# |MIRDY# |MIADDR3|MIADDR2|MIADDR1|MIADDR0| ++-------+-------+-------+-------+-------+-------+-------+-------+ + bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0 ++-------+-------+-------+-------+-------+-------+-------+-------+ +|MIDATA7|MIDATA6|MIDATA5|MIDATA4|MIDATA3|MIDATA2|MIDATA1|MIDATA0| ++-------+-------+-------+-------+-------+-------+-------+-------+ +***/ +#define MC417_MIWR 0x8000 +#define MC417_MIRD 0x4000 +#define MC417_MICS 0x2000 +#define MC417_MIRDY 0x1000 +#define MC417_MIADDR 0x0F00 +#define MC417_MIDATA 0x00FF + +/* MIADDR* nibble definitions */ +#define MCI_MEMORY_DATA_BYTE0 0x000 +#define MCI_MEMORY_DATA_BYTE1 0x100 +#define MCI_MEMORY_DATA_BYTE2 0x200 +#define MCI_MEMORY_DATA_BYTE3 0x300 +#define MCI_MEMORY_ADDRESS_BYTE2 0x400 +#define MCI_MEMORY_ADDRESS_BYTE1 0x500 +#define MCI_MEMORY_ADDRESS_BYTE0 0x600 +#define MCI_REGISTER_DATA_BYTE0 0x800 +#define MCI_REGISTER_DATA_BYTE1 0x900 +#define MCI_REGISTER_DATA_BYTE2 0xA00 +#define MCI_REGISTER_DATA_BYTE3 0xB00 +#define MCI_REGISTER_ADDRESS_BYTE0 0xC00 +#define MCI_REGISTER_ADDRESS_BYTE1 0xD00 +#define MCI_REGISTER_MODE 0xE00 + +/* Read and write modes */ +#define MCI_MODE_REGISTER_READ 0 +#define MCI_MODE_REGISTER_WRITE 1 +#define MCI_MODE_MEMORY_READ 0 +#define MCI_MODE_MEMORY_WRITE 0x40 + +/*** Bit definitions for MC417_CTL register **** + bits 31-6 bits 5-4 bit 3 bits 2-1 Bit 0 ++--------+-------------+--------+--------------+------------+ +|Reserved|MC417_SPD_CTL|Reserved|MC417_GPIO_SEL|UART_GPIO_EN| ++--------+-------------+--------+--------------+------------+ +***/ +#define MC417_SPD_CTL(x) (((x) << 4) & 0x00000030) +#define MC417_GPIO_SEL(x) (((x) << 1) & 0x00000006) +#define MC417_UART_GPIO_EN 0x00000001 + +/* Values for speed control */ +#define MC417_SPD_CTL_SLOW 0x1 +#define MC417_SPD_CTL_MEDIUM 0x0 +#define MC417_SPD_CTL_FAST 0x3 /* b'1x, but we use b'11 */ + +/* Values for GPIO select */ +#define MC417_GPIO_SEL_GPIO3 0x3 +#define MC417_GPIO_SEL_GPIO2 0x2 +#define MC417_GPIO_SEL_GPIO1 0x1 +#define MC417_GPIO_SEL_GPIO0 0x0 + +void cx23885_mc417_init(struct cx23885_dev *dev) +{ + u32 regval; + + dprintk(2, "%s()\n", __func__); + + /* Configure MC417_CTL register to defaults. */ + regval = MC417_SPD_CTL(MC417_SPD_CTL_FAST) | + MC417_GPIO_SEL(MC417_GPIO_SEL_GPIO3) | + MC417_UART_GPIO_EN; + cx_write(MC417_CTL, regval); + + /* Configure MC417_OEN to defaults. */ + regval = MC417_MIRDY; + cx_write(MC417_OEN, regval); + + /* Configure MC417_RWD to defaults. */ + regval = MC417_MIWR | MC417_MIRD | MC417_MICS; + cx_write(MC417_RWD, regval); +} + +static int mc417_wait_ready(struct cx23885_dev *dev) +{ + u32 mi_ready; + unsigned long timeout = jiffies + msecs_to_jiffies(1); + + for (;;) { + mi_ready = cx_read(MC417_RWD) & MC417_MIRDY; + if (mi_ready != 0) + return 0; + if (time_after(jiffies, timeout)) + return -1; + udelay(1); + } +} + +int mc417_register_write(struct cx23885_dev *dev, u16 address, u32 value) +{ + u32 regval; + + /* Enable MC417 GPIO outputs except for MC417_MIRDY, + * which is an input. + */ + cx_write(MC417_OEN, MC417_MIRDY); + + /* Write data byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE0 | + (value & 0x000000FF); + cx_write(MC417_RWD, regval); + + /* Transition CS/WR to effect write transaction across bus. */ + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE1 | + ((value >> 8) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE2 | + ((value >> 16) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 3 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE3 | + ((value >> 24) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_ADDRESS_BYTE0 | + (address & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_ADDRESS_BYTE1 | + ((address >> 8) & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Indicate that this is a write. */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_MODE | + MCI_MODE_REGISTER_WRITE; + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Wait for the trans to complete (MC417_MIRDY asserted). */ + return mc417_wait_ready(dev); +} + +int mc417_register_read(struct cx23885_dev *dev, u16 address, u32 *value) +{ + int retval; + u32 regval; + u32 tempval; + u32 dataval; + + /* Enable MC417 GPIO outputs except for MC417_MIRDY, + * which is an input. + */ + cx_write(MC417_OEN, MC417_MIRDY); + + /* Write address byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_ADDRESS_BYTE0 | + ((address & 0x00FF)); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_ADDRESS_BYTE1 | + ((address >> 8) & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Indicate that this is a register read. */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_MODE | + MCI_MODE_REGISTER_READ; + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Wait for the trans to complete (MC417_MIRDY asserted). */ + retval = mc417_wait_ready(dev); + + /* switch the DAT0-7 GPIO[10:3] to input mode */ + cx_write(MC417_OEN, MC417_MIRDY | MC417_MIDATA); + + /* Read data byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE0; + cx_write(MC417_RWD, regval); + + /* Transition RD to effect read transaction across bus. + * Transtion 0x5000 -> 0x9000 correct (RD/RDY -> WR/RDY)? + * Should it be 0x9000 -> 0xF000 (also why is RDY being set, its + * input only...) + */ + regval = MC417_MIWR | MC417_MIRDY | MCI_REGISTER_DATA_BYTE0; + cx_write(MC417_RWD, regval); + + /* Collect byte */ + tempval = cx_read(MC417_RWD); + dataval = tempval & 0x000000FF; + + /* Bring CS and RD high. */ + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE1; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_REGISTER_DATA_BYTE1; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 8); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE2; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_REGISTER_DATA_BYTE2; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 16); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 3 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_REGISTER_DATA_BYTE3; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_REGISTER_DATA_BYTE3; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 24); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + *value = dataval; + + return retval; +} + +int mc417_memory_write(struct cx23885_dev *dev, u32 address, u32 value) +{ + u32 regval; + + /* Enable MC417 GPIO outputs except for MC417_MIRDY, + * which is an input. + */ + cx_write(MC417_OEN, MC417_MIRDY); + + /* Write data byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE0 | + (value & 0x000000FF); + cx_write(MC417_RWD, regval); + + /* Transition CS/WR to effect write transaction across bus. */ + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE1 | + ((value >> 8) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE2 | + ((value >> 16) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write data byte 3 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE3 | + ((value >> 24) & 0x000000FF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE2 | + MCI_MODE_MEMORY_WRITE | ((address >> 16) & 0x3F); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE1 | + ((address >> 8) & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE0 | + (address & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Wait for the trans to complete (MC417_MIRDY asserted). */ + return mc417_wait_ready(dev); +} + +int mc417_memory_read(struct cx23885_dev *dev, u32 address, u32 *value) +{ + int retval; + u32 regval; + u32 tempval; + u32 dataval; + + /* Enable MC417 GPIO outputs except for MC417_MIRDY, + * which is an input. + */ + cx_write(MC417_OEN, MC417_MIRDY); + + /* Write address byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE2 | + MCI_MODE_MEMORY_READ | ((address >> 16) & 0x3F); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE1 | + ((address >> 8) & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Write address byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_ADDRESS_BYTE0 | + (address & 0xFF); + cx_write(MC417_RWD, regval); + regval |= MC417_MICS | MC417_MIWR; + cx_write(MC417_RWD, regval); + + /* Wait for the trans to complete (MC417_MIRDY asserted). */ + retval = mc417_wait_ready(dev); + + /* switch the DAT0-7 GPIO[10:3] to input mode */ + cx_write(MC417_OEN, MC417_MIRDY | MC417_MIDATA); + + /* Read data byte 3 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE3; + cx_write(MC417_RWD, regval); + + /* Transition RD to effect read transaction across bus. */ + regval = MC417_MIWR | MC417_MIRDY | MCI_MEMORY_DATA_BYTE3; + cx_write(MC417_RWD, regval); + + /* Collect byte */ + tempval = cx_read(MC417_RWD); + dataval = ((tempval & 0x000000FF) << 24); + + /* Bring CS and RD high. */ + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 2 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE2; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_MEMORY_DATA_BYTE2; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 16); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 1 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE1; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_MEMORY_DATA_BYTE1; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= ((tempval & 0x000000FF) << 8); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + /* Read data byte 0 */ + regval = MC417_MIRD | MC417_MIRDY | MCI_MEMORY_DATA_BYTE0; + cx_write(MC417_RWD, regval); + regval = MC417_MIWR | MC417_MIRDY | MCI_MEMORY_DATA_BYTE0; + cx_write(MC417_RWD, regval); + tempval = cx_read(MC417_RWD); + dataval |= (tempval & 0x000000FF); + regval = MC417_MIWR | MC417_MIRD | MC417_MICS | MC417_MIRDY; + cx_write(MC417_RWD, regval); + + *value = dataval; + + return retval; +} + +void mc417_gpio_set(struct cx23885_dev *dev, u32 mask) +{ + u32 val; + + /* Set the gpio value */ + mc417_register_read(dev, 0x900C, &val); + val |= (mask & 0x000ffff); + mc417_register_write(dev, 0x900C, val); +} + +void mc417_gpio_clear(struct cx23885_dev *dev, u32 mask) +{ + u32 val; + + /* Clear the gpio value */ + mc417_register_read(dev, 0x900C, &val); + val &= ~(mask & 0x0000ffff); + mc417_register_write(dev, 0x900C, val); +} + +void mc417_gpio_enable(struct cx23885_dev *dev, u32 mask, int asoutput) +{ + u32 val; + + /* Enable GPIO direction bits */ + mc417_register_read(dev, 0x9020, &val); + if (asoutput) + val |= (mask & 0x0000ffff); + else + val &= ~(mask & 0x0000ffff); + + mc417_register_write(dev, 0x9020, val); +} +/* ------------------------------------------------------------------ */ + +/* MPEG encoder API */ +static char *cmd_to_str(int cmd) +{ + switch (cmd) { + case CX2341X_ENC_PING_FW: + return "PING_FW"; + case CX2341X_ENC_START_CAPTURE: + return "START_CAPTURE"; + case CX2341X_ENC_STOP_CAPTURE: + return "STOP_CAPTURE"; + case CX2341X_ENC_SET_AUDIO_ID: + return "SET_AUDIO_ID"; + case CX2341X_ENC_SET_VIDEO_ID: + return "SET_VIDEO_ID"; + case CX2341X_ENC_SET_PCR_ID: + return "SET_PCR_ID"; + case CX2341X_ENC_SET_FRAME_RATE: + return "SET_FRAME_RATE"; + case CX2341X_ENC_SET_FRAME_SIZE: + return "SET_FRAME_SIZE"; + case CX2341X_ENC_SET_BIT_RATE: + return "SET_BIT_RATE"; + case CX2341X_ENC_SET_GOP_PROPERTIES: + return "SET_GOP_PROPERTIES"; + case CX2341X_ENC_SET_ASPECT_RATIO: + return "SET_ASPECT_RATIO"; + case CX2341X_ENC_SET_DNR_FILTER_MODE: + return "SET_DNR_FILTER_MODE"; + case CX2341X_ENC_SET_DNR_FILTER_PROPS: + return "SET_DNR_FILTER_PROPS"; + case CX2341X_ENC_SET_CORING_LEVELS: + return "SET_CORING_LEVELS"; + case CX2341X_ENC_SET_SPATIAL_FILTER_TYPE: + return "SET_SPATIAL_FILTER_TYPE"; + case CX2341X_ENC_SET_VBI_LINE: + return "SET_VBI_LINE"; + case CX2341X_ENC_SET_STREAM_TYPE: + return "SET_STREAM_TYPE"; + case CX2341X_ENC_SET_OUTPUT_PORT: + return "SET_OUTPUT_PORT"; + case CX2341X_ENC_SET_AUDIO_PROPERTIES: + return "SET_AUDIO_PROPERTIES"; + case CX2341X_ENC_HALT_FW: + return "HALT_FW"; + case CX2341X_ENC_GET_VERSION: + return "GET_VERSION"; + case CX2341X_ENC_SET_GOP_CLOSURE: + return "SET_GOP_CLOSURE"; + case CX2341X_ENC_GET_SEQ_END: + return "GET_SEQ_END"; + case CX2341X_ENC_SET_PGM_INDEX_INFO: + return "SET_PGM_INDEX_INFO"; + case CX2341X_ENC_SET_VBI_CONFIG: + return "SET_VBI_CONFIG"; + case CX2341X_ENC_SET_DMA_BLOCK_SIZE: + return "SET_DMA_BLOCK_SIZE"; + case CX2341X_ENC_GET_PREV_DMA_INFO_MB_10: + return "GET_PREV_DMA_INFO_MB_10"; + case CX2341X_ENC_GET_PREV_DMA_INFO_MB_9: + return "GET_PREV_DMA_INFO_MB_9"; + case CX2341X_ENC_SCHED_DMA_TO_HOST: + return "SCHED_DMA_TO_HOST"; + case CX2341X_ENC_INITIALIZE_INPUT: + return "INITIALIZE_INPUT"; + case CX2341X_ENC_SET_FRAME_DROP_RATE: + return "SET_FRAME_DROP_RATE"; + case CX2341X_ENC_PAUSE_ENCODER: + return "PAUSE_ENCODER"; + case CX2341X_ENC_REFRESH_INPUT: + return "REFRESH_INPUT"; + case CX2341X_ENC_SET_COPYRIGHT: + return "SET_COPYRIGHT"; + case CX2341X_ENC_SET_EVENT_NOTIFICATION: + return "SET_EVENT_NOTIFICATION"; + case CX2341X_ENC_SET_NUM_VSYNC_LINES: + return "SET_NUM_VSYNC_LINES"; + case CX2341X_ENC_SET_PLACEHOLDER: + return "SET_PLACEHOLDER"; + case CX2341X_ENC_MUTE_VIDEO: + return "MUTE_VIDEO"; + case CX2341X_ENC_MUTE_AUDIO: + return "MUTE_AUDIO"; + case CX2341X_ENC_MISC: + return "MISC"; + default: + return "UNKNOWN"; + } +} + +static int cx23885_mbox_func(void *priv, + u32 command, + int in, + int out, + u32 data[CX2341X_MBOX_MAX_DATA]) +{ + struct cx23885_dev *dev = priv; + unsigned long timeout; + u32 value, flag, retval = 0; + int i; + + dprintk(3, "%s: command(0x%X) = %s\n", __func__, command, + cmd_to_str(command)); + + /* this may not be 100% safe if we can't read any memory location + without side effects */ + mc417_memory_read(dev, dev->cx23417_mailbox - 4, &value); + if (value != 0x12345678) { + printk(KERN_ERR + "Firmware and/or mailbox pointer not initialized " + "or corrupted, signature = 0x%x, cmd = %s\n", value, + cmd_to_str(command)); + return -1; + } + + /* This read looks at 32 bits, but flag is only 8 bits. + * Seems we also bail if CMD or TIMEOUT bytes are set??? + */ + mc417_memory_read(dev, dev->cx23417_mailbox, &flag); + if (flag) { + printk(KERN_ERR "ERROR: Mailbox appears to be in use " + "(%x), cmd = %s\n", flag, cmd_to_str(command)); + return -1; + } + + flag |= 1; /* tell 'em we're working on it */ + mc417_memory_write(dev, dev->cx23417_mailbox, flag); + + /* write command + args + fill remaining with zeros */ + /* command code */ + mc417_memory_write(dev, dev->cx23417_mailbox + 1, command); + mc417_memory_write(dev, dev->cx23417_mailbox + 3, + IVTV_API_STD_TIMEOUT); /* timeout */ + for (i = 0; i < in; i++) { + mc417_memory_write(dev, dev->cx23417_mailbox + 4 + i, data[i]); + dprintk(3, "API Input %d = %d\n", i, data[i]); + } + for (; i < CX2341X_MBOX_MAX_DATA; i++) + mc417_memory_write(dev, dev->cx23417_mailbox + 4 + i, 0); + + flag |= 3; /* tell 'em we're done writing */ + mc417_memory_write(dev, dev->cx23417_mailbox, flag); + + /* wait for firmware to handle the API command */ + timeout = jiffies + msecs_to_jiffies(10); + for (;;) { + mc417_memory_read(dev, dev->cx23417_mailbox, &flag); + if (0 != (flag & 4)) + break; + if (time_after(jiffies, timeout)) { + printk(KERN_ERR "ERROR: API Mailbox timeout\n"); + return -1; + } + udelay(10); + } + + /* read output values */ + for (i = 0; i < out; i++) { + mc417_memory_read(dev, dev->cx23417_mailbox + 4 + i, data + i); + dprintk(3, "API Output %d = %d\n", i, data[i]); + } + + mc417_memory_read(dev, dev->cx23417_mailbox + 2, &retval); + dprintk(3, "API result = %d\n", retval); + + flag = 0; + mc417_memory_write(dev, dev->cx23417_mailbox, flag); + + return retval; +} + +/* We don't need to call the API often, so using just one + * mailbox will probably suffice + */ +static int cx23885_api_cmd(struct cx23885_dev *dev, + u32 command, + u32 inputcnt, + u32 outputcnt, + ...) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + va_list vargs; + int i, err; + + dprintk(3, "%s() cmds = 0x%08x\n", __func__, command); + + va_start(vargs, outputcnt); + for (i = 0; i < inputcnt; i++) + data[i] = va_arg(vargs, int); + + err = cx23885_mbox_func(dev, command, inputcnt, outputcnt, data); + for (i = 0; i < outputcnt; i++) { + int *vptr = va_arg(vargs, int *); + *vptr = data[i]; + } + va_end(vargs); + + return err; +} + +static int cx23885_find_mailbox(struct cx23885_dev *dev) +{ + u32 signature[4] = { + 0x12345678, 0x34567812, 0x56781234, 0x78123456 + }; + int signaturecnt = 0; + u32 value; + int i; + + dprintk(2, "%s()\n", __func__); + + for (i = 0; i < CX23885_FIRM_IMAGE_SIZE; i++) { + mc417_memory_read(dev, i, &value); + if (value == signature[signaturecnt]) + signaturecnt++; + else + signaturecnt = 0; + if (4 == signaturecnt) { + dprintk(1, "Mailbox signature found at 0x%x\n", i+1); + return i+1; + } + } + printk(KERN_ERR "Mailbox signature values not found!\n"); + return -1; +} + +static int cx23885_load_firmware(struct cx23885_dev *dev) +{ + static const unsigned char magic[8] = { + 0xa7, 0x0d, 0x00, 0x00, 0x66, 0xbb, 0x55, 0xaa + }; + const struct firmware *firmware; + int i, retval = 0; + u32 value = 0; + u32 gpio_output = 0; + u32 gpio_value; + u32 checksum = 0; + u32 *dataptr; + + dprintk(2, "%s()\n", __func__); + + /* Save GPIO settings before reset of APU */ + retval |= mc417_memory_read(dev, 0x9020, &gpio_output); + retval |= mc417_memory_read(dev, 0x900C, &gpio_value); + + retval = mc417_register_write(dev, + IVTV_REG_VPU, 0xFFFFFFED); + retval |= mc417_register_write(dev, + IVTV_REG_HW_BLOCKS, IVTV_CMD_HW_BLOCKS_RST); + retval |= mc417_register_write(dev, + IVTV_REG_ENC_SDRAM_REFRESH, 0x80000800); + retval |= mc417_register_write(dev, + IVTV_REG_ENC_SDRAM_PRECHARGE, 0x1A); + retval |= mc417_register_write(dev, + IVTV_REG_APU, 0); + + if (retval != 0) { + printk(KERN_ERR "%s: Error with mc417_register_write\n", + __func__); + return -1; + } + + retval = request_firmware(&firmware, CX23885_FIRM_IMAGE_NAME, + &dev->pci->dev); + + if (retval != 0) { + printk(KERN_ERR + "ERROR: Hotplug firmware request failed (%s).\n", + CX23885_FIRM_IMAGE_NAME); + printk(KERN_ERR "Please fix your hotplug setup, the board will " + "not work without firmware loaded!\n"); + return -1; + } + + if (firmware->size != CX23885_FIRM_IMAGE_SIZE) { + printk(KERN_ERR "ERROR: Firmware size mismatch " + "(have %zd, expected %d)\n", + firmware->size, CX23885_FIRM_IMAGE_SIZE); + release_firmware(firmware); + return -1; + } + + if (0 != memcmp(firmware->data, magic, 8)) { + printk(KERN_ERR + "ERROR: Firmware magic mismatch, wrong file?\n"); + release_firmware(firmware); + return -1; + } + + /* transfer to the chip */ + dprintk(2, "Loading firmware ...\n"); + dataptr = (u32 *)firmware->data; + for (i = 0; i < (firmware->size >> 2); i++) { + value = *dataptr; + checksum += ~value; + if (mc417_memory_write(dev, i, value) != 0) { + printk(KERN_ERR "ERROR: Loading firmware failed!\n"); + release_firmware(firmware); + return -1; + } + dataptr++; + } + + /* read back to verify with the checksum */ + dprintk(1, "Verifying firmware ...\n"); + for (i--; i >= 0; i--) { + if (mc417_memory_read(dev, i, &value) != 0) { + printk(KERN_ERR "ERROR: Reading firmware failed!\n"); + release_firmware(firmware); + return -1; + } + checksum -= ~value; + } + if (checksum) { + printk(KERN_ERR + "ERROR: Firmware load failed (checksum mismatch).\n"); + release_firmware(firmware); + return -1; + } + release_firmware(firmware); + dprintk(1, "Firmware upload successful.\n"); + + retval |= mc417_register_write(dev, IVTV_REG_HW_BLOCKS, + IVTV_CMD_HW_BLOCKS_RST); + + /* F/W power up disturbs the GPIOs, restore state */ + retval |= mc417_register_write(dev, 0x9020, gpio_output); + retval |= mc417_register_write(dev, 0x900C, gpio_value); + + retval |= mc417_register_read(dev, IVTV_REG_VPU, &value); + retval |= mc417_register_write(dev, IVTV_REG_VPU, value & 0xFFFFFFE8); + + /* Hardcoded GPIO's here */ + retval |= mc417_register_write(dev, 0x9020, 0x4000); + retval |= mc417_register_write(dev, 0x900C, 0x4000); + + mc417_register_read(dev, 0x9020, &gpio_output); + mc417_register_read(dev, 0x900C, &gpio_value); + + if (retval < 0) + printk(KERN_ERR "%s: Error with mc417_register_write\n", + __func__); + return 0; +} + +void cx23885_417_check_encoder(struct cx23885_dev *dev) +{ + u32 status, seq; + + status = seq = 0; + cx23885_api_cmd(dev, CX2341X_ENC_GET_SEQ_END, 0, 2, &status, &seq); + dprintk(1, "%s() status = %d, seq = %d\n", __func__, status, seq); +} + +static void cx23885_codec_settings(struct cx23885_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + + /* Dynamically change the height based on video standard */ + if (dev->encodernorm.id & V4L2_STD_525_60) + dev->ts1.height = 480; + else + dev->ts1.height = 576; + + /* assign frame size */ + cx23885_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0, + dev->ts1.height, dev->ts1.width); + + dev->mpeg_params.width = dev->ts1.width; + dev->mpeg_params.height = dev->ts1.height; + dev->mpeg_params.is_50hz = + (dev->encodernorm.id & V4L2_STD_625_50) != 0; + + cx2341x_update(dev, cx23885_mbox_func, NULL, &dev->mpeg_params); + + cx23885_api_cmd(dev, CX2341X_ENC_MISC, 2, 0, 3, 1); + cx23885_api_cmd(dev, CX2341X_ENC_MISC, 2, 0, 4, 1); +} + +static int cx23885_initialize_codec(struct cx23885_dev *dev, int startencoder) +{ + int version; + int retval; + u32 i, data[7]; + + dprintk(1, "%s()\n", __func__); + + retval = cx23885_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); /* ping */ + if (retval < 0) { + dprintk(2, "%s() PING OK\n", __func__); + retval = cx23885_load_firmware(dev); + if (retval < 0) { + printk(KERN_ERR "%s() f/w load failed\n", __func__); + return retval; + } + retval = cx23885_find_mailbox(dev); + if (retval < 0) { + printk(KERN_ERR "%s() mailbox < 0, error\n", + __func__); + return -1; + } + dev->cx23417_mailbox = retval; + retval = cx23885_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); + if (retval < 0) { + printk(KERN_ERR + "ERROR: cx23417 firmware ping failed!\n"); + return -1; + } + retval = cx23885_api_cmd(dev, CX2341X_ENC_GET_VERSION, 0, 1, + &version); + if (retval < 0) { + printk(KERN_ERR "ERROR: cx23417 firmware get encoder :" + "version failed!\n"); + return -1; + } + dprintk(1, "cx23417 firmware version is 0x%08x\n", version); + msleep(200); + } + + cx23885_codec_settings(dev); + msleep(60); + + cx23885_api_cmd(dev, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, 0, + CX23885_FIELD1_SAA7115, CX23885_FIELD2_SAA7115); + cx23885_api_cmd(dev, CX2341X_ENC_SET_PLACEHOLDER, 12, 0, + CX23885_CUSTOM_EXTENSION_USR_DATA, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0); + + /* Setup to capture VBI */ + data[0] = 0x0001BD00; + data[1] = 1; /* frames per interrupt */ + data[2] = 4; /* total bufs */ + data[3] = 0x91559155; /* start codes */ + data[4] = 0x206080C0; /* stop codes */ + data[5] = 6; /* lines */ + data[6] = 64; /* BPL */ + + cx23885_api_cmd(dev, CX2341X_ENC_SET_VBI_CONFIG, 7, 0, data[0], data[1], + data[2], data[3], data[4], data[5], data[6]); + + for (i = 2; i <= 24; i++) { + int valid; + + valid = ((i >= 19) && (i <= 21)); + cx23885_api_cmd(dev, CX2341X_ENC_SET_VBI_LINE, 5, 0, i, + valid, 0 , 0, 0); + cx23885_api_cmd(dev, CX2341X_ENC_SET_VBI_LINE, 5, 0, + i | 0x80000000, valid, 0, 0, 0); + } + + cx23885_api_cmd(dev, CX2341X_ENC_MUTE_AUDIO, 1, 0, CX23885_UNMUTE); + msleep(60); + + /* initialize the video input */ + cx23885_api_cmd(dev, CX2341X_ENC_INITIALIZE_INPUT, 0, 0); + msleep(60); + + /* Enable VIP style pixel invalidation so we work with scaled mode */ + mc417_memory_write(dev, 2120, 0x00000080); + + /* start capturing to the host interface */ + if (startencoder) { + cx23885_api_cmd(dev, CX2341X_ENC_START_CAPTURE, 2, 0, + CX23885_MPEG_CAPTURE, CX23885_RAW_BITS_NONE); + msleep(10); + } + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int bb_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct cx23885_fh *fh = q->priv_data; + + fh->dev->ts1.ts_packet_size = mpeglinesize; + fh->dev->ts1.ts_packet_count = mpeglines; + + *size = fh->dev->ts1.ts_packet_size * fh->dev->ts1.ts_packet_count; + *count = mpegbufs; + + return 0; +} + +static int bb_buf_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct cx23885_fh *fh = q->priv_data; + return cx23885_buf_prepare(q, &fh->dev->ts1, + (struct cx23885_buffer *)vb, + field); +} + +static void bb_buf_queue(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct cx23885_fh *fh = q->priv_data; + cx23885_buf_queue(&fh->dev->ts1, (struct cx23885_buffer *)vb); +} + +static void bb_buf_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + cx23885_free_buffer(q, (struct cx23885_buffer *)vb); +} + +static struct videobuf_queue_ops cx23885_qops = { + .buf_setup = bb_buf_setup, + .buf_prepare = bb_buf_prepare, + .buf_queue = bb_buf_queue, + .buf_release = bb_buf_release, +}; + +/* ------------------------------------------------------------------ */ + +static const u32 *ctrl_classes[] = { + cx2341x_mpeg_ctrls, + NULL +}; + +static int cx23885_queryctrl(struct cx23885_dev *dev, + struct v4l2_queryctrl *qctrl) +{ + qctrl->id = v4l2_ctrl_next(ctrl_classes, qctrl->id); + if (qctrl->id == 0) + return -EINVAL; + + /* MPEG V4L2 controls */ + if (cx2341x_ctrl_query(&dev->mpeg_params, qctrl)) + qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; + + return 0; +} + +static int cx23885_querymenu(struct cx23885_dev *dev, + struct v4l2_querymenu *qmenu) +{ + struct v4l2_queryctrl qctrl; + + qctrl.id = qmenu->id; + cx23885_queryctrl(dev, &qctrl); + return v4l2_ctrl_query_menu(qmenu, &qctrl, + cx2341x_ctrl_get_menu(&dev->mpeg_params, qmenu->id)); +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + call_all(dev, core, g_std, id); + + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(cx23885_tvnorms); i++) + if (*id & cx23885_tvnorms[i].id) + break; + if (i == ARRAY_SIZE(cx23885_tvnorms)) + return -EINVAL; + dev->encodernorm = cx23885_tvnorms[i]; + + /* Have the drier core notify the subdevices */ + mutex_lock(&dev->lock); + cx23885_set_tvnorm(dev, *id); + mutex_unlock(&dev->lock); + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + dprintk(1, "%s()\n", __func__); + return cx23885_enum_input(dev, i); +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + return cx23885_get_input(file, priv, i); +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return cx23885_set_input(file, priv, i); +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + if (UNSET == dev->tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + strcpy(t->name, "Television"); + call_all(dev, tuner, g_tuner, t); + + dprintk(1, "VIDIOC_G_TUNER: tuner type %d\n", t->type); + + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + if (UNSET == dev->tuner_type) + return -EINVAL; + + /* Update the A/V core */ + call_all(dev, tuner, s_tuner, t); + + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + if (UNSET == dev->tuner_type) + return -EINVAL; + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = dev->freq; + + call_all(dev, tuner, g_frequency, f); + + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + return cx23885_set_frequency(file, priv, f); +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctl) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + return cx23885_get_control(dev, ctl); +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctl) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + return cx23885_set_control(dev, ctl); +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + struct cx23885_tsport *tsport = &dev->ts1; + + strlcpy(cap->driver, dev->name, sizeof(cap->driver)); + strlcpy(cap->card, cx23885_boards[tsport->dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | + 0; + if (UNSET != dev->tuner_type) + cap->capabilities |= V4L2_CAP_TUNER; + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + strlcpy(f->description, "MPEG", sizeof(f->description)); + f->pixelformat = V4L2_PIX_FMT_MPEG; + + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = + dev->ts1.ts_packet_size * dev->ts1.ts_packet_count; + f->fmt.pix.colorspace = 0; + f->fmt.pix.width = dev->ts1.width; + f->fmt.pix.height = dev->ts1.height; + f->fmt.pix.field = fh->mpegq.field; + dprintk(1, "VIDIOC_G_FMT: w: %d, h: %d, f: %d\n", + dev->ts1.width, dev->ts1.height, fh->mpegq.field); + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = + dev->ts1.ts_packet_size * dev->ts1.ts_packet_count; + f->fmt.pix.colorspace = 0; + dprintk(1, "VIDIOC_TRY_FMT: w: %d, h: %d, f: %d\n", + dev->ts1.width, dev->ts1.height, fh->mpegq.field); + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = + dev->ts1.ts_packet_size * dev->ts1.ts_packet_count; + f->fmt.pix.colorspace = 0; + dprintk(1, "VIDIOC_S_FMT: w: %d, h: %d, f: %d\n", + f->fmt.pix.width, f->fmt.pix.height, f->fmt.pix.field); + return 0; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct cx23885_fh *fh = file->private_data; + + return videobuf_reqbufs(&fh->mpegq, p); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct cx23885_fh *fh = file->private_data; + + return videobuf_querybuf(&fh->mpegq, p); +} + +static int vidioc_qbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct cx23885_fh *fh = file->private_data; + + return videobuf_qbuf(&fh->mpegq, p); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct cx23885_fh *fh = priv; + + return videobuf_dqbuf(&fh->mpegq, b, file->f_flags & O_NONBLOCK); +} + + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct cx23885_fh *fh = file->private_data; + + return videobuf_streamon(&fh->mpegq); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct cx23885_fh *fh = file->private_data; + + return videobuf_streamoff(&fh->mpegq); +} + +static int vidioc_g_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *f) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + + if (f->ctrl_class != V4L2_CTRL_CLASS_MPEG) + return -EINVAL; + return cx2341x_ext_ctrls(&dev->mpeg_params, 0, f, VIDIOC_G_EXT_CTRLS); +} + +static int vidioc_s_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *f) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + struct cx2341x_mpeg_params p; + int err; + + if (f->ctrl_class != V4L2_CTRL_CLASS_MPEG) + return -EINVAL; + + p = dev->mpeg_params; + err = cx2341x_ext_ctrls(&p, 0, f, VIDIOC_S_EXT_CTRLS); + + if (err == 0) { + err = cx2341x_update(dev, cx23885_mbox_func, + &dev->mpeg_params, &p); + dev->mpeg_params = p; + } + return err; +} + +static int vidioc_try_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *f) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + struct cx2341x_mpeg_params p; + int err; + + if (f->ctrl_class != V4L2_CTRL_CLASS_MPEG) + return -EINVAL; + + p = dev->mpeg_params; + err = cx2341x_ext_ctrls(&p, 0, f, VIDIOC_TRY_EXT_CTRLS); + return err; +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + char name[32 + 2]; + + snprintf(name, sizeof(name), "%s/2", dev->name); + printk(KERN_INFO + "%s/2: ============ START LOG STATUS ============\n", + dev->name); + call_all(dev, core, log_status); + cx2341x_log_status(&dev->mpeg_params, name); + printk(KERN_INFO + "%s/2: ============= END LOG STATUS =============\n", + dev->name); + return 0; +} + +static int vidioc_querymenu(struct file *file, void *priv, + struct v4l2_querymenu *a) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + + return cx23885_querymenu(dev, a); +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *c) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + + return cx23885_queryctrl(dev, c); +} + +static int mpeg_open(struct file *file) +{ + struct cx23885_dev *dev = video_drvdata(file); + struct cx23885_fh *fh; + + dprintk(2, "%s()\n", __func__); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (!fh) + return -ENOMEM; + + file->private_data = fh; + fh->dev = dev; + + videobuf_queue_sg_init(&fh->mpegq, &cx23885_qops, + &dev->pci->dev, &dev->ts1.slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx23885_buffer), + fh, NULL); + return 0; +} + +static int mpeg_release(struct file *file) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + dprintk(2, "%s()\n", __func__); + + /* FIXME: Review this crap */ + /* Shut device down on last close */ + if (atomic_cmpxchg(&fh->v4l_reading, 1, 0) == 1) { + if (atomic_dec_return(&dev->v4l_reader_count) == 0) { + /* stop mpeg capture */ + cx23885_api_cmd(fh->dev, CX2341X_ENC_STOP_CAPTURE, 3, 0, + CX23885_END_NOW, CX23885_MPEG_CAPTURE, + CX23885_RAW_BITS_NONE); + + msleep(500); + cx23885_417_check_encoder(dev); + + cx23885_cancel_buffers(&fh->dev->ts1); + } + } + + if (fh->mpegq.streaming) + videobuf_streamoff(&fh->mpegq); + if (fh->mpegq.reading) + videobuf_read_stop(&fh->mpegq); + + videobuf_mmap_free(&fh->mpegq); + file->private_data = NULL; + kfree(fh); + + return 0; +} + +static ssize_t mpeg_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + dprintk(2, "%s()\n", __func__); + + /* Deal w/ A/V decoder * and mpeg encoder sync issues. */ + /* Start mpeg encoder on first read. */ + if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) { + if (atomic_inc_return(&dev->v4l_reader_count) == 1) { + if (cx23885_initialize_codec(dev, 1) < 0) + return -EINVAL; + } + } + + return videobuf_read_stream(&fh->mpegq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static unsigned int mpeg_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + dprintk(2, "%s\n", __func__); + + return videobuf_poll_stream(file, &fh->mpegq, wait); +} + +static int mpeg_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + dprintk(2, "%s()\n", __func__); + + return videobuf_mmap_mapper(&fh->mpegq, vma); +} + +static struct v4l2_file_operations mpeg_fops = { + .owner = THIS_MODULE, + .open = mpeg_open, + .release = mpeg_release, + .read = mpeg_read, + .poll = mpeg_poll, + .mmap = mpeg_mmap, + .ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops mpeg_ioctl_ops = { + .vidioc_querystd = vidioc_g_std, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_ext_ctrls = vidioc_g_ext_ctrls, + .vidioc_s_ext_ctrls = vidioc_s_ext_ctrls, + .vidioc_try_ext_ctrls = vidioc_try_ext_ctrls, + .vidioc_log_status = vidioc_log_status, + .vidioc_querymenu = vidioc_querymenu, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_chip_ident = cx23885_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = cx23885_g_register, + .vidioc_s_register = cx23885_s_register, +#endif +}; + +static struct video_device cx23885_mpeg_template = { + .name = "cx23885", + .fops = &mpeg_fops, + .ioctl_ops = &mpeg_ioctl_ops, + .tvnorms = CX23885_NORMS, + .current_norm = V4L2_STD_NTSC_M, +}; + +void cx23885_417_unregister(struct cx23885_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + + if (dev->v4l_device) { + if (video_is_registered(dev->v4l_device)) + video_unregister_device(dev->v4l_device); + else + video_device_release(dev->v4l_device); + dev->v4l_device = NULL; + } +} + +static struct video_device *cx23885_video_dev_alloc( + struct cx23885_tsport *tsport, + struct pci_dev *pci, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + struct cx23885_dev *dev = tsport->dev; + + dprintk(1, "%s()\n", __func__); + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + snprintf(vfd->name, sizeof(vfd->name), "%s (%s)", + cx23885_boards[tsport->dev->board].name, type); + vfd->parent = &pci->dev; + vfd->release = video_device_release; + return vfd; +} + +int cx23885_417_register(struct cx23885_dev *dev) +{ + /* FIXME: Port1 hardcoded here */ + int err = -ENODEV; + struct cx23885_tsport *tsport = &dev->ts1; + + dprintk(1, "%s()\n", __func__); + + if (cx23885_boards[dev->board].portb != CX23885_MPEG_ENCODER) + return err; + + /* Set default TV standard */ + dev->encodernorm = cx23885_tvnorms[0]; + + if (dev->encodernorm.id & V4L2_STD_525_60) + tsport->height = 480; + else + tsport->height = 576; + + tsport->width = 720; + cx2341x_fill_defaults(&dev->mpeg_params); + + dev->mpeg_params.port = CX2341X_PORT_SERIAL; + + /* Allocate and initialize V4L video device */ + dev->v4l_device = cx23885_video_dev_alloc(tsport, + dev->pci, &cx23885_mpeg_template, "mpeg"); + video_set_drvdata(dev->v4l_device, dev); + err = video_register_device(dev->v4l_device, + VFL_TYPE_GRABBER, -1); + if (err < 0) { + printk(KERN_INFO "%s: can't register mpeg device\n", dev->name); + return err; + } + + printk(KERN_INFO "%s: registered device %s [mpeg]\n", + dev->name, video_device_node_name(dev->v4l_device)); + + /* ST: Configure the encoder paramaters, but don't begin + * encoding, this resolves an issue where the first time the + * encoder is started video can be choppy. + */ + cx23885_initialize_codec(dev, 0); + + return 0; +} + +MODULE_FIRMWARE(CX23885_FIRM_IMAGE_NAME); diff --git a/drivers/media/video/cx23885/cx23885-alsa.c b/drivers/media/pci/cx23885/cx23885-alsa.c similarity index 100% rename from drivers/media/video/cx23885/cx23885-alsa.c rename to drivers/media/pci/cx23885/cx23885-alsa.c diff --git a/drivers/media/video/cx23885/cx23885-av.c b/drivers/media/pci/cx23885/cx23885-av.c similarity index 100% rename from drivers/media/video/cx23885/cx23885-av.c rename to drivers/media/pci/cx23885/cx23885-av.c diff --git a/drivers/media/video/cx23885/cx23885-av.h b/drivers/media/pci/cx23885/cx23885-av.h similarity index 100% rename from drivers/media/video/cx23885/cx23885-av.h rename to drivers/media/pci/cx23885/cx23885-av.h diff --git a/drivers/media/pci/cx23885/cx23885-cards.c b/drivers/media/pci/cx23885/cx23885-cards.c new file mode 100644 index 000000000000..39a4a4b9ed7e --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-cards.c @@ -0,0 +1,1694 @@ +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2006 Steven Toth + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cx23885.h" +#include "tuner-xc2028.h" +#include "netup-eeprom.h" +#include "netup-init.h" +#include "altera-ci.h" +#include "xc4000.h" +#include "xc5000.h" +#include "cx23888-ir.h" + +static unsigned int netup_card_rev = 4; +module_param(netup_card_rev, int, 0644); +MODULE_PARM_DESC(netup_card_rev, + "NetUP Dual DVB-T/C CI card revision"); +static unsigned int enable_885_ir; +module_param(enable_885_ir, int, 0644); +MODULE_PARM_DESC(enable_885_ir, + "Enable integrated IR controller for supported\n" + "\t\t CX2388[57] boards that are wired for it:\n" + "\t\t\tHVR-1250 (reported safe)\n" + "\t\t\tTerraTec Cinergy T PCIe Dual (not well tested, appears to be safe)\n" + "\t\t\tTeVii S470 (reported unsafe)\n" + "\t\t This can cause an interrupt storm with some cards.\n" + "\t\t Default: 0 [Disabled]"); + +/* ------------------------------------------------------------------ */ +/* board config info */ + +struct cx23885_board cx23885_boards[] = { + [CX23885_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + /* Ensure safe default for unknown boards */ + .clk_freq = 0, + .input = {{ + .type = CX23885_VMUX_COMPOSITE1, + .vmux = 0, + }, { + .type = CX23885_VMUX_COMPOSITE2, + .vmux = 1, + }, { + .type = CX23885_VMUX_COMPOSITE3, + .vmux = 2, + }, { + .type = CX23885_VMUX_COMPOSITE4, + .vmux = 3, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1800lp] = { + .name = "Hauppauge WinTV-HVR1800lp", + .portc = CX23885_MPEG_DVB, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xff00, + }, { + .type = CX23885_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0xff01, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff02, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xff02, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1800] = { + .name = "Hauppauge WinTV-HVR1800", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_ENCODER, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_PHILIPS_TDA8290, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .tuner_bus = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1, + .amux = CX25840_AUDIO8, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + .gpio0 = 0, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1250] = { + .name = "Hauppauge WinTV-HVR1250", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, +#ifdef MT2131_NO_ANALOG_SUPPORT_YET + .tuner_type = TUNER_PHILIPS_TDA8290, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .tuner_bus = 1, +#endif + .force_bff = 1, + .input = {{ +#ifdef MT2131_NO_ANALOG_SUPPORT_YET + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1, + .amux = CX25840_AUDIO8, + .gpio0 = 0xff00, + }, { +#endif + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + .gpio0 = 0xff02, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + .gpio0 = 0xff02, + } }, + }, + [CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP] = { + .name = "DViCO FusionHDTV5 Express", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1500Q] = { + .name = "Hauppauge WinTV-HVR1500Q", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1500] = { + .name = "Hauppauge WinTV-HVR1500", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, /* 0xc2 >> 1 */ + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .gpio0 = 0, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1200] = { + .name = "Hauppauge WinTV-HVR1200", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1700] = { + .name = "Hauppauge WinTV-HVR1700", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1400] = { + .name = "Hauppauge WinTV-HVR1400", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP] = { + .name = "DViCO FusionHDTV7 Dual Express", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP] = { + .name = "DViCO FusionHDTV DVB-T Dual Express", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H] = { + .name = "Leadtek Winfast PxDVR3200 H", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000] = { + .name = "Leadtek Winfast PxDVR3200 H XC4000", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_XC4000, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2 | + CX25840_NONE0_CH3, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE1, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN7_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN8_CH3 | + CX25840_COMPONENT_ON, + } }, + }, + [CX23885_BOARD_COMPRO_VIDEOMATE_E650F] = { + .name = "Compro VideoMate E650F", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_TBS_6920] = { + .name = "TurboSight TBS 6920", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_TEVII_S470] = { + .name = "TeVii S470", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_DVBWORLD_2005] = { + .name = "DVBWorld DVB-S2 2005", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_NETUP_DUAL_DVBS2_CI] = { + .ci_type = 1, + .name = "NetUP Dual DVB-S2 CI", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1270] = { + .name = "Hauppauge WinTV-HVR1270", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1275] = { + .name = "Hauppauge WinTV-HVR1275", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1255] = { + .name = "Hauppauge WinTV-HVR1255", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1255_22111] = { + .name = "Hauppauge WinTV-HVR1255", + .porta = CX23885_ANALOG_VIDEO, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1210] = { + .name = "Hauppauge WinTV-HVR1210", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_MYGICA_X8506] = { + .name = "Mygica X8506 DMB-TH", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x61, + .tuner_bus = 1, + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .input = { + { + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_COMPOSITE2, + }, + { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE8, + }, + { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, + { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_COMPONENT_ON | + CX25840_VIN1_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN7_CH3, + }, + }, + }, + [CX23885_BOARD_MAGICPRO_PROHDTVE2] = { + .name = "Magic-Pro ProHDTV Extreme 2", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x61, + .tuner_bus = 1, + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .input = { + { + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_COMPOSITE2, + }, + { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE8, + }, + { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, + { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_COMPONENT_ON | + CX25840_VIN1_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN7_CH3, + }, + }, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1850] = { + .name = "Hauppauge WinTV-HVR1850", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_ENCODER, + .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_ABSENT, + .tuner_addr = 0x42, /* 0x84 >> 1 */ + .force_bff = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN5_CH2 | + CX25840_VIN2_CH1 | + CX25840_DIF_ON, + .amux = CX25840_AUDIO8, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN6_CH1, + .amux = CX25840_AUDIO7, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_VIN7_CH3 | + CX25840_VIN4_CH2 | + CX25840_VIN8_CH1 | + CX25840_SVIDEO_ON, + .amux = CX25840_AUDIO7, + } }, + }, + [CX23885_BOARD_COMPRO_VIDEOMATE_E800] = { + .name = "Compro VideoMate E800", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_HAUPPAUGE_HVR1290] = { + .name = "Hauppauge WinTV-HVR1290", + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_MYGICA_X8558PRO] = { + .name = "Mygica X8558 PRO DMB-TH", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_LEADTEK_WINFAST_PXTV1200] = { + .name = "LEADTEK WinFast PxTV1200", + .porta = CX23885_ANALOG_VIDEO, + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .tuner_bus = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2 | + CX25840_NONE0_CH3, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE1, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN7_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN8_CH3 | + CX25840_COMPONENT_ON, + } }, + }, + [CX23885_BOARD_GOTVIEW_X5_3D_HYBRID] = { + .name = "GoTView X5 3D Hybrid", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x64, + .tuner_bus = 1, + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2, + .gpio0 = 0x02, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX23885_VMUX_COMPOSITE1, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + } }, + }, + [CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF] = { + .ci_type = 2, + .name = "NetUP Dual DVB-T/C-CI RF", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + .num_fds_portb = 2, + .num_fds_portc = 2, + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x64, + .input = { { + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_COMPOSITE1, + } }, + }, + [CX23885_BOARD_MPX885] = { + .name = "MPX-885", + .porta = CX23885_ANALOG_VIDEO, + .input = {{ + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE1, + .amux = CX25840_AUDIO6, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE2, + .vmux = CX25840_COMPOSITE2, + .amux = CX25840_AUDIO6, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE3, + .vmux = CX25840_COMPOSITE3, + .amux = CX25840_AUDIO7, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE4, + .vmux = CX25840_COMPOSITE4, + .amux = CX25840_AUDIO7, + .gpio0 = 0, + } }, + }, + [CX23885_BOARD_MYGICA_X8507] = { + .name = "Mygica X8507", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0x61, + .tuner_bus = 1, + .porta = CX23885_ANALOG_VIDEO, + .input = { + { + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_COMPOSITE2, + .amux = CX25840_AUDIO8, + }, + { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE8, + }, + { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, + { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_COMPONENT_ON | + CX25840_VIN1_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN7_CH3, + }, + }, + }, + [CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL] = { + .name = "TerraTec Cinergy T PCIe Dual", + .portb = CX23885_MPEG_DVB, + .portc = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_TEVII_S471] = { + .name = "TeVii S471", + .portb = CX23885_MPEG_DVB, + }, + [CX23885_BOARD_PROF_8000] = { + .name = "Prof Revolution DVB-S2 8000", + .portb = CX23885_MPEG_DVB, + } +}; +const unsigned int cx23885_bcount = ARRAY_SIZE(cx23885_boards); + +/* ------------------------------------------------------------------ */ +/* PCI subsystem IDs */ + +struct cx23885_subid cx23885_subids[] = { + { + .subvendor = 0x0070, + .subdevice = 0x3400, + .card = CX23885_BOARD_UNKNOWN, + }, { + .subvendor = 0x0070, + .subdevice = 0x7600, + .card = CX23885_BOARD_HAUPPAUGE_HVR1800lp, + }, { + .subvendor = 0x0070, + .subdevice = 0x7800, + .card = CX23885_BOARD_HAUPPAUGE_HVR1800, + }, { + .subvendor = 0x0070, + .subdevice = 0x7801, + .card = CX23885_BOARD_HAUPPAUGE_HVR1800, + }, { + .subvendor = 0x0070, + .subdevice = 0x7809, + .card = CX23885_BOARD_HAUPPAUGE_HVR1800, + }, { + .subvendor = 0x0070, + .subdevice = 0x7911, + .card = CX23885_BOARD_HAUPPAUGE_HVR1250, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd500, + .card = CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP, + }, { + .subvendor = 0x0070, + .subdevice = 0x7790, + .card = CX23885_BOARD_HAUPPAUGE_HVR1500Q, + }, { + .subvendor = 0x0070, + .subdevice = 0x7797, + .card = CX23885_BOARD_HAUPPAUGE_HVR1500Q, + }, { + .subvendor = 0x0070, + .subdevice = 0x7710, + .card = CX23885_BOARD_HAUPPAUGE_HVR1500, + }, { + .subvendor = 0x0070, + .subdevice = 0x7717, + .card = CX23885_BOARD_HAUPPAUGE_HVR1500, + }, { + .subvendor = 0x0070, + .subdevice = 0x71d1, + .card = CX23885_BOARD_HAUPPAUGE_HVR1200, + }, { + .subvendor = 0x0070, + .subdevice = 0x71d3, + .card = CX23885_BOARD_HAUPPAUGE_HVR1200, + }, { + .subvendor = 0x0070, + .subdevice = 0x8101, + .card = CX23885_BOARD_HAUPPAUGE_HVR1700, + }, { + .subvendor = 0x0070, + .subdevice = 0x8010, + .card = CX23885_BOARD_HAUPPAUGE_HVR1400, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd618, + .card = CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb78, + .card = CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP, + }, { + .subvendor = 0x107d, + .subdevice = 0x6681, + .card = CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f39, + .card = CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000, + }, { + .subvendor = 0x185b, + .subdevice = 0xe800, + .card = CX23885_BOARD_COMPRO_VIDEOMATE_E650F, + }, { + .subvendor = 0x6920, + .subdevice = 0x8888, + .card = CX23885_BOARD_TBS_6920, + }, { + .subvendor = 0xd470, + .subdevice = 0x9022, + .card = CX23885_BOARD_TEVII_S470, + }, { + .subvendor = 0x0001, + .subdevice = 0x2005, + .card = CX23885_BOARD_DVBWORLD_2005, + }, { + .subvendor = 0x1b55, + .subdevice = 0x2a2c, + .card = CX23885_BOARD_NETUP_DUAL_DVBS2_CI, + }, { + .subvendor = 0x0070, + .subdevice = 0x2211, + .card = CX23885_BOARD_HAUPPAUGE_HVR1270, + }, { + .subvendor = 0x0070, + .subdevice = 0x2215, + .card = CX23885_BOARD_HAUPPAUGE_HVR1275, + }, { + .subvendor = 0x0070, + .subdevice = 0x221d, + .card = CX23885_BOARD_HAUPPAUGE_HVR1275, + }, { + .subvendor = 0x0070, + .subdevice = 0x2251, + .card = CX23885_BOARD_HAUPPAUGE_HVR1255, + }, { + .subvendor = 0x0070, + .subdevice = 0x2259, + .card = CX23885_BOARD_HAUPPAUGE_HVR1255_22111, + }, { + .subvendor = 0x0070, + .subdevice = 0x2291, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x2295, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x2299, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x229d, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, /* HVR1215 */ + }, { + .subvendor = 0x0070, + .subdevice = 0x22f0, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x22f1, + .card = CX23885_BOARD_HAUPPAUGE_HVR1255, + }, { + .subvendor = 0x0070, + .subdevice = 0x22f2, + .card = CX23885_BOARD_HAUPPAUGE_HVR1275, + }, { + .subvendor = 0x0070, + .subdevice = 0x22f3, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, /* HVR1215 */ + }, { + .subvendor = 0x0070, + .subdevice = 0x22f4, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, + }, { + .subvendor = 0x0070, + .subdevice = 0x22f5, + .card = CX23885_BOARD_HAUPPAUGE_HVR1210, /* HVR1215 */ + }, { + .subvendor = 0x14f1, + .subdevice = 0x8651, + .card = CX23885_BOARD_MYGICA_X8506, + }, { + .subvendor = 0x14f1, + .subdevice = 0x8657, + .card = CX23885_BOARD_MAGICPRO_PROHDTVE2, + }, { + .subvendor = 0x0070, + .subdevice = 0x8541, + .card = CX23885_BOARD_HAUPPAUGE_HVR1850, + }, { + .subvendor = 0x1858, + .subdevice = 0xe800, + .card = CX23885_BOARD_COMPRO_VIDEOMATE_E800, + }, { + .subvendor = 0x0070, + .subdevice = 0x8551, + .card = CX23885_BOARD_HAUPPAUGE_HVR1290, + }, { + .subvendor = 0x14f1, + .subdevice = 0x8578, + .card = CX23885_BOARD_MYGICA_X8558PRO, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f22, + .card = CX23885_BOARD_LEADTEK_WINFAST_PXTV1200, + }, { + .subvendor = 0x5654, + .subdevice = 0x2390, + .card = CX23885_BOARD_GOTVIEW_X5_3D_HYBRID, + }, { + .subvendor = 0x1b55, + .subdevice = 0xe2e4, + .card = CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF, + }, { + .subvendor = 0x14f1, + .subdevice = 0x8502, + .card = CX23885_BOARD_MYGICA_X8507, + }, { + .subvendor = 0x153b, + .subdevice = 0x117e, + .card = CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL, + }, { + .subvendor = 0xd471, + .subdevice = 0x9022, + .card = CX23885_BOARD_TEVII_S471, + }, { + .subvendor = 0x8000, + .subdevice = 0x3034, + .card = CX23885_BOARD_PROF_8000, + }, +}; +const unsigned int cx23885_idcount = ARRAY_SIZE(cx23885_subids); + +void cx23885_card_list(struct cx23885_dev *dev) +{ + int i; + + if (0 == dev->pci->subsystem_vendor && + 0 == dev->pci->subsystem_device) { + printk(KERN_INFO + "%s: Board has no valid PCIe Subsystem ID and can't\n" + "%s: be autodetected. Pass card= insmod option\n" + "%s: to workaround that. Redirect complaints to the\n" + "%s: vendor of the TV card. Best regards,\n" + "%s: -- tux\n", + dev->name, dev->name, dev->name, dev->name, dev->name); + } else { + printk(KERN_INFO + "%s: Your board isn't known (yet) to the driver.\n" + "%s: Try to pick one of the existing card configs via\n" + "%s: card= insmod option. Updating to the latest\n" + "%s: version might help as well.\n", + dev->name, dev->name, dev->name, dev->name); + } + printk(KERN_INFO "%s: Here is a list of valid choices for the card= insmod option:\n", + dev->name); + for (i = 0; i < cx23885_bcount; i++) + printk(KERN_INFO "%s: card=%d -> %s\n", + dev->name, i, cx23885_boards[i].name); +} + +static void hauppauge_eeprom(struct cx23885_dev *dev, u8 *eeprom_data) +{ + struct tveeprom tv; + + tveeprom_hauppauge_analog(&dev->i2c_bus[0].i2c_client, &tv, + eeprom_data); + + /* Make sure we support the board model */ + switch (tv.model) { + case 22001: + /* WinTV-HVR1270 (PCIe, Retail, half height) + * ATSC/QAM and basic analog, IR Blast */ + case 22009: + /* WinTV-HVR1210 (PCIe, Retail, half height) + * DVB-T and basic analog, IR Blast */ + case 22011: + /* WinTV-HVR1270 (PCIe, Retail, half height) + * ATSC/QAM and basic analog, IR Recv */ + case 22019: + /* WinTV-HVR1210 (PCIe, Retail, half height) + * DVB-T and basic analog, IR Recv */ + case 22021: + /* WinTV-HVR1275 (PCIe, Retail, half height) + * ATSC/QAM and basic analog, IR Recv */ + case 22029: + /* WinTV-HVR1210 (PCIe, Retail, half height) + * DVB-T and basic analog, IR Recv */ + case 22101: + /* WinTV-HVR1270 (PCIe, Retail, full height) + * ATSC/QAM and basic analog, IR Blast */ + case 22109: + /* WinTV-HVR1210 (PCIe, Retail, full height) + * DVB-T and basic analog, IR Blast */ + case 22111: + /* WinTV-HVR1270 (PCIe, Retail, full height) + * ATSC/QAM and basic analog, IR Recv */ + case 22119: + /* WinTV-HVR1210 (PCIe, Retail, full height) + * DVB-T and basic analog, IR Recv */ + case 22121: + /* WinTV-HVR1275 (PCIe, Retail, full height) + * ATSC/QAM and basic analog, IR Recv */ + case 22129: + /* WinTV-HVR1210 (PCIe, Retail, full height) + * DVB-T and basic analog, IR Recv */ + case 71009: + /* WinTV-HVR1200 (PCIe, Retail, full height) + * DVB-T and basic analog */ + case 71359: + /* WinTV-HVR1200 (PCIe, OEM, half height) + * DVB-T and basic analog */ + case 71439: + /* WinTV-HVR1200 (PCIe, OEM, half height) + * DVB-T and basic analog */ + case 71449: + /* WinTV-HVR1200 (PCIe, OEM, full height) + * DVB-T and basic analog */ + case 71939: + /* WinTV-HVR1200 (PCIe, OEM, half height) + * DVB-T and basic analog */ + case 71949: + /* WinTV-HVR1200 (PCIe, OEM, full height) + * DVB-T and basic analog */ + case 71959: + /* WinTV-HVR1200 (PCIe, OEM, full height) + * DVB-T and basic analog */ + case 71979: + /* WinTV-HVR1200 (PCIe, OEM, half height) + * DVB-T and basic analog */ + case 71999: + /* WinTV-HVR1200 (PCIe, OEM, full height) + * DVB-T and basic analog */ + case 76601: + /* WinTV-HVR1800lp (PCIe, Retail, No IR, Dual + channel ATSC and MPEG2 HW Encoder */ + case 77001: + /* WinTV-HVR1500 (Express Card, OEM, No IR, ATSC + and Basic analog */ + case 77011: + /* WinTV-HVR1500 (Express Card, Retail, No IR, ATSC + and Basic analog */ + case 77041: + /* WinTV-HVR1500Q (Express Card, OEM, No IR, ATSC/QAM + and Basic analog */ + case 77051: + /* WinTV-HVR1500Q (Express Card, Retail, No IR, ATSC/QAM + and Basic analog */ + case 78011: + /* WinTV-HVR1800 (PCIe, Retail, 3.5mm in, IR, No FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 78501: + /* WinTV-HVR1800 (PCIe, OEM, RCA in, No IR, FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 78521: + /* WinTV-HVR1800 (PCIe, OEM, RCA in, No IR, FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 78531: + /* WinTV-HVR1800 (PCIe, OEM, RCA in, No IR, No FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 78631: + /* WinTV-HVR1800 (PCIe, OEM, No IR, No FM, + Dual channel ATSC and MPEG2 HW Encoder */ + case 79001: + /* WinTV-HVR1250 (PCIe, Retail, IR, full height, + ATSC and Basic analog */ + case 79101: + /* WinTV-HVR1250 (PCIe, Retail, IR, half height, + ATSC and Basic analog */ + case 79501: + /* WinTV-HVR1250 (PCIe, No IR, half height, + ATSC [at least] and Basic analog) */ + case 79561: + /* WinTV-HVR1250 (PCIe, OEM, No IR, half height, + ATSC and Basic analog */ + case 79571: + /* WinTV-HVR1250 (PCIe, OEM, No IR, full height, + ATSC and Basic analog */ + case 79671: + /* WinTV-HVR1250 (PCIe, OEM, No IR, half height, + ATSC and Basic analog */ + case 80019: + /* WinTV-HVR1400 (Express Card, Retail, IR, + * DVB-T and Basic analog */ + case 81509: + /* WinTV-HVR1700 (PCIe, OEM, No IR, half height) + * DVB-T and MPEG2 HW Encoder */ + case 81519: + /* WinTV-HVR1700 (PCIe, OEM, No IR, full height) + * DVB-T and MPEG2 HW Encoder */ + break; + case 85021: + /* WinTV-HVR1850 (PCIe, Retail, 3.5mm in, IR, FM, + Dual channel ATSC and MPEG2 HW Encoder */ + break; + case 85721: + /* WinTV-HVR1290 (PCIe, OEM, RCA in, IR, + Dual channel ATSC and Basic analog */ + break; + default: + printk(KERN_WARNING "%s: warning: " + "unknown hauppauge model #%d\n", + dev->name, tv.model); + break; + } + + printk(KERN_INFO "%s: hauppauge eeprom: model=%d\n", + dev->name, tv.model); +} + +int cx23885_tuner_callback(void *priv, int component, int command, int arg) +{ + struct cx23885_tsport *port = priv; + struct cx23885_dev *dev = port->dev; + u32 bitmask = 0; + + if ((command == XC2028_RESET_CLK) || (command == XC2028_I2C_FLUSH)) + return 0; + + if (command != 0) { + printk(KERN_ERR "%s(): Unknown command 0x%x.\n", + __func__, command); + return -EINVAL; + } + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1400: + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: + /* Tuner Reset Command */ + bitmask = 0x04; + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP: + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: + /* Two identical tuners on two different i2c buses, + * we need to reset the correct gpio. */ + if (port->nr == 1) + bitmask = 0x01; + else if (port->nr == 2) + bitmask = 0x04; + break; + case CX23885_BOARD_GOTVIEW_X5_3D_HYBRID: + /* Tuner Reset Command */ + bitmask = 0x02; + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + altera_ci_tuner_reset(dev, port->nr); + break; + } + + if (bitmask) { + /* Drive the tuner into reset and back out */ + cx_clear(GP0_IO, bitmask); + mdelay(200); + cx_set(GP0_IO, bitmask); + } + + return 0; +} + +void cx23885_gpio_setup(struct cx23885_dev *dev) +{ + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1250: + /* GPIO-0 cx24227 demodulator reset */ + cx_set(GP0_IO, 0x00010001); /* Bring the part out of reset */ + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500: + /* GPIO-0 cx24227 demodulator */ + /* GPIO-2 xc3028 tuner */ + + /* Put the parts into reset */ + cx_set(GP0_IO, 0x00050000); + cx_clear(GP0_IO, 0x00000005); + msleep(5); + + /* Bring the parts out of reset */ + cx_set(GP0_IO, 0x00050005); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + /* GPIO-0 cx24227 demodulator reset */ + /* GPIO-2 xc5000 tuner reset */ + cx_set(GP0_IO, 0x00050005); /* Bring the part out of reset */ + break; + case CX23885_BOARD_HAUPPAUGE_HVR1800: + /* GPIO-0 656_CLK */ + /* GPIO-1 656_D0 */ + /* GPIO-2 8295A Reset */ + /* GPIO-3-10 cx23417 data0-7 */ + /* GPIO-11-14 cx23417 addr0-3 */ + /* GPIO-15-18 cx23417 READY, CS, RD, WR */ + /* GPIO-19 IR_RX */ + + /* CX23417 GPIO's */ + /* EIO15 Zilog Reset */ + /* EIO14 S5H1409/CX24227 Reset */ + mc417_gpio_enable(dev, GPIO_15 | GPIO_14, 1); + + /* Put the demod into reset and protect the eeprom */ + mc417_gpio_clear(dev, GPIO_15 | GPIO_14); + mdelay(100); + + /* Bring the demod and blaster out of reset */ + mc417_gpio_set(dev, GPIO_15 | GPIO_14); + mdelay(100); + + /* Force the TDA8295A into reset and back */ + cx23885_gpio_enable(dev, GPIO_2, 1); + cx23885_gpio_set(dev, GPIO_2); + mdelay(20); + cx23885_gpio_clear(dev, GPIO_2); + mdelay(20); + cx23885_gpio_set(dev, GPIO_2); + mdelay(20); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1200: + /* GPIO-0 tda10048 demodulator reset */ + /* GPIO-2 tda18271 tuner reset */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x00050000); + mdelay(20); + cx_clear(GP0_IO, 0x00000005); + mdelay(20); + cx_set(GP0_IO, 0x00050005); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1700: + /* GPIO-0 TDA10048 demodulator reset */ + /* GPIO-2 TDA8295A Reset */ + /* GPIO-3-10 cx23417 data0-7 */ + /* GPIO-11-14 cx23417 addr0-3 */ + /* GPIO-15-18 cx23417 READY, CS, RD, WR */ + + /* The following GPIO's are on the interna AVCore (cx25840) */ + /* GPIO-19 IR_RX */ + /* GPIO-20 IR_TX 416/DVBT Select */ + /* GPIO-21 IIS DAT */ + /* GPIO-22 IIS WCLK */ + /* GPIO-23 IIS BCLK */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x00050000); + mdelay(20); + cx_clear(GP0_IO, 0x00000005); + mdelay(20); + cx_set(GP0_IO, 0x00050005); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1400: + /* GPIO-0 Dibcom7000p demodulator reset */ + /* GPIO-2 xc3028L tuner reset */ + /* GPIO-13 LED */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x00050000); + mdelay(20); + cx_clear(GP0_IO, 0x00000005); + mdelay(20); + cx_set(GP0_IO, 0x00050005); + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP: + /* GPIO-0 xc5000 tuner reset i2c bus 0 */ + /* GPIO-1 s5h1409 demod reset i2c bus 0 */ + /* GPIO-2 xc5000 tuner reset i2c bus 1 */ + /* GPIO-3 s5h1409 demod reset i2c bus 0 */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x000f0000); + mdelay(20); + cx_clear(GP0_IO, 0x0000000f); + mdelay(20); + cx_set(GP0_IO, 0x000f000f); + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: + /* GPIO-0 portb xc3028 reset */ + /* GPIO-1 portb zl10353 reset */ + /* GPIO-2 portc xc3028 reset */ + /* GPIO-3 portc zl10353 reset */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x000f0000); + mdelay(20); + cx_clear(GP0_IO, 0x0000000f); + mdelay(20); + cx_set(GP0_IO, 0x000f000f); + break; + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: + /* GPIO-2 xc3028 tuner reset */ + + /* The following GPIO's are on the internal AVCore (cx25840) */ + /* GPIO-? zl10353 demod reset */ + + /* Put the parts into reset and back */ + cx_set(GP0_IO, 0x00040000); + mdelay(20); + cx_clear(GP0_IO, 0x00000004); + mdelay(20); + cx_set(GP0_IO, 0x00040004); + break; + case CX23885_BOARD_TBS_6920: + case CX23885_BOARD_PROF_8000: + cx_write(MC417_CTL, 0x00000036); + cx_write(MC417_OEN, 0x00001000); + cx_set(MC417_RWD, 0x00000002); + mdelay(200); + cx_clear(MC417_RWD, 0x00000800); + mdelay(200); + cx_set(MC417_RWD, 0x00000800); + mdelay(200); + break; + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + /* GPIO-0 INTA from CiMax1 + GPIO-1 INTB from CiMax2 + GPIO-2 reset chips + GPIO-3 to GPIO-10 data/addr for CA + GPIO-11 ~CS0 to CiMax1 + GPIO-12 ~CS1 to CiMax2 + GPIO-13 ADL0 load LSB addr + GPIO-14 ADL1 load MSB addr + GPIO-15 ~RDY from CiMax + GPIO-17 ~RD to CiMax + GPIO-18 ~WR to CiMax + */ + cx_set(GP0_IO, 0x00040000); /* GPIO as out */ + /* GPIO1 and GPIO2 as INTA and INTB from CiMaxes, reset low */ + cx_clear(GP0_IO, 0x00030004); + mdelay(100);/* reset delay */ + cx_set(GP0_IO, 0x00040004); /* GPIO as out, reset high */ + cx_write(MC417_CTL, 0x00000037);/* enable GPIO3-18 pins */ + /* GPIO-15 IN as ~ACK, rest as OUT */ + cx_write(MC417_OEN, 0x00001000); + /* ~RD, ~WR high; ADL0, ADL1 low; ~CS0, ~CS1 high */ + cx_write(MC417_RWD, 0x0000c300); + /* enable irq */ + cx_write(GPIO_ISM, 0x00000000);/* INTERRUPTS active low*/ + break; + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1210: + /* GPIO-5 RF Control: 0 = RF1 Terrestrial, 1 = RF2 Cable */ + /* GPIO-6 I2C Gate which can isolate the demod from the bus */ + /* GPIO-9 Demod reset */ + + /* Put the parts into reset and back */ + cx23885_gpio_enable(dev, GPIO_9 | GPIO_6 | GPIO_5, 1); + cx23885_gpio_set(dev, GPIO_9 | GPIO_6 | GPIO_5); + cx23885_gpio_clear(dev, GPIO_9); + mdelay(20); + cx23885_gpio_set(dev, GPIO_9); + break; + case CX23885_BOARD_MYGICA_X8506: + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + case CX23885_BOARD_MYGICA_X8507: + /* GPIO-0 (0)Analog / (1)Digital TV */ + /* GPIO-1 reset XC5000 */ + /* GPIO-2 reset LGS8GL5 / LGS8G75 */ + cx23885_gpio_enable(dev, GPIO_0 | GPIO_1 | GPIO_2, 1); + cx23885_gpio_clear(dev, GPIO_1 | GPIO_2); + mdelay(100); + cx23885_gpio_set(dev, GPIO_0 | GPIO_1 | GPIO_2); + mdelay(100); + break; + case CX23885_BOARD_MYGICA_X8558PRO: + /* GPIO-0 reset first ATBM8830 */ + /* GPIO-1 reset second ATBM8830 */ + cx23885_gpio_enable(dev, GPIO_0 | GPIO_1, 1); + cx23885_gpio_clear(dev, GPIO_0 | GPIO_1); + mdelay(100); + cx23885_gpio_set(dev, GPIO_0 | GPIO_1); + mdelay(100); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + /* GPIO-0 656_CLK */ + /* GPIO-1 656_D0 */ + /* GPIO-2 Wake# */ + /* GPIO-3-10 cx23417 data0-7 */ + /* GPIO-11-14 cx23417 addr0-3 */ + /* GPIO-15-18 cx23417 READY, CS, RD, WR */ + /* GPIO-19 IR_RX */ + /* GPIO-20 C_IR_TX */ + /* GPIO-21 I2S DAT */ + /* GPIO-22 I2S WCLK */ + /* GPIO-23 I2S BCLK */ + /* ALT GPIO: EXP GPIO LATCH */ + + /* CX23417 GPIO's */ + /* GPIO-14 S5H1411/CX24228 Reset */ + /* GPIO-13 EEPROM write protect */ + mc417_gpio_enable(dev, GPIO_14 | GPIO_13, 1); + + /* Put the demod into reset and protect the eeprom */ + mc417_gpio_clear(dev, GPIO_14 | GPIO_13); + mdelay(100); + + /* Bring the demod out of reset */ + mc417_gpio_set(dev, GPIO_14); + mdelay(100); + + /* CX24228 GPIO */ + /* Connected to IF / Mux */ + break; + case CX23885_BOARD_GOTVIEW_X5_3D_HYBRID: + cx_set(GP0_IO, 0x00010001); /* Bring the part out of reset */ + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + /* GPIO-0 ~INT in + GPIO-1 TMS out + GPIO-2 ~reset chips out + GPIO-3 to GPIO-10 data/addr for CA in/out + GPIO-11 ~CS out + GPIO-12 ADDR out + GPIO-13 ~WR out + GPIO-14 ~RD out + GPIO-15 ~RDY in + GPIO-16 TCK out + GPIO-17 TDO in + GPIO-18 TDI out + */ + cx_set(GP0_IO, 0x00060000); /* GPIO-1,2 as out */ + /* GPIO-0 as INT, reset & TMS low */ + cx_clear(GP0_IO, 0x00010006); + mdelay(100);/* reset delay */ + cx_set(GP0_IO, 0x00000004); /* reset high */ + cx_write(MC417_CTL, 0x00000037);/* enable GPIO-3..18 pins */ + /* GPIO-17 is TDO in, GPIO-15 is ~RDY in, rest is out */ + cx_write(MC417_OEN, 0x00005000); + /* ~RD, ~WR high; ADDR low; ~CS high */ + cx_write(MC417_RWD, 0x00000d00); + /* enable irq */ + cx_write(GPIO_ISM, 0x00000000);/* INTERRUPTS active low*/ + break; + } +} + +int cx23885_ir_init(struct cx23885_dev *dev) +{ + static struct v4l2_subdev_io_pin_config ir_rxtx_pin_cfg[] = { + { + .flags = V4L2_SUBDEV_IO_PIN_INPUT, + .pin = CX23885_PIN_IR_RX_GPIO19, + .function = CX23885_PAD_IR_RX, + .value = 0, + .strength = CX25840_PIN_DRIVE_MEDIUM, + }, { + .flags = V4L2_SUBDEV_IO_PIN_OUTPUT, + .pin = CX23885_PIN_IR_TX_GPIO20, + .function = CX23885_PAD_IR_TX, + .value = 0, + .strength = CX25840_PIN_DRIVE_MEDIUM, + } + }; + const size_t ir_rxtx_pin_cfg_count = ARRAY_SIZE(ir_rxtx_pin_cfg); + + static struct v4l2_subdev_io_pin_config ir_rx_pin_cfg[] = { + { + .flags = V4L2_SUBDEV_IO_PIN_INPUT, + .pin = CX23885_PIN_IR_RX_GPIO19, + .function = CX23885_PAD_IR_RX, + .value = 0, + .strength = CX25840_PIN_DRIVE_MEDIUM, + } + }; + const size_t ir_rx_pin_cfg_count = ARRAY_SIZE(ir_rx_pin_cfg); + + struct v4l2_subdev_ir_parameters params; + int ret = 0; + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + case CX23885_BOARD_HAUPPAUGE_HVR1800: + case CX23885_BOARD_HAUPPAUGE_HVR1200: + case CX23885_BOARD_HAUPPAUGE_HVR1400: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1210: + /* FIXME: Implement me */ + break; + case CX23885_BOARD_HAUPPAUGE_HVR1270: + ret = cx23888_ir_probe(dev); + if (ret) + break; + dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_888_IR); + v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config, + ir_rx_pin_cfg_count, ir_rx_pin_cfg); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + ret = cx23888_ir_probe(dev); + if (ret) + break; + dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_888_IR); + v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config, + ir_rxtx_pin_cfg_count, ir_rxtx_pin_cfg); + /* + * For these boards we need to invert the Tx output via the + * IR controller to have the LED off while idle + */ + v4l2_subdev_call(dev->sd_ir, ir, tx_g_parameters, ¶ms); + params.enable = false; + params.shutdown = false; + params.invert_level = true; + v4l2_subdev_call(dev->sd_ir, ir, tx_s_parameters, ¶ms); + params.shutdown = true; + v4l2_subdev_call(dev->sd_ir, ir, tx_s_parameters, ¶ms); + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + if (!enable_885_ir) + break; + dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_AV_CORE); + if (dev->sd_ir == NULL) { + ret = -ENODEV; + break; + } + v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config, + ir_rx_pin_cfg_count, ir_rx_pin_cfg); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1250: + if (!enable_885_ir) + break; + dev->sd_ir = cx23885_find_hw(dev, CX23885_HW_AV_CORE); + if (dev->sd_ir == NULL) { + ret = -ENODEV; + break; + } + v4l2_subdev_call(dev->sd_cx25840, core, s_io_pin_config, + ir_rxtx_pin_cfg_count, ir_rxtx_pin_cfg); + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: + request_module("ir-kbd-i2c"); + break; + } + + return ret; +} + +void cx23885_ir_fini(struct cx23885_dev *dev) +{ + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + cx23885_irq_remove(dev, PCI_MSK_IR); + cx23888_ir_remove(dev); + dev->sd_ir = NULL; + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + cx23885_irq_remove(dev, PCI_MSK_AV_CORE); + /* sd_ir is a duplicate pointer to the AV Core, just clear it */ + dev->sd_ir = NULL; + break; + } +} + +int netup_jtag_io(void *device, int tms, int tdi, int read_tdo) +{ + int data; + int tdo = 0; + struct cx23885_dev *dev = (struct cx23885_dev *)device; + /*TMS*/ + data = ((cx_read(GP0_IO)) & (~0x00000002)); + data |= (tms ? 0x00020002 : 0x00020000); + cx_write(GP0_IO, data); + + /*TDI*/ + data = ((cx_read(MC417_RWD)) & (~0x0000a000)); + data |= (tdi ? 0x00008000 : 0); + cx_write(MC417_RWD, data); + if (read_tdo) + tdo = (data & 0x00004000) ? 1 : 0; /*TDO*/ + + cx_write(MC417_RWD, data | 0x00002000); + udelay(1); + /*TCK*/ + cx_write(MC417_RWD, data); + + return tdo; +} + +void cx23885_ir_pci_int_enable(struct cx23885_dev *dev) +{ + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + if (dev->sd_ir) + cx23885_irq_add_enable(dev, PCI_MSK_IR); + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + if (dev->sd_ir) + cx23885_irq_add_enable(dev, PCI_MSK_AV_CORE); + break; + } +} + +void cx23885_card_setup(struct cx23885_dev *dev) +{ + struct cx23885_tsport *ts1 = &dev->ts1; + struct cx23885_tsport *ts2 = &dev->ts2; + + static u8 eeprom[256]; + + if (dev->i2c_bus[0].i2c_rc == 0) { + dev->i2c_bus[0].i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&dev->i2c_bus[0].i2c_client, + eeprom, sizeof(eeprom)); + } + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1250: + if (dev->i2c_bus[0].i2c_rc == 0) { + if (eeprom[0x80] != 0x84) + hauppauge_eeprom(dev, eeprom+0xc0); + else + hauppauge_eeprom(dev, eeprom+0x80); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + case CX23885_BOARD_HAUPPAUGE_HVR1400: + if (dev->i2c_bus[0].i2c_rc == 0) + hauppauge_eeprom(dev, eeprom+0x80); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1800: + case CX23885_BOARD_HAUPPAUGE_HVR1800lp: + case CX23885_BOARD_HAUPPAUGE_HVR1200: + case CX23885_BOARD_HAUPPAUGE_HVR1700: + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1210: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + if (dev->i2c_bus[0].i2c_rc == 0) + hauppauge_eeprom(dev, eeprom+0xc0); + break; + } + + switch (dev->board) { + case CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP: + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + /* break omitted intentionally */ + case CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP: + ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1800: + /* Defaults for VID B - Analog encoder */ + /* DREQ_POL, SMODE, PUNC_CLK, MCLK_POL Serial bus + punc clk */ + ts1->gen_ctrl_val = 0x10e; + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + + /* APB_TSVALERR_POL (active low)*/ + ts1->vld_misc_val = 0x2000; + ts1->hw_sop_ctrl_val = (0x47 << 16 | 188 << 4 | 0xc); + cx_write(0x130184, 0xc); + + /* Defaults for VID C */ + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_TBS_6920: + ts1->gen_ctrl_val = 0x4; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_TEVII_S471: + case CX23885_BOARD_DVBWORLD_2005: + case CX23885_BOARD_PROF_8000: + ts1->gen_ctrl_val = 0x5; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_MYGICA_X8506: + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + ts1->gen_ctrl_val = 0x5; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_MYGICA_X8558PRO: + ts1->gen_ctrl_val = 0x5; /* Parallel */ + ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts1->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + break; + case CX23885_BOARD_HAUPPAUGE_HVR1250: + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + case CX23885_BOARD_HAUPPAUGE_HVR1800lp: + case CX23885_BOARD_HAUPPAUGE_HVR1200: + case CX23885_BOARD_HAUPPAUGE_HVR1700: + case CX23885_BOARD_HAUPPAUGE_HVR1400: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1210: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_GOTVIEW_X5_3D_HYBRID: + default: + ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ + ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ + ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; + } + + /* Certain boards support analog, or require the avcore to be + * loaded, ensure this happens. + */ + switch (dev->board) { + case CX23885_BOARD_TEVII_S470: + /* Currently only enabled for the integrated IR controller */ + if (!enable_885_ir) + break; + case CX23885_BOARD_HAUPPAUGE_HVR1250: + case CX23885_BOARD_HAUPPAUGE_HVR1800: + case CX23885_BOARD_HAUPPAUGE_HVR1800lp: + case CX23885_BOARD_HAUPPAUGE_HVR1700: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_MYGICA_X8506: + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: + case CX23885_BOARD_GOTVIEW_X5_3D_HYBRID: + case CX23885_BOARD_HAUPPAUGE_HVR1500: + case CX23885_BOARD_MPX885: + case CX23885_BOARD_MYGICA_X8507: + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + dev->sd_cx25840 = v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_bus[2].i2c_adap, + "cx25840", 0x88 >> 1, NULL); + if (dev->sd_cx25840) { + dev->sd_cx25840->grp_id = CX23885_HW_AV_CORE; + v4l2_subdev_call(dev->sd_cx25840, core, load_fw); + } + break; + } + + /* AUX-PLL 27MHz CLK */ + switch (dev->board) { + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + netup_initialize(dev); + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: { + int ret; + const struct firmware *fw; + const char *filename = "dvb-netup-altera-01.fw"; + char *action = "configure"; + static struct netup_card_info cinfo; + struct altera_config netup_config = { + .dev = dev, + .action = action, + .jtag_io = netup_jtag_io, + }; + + netup_initialize(dev); + + netup_get_card_info(&dev->i2c_bus[0].i2c_adap, &cinfo); + if (netup_card_rev) + cinfo.rev = netup_card_rev; + + switch (cinfo.rev) { + case 0x4: + filename = "dvb-netup-altera-04.fw"; + break; + default: + filename = "dvb-netup-altera-01.fw"; + break; + } + printk(KERN_INFO "NetUP card rev=0x%x fw_filename=%s\n", + cinfo.rev, filename); + + ret = request_firmware(&fw, filename, &dev->pci->dev); + if (ret != 0) + printk(KERN_ERR "did not find the firmware file. (%s) " + "Please see linux/Documentation/dvb/ for more details " + "on firmware-problems.", filename); + else + altera_init(&netup_config, fw); + + release_firmware(fw); + break; + } + } +} + +/* ------------------------------------------------------------------ */ diff --git a/drivers/media/video/cx23885/cx23885-core.c b/drivers/media/pci/cx23885/cx23885-core.c similarity index 100% rename from drivers/media/video/cx23885/cx23885-core.c rename to drivers/media/pci/cx23885/cx23885-core.c diff --git a/drivers/media/pci/cx23885/cx23885-dvb.c b/drivers/media/pci/cx23885/cx23885-dvb.c new file mode 100644 index 000000000000..4379d8a6dad5 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-dvb.c @@ -0,0 +1,1412 @@ +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2006 Steven Toth + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cx23885.h" +#include + +#include "dvb_ca_en50221.h" +#include "s5h1409.h" +#include "s5h1411.h" +#include "mt2131.h" +#include "tda8290.h" +#include "tda18271.h" +#include "lgdt330x.h" +#include "xc4000.h" +#include "xc5000.h" +#include "max2165.h" +#include "tda10048.h" +#include "tuner-xc2028.h" +#include "tuner-simple.h" +#include "dib7000p.h" +#include "dibx000_common.h" +#include "zl10353.h" +#include "stv0900.h" +#include "stv0900_reg.h" +#include "stv6110.h" +#include "lnbh24.h" +#include "cx24116.h" +#include "cimax2.h" +#include "lgs8gxx.h" +#include "netup-eeprom.h" +#include "netup-init.h" +#include "lgdt3305.h" +#include "atbm8830.h" +#include "ds3000.h" +#include "cx23885-f300.h" +#include "altera-ci.h" +#include "stv0367.h" +#include "drxk.h" +#include "mt2063.h" +#include "stv090x.h" +#include "stb6100.h" +#include "stb6100_cfg.h" + +static unsigned int debug; + +#define dprintk(level, fmt, arg...)\ + do { if (debug >= level)\ + printk(KERN_DEBUG "%s/0: " fmt, dev->name, ## arg);\ + } while (0) + +/* ------------------------------------------------------------------ */ + +static unsigned int alt_tuner; +module_param(alt_tuner, int, 0644); +MODULE_PARM_DESC(alt_tuner, "Enable alternate tuner configuration"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +/* ------------------------------------------------------------------ */ + +static int dvb_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct cx23885_tsport *port = q->priv_data; + + port->ts_packet_size = 188 * 4; + port->ts_packet_count = 32; + + *size = port->ts_packet_size * port->ts_packet_count; + *count = 32; + return 0; +} + +static int dvb_buf_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct cx23885_tsport *port = q->priv_data; + return cx23885_buf_prepare(q, port, (struct cx23885_buffer *)vb, field); +} + +static void dvb_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx23885_tsport *port = q->priv_data; + cx23885_buf_queue(port, (struct cx23885_buffer *)vb); +} + +static void dvb_buf_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + cx23885_free_buffer(q, (struct cx23885_buffer *)vb); +} + +static int cx23885_dvb_set_frontend(struct dvb_frontend *fe); + +static void cx23885_dvb_gate_ctrl(struct cx23885_tsport *port, int open) +{ + struct videobuf_dvb_frontends *f; + struct videobuf_dvb_frontend *fe; + + f = &port->frontends; + + if (f->gate <= 1) /* undefined or fe0 */ + fe = videobuf_dvb_get_frontend(f, 1); + else + fe = videobuf_dvb_get_frontend(f, f->gate); + + if (fe && fe->dvb.frontend && fe->dvb.frontend->ops.i2c_gate_ctrl) + fe->dvb.frontend->ops.i2c_gate_ctrl(fe->dvb.frontend, open); + + /* + * FIXME: Improve this path to avoid calling the + * cx23885_dvb_set_frontend() every time it passes here. + */ + cx23885_dvb_set_frontend(fe->dvb.frontend); +} + +static struct videobuf_queue_ops dvb_qops = { + .buf_setup = dvb_buf_setup, + .buf_prepare = dvb_buf_prepare, + .buf_queue = dvb_buf_queue, + .buf_release = dvb_buf_release, +}; + +static struct s5h1409_config hauppauge_generic_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static struct tda10048_config hauppauge_hvr1200_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_SERIAL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3800, + .dtv8_if_freq_khz = TDA10048_IF_4300, + .clk_freq_khz = TDA10048_CLK_16000, +}; + +static struct tda10048_config hauppauge_hvr1210_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_SERIAL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3500, + .dtv8_if_freq_khz = TDA10048_IF_4000, + .clk_freq_khz = TDA10048_CLK_16000, +}; + +static struct s5h1409_config hauppauge_ezqam_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .qam_if = 4000, + .inversion = S5H1409_INVERSION_ON, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1409_config hauppauge_hvr1800lp_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1409_config hauppauge_hvr1500_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static struct mt2131_config hauppauge_generic_tunerconfig = { + 0x61 +}; + +static struct lgdt330x_config fusionhdtv_5_express = { + .demod_address = 0x0e, + .demod_chip = LGDT3303, + .serial_mpeg = 0x40, +}; + +static struct s5h1409_config hauppauge_hvr1500q_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1409_config dvico_s5h1409_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1411_config dvico_s5h1411_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_ON, + .qam_if = S5H1411_IF_44000, + .vsb_if = S5H1411_IF_44000, + .inversion = S5H1411_INVERSION_OFF, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static struct s5h1411_config hcw_s5h1411_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .vsb_if = S5H1411_IF_44000, + .qam_if = S5H1411_IF_4000, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static struct xc5000_config hauppauge_hvr1500q_tunerconfig = { + .i2c_address = 0x61, + .if_khz = 5380, +}; + +static struct xc5000_config dvico_xc5000_tunerconfig = { + .i2c_address = 0x64, + .if_khz = 5380, +}; + +static struct tda829x_config tda829x_no_probe = { + .probe_tuner = TDA829X_DONT_PROBE, +}; + +static struct tda18271_std_map hauppauge_tda18271_std_map = { + .atsc_6 = { .if_freq = 5380, .agc_mode = 3, .std = 3, + .if_lvl = 6, .rfagc_top = 0x37 }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0, + .if_lvl = 6, .rfagc_top = 0x37 }, +}; + +static struct tda18271_std_map hauppauge_hvr1200_tda18271_std_map = { + .dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_7 = { .if_freq = 3800, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_8 = { .if_freq = 4300, .agc_mode = 3, .std = 6, + .if_lvl = 1, .rfagc_top = 0x37, }, +}; + +static struct tda18271_config hauppauge_tda18271_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda18271_config hauppauge_hvr1200_tuner_config = { + .std_map = &hauppauge_hvr1200_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda18271_config hauppauge_hvr1210_tuner_config = { + .gate = TDA18271_GATE_DIGITAL, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda18271_std_map hauppauge_hvr127x_std_map = { + .atsc_6 = { .if_freq = 3250, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x58 }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x58 }, +}; + +static struct tda18271_config hauppauge_hvr127x_config = { + .std_map = &hauppauge_hvr127x_std_map, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct lgdt3305_config hauppauge_lgdt3305_config = { + .i2c_addr = 0x0e, + .mpeg_mode = LGDT3305_MPEG_SERIAL, + .tpclk_edge = LGDT3305_TPCLK_FALLING_EDGE, + .tpvalid_polarity = LGDT3305_TP_VALID_HIGH, + .deny_i2c_rptr = 1, + .spectral_inversion = 1, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, +}; + +static struct dibx000_agc_config xc3028_agc_config = { + BAND_VHF | BAND_UHF, /* band_caps */ + + /* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=0, + * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, + * P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, + * P_agc_nb_est=2, P_agc_write=0 + */ + (0 << 15) | (0 << 14) | (0 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | + (3 << 5) | (0 << 4) | (2 << 1) | (0 << 0), /* setup */ + + 712, /* inv_gain */ + 21, /* time_stabiliz */ + + 0, /* alpha_level */ + 118, /* thlock */ + + 0, /* wbd_inv */ + 2867, /* wbd_ref */ + 0, /* wbd_sel */ + 2, /* wbd_alpha */ + + 0, /* agc1_max */ + 0, /* agc1_min */ + 39718, /* agc2_max */ + 9930, /* agc2_min */ + 0, /* agc1_pt1 */ + 0, /* agc1_pt2 */ + 0, /* agc1_pt3 */ + 0, /* agc1_slope1 */ + 0, /* agc1_slope2 */ + 0, /* agc2_pt1 */ + 128, /* agc2_pt2 */ + 29, /* agc2_slope1 */ + 29, /* agc2_slope2 */ + + 17, /* alpha_mant */ + 27, /* alpha_exp */ + 23, /* beta_mant */ + 51, /* beta_exp */ + + 1, /* perform_agc_softsplit */ +}; + +/* PLL Configuration for COFDM BW_MHz = 8.000000 + * With external clock = 30.000000 */ +static struct dibx000_bandwidth_config xc3028_bw_config = { + 60000, /* internal */ + 30000, /* sampling */ + 1, /* pll_cfg: prediv */ + 8, /* pll_cfg: ratio */ + 3, /* pll_cfg: range */ + 1, /* pll_cfg: reset */ + 0, /* pll_cfg: bypass */ + 0, /* misc: refdiv */ + 0, /* misc: bypclk_div */ + 1, /* misc: IO_CLK_en_core */ + 1, /* misc: ADClkSrc */ + 0, /* misc: modulo */ + (3 << 14) | (1 << 12) | (524 << 0), /* sad_cfg: refsel, sel, freq_15k */ + (1 << 25) | 5816102, /* ifreq = 5.200000 MHz */ + 20452225, /* timf */ + 30000000 /* xtal_hz */ +}; + +static struct dib7000p_config hauppauge_hvr1400_dib7000_config = { + .output_mpeg2_in_188_bytes = 1, + .hostbus_diversity = 1, + .tuner_is_baseband = 0, + .update_lna = NULL, + + .agc_config_count = 1, + .agc = &xc3028_agc_config, + .bw = &xc3028_bw_config, + + .gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS, + .gpio_val = DIB7000P_GPIO_DEFAULT_VALUES, + .gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS, + + .pwm_freq_div = 0, + .agc_control = NULL, + .spur_protect = 0, + + .output_mode = OUTMODE_MPEG2_SERIAL, +}; + +static struct zl10353_config dvico_fusionhdtv_xc3028 = { + .demod_address = 0x0f, + .if2 = 45600, + .no_tuner = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static struct stv0900_reg stv0900_ts_regs[] = { + { R0900_TSGENERAL, 0x00 }, + { R0900_P1_TSSPEED, 0x40 }, + { R0900_P2_TSSPEED, 0x40 }, + { R0900_P1_TSCFGM, 0xc0 }, + { R0900_P2_TSCFGM, 0xc0 }, + { R0900_P1_TSCFGH, 0xe0 }, + { R0900_P2_TSCFGH, 0xe0 }, + { R0900_P1_TSCFGL, 0x20 }, + { R0900_P2_TSCFGL, 0x20 }, + { 0xffff, 0xff }, /* terminate */ +}; + +static struct stv0900_config netup_stv0900_config = { + .demod_address = 0x68, + .demod_mode = 1, /* dual */ + .xtal = 8000000, + .clkmode = 3,/* 0-CLKI, 2-XTALI, else AUTO */ + .diseqc_mode = 2,/* 2/3 PWM */ + .ts_config_regs = stv0900_ts_regs, + .tun1_maddress = 0,/* 0x60 */ + .tun2_maddress = 3,/* 0x63 */ + .tun1_adc = 1,/* 1 Vpp */ + .tun2_adc = 1,/* 1 Vpp */ +}; + +static struct stv6110_config netup_stv6110_tunerconfig_a = { + .i2c_address = 0x60, + .mclk = 16000000, + .clk_div = 1, + .gain = 8, /* +16 dB - maximum gain */ +}; + +static struct stv6110_config netup_stv6110_tunerconfig_b = { + .i2c_address = 0x63, + .mclk = 16000000, + .clk_div = 1, + .gain = 8, /* +16 dB - maximum gain */ +}; + +static struct cx24116_config tbs_cx24116_config = { + .demod_address = 0x55, +}; + +static struct ds3000_config tevii_ds3000_config = { + .demod_address = 0x68, +}; + +static struct cx24116_config dvbworld_cx24116_config = { + .demod_address = 0x05, +}; + +static struct lgs8gxx_config mygica_x8506_lgs8gl5_config = { + .prod = LGS8GXX_PROD_LGS8GL5, + .demod_address = 0x19, + .serial_ts = 0, + .ts_clk_pol = 1, + .ts_clk_gated = 1, + .if_clk_freq = 30400, /* 30.4 MHz */ + .if_freq = 5380, /* 5.38 MHz */ + .if_neg_center = 1, + .ext_adc = 0, + .adc_signed = 0, + .if_neg_edge = 0, +}; + +static struct xc5000_config mygica_x8506_xc5000_config = { + .i2c_address = 0x61, + .if_khz = 5380, +}; + +static struct stv090x_config prof_8000_stv090x_config = { + .device = STV0903, + .demod_mode = STV090x_SINGLE, + .clk_mode = STV090x_CLK_EXT, + .xtal = 27000000, + .address = 0x6A, + .ts1_mode = STV090x_TSMODE_PARALLEL_PUNCTURED, + .repeater_level = STV090x_RPTLEVEL_64, + .adc1_range = STV090x_ADC_2Vpp, + .diseqc_envelope_mode = false, + + .tuner_get_frequency = stb6100_get_frequency, + .tuner_set_frequency = stb6100_set_frequency, + .tuner_set_bandwidth = stb6100_set_bandwidth, + .tuner_get_bandwidth = stb6100_get_bandwidth, +}; + +static struct stb6100_config prof_8000_stb6100_config = { + .tuner_address = 0x60, + .refclock = 27000000, +}; + +static int p8000_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) +{ + struct cx23885_tsport *port = fe->dvb->priv; + struct cx23885_dev *dev = port->dev; + + if (voltage == SEC_VOLTAGE_18) + cx_write(MC417_RWD, 0x00001e00); + else if (voltage == SEC_VOLTAGE_13) + cx_write(MC417_RWD, 0x00001a00); + else + cx_write(MC417_RWD, 0x00001800); + return 0; +} + +static int cx23885_dvb_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct cx23885_tsport *port = fe->dvb->priv; + struct cx23885_dev *dev = port->dev; + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1275: + switch (p->modulation) { + case VSB_8: + cx23885_gpio_clear(dev, GPIO_5); + break; + case QAM_64: + case QAM_256: + default: + cx23885_gpio_set(dev, GPIO_5); + break; + } + break; + case CX23885_BOARD_MYGICA_X8506: + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + /* Select Digital TV */ + cx23885_gpio_set(dev, GPIO_0); + break; + } + return 0; +} + +static struct lgs8gxx_config magicpro_prohdtve2_lgs8g75_config = { + .prod = LGS8GXX_PROD_LGS8G75, + .demod_address = 0x19, + .serial_ts = 0, + .ts_clk_pol = 1, + .ts_clk_gated = 1, + .if_clk_freq = 30400, /* 30.4 MHz */ + .if_freq = 6500, /* 6.50 MHz */ + .if_neg_center = 1, + .ext_adc = 0, + .adc_signed = 1, + .adc_vpp = 2, /* 1.6 Vpp */ + .if_neg_edge = 1, +}; + +static struct xc5000_config magicpro_prohdtve2_xc5000_config = { + .i2c_address = 0x61, + .if_khz = 6500, +}; + +static struct atbm8830_config mygica_x8558pro_atbm8830_cfg1 = { + .prod = ATBM8830_PROD_8830, + .demod_address = 0x44, + .serial_ts = 0, + .ts_sampling_edge = 1, + .ts_clk_gated = 0, + .osc_clk_freq = 30400, /* in kHz */ + .if_freq = 0, /* zero IF */ + .zif_swap_iq = 1, + .agc_min = 0x2E, + .agc_max = 0xFF, + .agc_hold_loop = 0, +}; + +static struct max2165_config mygic_x8558pro_max2165_cfg1 = { + .i2c_address = 0x60, + .osc_clk = 20 +}; + +static struct atbm8830_config mygica_x8558pro_atbm8830_cfg2 = { + .prod = ATBM8830_PROD_8830, + .demod_address = 0x44, + .serial_ts = 1, + .ts_sampling_edge = 1, + .ts_clk_gated = 0, + .osc_clk_freq = 30400, /* in kHz */ + .if_freq = 0, /* zero IF */ + .zif_swap_iq = 1, + .agc_min = 0x2E, + .agc_max = 0xFF, + .agc_hold_loop = 0, +}; + +static struct max2165_config mygic_x8558pro_max2165_cfg2 = { + .i2c_address = 0x60, + .osc_clk = 20 +}; +static struct stv0367_config netup_stv0367_config[] = { + { + .demod_address = 0x1c, + .xtal = 27000000, + .if_khz = 4500, + .if_iq_mode = 0, + .ts_mode = 1, + .clk_pol = 0, + }, { + .demod_address = 0x1d, + .xtal = 27000000, + .if_khz = 4500, + .if_iq_mode = 0, + .ts_mode = 1, + .clk_pol = 0, + }, +}; + +static struct xc5000_config netup_xc5000_config[] = { + { + .i2c_address = 0x61, + .if_khz = 4500, + }, { + .i2c_address = 0x64, + .if_khz = 4500, + }, +}; + +static struct drxk_config terratec_drxk_config[] = { + { + .adr = 0x29, + .no_i2c_bridge = 1, + }, { + .adr = 0x2a, + .no_i2c_bridge = 1, + }, +}; + +static struct mt2063_config terratec_mt2063_config[] = { + { + .tuner_address = 0x60, + }, { + .tuner_address = 0x67, + }, +}; + +int netup_altera_fpga_rw(void *device, int flag, int data, int read) +{ + struct cx23885_dev *dev = (struct cx23885_dev *)device; + unsigned long timeout = jiffies + msecs_to_jiffies(1); + uint32_t mem = 0; + + mem = cx_read(MC417_RWD); + if (read) + cx_set(MC417_OEN, ALT_DATA); + else { + cx_clear(MC417_OEN, ALT_DATA);/* D0-D7 out */ + mem &= ~ALT_DATA; + mem |= (data & ALT_DATA); + } + + if (flag) + mem |= ALT_AD_RG; + else + mem &= ~ALT_AD_RG; + + mem &= ~ALT_CS; + if (read) + mem = (mem & ~ALT_RD) | ALT_WR; + else + mem = (mem & ~ALT_WR) | ALT_RD; + + cx_write(MC417_RWD, mem); /* start RW cycle */ + + for (;;) { + mem = cx_read(MC417_RWD); + if ((mem & ALT_RDY) == 0) + break; + if (time_after(jiffies, timeout)) + break; + udelay(1); + } + + cx_set(MC417_RWD, ALT_RD | ALT_WR | ALT_CS); + if (read) + return mem & ALT_DATA; + + return 0; +}; + +static int dvb_register(struct cx23885_tsport *port) +{ + struct cx23885_dev *dev = port->dev; + struct cx23885_i2c *i2c_bus = NULL, *i2c_bus2 = NULL; + struct videobuf_dvb_frontend *fe0, *fe1 = NULL; + int mfe_shared = 0; /* bus not shared by default */ + int ret; + + /* Get the first frontend */ + fe0 = videobuf_dvb_get_frontend(&port->frontends, 1); + if (!fe0) + return -EINVAL; + + /* init struct videobuf_dvb */ + fe0->dvb.name = dev->name; + + /* multi-frontend gate control is undefined or defaults to fe0 */ + port->frontends.gate = 0; + + /* Sets the gate control callback to be used by i2c command calls */ + port->gate_ctrl = cx23885_dvb_gate_ctrl; + + /* init frontend */ + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1250: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &hauppauge_generic_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(mt2131_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &hauppauge_generic_tunerconfig, 0); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1275: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(lgdt3305_attach, + &hauppauge_lgdt3305_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_hvr127x_config); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &hcw_s5h1411_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_tda18271_config); + } + + tda18271_attach(&dev->ts1.analog_fe, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_tda18271_config); + + break; + case CX23885_BOARD_HAUPPAUGE_HVR1800: + i2c_bus = &dev->i2c_bus[0]; + switch (alt_tuner) { + case 1: + fe0->dvb.frontend = + dvb_attach(s5h1409_attach, + &hauppauge_ezqam_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_bus[1].i2c_adap, 0x42, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_tda18271_config); + } + break; + case 0: + default: + fe0->dvb.frontend = + dvb_attach(s5h1409_attach, + &hauppauge_generic_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) + dvb_attach(mt2131_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &hauppauge_generic_tunerconfig, 0); + break; + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1800lp: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &hauppauge_hvr1800lp_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(mt2131_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &hauppauge_generic_tunerconfig, 0); + } + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_5_express, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, 0x61, + TUNER_LG_TDVS_H06XF); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500Q: + i2c_bus = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &hauppauge_hvr1500q_config, + &dev->i2c_bus[0].i2c_adap); + if (fe0->dvb.frontend != NULL) + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &hauppauge_hvr1500q_tunerconfig); + break; + case CX23885_BOARD_HAUPPAUGE_HVR1500: + i2c_bus = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &hauppauge_hvr1500_config, + &dev->i2c_bus[0].i2c_adap); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &i2c_bus->i2c_adap, + .i2c_addr = 0x61, + }; + static struct xc2028_ctrl ctl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_OREN538, + }; + + fe = dvb_attach(xc2028_attach, + fe0->dvb.frontend, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1200: + case CX23885_BOARD_HAUPPAUGE_HVR1700: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &hauppauge_hvr1200_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_bus[1].i2c_adap, 0x42, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_hvr1200_tuner_config); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1210: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &hauppauge_hvr1210_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_hvr1210_tuner_config); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1400: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(dib7000p_attach, + &i2c_bus->i2c_adap, + 0x12, &hauppauge_hvr1400_dib7000_config); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &dev->i2c_bus[1].i2c_adap, + .i2c_addr = 0x64, + }; + static struct xc2028_ctrl ctl = { + .fname = XC3028L_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_DIBCOM52, + /* This is true for all demods with + v36 firmware? */ + .type = XC2028_D2633, + }; + + fe = dvb_attach(xc2028_attach, + fe0->dvb.frontend, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP: + i2c_bus = &dev->i2c_bus[port->nr - 1]; + + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &dvico_s5h1409_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend == NULL) + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &dvico_s5h1411_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &dvico_xc5000_tunerconfig); + break; + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: { + i2c_bus = &dev->i2c_bus[port->nr - 1]; + + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_xc3028, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &i2c_bus->i2c_adap, + .i2c_addr = 0x61, + }; + static struct xc2028_ctrl ctl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + }; + + fe = dvb_attach(xc2028_attach, fe0->dvb.frontend, + &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + } + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + i2c_bus = &dev->i2c_bus[0]; + + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_xc3028, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &dev->i2c_bus[1].i2c_adap, + .i2c_addr = 0x61, + }; + static struct xc2028_ctrl ctl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .demod = XC3028_FE_ZARLINK456, + }; + + fe = dvb_attach(xc2028_attach, fe0->dvb.frontend, + &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + i2c_bus = &dev->i2c_bus[0]; + + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_xc3028, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc4000_config cfg = { + .i2c_address = 0x61, + .default_pm = 0, + .dvb_amplitude = 134, + .set_smoothedcvbs = 1, + .if_khz = 4560 + }; + + fe = dvb_attach(xc4000_attach, fe0->dvb.frontend, + &dev->i2c_bus[1].i2c_adap, &cfg); + if (!fe) { + printk(KERN_ERR "%s/2: xc4000 attach failed\n", + dev->name); + goto frontend_detach; + } + } + break; + case CX23885_BOARD_TBS_6920: + i2c_bus = &dev->i2c_bus[1]; + + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &tbs_cx24116_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) + fe0->dvb.frontend->ops.set_voltage = f300_set_voltage; + + break; + case CX23885_BOARD_TEVII_S470: + i2c_bus = &dev->i2c_bus[1]; + + fe0->dvb.frontend = dvb_attach(ds3000_attach, + &tevii_ds3000_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) + fe0->dvb.frontend->ops.set_voltage = f300_set_voltage; + + break; + case CX23885_BOARD_DVBWORLD_2005: + i2c_bus = &dev->i2c_bus[1]; + + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &dvbworld_cx24116_config, + &i2c_bus->i2c_adap); + break; + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + i2c_bus = &dev->i2c_bus[0]; + switch (port->nr) { + /* port B */ + case 1: + fe0->dvb.frontend = dvb_attach(stv0900_attach, + &netup_stv0900_config, + &i2c_bus->i2c_adap, 0); + if (fe0->dvb.frontend != NULL) { + if (dvb_attach(stv6110_attach, + fe0->dvb.frontend, + &netup_stv6110_tunerconfig_a, + &i2c_bus->i2c_adap)) { + if (!dvb_attach(lnbh24_attach, + fe0->dvb.frontend, + &i2c_bus->i2c_adap, + LNBH24_PCL | LNBH24_TTX, + LNBH24_TEN, 0x09)) + printk(KERN_ERR + "No LNBH24 found!\n"); + + } + } + break; + /* port C */ + case 2: + fe0->dvb.frontend = dvb_attach(stv0900_attach, + &netup_stv0900_config, + &i2c_bus->i2c_adap, 1); + if (fe0->dvb.frontend != NULL) { + if (dvb_attach(stv6110_attach, + fe0->dvb.frontend, + &netup_stv6110_tunerconfig_b, + &i2c_bus->i2c_adap)) { + if (!dvb_attach(lnbh24_attach, + fe0->dvb.frontend, + &i2c_bus->i2c_adap, + LNBH24_PCL | LNBH24_TTX, + LNBH24_TEN, 0x0a)) + printk(KERN_ERR + "No LNBH24 found!\n"); + + } + } + break; + } + break; + case CX23885_BOARD_MYGICA_X8506: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(lgs8gxx_attach, + &mygica_x8506_lgs8gl5_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(xc5000_attach, + fe0->dvb.frontend, + &i2c_bus2->i2c_adap, + &mygica_x8506_xc5000_config); + } + break; + case CX23885_BOARD_MAGICPRO_PROHDTVE2: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(lgs8gxx_attach, + &magicpro_prohdtve2_lgs8g75_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(xc5000_attach, + fe0->dvb.frontend, + &i2c_bus2->i2c_adap, + &magicpro_prohdtve2_xc5000_config); + } + break; + case CX23885_BOARD_HAUPPAUGE_HVR1850: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &hcw_s5h1411_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[0].i2c_adap, + &hauppauge_tda18271_config); + + tda18271_attach(&dev->ts1.analog_fe, + 0x60, &dev->i2c_bus[1].i2c_adap, + &hauppauge_tda18271_config); + + break; + case CX23885_BOARD_HAUPPAUGE_HVR1290: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &hcw_s5h1411_config, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_bus[0].i2c_adap, + &hauppauge_tda18271_config); + break; + case CX23885_BOARD_MYGICA_X8558PRO: + switch (port->nr) { + /* port B */ + case 1: + i2c_bus = &dev->i2c_bus[0]; + fe0->dvb.frontend = dvb_attach(atbm8830_attach, + &mygica_x8558pro_atbm8830_cfg1, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(max2165_attach, + fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &mygic_x8558pro_max2165_cfg1); + } + break; + /* port C */ + case 2: + i2c_bus = &dev->i2c_bus[1]; + fe0->dvb.frontend = dvb_attach(atbm8830_attach, + &mygica_x8558pro_atbm8830_cfg2, + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(max2165_attach, + fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &mygic_x8558pro_max2165_cfg2); + } + break; + } + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + i2c_bus = &dev->i2c_bus[0]; + mfe_shared = 1;/* MFE */ + port->frontends.gate = 0;/* not clear for me yet */ + /* ports B, C */ + /* MFE frontend 1 DVB-T */ + fe0->dvb.frontend = dvb_attach(stv0367ter_attach, + &netup_stv0367_config[port->nr - 1], + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (NULL == dvb_attach(xc5000_attach, + fe0->dvb.frontend, + &i2c_bus->i2c_adap, + &netup_xc5000_config[port->nr - 1])) + goto frontend_detach; + /* load xc5000 firmware */ + fe0->dvb.frontend->ops.tuner_ops.init(fe0->dvb.frontend); + } + /* MFE frontend 2 */ + fe1 = videobuf_dvb_get_frontend(&port->frontends, 2); + if (fe1 == NULL) + goto frontend_detach; + /* DVB-C init */ + fe1->dvb.frontend = dvb_attach(stv0367cab_attach, + &netup_stv0367_config[port->nr - 1], + &i2c_bus->i2c_adap); + if (fe1->dvb.frontend != NULL) { + fe1->dvb.frontend->id = 1; + if (NULL == dvb_attach(xc5000_attach, + fe1->dvb.frontend, + &i2c_bus->i2c_adap, + &netup_xc5000_config[port->nr - 1])) + goto frontend_detach; + } + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + i2c_bus = &dev->i2c_bus[0]; + i2c_bus2 = &dev->i2c_bus[1]; + + switch (port->nr) { + /* port b */ + case 1: + fe0->dvb.frontend = dvb_attach(drxk_attach, + &terratec_drxk_config[0], + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(mt2063_attach, + fe0->dvb.frontend, + &terratec_mt2063_config[0], + &i2c_bus2->i2c_adap)) + goto frontend_detach; + } + break; + /* port c */ + case 2: + fe0->dvb.frontend = dvb_attach(drxk_attach, + &terratec_drxk_config[1], + &i2c_bus->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(mt2063_attach, + fe0->dvb.frontend, + &terratec_mt2063_config[1], + &i2c_bus2->i2c_adap)) + goto frontend_detach; + } + break; + } + break; + case CX23885_BOARD_TEVII_S471: + i2c_bus = &dev->i2c_bus[1]; + + fe0->dvb.frontend = dvb_attach(ds3000_attach, + &tevii_ds3000_config, + &i2c_bus->i2c_adap); + break; + case CX23885_BOARD_PROF_8000: + i2c_bus = &dev->i2c_bus[0]; + + fe0->dvb.frontend = dvb_attach(stv090x_attach, + &prof_8000_stv090x_config, + &i2c_bus->i2c_adap, + STV090x_DEMODULATOR_0); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(stb6100_attach, + fe0->dvb.frontend, + &prof_8000_stb6100_config, + &i2c_bus->i2c_adap)) + goto frontend_detach; + + fe0->dvb.frontend->ops.set_voltage = p8000_set_voltage; + } + break; + default: + printk(KERN_INFO "%s: The frontend of your DVB/ATSC card " + " isn't supported yet\n", + dev->name); + break; + } + + if ((NULL == fe0->dvb.frontend) || (fe1 && NULL == fe1->dvb.frontend)) { + printk(KERN_ERR "%s: frontend initialization failed\n", + dev->name); + goto frontend_detach; + } + + /* define general-purpose callback pointer */ + fe0->dvb.frontend->callback = cx23885_tuner_callback; + if (fe1) + fe1->dvb.frontend->callback = cx23885_tuner_callback; +#if 0 + /* Ensure all frontends negotiate bus access */ + fe0->dvb.frontend->ops.ts_bus_ctrl = cx23885_dvb_bus_ctrl; + if (fe1) + fe1->dvb.frontend->ops.ts_bus_ctrl = cx23885_dvb_bus_ctrl; +#endif + + /* Put the analog decoder in standby to keep it quiet */ + call_all(dev, core, s_power, 0); + + if (fe0->dvb.frontend->ops.analog_ops.standby) + fe0->dvb.frontend->ops.analog_ops.standby(fe0->dvb.frontend); + + /* register everything */ + ret = videobuf_dvb_register_bus(&port->frontends, THIS_MODULE, port, + &dev->pci->dev, adapter_nr, mfe_shared); + if (ret) + goto frontend_detach; + + /* init CI & MAC */ + switch (dev->board) { + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: { + static struct netup_card_info cinfo; + + netup_get_card_info(&dev->i2c_bus[0].i2c_adap, &cinfo); + memcpy(port->frontends.adapter.proposed_mac, + cinfo.port[port->nr - 1].mac, 6); + printk(KERN_INFO "NetUP Dual DVB-S2 CI card port%d MAC=%pM\n", + port->nr, port->frontends.adapter.proposed_mac); + + netup_ci_init(port); + break; + } + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: { + struct altera_ci_config netup_ci_cfg = { + .dev = dev,/* magic number to identify*/ + .adapter = &port->frontends.adapter,/* for CI */ + .demux = &fe0->dvb.demux,/* for hw pid filter */ + .fpga_rw = netup_altera_fpga_rw, + }; + + altera_ci_init(&netup_ci_cfg, port->nr); + break; + } + case CX23885_BOARD_TEVII_S470: { + u8 eeprom[256]; /* 24C02 i2c eeprom */ + + if (port->nr != 1) + break; + + /* Read entire EEPROM */ + dev->i2c_bus[0].i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&dev->i2c_bus[0].i2c_client, eeprom, sizeof(eeprom)); + printk(KERN_INFO "TeVii S470 MAC= %pM\n", eeprom + 0xa0); + memcpy(port->frontends.adapter.proposed_mac, eeprom + 0xa0, 6); + break; + } + } + + return ret; + +frontend_detach: + port->gate_ctrl = NULL; + videobuf_dvb_dealloc_frontends(&port->frontends); + return -EINVAL; +} + +int cx23885_dvb_register(struct cx23885_tsport *port) +{ + + struct videobuf_dvb_frontend *fe0; + struct cx23885_dev *dev = port->dev; + int err, i; + + /* Here we need to allocate the correct number of frontends, + * as reflected in the cards struct. The reality is that currently + * no cx23885 boards support this - yet. But, if we don't modify this + * code then the second frontend would never be allocated (later) + * and fail with error before the attach in dvb_register(). + * Without these changes we risk an OOPS later. The changes here + * are for safety, and should provide a good foundation for the + * future addition of any multi-frontend cx23885 based boards. + */ + printk(KERN_INFO "%s() allocating %d frontend(s)\n", __func__, + port->num_frontends); + + for (i = 1; i <= port->num_frontends; i++) { + if (videobuf_dvb_alloc_frontend( + &port->frontends, i) == NULL) { + printk(KERN_ERR "%s() failed to alloc\n", __func__); + return -ENOMEM; + } + + fe0 = videobuf_dvb_get_frontend(&port->frontends, i); + if (!fe0) + err = -EINVAL; + + dprintk(1, "%s\n", __func__); + dprintk(1, " ->probed by Card=%d Name=%s, PCI %02x:%02x\n", + dev->board, + dev->name, + dev->pci_bus, + dev->pci_slot); + + err = -ENODEV; + + /* dvb stuff */ + /* We have to init the queue for each frontend on a port. */ + printk(KERN_INFO "%s: cx23885 based dvb card\n", dev->name); + videobuf_queue_sg_init(&fe0->dvb.dvbq, &dvb_qops, + &dev->pci->dev, &port->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_TOP, + sizeof(struct cx23885_buffer), port, NULL); + } + err = dvb_register(port); + if (err != 0) + printk(KERN_ERR "%s() dvb_register failed err = %d\n", + __func__, err); + + return err; +} + +int cx23885_dvb_unregister(struct cx23885_tsport *port) +{ + struct videobuf_dvb_frontend *fe0; + + /* FIXME: in an error condition where the we have + * an expected number of frontends (attach problem) + * then this might not clean up correctly, if 1 + * is invalid. + * This comment only applies to future boards IF they + * implement MFE support. + */ + fe0 = videobuf_dvb_get_frontend(&port->frontends, 1); + if (fe0 && fe0->dvb.frontend) + videobuf_dvb_unregister_bus(&port->frontends); + + switch (port->dev->board) { + case CX23885_BOARD_NETUP_DUAL_DVBS2_CI: + netup_ci_exit(port); + break; + case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: + altera_ci_release(port->dev, port->nr); + break; + } + + port->gate_ctrl = NULL; + + return 0; +} + diff --git a/drivers/media/video/cx23885/cx23885-f300.c b/drivers/media/pci/cx23885/cx23885-f300.c similarity index 100% rename from drivers/media/video/cx23885/cx23885-f300.c rename to drivers/media/pci/cx23885/cx23885-f300.c diff --git a/drivers/media/video/cx23885/cx23885-f300.h b/drivers/media/pci/cx23885/cx23885-f300.h similarity index 100% rename from drivers/media/video/cx23885/cx23885-f300.h rename to drivers/media/pci/cx23885/cx23885-f300.h diff --git a/drivers/media/video/cx23885/cx23885-i2c.c b/drivers/media/pci/cx23885/cx23885-i2c.c similarity index 100% rename from drivers/media/video/cx23885/cx23885-i2c.c rename to drivers/media/pci/cx23885/cx23885-i2c.c diff --git a/drivers/media/pci/cx23885/cx23885-input.c b/drivers/media/pci/cx23885/cx23885-input.c new file mode 100644 index 000000000000..2c925f77cf2a --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-input.c @@ -0,0 +1,365 @@ +/* + * Driver for the Conexant CX23885/7/8 PCIe bridge + * + * Infrared remote control input device + * + * Most of this file is + * + * Copyright (C) 2009 Andy Walls + * + * However, the cx23885_input_{init,fini} functions contained herein are + * derived from Linux kernel files linux/media/video/.../...-input.c marked as: + * + * Copyright (C) 2008 + * Copyright (C) 2005 Ludovico Cavedon + * Markus Rechberger + * Mauro Carvalho Chehab + * Sascha Sommer + * Copyright (C) 2004, 2005 Chris Pascoe + * Copyright (C) 2003, 2004 Gerd Knorr + * Copyright (C) 2003 Pavel Machek + * + * 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. + */ + +#include +#include +#include + +#include "cx23885.h" + +#define MODULE_NAME "cx23885" + +static void cx23885_input_process_measurements(struct cx23885_dev *dev, + bool overrun) +{ + struct cx23885_kernel_ir *kernel_ir = dev->kernel_ir; + + ssize_t num; + int count, i; + bool handle = false; + struct ir_raw_event ir_core_event[64]; + + do { + num = 0; + v4l2_subdev_call(dev->sd_ir, ir, rx_read, (u8 *) ir_core_event, + sizeof(ir_core_event), &num); + + count = num / sizeof(struct ir_raw_event); + + for (i = 0; i < count; i++) { + ir_raw_event_store(kernel_ir->rc, + &ir_core_event[i]); + handle = true; + } + } while (num != 0); + + if (overrun) + ir_raw_event_reset(kernel_ir->rc); + else if (handle) + ir_raw_event_handle(kernel_ir->rc); +} + +void cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events) +{ + struct v4l2_subdev_ir_parameters params; + int overrun, data_available; + + if (dev->sd_ir == NULL || events == 0) + return; + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + /* + * The only boards we handle right now. However other boards + * using the CX2388x integrated IR controller should be similar + */ + break; + default: + return; + } + + overrun = events & (V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN | + V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN); + + data_available = events & (V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED | + V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ); + + if (overrun) { + /* If there was a FIFO overrun, stop the device */ + v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); + params.enable = false; + /* Mitigate race with cx23885_input_ir_stop() */ + params.shutdown = atomic_read(&dev->ir_input_stopping); + v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); + } + + if (data_available) + cx23885_input_process_measurements(dev, overrun); + + if (overrun) { + /* If there was a FIFO overrun, clear & restart the device */ + params.enable = true; + /* Mitigate race with cx23885_input_ir_stop() */ + params.shutdown = atomic_read(&dev->ir_input_stopping); + v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); + } +} + +static int cx23885_input_ir_start(struct cx23885_dev *dev) +{ + struct v4l2_subdev_ir_parameters params; + + if (dev->sd_ir == NULL) + return -ENODEV; + + atomic_set(&dev->ir_input_stopping, 0); + + v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + /* + * The IR controller on this board only returns pulse widths. + * Any other mode setting will fail to set up the device. + */ + params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + params.enable = true; + params.interrupt_enable = true; + params.shutdown = false; + + /* Setup for baseband compatible with both RC-5 and RC-6A */ + params.modulation = false; + /* RC-5: 2,222,222 ns = 1/36 kHz * 32 cycles * 2 marks * 1.25*/ + /* RC-6A: 3,333,333 ns = 1/36 kHz * 16 cycles * 6 marks * 1.25*/ + params.max_pulse_width = 3333333; /* ns */ + /* RC-5: 666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */ + /* RC-6A: 333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */ + params.noise_filter_min_width = 333333; /* ns */ + /* + * This board has inverted receive sense: + * mark is received as low logic level; + * falling edges are detected as rising edges; etc. + */ + params.invert_level = true; + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + case CX23885_BOARD_TEVII_S470: + /* + * The IR controller on this board only returns pulse widths. + * Any other mode setting will fail to set up the device. + */ + params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + params.enable = true; + params.interrupt_enable = true; + params.shutdown = false; + + /* Setup for a standard NEC protocol */ + params.carrier_freq = 37917; /* Hz, 455 kHz/12 for NEC */ + params.carrier_range_lower = 33000; /* Hz */ + params.carrier_range_upper = 43000; /* Hz */ + params.duty_cycle = 33; /* percent, 33 percent for NEC */ + + /* + * NEC max pulse width: (64/3)/(455 kHz/12) * 16 nec_units + * (64/3)/(455 kHz/12) * 16 nec_units * 1.375 = 12378022 ns + */ + params.max_pulse_width = 12378022; /* ns */ + + /* + * NEC noise filter min width: (64/3)/(455 kHz/12) * 1 nec_unit + * (64/3)/(455 kHz/12) * 1 nec_units * 0.625 = 351648 ns + */ + params.noise_filter_min_width = 351648; /* ns */ + + params.modulation = false; + params.invert_level = true; + break; + } + v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); + return 0; +} + +static int cx23885_input_ir_open(struct rc_dev *rc) +{ + struct cx23885_kernel_ir *kernel_ir = rc->priv; + + if (kernel_ir->cx == NULL) + return -ENODEV; + + return cx23885_input_ir_start(kernel_ir->cx); +} + +static void cx23885_input_ir_stop(struct cx23885_dev *dev) +{ + struct v4l2_subdev_ir_parameters params; + + if (dev->sd_ir == NULL) + return; + + /* + * Stop the sd_ir subdevice from generating notifications and + * scheduling work. + * It is shutdown this way in order to mitigate a race with + * cx23885_input_rx_work_handler() in the overrun case, which could + * re-enable the subdevice. + */ + atomic_set(&dev->ir_input_stopping, 1); + v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); + while (params.shutdown == false) { + params.enable = false; + params.interrupt_enable = false; + params.shutdown = true; + v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); + v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); + } + flush_work(&dev->cx25840_work); + flush_work(&dev->ir_rx_work); + flush_work(&dev->ir_tx_work); +} + +static void cx23885_input_ir_close(struct rc_dev *rc) +{ + struct cx23885_kernel_ir *kernel_ir = rc->priv; + + if (kernel_ir->cx != NULL) + cx23885_input_ir_stop(kernel_ir->cx); +} + +int cx23885_input_init(struct cx23885_dev *dev) +{ + struct cx23885_kernel_ir *kernel_ir; + struct rc_dev *rc; + char *rc_map; + enum rc_driver_type driver_type; + unsigned long allowed_protos; + + int ret; + + /* + * If the IR device (hardware registers, chip, GPIO lines, etc.) isn't + * encapsulated in a v4l2_subdev, then I'm not going to deal with it. + */ + if (dev->sd_ir == NULL) + return -ENODEV; + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1270: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_HAUPPAUGE_HVR1250: + /* Integrated CX2388[58] IR controller */ + driver_type = RC_DRIVER_IR_RAW; + allowed_protos = RC_TYPE_ALL; + /* The grey Hauppauge RC-5 remote */ + rc_map = RC_MAP_HAUPPAUGE; + break; + case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: + /* Integrated CX23885 IR controller */ + driver_type = RC_DRIVER_IR_RAW; + allowed_protos = RC_TYPE_NEC; + /* The grey Terratec remote with orange buttons */ + rc_map = RC_MAP_NEC_TERRATEC_CINERGY_XS; + break; + case CX23885_BOARD_TEVII_S470: + /* Integrated CX23885 IR controller */ + driver_type = RC_DRIVER_IR_RAW; + allowed_protos = RC_TYPE_ALL; + /* A guess at the remote */ + rc_map = RC_MAP_TEVII_NEC; + break; + default: + return -ENODEV; + } + + /* cx23885 board instance kernel IR state */ + kernel_ir = kzalloc(sizeof(struct cx23885_kernel_ir), GFP_KERNEL); + if (kernel_ir == NULL) + return -ENOMEM; + + kernel_ir->cx = dev; + kernel_ir->name = kasprintf(GFP_KERNEL, "cx23885 IR (%s)", + cx23885_boards[dev->board].name); + kernel_ir->phys = kasprintf(GFP_KERNEL, "pci-%s/ir0", + pci_name(dev->pci)); + + /* input device */ + rc = rc_allocate_device(); + if (!rc) { + ret = -ENOMEM; + goto err_out_free; + } + + kernel_ir->rc = rc; + rc->input_name = kernel_ir->name; + rc->input_phys = kernel_ir->phys; + rc->input_id.bustype = BUS_PCI; + rc->input_id.version = 1; + if (dev->pci->subsystem_vendor) { + rc->input_id.vendor = dev->pci->subsystem_vendor; + rc->input_id.product = dev->pci->subsystem_device; + } else { + rc->input_id.vendor = dev->pci->vendor; + rc->input_id.product = dev->pci->device; + } + rc->dev.parent = &dev->pci->dev; + rc->driver_type = driver_type; + rc->allowed_protos = allowed_protos; + rc->priv = kernel_ir; + rc->open = cx23885_input_ir_open; + rc->close = cx23885_input_ir_close; + rc->map_name = rc_map; + rc->driver_name = MODULE_NAME; + + /* Go */ + dev->kernel_ir = kernel_ir; + ret = rc_register_device(rc); + if (ret) + goto err_out_stop; + + return 0; + +err_out_stop: + cx23885_input_ir_stop(dev); + dev->kernel_ir = NULL; + rc_free_device(rc); +err_out_free: + kfree(kernel_ir->phys); + kfree(kernel_ir->name); + kfree(kernel_ir); + return ret; +} + +void cx23885_input_fini(struct cx23885_dev *dev) +{ + /* Always stop the IR hardware from generating interrupts */ + cx23885_input_ir_stop(dev); + + if (dev->kernel_ir == NULL) + return; + rc_unregister_device(dev->kernel_ir->rc); + kfree(dev->kernel_ir->phys); + kfree(dev->kernel_ir->name); + kfree(dev->kernel_ir); + dev->kernel_ir = NULL; +} diff --git a/drivers/media/video/cx23885/cx23885-input.h b/drivers/media/pci/cx23885/cx23885-input.h similarity index 100% rename from drivers/media/video/cx23885/cx23885-input.h rename to drivers/media/pci/cx23885/cx23885-input.h diff --git a/drivers/media/video/cx23885/cx23885-ioctl.c b/drivers/media/pci/cx23885/cx23885-ioctl.c similarity index 100% rename from drivers/media/video/cx23885/cx23885-ioctl.c rename to drivers/media/pci/cx23885/cx23885-ioctl.c diff --git a/drivers/media/video/cx23885/cx23885-ioctl.h b/drivers/media/pci/cx23885/cx23885-ioctl.h similarity index 100% rename from drivers/media/video/cx23885/cx23885-ioctl.h rename to drivers/media/pci/cx23885/cx23885-ioctl.h diff --git a/drivers/media/video/cx23885/cx23885-ir.c b/drivers/media/pci/cx23885/cx23885-ir.c similarity index 100% rename from drivers/media/video/cx23885/cx23885-ir.c rename to drivers/media/pci/cx23885/cx23885-ir.c diff --git a/drivers/media/video/cx23885/cx23885-ir.h b/drivers/media/pci/cx23885/cx23885-ir.h similarity index 100% rename from drivers/media/video/cx23885/cx23885-ir.h rename to drivers/media/pci/cx23885/cx23885-ir.h diff --git a/drivers/media/video/cx23885/cx23885-reg.h b/drivers/media/pci/cx23885/cx23885-reg.h similarity index 100% rename from drivers/media/video/cx23885/cx23885-reg.h rename to drivers/media/pci/cx23885/cx23885-reg.h diff --git a/drivers/media/video/cx23885/cx23885-vbi.c b/drivers/media/pci/cx23885/cx23885-vbi.c similarity index 100% rename from drivers/media/video/cx23885/cx23885-vbi.c rename to drivers/media/pci/cx23885/cx23885-vbi.c diff --git a/drivers/media/pci/cx23885/cx23885-video.c b/drivers/media/pci/cx23885/cx23885-video.c new file mode 100644 index 000000000000..8c4a9a5f9a50 --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885-video.c @@ -0,0 +1,1926 @@ +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2007 Steven Toth + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx23885.h" +#include +#include +#include "cx23885-ioctl.h" +#include "tuner-xc2028.h" + +#include + +MODULE_DESCRIPTION("v4l2 driver module for cx23885 based TV cards"); +MODULE_AUTHOR("Steven Toth "); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------------ */ + +static unsigned int video_nr[] = {[0 ... (CX23885_MAXBOARDS - 1)] = UNSET }; +static unsigned int vbi_nr[] = {[0 ... (CX23885_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio_nr[] = {[0 ... (CX23885_MAXBOARDS - 1)] = UNSET }; + +module_param_array(video_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr, "video device numbers"); +MODULE_PARM_DESC(vbi_nr, "vbi device numbers"); +MODULE_PARM_DESC(radio_nr, "radio device numbers"); + +static unsigned int video_debug; +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug, "enable debug messages [video]"); + +static unsigned int irq_debug; +module_param(irq_debug, int, 0644); +MODULE_PARM_DESC(irq_debug, "enable debug messages [IRQ handler]"); + +static unsigned int vid_limit = 16; +module_param(vid_limit, int, 0644); +MODULE_PARM_DESC(vid_limit, "capture memory limit in megabytes"); + +#define dprintk(level, fmt, arg...)\ + do { if (video_debug >= level)\ + printk(KERN_DEBUG "%s: " fmt, dev->name, ## arg);\ + } while (0) + +/* ------------------------------------------------------------------- */ +/* static data */ + +#define FORMAT_FLAGS_PACKED 0x01 +#if 0 +static struct cx23885_fmt formats[] = { + { + .name = "8 bpp, gray", + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, +}; +#else +static struct cx23885_fmt formats[] = { + { +#if 0 + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { +#endif + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + } +}; +#endif + +static struct cx23885_fmt *format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) + if (formats[i].fourcc == fourcc) + return formats+i; + + printk(KERN_ERR "%s(%c%c%c%c) NOT FOUND\n", __func__, + (fourcc & 0xff), + ((fourcc >> 8) & 0xff), + ((fourcc >> 16) & 0xff), + ((fourcc >> 24) & 0xff) + ); + return NULL; +} + +/* ------------------------------------------------------------------- */ + +static const struct v4l2_queryctrl no_ctl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; + +static struct cx23885_ctrl cx23885_ctls[] = { + /* --- video --- */ + { + .v = { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0x00, + .maximum = 0xff, + .step = 1, + .default_value = 0x7f, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 128, + .reg = LUMA_CTRL, + .mask = 0x00ff, + .shift = 0, + }, { + .v = { + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 0x7f, + .step = 1, + .default_value = 0x3f, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 0, + .reg = LUMA_CTRL, + .mask = 0xff00, + .shift = 8, + }, { + .v = { + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = -127, + .maximum = 128, + .step = 1, + .default_value = 0x0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 128, + .reg = CHROMA_CTRL, + .mask = 0xff0000, + .shift = 16, + }, { + /* strictly, this only describes only U saturation. + * V saturation is handled specially through code. + */ + .v = { + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 0x7f, + .step = 1, + .default_value = 0x3f, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 0, + .reg = CHROMA_CTRL, + .mask = 0x00ff, + .shift = 0, + }, { + /* --- audio --- */ + .v = { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .default_value = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, + .reg = PATH1_CTL1, + .mask = (0x1f << 24), + .shift = 24, + }, { + .v = { + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = 0, + .maximum = 65535, + .step = 65535 / 100, + .default_value = 65535, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .reg = PATH1_VOL_CTL, + .mask = 0xff, + .shift = 0, + } +}; +static const int CX23885_CTLS = ARRAY_SIZE(cx23885_ctls); + +/* Must be sorted from low to high control ID! */ +static const u32 cx23885_user_ctrls[] = { + V4L2_CID_USER_CLASS, + V4L2_CID_BRIGHTNESS, + V4L2_CID_CONTRAST, + V4L2_CID_SATURATION, + V4L2_CID_HUE, + V4L2_CID_AUDIO_VOLUME, + V4L2_CID_AUDIO_MUTE, + 0 +}; + +static const u32 *ctrl_classes[] = { + cx23885_user_ctrls, + NULL +}; + +void cx23885_video_wakeup(struct cx23885_dev *dev, + struct cx23885_dmaqueue *q, u32 count) +{ + struct cx23885_buffer *buf; + int bc; + + for (bc = 0;; bc++) { + if (list_empty(&q->active)) + break; + buf = list_entry(q->active.next, + struct cx23885_buffer, vb.queue); + + /* count comes from the hw and is is 16bit wide -- + * this trick handles wrap-arounds correctly for + * up to 32767 buffers in flight... */ + if ((s16) (count - buf->count) < 0) + break; + + do_gettimeofday(&buf->vb.ts); + dprintk(2, "[%p/%d] wakeup reg=%d buf=%d\n", buf, buf->vb.i, + count, buf->count); + buf->vb.state = VIDEOBUF_DONE; + list_del(&buf->vb.queue); + wake_up(&buf->vb.done); + } + if (list_empty(&q->active)) + del_timer(&q->timeout); + else + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + if (bc != 1) + printk(KERN_ERR "%s: %d buffers handled (should be 1)\n", + __func__, bc); +} + +int cx23885_set_tvnorm(struct cx23885_dev *dev, v4l2_std_id norm) +{ + dprintk(1, "%s(norm = 0x%08x) name: [%s]\n", + __func__, + (unsigned int)norm, + v4l2_norm_to_name(norm)); + + dev->tvnorm = norm; + + call_all(dev, core, s_std, norm); + + return 0; +} + +static struct video_device *cx23885_vdev_init(struct cx23885_dev *dev, + struct pci_dev *pci, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + dprintk(1, "%s()\n", __func__); + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), "%s (%s)", + cx23885_boards[dev->board].name, type); + video_set_drvdata(vfd, dev); + return vfd; +} + +static int cx23885_ctrl_query(struct v4l2_queryctrl *qctrl) +{ + int i; + + if (qctrl->id < V4L2_CID_BASE || + qctrl->id >= V4L2_CID_LASTP1) + return -EINVAL; + for (i = 0; i < CX23885_CTLS; i++) + if (cx23885_ctls[i].v.id == qctrl->id) + break; + if (i == CX23885_CTLS) { + *qctrl = no_ctl; + return 0; + } + *qctrl = cx23885_ctls[i].v; + return 0; +} + +/* ------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct cx23885_dev *dev, struct cx23885_fh *fh, + unsigned int bit) +{ + dprintk(1, "%s()\n", __func__); + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + mutex_lock(&dev->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + mutex_unlock(&dev->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dprintk(1, "res: get %d\n", bit); + mutex_unlock(&dev->lock); + return 1; +} + +static int res_check(struct cx23885_fh *fh, unsigned int bit) +{ + return fh->resources & bit; +} + +static int res_locked(struct cx23885_dev *dev, unsigned int bit) +{ + return dev->resources & bit; +} + +static void res_free(struct cx23885_dev *dev, struct cx23885_fh *fh, + unsigned int bits) +{ + BUG_ON((fh->resources & bits) != bits); + dprintk(1, "%s()\n", __func__); + + mutex_lock(&dev->lock); + fh->resources &= ~bits; + dev->resources &= ~bits; + dprintk(1, "res: put %d\n", bits); + mutex_unlock(&dev->lock); +} + +static int cx23885_flatiron_write(struct cx23885_dev *dev, u8 reg, u8 data) +{ + /* 8 bit registers, 8 bit values */ + u8 buf[] = { reg, data }; + + struct i2c_msg msg = { .addr = 0x98 >> 1, + .flags = 0, .buf = buf, .len = 2 }; + + return i2c_transfer(&dev->i2c_bus[2].i2c_adap, &msg, 1); +} + +static u8 cx23885_flatiron_read(struct cx23885_dev *dev, u8 reg) +{ + /* 8 bit registers, 8 bit values */ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + + struct i2c_msg msg[] = { + { .addr = 0x98 >> 1, .flags = 0, .buf = b0, .len = 1 }, + { .addr = 0x98 >> 1, .flags = I2C_M_RD, .buf = b1, .len = 1 } + }; + + ret = i2c_transfer(&dev->i2c_bus[2].i2c_adap, &msg[0], 2); + if (ret != 2) + printk(KERN_ERR "%s() error\n", __func__); + + return b1[0]; +} + +static void cx23885_flatiron_dump(struct cx23885_dev *dev) +{ + int i; + dprintk(1, "Flatiron dump\n"); + for (i = 0; i < 0x24; i++) { + dprintk(1, "FI[%02x] = %02x\n", i, + cx23885_flatiron_read(dev, i)); + } +} + +static int cx23885_flatiron_mux(struct cx23885_dev *dev, int input) +{ + u8 val; + dprintk(1, "%s(input = %d)\n", __func__, input); + + if (input == 1) + val = cx23885_flatiron_read(dev, CH_PWR_CTRL1) & ~FLD_CH_SEL; + else if (input == 2) + val = cx23885_flatiron_read(dev, CH_PWR_CTRL1) | FLD_CH_SEL; + else + return -EINVAL; + + val |= 0x20; /* Enable clock to delta-sigma and dec filter */ + + cx23885_flatiron_write(dev, CH_PWR_CTRL1, val); + + /* Wake up */ + cx23885_flatiron_write(dev, CH_PWR_CTRL2, 0); + + if (video_debug) + cx23885_flatiron_dump(dev); + + return 0; +} + +static int cx23885_video_mux(struct cx23885_dev *dev, unsigned int input) +{ + dprintk(1, "%s() video_mux: %d [vmux=%d, gpio=0x%x,0x%x,0x%x,0x%x]\n", + __func__, + input, INPUT(input)->vmux, + INPUT(input)->gpio0, INPUT(input)->gpio1, + INPUT(input)->gpio2, INPUT(input)->gpio3); + dev->input = input; + + if (dev->board == CX23885_BOARD_MYGICA_X8506 || + dev->board == CX23885_BOARD_MAGICPRO_PROHDTVE2 || + dev->board == CX23885_BOARD_MYGICA_X8507) { + /* Select Analog TV */ + if (INPUT(input)->type == CX23885_VMUX_TELEVISION) + cx23885_gpio_clear(dev, GPIO_0); + } + + /* Tell the internal A/V decoder */ + v4l2_subdev_call(dev->sd_cx25840, video, s_routing, + INPUT(input)->vmux, 0, 0); + + if ((dev->board == CX23885_BOARD_HAUPPAUGE_HVR1800) || + (dev->board == CX23885_BOARD_MPX885) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1250) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1255) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1255_22111) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1850)) { + /* Configure audio routing */ + v4l2_subdev_call(dev->sd_cx25840, audio, s_routing, + INPUT(input)->amux, 0, 0); + + if (INPUT(input)->amux == CX25840_AUDIO7) + cx23885_flatiron_mux(dev, 1); + else if (INPUT(input)->amux == CX25840_AUDIO6) + cx23885_flatiron_mux(dev, 2); + } + + return 0; +} + +static int cx23885_audio_mux(struct cx23885_dev *dev, unsigned int input) +{ + dprintk(1, "%s(input=%d)\n", __func__, input); + + /* The baseband video core of the cx23885 has two audio inputs. + * LR1 and LR2. In almost every single case so far only HVR1xxx + * cards we've only ever supported LR1. Time to support LR2, + * which is available via the optional white breakout header on + * the board. + * We'll use a could of existing enums in the card struct to allow + * devs to specify which baseband input they need, or just default + * to what we've always used. + */ + if (INPUT(input)->amux == CX25840_AUDIO7) + cx23885_flatiron_mux(dev, 1); + else if (INPUT(input)->amux == CX25840_AUDIO6) + cx23885_flatiron_mux(dev, 2); + else { + /* Not specifically defined, assume the default. */ + cx23885_flatiron_mux(dev, 1); + } + + return 0; +} + +/* ------------------------------------------------------------------ */ +static int cx23885_start_video_dma(struct cx23885_dev *dev, + struct cx23885_dmaqueue *q, + struct cx23885_buffer *buf) +{ + dprintk(1, "%s()\n", __func__); + + /* Stop the dma/fifo before we tamper with it's risc programs */ + cx_clear(VID_A_DMA_CTL, 0x11); + + /* setup fifo + format */ + cx23885_sram_channel_setup(dev, &dev->sram_channels[SRAM_CH01], + buf->bpl, buf->risc.dma); + + /* reset counter */ + cx_write(VID_A_GPCNT_CTL, 3); + q->count = 1; + + /* enable irq */ + cx23885_irq_add_enable(dev, 0x01); + cx_set(VID_A_INT_MSK, 0x000011); + + /* start dma */ + cx_set(DEV_CNTRL2, (1<<5)); + cx_set(VID_A_DMA_CTL, 0x11); /* FIFO and RISC enable */ + + return 0; +} + + +static int cx23885_restart_video_queue(struct cx23885_dev *dev, + struct cx23885_dmaqueue *q) +{ + struct cx23885_buffer *buf, *prev; + struct list_head *item; + dprintk(1, "%s()\n", __func__); + + if (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx23885_buffer, + vb.queue); + dprintk(2, "restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + cx23885_start_video_dma(dev, q, buf); + list_for_each(item, &q->active) { + buf = list_entry(item, struct cx23885_buffer, + vb.queue); + buf->count = q->count++; + } + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; + } + + prev = NULL; + for (;;) { + if (list_empty(&q->queued)) + return 0; + buf = list_entry(q->queued.next, struct cx23885_buffer, + vb.queue); + if (NULL == prev) { + list_move_tail(&buf->vb.queue, &q->active); + cx23885_start_video_dma(dev, q, buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2, "[%p/%d] restart_queue - first active\n", + buf, buf->vb.i); + + } else if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_move_tail(&buf->vb.queue, &q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + prev->risc.jmp[2] = cpu_to_le32(0); /* Bits 63 - 32 */ + dprintk(2, "[%p/%d] restart_queue - move to active\n", + buf, buf->vb.i); + } else { + return 0; + } + prev = buf; + } +} + +static int buffer_setup(struct videobuf_queue *q, unsigned int *count, + unsigned int *size) +{ + struct cx23885_fh *fh = q->priv_data; + + *size = fh->fmt->depth*fh->width*fh->height >> 3; + if (0 == *count) + *count = 32; + if (*size * *count > vid_limit * 1024 * 1024) + *count = (vid_limit * 1024 * 1024) / *size; + return 0; +} + +static int buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx23885_fh *fh = q->priv_data; + struct cx23885_dev *dev = fh->dev; + struct cx23885_buffer *buf = + container_of(vb, struct cx23885_buffer, vb); + int rc, init_buffer = 0; + u32 line0_offset, line1_offset; + struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb); + int field_tff; + + BUG_ON(NULL == fh->fmt); + if (fh->width < 48 || fh->width > norm_maxw(dev->tvnorm) || + fh->height < 32 || fh->height > norm_maxh(dev->tvnorm)) + return -EINVAL; + buf->vb.size = (fh->width * fh->height * fh->fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.field != field) { + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + init_buffer = 1; + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + init_buffer = 1; + rc = videobuf_iolock(q, &buf->vb, NULL); + if (0 != rc) + goto fail; + } + + if (init_buffer) { + buf->bpl = buf->vb.width * buf->fmt->depth >> 3; + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + cx23885_risc_buffer(dev->pci, &buf->risc, + dma->sglist, 0, UNSET, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_BOTTOM: + cx23885_risc_buffer(dev->pci, &buf->risc, + dma->sglist, UNSET, 0, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_INTERLACED: + if (dev->tvnorm & V4L2_STD_NTSC) + /* NTSC or */ + field_tff = 1; + else + field_tff = 0; + + if (cx23885_boards[dev->board].force_bff) + /* PAL / SECAM OR 888 in NTSC MODE */ + field_tff = 0; + + if (field_tff) { + /* cx25840 transmits NTSC bottom field first */ + dprintk(1, "%s() Creating TFF/NTSC risc\n", + __func__); + line0_offset = buf->bpl; + line1_offset = 0; + } else { + /* All other formats are top field first */ + dprintk(1, "%s() Creating BFF/PAL/SECAM risc\n", + __func__); + line0_offset = 0; + line1_offset = buf->bpl; + } + cx23885_risc_buffer(dev->pci, &buf->risc, + dma->sglist, line0_offset, + line1_offset, + buf->bpl, buf->bpl, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + cx23885_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + 0, buf->bpl * (buf->vb.height >> 1), + buf->bpl, 0, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + cx23885_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + buf->bpl * (buf->vb.height >> 1), 0, + buf->bpl, 0, + buf->vb.height >> 1); + break; + default: + BUG(); + } + } + dprintk(2, "[%p/%d] buffer_prep - %dx%d %dbpp \"%s\" - dma=0x%08lx\n", + buf, buf->vb.i, + fh->width, fh->height, fh->fmt->depth, fh->fmt->name, + (unsigned long)buf->risc.dma); + + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + + fail: + cx23885_free_buffer(q, buf); + return rc; +} + +static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct cx23885_buffer *buf = container_of(vb, + struct cx23885_buffer, vb); + struct cx23885_buffer *prev; + struct cx23885_fh *fh = vq->priv_data; + struct cx23885_dev *dev = fh->dev; + struct cx23885_dmaqueue *q = &dev->vidq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */ + + if (!list_empty(&q->queued)) { + list_add_tail(&buf->vb.queue, &q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2, "[%p/%d] buffer_queue - append to queued\n", + buf, buf->vb.i); + + } else if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue, &q->active); + cx23885_start_video_dma(dev, q, buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2, "[%p/%d] buffer_queue - first active\n", + buf, buf->vb.i); + + } else { + prev = list_entry(q->active.prev, struct cx23885_buffer, + vb.queue); + if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_add_tail(&buf->vb.queue, &q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + /* 64 bit bits 63-32 */ + prev->risc.jmp[2] = cpu_to_le32(0); + dprintk(2, "[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + + } else { + list_add_tail(&buf->vb.queue, &q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2, "[%p/%d] buffer_queue - first queued\n", + buf, buf->vb.i); + } + } +} + +static void buffer_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct cx23885_buffer *buf = container_of(vb, + struct cx23885_buffer, vb); + + cx23885_free_buffer(q, buf); +} + +static struct videobuf_queue_ops cx23885_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +static struct videobuf_queue *get_queue(struct cx23885_fh *fh) +{ + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &fh->vidq; + case V4L2_BUF_TYPE_VBI_CAPTURE: + return &fh->vbiq; + default: + BUG(); + return NULL; + } +} + +static int get_resource(struct cx23885_fh *fh) +{ + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return RESOURCE_VIDEO; + case V4L2_BUF_TYPE_VBI_CAPTURE: + return RESOURCE_VBI; + default: + BUG(); + return 0; + } +} + +static int video_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct cx23885_dev *dev = video_drvdata(file); + struct cx23885_fh *fh; + enum v4l2_buf_type type = 0; + int radio = 0; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + case VFL_TYPE_VBI: + type = V4L2_BUF_TYPE_VBI_CAPTURE; + break; + case VFL_TYPE_RADIO: + radio = 1; + break; + } + + dprintk(1, "open dev=%s radio=%d type=%s\n", + video_device_node_name(vdev), radio, v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + + file->private_data = fh; + fh->dev = dev; + fh->radio = radio; + fh->type = type; + fh->width = 320; + fh->height = 240; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_YUYV); + + videobuf_queue_sg_init(&fh->vidq, &cx23885_video_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx23885_buffer), + fh, NULL); + + videobuf_queue_sg_init(&fh->vbiq, &cx23885_vbi_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct cx23885_buffer), + fh, NULL); + + + dprintk(1, "post videobuf_queue_init()\n"); + + return 0; +} + +static ssize_t video_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct cx23885_fh *fh = file->private_data; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (res_locked(fh->dev, RESOURCE_VIDEO)) + return -EBUSY; + return videobuf_read_one(&fh->vidq, data, count, ppos, + file->f_flags & O_NONBLOCK); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!res_get(fh->dev, fh, RESOURCE_VBI)) + return -EBUSY; + return videobuf_read_stream(&fh->vbiq, data, count, ppos, 1, + file->f_flags & O_NONBLOCK); + default: + BUG(); + return 0; + } +} + +static unsigned int video_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_buffer *buf; + unsigned int rc = POLLERR; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { + if (!res_get(fh->dev, fh, RESOURCE_VBI)) + return POLLERR; + return videobuf_poll_stream(file, &fh->vbiq, wait); + } + + mutex_lock(&fh->vidq.vb_lock); + if (res_check(fh, RESOURCE_VIDEO)) { + /* streaming capture */ + if (list_empty(&fh->vidq.stream)) + goto done; + buf = list_entry(fh->vidq.stream.next, + struct cx23885_buffer, vb.stream); + } else { + /* read() capture */ + buf = (struct cx23885_buffer *)fh->vidq.read_buf; + if (NULL == buf) + goto done; + } + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == VIDEOBUF_DONE || + buf->vb.state == VIDEOBUF_ERROR) + rc = POLLIN|POLLRDNORM; + else + rc = 0; +done: + mutex_unlock(&fh->vidq.vb_lock); + return rc; +} + +static int video_release(struct file *file) +{ + struct cx23885_fh *fh = file->private_data; + struct cx23885_dev *dev = fh->dev; + + /* turn off overlay */ + if (res_check(fh, RESOURCE_OVERLAY)) { + /* FIXME */ + res_free(dev, fh, RESOURCE_OVERLAY); + } + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + videobuf_queue_cancel(&fh->vidq); + res_free(dev, fh, RESOURCE_VIDEO); + } + if (fh->vidq.read_buf) { + buffer_release(&fh->vidq, fh->vidq.read_buf); + kfree(fh->vidq.read_buf); + } + + /* stop vbi capture */ + if (res_check(fh, RESOURCE_VBI)) { + if (fh->vbiq.streaming) + videobuf_streamoff(&fh->vbiq); + if (fh->vbiq.reading) + videobuf_read_stop(&fh->vbiq); + res_free(dev, fh, RESOURCE_VBI); + } + + videobuf_mmap_free(&fh->vidq); + videobuf_mmap_free(&fh->vbiq); + + file->private_data = NULL; + kfree(fh); + + /* We are not putting the tuner to sleep here on exit, because + * we want to use the mpeg encoder in another session to capture + * tuner video. Closing this will result in no video to the encoder. + */ + + return 0; +} + +static int video_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct cx23885_fh *fh = file->private_data; + + return videobuf_mmap_mapper(get_queue(fh), vma); +} + +/* ------------------------------------------------------------------ */ +/* VIDEO CTRL IOCTLS */ + +int cx23885_get_control(struct cx23885_dev *dev, + struct v4l2_control *ctl) +{ + dprintk(1, "%s() calling cx25840(VIDIOC_G_CTRL)\n", __func__); + call_all(dev, core, g_ctrl, ctl); + return 0; +} + +int cx23885_set_control(struct cx23885_dev *dev, + struct v4l2_control *ctl) +{ + dprintk(1, "%s() calling cx25840(VIDIOC_S_CTRL)\n", __func__); + call_all(dev, core, s_ctrl, ctl); + + return 0; +} + +static void init_controls(struct cx23885_dev *dev) +{ + struct v4l2_control ctrl; + int i; + + for (i = 0; i < CX23885_CTLS; i++) { + ctrl.id = cx23885_ctls[i].v.id; + ctrl.value = cx23885_ctls[i].v.default_value; + + cx23885_set_control(dev, &ctrl); + } +} + +/* ------------------------------------------------------------------ */ +/* VIDEO IOCTLS */ + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_fh *fh = priv; + + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vidq.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + struct cx23885_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = norm_maxw(dev->tvnorm); + maxh = norm_maxh(dev->tvnorm); + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2, + &f->fmt.pix.height, 32, maxh, 0, 0); + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + struct v4l2_mbus_framefmt mbus_fmt; + int err; + + dprintk(2, "%s()\n", __func__); + err = vidioc_try_fmt_vid_cap(file, priv, f); + + if (0 != err) + return err; + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->vidq.field = f->fmt.pix.field; + dprintk(2, "%s() width=%d height=%d field=%d\n", __func__, + fh->width, fh->height, fh->vidq.field); + v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, V4L2_MBUS_FMT_FIXED); + call_all(dev, video, s_mbus_fmt, &mbus_fmt); + v4l2_fill_pix_format(&f->fmt.pix, &mbus_fmt); + return 0; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + strcpy(cap->driver, "cx23885"); + strlcpy(cap->card, cx23885_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info, "PCIe:%s", pci_name(dev->pci)); + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | + V4L2_CAP_VBI_CAPTURE; + if (UNSET != dev->tuner_type) + cap->capabilities |= V4L2_CAP_TUNER; + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (unlikely(f->index >= ARRAY_SIZE(formats))) + return -EINVAL; + + strlcpy(f->description, formats[f->index].name, + sizeof(f->description)); + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct cx23885_fh *fh = priv; + return videobuf_reqbufs(get_queue(fh), p); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct cx23885_fh *fh = priv; + return videobuf_querybuf(get_queue(fh), p); +} + +static int vidioc_qbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct cx23885_fh *fh = priv; + return videobuf_qbuf(get_queue(fh), p); +} + +static int vidioc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct cx23885_fh *fh = priv; + return videobuf_dqbuf(get_queue(fh), p, + file->f_flags & O_NONBLOCK); +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + dprintk(1, "%s()\n", __func__); + + if ((fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (fh->type != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + if (unlikely(i != fh->type)) + return -EINVAL; + + if (unlikely(!res_get(dev, fh, get_resource(fh)))) + return -EBUSY; + + /* Don't start VBI streaming unless vida streaming + * has already started. + */ + if ((fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) && + ((cx_read(VID_A_DMA_CTL) & 0x11) == 0)) + return -EINVAL; + + return videobuf_streamon(get_queue(fh)); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + int err, res; + dprintk(1, "%s()\n", __func__); + + if ((fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (fh->type != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + if (i != fh->type) + return -EINVAL; + + res = get_resource(fh); + err = videobuf_streamoff(get_queue(fh)); + if (err < 0) + return err; + res_free(dev, fh, res); + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + dprintk(1, "%s()\n", __func__); + + call_all(dev, core, g_std, id); + + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *tvnorms) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + dprintk(1, "%s()\n", __func__); + + mutex_lock(&dev->lock); + cx23885_set_tvnorm(dev, *tvnorms); + mutex_unlock(&dev->lock); + + return 0; +} + +int cx23885_enum_input(struct cx23885_dev *dev, struct v4l2_input *i) +{ + static const char *iname[] = { + [CX23885_VMUX_COMPOSITE1] = "Composite1", + [CX23885_VMUX_COMPOSITE2] = "Composite2", + [CX23885_VMUX_COMPOSITE3] = "Composite3", + [CX23885_VMUX_COMPOSITE4] = "Composite4", + [CX23885_VMUX_SVIDEO] = "S-Video", + [CX23885_VMUX_COMPONENT] = "Component", + [CX23885_VMUX_TELEVISION] = "Television", + [CX23885_VMUX_CABLE] = "Cable TV", + [CX23885_VMUX_DVB] = "DVB", + [CX23885_VMUX_DEBUG] = "for debug only", + }; + unsigned int n; + dprintk(1, "%s()\n", __func__); + + n = i->index; + if (n >= MAX_CX23885_INPUT) + return -EINVAL; + + if (0 == INPUT(n)->type) + return -EINVAL; + + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name, iname[INPUT(n)->type]); + if ((CX23885_VMUX_TELEVISION == INPUT(n)->type) || + (CX23885_VMUX_CABLE == INPUT(n)->type)) { + i->type = V4L2_INPUT_TYPE_TUNER; + i->std = CX23885_NORMS; + } + + /* Two selectable audio inputs for non-tv inputs */ + if (INPUT(n)->type != CX23885_VMUX_TELEVISION) + i->audioset = 0x3; + + if (dev->input == n) { + /* enum'd input matches our configured input. + * Ask the video decoder to process the call + * and give it an oppertunity to update the + * status field. + */ + call_all(dev, video, g_input_status, &i->status); + } + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + dprintk(1, "%s()\n", __func__); + return cx23885_enum_input(dev, i); +} + +int cx23885_get_input(struct file *file, void *priv, unsigned int *i) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + *i = dev->input; + dprintk(1, "%s() returns %d\n", __func__, *i); + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + return cx23885_get_input(file, priv, i); +} + +int cx23885_set_input(struct file *file, void *priv, unsigned int i) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + dprintk(1, "%s(%d)\n", __func__, i); + + if (i >= MAX_CX23885_INPUT) { + dprintk(1, "%s() -EINVAL\n", __func__); + return -EINVAL; + } + + if (INPUT(i)->type == 0) + return -EINVAL; + + mutex_lock(&dev->lock); + cx23885_video_mux(dev, i); + + /* By default establish the default audio input for the card also */ + /* Caller is free to use VIDIOC_S_AUDIO to override afterwards */ + cx23885_audio_mux(dev, i); + mutex_unlock(&dev->lock); + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return cx23885_set_input(file, priv, i); +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + + printk(KERN_INFO + "%s/0: ============ START LOG STATUS ============\n", + dev->name); + call_all(dev, core, log_status); + printk(KERN_INFO + "%s/0: ============= END LOG STATUS =============\n", + dev->name); + return 0; +} + +static int cx23885_query_audinput(struct file *file, void *priv, + struct v4l2_audio *i) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + static const char *iname[] = { + [0] = "Baseband L/R 1", + [1] = "Baseband L/R 2", + }; + unsigned int n; + dprintk(1, "%s()\n", __func__); + + n = i->index; + if (n >= 2) + return -EINVAL; + + memset(i, 0, sizeof(*i)); + i->index = n; + strcpy(i->name, iname[n]); + i->capability = V4L2_AUDCAP_STEREO; + i->mode = V4L2_AUDMODE_AVL; + return 0; + +} + +static int vidioc_enum_audinput(struct file *file, void *priv, + struct v4l2_audio *i) +{ + return cx23885_query_audinput(file, priv, i); +} + +static int vidioc_g_audinput(struct file *file, void *priv, + struct v4l2_audio *i) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + i->index = dev->audinput; + dprintk(1, "%s(input=%d)\n", __func__, i->index); + + return cx23885_query_audinput(file, priv, i); +} + +static int vidioc_s_audinput(struct file *file, void *priv, + const struct v4l2_audio *i) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + if (i->index >= 2) + return -EINVAL; + + dprintk(1, "%s(%d)\n", __func__, i->index); + + dev->audinput = i->index; + + /* Skip the audio defaults from the cards struct, caller wants + * directly touch the audio mux hardware. */ + cx23885_flatiron_mux(dev, dev->audinput + 1); + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qctrl) +{ + qctrl->id = v4l2_ctrl_next(ctrl_classes, qctrl->id); + if (unlikely(qctrl->id == 0)) + return -EINVAL; + return cx23885_ctrl_query(qctrl); +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctl) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + return cx23885_get_control(dev, ctl); +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctl) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + return cx23885_set_control(dev, ctl); +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + if (unlikely(UNSET == dev->tuner_type)) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + strcpy(t->name, "Television"); + + call_all(dev, tuner, g_tuner, t); + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + if (UNSET == dev->tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + /* Update the A/V core */ + call_all(dev, tuner, s_tuner, t); + + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + + if (unlikely(UNSET == dev->tuner_type)) + return -EINVAL; + + /* f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; */ + f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + f->frequency = dev->freq; + + call_all(dev, tuner, g_frequency, f); + + return 0; +} + +static int cx23885_set_freq(struct cx23885_dev *dev, struct v4l2_frequency *f) +{ + struct v4l2_control ctrl; + + if (unlikely(UNSET == dev->tuner_type)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + + mutex_lock(&dev->lock); + dev->freq = f->frequency; + + /* I need to mute audio here */ + ctrl.id = V4L2_CID_AUDIO_MUTE; + ctrl.value = 1; + cx23885_set_control(dev, &ctrl); + + call_all(dev, tuner, s_frequency, f); + + /* When changing channels it is required to reset TVAUDIO */ + msleep(100); + + /* I need to unmute audio here */ + ctrl.value = 0; + cx23885_set_control(dev, &ctrl); + + mutex_unlock(&dev->lock); + + return 0; +} + +static int cx23885_set_freq_via_ops(struct cx23885_dev *dev, + struct v4l2_frequency *f) +{ + struct v4l2_control ctrl; + struct videobuf_dvb_frontend *vfe; + struct dvb_frontend *fe; + + struct analog_parameters params = { + .mode = V4L2_TUNER_ANALOG_TV, + .audmode = V4L2_TUNER_MODE_STEREO, + .std = dev->tvnorm, + .frequency = f->frequency + }; + + mutex_lock(&dev->lock); + dev->freq = f->frequency; + + /* I need to mute audio here */ + ctrl.id = V4L2_CID_AUDIO_MUTE; + ctrl.value = 1; + cx23885_set_control(dev, &ctrl); + + /* If HVR1850 */ + dprintk(1, "%s() frequency=%d tuner=%d std=0x%llx\n", __func__, + params.frequency, f->tuner, params.std); + + vfe = videobuf_dvb_get_frontend(&dev->ts2.frontends, 1); + if (!vfe) { + mutex_unlock(&dev->lock); + return -EINVAL; + } + + fe = vfe->dvb.frontend; + + if ((dev->board == CX23885_BOARD_HAUPPAUGE_HVR1850) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1255) || + (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1255_22111)) + fe = &dev->ts1.analog_fe; + + if (fe && fe->ops.tuner_ops.set_analog_params) { + call_all(dev, core, s_std, dev->tvnorm); + fe->ops.tuner_ops.set_analog_params(fe, ¶ms); + } + else + printk(KERN_ERR "%s() No analog tuner, aborting\n", __func__); + + /* When changing channels it is required to reset TVAUDIO */ + msleep(100); + + /* I need to unmute audio here */ + ctrl.value = 0; + cx23885_set_control(dev, &ctrl); + + mutex_unlock(&dev->lock); + + return 0; +} + +int cx23885_set_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx23885_fh *fh = priv; + struct cx23885_dev *dev = fh->dev; + int ret; + + switch (dev->board) { + case CX23885_BOARD_HAUPPAUGE_HVR1255: + case CX23885_BOARD_HAUPPAUGE_HVR1255_22111: + case CX23885_BOARD_HAUPPAUGE_HVR1850: + ret = cx23885_set_freq_via_ops(dev, f); + break; + default: + ret = cx23885_set_freq(dev, f); + } + + return ret; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + return cx23885_set_frequency(file, priv, f); +} + +/* ----------------------------------------------------------- */ + +static void cx23885_vid_timeout(unsigned long data) +{ + struct cx23885_dev *dev = (struct cx23885_dev *)data; + struct cx23885_dmaqueue *q = &dev->vidq; + struct cx23885_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, + struct cx23885_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + printk(KERN_ERR "%s: [%p/%d] timeout - dma=0x%08lx\n", + dev->name, buf, buf->vb.i, + (unsigned long)buf->risc.dma); + } + cx23885_restart_video_queue(dev, q); + spin_unlock_irqrestore(&dev->slock, flags); +} + +int cx23885_video_irq(struct cx23885_dev *dev, u32 status) +{ + u32 mask, count; + int handled = 0; + + mask = cx_read(VID_A_INT_MSK); + if (0 == (status & mask)) + return handled; + + cx_write(VID_A_INT_STAT, status); + + /* risc op code error, fifo overflow or line sync detection error */ + if ((status & VID_BC_MSK_OPC_ERR) || + (status & VID_BC_MSK_SYNC) || + (status & VID_BC_MSK_OF)) { + + if (status & VID_BC_MSK_OPC_ERR) { + dprintk(7, " (VID_BC_MSK_OPC_ERR 0x%08x)\n", + VID_BC_MSK_OPC_ERR); + printk(KERN_WARNING "%s: video risc op code error\n", + dev->name); + cx23885_sram_channel_dump(dev, + &dev->sram_channels[SRAM_CH01]); + } + + if (status & VID_BC_MSK_SYNC) + dprintk(7, " (VID_BC_MSK_SYNC 0x%08x) " + "video lines miss-match\n", + VID_BC_MSK_SYNC); + + if (status & VID_BC_MSK_OF) + dprintk(7, " (VID_BC_MSK_OF 0x%08x) fifo overflow\n", + VID_BC_MSK_OF); + + } + + /* Video */ + if (status & VID_BC_MSK_RISCI1) { + spin_lock(&dev->slock); + count = cx_read(VID_A_GPCNT); + cx23885_video_wakeup(dev, &dev->vidq, count); + spin_unlock(&dev->slock); + handled++; + } + if (status & VID_BC_MSK_RISCI2) { + dprintk(2, "stopper video\n"); + spin_lock(&dev->slock); + cx23885_restart_video_queue(dev, &dev->vidq); + spin_unlock(&dev->slock); + handled++; + } + + /* Allow the VBI framework to process it's payload */ + handled += cx23885_vbi_irq(dev, status); + + return handled; +} + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_g_fmt_vbi_cap = cx23885_vbi_fmt, + .vidioc_try_fmt_vbi_cap = cx23885_vbi_fmt, + .vidioc_s_fmt_vbi_cap = cx23885_vbi_fmt, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_s_std = vidioc_s_std, + .vidioc_g_std = vidioc_g_std, + .vidioc_querystd = vidioc_g_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_log_status = vidioc_log_status, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_g_chip_ident = cx23885_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = cx23885_g_register, + .vidioc_s_register = cx23885_s_register, +#endif + .vidioc_enumaudio = vidioc_enum_audinput, + .vidioc_g_audio = vidioc_g_audinput, + .vidioc_s_audio = vidioc_s_audinput, +}; + +static struct video_device cx23885_vbi_template; +static struct video_device cx23885_video_template = { + .name = "cx23885-video", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = CX23885_NORMS, + .current_norm = V4L2_STD_NTSC_M, +}; + +static const struct v4l2_file_operations radio_fops = { + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .ioctl = video_ioctl2, +}; + + +void cx23885_video_unregister(struct cx23885_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + cx23885_irq_remove(dev, 0x01); + + if (dev->vbi_dev) { + if (video_is_registered(dev->vbi_dev)) + video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + btcx_riscmem_free(dev->pci, &dev->vbiq.stopper); + } + if (dev->video_dev) { + if (video_is_registered(dev->video_dev)) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + + btcx_riscmem_free(dev->pci, &dev->vidq.stopper); + } + + if (dev->audio_dev) + cx23885_audio_unregister(dev); +} + +int cx23885_video_register(struct cx23885_dev *dev) +{ + int err; + + dprintk(1, "%s()\n", __func__); + spin_lock_init(&dev->slock); + + /* Initialize VBI template */ + memcpy(&cx23885_vbi_template, &cx23885_video_template, + sizeof(cx23885_vbi_template)); + strcpy(cx23885_vbi_template.name, "cx23885-vbi"); + + dev->tvnorm = cx23885_video_template.current_norm; + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + INIT_LIST_HEAD(&dev->vidq.queued); + dev->vidq.timeout.function = cx23885_vid_timeout; + dev->vidq.timeout.data = (unsigned long)dev; + init_timer(&dev->vidq.timeout); + cx23885_risc_stopper(dev->pci, &dev->vidq.stopper, + VID_A_DMA_CTL, 0x11, 0x00); + + /* init vbi dma queues */ + INIT_LIST_HEAD(&dev->vbiq.active); + INIT_LIST_HEAD(&dev->vbiq.queued); + dev->vbiq.timeout.function = cx23885_vbi_timeout; + dev->vbiq.timeout.data = (unsigned long)dev; + init_timer(&dev->vbiq.timeout); + cx23885_risc_stopper(dev->pci, &dev->vbiq.stopper, + VID_A_DMA_CTL, 0x22, 0x00); + + cx23885_irq_add_enable(dev, 0x01); + + if ((TUNER_ABSENT != dev->tuner_type) && + ((dev->tuner_bus == 0) || (dev->tuner_bus == 1))) { + struct v4l2_subdev *sd = NULL; + + if (dev->tuner_addr) + sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_bus[dev->tuner_bus].i2c_adap, + "tuner", dev->tuner_addr, NULL); + else + sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, + &dev->i2c_bus[dev->tuner_bus].i2c_adap, + "tuner", 0, v4l2_i2c_tuner_addrs(ADDRS_TV)); + if (sd) { + struct tuner_setup tun_setup; + + memset(&tun_setup, 0, sizeof(tun_setup)); + tun_setup.mode_mask = T_ANALOG_TV; + tun_setup.type = dev->tuner_type; + tun_setup.addr = v4l2_i2c_subdev_addr(sd); + tun_setup.tuner_callback = cx23885_tuner_callback; + + v4l2_subdev_call(sd, tuner, s_type_addr, &tun_setup); + + if (dev->board == CX23885_BOARD_LEADTEK_WINFAST_PXTV1200) { + struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64 + }; + struct v4l2_priv_tun_config cfg = { + .tuner = dev->tuner_type, + .priv = &ctrl + }; + v4l2_subdev_call(sd, tuner, s_config, &cfg); + } + } + } + + /* register Video device */ + dev->video_dev = cx23885_vdev_init(dev, dev->pci, + &cx23885_video_template, "video"); + err = video_register_device(dev->video_dev, VFL_TYPE_GRABBER, + video_nr[dev->nr]); + if (err < 0) { + printk(KERN_INFO "%s: can't register video device\n", + dev->name); + goto fail_unreg; + } + printk(KERN_INFO "%s: registered device %s [v4l2]\n", + dev->name, video_device_node_name(dev->video_dev)); + + /* register VBI device */ + dev->vbi_dev = cx23885_vdev_init(dev, dev->pci, + &cx23885_vbi_template, "vbi"); + err = video_register_device(dev->vbi_dev, VFL_TYPE_VBI, + vbi_nr[dev->nr]); + if (err < 0) { + printk(KERN_INFO "%s: can't register vbi device\n", + dev->name); + goto fail_unreg; + } + printk(KERN_INFO "%s: registered device %s\n", + dev->name, video_device_node_name(dev->vbi_dev)); + + /* Register ALSA audio device */ + dev->audio_dev = cx23885_audio_register(dev); + + /* initial device configuration */ + mutex_lock(&dev->lock); + cx23885_set_tvnorm(dev, dev->tvnorm); + init_controls(dev); + cx23885_video_mux(dev, 0); + cx23885_audio_mux(dev, 0); + mutex_unlock(&dev->lock); + + return 0; + +fail_unreg: + cx23885_video_unregister(dev); + return err; +} + diff --git a/drivers/media/pci/cx23885/cx23885.h b/drivers/media/pci/cx23885/cx23885.h new file mode 100644 index 000000000000..67f40d31450b --- /dev/null +++ b/drivers/media/pci/cx23885/cx23885.h @@ -0,0 +1,654 @@ +/* + * Driver for the Conexant CX23885 PCIe bridge + * + * Copyright (c) 2006 Steven Toth + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "btcx-risc.h" +#include "cx23885-reg.h" +#include "media/cx2341x.h" + +#include + +#define CX23885_VERSION "0.0.3" + +#define UNSET (-1U) + +#define CX23885_MAXBOARDS 8 + +/* Max number of inputs by card */ +#define MAX_CX23885_INPUT 8 +#define INPUT(nr) (&cx23885_boards[dev->board].input[nr]) +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +#define BUFFER_TIMEOUT (HZ) /* 0.5 seconds */ + +#define CX23885_BOARD_NOAUTO UNSET +#define CX23885_BOARD_UNKNOWN 0 +#define CX23885_BOARD_HAUPPAUGE_HVR1800lp 1 +#define CX23885_BOARD_HAUPPAUGE_HVR1800 2 +#define CX23885_BOARD_HAUPPAUGE_HVR1250 3 +#define CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP 4 +#define CX23885_BOARD_HAUPPAUGE_HVR1500Q 5 +#define CX23885_BOARD_HAUPPAUGE_HVR1500 6 +#define CX23885_BOARD_HAUPPAUGE_HVR1200 7 +#define CX23885_BOARD_HAUPPAUGE_HVR1700 8 +#define CX23885_BOARD_HAUPPAUGE_HVR1400 9 +#define CX23885_BOARD_DVICO_FUSIONHDTV_7_DUAL_EXP 10 +#define CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP 11 +#define CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H 12 +#define CX23885_BOARD_COMPRO_VIDEOMATE_E650F 13 +#define CX23885_BOARD_TBS_6920 14 +#define CX23885_BOARD_TEVII_S470 15 +#define CX23885_BOARD_DVBWORLD_2005 16 +#define CX23885_BOARD_NETUP_DUAL_DVBS2_CI 17 +#define CX23885_BOARD_HAUPPAUGE_HVR1270 18 +#define CX23885_BOARD_HAUPPAUGE_HVR1275 19 +#define CX23885_BOARD_HAUPPAUGE_HVR1255 20 +#define CX23885_BOARD_HAUPPAUGE_HVR1210 21 +#define CX23885_BOARD_MYGICA_X8506 22 +#define CX23885_BOARD_MAGICPRO_PROHDTVE2 23 +#define CX23885_BOARD_HAUPPAUGE_HVR1850 24 +#define CX23885_BOARD_COMPRO_VIDEOMATE_E800 25 +#define CX23885_BOARD_HAUPPAUGE_HVR1290 26 +#define CX23885_BOARD_MYGICA_X8558PRO 27 +#define CX23885_BOARD_LEADTEK_WINFAST_PXTV1200 28 +#define CX23885_BOARD_GOTVIEW_X5_3D_HYBRID 29 +#define CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF 30 +#define CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000 31 +#define CX23885_BOARD_MPX885 32 +#define CX23885_BOARD_MYGICA_X8507 33 +#define CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL 34 +#define CX23885_BOARD_TEVII_S471 35 +#define CX23885_BOARD_HAUPPAUGE_HVR1255_22111 36 +#define CX23885_BOARD_PROF_8000 37 + +#define GPIO_0 0x00000001 +#define GPIO_1 0x00000002 +#define GPIO_2 0x00000004 +#define GPIO_3 0x00000008 +#define GPIO_4 0x00000010 +#define GPIO_5 0x00000020 +#define GPIO_6 0x00000040 +#define GPIO_7 0x00000080 +#define GPIO_8 0x00000100 +#define GPIO_9 0x00000200 +#define GPIO_10 0x00000400 +#define GPIO_11 0x00000800 +#define GPIO_12 0x00001000 +#define GPIO_13 0x00002000 +#define GPIO_14 0x00004000 +#define GPIO_15 0x00008000 + +/* Currently unsupported by the driver: PAL/H, NTSC/Kr, SECAM B/G/H/LC */ +#define CX23885_NORMS (\ + V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_443 | \ + V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_I | \ + V4L2_STD_PAL_M | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | \ + V4L2_STD_PAL_60 | V4L2_STD_SECAM_L | V4L2_STD_SECAM_DK) + +struct cx23885_fmt { + char *name; + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; + u32 cxformat; +}; + +struct cx23885_ctrl { + struct v4l2_queryctrl v; + u32 off; + u32 reg; + u32 mask; + u32 shift; +}; + +struct cx23885_tvnorm { + char *name; + v4l2_std_id id; + u32 cxiformat; + u32 cxoformat; +}; + +struct cx23885_fh { + struct cx23885_dev *dev; + enum v4l2_buf_type type; + int radio; + u32 resources; + + /* video overlay */ + struct v4l2_window win; + struct v4l2_clip *clips; + unsigned int nclips; + + /* video capture */ + struct cx23885_fmt *fmt; + unsigned int width, height; + + /* vbi capture */ + struct videobuf_queue vidq; + struct videobuf_queue vbiq; + + /* MPEG Encoder specifics ONLY */ + struct videobuf_queue mpegq; + atomic_t v4l_reading; +}; + +enum cx23885_itype { + CX23885_VMUX_COMPOSITE1 = 1, + CX23885_VMUX_COMPOSITE2, + CX23885_VMUX_COMPOSITE3, + CX23885_VMUX_COMPOSITE4, + CX23885_VMUX_SVIDEO, + CX23885_VMUX_COMPONENT, + CX23885_VMUX_TELEVISION, + CX23885_VMUX_CABLE, + CX23885_VMUX_DVB, + CX23885_VMUX_DEBUG, + CX23885_RADIO, +}; + +enum cx23885_src_sel_type { + CX23885_SRC_SEL_EXT_656_VIDEO = 0, + CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO +}; + +/* buffer for one video frame */ +struct cx23885_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* cx23885 specific */ + unsigned int bpl; + struct btcx_riscmem risc; + struct cx23885_fmt *fmt; + u32 count; +}; + +struct cx23885_input { + enum cx23885_itype type; + unsigned int vmux; + unsigned int amux; + u32 gpio0, gpio1, gpio2, gpio3; +}; + +typedef enum { + CX23885_MPEG_UNDEFINED = 0, + CX23885_MPEG_DVB, + CX23885_ANALOG_VIDEO, + CX23885_MPEG_ENCODER, +} port_t; + +struct cx23885_board { + char *name; + port_t porta, portb, portc; + int num_fds_portb, num_fds_portc; + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + unsigned int tuner_bus; + + /* Vendors can and do run the PCIe bridge at different + * clock rates, driven physically by crystals on the PCBs. + * The core has to accommodate this. This allows the user + * to add new boards with new frequencys. The value is + * expressed in Hz. + * + * The core framework will default this value based on + * current designs, but it can vary. + */ + u32 clk_freq; + struct cx23885_input input[MAX_CX23885_INPUT]; + int ci_type; /* for NetUP */ + /* Force bottom field first during DMA (888 workaround) */ + u32 force_bff; +}; + +struct cx23885_subid { + u16 subvendor; + u16 subdevice; + u32 card; +}; + +struct cx23885_i2c { + struct cx23885_dev *dev; + + int nr; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + u32 i2c_rc; + + /* 885 registers used for raw addess */ + u32 i2c_period; + u32 reg_ctrl; + u32 reg_stat; + u32 reg_addr; + u32 reg_rdata; + u32 reg_wdata; +}; + +struct cx23885_dmaqueue { + struct list_head active; + struct list_head queued; + struct timer_list timeout; + struct btcx_riscmem stopper; + u32 count; +}; + +struct cx23885_tsport { + struct cx23885_dev *dev; + + int nr; + int sram_chno; + + struct videobuf_dvb_frontends frontends; + + /* dma queues */ + struct cx23885_dmaqueue mpegq; + u32 ts_packet_size; + u32 ts_packet_count; + + int width; + int height; + + spinlock_t slock; + + /* registers */ + u32 reg_gpcnt; + u32 reg_gpcnt_ctl; + u32 reg_dma_ctl; + u32 reg_lngth; + u32 reg_hw_sop_ctrl; + u32 reg_gen_ctrl; + u32 reg_bd_pkt_status; + u32 reg_sop_status; + u32 reg_fifo_ovfl_stat; + u32 reg_vld_misc; + u32 reg_ts_clk_en; + u32 reg_ts_int_msk; + u32 reg_ts_int_stat; + u32 reg_src_sel; + + /* Default register vals */ + int pci_irqmask; + u32 dma_ctl_val; + u32 ts_int_msk_val; + u32 gen_ctrl_val; + u32 ts_clk_en_val; + u32 src_sel_val; + u32 vld_misc_val; + u32 hw_sop_ctrl_val; + + /* Allow a single tsport to have multiple frontends */ + u32 num_frontends; + void (*gate_ctrl)(struct cx23885_tsport *port, int open); + void *port_priv; + + /* Workaround for a temp dvb_frontend that the tuner can attached to */ + struct dvb_frontend analog_fe; +}; + +struct cx23885_kernel_ir { + struct cx23885_dev *cx; + char *name; + char *phys; + + struct rc_dev *rc; +}; + +struct cx23885_audio_buffer { + unsigned int bpl; + struct btcx_riscmem risc; + struct videobuf_dmabuf dma; +}; + +struct cx23885_audio_dev { + struct cx23885_dev *dev; + + struct pci_dev *pci; + + struct snd_card *card; + + spinlock_t lock; + + atomic_t count; + + unsigned int dma_size; + unsigned int period_size; + unsigned int num_periods; + + struct videobuf_dmabuf *dma_risc; + + struct cx23885_audio_buffer *buf; + + struct snd_pcm_substream *substream; +}; + +struct cx23885_dev { + atomic_t refcount; + struct v4l2_device v4l2_dev; + + /* pci stuff */ + struct pci_dev *pci; + unsigned char pci_rev, pci_lat; + int pci_bus, pci_slot; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + int pci_irqmask; + spinlock_t pci_irqmask_lock; /* protects mask reg too */ + int hwrevision; + + /* This valud is board specific and is used to configure the + * AV core so we see nice clean and stable video and audio. */ + u32 clk_freq; + + /* I2C adapters: Master 1 & 2 (External) & Master 3 (Internal only) */ + struct cx23885_i2c i2c_bus[3]; + + int nr; + struct mutex lock; + struct mutex gpio_lock; + + /* board details */ + unsigned int board; + char name[32]; + + struct cx23885_tsport ts1, ts2; + + /* sram configuration */ + struct sram_channel *sram_channels; + + enum { + CX23885_BRIDGE_UNDEFINED = 0, + CX23885_BRIDGE_885 = 885, + CX23885_BRIDGE_887 = 887, + CX23885_BRIDGE_888 = 888, + } bridge; + + /* Analog video */ + u32 resources; + unsigned int input; + unsigned int audinput; /* Selectable audio input */ + u32 tvaudio; + v4l2_std_id tvnorm; + unsigned int tuner_type; + unsigned char tuner_addr; + unsigned int tuner_bus; + unsigned int radio_type; + unsigned char radio_addr; + unsigned int has_radio; + struct v4l2_subdev *sd_cx25840; + struct work_struct cx25840_work; + + /* Infrared */ + struct v4l2_subdev *sd_ir; + struct work_struct ir_rx_work; + unsigned long ir_rx_notifications; + struct work_struct ir_tx_work; + unsigned long ir_tx_notifications; + + struct cx23885_kernel_ir *kernel_ir; + atomic_t ir_input_stopping; + + /* V4l */ + u32 freq; + struct video_device *video_dev; + struct video_device *vbi_dev; + struct video_device *radio_dev; + + struct cx23885_dmaqueue vidq; + struct cx23885_dmaqueue vbiq; + spinlock_t slock; + + /* MPEG Encoder ONLY settings */ + u32 cx23417_mailbox; + struct cx2341x_mpeg_params mpeg_params; + struct video_device *v4l_device; + atomic_t v4l_reader_count; + struct cx23885_tvnorm encodernorm; + + /* Analog raw audio */ + struct cx23885_audio_dev *audio_dev; + +}; + +static inline struct cx23885_dev *to_cx23885(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct cx23885_dev, v4l2_dev); +} + +#define call_all(dev, o, f, args...) \ + v4l2_device_call_all(&dev->v4l2_dev, 0, o, f, ##args) + +#define CX23885_HW_888_IR (1 << 0) +#define CX23885_HW_AV_CORE (1 << 1) + +#define call_hw(dev, grpid, o, f, args...) \ + v4l2_device_call_all(&dev->v4l2_dev, grpid, o, f, ##args) + +extern struct v4l2_subdev *cx23885_find_hw(struct cx23885_dev *dev, u32 hw); + +#define SRAM_CH01 0 /* Video A */ +#define SRAM_CH02 1 /* VBI A */ +#define SRAM_CH03 2 /* Video B */ +#define SRAM_CH04 3 /* Transport via B */ +#define SRAM_CH05 4 /* VBI B */ +#define SRAM_CH06 5 /* Video C */ +#define SRAM_CH07 6 /* Transport via C */ +#define SRAM_CH08 7 /* Audio Internal A */ +#define SRAM_CH09 8 /* Audio Internal B */ +#define SRAM_CH10 9 /* Audio External */ +#define SRAM_CH11 10 /* COMB_3D_N */ +#define SRAM_CH12 11 /* Comb 3D N1 */ +#define SRAM_CH13 12 /* Comb 3D N2 */ +#define SRAM_CH14 13 /* MOE Vid */ +#define SRAM_CH15 14 /* MOE RSLT */ + +struct sram_channel { + char *name; + u32 cmds_start; + u32 ctrl_start; + u32 cdt; + u32 fifo_start; + u32 fifo_size; + u32 ptr1_reg; + u32 ptr2_reg; + u32 cnt1_reg; + u32 cnt2_reg; + u32 jumponly; +}; + +/* ----------------------------------------------------------- */ + +#define cx_read(reg) readl(dev->lmmio + ((reg)>>2)) +#define cx_write(reg, value) writel((value), dev->lmmio + ((reg)>>2)) + +#define cx_andor(reg, mask, value) \ + writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+((reg)>>2)) + +#define cx_set(reg, bit) cx_andor((reg), (bit), (bit)) +#define cx_clear(reg, bit) cx_andor((reg), (bit), 0) + +/* ----------------------------------------------------------- */ +/* cx23885-core.c */ + +extern int cx23885_sram_channel_setup(struct cx23885_dev *dev, + struct sram_channel *ch, + unsigned int bpl, u32 risc); + +extern void cx23885_sram_channel_dump(struct cx23885_dev *dev, + struct sram_channel *ch); + +extern int cx23885_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc, + u32 reg, u32 mask, u32 value); + +extern int cx23885_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines); + +extern int cx23885_risc_vbibuffer(struct pci_dev *pci, + struct btcx_riscmem *risc, struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines); + +void cx23885_cancel_buffers(struct cx23885_tsport *port); + +extern int cx23885_restart_queue(struct cx23885_tsport *port, + struct cx23885_dmaqueue *q); + +extern void cx23885_wakeup(struct cx23885_tsport *port, + struct cx23885_dmaqueue *q, u32 count); + +extern void cx23885_gpio_set(struct cx23885_dev *dev, u32 mask); +extern void cx23885_gpio_clear(struct cx23885_dev *dev, u32 mask); +extern u32 cx23885_gpio_get(struct cx23885_dev *dev, u32 mask); +extern void cx23885_gpio_enable(struct cx23885_dev *dev, u32 mask, + int asoutput); + +extern void cx23885_irq_add_enable(struct cx23885_dev *dev, u32 mask); +extern void cx23885_irq_enable(struct cx23885_dev *dev, u32 mask); +extern void cx23885_irq_disable(struct cx23885_dev *dev, u32 mask); +extern void cx23885_irq_remove(struct cx23885_dev *dev, u32 mask); + +/* ----------------------------------------------------------- */ +/* cx23885-cards.c */ +extern struct cx23885_board cx23885_boards[]; +extern const unsigned int cx23885_bcount; + +extern struct cx23885_subid cx23885_subids[]; +extern const unsigned int cx23885_idcount; + +extern int cx23885_tuner_callback(void *priv, int component, + int command, int arg); +extern void cx23885_card_list(struct cx23885_dev *dev); +extern int cx23885_ir_init(struct cx23885_dev *dev); +extern void cx23885_ir_pci_int_enable(struct cx23885_dev *dev); +extern void cx23885_ir_fini(struct cx23885_dev *dev); +extern void cx23885_gpio_setup(struct cx23885_dev *dev); +extern void cx23885_card_setup(struct cx23885_dev *dev); +extern void cx23885_card_setup_pre_i2c(struct cx23885_dev *dev); + +extern int cx23885_dvb_register(struct cx23885_tsport *port); +extern int cx23885_dvb_unregister(struct cx23885_tsport *port); + +extern int cx23885_buf_prepare(struct videobuf_queue *q, + struct cx23885_tsport *port, + struct cx23885_buffer *buf, + enum v4l2_field field); +extern void cx23885_buf_queue(struct cx23885_tsport *port, + struct cx23885_buffer *buf); +extern void cx23885_free_buffer(struct videobuf_queue *q, + struct cx23885_buffer *buf); + +/* ----------------------------------------------------------- */ +/* cx23885-video.c */ +/* Video */ +extern int cx23885_video_register(struct cx23885_dev *dev); +extern void cx23885_video_unregister(struct cx23885_dev *dev); +extern int cx23885_video_irq(struct cx23885_dev *dev, u32 status); +extern void cx23885_video_wakeup(struct cx23885_dev *dev, + struct cx23885_dmaqueue *q, u32 count); +int cx23885_enum_input(struct cx23885_dev *dev, struct v4l2_input *i); +int cx23885_set_input(struct file *file, void *priv, unsigned int i); +int cx23885_get_input(struct file *file, void *priv, unsigned int *i); +int cx23885_set_frequency(struct file *file, void *priv, struct v4l2_frequency *f); +int cx23885_set_control(struct cx23885_dev *dev, struct v4l2_control *ctl); +int cx23885_get_control(struct cx23885_dev *dev, struct v4l2_control *ctl); +int cx23885_set_tvnorm(struct cx23885_dev *dev, v4l2_std_id norm); + +/* ----------------------------------------------------------- */ +/* cx23885-vbi.c */ +extern int cx23885_vbi_fmt(struct file *file, void *priv, + struct v4l2_format *f); +extern void cx23885_vbi_timeout(unsigned long data); +extern struct videobuf_queue_ops cx23885_vbi_qops; +extern int cx23885_restart_vbi_queue(struct cx23885_dev *dev, + struct cx23885_dmaqueue *q); +extern int cx23885_vbi_irq(struct cx23885_dev *dev, u32 status); + +/* cx23885-i2c.c */ +extern int cx23885_i2c_register(struct cx23885_i2c *bus); +extern int cx23885_i2c_unregister(struct cx23885_i2c *bus); +extern void cx23885_av_clk(struct cx23885_dev *dev, int enable); + +/* ----------------------------------------------------------- */ +/* cx23885-417.c */ +extern int cx23885_417_register(struct cx23885_dev *dev); +extern void cx23885_417_unregister(struct cx23885_dev *dev); +extern int cx23885_irq_417(struct cx23885_dev *dev, u32 status); +extern void cx23885_417_check_encoder(struct cx23885_dev *dev); +extern void cx23885_mc417_init(struct cx23885_dev *dev); +extern int mc417_memory_read(struct cx23885_dev *dev, u32 address, u32 *value); +extern int mc417_memory_write(struct cx23885_dev *dev, u32 address, u32 value); +extern int mc417_register_read(struct cx23885_dev *dev, + u16 address, u32 *value); +extern int mc417_register_write(struct cx23885_dev *dev, + u16 address, u32 value); +extern void mc417_gpio_set(struct cx23885_dev *dev, u32 mask); +extern void mc417_gpio_clear(struct cx23885_dev *dev, u32 mask); +extern void mc417_gpio_enable(struct cx23885_dev *dev, u32 mask, int asoutput); + +/* ----------------------------------------------------------- */ +/* cx23885-alsa.c */ +extern struct cx23885_audio_dev *cx23885_audio_register( + struct cx23885_dev *dev); +extern void cx23885_audio_unregister(struct cx23885_dev *dev); +extern int cx23885_audio_irq(struct cx23885_dev *dev, u32 status, u32 mask); +extern int cx23885_risc_databuffer(struct pci_dev *pci, + struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int bpl, + unsigned int lines, + unsigned int lpi); + +/* ----------------------------------------------------------- */ +/* tv norms */ + +static inline unsigned int norm_maxw(v4l2_std_id norm) +{ + return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 720 : 768; +} + +static inline unsigned int norm_maxh(v4l2_std_id norm) +{ + return (norm & V4L2_STD_625_50) ? 576 : 480; +} + +static inline unsigned int norm_swidth(v4l2_std_id norm) +{ + return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 754 : 922; +} diff --git a/drivers/media/video/cx23885/cx23888-ir.c b/drivers/media/pci/cx23885/cx23888-ir.c similarity index 100% rename from drivers/media/video/cx23885/cx23888-ir.c rename to drivers/media/pci/cx23885/cx23888-ir.c diff --git a/drivers/media/video/cx23885/cx23888-ir.h b/drivers/media/pci/cx23885/cx23888-ir.h similarity index 100% rename from drivers/media/video/cx23885/cx23888-ir.h rename to drivers/media/pci/cx23885/cx23888-ir.h diff --git a/drivers/media/video/cx23885/netup-eeprom.c b/drivers/media/pci/cx23885/netup-eeprom.c similarity index 100% rename from drivers/media/video/cx23885/netup-eeprom.c rename to drivers/media/pci/cx23885/netup-eeprom.c diff --git a/drivers/media/video/cx23885/netup-eeprom.h b/drivers/media/pci/cx23885/netup-eeprom.h similarity index 100% rename from drivers/media/video/cx23885/netup-eeprom.h rename to drivers/media/pci/cx23885/netup-eeprom.h diff --git a/drivers/media/video/cx23885/netup-init.c b/drivers/media/pci/cx23885/netup-init.c similarity index 100% rename from drivers/media/video/cx23885/netup-init.c rename to drivers/media/pci/cx23885/netup-init.c diff --git a/drivers/media/video/cx23885/netup-init.h b/drivers/media/pci/cx23885/netup-init.h similarity index 100% rename from drivers/media/video/cx23885/netup-init.h rename to drivers/media/pci/cx23885/netup-init.h diff --git a/drivers/media/video/cx25821/Kconfig b/drivers/media/pci/cx25821/Kconfig similarity index 100% rename from drivers/media/video/cx25821/Kconfig rename to drivers/media/pci/cx25821/Kconfig diff --git a/drivers/media/pci/cx25821/Makefile b/drivers/media/pci/cx25821/Makefile new file mode 100644 index 000000000000..5bf3ea4c1556 --- /dev/null +++ b/drivers/media/pci/cx25821/Makefile @@ -0,0 +1,13 @@ +cx25821-y := cx25821-core.o cx25821-cards.o cx25821-i2c.o \ + cx25821-gpio.o cx25821-medusa-video.o \ + cx25821-video.o cx25821-video-upstream.o \ + cx25821-video-upstream-ch2.o \ + cx25821-audio-upstream.o + +obj-$(CONFIG_VIDEO_CX25821) += cx25821.o +obj-$(CONFIG_VIDEO_CX25821_ALSA) += cx25821-alsa.o + +ccflags-y += -Idrivers/media/i2c +ccflags-y += -Idrivers/media/tuners +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += -Idrivers/media/dvb-frontends diff --git a/drivers/media/video/cx25821/cx25821-alsa.c b/drivers/media/pci/cx25821/cx25821-alsa.c similarity index 100% rename from drivers/media/video/cx25821/cx25821-alsa.c rename to drivers/media/pci/cx25821/cx25821-alsa.c diff --git a/drivers/media/video/cx25821/cx25821-audio-upstream.c b/drivers/media/pci/cx25821/cx25821-audio-upstream.c similarity index 100% rename from drivers/media/video/cx25821/cx25821-audio-upstream.c rename to drivers/media/pci/cx25821/cx25821-audio-upstream.c diff --git a/drivers/media/video/cx25821/cx25821-audio-upstream.h b/drivers/media/pci/cx25821/cx25821-audio-upstream.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-audio-upstream.h rename to drivers/media/pci/cx25821/cx25821-audio-upstream.h diff --git a/drivers/media/video/cx25821/cx25821-audio.h b/drivers/media/pci/cx25821/cx25821-audio.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-audio.h rename to drivers/media/pci/cx25821/cx25821-audio.h diff --git a/drivers/media/video/cx25821/cx25821-biffuncs.h b/drivers/media/pci/cx25821/cx25821-biffuncs.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-biffuncs.h rename to drivers/media/pci/cx25821/cx25821-biffuncs.h diff --git a/drivers/media/video/cx25821/cx25821-cards.c b/drivers/media/pci/cx25821/cx25821-cards.c similarity index 100% rename from drivers/media/video/cx25821/cx25821-cards.c rename to drivers/media/pci/cx25821/cx25821-cards.c diff --git a/drivers/media/video/cx25821/cx25821-core.c b/drivers/media/pci/cx25821/cx25821-core.c similarity index 100% rename from drivers/media/video/cx25821/cx25821-core.c rename to drivers/media/pci/cx25821/cx25821-core.c diff --git a/drivers/media/video/cx25821/cx25821-gpio.c b/drivers/media/pci/cx25821/cx25821-gpio.c similarity index 100% rename from drivers/media/video/cx25821/cx25821-gpio.c rename to drivers/media/pci/cx25821/cx25821-gpio.c diff --git a/drivers/media/video/cx25821/cx25821-i2c.c b/drivers/media/pci/cx25821/cx25821-i2c.c similarity index 100% rename from drivers/media/video/cx25821/cx25821-i2c.c rename to drivers/media/pci/cx25821/cx25821-i2c.c diff --git a/drivers/media/video/cx25821/cx25821-medusa-defines.h b/drivers/media/pci/cx25821/cx25821-medusa-defines.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-medusa-defines.h rename to drivers/media/pci/cx25821/cx25821-medusa-defines.h diff --git a/drivers/media/video/cx25821/cx25821-medusa-reg.h b/drivers/media/pci/cx25821/cx25821-medusa-reg.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-medusa-reg.h rename to drivers/media/pci/cx25821/cx25821-medusa-reg.h diff --git a/drivers/media/video/cx25821/cx25821-medusa-video.c b/drivers/media/pci/cx25821/cx25821-medusa-video.c similarity index 100% rename from drivers/media/video/cx25821/cx25821-medusa-video.c rename to drivers/media/pci/cx25821/cx25821-medusa-video.c diff --git a/drivers/media/video/cx25821/cx25821-medusa-video.h b/drivers/media/pci/cx25821/cx25821-medusa-video.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-medusa-video.h rename to drivers/media/pci/cx25821/cx25821-medusa-video.h diff --git a/drivers/media/video/cx25821/cx25821-reg.h b/drivers/media/pci/cx25821/cx25821-reg.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-reg.h rename to drivers/media/pci/cx25821/cx25821-reg.h diff --git a/drivers/media/video/cx25821/cx25821-sram.h b/drivers/media/pci/cx25821/cx25821-sram.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-sram.h rename to drivers/media/pci/cx25821/cx25821-sram.h diff --git a/drivers/media/video/cx25821/cx25821-video-upstream-ch2.c b/drivers/media/pci/cx25821/cx25821-video-upstream-ch2.c similarity index 100% rename from drivers/media/video/cx25821/cx25821-video-upstream-ch2.c rename to drivers/media/pci/cx25821/cx25821-video-upstream-ch2.c diff --git a/drivers/media/video/cx25821/cx25821-video-upstream-ch2.h b/drivers/media/pci/cx25821/cx25821-video-upstream-ch2.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-video-upstream-ch2.h rename to drivers/media/pci/cx25821/cx25821-video-upstream-ch2.h diff --git a/drivers/media/video/cx25821/cx25821-video-upstream.c b/drivers/media/pci/cx25821/cx25821-video-upstream.c similarity index 100% rename from drivers/media/video/cx25821/cx25821-video-upstream.c rename to drivers/media/pci/cx25821/cx25821-video-upstream.c diff --git a/drivers/media/video/cx25821/cx25821-video-upstream.h b/drivers/media/pci/cx25821/cx25821-video-upstream.h similarity index 100% rename from drivers/media/video/cx25821/cx25821-video-upstream.h rename to drivers/media/pci/cx25821/cx25821-video-upstream.h diff --git a/drivers/media/pci/cx25821/cx25821-video.c b/drivers/media/pci/cx25821/cx25821-video.c new file mode 100644 index 000000000000..0a80245165d0 --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-video.c @@ -0,0 +1,1990 @@ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + * Based on Steven Toth cx23885 driver + * Parts adapted/taken from Eduardo Moscoso Rubino + * Copyright (C) 2009 Eduardo Moscoso Rubino + * + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "cx25821-video.h" + +MODULE_DESCRIPTION("v4l2 driver module for cx25821 based TV cards"); +MODULE_AUTHOR("Hiep Huynh "); +MODULE_LICENSE("GPL"); + +static unsigned int video_nr[] = {[0 ... (CX25821_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio_nr[] = {[0 ... (CX25821_MAXBOARDS - 1)] = UNSET }; + +module_param_array(video_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr, "video device numbers"); +MODULE_PARM_DESC(radio_nr, "radio device numbers"); + +static unsigned int video_debug = VIDEO_DEBUG; +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug, "enable debug messages [video]"); + +static unsigned int irq_debug; +module_param(irq_debug, int, 0644); +MODULE_PARM_DESC(irq_debug, "enable debug messages [IRQ handler]"); + +unsigned int vid_limit = 16; +module_param(vid_limit, int, 0644); +MODULE_PARM_DESC(vid_limit, "capture memory limit in megabytes"); + +static void cx25821_init_controls(struct cx25821_dev *dev, int chan_num); + +static const struct v4l2_file_operations video_fops; +static const struct v4l2_ioctl_ops video_ioctl_ops; + +#define FORMAT_FLAGS_PACKED 0x01 + +struct cx25821_fmt formats[] = { + { + .name = "8 bpp, gray", + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "4:1:1, packed, Y41P", + .fourcc = V4L2_PIX_FMT_Y41P, + .depth = 12, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "4:2:0, YUV", + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .flags = FORMAT_FLAGS_PACKED, + }, +}; + +int cx25821_get_format_size(void) +{ + return ARRAY_SIZE(formats); +} + +struct cx25821_fmt *cx25821_format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + if (fourcc == V4L2_PIX_FMT_Y41P || fourcc == V4L2_PIX_FMT_YUV411P) + return formats + 1; + + for (i = 0; i < ARRAY_SIZE(formats); i++) + if (formats[i].fourcc == fourcc) + return formats + i; + + pr_err("%s(0x%08x) NOT FOUND\n", __func__, fourcc); + return NULL; +} + +void cx25821_video_wakeup(struct cx25821_dev *dev, struct cx25821_dmaqueue *q, + u32 count) +{ + struct cx25821_buffer *buf; + int bc; + + for (bc = 0;; bc++) { + if (list_empty(&q->active)) { + dprintk(1, "bc=%d (=0: active empty)\n", bc); + break; + } + + buf = list_entry(q->active.next, struct cx25821_buffer, + vb.queue); + + /* count comes from the hw and it is 16bit wide -- + * this trick handles wrap-arounds correctly for + * up to 32767 buffers in flight... */ + if ((s16) (count - buf->count) < 0) + break; + + do_gettimeofday(&buf->vb.ts); + buf->vb.state = VIDEOBUF_DONE; + list_del(&buf->vb.queue); + wake_up(&buf->vb.done); + } + + if (list_empty(&q->active)) + del_timer(&q->timeout); + else + mod_timer(&q->timeout, jiffies + BUFFER_TIMEOUT); + if (bc != 1) + pr_err("%s: %d buffers handled (should be 1)\n", __func__, bc); +} + +#ifdef TUNER_FLAG +int cx25821_set_tvnorm(struct cx25821_dev *dev, v4l2_std_id norm) +{ + dprintk(1, "%s(norm = 0x%08x) name: [%s]\n", + __func__, (unsigned int)norm, v4l2_norm_to_name(norm)); + + dev->tvnorm = norm; + + /* Tell the internal A/V decoder */ + cx25821_call_all(dev, core, s_std, norm); + + return 0; +} +#endif + +struct video_device *cx25821_vdev_init(struct cx25821_dev *dev, + struct pci_dev *pci, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + dprintk(1, "%s()\n", __func__); + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", dev->name, type, + cx25821_boards[dev->board].name); + video_set_drvdata(vfd, dev); + return vfd; +} + +/* +static int cx25821_ctrl_query(struct v4l2_queryctrl *qctrl) +{ + int i; + + if (qctrl->id < V4L2_CID_BASE || qctrl->id >= V4L2_CID_LASTP1) + return -EINVAL; + for (i = 0; i < CX25821_CTLS; i++) + if (cx25821_ctls[i].v.id == qctrl->id) + break; + if (i == CX25821_CTLS) { + *qctrl = no_ctl; + return 0; + } + *qctrl = cx25821_ctls[i].v; + return 0; +} +*/ + +/* resource management */ +int cx25821_res_get(struct cx25821_dev *dev, struct cx25821_fh *fh, + unsigned int bit) +{ + dprintk(1, "%s()\n", __func__); + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + mutex_lock(&dev->lock); + if (dev->channels[fh->channel_id].resources & bit) { + /* no, someone else uses it */ + mutex_unlock(&dev->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->channels[fh->channel_id].resources |= bit; + dprintk(1, "res: get %d\n", bit); + mutex_unlock(&dev->lock); + return 1; +} + +int cx25821_res_check(struct cx25821_fh *fh, unsigned int bit) +{ + return fh->resources & bit; +} + +int cx25821_res_locked(struct cx25821_fh *fh, unsigned int bit) +{ + return fh->dev->channels[fh->channel_id].resources & bit; +} + +void cx25821_res_free(struct cx25821_dev *dev, struct cx25821_fh *fh, + unsigned int bits) +{ + BUG_ON((fh->resources & bits) != bits); + dprintk(1, "%s()\n", __func__); + + mutex_lock(&dev->lock); + fh->resources &= ~bits; + dev->channels[fh->channel_id].resources &= ~bits; + dprintk(1, "res: put %d\n", bits); + mutex_unlock(&dev->lock); +} + +int cx25821_video_mux(struct cx25821_dev *dev, unsigned int input) +{ + struct v4l2_routing route; + memset(&route, 0, sizeof(route)); + + dprintk(1, "%s(): video_mux: %d [vmux=%d, gpio=0x%x,0x%x,0x%x,0x%x]\n", + __func__, input, INPUT(input)->vmux, INPUT(input)->gpio0, + INPUT(input)->gpio1, INPUT(input)->gpio2, INPUT(input)->gpio3); + dev->input = input; + + route.input = INPUT(input)->vmux; + + /* Tell the internal A/V decoder */ + cx25821_call_all(dev, video, s_routing, INPUT(input)->vmux, 0, 0); + + return 0; +} + +int cx25821_start_video_dma(struct cx25821_dev *dev, + struct cx25821_dmaqueue *q, + struct cx25821_buffer *buf, + struct sram_channel *channel) +{ + int tmp = 0; + + /* setup fifo + format */ + cx25821_sram_channel_setup(dev, channel, buf->bpl, buf->risc.dma); + + /* reset counter */ + cx_write(channel->gpcnt_ctl, 3); + q->count = 1; + + /* enable irq */ + cx_set(PCI_INT_MSK, cx_read(PCI_INT_MSK) | (1 << channel->i)); + cx_set(channel->int_msk, 0x11); + + /* start dma */ + cx_write(channel->dma_ctl, 0x11); /* FIFO and RISC enable */ + + /* make sure upstream setting if any is reversed */ + tmp = cx_read(VID_CH_MODE_SEL); + cx_write(VID_CH_MODE_SEL, tmp & 0xFFFFFE00); + + return 0; +} + +int cx25821_restart_video_queue(struct cx25821_dev *dev, + struct cx25821_dmaqueue *q, + struct sram_channel *channel) +{ + struct cx25821_buffer *buf, *prev; + struct list_head *item; + + if (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx25821_buffer, + vb.queue); + + cx25821_start_video_dma(dev, q, buf, channel); + + list_for_each(item, &q->active) { + buf = list_entry(item, struct cx25821_buffer, vb.queue); + buf->count = q->count++; + } + + mod_timer(&q->timeout, jiffies + BUFFER_TIMEOUT); + return 0; + } + + prev = NULL; + for (;;) { + if (list_empty(&q->queued)) + return 0; + + buf = list_entry(q->queued.next, struct cx25821_buffer, + vb.queue); + + if (NULL == prev) { + list_move_tail(&buf->vb.queue, &q->active); + cx25821_start_video_dma(dev, q, buf, channel); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies + BUFFER_TIMEOUT); + } else if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_move_tail(&buf->vb.queue, &q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + prev->risc.jmp[2] = cpu_to_le32(0); /* Bits 63 - 32 */ + } else { + return 0; + } + prev = buf; + } +} + +void cx25821_vid_timeout(unsigned long data) +{ + struct cx25821_data *timeout_data = (struct cx25821_data *)data; + struct cx25821_dev *dev = timeout_data->dev; + struct sram_channel *channel = timeout_data->channel; + struct cx25821_dmaqueue *q = &dev->channels[channel->i].vidq; + struct cx25821_buffer *buf; + unsigned long flags; + + /* cx25821_sram_channel_dump(dev, channel); */ + cx_clear(channel->dma_ctl, 0x11); + + spin_lock_irqsave(&dev->slock, flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx25821_buffer, + vb.queue); + list_del(&buf->vb.queue); + + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + } + + cx25821_restart_video_queue(dev, q, channel); + spin_unlock_irqrestore(&dev->slock, flags); +} + +int cx25821_video_irq(struct cx25821_dev *dev, int chan_num, u32 status) +{ + u32 count = 0; + int handled = 0; + u32 mask; + struct sram_channel *channel = dev->channels[chan_num].sram_channels; + + mask = cx_read(channel->int_msk); + if (0 == (status & mask)) + return handled; + + cx_write(channel->int_stat, status); + + /* risc op code error */ + if (status & (1 << 16)) { + pr_warn("%s, %s: video risc op code error\n", + dev->name, channel->name); + cx_clear(channel->dma_ctl, 0x11); + cx25821_sram_channel_dump(dev, channel); + } + + /* risc1 y */ + if (status & FLD_VID_DST_RISC1) { + spin_lock(&dev->slock); + count = cx_read(channel->gpcnt); + cx25821_video_wakeup(dev, &dev->channels[channel->i].vidq, + count); + spin_unlock(&dev->slock); + handled++; + } + + /* risc2 y */ + if (status & 0x10) { + dprintk(2, "stopper video\n"); + spin_lock(&dev->slock); + cx25821_restart_video_queue(dev, + &dev->channels[channel->i].vidq, channel); + spin_unlock(&dev->slock); + handled++; + } + return handled; +} + +void cx25821_videoioctl_unregister(struct cx25821_dev *dev) +{ + if (dev->ioctl_dev) { + if (video_is_registered(dev->ioctl_dev)) + video_unregister_device(dev->ioctl_dev); + else + video_device_release(dev->ioctl_dev); + + dev->ioctl_dev = NULL; + } +} + +void cx25821_video_unregister(struct cx25821_dev *dev, int chan_num) +{ + cx_clear(PCI_INT_MSK, 1); + + if (dev->channels[chan_num].video_dev) { + if (video_is_registered(dev->channels[chan_num].video_dev)) + video_unregister_device( + dev->channels[chan_num].video_dev); + else + video_device_release( + dev->channels[chan_num].video_dev); + + dev->channels[chan_num].video_dev = NULL; + + btcx_riscmem_free(dev->pci, + &dev->channels[chan_num].vidq.stopper); + + pr_warn("device %d released!\n", chan_num); + } + +} + +int cx25821_video_register(struct cx25821_dev *dev) +{ + int err; + int i; + + struct video_device cx25821_video_device = { + .name = "cx25821-video", + .fops = &video_fops, + .minor = -1, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = CX25821_NORMS, + .current_norm = V4L2_STD_NTSC_M, + }; + + spin_lock_init(&dev->slock); + + for (i = 0; i < MAX_VID_CHANNEL_NUM - 1; ++i) { + cx25821_init_controls(dev, i); + + cx25821_risc_stopper(dev->pci, &dev->channels[i].vidq.stopper, + dev->channels[i].sram_channels->dma_ctl, 0x11, 0); + + dev->channels[i].sram_channels = &cx25821_sram_channels[i]; + dev->channels[i].video_dev = NULL; + dev->channels[i].resources = 0; + + cx_write(dev->channels[i].sram_channels->int_stat, 0xffffffff); + + INIT_LIST_HEAD(&dev->channels[i].vidq.active); + INIT_LIST_HEAD(&dev->channels[i].vidq.queued); + + dev->channels[i].timeout_data.dev = dev; + dev->channels[i].timeout_data.channel = + &cx25821_sram_channels[i]; + dev->channels[i].vidq.timeout.function = cx25821_vid_timeout; + dev->channels[i].vidq.timeout.data = + (unsigned long)&dev->channels[i].timeout_data; + init_timer(&dev->channels[i].vidq.timeout); + + /* register v4l devices */ + dev->channels[i].video_dev = cx25821_vdev_init(dev, dev->pci, + &cx25821_video_device, "video"); + + err = video_register_device(dev->channels[i].video_dev, + VFL_TYPE_GRABBER, video_nr[dev->nr]); + + if (err < 0) + goto fail_unreg; + + } + + /* set PCI interrupt */ + cx_set(PCI_INT_MSK, 0xff); + + /* initial device configuration */ + mutex_lock(&dev->lock); +#ifdef TUNER_FLAG + dev->tvnorm = cx25821_video_device.current_norm; + cx25821_set_tvnorm(dev, dev->tvnorm); +#endif + mutex_unlock(&dev->lock); + + return 0; + +fail_unreg: + cx25821_video_unregister(dev, i); + return err; +} + +int cx25821_buffer_setup(struct videobuf_queue *q, unsigned int *count, + unsigned int *size) +{ + struct cx25821_fh *fh = q->priv_data; + + *size = fh->fmt->depth * fh->width * fh->height >> 3; + + if (0 == *count) + *count = 32; + + if (*size * *count > vid_limit * 1024 * 1024) + *count = (vid_limit * 1024 * 1024) / *size; + + return 0; +} + +int cx25821_buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx25821_fh *fh = q->priv_data; + struct cx25821_dev *dev = fh->dev; + struct cx25821_buffer *buf = + container_of(vb, struct cx25821_buffer, vb); + int rc, init_buffer = 0; + u32 line0_offset; + struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb); + int bpl_local = LINE_SIZE_D1; + int channel_opened = fh->channel_id; + + BUG_ON(NULL == fh->fmt); + if (fh->width < 48 || fh->width > 720 || + fh->height < 32 || fh->height > 576) + return -EINVAL; + + buf->vb.size = (fh->width * fh->height * fh->fmt->depth) >> 3; + + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || buf->vb.field != field) { + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + init_buffer = 1; + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + init_buffer = 1; + rc = videobuf_iolock(q, &buf->vb, NULL); + if (0 != rc) { + printk(KERN_DEBUG pr_fmt("videobuf_iolock failed!\n")); + goto fail; + } + } + + dprintk(1, "init_buffer=%d\n", init_buffer); + + if (init_buffer) { + + channel_opened = dev->channel_opened; + if (channel_opened < 0 || channel_opened > 7) + channel_opened = 7; + + if (dev->channels[channel_opened].pixel_formats == + PIXEL_FRMT_411) + buf->bpl = (buf->fmt->depth * buf->vb.width) >> 3; + else + buf->bpl = (buf->fmt->depth >> 3) * (buf->vb.width); + + if (dev->channels[channel_opened].pixel_formats == + PIXEL_FRMT_411) { + bpl_local = buf->bpl; + } else { + bpl_local = buf->bpl; /* Default */ + + if (channel_opened >= 0 && channel_opened <= 7) { + if (dev->channels[channel_opened] + .use_cif_resolution) { + if (dev->tvnorm & V4L2_STD_PAL_BG || + dev->tvnorm & V4L2_STD_PAL_DK) + bpl_local = 352 << 1; + else + bpl_local = dev->channels[ + channel_opened]. + cif_width << 1; + } + } + } + + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + cx25821_risc_buffer(dev->pci, &buf->risc, + dma->sglist, 0, UNSET, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_BOTTOM: + cx25821_risc_buffer(dev->pci, &buf->risc, + dma->sglist, UNSET, 0, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_INTERLACED: + /* All other formats are top field first */ + line0_offset = 0; + dprintk(1, "top field first\n"); + + cx25821_risc_buffer(dev->pci, &buf->risc, + dma->sglist, line0_offset, + bpl_local, bpl_local, bpl_local, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + cx25821_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + 0, buf->bpl * (buf->vb.height >> 1), + buf->bpl, 0, buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + cx25821_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + buf->bpl * (buf->vb.height >> 1), 0, + buf->bpl, 0, buf->vb.height >> 1); + break; + default: + BUG(); + } + } + + dprintk(2, "[%p/%d] buffer_prep - %dx%d %dbpp \"%s\" - dma=0x%08lx\n", + buf, buf->vb.i, fh->width, fh->height, fh->fmt->depth, + fh->fmt->name, (unsigned long)buf->risc.dma); + + buf->vb.state = VIDEOBUF_PREPARED; + + return 0; + +fail: + cx25821_free_buffer(q, buf); + return rc; +} + +void cx25821_buffer_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct cx25821_buffer *buf = + container_of(vb, struct cx25821_buffer, vb); + + cx25821_free_buffer(q, buf); +} + +struct videobuf_queue *get_queue(struct cx25821_fh *fh) +{ + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &fh->vidq; + default: + BUG(); + return NULL; + } +} + +int cx25821_get_resource(struct cx25821_fh *fh, int resource) +{ + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return resource; + default: + BUG(); + return 0; + } +} + +int cx25821_video_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct cx25821_fh *fh = file->private_data; + + return videobuf_mmap_mapper(get_queue(fh), vma); +} + + +static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct cx25821_buffer *buf = + container_of(vb, struct cx25821_buffer, vb); + struct cx25821_buffer *prev; + struct cx25821_fh *fh = vq->priv_data; + struct cx25821_dev *dev = fh->dev; + struct cx25821_dmaqueue *q = &dev->channels[fh->channel_id].vidq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */ + + dprintk(2, "jmp to stopper (0x%x)\n", buf->risc.jmp[1]); + + if (!list_empty(&q->queued)) { + list_add_tail(&buf->vb.queue, &q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2, "[%p/%d] buffer_queue - append to queued\n", buf, + buf->vb.i); + + } else if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue, &q->active); + cx25821_start_video_dma(dev, q, buf, + dev->channels[fh->channel_id].sram_channels); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies + BUFFER_TIMEOUT); + dprintk(2, "[%p/%d] buffer_queue - first active, buf cnt = %d, q->count = %d\n", + buf, buf->vb.i, buf->count, q->count); + } else { + prev = list_entry(q->active.prev, struct cx25821_buffer, + vb.queue); + if (prev->vb.width == buf->vb.width + && prev->vb.height == buf->vb.height + && prev->fmt == buf->fmt) { + list_add_tail(&buf->vb.queue, &q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + + /* 64 bit bits 63-32 */ + prev->risc.jmp[2] = cpu_to_le32(0); + dprintk(2, "[%p/%d] buffer_queue - append to active, buf->count=%d\n", + buf, buf->vb.i, buf->count); + + } else { + list_add_tail(&buf->vb.queue, &q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2, "[%p/%d] buffer_queue - first queued\n", buf, + buf->vb.i); + } + } + + if (list_empty(&q->active)) + dprintk(2, "active queue empty!\n"); +} + +static struct videobuf_queue_ops cx25821_video_qops = { + .buf_setup = cx25821_buffer_setup, + .buf_prepare = cx25821_buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = cx25821_buffer_release, +}; + +static int video_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct cx25821_dev *h, *dev = video_drvdata(file); + struct cx25821_fh *fh; + struct list_head *list; + int minor = video_devdata(file)->minor; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + u32 pix_format; + int ch_id = 0; + int i; + + dprintk(1, "open dev=%s type=%s\n", video_device_node_name(vdev), + v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + + mutex_lock(&cx25821_devlist_mutex); + + list_for_each(list, &cx25821_devlist) + { + h = list_entry(list, struct cx25821_dev, devlist); + + for (i = 0; i < MAX_VID_CHANNEL_NUM; i++) { + if (h->channels[i].video_dev && + h->channels[i].video_dev->minor == minor) { + dev = h; + ch_id = i; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + } + } + } + + if (NULL == dev) { + mutex_unlock(&cx25821_devlist_mutex); + kfree(fh); + return -ENODEV; + } + + file->private_data = fh; + fh->dev = dev; + fh->type = type; + fh->width = 720; + fh->channel_id = ch_id; + + if (dev->tvnorm & V4L2_STD_PAL_BG || dev->tvnorm & V4L2_STD_PAL_DK) + fh->height = 576; + else + fh->height = 480; + + dev->channel_opened = fh->channel_id; + if (dev->channels[ch_id].pixel_formats == PIXEL_FRMT_411) + pix_format = V4L2_PIX_FMT_Y41P; + else + pix_format = V4L2_PIX_FMT_YUYV; + fh->fmt = cx25821_format_by_fourcc(pix_format); + + v4l2_prio_open(&dev->channels[ch_id].prio, &fh->prio); + + videobuf_queue_sg_init(&fh->vidq, &cx25821_video_qops, &dev->pci->dev, + &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, sizeof(struct cx25821_buffer), + fh, NULL); + + dprintk(1, "post videobuf_queue_init()\n"); + mutex_unlock(&cx25821_devlist_mutex); + + return 0; +} + +static ssize_t video_read(struct file *file, char __user * data, size_t count, + loff_t *ppos) +{ + struct cx25821_fh *fh = file->private_data; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (cx25821_res_locked(fh, RESOURCE_VIDEO0)) + return -EBUSY; + + return videobuf_read_one(&fh->vidq, data, count, ppos, + file->f_flags & O_NONBLOCK); + + default: + BUG(); + return 0; + } +} + +static unsigned int video_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct cx25821_fh *fh = file->private_data; + struct cx25821_buffer *buf; + + if (cx25821_res_check(fh, RESOURCE_VIDEO0)) { + /* streaming capture */ + if (list_empty(&fh->vidq.stream)) + return POLLERR; + buf = list_entry(fh->vidq.stream.next, + struct cx25821_buffer, vb.stream); + } else { + /* read() capture */ + buf = (struct cx25821_buffer *)fh->vidq.read_buf; + if (NULL == buf) + return POLLERR; + } + + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == VIDEOBUF_DONE || buf->vb.state == VIDEOBUF_ERROR) { + if (buf->vb.state == VIDEOBUF_DONE) { + struct cx25821_dev *dev = fh->dev; + + if (dev && dev->channels[fh->channel_id] + .use_cif_resolution) { + u8 cam_id = *((char *)buf->vb.baddr + 3); + memcpy((char *)buf->vb.baddr, + (char *)buf->vb.baddr + (fh->width * 2), + (fh->width * 2)); + *((char *)buf->vb.baddr + 3) = cam_id; + } + } + + return POLLIN | POLLRDNORM; + } + + return 0; +} + +static int video_release(struct file *file) +{ + struct cx25821_fh *fh = file->private_data; + struct cx25821_dev *dev = fh->dev; + + /* stop the risc engine and fifo */ + cx_write(channel0->dma_ctl, 0); /* FIFO and RISC disable */ + + /* stop video capture */ + if (cx25821_res_check(fh, RESOURCE_VIDEO0)) { + videobuf_queue_cancel(&fh->vidq); + cx25821_res_free(dev, fh, RESOURCE_VIDEO0); + } + + if (fh->vidq.read_buf) { + cx25821_buffer_release(&fh->vidq, fh->vidq.read_buf); + kfree(fh->vidq.read_buf); + } + + videobuf_mmap_free(&fh->vidq); + + v4l2_prio_close(&dev->channels[fh->channel_id].prio, fh->prio); + file->private_data = NULL; + kfree(fh); + + return 0; +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct cx25821_fh *fh = priv; + struct cx25821_dev *dev = fh->dev; + + if (unlikely(fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)) + return -EINVAL; + + if (unlikely(i != fh->type)) + return -EINVAL; + + if (unlikely(!cx25821_res_get(dev, fh, cx25821_get_resource(fh, + RESOURCE_VIDEO0)))) + return -EBUSY; + + return videobuf_streamon(get_queue(fh)); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct cx25821_fh *fh = priv; + struct cx25821_dev *dev = fh->dev; + int err, res; + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (i != fh->type) + return -EINVAL; + + res = cx25821_get_resource(fh, RESOURCE_VIDEO0); + err = videobuf_streamoff(get_queue(fh)); + if (err < 0) + return err; + cx25821_res_free(dev, fh, res); + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx25821_fh *fh = priv; + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + struct v4l2_mbus_framefmt mbus_fmt; + int err; + int pix_format = PIXEL_FRMT_422; + + if (fh) { + err = v4l2_prio_check(&dev->channels[fh->channel_id].prio, + fh->prio); + if (0 != err) + return err; + } + + dprintk(2, "%s()\n", __func__); + err = cx25821_vidioc_try_fmt_vid_cap(file, priv, f); + + if (0 != err) + return err; + + fh->fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat); + fh->vidq.field = f->fmt.pix.field; + + /* check if width and height is valid based on set standard */ + if (cx25821_is_valid_width(f->fmt.pix.width, dev->tvnorm)) + fh->width = f->fmt.pix.width; + + if (cx25821_is_valid_height(f->fmt.pix.height, dev->tvnorm)) + fh->height = f->fmt.pix.height; + + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_Y41P) + pix_format = PIXEL_FRMT_411; + else if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + pix_format = PIXEL_FRMT_422; + else + return -EINVAL; + + cx25821_set_pixel_format(dev, SRAM_CH00, pix_format); + + /* check if cif resolution */ + if (fh->width == 320 || fh->width == 352) + dev->channels[fh->channel_id].use_cif_resolution = 1; + else + dev->channels[fh->channel_id].use_cif_resolution = 0; + + dev->channels[fh->channel_id].cif_width = fh->width; + medusa_set_resolution(dev, fh->width, SRAM_CH00); + + dprintk(2, "%s(): width=%d height=%d field=%d\n", __func__, fh->width, + fh->height, fh->vidq.field); + v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, V4L2_MBUS_FMT_FIXED); + cx25821_call_all(dev, video, s_mbus_fmt, &mbus_fmt); + + return 0; +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + int ret_val = 0; + struct cx25821_fh *fh = priv; + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + + ret_val = videobuf_dqbuf(get_queue(fh), p, file->f_flags & O_NONBLOCK); + + p->sequence = dev->channels[fh->channel_id].vidq.count; + + return ret_val; +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + struct cx25821_fh *fh = priv; + char name[32 + 2]; + + struct sram_channel *sram_ch = dev->channels[fh->channel_id] + .sram_channels; + u32 tmp = 0; + + snprintf(name, sizeof(name), "%s/2", dev->name); + pr_info("%s/2: ============ START LOG STATUS ============\n", + dev->name); + cx25821_call_all(dev, core, log_status); + tmp = cx_read(sram_ch->dma_ctl); + pr_info("Video input 0 is %s\n", + (tmp & 0x11) ? "streaming" : "stopped"); + pr_info("%s/2: ============= END LOG STATUS =============\n", + dev->name); + return 0; +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctl) +{ + struct cx25821_fh *fh = priv; + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + int err; + + if (fh) { + err = v4l2_prio_check(&dev->channels[fh->channel_id].prio, + fh->prio); + if (0 != err) + return err; + } + + return cx25821_set_control(dev, ctl, fh->channel_id); +} + +/* VIDEO IOCTLS */ +int cx25821_vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx25821_fh *fh = priv; + + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vidq.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +int cx25821_vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx25821_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = cx25821_format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = 720; + maxh = 576; + + if (V4L2_FIELD_ANY == field) { + if (f->fmt.pix.height > maxh / 2) + field = V4L2_FIELD_INTERLACED; + else + field = V4L2_FIELD_TOP; + } + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + f->fmt.pix.width &= ~0x03; + f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +int cx25821_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + + strcpy(cap->driver, "cx25821"); + strlcpy(cap->card, cx25821_boards[dev->board].name, sizeof(cap->card)); + sprintf(cap->bus_info, "PCIe:%s", pci_name(dev->pci)); + cap->version = CX25821_VERSION_CODE; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + if (UNSET != dev->tuner_type) + cap->capabilities |= V4L2_CAP_TUNER; + return 0; +} + +int cx25821_vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (unlikely(f->index >= ARRAY_SIZE(formats))) + return -EINVAL; + + strlcpy(f->description, formats[f->index].name, sizeof(f->description)); + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +int cx25821_vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct cx25821_fh *fh = priv; + return videobuf_reqbufs(get_queue(fh), p); +} + +int cx25821_vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct cx25821_fh *fh = priv; + return videobuf_querybuf(get_queue(fh), p); +} + +int cx25821_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct cx25821_fh *fh = priv; + return videobuf_qbuf(get_queue(fh), p); +} + +int cx25821_vidioc_g_priority(struct file *file, void *f, enum v4l2_priority *p) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)f)->dev; + struct cx25821_fh *fh = f; + + *p = v4l2_prio_max(&dev->channels[fh->channel_id].prio); + + return 0; +} + +int cx25821_vidioc_s_priority(struct file *file, void *f, + enum v4l2_priority prio) +{ + struct cx25821_fh *fh = f; + struct cx25821_dev *dev = ((struct cx25821_fh *)f)->dev; + + return v4l2_prio_change(&dev->channels[fh->channel_id].prio, &fh->prio, + prio); +} + +#ifdef TUNER_FLAG +int cx25821_vidioc_s_std(struct file *file, void *priv, v4l2_std_id * tvnorms) +{ + struct cx25821_fh *fh = priv; + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + int err; + + dprintk(1, "%s()\n", __func__); + + if (fh) { + err = v4l2_prio_check(&dev->channels[fh->channel_id].prio, + fh->prio); + if (0 != err) + return err; + } + + if (dev->tvnorm == *tvnorms) + return 0; + + mutex_lock(&dev->lock); + cx25821_set_tvnorm(dev, *tvnorms); + mutex_unlock(&dev->lock); + + medusa_set_videostandard(dev); + + return 0; +} +#endif + +int cx25821_enum_input(struct cx25821_dev *dev, struct v4l2_input *i) +{ + static const char * const iname[] = { + [CX25821_VMUX_COMPOSITE] = "Composite", + [CX25821_VMUX_SVIDEO] = "S-Video", + [CX25821_VMUX_DEBUG] = "for debug only", + }; + unsigned int n; + dprintk(1, "%s()\n", __func__); + + n = i->index; + if (n >= 2) + return -EINVAL; + + if (0 == INPUT(n)->type) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name, iname[INPUT(n)->type]); + + i->std = CX25821_NORMS; + return 0; +} + +int cx25821_vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + dprintk(1, "%s()\n", __func__); + return cx25821_enum_input(dev, i); +} + +int cx25821_vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + + *i = dev->input; + dprintk(1, "%s(): returns %d\n", __func__, *i); + return 0; +} + +int cx25821_vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct cx25821_fh *fh = priv; + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + int err; + + dprintk(1, "%s(%d)\n", __func__, i); + + if (fh) { + err = v4l2_prio_check(&dev->channels[fh->channel_id].prio, + fh->prio); + if (0 != err) + return err; + } + + if (i >= CX25821_NR_INPUT) { + dprintk(1, "%s(): -EINVAL\n", __func__); + return -EINVAL; + } + + mutex_lock(&dev->lock); + cx25821_video_mux(dev, i); + mutex_unlock(&dev->lock); + return 0; +} + +#ifdef TUNER_FLAG +int cx25821_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx25821_fh *fh = priv; + struct cx25821_dev *dev = fh->dev; + + f->frequency = dev->freq; + + cx25821_call_all(dev, tuner, g_frequency, f); + + return 0; +} + +int cx25821_set_freq(struct cx25821_dev *dev, struct v4l2_frequency *f) +{ + mutex_lock(&dev->lock); + dev->freq = f->frequency; + + cx25821_call_all(dev, tuner, s_frequency, f); + + /* When changing channels it is required to reset TVAUDIO */ + msleep(10); + + mutex_unlock(&dev->lock); + + return 0; +} + +int cx25821_vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx25821_fh *fh = priv; + struct cx25821_dev *dev; + int err; + + if (fh) { + dev = fh->dev; + err = v4l2_prio_check(&dev->channels[fh->channel_id].prio, + fh->prio); + if (0 != err) + return err; + } else { + pr_err("Invalid fh pointer!\n"); + return -EINVAL; + } + + return cx25821_set_freq(dev, f); +} +#endif + +#ifdef CONFIG_VIDEO_ADV_DEBUG +int cx25821_vidioc_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)fh)->dev; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + + cx25821_call_all(dev, core, g_register, reg); + + return 0; +} + +int cx25821_vidioc_s_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)fh)->dev; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + + cx25821_call_all(dev, core, s_register, reg); + + return 0; +} + +#endif + +#ifdef TUNER_FLAG +int cx25821_vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + + if (unlikely(UNSET == dev->tuner_type)) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + strcpy(t->name, "Television"); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM; + t->rangehigh = 0xffffffffUL; + + t->signal = 0xffff; /* LOCKED */ + return 0; +} + +int cx25821_vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *t) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + struct cx25821_fh *fh = priv; + int err; + + if (fh) { + err = v4l2_prio_check(&dev->channels[fh->channel_id].prio, + fh->prio); + if (0 != err) + return err; + } + + dprintk(1, "%s()\n", __func__); + if (UNSET == dev->tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + return 0; +} + +#endif +/*****************************************************************************/ +static const struct v4l2_queryctrl no_ctl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; + +static struct v4l2_queryctrl cx25821_ctls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0, + .maximum = 10000, + .step = 1, + .default_value = 6200, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 10000, + .step = 1, + .default_value = 5000, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 10000, + .step = 1, + .default_value = 5000, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = 0, + .maximum = 10000, + .step = 1, + .default_value = 5000, + .type = V4L2_CTRL_TYPE_INTEGER, + } +}; +static const int CX25821_CTLS = ARRAY_SIZE(cx25821_ctls); + +static int cx25821_ctrl_query(struct v4l2_queryctrl *qctrl) +{ + int i; + + if (qctrl->id < V4L2_CID_BASE || qctrl->id >= V4L2_CID_LASTP1) + return -EINVAL; + for (i = 0; i < CX25821_CTLS; i++) + if (cx25821_ctls[i].id == qctrl->id) + break; + if (i == CX25821_CTLS) { + *qctrl = no_ctl; + return 0; + } + *qctrl = cx25821_ctls[i]; + return 0; +} + +int cx25821_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qctrl) +{ + return cx25821_ctrl_query(qctrl); +} + +/* ------------------------------------------------------------------ */ +/* VIDEO CTRL IOCTLS */ + +static const struct v4l2_queryctrl *ctrl_by_id(unsigned int id) +{ + unsigned int i; + + for (i = 0; i < CX25821_CTLS; i++) + if (cx25821_ctls[i].id == id) + return cx25821_ctls + i; + return NULL; +} + +int cx25821_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctl) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + struct cx25821_fh *fh = priv; + + const struct v4l2_queryctrl *ctrl; + + ctrl = ctrl_by_id(ctl->id); + + if (NULL == ctrl) + return -EINVAL; + switch (ctl->id) { + case V4L2_CID_BRIGHTNESS: + ctl->value = dev->channels[fh->channel_id].ctl_bright; + break; + case V4L2_CID_HUE: + ctl->value = dev->channels[fh->channel_id].ctl_hue; + break; + case V4L2_CID_CONTRAST: + ctl->value = dev->channels[fh->channel_id].ctl_contrast; + break; + case V4L2_CID_SATURATION: + ctl->value = dev->channels[fh->channel_id].ctl_saturation; + break; + } + return 0; +} + +int cx25821_set_control(struct cx25821_dev *dev, + struct v4l2_control *ctl, int chan_num) +{ + int err; + const struct v4l2_queryctrl *ctrl; + + err = -EINVAL; + + ctrl = ctrl_by_id(ctl->id); + + if (NULL == ctrl) + return err; + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_BOOLEAN: + case V4L2_CTRL_TYPE_MENU: + case V4L2_CTRL_TYPE_INTEGER: + if (ctl->value < ctrl->minimum) + ctl->value = ctrl->minimum; + if (ctl->value > ctrl->maximum) + ctl->value = ctrl->maximum; + break; + default: + /* nothing */ ; + } + + switch (ctl->id) { + case V4L2_CID_BRIGHTNESS: + dev->channels[chan_num].ctl_bright = ctl->value; + medusa_set_brightness(dev, ctl->value, chan_num); + break; + case V4L2_CID_HUE: + dev->channels[chan_num].ctl_hue = ctl->value; + medusa_set_hue(dev, ctl->value, chan_num); + break; + case V4L2_CID_CONTRAST: + dev->channels[chan_num].ctl_contrast = ctl->value; + medusa_set_contrast(dev, ctl->value, chan_num); + break; + case V4L2_CID_SATURATION: + dev->channels[chan_num].ctl_saturation = ctl->value; + medusa_set_saturation(dev, ctl->value, chan_num); + break; + } + + err = 0; + + return err; +} + +static void cx25821_init_controls(struct cx25821_dev *dev, int chan_num) +{ + struct v4l2_control ctrl; + int i; + for (i = 0; i < CX25821_CTLS; i++) { + ctrl.id = cx25821_ctls[i].id; + ctrl.value = cx25821_ctls[i].default_value; + + cx25821_set_control(dev, &ctrl, chan_num); + } +} + +int cx25821_vidioc_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *cropcap) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + + if (cropcap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + cropcap->bounds.top = 0; + cropcap->bounds.left = 0; + cropcap->bounds.width = 720; + cropcap->bounds.height = dev->tvnorm == V4L2_STD_PAL_BG ? 576 : 480; + cropcap->pixelaspect.numerator = + dev->tvnorm == V4L2_STD_PAL_BG ? 59 : 10; + cropcap->pixelaspect.denominator = + dev->tvnorm == V4L2_STD_PAL_BG ? 54 : 11; + cropcap->defrect = cropcap->bounds; + return 0; +} + +int cx25821_vidioc_s_crop(struct file *file, void *priv, const struct v4l2_crop *crop) +{ + struct cx25821_dev *dev = ((struct cx25821_fh *)priv)->dev; + struct cx25821_fh *fh = priv; + int err; + + if (fh) { + err = v4l2_prio_check(&dev->channels[fh->channel_id].prio, + fh->prio); + if (0 != err) + return err; + } + /* cx25821_vidioc_s_crop not supported */ + return -EINVAL; +} + +int cx25821_vidioc_g_crop(struct file *file, void *priv, struct v4l2_crop *crop) +{ + /* cx25821_vidioc_g_crop not supported */ + return -EINVAL; +} + +int cx25821_vidioc_querystd(struct file *file, void *priv, v4l2_std_id * norm) +{ + /* medusa does not support video standard sensing of current input */ + *norm = CX25821_NORMS; + + return 0; +} + +int cx25821_is_valid_width(u32 width, v4l2_std_id tvnorm) +{ + if (tvnorm == V4L2_STD_PAL_BG) { + if (width == 352 || width == 720) + return 1; + else + return 0; + } + + if (tvnorm == V4L2_STD_NTSC_M) { + if (width == 320 || width == 352 || width == 720) + return 1; + else + return 0; + } + return 0; +} + +int cx25821_is_valid_height(u32 height, v4l2_std_id tvnorm) +{ + if (tvnorm == V4L2_STD_PAL_BG) { + if (height == 576 || height == 288) + return 1; + else + return 0; + } + + if (tvnorm == V4L2_STD_NTSC_M) { + if (height == 480 || height == 240) + return 1; + else + return 0; + } + + return 0; +} + +static long video_ioctl_upstream9(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct cx25821_fh *fh = file->private_data; + struct cx25821_dev *dev = fh->dev; + int command = 0; + struct upstream_user_struct *data_from_user; + + data_from_user = (struct upstream_user_struct *)arg; + + if (!data_from_user) { + pr_err("%s(): Upstream data is INVALID. Returning\n", __func__); + return 0; + } + + command = data_from_user->command; + + if (command != UPSTREAM_START_VIDEO && command != UPSTREAM_STOP_VIDEO) + return 0; + + dev->input_filename = data_from_user->input_filename; + dev->input_audiofilename = data_from_user->input_filename; + dev->vid_stdname = data_from_user->vid_stdname; + dev->pixel_format = data_from_user->pixel_format; + dev->channel_select = data_from_user->channel_select; + dev->command = data_from_user->command; + + switch (command) { + case UPSTREAM_START_VIDEO: + cx25821_start_upstream_video_ch1(dev, data_from_user); + break; + + case UPSTREAM_STOP_VIDEO: + cx25821_stop_upstream_video_ch1(dev); + break; + } + + return 0; +} + +static long video_ioctl_upstream10(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct cx25821_fh *fh = file->private_data; + struct cx25821_dev *dev = fh->dev; + int command = 0; + struct upstream_user_struct *data_from_user; + + data_from_user = (struct upstream_user_struct *)arg; + + if (!data_from_user) { + pr_err("%s(): Upstream data is INVALID. Returning\n", __func__); + return 0; + } + + command = data_from_user->command; + + if (command != UPSTREAM_START_VIDEO && command != UPSTREAM_STOP_VIDEO) + return 0; + + dev->input_filename_ch2 = data_from_user->input_filename; + dev->input_audiofilename = data_from_user->input_filename; + dev->vid_stdname_ch2 = data_from_user->vid_stdname; + dev->pixel_format_ch2 = data_from_user->pixel_format; + dev->channel_select_ch2 = data_from_user->channel_select; + dev->command_ch2 = data_from_user->command; + + switch (command) { + case UPSTREAM_START_VIDEO: + cx25821_start_upstream_video_ch2(dev, data_from_user); + break; + + case UPSTREAM_STOP_VIDEO: + cx25821_stop_upstream_video_ch2(dev); + break; + } + + return 0; +} + +static long video_ioctl_upstream11(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct cx25821_fh *fh = file->private_data; + struct cx25821_dev *dev = fh->dev; + int command = 0; + struct upstream_user_struct *data_from_user; + + data_from_user = (struct upstream_user_struct *)arg; + + if (!data_from_user) { + pr_err("%s(): Upstream data is INVALID. Returning\n", __func__); + return 0; + } + + command = data_from_user->command; + + if (command != UPSTREAM_START_AUDIO && command != UPSTREAM_STOP_AUDIO) + return 0; + + dev->input_filename = data_from_user->input_filename; + dev->input_audiofilename = data_from_user->input_filename; + dev->vid_stdname = data_from_user->vid_stdname; + dev->pixel_format = data_from_user->pixel_format; + dev->channel_select = data_from_user->channel_select; + dev->command = data_from_user->command; + + switch (command) { + case UPSTREAM_START_AUDIO: + cx25821_start_upstream_audio(dev, data_from_user); + break; + + case UPSTREAM_STOP_AUDIO: + cx25821_stop_upstream_audio(dev); + break; + } + + return 0; +} + +static long video_ioctl_set(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct cx25821_fh *fh = file->private_data; + struct cx25821_dev *dev = fh->dev; + struct downstream_user_struct *data_from_user; + int command; + int width = 720; + int selected_channel = 0; + int pix_format = 0; + int i = 0; + int cif_enable = 0; + int cif_width = 0; + + data_from_user = (struct downstream_user_struct *)arg; + + if (!data_from_user) { + pr_err("%s(): User data is INVALID. Returning\n", __func__); + return 0; + } + + command = data_from_user->command; + + if (command != SET_VIDEO_STD && command != SET_PIXEL_FORMAT + && command != ENABLE_CIF_RESOLUTION && command != REG_READ + && command != REG_WRITE && command != MEDUSA_READ + && command != MEDUSA_WRITE) { + return 0; + } + + switch (command) { + case SET_VIDEO_STD: + if (!strcmp(data_from_user->vid_stdname, "PAL")) + dev->tvnorm = V4L2_STD_PAL_BG; + else + dev->tvnorm = V4L2_STD_NTSC_M; + medusa_set_videostandard(dev); + break; + + case SET_PIXEL_FORMAT: + selected_channel = data_from_user->decoder_select; + pix_format = data_from_user->pixel_format; + + if (!(selected_channel <= 7 && selected_channel >= 0)) { + selected_channel -= 4; + selected_channel = selected_channel % 8; + } + + if (selected_channel >= 0) + cx25821_set_pixel_format(dev, selected_channel, + pix_format); + + break; + + case ENABLE_CIF_RESOLUTION: + selected_channel = data_from_user->decoder_select; + cif_enable = data_from_user->cif_resolution_enable; + cif_width = data_from_user->cif_width; + + if (cif_enable) { + if (dev->tvnorm & V4L2_STD_PAL_BG + || dev->tvnorm & V4L2_STD_PAL_DK) { + width = 352; + } else { + width = cif_width; + if (cif_width != 320 && cif_width != 352) + width = 320; + } + } + + if (!(selected_channel <= 7 && selected_channel >= 0)) { + selected_channel -= 4; + selected_channel = selected_channel % 8; + } + + if (selected_channel <= 7 && selected_channel >= 0) { + dev->channels[selected_channel].use_cif_resolution = + cif_enable; + dev->channels[selected_channel].cif_width = width; + } else { + for (i = 0; i < VID_CHANNEL_NUM; i++) { + dev->channels[i].use_cif_resolution = + cif_enable; + dev->channels[i].cif_width = width; + } + } + + medusa_set_resolution(dev, width, selected_channel); + break; + case REG_READ: + data_from_user->reg_data = cx_read(data_from_user->reg_address); + break; + case REG_WRITE: + cx_write(data_from_user->reg_address, data_from_user->reg_data); + break; + case MEDUSA_READ: + cx25821_i2c_read(&dev->i2c_bus[0], + (u16) data_from_user->reg_address, + &data_from_user->reg_data); + break; + case MEDUSA_WRITE: + cx25821_i2c_write(&dev->i2c_bus[0], + (u16) data_from_user->reg_address, + data_from_user->reg_data); + break; + } + + return 0; +} + +static long cx25821_video_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + struct cx25821_fh *fh = file->private_data; + + /* check to see if it's the video upstream */ + if (fh->channel_id == SRAM_CH09) { + ret = video_ioctl_upstream9(file, cmd, arg); + return ret; + } else if (fh->channel_id == SRAM_CH10) { + ret = video_ioctl_upstream10(file, cmd, arg); + return ret; + } else if (fh->channel_id == SRAM_CH11) { + ret = video_ioctl_upstream11(file, cmd, arg); + ret = video_ioctl_set(file, cmd, arg); + return ret; + } + + return video_ioctl2(file, cmd, arg); +} + +/* exported stuff */ +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = cx25821_video_mmap, + .ioctl = cx25821_video_ioctl, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = cx25821_vidioc_querycap, + .vidioc_enum_fmt_vid_cap = cx25821_vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = cx25821_vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = cx25821_vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = cx25821_vidioc_reqbufs, + .vidioc_querybuf = cx25821_vidioc_querybuf, + .vidioc_qbuf = cx25821_vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, +#ifdef TUNER_FLAG + .vidioc_s_std = cx25821_vidioc_s_std, + .vidioc_querystd = cx25821_vidioc_querystd, +#endif + .vidioc_cropcap = cx25821_vidioc_cropcap, + .vidioc_s_crop = cx25821_vidioc_s_crop, + .vidioc_g_crop = cx25821_vidioc_g_crop, + .vidioc_enum_input = cx25821_vidioc_enum_input, + .vidioc_g_input = cx25821_vidioc_g_input, + .vidioc_s_input = cx25821_vidioc_s_input, + .vidioc_g_ctrl = cx25821_vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_queryctrl = cx25821_vidioc_queryctrl, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_log_status = vidioc_log_status, + .vidioc_g_priority = cx25821_vidioc_g_priority, + .vidioc_s_priority = cx25821_vidioc_s_priority, +#ifdef TUNER_FLAG + .vidioc_g_tuner = cx25821_vidioc_g_tuner, + .vidioc_s_tuner = cx25821_vidioc_s_tuner, + .vidioc_g_frequency = cx25821_vidioc_g_frequency, + .vidioc_s_frequency = cx25821_vidioc_s_frequency, +#endif +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = cx25821_vidioc_g_register, + .vidioc_s_register = cx25821_vidioc_s_register, +#endif +}; + +struct video_device cx25821_videoioctl_template = { + .name = "cx25821-videoioctl", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = CX25821_NORMS, + .current_norm = V4L2_STD_NTSC_M, +}; diff --git a/drivers/media/pci/cx25821/cx25821-video.h b/drivers/media/pci/cx25821/cx25821-video.h new file mode 100644 index 000000000000..c265e35b37c3 --- /dev/null +++ b/drivers/media/pci/cx25821/cx25821-video.h @@ -0,0 +1,186 @@ +/* + * Driver for the Conexant CX25821 PCIe bridge + * + * Copyright (C) 2009 Conexant Systems Inc. + * Authors , + * Based on Steven Toth cx23885 driver + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef CX25821_VIDEO_H_ +#define CX25821_VIDEO_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx25821.h" +#include +#include + +#define TUNER_FLAG + +#define VIDEO_DEBUG 0 + +#define dprintk(level, fmt, arg...) \ +do { \ + if (VIDEO_DEBUG >= level) \ + printk(KERN_DEBUG "%s/0: " fmt, dev->name, ##arg); \ +} while (0) + +/* For IOCTL to identify running upstream */ +#define UPSTREAM_START_VIDEO 700 +#define UPSTREAM_STOP_VIDEO 701 +#define UPSTREAM_START_AUDIO 702 +#define UPSTREAM_STOP_AUDIO 703 +#define UPSTREAM_DUMP_REGISTERS 702 +#define SET_VIDEO_STD 800 +#define SET_PIXEL_FORMAT 1000 +#define ENABLE_CIF_RESOLUTION 1001 + +#define REG_READ 900 +#define REG_WRITE 901 +#define MEDUSA_READ 910 +#define MEDUSA_WRITE 911 + +extern struct sram_channel *channel0; +extern struct sram_channel *channel1; +extern struct sram_channel *channel2; +extern struct sram_channel *channel3; +extern struct sram_channel *channel4; +extern struct sram_channel *channel5; +extern struct sram_channel *channel6; +extern struct sram_channel *channel7; +extern struct sram_channel *channel9; +extern struct sram_channel *channel10; +extern struct sram_channel *channel11; +extern struct video_device cx25821_videoioctl_template; +/* extern const u32 *ctrl_classes[]; */ + +extern unsigned int vid_limit; + +#define FORMAT_FLAGS_PACKED 0x01 +extern struct cx25821_fmt formats[]; +extern struct cx25821_fmt *cx25821_format_by_fourcc(unsigned int fourcc); +extern struct cx25821_data timeout_data[MAX_VID_CHANNEL_NUM]; + +extern void cx25821_video_wakeup(struct cx25821_dev *dev, + struct cx25821_dmaqueue *q, u32 count); + +#ifdef TUNER_FLAG +extern int cx25821_set_tvnorm(struct cx25821_dev *dev, v4l2_std_id norm); +#endif + +extern int cx25821_res_get(struct cx25821_dev *dev, struct cx25821_fh *fh, + unsigned int bit); +extern int cx25821_res_check(struct cx25821_fh *fh, unsigned int bit); +extern int cx25821_res_locked(struct cx25821_fh *fh, unsigned int bit); +extern void cx25821_res_free(struct cx25821_dev *dev, struct cx25821_fh *fh, + unsigned int bits); +extern int cx25821_video_mux(struct cx25821_dev *dev, unsigned int input); +extern int cx25821_start_video_dma(struct cx25821_dev *dev, + struct cx25821_dmaqueue *q, + struct cx25821_buffer *buf, + struct sram_channel *channel); + +extern int cx25821_set_scale(struct cx25821_dev *dev, unsigned int width, + unsigned int height, enum v4l2_field field); +extern int cx25821_video_irq(struct cx25821_dev *dev, int chan_num, u32 status); +extern void cx25821_video_unregister(struct cx25821_dev *dev, int chan_num); +extern int cx25821_video_register(struct cx25821_dev *dev); +extern int cx25821_get_format_size(void); + +extern int cx25821_buffer_setup(struct videobuf_queue *q, unsigned int *count, + unsigned int *size); +extern int cx25821_buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field); +extern void cx25821_buffer_release(struct videobuf_queue *q, + struct videobuf_buffer *vb); +extern struct videobuf_queue *get_queue(struct cx25821_fh *fh); +extern int cx25821_get_resource(struct cx25821_fh *fh, int resource); +extern int cx25821_video_mmap(struct file *file, struct vm_area_struct *vma); +extern int cx25821_vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f); +extern int cx25821_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap); +extern int cx25821_vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f); +extern int cx25821_vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p); +extern int cx25821_vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *p); +extern int cx25821_vidioc_qbuf(struct file *file, void *priv, + struct v4l2_buffer *p); +extern int cx25821_vidioc_s_std(struct file *file, void *priv, + v4l2_std_id *tvnorms); +extern int cx25821_enum_input(struct cx25821_dev *dev, struct v4l2_input *i); +extern int cx25821_vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i); +extern int cx25821_vidioc_g_input(struct file *file, void *priv, + unsigned int *i); +extern int cx25821_vidioc_s_input(struct file *file, void *priv, + unsigned int i); +extern int cx25821_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctl); +extern int cx25821_vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f); +extern int cx25821_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f); +extern int cx25821_set_freq(struct cx25821_dev *dev, struct v4l2_frequency *f); +extern int cx25821_vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f); +extern int cx25821_vidioc_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg); +extern int cx25821_vidioc_s_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg); +extern int cx25821_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t); +extern int cx25821_vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t); + +extern int cx25821_is_valid_width(u32 width, v4l2_std_id tvnorm); +extern int cx25821_is_valid_height(u32 height, v4l2_std_id tvnorm); + +extern int cx25821_vidioc_g_priority(struct file *file, void *f, + enum v4l2_priority *p); +extern int cx25821_vidioc_s_priority(struct file *file, void *f, + enum v4l2_priority prio); + +extern int cx25821_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qctrl); +extern int cx25821_set_control(struct cx25821_dev *dev, + struct v4l2_control *ctrl, int chan_num); + +extern int cx25821_vidioc_cropcap(struct file *file, void *fh, + struct v4l2_cropcap *cropcap); +extern int cx25821_vidioc_s_crop(struct file *file, void *priv, + const struct v4l2_crop *crop); +extern int cx25821_vidioc_g_crop(struct file *file, void *priv, + struct v4l2_crop *crop); + +extern int cx25821_vidioc_querystd(struct file *file, void *priv, + v4l2_std_id *norm); +#endif diff --git a/drivers/media/video/cx25821/cx25821.h b/drivers/media/pci/cx25821/cx25821.h similarity index 100% rename from drivers/media/video/cx25821/cx25821.h rename to drivers/media/pci/cx25821/cx25821.h diff --git a/drivers/media/pci/cx88/Kconfig b/drivers/media/pci/cx88/Kconfig new file mode 100644 index 000000000000..d27fccbf03c4 --- /dev/null +++ b/drivers/media/pci/cx88/Kconfig @@ -0,0 +1,86 @@ +config VIDEO_CX88 + tristate "Conexant 2388x (bt878 successor) support" + depends on VIDEO_DEV && PCI && I2C && RC_CORE + select I2C_ALGOBIT + select VIDEO_BTCX + select VIDEOBUF_DMA_SG + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_WM8775 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + This is a video4linux driver for Conexant 2388x based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called cx8800 + +config VIDEO_CX88_ALSA + tristate "Conexant 2388x DMA audio support" + depends on VIDEO_CX88 && SND + select SND_PCM + ---help--- + This is a video4linux driver for direct (DMA) audio on + Conexant 2388x based TV cards using ALSA. + + It only works with boards with function 01 enabled. + To check if your board supports, use lspci -n. + If supported, you should see 14f1:8801 or 14f1:8811 + PCI device. + + To compile this driver as a module, choose M here: the + module will be called cx88-alsa. + +config VIDEO_CX88_BLACKBIRD + tristate "Blackbird MPEG encoder support (cx2388x + cx23416)" + depends on VIDEO_CX88 + select VIDEO_CX2341X + ---help--- + This adds support for MPEG encoder cards based on the + Blackbird reference design, using the Conexant 2388x + and 23416 chips. + + To compile this driver as a module, choose M here: the + module will be called cx88-blackbird. + +config VIDEO_CX88_DVB + tristate "DVB/ATSC Support for cx2388x based TV cards" + depends on VIDEO_CX88 && DVB_CORE + select VIDEOBUF_DVB + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_OR51132 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX22702 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_NXT200X if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24123 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6421 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT + ---help--- + This adds support for DVB/ATSC cards based on the + Conexant 2388x chip. + + To compile this driver as a module, choose M here: the + module will be called cx88-dvb. + +config VIDEO_CX88_VP3054 + tristate "VP-3054 Secondary I2C Bus Support" + default m + depends on VIDEO_CX88_DVB && DVB_MT352 + ---help--- + This adds DVB-T support for cards based on the + Conexant 2388x chip and the MT352 demodulator, + which also require support for the VP-3054 + Secondary I2C bus, such at DNTV Live! DVB-T Pro. + +config VIDEO_CX88_MPEG + tristate + depends on VIDEO_CX88_DVB || VIDEO_CX88_BLACKBIRD + default y diff --git a/drivers/media/pci/cx88/Makefile b/drivers/media/pci/cx88/Makefile new file mode 100644 index 000000000000..d3679c3ee248 --- /dev/null +++ b/drivers/media/pci/cx88/Makefile @@ -0,0 +1,16 @@ +cx88xx-objs := cx88-cards.o cx88-core.o cx88-i2c.o cx88-tvaudio.o \ + cx88-dsp.o cx88-input.o +cx8800-objs := cx88-video.o cx88-vbi.o +cx8802-objs := cx88-mpeg.o + +obj-$(CONFIG_VIDEO_CX88) += cx88xx.o cx8800.o +obj-$(CONFIG_VIDEO_CX88_MPEG) += cx8802.o +obj-$(CONFIG_VIDEO_CX88_ALSA) += cx88-alsa.o +obj-$(CONFIG_VIDEO_CX88_BLACKBIRD) += cx88-blackbird.o +obj-$(CONFIG_VIDEO_CX88_DVB) += cx88-dvb.o +obj-$(CONFIG_VIDEO_CX88_VP3054) += cx88-vp3054-i2c.o + +ccflags-y += -Idrivers/media/i2c +ccflags-y += -Idrivers/media/tuners +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += -Idrivers/media/dvb-frontends diff --git a/drivers/media/pci/cx88/cx88-alsa.c b/drivers/media/pci/cx88/cx88-alsa.c new file mode 100644 index 000000000000..3aa6856ead3b --- /dev/null +++ b/drivers/media/pci/cx88/cx88-alsa.c @@ -0,0 +1,975 @@ +/* + * + * Support for audio capture + * PCI function #1 of the cx2388x. + * + * (c) 2007 Trent Piepho + * (c) 2005,2006 Ricardo Cerqueira + * (c) 2005 Mauro Carvalho Chehab + * Based on a dummy cx88 module by Gerd Knorr + * Based on dummy.c by Jaroslav Kysela + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx88.h" +#include "cx88-reg.h" + +#define dprintk(level,fmt, arg...) if (debug >= level) \ + printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg) + +#define dprintk_core(level,fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "%s/1: " fmt, chip->core->name , ## arg) + +/**************************************************************************** + Data type declarations - Can be moded to a header file later + ****************************************************************************/ + +struct cx88_audio_buffer { + unsigned int bpl; + struct btcx_riscmem risc; + struct videobuf_dmabuf dma; +}; + +struct cx88_audio_dev { + struct cx88_core *core; + struct cx88_dmaqueue q; + + /* pci i/o */ + struct pci_dev *pci; + + /* audio controls */ + int irq; + + struct snd_card *card; + + spinlock_t reg_lock; + atomic_t count; + + unsigned int dma_size; + unsigned int period_size; + unsigned int num_periods; + + struct videobuf_dmabuf *dma_risc; + + struct cx88_audio_buffer *buf; + + struct snd_pcm_substream *substream; +}; +typedef struct cx88_audio_dev snd_cx88_card_t; + + + +/**************************************************************************** + Module global static vars + ****************************************************************************/ + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static const char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 1}; + +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable cx88x soundcard. default enabled."); + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for cx88x capture interface(s)."); + + +/**************************************************************************** + Module macros + ****************************************************************************/ + +MODULE_DESCRIPTION("ALSA driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Ricardo Cerqueira"); +MODULE_AUTHOR("Mauro Carvalho Chehab "); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CX88_VERSION); + +MODULE_SUPPORTED_DEVICE("{{Conexant,23881}," + "{{Conexant,23882}," + "{{Conexant,23883}"); +static unsigned int debug; +module_param(debug,int,0644); +MODULE_PARM_DESC(debug,"enable debug messages"); + +/**************************************************************************** + Module specific funtions + ****************************************************************************/ + +/* + * BOARD Specific: Sets audio DMA + */ + +static int _cx88_start_audio_dma(snd_cx88_card_t *chip) +{ + struct cx88_audio_buffer *buf = chip->buf; + struct cx88_core *core=chip->core; + const struct sram_channel *audio_ch = &cx88_sram_channels[SRAM_CH25]; + + /* Make sure RISC/FIFO are off before changing FIFO/RISC settings */ + cx_clear(MO_AUD_DMACNTRL, 0x11); + + /* setup fifo + format - out channel */ + cx88_sram_channel_setup(chip->core, audio_ch, buf->bpl, buf->risc.dma); + + /* sets bpl size */ + cx_write(MO_AUDD_LNGTH, buf->bpl); + + /* reset counter */ + cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET); + atomic_set(&chip->count, 0); + + dprintk(1, "Start audio DMA, %d B/line, %d lines/FIFO, %d periods, %d " + "byte buffer\n", buf->bpl, cx_read(audio_ch->cmds_start + 8)>>1, + chip->num_periods, buf->bpl * chip->num_periods); + + /* Enables corresponding bits at AUD_INT_STAT */ + cx_write(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | + AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1); + + /* Clean any pending interrupt bits already set */ + cx_write(MO_AUD_INTSTAT, ~0); + + /* enable audio irqs */ + cx_set(MO_PCI_INTMSK, chip->core->pci_irqmask | PCI_INT_AUDINT); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1<<5)); /* Enables Risc Processor */ + cx_set(MO_AUD_DMACNTRL, 0x11); /* audio downstream FIFO and RISC enable */ + + if (debug) + cx88_sram_channel_dump(chip->core, audio_ch); + + return 0; +} + +/* + * BOARD Specific: Resets audio DMA + */ +static int _cx88_stop_audio_dma(snd_cx88_card_t *chip) +{ + struct cx88_core *core=chip->core; + dprintk(1, "Stopping audio DMA\n"); + + /* stop dma */ + cx_clear(MO_AUD_DMACNTRL, 0x11); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT); + cx_clear(MO_AUD_INTMSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | + AUD_INT_DN_RISCI2 | AUD_INT_DN_RISCI1); + + if (debug) + cx88_sram_channel_dump(chip->core, &cx88_sram_channels[SRAM_CH25]); + + return 0; +} + +#define MAX_IRQ_LOOP 50 + +/* + * BOARD Specific: IRQ dma bits + */ +static const char *cx88_aud_irqs[32] = { + "dn_risci1", "up_risci1", "rds_dn_risc1", /* 0-2 */ + NULL, /* reserved */ + "dn_risci2", "up_risci2", "rds_dn_risc2", /* 4-6 */ + NULL, /* reserved */ + "dnf_of", "upf_uf", "rds_dnf_uf", /* 8-10 */ + NULL, /* reserved */ + "dn_sync", "up_sync", "rds_dn_sync", /* 12-14 */ + NULL, /* reserved */ + "opc_err", "par_err", "rip_err", /* 16-18 */ + "pci_abort", "ber_irq", "mchg_irq" /* 19-21 */ +}; + +/* + * BOARD Specific: Threats IRQ audio specific calls + */ +static void cx8801_aud_irq(snd_cx88_card_t *chip) +{ + struct cx88_core *core = chip->core; + u32 status, mask; + + status = cx_read(MO_AUD_INTSTAT); + mask = cx_read(MO_AUD_INTMSK); + if (0 == (status & mask)) + return; + cx_write(MO_AUD_INTSTAT, status); + if (debug > 1 || (status & mask & ~0xff)) + cx88_print_irqbits(core->name, "irq aud", + cx88_aud_irqs, ARRAY_SIZE(cx88_aud_irqs), + status, mask); + /* risc op code error */ + if (status & AUD_INT_OPC_ERR) { + printk(KERN_WARNING "%s/1: Audio risc op code error\n",core->name); + cx_clear(MO_AUD_DMACNTRL, 0x11); + cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH25]); + } + if (status & AUD_INT_DN_SYNC) { + dprintk(1, "Downstream sync error\n"); + cx_write(MO_AUDD_GPCNTRL, GP_COUNT_CONTROL_RESET); + return; + } + /* risc1 downstream */ + if (status & AUD_INT_DN_RISCI1) { + atomic_set(&chip->count, cx_read(MO_AUDD_GPCNT)); + snd_pcm_period_elapsed(chip->substream); + } + /* FIXME: Any other status should deserve a special handling? */ +} + +/* + * BOARD Specific: Handles IRQ calls + */ +static irqreturn_t cx8801_irq(int irq, void *dev_id) +{ + snd_cx88_card_t *chip = dev_id; + struct cx88_core *core = chip->core; + u32 status; + int loop, handled = 0; + + for (loop = 0; loop < MAX_IRQ_LOOP; loop++) { + status = cx_read(MO_PCI_INTSTAT) & + (core->pci_irqmask | PCI_INT_AUDINT); + if (0 == status) + goto out; + dprintk(3, "cx8801_irq loop %d/%d, status %x\n", + loop, MAX_IRQ_LOOP, status); + handled = 1; + cx_write(MO_PCI_INTSTAT, status); + + if (status & core->pci_irqmask) + cx88_core_irq(core, status); + if (status & PCI_INT_AUDINT) + cx8801_aud_irq(chip); + } + + if (MAX_IRQ_LOOP == loop) { + printk(KERN_ERR + "%s/1: IRQ loop detected, disabling interrupts\n", + core->name); + cx_clear(MO_PCI_INTMSK, PCI_INT_AUDINT); + } + + out: + return IRQ_RETVAL(handled); +} + + +static int dsp_buffer_free(snd_cx88_card_t *chip) +{ + BUG_ON(!chip->dma_size); + + dprintk(2,"Freeing buffer\n"); + videobuf_dma_unmap(&chip->pci->dev, chip->dma_risc); + videobuf_dma_free(chip->dma_risc); + btcx_riscmem_free(chip->pci,&chip->buf->risc); + kfree(chip->buf); + + chip->dma_risc = NULL; + chip->dma_size = 0; + + return 0; +} + +/**************************************************************************** + ALSA PCM Interface + ****************************************************************************/ + +/* + * Digital hardware definition + */ +#define DEFAULT_FIFO_SIZE 4096 +static const struct snd_pcm_hardware snd_cx88_digital_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + /* Analog audio output will be full of clicks and pops if there + are not exactly four lines in the SRAM FIFO buffer. */ + .period_bytes_min = DEFAULT_FIFO_SIZE/4, + .period_bytes_max = DEFAULT_FIFO_SIZE/4, + .periods_min = 1, + .periods_max = 1024, + .buffer_bytes_max = (1024*1024), +}; + +/* + * audio pcm capture open callback + */ +static int snd_cx88_pcm_open(struct snd_pcm_substream *substream) +{ + snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + if (!chip) { + printk(KERN_ERR "BUG: cx88 can't find device struct." + " Can't proceed with open\n"); + return -ENODEV; + } + + err = snd_pcm_hw_constraint_pow2(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto _error; + + chip->substream = substream; + + runtime->hw = snd_cx88_digital_hw; + + if (cx88_sram_channels[SRAM_CH25].fifo_size != DEFAULT_FIFO_SIZE) { + unsigned int bpl = cx88_sram_channels[SRAM_CH25].fifo_size / 4; + bpl &= ~7; /* must be multiple of 8 */ + runtime->hw.period_bytes_min = bpl; + runtime->hw.period_bytes_max = bpl; + } + + return 0; +_error: + dprintk(1,"Error opening PCM!\n"); + return err; +} + +/* + * audio close callback + */ +static int snd_cx88_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * hw_params callback + */ +static int snd_cx88_hw_params(struct snd_pcm_substream * substream, + struct snd_pcm_hw_params * hw_params) +{ + snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + struct videobuf_dmabuf *dma; + + struct cx88_audio_buffer *buf; + int ret; + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + chip->period_size = params_period_bytes(hw_params); + chip->num_periods = params_periods(hw_params); + chip->dma_size = chip->period_size * params_periods(hw_params); + + BUG_ON(!chip->dma_size); + BUG_ON(chip->num_periods & (chip->num_periods-1)); + + buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (NULL == buf) + return -ENOMEM; + + buf->bpl = chip->period_size; + + dma = &buf->dma; + videobuf_dma_init(dma); + ret = videobuf_dma_init_kernel(dma, PCI_DMA_FROMDEVICE, + (PAGE_ALIGN(chip->dma_size) >> PAGE_SHIFT)); + if (ret < 0) + goto error; + + ret = videobuf_dma_map(&chip->pci->dev, dma); + if (ret < 0) + goto error; + + ret = cx88_risc_databuffer(chip->pci, &buf->risc, dma->sglist, + chip->period_size, chip->num_periods, 1); + if (ret < 0) + goto error; + + /* Loop back to start of program */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP|RISC_IRQ1|RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + + chip->buf = buf; + chip->dma_risc = dma; + + substream->runtime->dma_area = chip->dma_risc->vaddr; + substream->runtime->dma_bytes = chip->dma_size; + substream->runtime->dma_addr = 0; + return 0; + +error: + kfree(buf); + return ret; +} + +/* + * hw free callback + */ +static int snd_cx88_hw_free(struct snd_pcm_substream * substream) +{ + + snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + return 0; +} + +/* + * prepare callback + */ +static int snd_cx88_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * trigger callback + */ +static int snd_cx88_card_trigger(struct snd_pcm_substream *substream, int cmd) +{ + snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + int err; + + /* Local interrupts are already disabled by ALSA */ + spin_lock(&chip->reg_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + err=_cx88_start_audio_dma(chip); + break; + case SNDRV_PCM_TRIGGER_STOP: + err=_cx88_stop_audio_dma(chip); + break; + default: + err=-EINVAL; + break; + } + + spin_unlock(&chip->reg_lock); + + return err; +} + +/* + * pointer callback + */ +static snd_pcm_uframes_t snd_cx88_pointer(struct snd_pcm_substream *substream) +{ + snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u16 count; + + count = atomic_read(&chip->count); + +// dprintk(2, "%s - count %d (+%u), period %d, frame %lu\n", __func__, +// count, new, count & (runtime->periods-1), +// runtime->period_size * (count & (runtime->periods-1))); + return runtime->period_size * (count & (runtime->periods-1)); +} + +/* + * page callback (needed for mmap) + */ +static struct page *snd_cx88_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + void *pageptr = substream->runtime->dma_area + offset; + return vmalloc_to_page(pageptr); +} + +/* + * operators + */ +static struct snd_pcm_ops snd_cx88_pcm_ops = { + .open = snd_cx88_pcm_open, + .close = snd_cx88_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cx88_hw_params, + .hw_free = snd_cx88_hw_free, + .prepare = snd_cx88_prepare, + .trigger = snd_cx88_card_trigger, + .pointer = snd_cx88_pointer, + .page = snd_cx88_page, +}; + +/* + * create a PCM device + */ +static int __devinit snd_cx88_pcm(snd_cx88_card_t *chip, int device, const char *name) +{ + int err; + struct snd_pcm *pcm; + + err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + strcpy(pcm->name, name); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cx88_pcm_ops); + + return 0; +} + +/**************************************************************************** + CONTROL INTERFACE + ****************************************************************************/ +static int snd_cx88_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 0; + info->value.integer.max = 0x3f; + + return 0; +} + +static int snd_cx88_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core=chip->core; + int vol = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f), + bal = cx_read(AUD_BAL_CTL); + + value->value.integer.value[(bal & 0x40) ? 0 : 1] = vol; + vol -= (bal & 0x3f); + value->value.integer.value[(bal & 0x40) ? 1 : 0] = vol < 0 ? 0 : vol; + + return 0; +} + +static void snd_cx88_wm8775_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + int left = value->value.integer.value[0]; + int right = value->value.integer.value[1]; + int v, b; + + /* Pass volume & balance onto any WM8775 */ + if (left >= right) { + v = left << 10; + b = left ? (0x8000 * right) / left : 0x8000; + } else { + v = right << 10; + b = right ? 0xffff - (0x8000 * left) / right : 0x8000; + } + wm8775_s_ctrl(core, V4L2_CID_AUDIO_VOLUME, v); + wm8775_s_ctrl(core, V4L2_CID_AUDIO_BALANCE, b); +} + +/* OK - TODO: test it */ +static int snd_cx88_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core=chip->core; + int left, right, v, b; + int changed = 0; + u32 old; + + if (core->board.audio_chip == V4L2_IDENT_WM8775) + snd_cx88_wm8775_volume_put(kcontrol, value); + + left = value->value.integer.value[0] & 0x3f; + right = value->value.integer.value[1] & 0x3f; + b = right - left; + if (b < 0) { + v = 0x3f - left; + b = (-b) | 0x40; + } else { + v = 0x3f - right; + } + /* Do we really know this will always be called with IRQs on? */ + spin_lock_irq(&chip->reg_lock); + old = cx_read(AUD_VOL_CTL); + if (v != (old & 0x3f)) { + cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, (old & ~0x3f) | v); + changed = 1; + } + if ((cx_read(AUD_BAL_CTL) & 0x7f) != b) { + cx_write(AUD_BAL_CTL, b); + changed = 1; + } + spin_unlock_irq(&chip->reg_lock); + + return changed; +} + +static const DECLARE_TLV_DB_SCALE(snd_cx88_db_scale, -6300, 100, 0); + +static const struct snd_kcontrol_new snd_cx88_volume = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "Analog-TV Volume", + .info = snd_cx88_volume_info, + .get = snd_cx88_volume_get, + .put = snd_cx88_volume_put, + .tlv.p = snd_cx88_db_scale, +}; + +static int snd_cx88_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + u32 bit = kcontrol->private_value; + + value->value.integer.value[0] = !(cx_read(AUD_VOL_CTL) & bit); + return 0; +} + +static int snd_cx88_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + u32 bit = kcontrol->private_value; + int ret = 0; + u32 vol; + + spin_lock_irq(&chip->reg_lock); + vol = cx_read(AUD_VOL_CTL); + if (value->value.integer.value[0] != !(vol & bit)) { + vol ^= bit; + cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, vol); + /* Pass mute onto any WM8775 */ + if ((core->board.audio_chip == V4L2_IDENT_WM8775) && + ((1<<6) == bit)) + wm8775_s_ctrl(core, V4L2_CID_AUDIO_MUTE, 0 != (vol & bit)); + ret = 1; + } + spin_unlock_irq(&chip->reg_lock); + return ret; +} + +static const struct snd_kcontrol_new snd_cx88_dac_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Audio-Out Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_cx88_switch_get, + .put = snd_cx88_switch_put, + .private_value = (1<<8), +}; + +static const struct snd_kcontrol_new snd_cx88_source_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog-TV Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_cx88_switch_get, + .put = snd_cx88_switch_put, + .private_value = (1<<6), +}; + +static int snd_cx88_alc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + s32 val; + + val = wm8775_g_ctrl(core, V4L2_CID_AUDIO_LOUDNESS); + value->value.integer.value[0] = val ? 1 : 0; + return 0; +} + +static int snd_cx88_alc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + snd_cx88_card_t *chip = snd_kcontrol_chip(kcontrol); + struct cx88_core *core = chip->core; + struct v4l2_control client_ctl; + + memset(&client_ctl, 0, sizeof(client_ctl)); + client_ctl.value = 0 != value->value.integer.value[0]; + client_ctl.id = V4L2_CID_AUDIO_LOUDNESS; + call_hw(core, WM8775_GID, core, s_ctrl, &client_ctl); + + return 0; +} + +static struct snd_kcontrol_new snd_cx88_alc_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line-In ALC Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_cx88_alc_get, + .put = snd_cx88_alc_put, +}; + +/**************************************************************************** + Basic Flow for Sound Devices + ****************************************************************************/ + +/* + * PCI ID Table - 14f1:8801 and 14f1:8811 means function 1: Audio + * Only boards with eeprom and byte 1 at eeprom=1 have it + */ + +static const struct pci_device_id cx88_audio_pci_tbl[] __devinitdata = { + {0x14f1,0x8801,PCI_ANY_ID,PCI_ANY_ID,0,0,0}, + {0x14f1,0x8811,PCI_ANY_ID,PCI_ANY_ID,0,0,0}, + {0, } +}; +MODULE_DEVICE_TABLE(pci, cx88_audio_pci_tbl); + +/* + * Chip-specific destructor + */ + +static int snd_cx88_free(snd_cx88_card_t *chip) +{ + + if (chip->irq >= 0) + free_irq(chip->irq, chip); + + cx88_core_put(chip->core,chip->pci); + + pci_disable_device(chip->pci); + return 0; +} + +/* + * Component Destructor + */ +static void snd_cx88_dev_free(struct snd_card * card) +{ + snd_cx88_card_t *chip = card->private_data; + + snd_cx88_free(chip); +} + + +/* + * Alsa Constructor - Component probe + */ + +static int devno; +static int __devinit snd_cx88_create(struct snd_card *card, + struct pci_dev *pci, + snd_cx88_card_t **rchip, + struct cx88_core **core_ptr) +{ + snd_cx88_card_t *chip; + struct cx88_core *core; + int err; + unsigned char pci_lat; + + *rchip = NULL; + + err = pci_enable_device(pci); + if (err < 0) + return err; + + pci_set_master(pci); + + chip = card->private_data; + + core = cx88_core_get(pci); + if (NULL == core) { + err = -EINVAL; + return err; + } + + if (!pci_dma_supported(pci,DMA_BIT_MASK(32))) { + dprintk(0, "%s/1: Oops: no 32bit PCI DMA ???\n",core->name); + err = -EIO; + cx88_core_put(core, pci); + return err; + } + + + /* pci init */ + chip->card = card; + chip->pci = pci; + chip->irq = -1; + spin_lock_init(&chip->reg_lock); + + chip->core = core; + + /* get irq */ + err = request_irq(chip->pci->irq, cx8801_irq, + IRQF_SHARED | IRQF_DISABLED, chip->core->name, chip); + if (err < 0) { + dprintk(0, "%s: can't get IRQ %d\n", + chip->core->name, chip->pci->irq); + return err; + } + + /* print pci info */ + pci_read_config_byte(pci, PCI_LATENCY_TIMER, &pci_lat); + + dprintk(1,"ALSA %s/%i: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%llx\n", core->name, devno, + pci_name(pci), pci->revision, pci->irq, + pci_lat, (unsigned long long)pci_resource_start(pci,0)); + + chip->irq = pci->irq; + synchronize_irq(chip->irq); + + snd_card_set_dev(card, &pci->dev); + + *rchip = chip; + *core_ptr = core; + + return 0; +} + +static int __devinit cx88_audio_initdev(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + snd_cx88_card_t *chip; + struct cx88_core *core = NULL; + int err; + + if (devno >= SNDRV_CARDS) + return (-ENODEV); + + if (!enable[devno]) { + ++devno; + return (-ENOENT); + } + + err = snd_card_create(index[devno], id[devno], THIS_MODULE, + sizeof(snd_cx88_card_t), &card); + if (err < 0) + return err; + + card->private_free = snd_cx88_dev_free; + + err = snd_cx88_create(card, pci, &chip, &core); + if (err < 0) + goto error; + + err = snd_cx88_pcm(chip, 0, "CX88 Digital"); + if (err < 0) + goto error; + + err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_volume, chip)); + if (err < 0) + goto error; + err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_dac_switch, chip)); + if (err < 0) + goto error; + err = snd_ctl_add(card, snd_ctl_new1(&snd_cx88_source_switch, chip)); + if (err < 0) + goto error; + + /* If there's a wm8775 then add a Line-In ALC switch */ + if (core->board.audio_chip == V4L2_IDENT_WM8775) + snd_ctl_add(card, snd_ctl_new1(&snd_cx88_alc_switch, chip)); + + strcpy (card->driver, "CX88x"); + sprintf(card->shortname, "Conexant CX%x", pci->device); + sprintf(card->longname, "%s at %#llx", + card->shortname,(unsigned long long)pci_resource_start(pci, 0)); + strcpy (card->mixername, "CX88"); + + dprintk (0, "%s/%i: ALSA support for cx2388x boards\n", + card->driver,devno); + + err = snd_card_register(card); + if (err < 0) + goto error; + pci_set_drvdata(pci,card); + + devno++; + return 0; + +error: + snd_card_free(card); + return err; +} +/* + * ALSA destructor + */ +static void __devexit cx88_audio_finidev(struct pci_dev *pci) +{ + struct cx88_audio_dev *card = pci_get_drvdata(pci); + + snd_card_free((void *)card); + + pci_set_drvdata(pci, NULL); + + devno--; +} + +/* + * PCI driver definition + */ + +static struct pci_driver cx88_audio_pci_driver = { + .name = "cx88_audio", + .id_table = cx88_audio_pci_tbl, + .probe = cx88_audio_initdev, + .remove = __devexit_p(cx88_audio_finidev), +}; + +/**************************************************************************** + LINUX MODULE INIT + ****************************************************************************/ + +/* + * module init + */ +static int __init cx88_audio_init(void) +{ + printk(KERN_INFO "cx2388x alsa driver version %s loaded\n", + CX88_VERSION); + return pci_register_driver(&cx88_audio_pci_driver); +} + +/* + * module remove + */ +static void __exit cx88_audio_fini(void) +{ + pci_unregister_driver(&cx88_audio_pci_driver); +} + +module_init(cx88_audio_init); +module_exit(cx88_audio_fini); diff --git a/drivers/media/pci/cx88/cx88-blackbird.c b/drivers/media/pci/cx88/cx88-blackbird.c new file mode 100644 index 000000000000..def363fb71c0 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-blackbird.c @@ -0,0 +1,1299 @@ +/* + * + * Support for a cx23416 mpeg encoder via cx2388x host port. + * "blackbird" reference design. + * + * (c) 2004 Jelle Foks + * (c) 2004 Gerd Knorr + * + * (c) 2005-2006 Mauro Carvalho Chehab + * - video_ioctl2 conversion + * + * Includes parts from the ivtv driver + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx88.h" + +MODULE_DESCRIPTION("driver for cx2388x/cx23416 based mpeg encoder cards"); +MODULE_AUTHOR("Jelle Foks , Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CX88_VERSION); + +static unsigned int mpegbufs = 32; +module_param(mpegbufs,int,0644); +MODULE_PARM_DESC(mpegbufs,"number of mpeg buffers, range 2-32"); + +static unsigned int debug; +module_param(debug,int,0644); +MODULE_PARM_DESC(debug,"enable debug messages [blackbird]"); + +#define dprintk(level,fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "%s/2-bb: " fmt, dev->core->name , ## arg) + + +/* ------------------------------------------------------------------ */ + +#define BLACKBIRD_FIRM_IMAGE_SIZE 376836 + +/* defines below are from ivtv-driver.h */ + +#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF + +/* Firmware API commands */ +#define IVTV_API_STD_TIMEOUT 500 + +enum blackbird_capture_type { + BLACKBIRD_MPEG_CAPTURE, + BLACKBIRD_RAW_CAPTURE, + BLACKBIRD_RAW_PASSTHRU_CAPTURE +}; +enum blackbird_capture_bits { + BLACKBIRD_RAW_BITS_NONE = 0x00, + BLACKBIRD_RAW_BITS_YUV_CAPTURE = 0x01, + BLACKBIRD_RAW_BITS_PCM_CAPTURE = 0x02, + BLACKBIRD_RAW_BITS_VBI_CAPTURE = 0x04, + BLACKBIRD_RAW_BITS_PASSTHRU_CAPTURE = 0x08, + BLACKBIRD_RAW_BITS_TO_HOST_CAPTURE = 0x10 +}; +enum blackbird_capture_end { + BLACKBIRD_END_AT_GOP, /* stop at the end of gop, generate irq */ + BLACKBIRD_END_NOW, /* stop immediately, no irq */ +}; +enum blackbird_framerate { + BLACKBIRD_FRAMERATE_NTSC_30, /* NTSC: 30fps */ + BLACKBIRD_FRAMERATE_PAL_25 /* PAL: 25fps */ +}; +enum blackbird_stream_port { + BLACKBIRD_OUTPUT_PORT_MEMORY, + BLACKBIRD_OUTPUT_PORT_STREAMING, + BLACKBIRD_OUTPUT_PORT_SERIAL +}; +enum blackbird_data_xfer_status { + BLACKBIRD_MORE_BUFFERS_FOLLOW, + BLACKBIRD_LAST_BUFFER, +}; +enum blackbird_picture_mask { + BLACKBIRD_PICTURE_MASK_NONE, + BLACKBIRD_PICTURE_MASK_I_FRAMES, + BLACKBIRD_PICTURE_MASK_I_P_FRAMES = 0x3, + BLACKBIRD_PICTURE_MASK_ALL_FRAMES = 0x7, +}; +enum blackbird_vbi_mode_bits { + BLACKBIRD_VBI_BITS_SLICED, + BLACKBIRD_VBI_BITS_RAW, +}; +enum blackbird_vbi_insertion_bits { + BLACKBIRD_VBI_BITS_INSERT_IN_XTENSION_USR_DATA, + BLACKBIRD_VBI_BITS_INSERT_IN_PRIVATE_PACKETS = 0x1 << 1, + BLACKBIRD_VBI_BITS_SEPARATE_STREAM = 0x2 << 1, + BLACKBIRD_VBI_BITS_SEPARATE_STREAM_USR_DATA = 0x4 << 1, + BLACKBIRD_VBI_BITS_SEPARATE_STREAM_PRV_DATA = 0x5 << 1, +}; +enum blackbird_dma_unit { + BLACKBIRD_DMA_BYTES, + BLACKBIRD_DMA_FRAMES, +}; +enum blackbird_dma_transfer_status_bits { + BLACKBIRD_DMA_TRANSFER_BITS_DONE = 0x01, + BLACKBIRD_DMA_TRANSFER_BITS_ERROR = 0x04, + BLACKBIRD_DMA_TRANSFER_BITS_LL_ERROR = 0x10, +}; +enum blackbird_pause { + BLACKBIRD_PAUSE_ENCODING, + BLACKBIRD_RESUME_ENCODING, +}; +enum blackbird_copyright { + BLACKBIRD_COPYRIGHT_OFF, + BLACKBIRD_COPYRIGHT_ON, +}; +enum blackbird_notification_type { + BLACKBIRD_NOTIFICATION_REFRESH, +}; +enum blackbird_notification_status { + BLACKBIRD_NOTIFICATION_OFF, + BLACKBIRD_NOTIFICATION_ON, +}; +enum blackbird_notification_mailbox { + BLACKBIRD_NOTIFICATION_NO_MAILBOX = -1, +}; +enum blackbird_field1_lines { + BLACKBIRD_FIELD1_SAA7114 = 0x00EF, /* 239 */ + BLACKBIRD_FIELD1_SAA7115 = 0x00F0, /* 240 */ + BLACKBIRD_FIELD1_MICRONAS = 0x0105, /* 261 */ +}; +enum blackbird_field2_lines { + BLACKBIRD_FIELD2_SAA7114 = 0x00EF, /* 239 */ + BLACKBIRD_FIELD2_SAA7115 = 0x00F0, /* 240 */ + BLACKBIRD_FIELD2_MICRONAS = 0x0106, /* 262 */ +}; +enum blackbird_custom_data_type { + BLACKBIRD_CUSTOM_EXTENSION_USR_DATA, + BLACKBIRD_CUSTOM_PRIVATE_PACKET, +}; +enum blackbird_mute { + BLACKBIRD_UNMUTE, + BLACKBIRD_MUTE, +}; +enum blackbird_mute_video_mask { + BLACKBIRD_MUTE_VIDEO_V_MASK = 0x0000FF00, + BLACKBIRD_MUTE_VIDEO_U_MASK = 0x00FF0000, + BLACKBIRD_MUTE_VIDEO_Y_MASK = 0xFF000000, +}; +enum blackbird_mute_video_shift { + BLACKBIRD_MUTE_VIDEO_V_SHIFT = 8, + BLACKBIRD_MUTE_VIDEO_U_SHIFT = 16, + BLACKBIRD_MUTE_VIDEO_Y_SHIFT = 24, +}; + +/* Registers */ +#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_SPU (0x9050 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_HW_BLOCKS (0x9054 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_VPU (0x9058 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_APU (0xA064 /*| IVTV_REG_OFFSET*/) + +/* ------------------------------------------------------------------ */ + +static void host_setup(struct cx88_core *core) +{ + /* toggle reset of the host */ + cx_write(MO_GPHST_SOFT_RST, 1); + udelay(100); + cx_write(MO_GPHST_SOFT_RST, 0); + udelay(100); + + /* host port setup */ + cx_write(MO_GPHST_WSC, 0x44444444U); + cx_write(MO_GPHST_XFR, 0); + cx_write(MO_GPHST_WDTH, 15); + cx_write(MO_GPHST_HDSHK, 0); + cx_write(MO_GPHST_MUX16, 0x44448888U); + cx_write(MO_GPHST_MODE, 0); +} + +/* ------------------------------------------------------------------ */ + +#define P1_MDATA0 0x390000 +#define P1_MDATA1 0x390001 +#define P1_MDATA2 0x390002 +#define P1_MDATA3 0x390003 +#define P1_MADDR2 0x390004 +#define P1_MADDR1 0x390005 +#define P1_MADDR0 0x390006 +#define P1_RDATA0 0x390008 +#define P1_RDATA1 0x390009 +#define P1_RDATA2 0x39000A +#define P1_RDATA3 0x39000B +#define P1_RADDR0 0x39000C +#define P1_RADDR1 0x39000D +#define P1_RRDWR 0x39000E + +static int wait_ready_gpio0_bit1(struct cx88_core *core, u32 state) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1); + u32 gpio0,need; + + need = state ? 2 : 0; + for (;;) { + gpio0 = cx_read(MO_GP0_IO) & 2; + if (need == gpio0) + return 0; + if (time_after(jiffies,timeout)) + return -1; + udelay(1); + } +} + +static int memory_write(struct cx88_core *core, u32 address, u32 value) +{ + /* Warning: address is dword address (4 bytes) */ + cx_writeb(P1_MDATA0, (unsigned int)value); + cx_writeb(P1_MDATA1, (unsigned int)(value >> 8)); + cx_writeb(P1_MDATA2, (unsigned int)(value >> 16)); + cx_writeb(P1_MDATA3, (unsigned int)(value >> 24)); + cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) | 0x40); + cx_writeb(P1_MADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_MADDR0, (unsigned int)address); + cx_read(P1_MDATA0); + cx_read(P1_MADDR0); + + return wait_ready_gpio0_bit1(core,1); +} + +static int memory_read(struct cx88_core *core, u32 address, u32 *value) +{ + int retval; + u32 val; + + /* Warning: address is dword address (4 bytes) */ + cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) & ~0xC0); + cx_writeb(P1_MADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_MADDR0, (unsigned int)address); + cx_read(P1_MADDR0); + + retval = wait_ready_gpio0_bit1(core,1); + + cx_writeb(P1_MDATA3, 0); + val = (unsigned char)cx_read(P1_MDATA3) << 24; + cx_writeb(P1_MDATA2, 0); + val |= (unsigned char)cx_read(P1_MDATA2) << 16; + cx_writeb(P1_MDATA1, 0); + val |= (unsigned char)cx_read(P1_MDATA1) << 8; + cx_writeb(P1_MDATA0, 0); + val |= (unsigned char)cx_read(P1_MDATA0); + + *value = val; + return retval; +} + +static int register_write(struct cx88_core *core, u32 address, u32 value) +{ + cx_writeb(P1_RDATA0, (unsigned int)value); + cx_writeb(P1_RDATA1, (unsigned int)(value >> 8)); + cx_writeb(P1_RDATA2, (unsigned int)(value >> 16)); + cx_writeb(P1_RDATA3, (unsigned int)(value >> 24)); + cx_writeb(P1_RADDR0, (unsigned int)address); + cx_writeb(P1_RADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_RRDWR, 1); + cx_read(P1_RDATA0); + cx_read(P1_RADDR0); + + return wait_ready_gpio0_bit1(core,1); +} + + +static int register_read(struct cx88_core *core, u32 address, u32 *value) +{ + int retval; + u32 val; + + cx_writeb(P1_RADDR0, (unsigned int)address); + cx_writeb(P1_RADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_RRDWR, 0); + cx_read(P1_RADDR0); + + retval = wait_ready_gpio0_bit1(core,1); + val = (unsigned char)cx_read(P1_RDATA0); + val |= (unsigned char)cx_read(P1_RDATA1) << 8; + val |= (unsigned char)cx_read(P1_RDATA2) << 16; + val |= (unsigned char)cx_read(P1_RDATA3) << 24; + + *value = val; + return retval; +} + +/* ------------------------------------------------------------------ */ + +static int blackbird_mbox_func(void *priv, u32 command, int in, int out, u32 data[CX2341X_MBOX_MAX_DATA]) +{ + struct cx8802_dev *dev = priv; + unsigned long timeout; + u32 value, flag, retval; + int i; + + dprintk(1,"%s: 0x%X\n", __func__, command); + + /* this may not be 100% safe if we can't read any memory location + without side effects */ + memory_read(dev->core, dev->mailbox - 4, &value); + if (value != 0x12345678) { + dprintk(0, "Firmware and/or mailbox pointer not initialized or corrupted\n"); + return -1; + } + + memory_read(dev->core, dev->mailbox, &flag); + if (flag) { + dprintk(0, "ERROR: Mailbox appears to be in use (%x)\n", flag); + return -1; + } + + flag |= 1; /* tell 'em we're working on it */ + memory_write(dev->core, dev->mailbox, flag); + + /* write command + args + fill remaining with zeros */ + memory_write(dev->core, dev->mailbox + 1, command); /* command code */ + memory_write(dev->core, dev->mailbox + 3, IVTV_API_STD_TIMEOUT); /* timeout */ + for (i = 0; i < in; i++) { + memory_write(dev->core, dev->mailbox + 4 + i, data[i]); + dprintk(1, "API Input %d = %d\n", i, data[i]); + } + for (; i < CX2341X_MBOX_MAX_DATA; i++) + memory_write(dev->core, dev->mailbox + 4 + i, 0); + + flag |= 3; /* tell 'em we're done writing */ + memory_write(dev->core, dev->mailbox, flag); + + /* wait for firmware to handle the API command */ + timeout = jiffies + msecs_to_jiffies(10); + for (;;) { + memory_read(dev->core, dev->mailbox, &flag); + if (0 != (flag & 4)) + break; + if (time_after(jiffies,timeout)) { + dprintk(0, "ERROR: API Mailbox timeout\n"); + return -1; + } + udelay(10); + } + + /* read output values */ + for (i = 0; i < out; i++) { + memory_read(dev->core, dev->mailbox + 4 + i, data + i); + dprintk(1, "API Output %d = %d\n", i, data[i]); + } + + memory_read(dev->core, dev->mailbox + 2, &retval); + dprintk(1, "API result = %d\n",retval); + + flag = 0; + memory_write(dev->core, dev->mailbox, flag); + return retval; +} +/* ------------------------------------------------------------------ */ + +/* We don't need to call the API often, so using just one mailbox will probably suffice */ +static int blackbird_api_cmd(struct cx8802_dev *dev, u32 command, + u32 inputcnt, u32 outputcnt, ...) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + va_list vargs; + int i, err; + + va_start(vargs, outputcnt); + + for (i = 0; i < inputcnt; i++) { + data[i] = va_arg(vargs, int); + } + err = blackbird_mbox_func(dev, command, inputcnt, outputcnt, data); + for (i = 0; i < outputcnt; i++) { + int *vptr = va_arg(vargs, int *); + *vptr = data[i]; + } + va_end(vargs); + return err; +} + +static int blackbird_find_mailbox(struct cx8802_dev *dev) +{ + u32 signature[4]={0x12345678, 0x34567812, 0x56781234, 0x78123456}; + int signaturecnt=0; + u32 value; + int i; + + for (i = 0; i < BLACKBIRD_FIRM_IMAGE_SIZE; i++) { + memory_read(dev->core, i, &value); + if (value == signature[signaturecnt]) + signaturecnt++; + else + signaturecnt = 0; + if (4 == signaturecnt) { + dprintk(1, "Mailbox signature found\n"); + return i+1; + } + } + dprintk(0, "Mailbox signature values not found!\n"); + return -1; +} + +static int blackbird_load_firmware(struct cx8802_dev *dev) +{ + static const unsigned char magic[8] = { + 0xa7, 0x0d, 0x00, 0x00, 0x66, 0xbb, 0x55, 0xaa + }; + const struct firmware *firmware; + int i, retval = 0; + u32 value = 0; + u32 checksum = 0; + u32 *dataptr; + + retval = register_write(dev->core, IVTV_REG_VPU, 0xFFFFFFED); + retval |= register_write(dev->core, IVTV_REG_HW_BLOCKS, IVTV_CMD_HW_BLOCKS_RST); + retval |= register_write(dev->core, IVTV_REG_ENC_SDRAM_REFRESH, 0x80000640); + retval |= register_write(dev->core, IVTV_REG_ENC_SDRAM_PRECHARGE, 0x1A); + msleep(1); + retval |= register_write(dev->core, IVTV_REG_APU, 0); + + if (retval < 0) + dprintk(0, "Error with register_write\n"); + + retval = request_firmware(&firmware, CX2341X_FIRM_ENC_FILENAME, + &dev->pci->dev); + + + if (retval != 0) { + dprintk(0, "ERROR: Hotplug firmware request failed (%s).\n", + CX2341X_FIRM_ENC_FILENAME); + dprintk(0, "Please fix your hotplug setup, the board will " + "not work without firmware loaded!\n"); + return -1; + } + + if (firmware->size != BLACKBIRD_FIRM_IMAGE_SIZE) { + dprintk(0, "ERROR: Firmware size mismatch (have %zd, expected %d)\n", + firmware->size, BLACKBIRD_FIRM_IMAGE_SIZE); + release_firmware(firmware); + return -1; + } + + if (0 != memcmp(firmware->data, magic, 8)) { + dprintk(0, "ERROR: Firmware magic mismatch, wrong file?\n"); + release_firmware(firmware); + return -1; + } + + /* transfer to the chip */ + dprintk(1,"Loading firmware ...\n"); + dataptr = (u32*)firmware->data; + for (i = 0; i < (firmware->size >> 2); i++) { + value = le32_to_cpu(*dataptr); + checksum += ~value; + memory_write(dev->core, i, value); + dataptr++; + } + + /* read back to verify with the checksum */ + for (i--; i >= 0; i--) { + memory_read(dev->core, i, &value); + checksum -= ~value; + } + if (checksum) { + dprintk(0, "ERROR: Firmware load failed (checksum mismatch).\n"); + release_firmware(firmware); + return -1; + } + release_firmware(firmware); + dprintk(0, "Firmware upload successful.\n"); + + retval |= register_write(dev->core, IVTV_REG_HW_BLOCKS, IVTV_CMD_HW_BLOCKS_RST); + retval |= register_read(dev->core, IVTV_REG_SPU, &value); + retval |= register_write(dev->core, IVTV_REG_SPU, value & 0xFFFFFFFE); + msleep(1); + + retval |= register_read(dev->core, IVTV_REG_VPU, &value); + retval |= register_write(dev->core, IVTV_REG_VPU, value & 0xFFFFFFE8); + + if (retval < 0) + dprintk(0, "Error with register_write\n"); + return 0; +} + +/** + Settings used by the windows tv app for PVR2000: +================================================================================================================= +Profile | Codec | Resolution | CBR/VBR | Video Qlty | V. Bitrate | Frmrate | Audio Codec | A. Bitrate | A. Mode +----------------------------------------------------------------------------------------------------------------- +MPEG-1 | MPEG1 | 352x288PAL | (CBR) | 1000:Optimal | 2000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo +MPEG-2 | MPEG2 | 720x576PAL | VBR | 600 :Good | 4000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo +VCD | MPEG1 | 352x288PAL | (CBR) | 1000:Optimal | 1150 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo +DVD | MPEG2 | 720x576PAL | VBR | 600 :Good | 6000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo +DB* DVD | MPEG2 | 720x576PAL | CBR | 600 :Good | 6000 Kbps | 25fps | MPG1 Layer2 | 224kbps | Stereo +================================================================================================================= +*DB: "DirectBurn" +*/ + +static void blackbird_codec_settings(struct cx8802_dev *dev) +{ + /* assign frame size */ + blackbird_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0, + dev->height, dev->width); + + dev->cxhdl.width = dev->width; + dev->cxhdl.height = dev->height; + cx2341x_handler_set_50hz(&dev->cxhdl, dev->core->tvnorm & V4L2_STD_625_50); + cx2341x_handler_setup(&dev->cxhdl); +} + +static int blackbird_initialize_codec(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + int version; + int retval; + + dprintk(1,"Initialize codec\n"); + retval = blackbird_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); /* ping */ + if (retval < 0) { + + dev->mpeg_active = 0; + + /* ping was not successful, reset and upload firmware */ + cx_write(MO_SRST_IO, 0); /* SYS_RSTO=0 */ + cx_write(MO_SRST_IO, 1); /* SYS_RSTO=1 */ + retval = blackbird_load_firmware(dev); + if (retval < 0) + return retval; + + retval = blackbird_find_mailbox(dev); + if (retval < 0) + return -1; + + dev->mailbox = retval; + + retval = blackbird_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); /* ping */ + if (retval < 0) { + dprintk(0, "ERROR: Firmware ping failed!\n"); + return -1; + } + + retval = blackbird_api_cmd(dev, CX2341X_ENC_GET_VERSION, 0, 1, &version); + if (retval < 0) { + dprintk(0, "ERROR: Firmware get encoder version failed!\n"); + return -1; + } + dprintk(0, "Firmware version is 0x%08x\n", version); + } + + cx_write(MO_PINMUX_IO, 0x88); /* 656-8bit IO and enable MPEG parallel IO */ + cx_clear(MO_INPUT_FORMAT, 0x100); /* chroma subcarrier lock to normal? */ + cx_write(MO_VBOS_CONTROL, 0x84A00); /* no 656 mode, 8-bit pixels, disable VBI */ + cx_clear(MO_OUTPUT_FORMAT, 0x0008); /* Normal Y-limits to let the mpeg encoder sync */ + + blackbird_codec_settings(dev); + + blackbird_api_cmd(dev, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, 0, + BLACKBIRD_FIELD1_SAA7115, + BLACKBIRD_FIELD2_SAA7115 + ); + + blackbird_api_cmd(dev, CX2341X_ENC_SET_PLACEHOLDER, 12, 0, + BLACKBIRD_CUSTOM_EXTENSION_USR_DATA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + return 0; +} + +static int blackbird_start_codec(struct file *file, void *priv) +{ + struct cx8802_dev *dev = ((struct cx8802_fh *)priv)->dev; + struct cx88_core *core = dev->core; + /* start capturing to the host interface */ + u32 reg; + + int i; + int lastchange = -1; + int lastval = 0; + + for (i = 0; (i < 10) && (i < (lastchange + 4)); i++) { + reg = cx_read(AUD_STATUS); + + dprintk(1, "AUD_STATUS:%dL: 0x%x\n", i, reg); + if ((reg & 0x0F) != lastval) { + lastval = reg & 0x0F; + lastchange = i; + } + msleep(100); + } + + /* unmute audio source */ + cx_clear(AUD_VOL_CTL, (1 << 6)); + + blackbird_api_cmd(dev, CX2341X_ENC_REFRESH_INPUT, 0, 0); + + /* initialize the video input */ + blackbird_api_cmd(dev, CX2341X_ENC_INITIALIZE_INPUT, 0, 0); + + cx2341x_handler_set_busy(&dev->cxhdl, 1); + + /* start capturing to the host interface */ + blackbird_api_cmd(dev, CX2341X_ENC_START_CAPTURE, 2, 0, + BLACKBIRD_MPEG_CAPTURE, + BLACKBIRD_RAW_BITS_NONE + ); + + dev->mpeg_active = 1; + return 0; +} + +static int blackbird_stop_codec(struct cx8802_dev *dev) +{ + blackbird_api_cmd(dev, CX2341X_ENC_STOP_CAPTURE, 3, 0, + BLACKBIRD_END_NOW, + BLACKBIRD_MPEG_CAPTURE, + BLACKBIRD_RAW_BITS_NONE + ); + + cx2341x_handler_set_busy(&dev->cxhdl, 0); + + dev->mpeg_active = 0; + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int bb_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct cx8802_fh *fh = q->priv_data; + + fh->dev->ts_packet_size = 188 * 4; /* was: 512 */ + fh->dev->ts_packet_count = mpegbufs; /* was: 100 */ + + *size = fh->dev->ts_packet_size * fh->dev->ts_packet_count; + *count = fh->dev->ts_packet_count; + return 0; +} + +static int +bb_buf_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8802_fh *fh = q->priv_data; + return cx8802_buf_prepare(q, fh->dev, (struct cx88_buffer*)vb, field); +} + +static void +bb_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx8802_fh *fh = q->priv_data; + cx8802_buf_queue(fh->dev, (struct cx88_buffer*)vb); +} + +static void +bb_buf_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + cx88_free_buffer(q, (struct cx88_buffer*)vb); +} + +static struct videobuf_queue_ops blackbird_qops = { + .buf_setup = bb_buf_setup, + .buf_prepare = bb_buf_prepare, + .buf_queue = bb_buf_queue, + .buf_release = bb_buf_release, +}; + +/* ------------------------------------------------------------------ */ + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx8802_dev *dev = ((struct cx8802_fh *)priv)->dev; + struct cx88_core *core = dev->core; + + strcpy(cap->driver, "cx88_blackbird"); + sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); + cx88_querycap(file, core, cap); + return 0; +} + +static int vidioc_enum_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + strlcpy(f->description, "MPEG", sizeof(f->description)); + f->pixelformat = V4L2_PIX_FMT_MPEG; + f->flags = V4L2_FMT_FLAG_COMPRESSED; + return 0; +} + +static int vidioc_g_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = 188 * 4 * mpegbufs; /* 188 * 4 * 1024; */; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.field = fh->mpegq.field; + dprintk(1, "VIDIOC_G_FMT: w: %d, h: %d, f: %d\n", + dev->width, dev->height, fh->mpegq.field ); + return 0; +} + +static int vidioc_try_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = 188 * 4 * mpegbufs; /* 188 * 4 * 1024; */; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + dprintk(1, "VIDIOC_TRY_FMT: w: %d, h: %d, f: %d\n", + dev->width, dev->height, fh->mpegq.field ); + return 0; +} + +static int vidioc_s_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = 188 * 4 * mpegbufs; /* 188 * 4 * 1024; */; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + dev->width = f->fmt.pix.width; + dev->height = f->fmt.pix.height; + fh->mpegq.field = f->fmt.pix.field; + cx88_set_scale(core, f->fmt.pix.width, f->fmt.pix.height, f->fmt.pix.field); + blackbird_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0, + f->fmt.pix.height, f->fmt.pix.width); + dprintk(1, "VIDIOC_S_FMT: w: %d, h: %d, f: %d\n", + f->fmt.pix.width, f->fmt.pix.height, f->fmt.pix.field ); + return 0; +} + +static int vidioc_reqbufs (struct file *file, void *priv, struct v4l2_requestbuffers *p) +{ + struct cx8802_fh *fh = priv; + return (videobuf_reqbufs(&fh->mpegq, p)); +} + +static int vidioc_querybuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct cx8802_fh *fh = priv; + return (videobuf_querybuf(&fh->mpegq, p)); +} + +static int vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct cx8802_fh *fh = priv; + return (videobuf_qbuf(&fh->mpegq, p)); +} + +static int vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct cx8802_fh *fh = priv; + return (videobuf_dqbuf(&fh->mpegq, p, + file->f_flags & O_NONBLOCK)); +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + + if (!dev->mpeg_active) + blackbird_start_codec(file, fh); + return videobuf_streamon(&fh->mpegq); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + + if (dev->mpeg_active) + blackbird_stop_codec(dev); + return videobuf_streamoff(&fh->mpegq); +} + +static int vidioc_s_frequency (struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx8802_fh *fh = priv; + struct cx8802_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + if (dev->mpeg_active) + blackbird_stop_codec(dev); + + cx88_set_freq (core,f); + blackbird_initialize_codec(dev); + cx88_set_scale(dev->core, dev->width, dev->height, + fh->mpegq.field); + return 0; +} + +static int vidioc_log_status (struct file *file, void *priv) +{ + struct cx8802_dev *dev = ((struct cx8802_fh *)priv)->dev; + struct cx88_core *core = dev->core; + char name[32 + 2]; + + snprintf(name, sizeof(name), "%s/2", core->name); + call_all(core, core, log_status); + v4l2_ctrl_handler_log_status(&dev->cxhdl.hdl, name); + return 0; +} + +static int vidioc_enum_input (struct file *file, void *priv, + struct v4l2_input *i) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + return cx88_enum_input (core,i); +} + +static int vidioc_g_frequency (struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx8802_fh *fh = priv; + struct cx88_core *core = fh->dev->core; + + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + + f->frequency = core->freq; + call_all(core, tuner, g_frequency, f); + + return 0; +} + +static int vidioc_g_input (struct file *file, void *priv, unsigned int *i) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + *i = core->input; + return 0; +} + +static int vidioc_s_input (struct file *file, void *priv, unsigned int i) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + if (i >= 4) + return -EINVAL; + if (0 == INPUT(i).type) + return -EINVAL; + + mutex_lock(&core->lock); + cx88_newstation(core); + cx88_video_mux(core,i); + mutex_unlock(&core->lock); + return 0; +} + +static int vidioc_g_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + u32 reg; + + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + strcpy(t->name, "Television"); + t->capability = V4L2_TUNER_CAP_NORM; + t->rangehigh = 0xffffffffUL; + call_all(core, tuner, g_tuner, t); + + cx88_get_stereo(core ,t); + reg = cx_read(MO_DEVICE_STATUS); + t->signal = (reg & (1<<5)) ? 0xffff : 0x0000; + return 0; +} + +static int vidioc_s_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + if (UNSET == core->board.tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + cx88_set_stereo(core, t->audmode, 1); + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + *tvnorm = core->tvnorm; + return 0; +} + +static int vidioc_s_std (struct file *file, void *priv, v4l2_std_id *id) +{ + struct cx88_core *core = ((struct cx8802_fh *)priv)->dev->core; + + mutex_lock(&core->lock); + cx88_set_tvnorm(core,*id); + mutex_unlock(&core->lock); + return 0; +} + +/* FIXME: cx88_ioctl_hook not implemented */ + +static int mpeg_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct cx8802_dev *dev = video_drvdata(file); + struct cx8802_fh *fh; + struct cx8802_driver *drv = NULL; + int err; + + dprintk( 1, "%s\n", __func__); + + mutex_lock(&dev->core->lock); + + /* Make sure we can acquire the hardware */ + drv = cx8802_get_driver(dev, CX88_MPEG_BLACKBIRD); + if (!drv) { + dprintk(1, "%s: blackbird driver is not loaded\n", __func__); + mutex_unlock(&dev->core->lock); + return -ENODEV; + } + + err = drv->request_acquire(drv); + if (err != 0) { + dprintk(1,"%s: Unable to acquire hardware, %d\n", __func__, err); + mutex_unlock(&dev->core->lock); + return err; + } + + if (!dev->core->mpeg_users && blackbird_initialize_codec(dev) < 0) { + drv->request_release(drv); + mutex_unlock(&dev->core->lock); + return -EINVAL; + } + dprintk(1, "open dev=%s\n", video_device_node_name(vdev)); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) { + drv->request_release(drv); + mutex_unlock(&dev->core->lock); + return -ENOMEM; + } + v4l2_fh_init(&fh->fh, vdev); + file->private_data = fh; + fh->dev = dev; + + videobuf_queue_sg_init(&fh->mpegq, &blackbird_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx88_buffer), + fh, NULL); + + /* FIXME: locking against other video device */ + cx88_set_scale(dev->core, dev->width, dev->height, + fh->mpegq.field); + + dev->core->mpeg_users++; + mutex_unlock(&dev->core->lock); + v4l2_fh_add(&fh->fh); + return 0; +} + +static int mpeg_release(struct file *file) +{ + struct cx8802_fh *fh = file->private_data; + struct cx8802_dev *dev = fh->dev; + struct cx8802_driver *drv = NULL; + + mutex_lock(&dev->core->lock); + + if (dev->mpeg_active && dev->core->mpeg_users == 1) + blackbird_stop_codec(dev); + + cx8802_cancel_buffers(fh->dev); + /* stop mpeg capture */ + videobuf_stop(&fh->mpegq); + + videobuf_mmap_free(&fh->mpegq); + + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + file->private_data = NULL; + kfree(fh); + + /* Make sure we release the hardware */ + drv = cx8802_get_driver(dev, CX88_MPEG_BLACKBIRD); + WARN_ON(!drv); + if (drv) + drv->request_release(drv); + + dev->core->mpeg_users--; + + mutex_unlock(&dev->core->lock); + + return 0; +} + +static ssize_t +mpeg_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct cx8802_fh *fh = file->private_data; + struct cx8802_dev *dev = fh->dev; + + if (!dev->mpeg_active) + blackbird_start_codec(file, fh); + + return videobuf_read_stream(&fh->mpegq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static unsigned int +mpeg_poll(struct file *file, struct poll_table_struct *wait) +{ + unsigned long req_events = poll_requested_events(wait); + struct cx8802_fh *fh = file->private_data; + struct cx8802_dev *dev = fh->dev; + + if (!dev->mpeg_active && (req_events & (POLLIN | POLLRDNORM))) + blackbird_start_codec(file, fh); + + return v4l2_ctrl_poll(file, wait) | videobuf_poll_stream(file, &fh->mpegq, wait); +} + +static int +mpeg_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct cx8802_fh *fh = file->private_data; + + return videobuf_mmap_mapper(&fh->mpegq, vma); +} + +static const struct v4l2_file_operations mpeg_fops = +{ + .owner = THIS_MODULE, + .open = mpeg_open, + .release = mpeg_release, + .read = mpeg_read, + .poll = mpeg_poll, + .mmap = mpeg_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops mpeg_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_log_status = vidioc_log_status, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static struct video_device cx8802_mpeg_template = { + .name = "cx8802", + .fops = &mpeg_fops, + .ioctl_ops = &mpeg_ioctl_ops, + .tvnorms = CX88_NORMS, +}; + +/* ------------------------------------------------------------------ */ + +/* The CX8802 MPEG API will call this when we can use the hardware */ +static int cx8802_blackbird_advise_acquire(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + int err = 0; + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* By default, core setup will leave the cx22702 out of reset, on the bus. + * We left the hardware on power up with the cx22702 active. + * We're being given access to re-arrange the GPIOs. + * Take the bus off the cx22702 and put the cx23416 on it. + */ + /* Toggle reset on cx22702 leaving i2c active */ + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000080); + udelay(50); + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + /* tri-state the cx22702 pins */ + cx_set(MO_GP0_IO, 0x00000004); + udelay(1000); + break; + default: + err = -ENODEV; + } + return err; +} + +/* The CX8802 MPEG API will call this when we need to release the hardware */ +static int cx8802_blackbird_advise_release(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + int err = 0; + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* Exit leaving the cx23416 on the bus */ + break; + default: + err = -ENODEV; + } + return err; +} + +static void blackbird_unregister_video(struct cx8802_dev *dev) +{ + if (dev->mpeg_dev) { + if (video_is_registered(dev->mpeg_dev)) + video_unregister_device(dev->mpeg_dev); + else + video_device_release(dev->mpeg_dev); + dev->mpeg_dev = NULL; + } +} + +static int blackbird_register_video(struct cx8802_dev *dev) +{ + int err; + + dev->mpeg_dev = cx88_vdev_init(dev->core,dev->pci, + &cx8802_mpeg_template,"mpeg"); + dev->mpeg_dev->ctrl_handler = &dev->cxhdl.hdl; + video_set_drvdata(dev->mpeg_dev, dev); + err = video_register_device(dev->mpeg_dev,VFL_TYPE_GRABBER, -1); + if (err < 0) { + printk(KERN_INFO "%s/2: can't register mpeg device\n", + dev->core->name); + return err; + } + printk(KERN_INFO "%s/2: registered device %s [mpeg]\n", + dev->core->name, video_device_node_name(dev->mpeg_dev)); + return 0; +} + +/* ----------------------------------------------------------- */ + +static int cx8802_blackbird_probe(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + struct cx8802_dev *dev = core->dvbdev; + int err; + + dprintk( 1, "%s\n", __func__); + dprintk( 1, " ->being probed by Card=%d Name=%s, PCI %02x:%02x\n", + core->boardnr, + core->name, + core->pci_bus, + core->pci_slot); + + err = -ENODEV; + if (!(core->board.mpeg & CX88_MPEG_BLACKBIRD)) + goto fail_core; + + dev->width = 720; + if (core->tvnorm & V4L2_STD_525_60) { + dev->height = 480; + } else { + dev->height = 576; + } + dev->cxhdl.port = CX2341X_PORT_STREAMING; + dev->cxhdl.width = dev->width; + dev->cxhdl.height = dev->height; + dev->cxhdl.func = blackbird_mbox_func; + dev->cxhdl.priv = dev; + err = cx2341x_handler_init(&dev->cxhdl, 36); + if (err) + goto fail_core; + v4l2_ctrl_add_handler(&dev->cxhdl.hdl, &core->video_hdl, NULL); + + /* blackbird stuff */ + printk("%s/2: cx23416 based mpeg encoder (blackbird reference design)\n", + core->name); + host_setup(dev->core); + + blackbird_initialize_codec(dev); + + /* initial device configuration: needed ? */ +// init_controls(core); + cx88_set_tvnorm(core,core->tvnorm); + cx88_video_mux(core,0); + cx2341x_handler_set_50hz(&dev->cxhdl, dev->height == 576); + cx2341x_handler_setup(&dev->cxhdl); + blackbird_register_video(dev); + + return 0; + + fail_core: + return err; +} + +static int cx8802_blackbird_remove(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + struct cx8802_dev *dev = core->dvbdev; + + /* blackbird */ + blackbird_unregister_video(drv->core->dvbdev); + v4l2_ctrl_handler_free(&dev->cxhdl.hdl); + + return 0; +} + +static struct cx8802_driver cx8802_blackbird_driver = { + .type_id = CX88_MPEG_BLACKBIRD, + .hw_access = CX8802_DRVCTL_SHARED, + .probe = cx8802_blackbird_probe, + .remove = cx8802_blackbird_remove, + .advise_acquire = cx8802_blackbird_advise_acquire, + .advise_release = cx8802_blackbird_advise_release, +}; + +static int __init blackbird_init(void) +{ + printk(KERN_INFO "cx2388x blackbird driver version %s loaded\n", + CX88_VERSION); + return cx8802_register_driver(&cx8802_blackbird_driver); +} + +static void __exit blackbird_fini(void) +{ + cx8802_unregister_driver(&cx8802_blackbird_driver); +} + +module_init(blackbird_init); +module_exit(blackbird_fini); + +module_param_named(video_debug,cx8802_mpeg_template.debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages [video]"); diff --git a/drivers/media/pci/cx88/cx88-cards.c b/drivers/media/pci/cx88/cx88-cards.c new file mode 100644 index 000000000000..0c255248cbcd --- /dev/null +++ b/drivers/media/pci/cx88/cx88-cards.c @@ -0,0 +1,3811 @@ +/* + * + * device driver for Conexant 2388x based TV cards + * card-specific stuff. + * + * (c) 2003 Gerd Knorr [SuSE Labs] + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "cx88.h" +#include "tea5767.h" +#include "xc4000.h" + +static unsigned int tuner[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int card[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; + +module_param_array(tuner, int, NULL, 0444); +module_param_array(radio, int, NULL, 0444); +module_param_array(card, int, NULL, 0444); + +MODULE_PARM_DESC(tuner,"tuner type"); +MODULE_PARM_DESC(radio,"radio tuner type"); +MODULE_PARM_DESC(card,"card type"); + +static unsigned int latency = UNSET; +module_param(latency,int,0444); +MODULE_PARM_DESC(latency,"pci latency timer"); + +static int disable_ir; +module_param(disable_ir, int, 0444); +MODULE_PARM_DESC(disable_ir, "Disable IR support"); + +#define info_printk(core, fmt, arg...) \ + printk(KERN_INFO "%s: " fmt, core->name , ## arg) + +#define warn_printk(core, fmt, arg...) \ + printk(KERN_WARNING "%s: " fmt, core->name , ## arg) + +#define err_printk(core, fmt, arg...) \ + printk(KERN_ERR "%s: " fmt, core->name , ## arg) + + +/* ------------------------------------------------------------------ */ +/* board config info */ + +/* If radio_type !=UNSET, radio_addr should be specified + */ + +static const struct cx88_board cx88_boards[] = { + [CX88_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE2, + .vmux = 1, + },{ + .type = CX88_VMUX_COMPOSITE3, + .vmux = 2, + },{ + .type = CX88_VMUX_COMPOSITE4, + .vmux = 3, + }}, + }, + [CX88_BOARD_HAUPPAUGE] = { + .name = "Hauppauge WinTV 34xxx models", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xff00, // internal decoder + },{ + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0xff01, // mono from tuner chip + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff02, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xff02, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xff01, + }, + }, + [CX88_BOARD_GDI] = { + .name = "GDI Black Gold", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + }, + [CX88_BOARD_PIXELVIEW] = { + .name = "PixelView", + .tuner_type = TUNER_PHILIPS_PAL, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xff00, // internal decoder + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xff10, + }, + }, + [CX88_BOARD_ATI_WONDER_PRO] = { + .name = "ATI TV Wonder Pro", + .tuner_type = TUNER_PHILIPS_4IN1, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x03ff, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x03fe, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x03fe, + }}, + }, + [CX88_BOARD_WINFAST2000XP_EXPERT] = { + .name = "Leadtek Winfast 2000XP Expert", + .tuner_type = TUNER_PHILIPS_4IN1, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00F5e700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5e700, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00F5c700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5c700, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00F5c700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5c700, + .gpio3 = 0x02000000, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00F5d700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5d700, + .gpio3 = 0x02000000, + }, + }, + [CX88_BOARD_AVERTV_STUDIO_303] = { + .name = "AverTV Studio 303 (M126)", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio1 = 0xe09f, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio1 = 0xe05f, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio1 = 0xe05f, + }}, + .radio = { + .gpio1 = 0xe0df, + .type = CX88_RADIO, + }, + }, + [CX88_BOARD_MSI_TVANYWHERE_MASTER] = { + // added gpio values thanks to Michal + // values for PAL from DScaler + .name = "MSI TV-@nywhere Master", + .tuner_type = TUNER_MT2032, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER_NTSC, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff40, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff40, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff40, + }}, + .radio = { + .type = CX88_RADIO, + .vmux = 3, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff20, + }, + }, + [CX88_BOARD_WINFAST_DV2000] = { + .name = "Leadtek Winfast DV2000", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0035e700, + .gpio1 = 0x00003004, + .gpio2 = 0x0035e700, + .gpio3 = 0x02000000, + },{ + + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0035c700, + .gpio1 = 0x00003004, + .gpio2 = 0x0035c700, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0035c700, + .gpio1 = 0x0035c700, + .gpio2 = 0x02000000, + .gpio3 = 0x02000000, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0035d700, + .gpio1 = 0x00007004, + .gpio2 = 0x0035d700, + .gpio3 = 0x02000000, + }, + }, + [CX88_BOARD_LEADTEK_PVR2000] = { + // gpio values for PAL version from regspy by DScaler + .name = "Leadtek PVR 2000", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000bde2, + .audioroute = 1, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0000bde6, + .audioroute = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000bde6, + .audioroute = 1, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0000bd62, + .audioroute = 1, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_IODATA_GVVCP3PCI] = { + .name = "IODATA GV-VCP3/PCI", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE2, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + }, + [CX88_BOARD_PROLINK_PLAYTVPVR] = { + .name = "Prolink PlayTV PVR", + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xbff0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xbff3, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xbff3, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xbff0, + }, + }, + [CX88_BOARD_ASUS_PVR_416] = { + .name = "ASUS PVR-416", + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000fde6, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000fde6, // 0x0000fda6 L,R RCA audio in? + .audioroute = 1, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0000fde2, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_MSI_TVANYWHERE] = { + .name = "MSI TV-@nywhere", + .tuner_type = TUNER_MT2032, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00000fbf, + .gpio2 = 0x0000fc08, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000fbf, + .gpio2 = 0x0000fc68, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000fbf, + .gpio2 = 0x0000fc68, + }}, + }, + [CX88_BOARD_KWORLD_DVB_T] = { + .name = "KWorld/VStream XPert DVB-T", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1] = { + .name = "DViCO FusionHDTV DVB-T1", + .tuner_type = TUNER_ABSENT, /* No analog tuner */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000027df, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000027df, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_LTV883] = { + .name = "KWorld LTV883RF", + .tuner_type = TUNER_TNF_8831BGFF, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x07f8, + },{ + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0x07f9, // mono from tuner chip + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000007fa, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000007fa, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x000007f8, + }, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q] = { + .name = "DViCO FusionHDTV 3 Gold-Q", + .tuner_type = TUNER_MICROTUNE_4042FI5, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + /* + GPIO[0] resets DT3302 DTV receiver + 0 - reset asserted + 1 - normal operation + GPIO[1] mutes analog audio output connector + 0 - enable selected source + 1 - mute + GPIO[2] selects source for analog audio output connector + 0 - analog audio input connector on tab + 1 - analog DAC output from CX23881 chip + GPIO[3] selects RF input connector on tuner module + 0 - RF connector labeled CABLE + 1 - RF connector labeled ANT + GPIO[4] selects high RF for QAM256 mode + 0 - normal RF + 1 - high RF + */ + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0f0d, + },{ + .type = CX88_VMUX_CABLE, + .vmux = 0, + .gpio0 = 0x0f05, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0f00, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0f00, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_DVB_T1] = { + .name = "Hauppauge Nova-T DVB-T", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_CONEXANT_DVB_T1] = { + .name = "Conexant DVB-T reference design", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROVIDEO_PV259] = { + .name = "Provideo PV259", + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .audioroute = 1, + }}, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS] = { + .name = "DViCO FusionHDTV DVB-T Plus", + .tuner_type = TUNER_ABSENT, /* No analog tuner */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000027df, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000027df, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DNTV_LIVE_DVB_T] = { + .name = "digitalnow DNTV Live! DVB-T", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000700, + .gpio2 = 0x00000101, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000700, + .gpio2 = 0x00000101, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PCHDTV_HD3000] = { + .name = "pcHDTV HD3000 HDTV", + .tuner_type = TUNER_THOMSON_DTT761X, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + /* GPIO[2] = audio source for analog audio out connector + * 0 = analog audio input connector + * 1 = CX88 audio DACs + * + * GPIO[7] = input to CX88's audio/chroma ADC + * 0 = FM 10.7 MHz IF + * 1 = Sound 4.5 MHz IF + * + * GPIO[1,5,6] = Oren 51132 pins 27,35,28 respectively + * + * GPIO[16] = Remote control input + */ + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00008484, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00008400, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00008400, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00008404, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_ROSLYN] = { + // entry added by Kaustubh D. Bhalerao + // GPIO values obtained from regspy, courtesy Sean Covel + .name = "Hauppauge WinTV 28xxx (Roslyn) models", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xed1a, + .gpio2 = 0x00ff, + },{ + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0xff01, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff02, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xed92, + .gpio2 = 0x00ff, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xed96, + .gpio2 = 0x00ff, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_DIGITALLOGIC_MEC] = { + .name = "Digital-Logic MICROSPACE Entertainment Center (MEC)", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00009d80, + .audioroute = 1, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00009d76, + .audioroute = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00009d76, + .audioroute = 1, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00009d00, + .audioroute = 1, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_IODATA_GVBCTV7E] = { + .name = "IODATA GV/BCTV7E", + .tuner_type = TUNER_PHILIPS_FQ1286, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 1, + .gpio1 = 0x0000e03f, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 2, + .gpio1 = 0x0000e07f, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 3, + .gpio1 = 0x0000e07f, + }} + }, + [CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO] = { + .name = "PixelView PlayTV Ultra Pro (Stereo)", + /* May be also TUNER_YMEC_TVF_5533MF for NTSC/M or PAL/M */ + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + /* Some variants use a tda9874 and so need the tvaudio module. */ + .audio_chip = V4L2_IDENT_TVAUDIO, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xbf61, /* internal decoder */ + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xbf63, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xbf63, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xbf60, + }, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T] = { + .name = "DViCO FusionHDTV 3 Gold-T", + .tuner_type = TUNER_THOMSON_DTT761X, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x97ed, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x97e9, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x97e9, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_ADSTECH_DVB_T_PCI] = { + .name = "ADS Tech Instant TV DVB-T PCI", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1] = { + .name = "TerraTec Cinergy 1400 DVB-T", + .tuner_type = TUNER_ABSENT, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 2, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD] = { + .name = "DViCO FusionHDTV 5 Gold", + .tuner_type = TUNER_LG_TDVS_H06XF, /* TDVS-H062F */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x87fd, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x87f9, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x87f9, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_AVERMEDIA_ULTRATV_MC_550] = { + .name = "AverMedia UltraTV Media Center PCI 550", + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 0, + .gpio0 = 0x0000cd73, + .audioroute = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 1, + .gpio0 = 0x0000cd73, + .audioroute = 1, + },{ + .type = CX88_VMUX_TELEVISION, + .vmux = 3, + .gpio0 = 0x0000cdb3, + .audioroute = 1, + }}, + .radio = { + .type = CX88_RADIO, + .vmux = 2, + .gpio0 = 0x0000cdf3, + .audioroute = 1, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_KWORLD_VSTREAM_EXPERT_DVD] = { + /* Alexander Wold */ + .name = "Kworld V-Stream Xpert DVD", + .tuner_type = UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x03000000, + .gpio1 = 0x01000000, + .gpio2 = 0x02000000, + .gpio3 = 0x00100000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x03000000, + .gpio1 = 0x01000000, + .gpio2 = 0x02000000, + .gpio3 = 0x00100000, + }}, + }, + [CX88_BOARD_ATI_HDTVWONDER] = { + .name = "ATI HDTV Wonder", + .tuner_type = TUNER_PHILIPS_TUV1236D, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00000ff7, + .gpio1 = 0x000000ff, + .gpio2 = 0x00000001, + .gpio3 = 0x00000000, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000ffe, + .gpio1 = 0x000000ff, + .gpio2 = 0x00000001, + .gpio3 = 0x00000000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000ffe, + .gpio1 = 0x000000ff, + .gpio2 = 0x00000001, + .gpio3 = 0x00000000, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_WINFAST_DTV1000] = { + .name = "WinFast DTV1000-T", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_AVERTV_303] = { + .name = "AVerTV 303 (M126)", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00ff, + .gpio1 = 0xe09f, + .gpio2 = 0x0010, + .gpio3 = 0x0000, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00ff, + .gpio1 = 0xe05f, + .gpio2 = 0x0010, + .gpio3 = 0x0000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00ff, + .gpio1 = 0xe05f, + .gpio2 = 0x0010, + .gpio3 = 0x0000, + }}, + }, + [CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1] = { + .name = "Hauppauge Nova-S-Plus DVB-S", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .audio_chip = V4L2_IDENT_WM8775, + .i2sinputcntl = 2, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + /* 2: Line-In */ + .audioroute = 2, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + /* 2: Line-In */ + .audioroute = 2, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + /* 2: Line-In */ + .audioroute = 2, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_NOVASE2_S1] = { + .name = "Hauppauge Nova-SE2 DVB-S", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_DVBS_100] = { + .name = "KWorld DVB-S 100", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .audio_chip = V4L2_IDENT_WM8775, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + /* 2: Line-In */ + .audioroute = 2, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + /* 2: Line-In */ + .audioroute = 2, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + /* 2: Line-In */ + .audioroute = 2, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_HVR1100] = { + .name = "Hauppauge WinTV-HVR1100 DVB-T/Hybrid", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + /* fixme: Add radio support */ + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_HVR1100LP] = { + .name = "Hauppauge WinTV-HVR1100 DVB-T/Hybrid (Low Profile)", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + }}, + /* fixme: Add radio support */ + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DNTV_LIVE_DVB_T_PRO] = { + .name = "digitalnow DNTV Live! DVB-T Pro", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE | + TDA9887_PORT2_ACTIVE, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xf80808, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xf80808, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xf80808, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xf80808, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_DVB_T_CX22702] = { + /* Kworld V-stream Xpert DVB-T with Thomson tuner */ + /* DTT 7579 Conexant CX22702-19 Conexant CX2388x */ + /* Manenti Marco */ + .name = "KWorld/VStream XPert DVB-T with cx22702", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL] = { + .name = "DViCO FusionHDTV DVB-T Dual Digital", + .tuner_type = TUNER_ABSENT, /* No analog tuner */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000067df, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000067df, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT] = { + .name = "KWorld HardwareMpegTV XPert", + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x3de2, + .gpio2 = 0x00ff, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x3de6, + .audioroute = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x3de6, + .audioroute = 1, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x3de6, + .gpio2 = 0x00ff, + }, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID] = { + .name = "DViCO FusionHDTV DVB-T Hybrid", + .tuner_type = TUNER_THOMSON_FE6600, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000a75f, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0000a75b, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000a75b, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PCHDTV_HD5500] = { + .name = "pcHDTV HD5500 HDTV", + .tuner_type = TUNER_LG_TDVS_H06XF, /* TDVS-H064F */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x87fd, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x87f9, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x87f9, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_KWORLD_MCE200_DELUXE] = { + /* FIXME: tested TV input only, disabled composite, + svideo and radio until they can be tested also. */ + .name = "Kworld MCE 200 Deluxe", + .tuner_type = TUNER_TENA_9533_DI, + .radio_type = UNSET, + .tda9887_conf = TDA9887_PRESENT, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000BDE6 + }}, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_PIXELVIEW_PLAYTV_P7000] = { + /* FIXME: SVideo, Composite and FM inputs are untested */ + .name = "PixelView PlayTV P7000", + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT | TDA9887_PORT1_ACTIVE | + TDA9887_PORT2_ACTIVE, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x5da6, + }}, + .mpeg = CX88_MPEG_BLACKBIRD, + }, + [CX88_BOARD_NPGTECH_REALTV_TOP10FM] = { + .name = "NPG Tech Real TV FM Top 10", + .tuner_type = TUNER_TNF_5335MF, /* Actually a TNF9535 */ + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0788, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x078b, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x078b, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x074a, + }, + }, + [CX88_BOARD_WINFAST_DTV2000H] = { + .name = "WinFast DTV2000 H", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00017304, + .gpio1 = 0x00008203, + .gpio2 = 0x00017304, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0001d701, + .gpio1 = 0x0000b207, + .gpio2 = 0x0001d701, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_COMPOSITE2, + .vmux = 2, + .gpio0 = 0x0001d503, + .gpio1 = 0x0000b207, + .gpio2 = 0x0001d503, + .gpio3 = 0x02000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 3, + .gpio0 = 0x0001d701, + .gpio1 = 0x0000b207, + .gpio2 = 0x0001d701, + .gpio3 = 0x02000000, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00015702, + .gpio1 = 0x0000f207, + .gpio2 = 0x00015702, + .gpio3 = 0x02000000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_WINFAST_DTV2000H_J] = { + .name = "WinFast DTV2000 H rev. J", + .tuner_type = TUNER_PHILIPS_FMD1216MEX_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00017300, + .gpio1 = 0x00008207, + .gpio2 = 0x00000000, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00018300, + .gpio1 = 0x0000f207, + .gpio2 = 0x00017304, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00018301, + .gpio1 = 0x0000f207, + .gpio2 = 0x00017304, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00018301, + .gpio1 = 0x0000f207, + .gpio2 = 0x00017304, + .gpio3 = 0x02000000, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00015702, + .gpio1 = 0x0000f207, + .gpio2 = 0x00015702, + .gpio3 = 0x02000000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_GENIATECH_DVBS] = { + .name = "Geniatech DVB-S", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_HVR3000] = { + .name = "Hauppauge WinTV-HVR3000 TriMode Analog/DVB-S/DVB-T", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .audio_chip = V4L2_IDENT_WM8775, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x84bf, + /* 1: TV Audio / FM Mono */ + .audioroute = 1, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x84bf, + /* 2: Line-In */ + .audioroute = 2, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x84bf, + /* 2: Line-In */ + .audioroute = 2, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x84bf, + /* 4: FM Stereo (untested) */ + .audioroute = 8, + }, + .mpeg = CX88_MPEG_DVB, + .num_frontends = 2, + }, + [CX88_BOARD_NORWOOD_MICRO] = { + .name = "Norwood Micro TV Tuner", + .tuner_type = TUNER_TNF_5335MF, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0709, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x070b, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x070b, + }}, + }, + [CX88_BOARD_TE_DTV_250_OEM_SWANN] = { + .name = "Shenzhen Tungsten Ages Tech TE-DTV-250 / Swann OEM", + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x003fffff, + .gpio1 = 0x00e00000, + .gpio2 = 0x003fffff, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x003fffff, + .gpio1 = 0x00e00000, + .gpio2 = 0x003fffff, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x003fffff, + .gpio1 = 0x00e00000, + .gpio2 = 0x003fffff, + .gpio3 = 0x02000000, + }}, + }, + [CX88_BOARD_HAUPPAUGE_HVR1300] = { + .name = "Hauppauge WinTV-HVR1300 DVB-T/Hybrid MPEG Encoder", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .audio_chip = V4L2_IDENT_WM8775, + /* + * gpio0 as reported by Mike Crash + */ + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xef88, + /* 1: TV Audio / FM Mono */ + .audioroute = 1, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xef88, + /* 2: Line-In */ + .audioroute = 2, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xef88, + /* 2: Line-In */ + .audioroute = 2, + }}, + .mpeg = CX88_MPEG_DVB | CX88_MPEG_BLACKBIRD, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xef88, + /* 4: FM Stereo (untested) */ + .audioroute = 8, + }, + }, + [CX88_BOARD_SAMSUNG_SMT_7020] = { + .name = "Samsung SMT 7020 DVB-S", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_ADSTECH_PTV_390] = { + .name = "ADS Tech Instant Video PCI", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DEBUG, + .vmux = 3, + .gpio0 = 0x04ff, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x07fa, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x07fa, + }}, + }, + [CX88_BOARD_PINNACLE_PCTV_HD_800i] = { + .name = "Pinnacle PCTV HD 800i", + .tuner_type = TUNER_XC5000, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x04fb, + .gpio1 = 0x10ff, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x04fb, + .gpio1 = 0x10ef, + .audioroute = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x04fb, + .gpio1 = 0x10ef, + .audioroute = 1, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO] = { + .name = "DViCO FusionHDTV 5 PCI nano", + /* xc3008 tuner, digital only for now */ + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x000027df, /* Unconfirmed */ + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000027df, /* Unconfirmed */ + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000027df, /* Unconfirmed */ + .audioroute = 1, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PINNACLE_HYBRID_PCTV] = { + .name = "Pinnacle Hybrid PCTV", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x004ff, + .gpio1 = 0x010ff, + .gpio2 = 0x00001, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x004fb, + .gpio1 = 0x010ef, + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x004fb, + .gpio1 = 0x010ef, + .audioroute = 1, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x004ff, + .gpio1 = 0x010ff, + .gpio2 = 0x0ff, + }, + .mpeg = CX88_MPEG_DVB, + }, + /* Terry Wu */ + /* TV Audio : set GPIO 2, 18, 19 value to 0, 1, 0 */ + /* FM Audio : set GPIO 2, 18, 19 value to 0, 0, 0 */ + /* Line-in Audio : set GPIO 2, 18, 19 value to 0, 1, 1 */ + /* Mute Audio : set GPIO 2 value to 1 */ + [CX88_BOARD_WINFAST_TV2000_XP_GLOBAL] = { + .name = "Leadtek TV2000 XP Global", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C04, /* pin 18 = 1, pin 19 = 0 */ + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */ + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */ + .gpio3 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C00, /* pin 18 = 0, pin 19 = 0 */ + .gpio3 = 0x0000, + }, + }, + [CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36] = { + .name = "Leadtek TV2000 XP Global (SC4100)", + .tuner_type = TUNER_XC4000, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C04, /* pin 18 = 1, pin 19 = 0 */ + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */ + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C0C, /* pin 18 = 1, pin 19 = 1 */ + .gpio3 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x0000, + .gpio2 = 0x0C00, /* pin 18 = 0, pin 19 = 0 */ + .gpio3 = 0x0000, + }, + }, + [CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43] = { + .name = "Leadtek TV2000 XP Global (XC4100)", + .tuner_type = TUNER_XC4000, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6040, /* pin 14 = 1, pin 13 = 0 */ + .gpio2 = 0x0000, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 14 = 1, pin 13 = 1 */ + .gpio2 = 0x0000, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 14 = 1, pin 13 = 1 */ + .gpio2 = 0x0000, + .gpio3 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6000, /* pin 14 = 1, pin 13 = 0 */ + .gpio2 = 0x0000, + .gpio3 = 0x0000, + }, + }, + [CX88_BOARD_POWERCOLOR_REAL_ANGEL] = { + .name = "PowerColor RA330", /* Long names may confuse LIRC. */ + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = { { + .type = CX88_VMUX_DEBUG, + .vmux = 3, /* Due to the way the cx88 driver is written, */ + .gpio0 = 0x00ff, /* there is no way to deactivate audio pass- */ + .gpio1 = 0xf39d, /* through without this entry. Furthermore, if */ + .gpio3 = 0x0000, /* the TV mux entry is first, you get audio */ + }, { /* from the tuner on boot for a little while. */ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00ff, + .gpio1 = 0xf35d, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00ff, + .gpio1 = 0xf37d, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000ff, + .gpio1 = 0x0f37d, + .gpio3 = 0x00000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x000ff, + .gpio1 = 0x0f35d, + .gpio3 = 0x00000, + }, + }, + [CX88_BOARD_GENIATECH_X8000_MT] = { + /* Also PowerColor Real Angel 330 and Geniatech X800 OEM */ + .name = "Geniatech X8000-MT DVBT", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00000000, + .gpio1 = 0x00e3e341, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000000, + .gpio1 = 0x00e3e361, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000000, + .gpio1 = 0x00e3e361, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00000000, + .gpio1 = 0x00e3e341, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO] = { + .name = "DViCO FusionHDTV DVB-T PRO", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000067df, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000067df, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD] = { + .name = "DViCO FusionHDTV 7 Gold", + .tuner_type = TUNER_XC5000, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x10df, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x16d9, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x16d9, + }}, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROLINK_PV_8000GT] = { + .name = "Prolink Pixelview MPEG 8000GT", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0ff, + .gpio2 = 0x0cfb, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio2 = 0x0cfb, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio2 = 0x0cfb, + } }, + .radio = { + .type = CX88_RADIO, + .gpio2 = 0x0cfb, + }, + }, + [CX88_BOARD_PROLINK_PV_GLOBAL_XTREME] = { + .name = "Prolink Pixelview Global Extreme", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x04fb, + .gpio1 = 0x04080, + .gpio2 = 0x0cf7, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x04fb, + .gpio1 = 0x04080, + .gpio2 = 0x0cfb, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x04fb, + .gpio1 = 0x04080, + .gpio2 = 0x0cfb, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x04ff, + .gpio1 = 0x04080, + .gpio2 = 0x0cf7, + }, + }, + /* Both radio, analog and ATSC work with this board. + However, for analog to work, s5h1409 gate should be open, + otherwise, tuner-xc3028 won't be detected. + A proper fix require using the newer i2c methods to add + tuner-xc3028 without doing an i2c probe. + */ + [CX88_BOARD_KWORLD_ATSC_120] = { + .name = "Kworld PlusTV HD PCI 120 (ATSC 120)", + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x000000ff, + .gpio1 = 0x0000f35d, + .gpio2 = 0x00000000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000000ff, + .gpio1 = 0x0000f37e, + .gpio2 = 0x00000000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000000ff, + .gpio1 = 0x0000f37e, + .gpio2 = 0x00000000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x000000ff, + .gpio1 = 0x0000f35d, + .gpio2 = 0x00000000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_HVR4000] = { + .name = "Hauppauge WinTV-HVR4000 DVB-S/S2/T/Hybrid", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + .audio_chip = V4L2_IDENT_WM8775, + /* + * GPIO0 (WINTV2000) + * + * Analogue SAT DVB-T + * Antenna 0xc4bf 0xc4bb + * Composite 0xc4bf 0xc4bb + * S-Video 0xc4bf 0xc4bb + * Composite1 0xc4ff 0xc4fb + * S-Video1 0xc4ff 0xc4fb + * + * BIT VALUE FUNCTION GP{x}_IO + * 0 1 I:? + * 1 1 I:? + * 2 1 O:MPEG PORT 0=DVB-T 1=DVB-S + * 3 1 I:? + * 4 1 I:? + * 5 1 I:? + * 6 0 O:INPUT SELECTOR 0=INTERNAL 1=EXPANSION + * 7 1 O:DVB-T DEMOD RESET LOW + * + * BIT VALUE FUNCTION GP{x}_OE + * 8 0 I + * 9 0 I + * a 1 O + * b 0 I + * c 0 I + * d 0 I + * e 1 O + * f 1 O + * + * WM8775 ADC + * + * 1: TV Audio / FM Mono + * 2: Line-In + * 3: Line-In Expansion + * 4: FM Stereo + */ + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xc4bf, + /* 1: TV Audio / FM Mono */ + .audioroute = 1, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xc4bf, + /* 2: Line-In */ + .audioroute = 2, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xc4bf, + /* 2: Line-In */ + .audioroute = 2, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xc4bf, + /* 4: FM Stereo */ + .audioroute = 8, + }, + .mpeg = CX88_MPEG_DVB, + .num_frontends = 2, + }, + [CX88_BOARD_HAUPPAUGE_HVR4000LITE] = { + .name = "Hauppauge WinTV-HVR4000(Lite) DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TEVII_S420] = { + .name = "TeVii S420 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TEVII_S460] = { + .name = "TeVii S460 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TEVII_S464] = { + .name = "TeVii S464 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_OMICOM_SS4_PCI] = { + .name = "Omicom SS4 DVB-S/S2 PCI", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TBS_8910] = { + .name = "TBS 8910 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TBS_8920] = { + .name = "TBS 8920 DVB-S/S2", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + .gpio0 = 0x8080, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROF_6200] = { + .name = "Prof 6200 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROF_7300] = { + .name = "PROF 7300 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_SATTRADE_ST4200] = { + .name = "SATTRADE ST4200 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII] = { + .name = "Terratec Cinergy HT PCI MKII", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x004ff, + .gpio1 = 0x010ff, + .gpio2 = 0x00001, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x004fb, + .gpio1 = 0x010ef, + .audioroute = 1, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x004fb, + .gpio1 = 0x010ef, + .audioroute = 1, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x004ff, + .gpio1 = 0x010ff, + .gpio2 = 0x0ff, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_IRONLY] = { + .name = "Hauppauge WinTV-IR Only", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + }, + [CX88_BOARD_WINFAST_DTV1800H] = { + .name = "Leadtek WinFast DTV1800 Hybrid", + .tuner_type = TUNER_XC2028, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + /* + * GPIO setting + * + * 2: mute (0=off,1=on) + * 12: tuner reset pin + * 13: audio source (0=tuner audio,1=line in) + * 14: FM (0=on,1=off ???) + */ + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6040, /* pin 13 = 0, pin 14 = 1 */ + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */ + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */ + .gpio2 = 0x0000, + } }, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6000, /* pin 13 = 0, pin 14 = 0 */ + .gpio2 = 0x0000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_WINFAST_DTV1800H_XC4000] = { + .name = "Leadtek WinFast DTV1800 H (XC4000)", + .tuner_type = TUNER_XC4000, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + /* + * GPIO setting + * + * 2: mute (0=off,1=on) + * 12: tuner reset pin + * 13: audio source (0=tuner audio,1=line in) + * 14: FM (0=on,1=off ???) + */ + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6040, /* pin 13 = 0, pin 14 = 1 */ + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */ + .gpio2 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6060, /* pin 13 = 1, pin 14 = 1 */ + .gpio2 = 0x0000, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0400, /* pin 2 = 0 */ + .gpio1 = 0x6000, /* pin 13 = 0, pin 14 = 0 */ + .gpio2 = 0x0000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_WINFAST_DTV2000H_PLUS] = { + .name = "Leadtek WinFast DTV2000 H PLUS", + .tuner_type = TUNER_XC4000, + .radio_type = UNSET, + .tuner_addr = 0x61, + .radio_addr = ADDR_UNSET, + /* + * GPIO + * 2: 1: mute audio + * 12: 0: reset XC4000 + * 13: 1: audio input is line in (0: tuner) + * 14: 0: FM radio + * 16: 0: RF input is cable + */ + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0403, + .gpio1 = 0xF0D7, + .gpio2 = 0x0101, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_CABLE, + .vmux = 0, + .gpio0 = 0x0403, + .gpio1 = 0xF0D7, + .gpio2 = 0x0100, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0403, /* was 0x0407 */ + .gpio1 = 0xF0F7, + .gpio2 = 0x0101, + .gpio3 = 0x0000, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0403, /* was 0x0407 */ + .gpio1 = 0xF0F7, + .gpio2 = 0x0101, + .gpio3 = 0x0000, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0403, + .gpio1 = 0xF097, + .gpio2 = 0x0100, + .gpio3 = 0x0000, + }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_PROF_7301] = { + .name = "Prof 7301 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = { { + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TWINHAN_VP1027_DVBS] = { + .name = "Twinhan VP-1027 DVB-S", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, +}; + +/* ------------------------------------------------------------------ */ +/* PCI subsystem IDs */ + +static const struct cx88_subid cx88_subids[] = { + { + .subvendor = 0x0070, + .subdevice = 0x3400, + .card = CX88_BOARD_HAUPPAUGE, + },{ + .subvendor = 0x0070, + .subdevice = 0x3401, + .card = CX88_BOARD_HAUPPAUGE, + },{ + .subvendor = 0x14c7, + .subdevice = 0x0106, + .card = CX88_BOARD_GDI, + },{ + .subvendor = 0x14c7, + .subdevice = 0x0107, /* with mpeg encoder */ + .card = CX88_BOARD_GDI, + },{ + .subvendor = PCI_VENDOR_ID_ATI, + .subdevice = 0x00f8, + .card = CX88_BOARD_ATI_WONDER_PRO, + }, { + .subvendor = PCI_VENDOR_ID_ATI, + .subdevice = 0x00f9, + .card = CX88_BOARD_ATI_WONDER_PRO, + }, { + .subvendor = 0x107d, + .subdevice = 0x6611, + .card = CX88_BOARD_WINFAST2000XP_EXPERT, + },{ + .subvendor = 0x107d, + .subdevice = 0x6613, /* NTSC */ + .card = CX88_BOARD_WINFAST2000XP_EXPERT, + },{ + .subvendor = 0x107d, + .subdevice = 0x6620, + .card = CX88_BOARD_WINFAST_DV2000, + },{ + .subvendor = 0x107d, + .subdevice = 0x663b, + .card = CX88_BOARD_LEADTEK_PVR2000, + },{ + .subvendor = 0x107d, + .subdevice = 0x663c, + .card = CX88_BOARD_LEADTEK_PVR2000, + },{ + .subvendor = 0x1461, + .subdevice = 0x000b, + .card = CX88_BOARD_AVERTV_STUDIO_303, + },{ + .subvendor = 0x1462, + .subdevice = 0x8606, + .card = CX88_BOARD_MSI_TVANYWHERE_MASTER, + },{ + .subvendor = 0x10fc, + .subdevice = 0xd003, + .card = CX88_BOARD_IODATA_GVVCP3PCI, + },{ + .subvendor = 0x1043, + .subdevice = 0x4823, /* with mpeg encoder */ + .card = CX88_BOARD_ASUS_PVR_416, + },{ + .subvendor = 0x17de, + .subdevice = 0x08a6, + .card = CX88_BOARD_KWORLD_DVB_T, + },{ + .subvendor = 0x18ac, + .subdevice = 0xd810, + .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q, + },{ + .subvendor = 0x18ac, + .subdevice = 0xd820, + .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T, + },{ + .subvendor = 0x18ac, + .subdevice = 0xdb00, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1, + },{ + .subvendor = 0x0070, + .subdevice = 0x9002, + .card = CX88_BOARD_HAUPPAUGE_DVB_T1, + },{ + .subvendor = 0x14f1, + .subdevice = 0x0187, + .card = CX88_BOARD_CONEXANT_DVB_T1, + },{ + .subvendor = 0x1540, + .subdevice = 0x2580, + .card = CX88_BOARD_PROVIDEO_PV259, + },{ + .subvendor = 0x18ac, + .subdevice = 0xdb10, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS, + },{ + .subvendor = 0x1554, + .subdevice = 0x4811, + .card = CX88_BOARD_PIXELVIEW, + },{ + .subvendor = 0x7063, + .subdevice = 0x3000, /* HD-3000 card */ + .card = CX88_BOARD_PCHDTV_HD3000, + },{ + .subvendor = 0x17de, + .subdevice = 0xa8a6, + .card = CX88_BOARD_DNTV_LIVE_DVB_T, + },{ + .subvendor = 0x0070, + .subdevice = 0x2801, + .card = CX88_BOARD_HAUPPAUGE_ROSLYN, + },{ + .subvendor = 0x14f1, + .subdevice = 0x0342, + .card = CX88_BOARD_DIGITALLOGIC_MEC, + },{ + .subvendor = 0x10fc, + .subdevice = 0xd035, + .card = CX88_BOARD_IODATA_GVBCTV7E, + },{ + .subvendor = 0x1421, + .subdevice = 0x0334, + .card = CX88_BOARD_ADSTECH_DVB_T_PCI, + },{ + .subvendor = 0x153b, + .subdevice = 0x1166, + .card = CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1, + },{ + .subvendor = 0x18ac, + .subdevice = 0xd500, + .card = CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD, + },{ + .subvendor = 0x1461, + .subdevice = 0x8011, + .card = CX88_BOARD_AVERMEDIA_ULTRATV_MC_550, + },{ + .subvendor = PCI_VENDOR_ID_ATI, + .subdevice = 0xa101, + .card = CX88_BOARD_ATI_HDTVWONDER, + },{ + .subvendor = 0x107d, + .subdevice = 0x665f, + .card = CX88_BOARD_WINFAST_DTV1000, + },{ + .subvendor = 0x1461, + .subdevice = 0x000a, + .card = CX88_BOARD_AVERTV_303, + },{ + .subvendor = 0x0070, + .subdevice = 0x9200, + .card = CX88_BOARD_HAUPPAUGE_NOVASE2_S1, + },{ + .subvendor = 0x0070, + .subdevice = 0x9201, + .card = CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1, + },{ + .subvendor = 0x0070, + .subdevice = 0x9202, + .card = CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1, + },{ + .subvendor = 0x17de, + .subdevice = 0x08b2, + .card = CX88_BOARD_KWORLD_DVBS_100, + },{ + .subvendor = 0x0070, + .subdevice = 0x9400, + .card = CX88_BOARD_HAUPPAUGE_HVR1100, + },{ + .subvendor = 0x0070, + .subdevice = 0x9402, + .card = CX88_BOARD_HAUPPAUGE_HVR1100, + },{ + .subvendor = 0x0070, + .subdevice = 0x9800, + .card = CX88_BOARD_HAUPPAUGE_HVR1100LP, + },{ + .subvendor = 0x0070, + .subdevice = 0x9802, + .card = CX88_BOARD_HAUPPAUGE_HVR1100LP, + },{ + .subvendor = 0x0070, + .subdevice = 0x9001, + .card = CX88_BOARD_HAUPPAUGE_DVB_T1, + },{ + .subvendor = 0x1822, + .subdevice = 0x0025, + .card = CX88_BOARD_DNTV_LIVE_DVB_T_PRO, + },{ + .subvendor = 0x17de, + .subdevice = 0x08a1, + .card = CX88_BOARD_KWORLD_DVB_T_CX22702, + },{ + .subvendor = 0x18ac, + .subdevice = 0xdb50, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL, + },{ + .subvendor = 0x18ac, + .subdevice = 0xdb54, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL, + /* Re-branded DViCO: DigitalNow DVB-T Dual */ + },{ + .subvendor = 0x18ac, + .subdevice = 0xdb11, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS, + /* Re-branded DViCO: UltraView DVB-T Plus */ + }, { + .subvendor = 0x18ac, + .subdevice = 0xdb30, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO, + }, { + .subvendor = 0x17de, + .subdevice = 0x0840, + .card = CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT, + },{ + .subvendor = 0x1421, + .subdevice = 0x0305, + .card = CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT, + },{ + .subvendor = 0x18ac, + .subdevice = 0xdb40, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID, + },{ + .subvendor = 0x18ac, + .subdevice = 0xdb44, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID, + },{ + .subvendor = 0x7063, + .subdevice = 0x5500, + .card = CX88_BOARD_PCHDTV_HD5500, + },{ + .subvendor = 0x17de, + .subdevice = 0x0841, + .card = CX88_BOARD_KWORLD_MCE200_DELUXE, + },{ + .subvendor = 0x1822, + .subdevice = 0x0019, + .card = CX88_BOARD_DNTV_LIVE_DVB_T_PRO, + },{ + .subvendor = 0x1554, + .subdevice = 0x4813, + .card = CX88_BOARD_PIXELVIEW_PLAYTV_P7000, + },{ + .subvendor = 0x14f1, + .subdevice = 0x0842, + .card = CX88_BOARD_NPGTECH_REALTV_TOP10FM, + },{ + .subvendor = 0x107d, + .subdevice = 0x665e, + .card = CX88_BOARD_WINFAST_DTV2000H, + },{ + .subvendor = 0x107d, + .subdevice = 0x6f2b, + .card = CX88_BOARD_WINFAST_DTV2000H_J, + },{ + .subvendor = 0x18ac, + .subdevice = 0xd800, /* FusionHDTV 3 Gold (original revision) */ + .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q, + },{ + .subvendor = 0x14f1, + .subdevice = 0x0084, + .card = CX88_BOARD_GENIATECH_DVBS, + },{ + .subvendor = 0x0070, + .subdevice = 0x1404, + .card = CX88_BOARD_HAUPPAUGE_HVR3000, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdc00, + .card = CX88_BOARD_SAMSUNG_SMT_7020, + }, { + .subvendor = 0x18ac, + .subdevice = 0xdccd, + .card = CX88_BOARD_SAMSUNG_SMT_7020, + },{ + .subvendor = 0x1461, + .subdevice = 0xc111, /* AverMedia M150-D */ + /* This board is known to work with the ASUS PVR416 config */ + .card = CX88_BOARD_ASUS_PVR_416, + },{ + .subvendor = 0xc180, + .subdevice = 0xc980, + .card = CX88_BOARD_TE_DTV_250_OEM_SWANN, + },{ + .subvendor = 0x0070, + .subdevice = 0x9600, + .card = CX88_BOARD_HAUPPAUGE_HVR1300, + },{ + .subvendor = 0x0070, + .subdevice = 0x9601, + .card = CX88_BOARD_HAUPPAUGE_HVR1300, + },{ + .subvendor = 0x0070, + .subdevice = 0x9602, + .card = CX88_BOARD_HAUPPAUGE_HVR1300, + },{ + .subvendor = 0x107d, + .subdevice = 0x6632, + .card = CX88_BOARD_LEADTEK_PVR2000, + },{ + .subvendor = 0x12ab, + .subdevice = 0x2300, /* Club3D Zap TV2100 */ + .card = CX88_BOARD_KWORLD_DVB_T_CX22702, + },{ + .subvendor = 0x0070, + .subdevice = 0x9000, + .card = CX88_BOARD_HAUPPAUGE_DVB_T1, + },{ + .subvendor = 0x0070, + .subdevice = 0x1400, + .card = CX88_BOARD_HAUPPAUGE_HVR3000, + },{ + .subvendor = 0x0070, + .subdevice = 0x1401, + .card = CX88_BOARD_HAUPPAUGE_HVR3000, + },{ + .subvendor = 0x0070, + .subdevice = 0x1402, + .card = CX88_BOARD_HAUPPAUGE_HVR3000, + },{ + .subvendor = 0x1421, + .subdevice = 0x0341, /* ADS Tech InstantTV DVB-S */ + .card = CX88_BOARD_KWORLD_DVBS_100, + },{ + .subvendor = 0x1421, + .subdevice = 0x0390, + .card = CX88_BOARD_ADSTECH_PTV_390, + },{ + .subvendor = 0x11bd, + .subdevice = 0x0051, + .card = CX88_BOARD_PINNACLE_PCTV_HD_800i, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd530, + .card = CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO, + }, { + .subvendor = 0x12ab, + .subdevice = 0x1788, + .card = CX88_BOARD_PINNACLE_HYBRID_PCTV, + }, { + .subvendor = 0x14f1, + .subdevice = 0xea3d, + .card = CX88_BOARD_POWERCOLOR_REAL_ANGEL, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f18, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL, + }, { + .subvendor = 0x14f1, + .subdevice = 0x8852, + .card = CX88_BOARD_GENIATECH_X8000_MT, + }, { + .subvendor = 0x18ac, + .subdevice = 0xd610, + .card = CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD, + }, { + .subvendor = 0x1554, + .subdevice = 0x4935, + .card = CX88_BOARD_PROLINK_PV_8000GT, + }, { + .subvendor = 0x1554, + .subdevice = 0x4976, + .card = CX88_BOARD_PROLINK_PV_GLOBAL_XTREME, + }, { + .subvendor = 0x17de, + .subdevice = 0x08c1, + .card = CX88_BOARD_KWORLD_ATSC_120, + }, { + .subvendor = 0x0070, + .subdevice = 0x6900, + .card = CX88_BOARD_HAUPPAUGE_HVR4000, + }, { + .subvendor = 0x0070, + .subdevice = 0x6904, + .card = CX88_BOARD_HAUPPAUGE_HVR4000, + }, { + .subvendor = 0x0070, + .subdevice = 0x6902, + .card = CX88_BOARD_HAUPPAUGE_HVR4000, + }, { + .subvendor = 0x0070, + .subdevice = 0x6905, + .card = CX88_BOARD_HAUPPAUGE_HVR4000LITE, + }, { + .subvendor = 0x0070, + .subdevice = 0x6906, + .card = CX88_BOARD_HAUPPAUGE_HVR4000LITE, + }, { + .subvendor = 0xd420, + .subdevice = 0x9022, + .card = CX88_BOARD_TEVII_S420, + }, { + .subvendor = 0xd460, + .subdevice = 0x9022, + .card = CX88_BOARD_TEVII_S460, + }, { + .subvendor = 0xd464, + .subdevice = 0x9022, + .card = CX88_BOARD_TEVII_S464, + }, { + .subvendor = 0xA044, + .subdevice = 0x2011, + .card = CX88_BOARD_OMICOM_SS4_PCI, + }, { + .subvendor = 0x8910, + .subdevice = 0x8888, + .card = CX88_BOARD_TBS_8910, + }, { + .subvendor = 0x8920, + .subdevice = 0x8888, + .card = CX88_BOARD_TBS_8920, + }, { + .subvendor = 0xb022, + .subdevice = 0x3022, + .card = CX88_BOARD_PROF_6200, + }, { + .subvendor = 0xB033, + .subdevice = 0x3033, + .card = CX88_BOARD_PROF_7300, + }, { + .subvendor = 0xb200, + .subdevice = 0x4200, + .card = CX88_BOARD_SATTRADE_ST4200, + }, { + .subvendor = 0x153b, + .subdevice = 0x1177, + .card = CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII, + }, { + .subvendor = 0x0070, + .subdevice = 0x9290, + .card = CX88_BOARD_HAUPPAUGE_IRONLY, + }, { + .subvendor = 0x107d, + .subdevice = 0x6654, + .card = CX88_BOARD_WINFAST_DTV1800H, + }, { + /* WinFast DTV1800 H with XC4000 tuner */ + .subvendor = 0x107d, + .subdevice = 0x6f38, + .card = CX88_BOARD_WINFAST_DTV1800H_XC4000, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f42, + .card = CX88_BOARD_WINFAST_DTV2000H_PLUS, + }, { + /* PVR2000 PAL Model [107d:6630] */ + .subvendor = 0x107d, + .subdevice = 0x6630, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* PVR2000 PAL Model [107d:6638] */ + .subvendor = 0x107d, + .subdevice = 0x6638, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* PVR2000 NTSC Model [107d:6631] */ + .subvendor = 0x107d, + .subdevice = 0x6631, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* PVR2000 NTSC Model [107d:6637] */ + .subvendor = 0x107d, + .subdevice = 0x6637, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* PVR2000 NTSC Model [107d:663d] */ + .subvendor = 0x107d, + .subdevice = 0x663d, + .card = CX88_BOARD_LEADTEK_PVR2000, + }, { + /* DV2000 NTSC Model [107d:6621] */ + .subvendor = 0x107d, + .subdevice = 0x6621, + .card = CX88_BOARD_WINFAST_DV2000, + }, { + /* TV2000 XP Global [107d:6618] */ + .subvendor = 0x107d, + .subdevice = 0x6618, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL, + }, { + /* TV2000 XP Global [107d:6618] */ + .subvendor = 0x107d, + .subdevice = 0x6619, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL, + }, { + /* WinFast TV2000 XP Global with XC4000 tuner */ + .subvendor = 0x107d, + .subdevice = 0x6f36, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36, + }, { + /* WinFast TV2000 XP Global with XC4000 tuner and different GPIOs */ + .subvendor = 0x107d, + .subdevice = 0x6f43, + .card = CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43, + }, { + .subvendor = 0xb034, + .subdevice = 0x3034, + .card = CX88_BOARD_PROF_7301, + }, { + .subvendor = 0x1822, + .subdevice = 0x0023, + .card = CX88_BOARD_TWINHAN_VP1027_DVBS, + }, +}; + +/* ----------------------------------------------------------------------- */ +/* some leadtek specific stuff */ + +static void leadtek_eeprom(struct cx88_core *core, u8 *eeprom_data) +{ + if (eeprom_data[4] != 0x7d || + eeprom_data[5] != 0x10 || + eeprom_data[7] != 0x66) { + warn_printk(core, "Leadtek eeprom invalid.\n"); + return; + } + + /* Terry Wu */ + switch (eeprom_data[6]) { + case 0x13: /* SSID 6613 for TV2000 XP Expert NTSC Model */ + case 0x21: /* SSID 6621 for DV2000 NTSC Model */ + case 0x31: /* SSID 6631 for PVR2000 NTSC Model */ + case 0x37: /* SSID 6637 for PVR2000 NTSC Model */ + case 0x3d: /* SSID 6637 for PVR2000 NTSC Model */ + core->board.tuner_type = TUNER_PHILIPS_FM1236_MK3; + break; + default: + core->board.tuner_type = TUNER_PHILIPS_FM1216ME_MK3; + break; + } + + info_printk(core, "Leadtek Winfast 2000XP Expert config: " + "tuner=%d, eeprom[0]=0x%02x\n", + core->board.tuner_type, eeprom_data[0]); +} + +static void hauppauge_eeprom(struct cx88_core *core, u8 *eeprom_data) +{ + struct tveeprom tv; + + tveeprom_hauppauge_analog(&core->i2c_client, &tv, eeprom_data); + core->board.tuner_type = tv.tuner_type; + core->tuner_formats = tv.tuner_formats; + core->board.radio.type = tv.has_radio ? CX88_RADIO : 0; + + /* Make sure we support the board model */ + switch (tv.model) + { + case 14009: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in) */ + case 14019: /* WinTV-HVR3000 (Retail, IR Blaster, b/panel video, 3.5mm audio in) */ + case 14029: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - 880 bridge) */ + case 14109: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - low profile) */ + case 14129: /* WinTV-HVR3000 (Retail, IR, b/panel video, 3.5mm audio in - 880 bridge - LP) */ + case 14559: /* WinTV-HVR3000 (OEM, no IR, b/panel video, 3.5mm audio in) */ + case 14569: /* WinTV-HVR3000 (OEM, no IR, no back panel video) */ + case 14659: /* WinTV-HVR3000 (OEM, no IR, b/panel video, RCA audio in - Low profile) */ + case 14669: /* WinTV-HVR3000 (OEM, no IR, no b/panel video - Low profile) */ + case 28552: /* WinTV-PVR 'Roslyn' (No IR) */ + case 34519: /* WinTV-PCI-FM */ + case 69009: + /* WinTV-HVR4000 (DVBS/S2/T, Video and IR, back panel inputs) */ + case 69100: /* WinTV-HVR4000LITE (DVBS/S2, IR) */ + case 69500: /* WinTV-HVR4000LITE (DVBS/S2, No IR) */ + case 69559: + /* WinTV-HVR4000 (DVBS/S2/T, Video no IR, back panel inputs) */ + case 69569: /* WinTV-HVR4000 (DVBS/S2/T, Video no IR) */ + case 90002: /* Nova-T-PCI (9002) */ + case 92001: /* Nova-S-Plus (Video and IR) */ + case 92002: /* Nova-S-Plus (Video and IR) */ + case 90003: /* Nova-T-PCI (9002 No RF out) */ + case 90500: /* Nova-T-PCI (oem) */ + case 90501: /* Nova-T-PCI (oem/IR) */ + case 92000: /* Nova-SE2 (OEM, No Video or IR) */ + case 92900: /* WinTV-IROnly (No analog or digital Video inputs) */ + case 94009: /* WinTV-HVR1100 (Video and IR Retail) */ + case 94501: /* WinTV-HVR1100 (Video and IR OEM) */ + case 96009: /* WinTV-HVR1300 (PAL Video, MPEG Video and IR RX) */ + case 96019: /* WinTV-HVR1300 (PAL Video, MPEG Video and IR RX/TX) */ + case 96559: /* WinTV-HVR1300 (PAL Video, MPEG Video no IR) */ + case 96569: /* WinTV-HVR1300 () */ + case 96659: /* WinTV-HVR1300 () */ + case 98559: /* WinTV-HVR1100LP (Video no IR, Retail - Low Profile) */ + /* known */ + break; + case CX88_BOARD_SAMSUNG_SMT_7020: + cx_set(MO_GP0_IO, 0x008989FF); + break; + default: + warn_printk(core, "warning: unknown hauppauge model #%d\n", + tv.model); + break; + } + + info_printk(core, "hauppauge eeprom: model=%d\n", tv.model); +} + +/* ----------------------------------------------------------------------- */ +/* some GDI (was: Modular Technology) specific stuff */ + +static const struct { + int id; + int fm; + const char *name; +} gdi_tuner[] = { + [ 0x01 ] = { .id = TUNER_ABSENT, + .name = "NTSC_M" }, + [ 0x02 ] = { .id = TUNER_ABSENT, + .name = "PAL_B" }, + [ 0x03 ] = { .id = TUNER_ABSENT, + .name = "PAL_I" }, + [ 0x04 ] = { .id = TUNER_ABSENT, + .name = "PAL_D" }, + [ 0x05 ] = { .id = TUNER_ABSENT, + .name = "SECAM" }, + + [ 0x10 ] = { .id = TUNER_ABSENT, + .fm = 1, + .name = "TEMIC_4049" }, + [ 0x11 ] = { .id = TUNER_TEMIC_4136FY5, + .name = "TEMIC_4136" }, + [ 0x12 ] = { .id = TUNER_ABSENT, + .name = "TEMIC_4146" }, + + [ 0x20 ] = { .id = TUNER_PHILIPS_FQ1216ME, + .fm = 1, + .name = "PHILIPS_FQ1216_MK3" }, + [ 0x21 ] = { .id = TUNER_ABSENT, .fm = 1, + .name = "PHILIPS_FQ1236_MK3" }, + [ 0x22 ] = { .id = TUNER_ABSENT, + .name = "PHILIPS_FI1236_MK3" }, + [ 0x23 ] = { .id = TUNER_ABSENT, + .name = "PHILIPS_FI1216_MK3" }, +}; + +static void gdi_eeprom(struct cx88_core *core, u8 *eeprom_data) +{ + const char *name = (eeprom_data[0x0d] < ARRAY_SIZE(gdi_tuner)) + ? gdi_tuner[eeprom_data[0x0d]].name : NULL; + + info_printk(core, "GDI: tuner=%s\n", name ? name : "unknown"); + if (NULL == name) + return; + core->board.tuner_type = gdi_tuner[eeprom_data[0x0d]].id; + core->board.radio.type = gdi_tuner[eeprom_data[0x0d]].fm ? + CX88_RADIO : 0; +} + +/* ------------------------------------------------------------------- */ +/* some Divco specific stuff */ +static int cx88_dvico_xc2028_callback(struct cx88_core *core, + int command, int arg) +{ + switch (command) { + case XC2028_TUNER_RESET: + switch (core->boardnr) { + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + /* GPIO-4 xc3028 tuner */ + + cx_set(MO_GP0_IO, 0x00001000); + cx_clear(MO_GP0_IO, 0x00000010); + msleep(100); + cx_set(MO_GP0_IO, 0x00000010); + msleep(100); + break; + default: + cx_write(MO_GP0_IO, 0x101000); + mdelay(5); + cx_set(MO_GP0_IO, 0x101010); + } + break; + default: + return -EINVAL; + } + + return 0; +} + + +/* ----------------------------------------------------------------------- */ +/* some Geniatech specific stuff */ + +static int cx88_xc3028_geniatech_tuner_callback(struct cx88_core *core, + int command, int mode) +{ + switch (command) { + case XC2028_TUNER_RESET: + switch (INPUT(core->input).type) { + case CX88_RADIO: + break; + case CX88_VMUX_DVB: + cx_write(MO_GP1_IO, 0x030302); + mdelay(50); + break; + default: + cx_write(MO_GP1_IO, 0x030301); + mdelay(50); + } + cx_write(MO_GP1_IO, 0x101010); + mdelay(50); + cx_write(MO_GP1_IO, 0x101000); + mdelay(50); + cx_write(MO_GP1_IO, 0x101010); + mdelay(50); + return 0; + } + return -EINVAL; +} + +static int cx88_xc3028_winfast1800h_callback(struct cx88_core *core, + int command, int arg) +{ + switch (command) { + case XC2028_TUNER_RESET: + /* GPIO 12 (xc3028 tuner reset) */ + cx_set(MO_GP1_IO, 0x1010); + mdelay(50); + cx_clear(MO_GP1_IO, 0x10); + mdelay(75); + cx_set(MO_GP1_IO, 0x10); + mdelay(75); + return 0; + } + return -EINVAL; +} + +static int cx88_xc4000_winfast2000h_plus_callback(struct cx88_core *core, + int command, int arg) +{ + switch (command) { + case XC4000_TUNER_RESET: + /* GPIO 12 (xc4000 tuner reset) */ + cx_set(MO_GP1_IO, 0x1010); + mdelay(50); + cx_clear(MO_GP1_IO, 0x10); + mdelay(75); + cx_set(MO_GP1_IO, 0x10); + mdelay(75); + return 0; + } + return -EINVAL; +} + +/* ------------------------------------------------------------------- */ +/* some Divco specific stuff */ +static int cx88_pv_8000gt_callback(struct cx88_core *core, + int command, int arg) +{ + switch (command) { + case XC2028_TUNER_RESET: + cx_write(MO_GP2_IO, 0xcf7); + mdelay(50); + cx_write(MO_GP2_IO, 0xef5); + mdelay(50); + cx_write(MO_GP2_IO, 0xcf7); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ +/* some DViCO specific stuff */ + +static void dvico_fusionhdtv_hybrid_init(struct cx88_core *core) +{ + struct i2c_msg msg = { .addr = 0x45, .flags = 0 }; + int i, err; + static u8 init_bufs[13][5] = { + { 0x10, 0x00, 0x20, 0x01, 0x03 }, + { 0x10, 0x10, 0x01, 0x00, 0x21 }, + { 0x10, 0x10, 0x10, 0x00, 0xCA }, + { 0x10, 0x10, 0x12, 0x00, 0x08 }, + { 0x10, 0x10, 0x13, 0x00, 0x0A }, + { 0x10, 0x10, 0x16, 0x01, 0xC0 }, + { 0x10, 0x10, 0x22, 0x01, 0x3D }, + { 0x10, 0x10, 0x73, 0x01, 0x2E }, + { 0x10, 0x10, 0x72, 0x00, 0xC5 }, + { 0x10, 0x10, 0x71, 0x01, 0x97 }, + { 0x10, 0x10, 0x70, 0x00, 0x0F }, + { 0x10, 0x10, 0xB0, 0x00, 0x01 }, + { 0x03, 0x0C }, + }; + + for (i = 0; i < ARRAY_SIZE(init_bufs); i++) { + msg.buf = init_bufs[i]; + msg.len = (i != 12 ? 5 : 2); + err = i2c_transfer(&core->i2c_adap, &msg, 1); + if (err != 1) { + warn_printk(core, "dvico_fusionhdtv_hybrid_init buf %d " + "failed (err = %d)!\n", i, err); + return; + } + } +} + +static int cx88_xc2028_tuner_callback(struct cx88_core *core, + int command, int arg) +{ + /* Board-specific callbacks */ + switch (core->boardnr) { + case CX88_BOARD_POWERCOLOR_REAL_ANGEL: + case CX88_BOARD_GENIATECH_X8000_MT: + case CX88_BOARD_KWORLD_ATSC_120: + return cx88_xc3028_geniatech_tuner_callback(core, + command, arg); + case CX88_BOARD_PROLINK_PV_8000GT: + case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME: + return cx88_pv_8000gt_callback(core, command, arg); + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO: + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + return cx88_dvico_xc2028_callback(core, command, arg); + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL: + case CX88_BOARD_WINFAST_DTV1800H: + return cx88_xc3028_winfast1800h_callback(core, command, arg); + } + + switch (command) { + case XC2028_TUNER_RESET: + switch (INPUT(core->input).type) { + case CX88_RADIO: + info_printk(core, "setting GPIO to radio!\n"); + cx_write(MO_GP0_IO, 0x4ff); + mdelay(250); + cx_write(MO_GP2_IO, 0xff); + mdelay(250); + break; + case CX88_VMUX_DVB: /* Digital TV*/ + default: /* Analog TV */ + info_printk(core, "setting GPIO to TV!\n"); + break; + } + cx_write(MO_GP1_IO, 0x101010); + mdelay(250); + cx_write(MO_GP1_IO, 0x101000); + mdelay(250); + cx_write(MO_GP1_IO, 0x101010); + mdelay(250); + return 0; + } + return -EINVAL; +} + +static int cx88_xc4000_tuner_callback(struct cx88_core *core, + int command, int arg) +{ + /* Board-specific callbacks */ + switch (core->boardnr) { + case CX88_BOARD_WINFAST_DTV1800H_XC4000: + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43: + return cx88_xc4000_winfast2000h_plus_callback(core, + command, arg); + } + return -EINVAL; +} + +/* ----------------------------------------------------------------------- */ +/* Tuner callback function. Currently only needed for the Pinnacle * + * PCTV HD 800i with an xc5000 sillicon tuner. This is used for both * + * analog tuner attach (tuner-core.c) and dvb tuner attach (cx88-dvb.c) */ + +static int cx88_xc5000_tuner_callback(struct cx88_core *core, + int command, int arg) +{ + switch (core->boardnr) { + case CX88_BOARD_PINNACLE_PCTV_HD_800i: + if (command == 0) { /* This is the reset command from xc5000 */ + + /* djh - According to the engineer at PCTV Systems, + the xc5000 reset pin is supposed to be on GPIO12. + However, despite three nights of effort, pulling + that GPIO low didn't reset the xc5000. While + pulling MO_SRST_IO low does reset the xc5000, this + also resets in the s5h1409 being reset as well. + This causes tuning to always fail since the internal + state of the s5h1409 does not match the driver's + state. Given that the only two conditions in which + the driver performs a reset is during firmware load + and powering down the chip, I am taking out the + reset. We know that the chip is being reset + when the cx88 comes online, and not being able to + do power management for this board is worse than + not having any tuning at all. */ + return 0; + } else { + err_printk(core, "xc5000: unknown tuner " + "callback command.\n"); + return -EINVAL; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: + if (command == 0) { /* This is the reset command from xc5000 */ + cx_clear(MO_GP0_IO, 0x00000010); + msleep(10); + cx_set(MO_GP0_IO, 0x00000010); + return 0; + } else { + printk(KERN_ERR + "xc5000: unknown tuner callback command.\n"); + return -EINVAL; + } + break; + } + return 0; /* Should never be here */ +} + +int cx88_tuner_callback(void *priv, int component, int command, int arg) +{ + struct i2c_algo_bit_data *i2c_algo = priv; + struct cx88_core *core; + + if (!i2c_algo) { + printk(KERN_ERR "cx88: Error - i2c private data undefined.\n"); + return -EINVAL; + } + + core = i2c_algo->data; + + if (!core) { + printk(KERN_ERR "cx88: Error - device struct undefined.\n"); + return -EINVAL; + } + + if (component != DVB_FRONTEND_COMPONENT_TUNER) + return -EINVAL; + + switch (core->board.tuner_type) { + case TUNER_XC2028: + info_printk(core, "Calling XC2028/3028 callback\n"); + return cx88_xc2028_tuner_callback(core, command, arg); + case TUNER_XC4000: + info_printk(core, "Calling XC4000 callback\n"); + return cx88_xc4000_tuner_callback(core, command, arg); + case TUNER_XC5000: + info_printk(core, "Calling XC5000 callback\n"); + return cx88_xc5000_tuner_callback(core, command, arg); + } + err_printk(core, "Error: Calling callback for tuner %d\n", + core->board.tuner_type); + return -EINVAL; +} +EXPORT_SYMBOL(cx88_tuner_callback); + +/* ----------------------------------------------------------------------- */ + +static void cx88_card_list(struct cx88_core *core, struct pci_dev *pci) +{ + int i; + + if (0 == pci->subsystem_vendor && + 0 == pci->subsystem_device) { + printk(KERN_ERR + "%s: Your board has no valid PCI Subsystem ID and thus can't\n" + "%s: be autodetected. Please pass card= insmod option to\n" + "%s: workaround that. Redirect complaints to the vendor of\n" + "%s: the TV card. Best regards,\n" + "%s: -- tux\n", + core->name,core->name,core->name,core->name,core->name); + } else { + printk(KERN_ERR + "%s: Your board isn't known (yet) to the driver. You can\n" + "%s: try to pick one of the existing card configs via\n" + "%s: card= insmod option. Updating to the latest\n" + "%s: version might help as well.\n", + core->name,core->name,core->name,core->name); + } + err_printk(core, "Here is a list of valid choices for the card= " + "insmod option:\n"); + for (i = 0; i < ARRAY_SIZE(cx88_boards); i++) + printk(KERN_ERR "%s: card=%d -> %s\n", + core->name, i, cx88_boards[i].name); +} + +static void cx88_card_setup_pre_i2c(struct cx88_core *core) +{ + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* + * Bring the 702 demod up before i2c scanning/attach or devices are hidden + * We leave here with the 702 on the bus + * + * "reset the IR receiver on GPIO[3]" + * Reported by Mike Crash + */ + cx_write(MO_GP0_IO, 0x0000ef88); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000088); + udelay(50); + cx_set(MO_GP0_IO, 0x00000088); /* 702 out of reset */ + udelay(1000); + break; + + case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME: + case CX88_BOARD_PROLINK_PV_8000GT: + cx_write(MO_GP2_IO, 0xcf7); + mdelay(50); + cx_write(MO_GP2_IO, 0xef5); + mdelay(50); + cx_write(MO_GP2_IO, 0xcf7); + msleep(10); + break; + + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: + /* Enable the xc5000 tuner */ + cx_set(MO_GP0_IO, 0x00001010); + break; + + case CX88_BOARD_WINFAST_DTV2000H_J: + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + /* Init GPIO */ + cx_write(MO_GP0_IO, core->board.input[0].gpio0); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000080); + udelay(50); + cx_set(MO_GP0_IO, 0x00000080); /* 702 out of reset */ + udelay(1000); + break; + + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL: + case CX88_BOARD_WINFAST_DTV1800H: + cx88_xc3028_winfast1800h_callback(core, XC2028_TUNER_RESET, 0); + break; + + case CX88_BOARD_WINFAST_DTV1800H_XC4000: + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36: + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43: + cx88_xc4000_winfast2000h_plus_callback(core, + XC4000_TUNER_RESET, 0); + break; + + case CX88_BOARD_TWINHAN_VP1027_DVBS: + cx_write(MO_GP0_IO, 0x00003230); + cx_write(MO_GP0_IO, 0x00003210); + msleep(1); + cx_write(MO_GP0_IO, 0x00001230); + break; + } +} + +/* + * Sets board-dependent xc3028 configuration + */ +void cx88_setup_xc3028(struct cx88_core *core, struct xc2028_ctrl *ctl) +{ + memset(ctl, 0, sizeof(*ctl)); + + ctl->fname = XC2028_DEFAULT_FIRMWARE; + ctl->max_len = 64; + + switch (core->boardnr) { + case CX88_BOARD_POWERCOLOR_REAL_ANGEL: + /* Now works with firmware version 2.7 */ + if (core->i2c_algo.udelay < 16) + core->i2c_algo.udelay = 16; + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO: + case CX88_BOARD_WINFAST_DTV1800H: + ctl->demod = XC3028_FE_ZARLINK456; + break; + case CX88_BOARD_KWORLD_ATSC_120: + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + ctl->demod = XC3028_FE_OREN538; + break; + case CX88_BOARD_GENIATECH_X8000_MT: + /* FIXME: For this board, the xc3028 never recovers after being + powered down (the reset GPIO probably is not set properly). + We don't have access to the hardware so we cannot determine + which GPIO is used for xc3028, so just disable power xc3028 + power management for now */ + ctl->disable_power_mgmt = 1; + break; + case CX88_BOARD_WINFAST_TV2000_XP_GLOBAL: + case CX88_BOARD_PROLINK_PV_GLOBAL_XTREME: + case CX88_BOARD_PROLINK_PV_8000GT: + /* + * Those boards uses non-MTS firmware + */ + break; + case CX88_BOARD_PINNACLE_HYBRID_PCTV: + case CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII: + ctl->demod = XC3028_FE_ZARLINK456; + ctl->mts = 1; + break; + default: + ctl->demod = XC3028_FE_OREN538; + ctl->mts = 1; + } +} +EXPORT_SYMBOL_GPL(cx88_setup_xc3028); + +static void cx88_card_setup(struct cx88_core *core) +{ + static u8 eeprom[256]; + struct tuner_setup tun_setup; + unsigned int mode_mask = T_RADIO | T_ANALOG_TV; + + memset(&tun_setup, 0, sizeof(tun_setup)); + + if (0 == core->i2c_rc) { + core->i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&core->i2c_client, eeprom, sizeof(eeprom)); + } + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE: + case CX88_BOARD_HAUPPAUGE_ROSLYN: + if (0 == core->i2c_rc) + hauppauge_eeprom(core, eeprom+8); + break; + case CX88_BOARD_GDI: + if (0 == core->i2c_rc) + gdi_eeprom(core, eeprom); + break; + case CX88_BOARD_LEADTEK_PVR2000: + case CX88_BOARD_WINFAST_DV2000: + case CX88_BOARD_WINFAST2000XP_EXPERT: + if (0 == core->i2c_rc) + leadtek_eeprom(core, eeprom); + break; + case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: + case CX88_BOARD_HAUPPAUGE_NOVASE2_S1: + case CX88_BOARD_HAUPPAUGE_DVB_T1: + case CX88_BOARD_HAUPPAUGE_HVR1100: + case CX88_BOARD_HAUPPAUGE_HVR1100LP: + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR1300: + case CX88_BOARD_HAUPPAUGE_HVR4000: + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: + case CX88_BOARD_HAUPPAUGE_IRONLY: + if (0 == core->i2c_rc) + hauppauge_eeprom(core, eeprom); + break; + case CX88_BOARD_KWORLD_DVBS_100: + cx_write(MO_GP0_IO, 0x000007f8); + cx_write(MO_GP1_IO, 0x00000001); + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO: + /* GPIO0:0 is hooked to demod reset */ + /* GPIO0:4 is hooked to xc3028 reset */ + cx_write(MO_GP0_IO, 0x00111100); + msleep(1); + cx_write(MO_GP0_IO, 0x00111111); + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL: + /* GPIO0:6 is hooked to FX2 reset pin */ + cx_set(MO_GP0_IO, 0x00004040); + cx_clear(MO_GP0_IO, 0x00000040); + msleep(1000); + cx_set(MO_GP0_IO, 0x00004040); + /* FALLTHROUGH */ + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1: + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS: + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID: + /* GPIO0:0 is hooked to mt352 reset pin */ + cx_set(MO_GP0_IO, 0x00000101); + cx_clear(MO_GP0_IO, 0x00000001); + msleep(1); + cx_set(MO_GP0_IO, 0x00000101); + if (0 == core->i2c_rc && + core->boardnr == CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID) + dvico_fusionhdtv_hybrid_init(core); + break; + case CX88_BOARD_KWORLD_DVB_T: + case CX88_BOARD_DNTV_LIVE_DVB_T: + cx_set(MO_GP0_IO, 0x00000707); + cx_set(MO_GP2_IO, 0x00000101); + cx_clear(MO_GP2_IO, 0x00000001); + msleep(1); + cx_clear(MO_GP0_IO, 0x00000007); + cx_set(MO_GP2_IO, 0x00000101); + break; + case CX88_BOARD_DNTV_LIVE_DVB_T_PRO: + cx_write(MO_GP0_IO, 0x00080808); + break; + case CX88_BOARD_ATI_HDTVWONDER: + if (0 == core->i2c_rc) { + /* enable tuner */ + int i; + static const u8 buffer [][2] = { + {0x10,0x12}, + {0x13,0x04}, + {0x16,0x00}, + {0x14,0x04}, + {0x17,0x00} + }; + core->i2c_client.addr = 0x0a; + + for (i = 0; i < ARRAY_SIZE(buffer); i++) + if (2 != i2c_master_send(&core->i2c_client, + buffer[i],2)) + warn_printk(core, "Unable to enable " + "tuner(%i).\n", i); + } + break; + case CX88_BOARD_MSI_TVANYWHERE_MASTER: + { + struct v4l2_priv_tun_config tea5767_cfg; + struct tea5767_ctrl ctl; + + memset(&ctl, 0, sizeof(ctl)); + + ctl.high_cut = 1; + ctl.st_noise = 1; + ctl.deemph_75 = 1; + ctl.xtal_freq = TEA5767_HIGH_LO_13MHz; + + tea5767_cfg.tuner = TUNER_TEA5767; + tea5767_cfg.priv = &ctl; + + call_all(core, tuner, s_config, &tea5767_cfg); + break; + } + case CX88_BOARD_TEVII_S420: + case CX88_BOARD_TEVII_S460: + case CX88_BOARD_TEVII_S464: + case CX88_BOARD_OMICOM_SS4_PCI: + case CX88_BOARD_TBS_8910: + case CX88_BOARD_TBS_8920: + case CX88_BOARD_PROF_6200: + case CX88_BOARD_PROF_7300: + case CX88_BOARD_PROF_7301: + case CX88_BOARD_SATTRADE_ST4200: + cx_write(MO_GP0_IO, 0x8000); + msleep(100); + cx_write(MO_SRST_IO, 0); + msleep(10); + cx_write(MO_GP0_IO, 0x8080); + msleep(100); + cx_write(MO_SRST_IO, 1); + msleep(100); + break; + } /*end switch() */ + + + /* Setup tuners */ + if ((core->board.radio_type != UNSET)) { + tun_setup.mode_mask = T_RADIO; + tun_setup.type = core->board.radio_type; + tun_setup.addr = core->board.radio_addr; + tun_setup.tuner_callback = cx88_tuner_callback; + call_all(core, tuner, s_type_addr, &tun_setup); + mode_mask &= ~T_RADIO; + } + + if (core->board.tuner_type != TUNER_ABSENT) { + tun_setup.mode_mask = mode_mask; + tun_setup.type = core->board.tuner_type; + tun_setup.addr = core->board.tuner_addr; + tun_setup.tuner_callback = cx88_tuner_callback; + + call_all(core, tuner, s_type_addr, &tun_setup); + } + + if (core->board.tda9887_conf) { + struct v4l2_priv_tun_config tda9887_cfg; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &core->board.tda9887_conf; + + call_all(core, tuner, s_config, &tda9887_cfg); + } + + if (core->board.tuner_type == TUNER_XC2028) { + struct v4l2_priv_tun_config xc2028_cfg; + struct xc2028_ctrl ctl; + + /* Fills device-dependent initialization parameters */ + cx88_setup_xc3028(core, &ctl); + + /* Sends parameters to xc2028/3028 tuner */ + memset(&xc2028_cfg, 0, sizeof(xc2028_cfg)); + xc2028_cfg.tuner = TUNER_XC2028; + xc2028_cfg.priv = &ctl; + info_printk(core, "Asking xc2028/3028 to load firmware %s\n", + ctl.fname); + call_all(core, tuner, s_config, &xc2028_cfg); + } + call_all(core, core, s_power, 0); +} + +/* ------------------------------------------------------------------ */ + +static int cx88_pci_quirks(const char *name, struct pci_dev *pci) +{ + unsigned int lat = UNSET; + u8 ctrl = 0; + u8 value; + + /* check pci quirks */ + if (pci_pci_problems & PCIPCI_TRITON) { + printk(KERN_INFO "%s: quirk: PCIPCI_TRITON -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_NATOMA) { + printk(KERN_INFO "%s: quirk: PCIPCI_NATOMA -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VIAETBF) { + printk(KERN_INFO "%s: quirk: PCIPCI_VIAETBF -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VSFX) { + printk(KERN_INFO "%s: quirk: PCIPCI_VSFX -- set VSFX\n", + name); + ctrl |= CX88X_EN_VSFX; + } +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) { + printk(KERN_INFO "%s: quirk: PCIPCI_ALIMAGIK -- latency fixup\n", + name); + lat = 0x0A; + } +#endif + + /* check insmod options */ + if (UNSET != latency) + lat = latency; + + /* apply stuff */ + if (ctrl) { + pci_read_config_byte(pci, CX88X_DEVCTRL, &value); + value |= ctrl; + pci_write_config_byte(pci, CX88X_DEVCTRL, value); + } + if (UNSET != lat) { + printk(KERN_INFO "%s: setting pci latency timer to %d\n", + name, latency); + pci_write_config_byte(pci, PCI_LATENCY_TIMER, latency); + } + return 0; +} + +int cx88_get_resources(const struct cx88_core *core, struct pci_dev *pci) +{ + if (request_mem_region(pci_resource_start(pci,0), + pci_resource_len(pci,0), + core->name)) + return 0; + printk(KERN_ERR + "%s/%d: Can't get MMIO memory @ 0x%llx, subsystem: %04x:%04x\n", + core->name, PCI_FUNC(pci->devfn), + (unsigned long long)pci_resource_start(pci, 0), + pci->subsystem_vendor, pci->subsystem_device); + return -EBUSY; +} + +/* Allocate and initialize the cx88 core struct. One should hold the + * devlist mutex before calling this. */ +struct cx88_core *cx88_core_create(struct pci_dev *pci, int nr) +{ + struct cx88_core *core; + int i; + + core = kzalloc(sizeof(*core), GFP_KERNEL); + if (core == NULL) + return NULL; + + atomic_inc(&core->refcount); + core->pci_bus = pci->bus->number; + core->pci_slot = PCI_SLOT(pci->devfn); + core->pci_irqmask = PCI_INT_RISC_RD_BERRINT | PCI_INT_RISC_WR_BERRINT | + PCI_INT_BRDG_BERRINT | PCI_INT_SRC_DMA_BERRINT | + PCI_INT_DST_DMA_BERRINT | PCI_INT_IPB_DMA_BERRINT; + mutex_init(&core->lock); + + core->nr = nr; + sprintf(core->name, "cx88[%d]", core->nr); + + strcpy(core->v4l2_dev.name, core->name); + if (v4l2_device_register(NULL, &core->v4l2_dev)) { + kfree(core); + return NULL; + } + + if (v4l2_ctrl_handler_init(&core->video_hdl, 13)) { + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); + return NULL; + } + + if (v4l2_ctrl_handler_init(&core->audio_hdl, 13)) { + v4l2_ctrl_handler_free(&core->video_hdl); + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); + return NULL; + } + + if (0 != cx88_get_resources(core, pci)) { + v4l2_ctrl_handler_free(&core->video_hdl); + v4l2_ctrl_handler_free(&core->audio_hdl); + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); + return NULL; + } + + /* PCI stuff */ + cx88_pci_quirks(core->name, pci); + core->lmmio = ioremap(pci_resource_start(pci, 0), + pci_resource_len(pci, 0)); + core->bmmio = (u8 __iomem *)core->lmmio; + + if (core->lmmio == NULL) { + release_mem_region(pci_resource_start(pci, 0), + pci_resource_len(pci, 0)); + v4l2_ctrl_handler_free(&core->video_hdl); + v4l2_ctrl_handler_free(&core->audio_hdl); + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); + return NULL; + } + + /* board config */ + core->boardnr = UNSET; + if (card[core->nr] < ARRAY_SIZE(cx88_boards)) + core->boardnr = card[core->nr]; + for (i = 0; UNSET == core->boardnr && i < ARRAY_SIZE(cx88_subids); i++) + if (pci->subsystem_vendor == cx88_subids[i].subvendor && + pci->subsystem_device == cx88_subids[i].subdevice) + core->boardnr = cx88_subids[i].card; + if (UNSET == core->boardnr) { + core->boardnr = CX88_BOARD_UNKNOWN; + cx88_card_list(core, pci); + } + + memcpy(&core->board, &cx88_boards[core->boardnr], sizeof(core->board)); + + if (!core->board.num_frontends && (core->board.mpeg & CX88_MPEG_DVB)) + core->board.num_frontends = 1; + + info_printk(core, "subsystem: %04x:%04x, board: %s [card=%d,%s], frontend(s): %d\n", + pci->subsystem_vendor, pci->subsystem_device, core->board.name, + core->boardnr, card[core->nr] == core->boardnr ? + "insmod option" : "autodetected", + core->board.num_frontends); + + if (tuner[core->nr] != UNSET) + core->board.tuner_type = tuner[core->nr]; + if (radio[core->nr] != UNSET) + core->board.radio_type = radio[core->nr]; + + info_printk(core, "TV tuner type %d, Radio tuner type %d\n", + core->board.tuner_type, core->board.radio_type); + + /* init hardware */ + cx88_reset(core); + cx88_card_setup_pre_i2c(core); + cx88_i2c_init(core, pci); + + /* load tuner module, if needed */ + if (TUNER_ABSENT != core->board.tuner_type) { + /* Ignore 0x6b and 0x6f on cx88 boards. + * FusionHDTV5 RT Gold has an ir receiver at 0x6b + * and an RTC at 0x6f which can get corrupted if probed. */ + static const unsigned short tv_addrs[] = { + 0x42, 0x43, 0x4a, 0x4b, /* tda8290 */ + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, + I2C_CLIENT_END + }; + int has_demod = (core->board.tda9887_conf & TDA9887_PRESENT); + + /* I don't trust the radio_type as is stored in the card + definitions, so we just probe for it. + The radio_type is sometimes missing, or set to UNSET but + later code configures a tea5767. + */ + v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap, + "tuner", 0, v4l2_i2c_tuner_addrs(ADDRS_RADIO)); + if (has_demod) + v4l2_i2c_new_subdev(&core->v4l2_dev, + &core->i2c_adap, "tuner", + 0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD)); + if (core->board.tuner_addr == ADDR_UNSET) { + v4l2_i2c_new_subdev(&core->v4l2_dev, + &core->i2c_adap, "tuner", + 0, has_demod ? tv_addrs + 4 : tv_addrs); + } else { + v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap, + "tuner", core->board.tuner_addr, NULL); + } + } + + cx88_card_setup(core); + if (!disable_ir) { + cx88_i2c_init_ir(core); + cx88_ir_init(core, pci); + } + + return core; +} diff --git a/drivers/media/pci/cx88/cx88-core.c b/drivers/media/pci/cx88/cx88-core.c new file mode 100644 index 000000000000..c97b174be3ab --- /dev/null +++ b/drivers/media/pci/cx88/cx88-core.c @@ -0,0 +1,1131 @@ +/* + * + * device driver for Conexant 2388x based TV cards + * driver core + * + * (c) 2003 Gerd Knorr [SuSE Labs] + * + * (c) 2005-2006 Mauro Carvalho Chehab + * - Multituner support + * - video_ioctl2 conversion + * - PAL/M fixes + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx88.h" +#include +#include + +MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------------ */ + +static unsigned int core_debug; +module_param(core_debug,int,0644); +MODULE_PARM_DESC(core_debug,"enable debug messages [core]"); + +static unsigned int nicam; +module_param(nicam,int,0644); +MODULE_PARM_DESC(nicam,"tv audio is nicam"); + +static unsigned int nocomb; +module_param(nocomb,int,0644); +MODULE_PARM_DESC(nocomb,"disable comb filter"); + +#define dprintk(level,fmt, arg...) if (core_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, core->name , ## arg) + +static unsigned int cx88_devcount; +static LIST_HEAD(cx88_devlist); +static DEFINE_MUTEX(devlist); + +#define NO_SYNC_LINE (-1U) + +/* @lpi: lines per IRQ, or 0 to not generate irqs. Note: IRQ to be + generated _after_ lpi lines are transferred. */ +static __le32* cx88_risc_field(__le32 *rp, struct scatterlist *sglist, + unsigned int offset, u32 sync_line, + unsigned int bpl, unsigned int padding, + unsigned int lines, unsigned int lpi) +{ + struct scatterlist *sg; + unsigned int line,todo,sol; + + /* sync instruction */ + if (sync_line != NO_SYNC_LINE) + *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line); + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg++; + } + if (lpi && line>0 && !(line % lpi)) + sol = RISC_SOL | RISC_IRQ1 | RISC_CNT_INC; + else + sol = RISC_SOL; + if (bpl <= sg_dma_len(sg)-offset) { + /* fits into current chunk */ + *(rp++)=cpu_to_le32(RISC_WRITE|sol|RISC_EOL|bpl); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + offset+=bpl; + } else { + /* scanline needs to be split */ + todo = bpl; + *(rp++)=cpu_to_le32(RISC_WRITE|sol| + (sg_dma_len(sg)-offset)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + todo -= (sg_dma_len(sg)-offset); + offset = 0; + sg++; + while (todo > sg_dma_len(sg)) { + *(rp++)=cpu_to_le32(RISC_WRITE| + sg_dma_len(sg)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + todo -= sg_dma_len(sg); + sg++; + } + *(rp++)=cpu_to_le32(RISC_WRITE|RISC_EOL|todo); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + offset += todo; + } + offset += padding; + } + + return rp; +} + +int cx88_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines) +{ + u32 instructions,fields; + __le32 *rp; + int rc; + + fields = 0; + if (UNSET != top_offset) + fields++; + if (UNSET != bottom_offset) + fields++; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 2 dwords). Padding + can cause next bpl to start close to a page border. First DMA + region may be smaller than PAGE_SIZE */ + instructions = fields * (1 + ((bpl + padding) * lines) / PAGE_SIZE + lines); + instructions += 2; + if ((rc = btcx_riscmem_alloc(pci,risc,instructions*8)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + if (UNSET != top_offset) + rp = cx88_risc_field(rp, sglist, top_offset, 0, + bpl, padding, lines, 0); + if (UNSET != bottom_offset) + rp = cx88_risc_field(rp, sglist, bottom_offset, 0x200, + bpl, padding, lines, 0); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) * sizeof (*risc->cpu) > risc->size); + return 0; +} + +int cx88_risc_databuffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, unsigned int bpl, + unsigned int lines, unsigned int lpi) +{ + u32 instructions; + __le32 *rp; + int rc; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 2 dwords). Here + there is no padding and no sync. First DMA region may be smaller + than PAGE_SIZE */ + instructions = 1 + (bpl * lines) / PAGE_SIZE + lines; + instructions += 1; + if ((rc = btcx_riscmem_alloc(pci,risc,instructions*8)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + rp = cx88_risc_field(rp, sglist, 0, NO_SYNC_LINE, bpl, 0, lines, lpi); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) * sizeof (*risc->cpu) > risc->size); + return 0; +} + +int cx88_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc, + u32 reg, u32 mask, u32 value) +{ + __le32 *rp; + int rc; + + if ((rc = btcx_riscmem_alloc(pci, risc, 4*16)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(RISC_WRITECR | RISC_IRQ2 | RISC_IMM); + *(rp++) = cpu_to_le32(reg); + *(rp++) = cpu_to_le32(value); + *(rp++) = cpu_to_le32(mask); + *(rp++) = cpu_to_le32(RISC_JUMP); + *(rp++) = cpu_to_le32(risc->dma); + return 0; +} + +void +cx88_free_buffer(struct videobuf_queue *q, struct cx88_buffer *buf) +{ + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + + BUG_ON(in_interrupt()); + videobuf_waiton(q, &buf->vb, 0, 0); + videobuf_dma_unmap(q->dev, dma); + videobuf_dma_free(dma); + btcx_riscmem_free(to_pci_dev(q->dev), &buf->risc); + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +/* ------------------------------------------------------------------ */ +/* our SRAM memory layout */ + +/* we are going to put all thr risc programs into host memory, so we + * can use the whole SDRAM for the DMA fifos. To simplify things, we + * use a static memory layout. That surely will waste memory in case + * we don't use all DMA channels at the same time (which will be the + * case most of the time). But that still gives us enough FIFO space + * to be able to deal with insane long pci latencies ... + * + * FIFO space allocations: + * channel 21 (y video) - 10.0k + * channel 22 (u video) - 2.0k + * channel 23 (v video) - 2.0k + * channel 24 (vbi) - 4.0k + * channels 25+26 (audio) - 4.0k + * channel 28 (mpeg) - 4.0k + * channel 27 (audio rds)- 3.0k + * TOTAL = 29.0k + * + * Every channel has 160 bytes control data (64 bytes instruction + * queue and 6 CDT entries), which is close to 2k total. + * + * Address layout: + * 0x0000 - 0x03ff CMDs / reserved + * 0x0400 - 0x0bff instruction queues + CDs + * 0x0c00 - FIFOs + */ + +const struct sram_channel cx88_sram_channels[] = { + [SRAM_CH21] = { + .name = "video y / packed", + .cmds_start = 0x180040, + .ctrl_start = 0x180400, + .cdt = 0x180400 + 64, + .fifo_start = 0x180c00, + .fifo_size = 0x002800, + .ptr1_reg = MO_DMA21_PTR1, + .ptr2_reg = MO_DMA21_PTR2, + .cnt1_reg = MO_DMA21_CNT1, + .cnt2_reg = MO_DMA21_CNT2, + }, + [SRAM_CH22] = { + .name = "video u", + .cmds_start = 0x180080, + .ctrl_start = 0x1804a0, + .cdt = 0x1804a0 + 64, + .fifo_start = 0x183400, + .fifo_size = 0x000800, + .ptr1_reg = MO_DMA22_PTR1, + .ptr2_reg = MO_DMA22_PTR2, + .cnt1_reg = MO_DMA22_CNT1, + .cnt2_reg = MO_DMA22_CNT2, + }, + [SRAM_CH23] = { + .name = "video v", + .cmds_start = 0x1800c0, + .ctrl_start = 0x180540, + .cdt = 0x180540 + 64, + .fifo_start = 0x183c00, + .fifo_size = 0x000800, + .ptr1_reg = MO_DMA23_PTR1, + .ptr2_reg = MO_DMA23_PTR2, + .cnt1_reg = MO_DMA23_CNT1, + .cnt2_reg = MO_DMA23_CNT2, + }, + [SRAM_CH24] = { + .name = "vbi", + .cmds_start = 0x180100, + .ctrl_start = 0x1805e0, + .cdt = 0x1805e0 + 64, + .fifo_start = 0x184400, + .fifo_size = 0x001000, + .ptr1_reg = MO_DMA24_PTR1, + .ptr2_reg = MO_DMA24_PTR2, + .cnt1_reg = MO_DMA24_CNT1, + .cnt2_reg = MO_DMA24_CNT2, + }, + [SRAM_CH25] = { + .name = "audio from", + .cmds_start = 0x180140, + .ctrl_start = 0x180680, + .cdt = 0x180680 + 64, + .fifo_start = 0x185400, + .fifo_size = 0x001000, + .ptr1_reg = MO_DMA25_PTR1, + .ptr2_reg = MO_DMA25_PTR2, + .cnt1_reg = MO_DMA25_CNT1, + .cnt2_reg = MO_DMA25_CNT2, + }, + [SRAM_CH26] = { + .name = "audio to", + .cmds_start = 0x180180, + .ctrl_start = 0x180720, + .cdt = 0x180680 + 64, /* same as audio IN */ + .fifo_start = 0x185400, /* same as audio IN */ + .fifo_size = 0x001000, /* same as audio IN */ + .ptr1_reg = MO_DMA26_PTR1, + .ptr2_reg = MO_DMA26_PTR2, + .cnt1_reg = MO_DMA26_CNT1, + .cnt2_reg = MO_DMA26_CNT2, + }, + [SRAM_CH28] = { + .name = "mpeg", + .cmds_start = 0x180200, + .ctrl_start = 0x1807C0, + .cdt = 0x1807C0 + 64, + .fifo_start = 0x186400, + .fifo_size = 0x001000, + .ptr1_reg = MO_DMA28_PTR1, + .ptr2_reg = MO_DMA28_PTR2, + .cnt1_reg = MO_DMA28_CNT1, + .cnt2_reg = MO_DMA28_CNT2, + }, + [SRAM_CH27] = { + .name = "audio rds", + .cmds_start = 0x1801C0, + .ctrl_start = 0x180860, + .cdt = 0x180860 + 64, + .fifo_start = 0x187400, + .fifo_size = 0x000C00, + .ptr1_reg = MO_DMA27_PTR1, + .ptr2_reg = MO_DMA27_PTR2, + .cnt1_reg = MO_DMA27_CNT1, + .cnt2_reg = MO_DMA27_CNT2, + }, +}; + +int cx88_sram_channel_setup(struct cx88_core *core, + const struct sram_channel *ch, + unsigned int bpl, u32 risc) +{ + unsigned int i,lines; + u32 cdt; + + bpl = (bpl + 7) & ~7; /* alignment */ + cdt = ch->cdt; + lines = ch->fifo_size / bpl; + if (lines > 6) + lines = 6; + BUG_ON(lines < 2); + + /* write CDT */ + for (i = 0; i < lines; i++) + cx_write(cdt + 16*i, ch->fifo_start + bpl*i); + + /* write CMDS */ + cx_write(ch->cmds_start + 0, risc); + cx_write(ch->cmds_start + 4, cdt); + cx_write(ch->cmds_start + 8, (lines*16) >> 3); + cx_write(ch->cmds_start + 12, ch->ctrl_start); + cx_write(ch->cmds_start + 16, 64 >> 2); + for (i = 20; i < 64; i += 4) + cx_write(ch->cmds_start + i, 0); + + /* fill registers */ + cx_write(ch->ptr1_reg, ch->fifo_start); + cx_write(ch->ptr2_reg, cdt); + cx_write(ch->cnt1_reg, (bpl >> 3) -1); + cx_write(ch->cnt2_reg, (lines*16) >> 3); + + dprintk(2,"sram setup %s: bpl=%d lines=%d\n", ch->name, bpl, lines); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* debug helper code */ + +static int cx88_risc_decode(u32 risc) +{ + static const char * const instr[16] = { + [ RISC_SYNC >> 28 ] = "sync", + [ RISC_WRITE >> 28 ] = "write", + [ RISC_WRITEC >> 28 ] = "writec", + [ RISC_READ >> 28 ] = "read", + [ RISC_READC >> 28 ] = "readc", + [ RISC_JUMP >> 28 ] = "jump", + [ RISC_SKIP >> 28 ] = "skip", + [ RISC_WRITERM >> 28 ] = "writerm", + [ RISC_WRITECM >> 28 ] = "writecm", + [ RISC_WRITECR >> 28 ] = "writecr", + }; + static int const incr[16] = { + [ RISC_WRITE >> 28 ] = 2, + [ RISC_JUMP >> 28 ] = 2, + [ RISC_WRITERM >> 28 ] = 3, + [ RISC_WRITECM >> 28 ] = 3, + [ RISC_WRITECR >> 28 ] = 4, + }; + static const char * const bits[] = { + "12", "13", "14", "resync", + "cnt0", "cnt1", "18", "19", + "20", "21", "22", "23", + "irq1", "irq2", "eol", "sol", + }; + int i; + + printk("0x%08x [ %s", risc, + instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); + for (i = ARRAY_SIZE(bits)-1; i >= 0; i--) + if (risc & (1 << (i + 12))) + printk(" %s",bits[i]); + printk(" count=%d ]\n", risc & 0xfff); + return incr[risc >> 28] ? incr[risc >> 28] : 1; +} + + +void cx88_sram_channel_dump(struct cx88_core *core, + const struct sram_channel *ch) +{ + static const char * const name[] = { + "initial risc", + "cdt base", + "cdt size", + "iq base", + "iq size", + "risc pc", + "iq wr ptr", + "iq rd ptr", + "cdt current", + "pci target", + "line / byte", + }; + u32 risc; + unsigned int i,j,n; + + printk("%s: %s - dma channel status dump\n", + core->name,ch->name); + for (i = 0; i < ARRAY_SIZE(name); i++) + printk("%s: cmds: %-12s: 0x%08x\n", + core->name,name[i], + cx_read(ch->cmds_start + 4*i)); + for (n = 1, i = 0; i < 4; i++) { + risc = cx_read(ch->cmds_start + 4 * (i+11)); + printk("%s: risc%d: ", core->name, i); + if (--n) + printk("0x%08x [ arg #%d ]\n", risc, n); + else + n = cx88_risc_decode(risc); + } + for (i = 0; i < 16; i += n) { + risc = cx_read(ch->ctrl_start + 4 * i); + printk("%s: iq %x: ", core->name, i); + n = cx88_risc_decode(risc); + for (j = 1; j < n; j++) { + risc = cx_read(ch->ctrl_start + 4 * (i+j)); + printk("%s: iq %x: 0x%08x [ arg #%d ]\n", + core->name, i+j, risc, j); + } + } + + printk("%s: fifo: 0x%08x -> 0x%x\n", + core->name, ch->fifo_start, ch->fifo_start+ch->fifo_size); + printk("%s: ctrl: 0x%08x -> 0x%x\n", + core->name, ch->ctrl_start, ch->ctrl_start+6*16); + printk("%s: ptr1_reg: 0x%08x\n", + core->name,cx_read(ch->ptr1_reg)); + printk("%s: ptr2_reg: 0x%08x\n", + core->name,cx_read(ch->ptr2_reg)); + printk("%s: cnt1_reg: 0x%08x\n", + core->name,cx_read(ch->cnt1_reg)); + printk("%s: cnt2_reg: 0x%08x\n", + core->name,cx_read(ch->cnt2_reg)); +} + +static const char *cx88_pci_irqs[32] = { + "vid", "aud", "ts", "vip", "hst", "5", "6", "tm1", + "src_dma", "dst_dma", "risc_rd_err", "risc_wr_err", + "brdg_err", "src_dma_err", "dst_dma_err", "ipb_dma_err", + "i2c", "i2c_rack", "ir_smp", "gpio0", "gpio1" +}; + +void cx88_print_irqbits(const char *name, const char *tag, const char *strings[], + int len, u32 bits, u32 mask) +{ + unsigned int i; + + printk(KERN_DEBUG "%s: %s [0x%x]", name, tag, bits); + for (i = 0; i < len; i++) { + if (!(bits & (1 << i))) + continue; + if (strings[i]) + printk(" %s", strings[i]); + else + printk(" %d", i); + if (!(mask & (1 << i))) + continue; + printk("*"); + } + printk("\n"); +} + +/* ------------------------------------------------------------------ */ + +int cx88_core_irq(struct cx88_core *core, u32 status) +{ + int handled = 0; + + if (status & PCI_INT_IR_SMPINT) { + cx88_ir_irq(core); + handled++; + } + if (!handled) + cx88_print_irqbits(core->name, "irq pci", + cx88_pci_irqs, ARRAY_SIZE(cx88_pci_irqs), + status, core->pci_irqmask); + return handled; +} + +void cx88_wakeup(struct cx88_core *core, + struct cx88_dmaqueue *q, u32 count) +{ + struct cx88_buffer *buf; + int bc; + + for (bc = 0;; bc++) { + if (list_empty(&q->active)) + break; + buf = list_entry(q->active.next, + struct cx88_buffer, vb.queue); + /* count comes from the hw and is is 16bit wide -- + * this trick handles wrap-arounds correctly for + * up to 32767 buffers in flight... */ + if ((s16) (count - buf->count) < 0) + break; + do_gettimeofday(&buf->vb.ts); + dprintk(2,"[%p/%d] wakeup reg=%d buf=%d\n",buf,buf->vb.i, + count, buf->count); + buf->vb.state = VIDEOBUF_DONE; + list_del(&buf->vb.queue); + wake_up(&buf->vb.done); + } + if (list_empty(&q->active)) { + del_timer(&q->timeout); + } else { + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + } + if (bc != 1) + dprintk(2, "%s: %d buffers handled (should be 1)\n", + __func__, bc); +} + +void cx88_shutdown(struct cx88_core *core) +{ + /* disable RISC controller + IRQs */ + cx_write(MO_DEV_CNTRL2, 0); + + /* stop dma transfers */ + cx_write(MO_VID_DMACNTRL, 0x0); + cx_write(MO_AUD_DMACNTRL, 0x0); + cx_write(MO_TS_DMACNTRL, 0x0); + cx_write(MO_VIP_DMACNTRL, 0x0); + cx_write(MO_GPHST_DMACNTRL, 0x0); + + /* stop interrupts */ + cx_write(MO_PCI_INTMSK, 0x0); + cx_write(MO_VID_INTMSK, 0x0); + cx_write(MO_AUD_INTMSK, 0x0); + cx_write(MO_TS_INTMSK, 0x0); + cx_write(MO_VIP_INTMSK, 0x0); + cx_write(MO_GPHST_INTMSK, 0x0); + + /* stop capturing */ + cx_write(VID_CAPTURE_CONTROL, 0); +} + +int cx88_reset(struct cx88_core *core) +{ + dprintk(1,"%s\n",__func__); + cx88_shutdown(core); + + /* clear irq status */ + cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int + cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int + cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int + + /* wait a bit */ + msleep(100); + + /* init sram */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21], 720*4, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH22], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH23], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH24], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH25], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH26], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28], 188*4, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH27], 128, 0); + + /* misc init ... */ + cx_write(MO_INPUT_FORMAT, ((1 << 13) | // agc enable + (1 << 12) | // agc gain + (1 << 11) | // adaptibe agc + (0 << 10) | // chroma agc + (0 << 9) | // ckillen + (7))); + + /* setup image format */ + cx_andor(MO_COLOR_CTRL, 0x4000, 0x4000); + + /* setup FIFO Thresholds */ + cx_write(MO_PDMA_STHRSH, 0x0807); + cx_write(MO_PDMA_DTHRSH, 0x0807); + + /* fixes flashing of image */ + cx_write(MO_AGC_SYNC_TIP1, 0x0380000F); + cx_write(MO_AGC_BACK_VBI, 0x00E00555); + + cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int + cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int + cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int + + /* Reset on-board parts */ + cx_write(MO_SRST_IO, 0); + msleep(10); + cx_write(MO_SRST_IO, 1); + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static unsigned int inline norm_swidth(v4l2_std_id norm) +{ + return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 754 : 922; +} + +static unsigned int inline norm_hdelay(v4l2_std_id norm) +{ + return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 135 : 186; +} + +static unsigned int inline norm_vdelay(v4l2_std_id norm) +{ + return (norm & V4L2_STD_625_50) ? 0x24 : 0x18; +} + +static unsigned int inline norm_fsc8(v4l2_std_id norm) +{ + if (norm & V4L2_STD_PAL_M) + return 28604892; // 3.575611 MHz + + if (norm & (V4L2_STD_PAL_Nc)) + return 28656448; // 3.582056 MHz + + if (norm & V4L2_STD_NTSC) // All NTSC/M and variants + return 28636360; // 3.57954545 MHz +/- 10 Hz + + /* SECAM have also different sub carrier for chroma, + but step_db and step_dr, at cx88_set_tvnorm already handles that. + + The same FSC applies to PAL/BGDKIH, PAL/60, NTSC/4.43 and PAL/N + */ + + return 35468950; // 4.43361875 MHz +/- 5 Hz +} + +static unsigned int inline norm_htotal(v4l2_std_id norm) +{ + + unsigned int fsc4=norm_fsc8(norm)/2; + + /* returns 4*FSC / vtotal / frames per seconds */ + return (norm & V4L2_STD_625_50) ? + ((fsc4+312)/625+12)/25 : + ((fsc4+262)/525*1001+15000)/30000; +} + +static unsigned int inline norm_vbipack(v4l2_std_id norm) +{ + return (norm & V4L2_STD_625_50) ? 511 : 400; +} + +int cx88_set_scale(struct cx88_core *core, unsigned int width, unsigned int height, + enum v4l2_field field) +{ + unsigned int swidth = norm_swidth(core->tvnorm); + unsigned int sheight = norm_maxh(core->tvnorm); + u32 value; + + dprintk(1,"set_scale: %dx%d [%s%s,%s]\n", width, height, + V4L2_FIELD_HAS_TOP(field) ? "T" : "", + V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "", + v4l2_norm_to_name(core->tvnorm)); + if (!V4L2_FIELD_HAS_BOTH(field)) + height *= 2; + + // recalc H delay and scale registers + value = (width * norm_hdelay(core->tvnorm)) / swidth; + value &= 0x3fe; + cx_write(MO_HDELAY_EVEN, value); + cx_write(MO_HDELAY_ODD, value); + dprintk(1,"set_scale: hdelay 0x%04x (width %d)\n", value,swidth); + + value = (swidth * 4096 / width) - 4096; + cx_write(MO_HSCALE_EVEN, value); + cx_write(MO_HSCALE_ODD, value); + dprintk(1,"set_scale: hscale 0x%04x\n", value); + + cx_write(MO_HACTIVE_EVEN, width); + cx_write(MO_HACTIVE_ODD, width); + dprintk(1,"set_scale: hactive 0x%04x\n", width); + + // recalc V scale Register (delay is constant) + cx_write(MO_VDELAY_EVEN, norm_vdelay(core->tvnorm)); + cx_write(MO_VDELAY_ODD, norm_vdelay(core->tvnorm)); + dprintk(1,"set_scale: vdelay 0x%04x\n", norm_vdelay(core->tvnorm)); + + value = (0x10000 - (sheight * 512 / height - 512)) & 0x1fff; + cx_write(MO_VSCALE_EVEN, value); + cx_write(MO_VSCALE_ODD, value); + dprintk(1,"set_scale: vscale 0x%04x\n", value); + + cx_write(MO_VACTIVE_EVEN, sheight); + cx_write(MO_VACTIVE_ODD, sheight); + dprintk(1,"set_scale: vactive 0x%04x\n", sheight); + + // setup filters + value = 0; + value |= (1 << 19); // CFILT (default) + if (core->tvnorm & V4L2_STD_SECAM) { + value |= (1 << 15); + value |= (1 << 16); + } + if (INPUT(core->input).type == CX88_VMUX_SVIDEO) + value |= (1 << 13) | (1 << 5); + if (V4L2_FIELD_INTERLACED == field) + value |= (1 << 3); // VINT (interlaced vertical scaling) + if (width < 385) + value |= (1 << 0); // 3-tap interpolation + if (width < 193) + value |= (1 << 1); // 5-tap interpolation + if (nocomb) + value |= (3 << 5); // disable comb filter + + cx_andor(MO_FILTER_EVEN, 0x7ffc7f, value); /* preserve PEAKEN, PSEL */ + cx_andor(MO_FILTER_ODD, 0x7ffc7f, value); + dprintk(1,"set_scale: filter 0x%04x\n", value); + + return 0; +} + +static const u32 xtal = 28636363; + +static int set_pll(struct cx88_core *core, int prescale, u32 ofreq) +{ + static const u32 pre[] = { 0, 0, 0, 3, 2, 1 }; + u64 pll; + u32 reg; + int i; + + if (prescale < 2) + prescale = 2; + if (prescale > 5) + prescale = 5; + + pll = ofreq * 8 * prescale * (u64)(1 << 20); + do_div(pll,xtal); + reg = (pll & 0x3ffffff) | (pre[prescale] << 26); + if (((reg >> 20) & 0x3f) < 14) { + printk("%s/0: pll out of range\n",core->name); + return -1; + } + + dprintk(1,"set_pll: MO_PLL_REG 0x%08x [old=0x%08x,freq=%d]\n", + reg, cx_read(MO_PLL_REG), ofreq); + cx_write(MO_PLL_REG, reg); + for (i = 0; i < 100; i++) { + reg = cx_read(MO_DEVICE_STATUS); + if (reg & (1<<2)) { + dprintk(1,"pll locked [pre=%d,ofreq=%d]\n", + prescale,ofreq); + return 0; + } + dprintk(1,"pll not locked yet, waiting ...\n"); + msleep(10); + } + dprintk(1,"pll NOT locked [pre=%d,ofreq=%d]\n",prescale,ofreq); + return -1; +} + +int cx88_start_audio_dma(struct cx88_core *core) +{ + /* constant 128 made buzz in analog Nicam-stereo for bigger fifo_size */ + int bpl = cx88_sram_channels[SRAM_CH25].fifo_size/4; + + int rds_bpl = cx88_sram_channels[SRAM_CH27].fifo_size/AUD_RDS_LINES; + + /* If downstream RISC is enabled, bail out; ALSA is managing DMA */ + if (cx_read(MO_AUD_DMACNTRL) & 0x10) + return 0; + + /* setup fifo + format */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH25], bpl, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH26], bpl, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH27], + rds_bpl, 0); + + cx_write(MO_AUDD_LNGTH, bpl); /* fifo bpl size */ + cx_write(MO_AUDR_LNGTH, rds_bpl); /* fifo bpl size */ + + /* enable Up, Down and Audio RDS fifo */ + cx_write(MO_AUD_DMACNTRL, 0x0007); + + return 0; +} + +int cx88_stop_audio_dma(struct cx88_core *core) +{ + /* If downstream RISC is enabled, bail out; ALSA is managing DMA */ + if (cx_read(MO_AUD_DMACNTRL) & 0x10) + return 0; + + /* stop dma */ + cx_write(MO_AUD_DMACNTRL, 0x0000); + + return 0; +} + +static int set_tvaudio(struct cx88_core *core) +{ + v4l2_std_id norm = core->tvnorm; + + if (CX88_VMUX_TELEVISION != INPUT(core->input).type && + CX88_VMUX_CABLE != INPUT(core->input).type) + return 0; + + if (V4L2_STD_PAL_BG & norm) { + core->tvaudio = WW_BG; + + } else if (V4L2_STD_PAL_DK & norm) { + core->tvaudio = WW_DK; + + } else if (V4L2_STD_PAL_I & norm) { + core->tvaudio = WW_I; + + } else if (V4L2_STD_SECAM_L & norm) { + core->tvaudio = WW_L; + + } else if ((V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H) & norm) { + core->tvaudio = WW_BG; + + } else if (V4L2_STD_SECAM_DK & norm) { + core->tvaudio = WW_DK; + + } else if ((V4L2_STD_NTSC_M & norm) || + (V4L2_STD_PAL_M & norm)) { + core->tvaudio = WW_BTSC; + + } else if (V4L2_STD_NTSC_M_JP & norm) { + core->tvaudio = WW_EIAJ; + + } else { + printk("%s/0: tvaudio support needs work for this tv norm [%s], sorry\n", + core->name, v4l2_norm_to_name(core->tvnorm)); + core->tvaudio = WW_NONE; + return 0; + } + + cx_andor(MO_AFECFG_IO, 0x1f, 0x0); + cx88_set_tvaudio(core); + /* cx88_set_stereo(dev,V4L2_TUNER_MODE_STEREO); */ + +/* + This should be needed only on cx88-alsa. It seems that some cx88 chips have + bugs and does require DMA enabled for it to work. + */ + cx88_start_audio_dma(core); + return 0; +} + + + +int cx88_set_tvnorm(struct cx88_core *core, v4l2_std_id norm) +{ + u32 fsc8; + u32 adc_clock; + u32 vdec_clock; + u32 step_db,step_dr; + u64 tmp64; + u32 bdelay,agcdelay,htotal; + u32 cxiformat, cxoformat; + + core->tvnorm = norm; + fsc8 = norm_fsc8(norm); + adc_clock = xtal; + vdec_clock = fsc8; + step_db = fsc8; + step_dr = fsc8; + + if (norm & V4L2_STD_NTSC_M_JP) { + cxiformat = VideoFormatNTSCJapan; + cxoformat = 0x181f0008; + } else if (norm & V4L2_STD_NTSC_443) { + cxiformat = VideoFormatNTSC443; + cxoformat = 0x181f0008; + } else if (norm & V4L2_STD_PAL_M) { + cxiformat = VideoFormatPALM; + cxoformat = 0x1c1f0008; + } else if (norm & V4L2_STD_PAL_N) { + cxiformat = VideoFormatPALN; + cxoformat = 0x1c1f0008; + } else if (norm & V4L2_STD_PAL_Nc) { + cxiformat = VideoFormatPALNC; + cxoformat = 0x1c1f0008; + } else if (norm & V4L2_STD_PAL_60) { + cxiformat = VideoFormatPAL60; + cxoformat = 0x181f0008; + } else if (norm & V4L2_STD_NTSC) { + cxiformat = VideoFormatNTSC; + cxoformat = 0x181f0008; + } else if (norm & V4L2_STD_SECAM) { + step_db = 4250000 * 8; + step_dr = 4406250 * 8; + + cxiformat = VideoFormatSECAM; + cxoformat = 0x181f0008; + } else { /* PAL */ + cxiformat = VideoFormatPAL; + cxoformat = 0x181f0008; + } + + dprintk(1,"set_tvnorm: \"%s\" fsc8=%d adc=%d vdec=%d db/dr=%d/%d\n", + v4l2_norm_to_name(core->tvnorm), fsc8, adc_clock, vdec_clock, + step_db, step_dr); + set_pll(core,2,vdec_clock); + + dprintk(1,"set_tvnorm: MO_INPUT_FORMAT 0x%08x [old=0x%08x]\n", + cxiformat, cx_read(MO_INPUT_FORMAT) & 0x0f); + /* Chroma AGC must be disabled if SECAM is used, we enable it + by default on PAL and NTSC */ + cx_andor(MO_INPUT_FORMAT, 0x40f, + norm & V4L2_STD_SECAM ? cxiformat : cxiformat | 0x400); + + // FIXME: as-is from DScaler + dprintk(1,"set_tvnorm: MO_OUTPUT_FORMAT 0x%08x [old=0x%08x]\n", + cxoformat, cx_read(MO_OUTPUT_FORMAT)); + cx_write(MO_OUTPUT_FORMAT, cxoformat); + + // MO_SCONV_REG = adc clock / video dec clock * 2^17 + tmp64 = adc_clock * (u64)(1 << 17); + do_div(tmp64, vdec_clock); + dprintk(1,"set_tvnorm: MO_SCONV_REG 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SCONV_REG)); + cx_write(MO_SCONV_REG, (u32)tmp64); + + // MO_SUB_STEP = 8 * fsc / video dec clock * 2^22 + tmp64 = step_db * (u64)(1 << 22); + do_div(tmp64, vdec_clock); + dprintk(1,"set_tvnorm: MO_SUB_STEP 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SUB_STEP)); + cx_write(MO_SUB_STEP, (u32)tmp64); + + // MO_SUB_STEP_DR = 8 * 4406250 / video dec clock * 2^22 + tmp64 = step_dr * (u64)(1 << 22); + do_div(tmp64, vdec_clock); + dprintk(1,"set_tvnorm: MO_SUB_STEP_DR 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SUB_STEP_DR)); + cx_write(MO_SUB_STEP_DR, (u32)tmp64); + + // bdelay + agcdelay + bdelay = vdec_clock * 65 / 20000000 + 21; + agcdelay = vdec_clock * 68 / 20000000 + 15; + dprintk(1,"set_tvnorm: MO_AGC_BURST 0x%08x [old=0x%08x,bdelay=%d,agcdelay=%d]\n", + (bdelay << 8) | agcdelay, cx_read(MO_AGC_BURST), bdelay, agcdelay); + cx_write(MO_AGC_BURST, (bdelay << 8) | agcdelay); + + // htotal + tmp64 = norm_htotal(norm) * (u64)vdec_clock; + do_div(tmp64, fsc8); + htotal = (u32)tmp64; + dprintk(1,"set_tvnorm: MO_HTOTAL 0x%08x [old=0x%08x,htotal=%d]\n", + htotal, cx_read(MO_HTOTAL), (u32)tmp64); + cx_andor(MO_HTOTAL, 0x07ff, htotal); + + // vbi stuff, set vbi offset to 10 (for 20 Clk*2 pixels), this makes + // the effective vbi offset ~244 samples, the same as the Bt8x8 + cx_write(MO_VBI_PACKET, (10<<11) | norm_vbipack(norm)); + + // this is needed as well to set all tvnorm parameter + cx88_set_scale(core, 320, 240, V4L2_FIELD_INTERLACED); + + // audio + set_tvaudio(core); + + // tell i2c chips + call_all(core, core, s_std, norm); + + /* The chroma_agc control should be inaccessible if the video format is SECAM */ + v4l2_ctrl_grab(core->chroma_agc, cxiformat == VideoFormatSECAM); + + // done + return 0; +} + +/* ------------------------------------------------------------------ */ + +struct video_device *cx88_vdev_init(struct cx88_core *core, + struct pci_dev *pci, + const struct video_device *template_, + const char *type) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template_; + vfd->v4l2_dev = &core->v4l2_dev; + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", + core->name, type, core->board.name); + set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); + return vfd; +} + +struct cx88_core* cx88_core_get(struct pci_dev *pci) +{ + struct cx88_core *core; + + mutex_lock(&devlist); + list_for_each_entry(core, &cx88_devlist, devlist) { + if (pci->bus->number != core->pci_bus) + continue; + if (PCI_SLOT(pci->devfn) != core->pci_slot) + continue; + + if (0 != cx88_get_resources(core, pci)) { + mutex_unlock(&devlist); + return NULL; + } + atomic_inc(&core->refcount); + mutex_unlock(&devlist); + return core; + } + + core = cx88_core_create(pci, cx88_devcount); + if (NULL != core) { + cx88_devcount++; + list_add_tail(&core->devlist, &cx88_devlist); + } + + mutex_unlock(&devlist); + return core; +} + +void cx88_core_put(struct cx88_core *core, struct pci_dev *pci) +{ + release_mem_region(pci_resource_start(pci,0), + pci_resource_len(pci,0)); + + if (!atomic_dec_and_test(&core->refcount)) + return; + + mutex_lock(&devlist); + cx88_ir_fini(core); + if (0 == core->i2c_rc) { + if (core->i2c_rtc) + i2c_unregister_device(core->i2c_rtc); + i2c_del_adapter(&core->i2c_adap); + } + list_del(&core->devlist); + iounmap(core->lmmio); + cx88_devcount--; + mutex_unlock(&devlist); + v4l2_ctrl_handler_free(&core->video_hdl); + v4l2_ctrl_handler_free(&core->audio_hdl); + v4l2_device_unregister(&core->v4l2_dev); + kfree(core); +} + +/* ------------------------------------------------------------------ */ + +EXPORT_SYMBOL(cx88_print_irqbits); + +EXPORT_SYMBOL(cx88_core_irq); +EXPORT_SYMBOL(cx88_wakeup); +EXPORT_SYMBOL(cx88_reset); +EXPORT_SYMBOL(cx88_shutdown); + +EXPORT_SYMBOL(cx88_risc_buffer); +EXPORT_SYMBOL(cx88_risc_databuffer); +EXPORT_SYMBOL(cx88_risc_stopper); +EXPORT_SYMBOL(cx88_free_buffer); + +EXPORT_SYMBOL(cx88_sram_channels); +EXPORT_SYMBOL(cx88_sram_channel_setup); +EXPORT_SYMBOL(cx88_sram_channel_dump); + +EXPORT_SYMBOL(cx88_set_tvnorm); +EXPORT_SYMBOL(cx88_set_scale); + +EXPORT_SYMBOL(cx88_vdev_init); +EXPORT_SYMBOL(cx88_core_get); +EXPORT_SYMBOL(cx88_core_put); + +EXPORT_SYMBOL(cx88_ir_start); +EXPORT_SYMBOL(cx88_ir_stop); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + * kate: eol "unix"; indent-width 3; remove-trailing-space on; replace-trailing-space-save on; tab-width 8; replace-tabs off; space-indent off; mixed-indent off + */ diff --git a/drivers/media/video/cx88/cx88-dsp.c b/drivers/media/pci/cx88/cx88-dsp.c similarity index 100% rename from drivers/media/video/cx88/cx88-dsp.c rename to drivers/media/pci/cx88/cx88-dsp.c diff --git a/drivers/media/pci/cx88/cx88-dvb.c b/drivers/media/pci/cx88/cx88-dvb.c new file mode 100644 index 000000000000..d803bba09525 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-dvb.c @@ -0,0 +1,1778 @@ +/* + * + * device driver for Conexant 2388x based TV cards + * MPEG Transport Stream (DVB) routines + * + * (c) 2004, 2005 Chris Pascoe + * (c) 2004 Gerd Knorr [SuSE Labs] + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cx88.h" +#include "dvb-pll.h" +#include + +#include "mt352.h" +#include "mt352_priv.h" +#include "cx88-vp3054-i2c.h" +#include "zl10353.h" +#include "cx22702.h" +#include "or51132.h" +#include "lgdt330x.h" +#include "s5h1409.h" +#include "xc4000.h" +#include "xc5000.h" +#include "nxt200x.h" +#include "cx24123.h" +#include "isl6421.h" +#include "tuner-simple.h" +#include "tda9887.h" +#include "s5h1411.h" +#include "stv0299.h" +#include "z0194a.h" +#include "stv0288.h" +#include "stb6000.h" +#include "cx24116.h" +#include "stv0900.h" +#include "stb6100.h" +#include "stb6100_proc.h" +#include "mb86a16.h" +#include "ds3000.h" + +MODULE_DESCRIPTION("driver for cx2388x based DVB cards"); +MODULE_AUTHOR("Chris Pascoe "); +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CX88_VERSION); + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages [dvb]"); + +static unsigned int dvb_buf_tscnt = 32; +module_param(dvb_buf_tscnt, int, 0644); +MODULE_PARM_DESC(dvb_buf_tscnt, "DVB Buffer TS count [dvb]"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define dprintk(level,fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "%s/2-dvb: " fmt, core->name, ## arg) + +/* ------------------------------------------------------------------ */ + +static int dvb_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct cx8802_dev *dev = q->priv_data; + + dev->ts_packet_size = 188 * 4; + dev->ts_packet_count = dvb_buf_tscnt; + + *size = dev->ts_packet_size * dev->ts_packet_count; + *count = dvb_buf_tscnt; + return 0; +} + +static int dvb_buf_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct cx8802_dev *dev = q->priv_data; + return cx8802_buf_prepare(q, dev, (struct cx88_buffer*)vb,field); +} + +static void dvb_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx8802_dev *dev = q->priv_data; + cx8802_buf_queue(dev, (struct cx88_buffer*)vb); +} + +static void dvb_buf_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + cx88_free_buffer(q, (struct cx88_buffer*)vb); +} + +static const struct videobuf_queue_ops dvb_qops = { + .buf_setup = dvb_buf_setup, + .buf_prepare = dvb_buf_prepare, + .buf_queue = dvb_buf_queue, + .buf_release = dvb_buf_release, +}; + +/* ------------------------------------------------------------------ */ + +static int cx88_dvb_bus_ctrl(struct dvb_frontend* fe, int acquire) +{ + struct cx8802_dev *dev= fe->dvb->priv; + struct cx8802_driver *drv = NULL; + int ret = 0; + int fe_id; + + fe_id = videobuf_dvb_find_frontend(&dev->frontends, fe); + if (!fe_id) { + printk(KERN_ERR "%s() No frontend found\n", __func__); + return -EINVAL; + } + + mutex_lock(&dev->core->lock); + drv = cx8802_get_driver(dev, CX88_MPEG_DVB); + if (drv) { + if (acquire){ + dev->frontends.active_fe_id = fe_id; + ret = drv->request_acquire(drv); + } else { + ret = drv->request_release(drv); + dev->frontends.active_fe_id = 0; + } + } + mutex_unlock(&dev->core->lock); + + return ret; +} + +static void cx88_dvb_gate_ctrl(struct cx88_core *core, int open) +{ + struct videobuf_dvb_frontends *f; + struct videobuf_dvb_frontend *fe; + + if (!core->dvbdev) + return; + + f = &core->dvbdev->frontends; + + if (!f) + return; + + if (f->gate <= 1) /* undefined or fe0 */ + fe = videobuf_dvb_get_frontend(f, 1); + else + fe = videobuf_dvb_get_frontend(f, f->gate); + + if (fe && fe->dvb.frontend && fe->dvb.frontend->ops.i2c_gate_ctrl) + fe->dvb.frontend->ops.i2c_gate_ctrl(fe->dvb.frontend, open); +} + +/* ------------------------------------------------------------------ */ + +static int dvico_fusionhdtv_demod_init(struct dvb_frontend* fe) +{ + static const u8 clock_config [] = { CLOCK_CTL, 0x38, 0x39 }; + static const u8 reset [] = { RESET, 0x80 }; + static const u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static const u8 agc_cfg [] = { AGC_TARGET, 0x24, 0x20 }; + static const u8 gpp_ctl_cfg [] = { GPP_CTL, 0x33 }; + static const u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + return 0; +} + +static int dvico_dual_demod_init(struct dvb_frontend *fe) +{ + static const u8 clock_config [] = { CLOCK_CTL, 0x38, 0x38 }; + static const u8 reset [] = { RESET, 0x80 }; + static const u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static const u8 agc_cfg [] = { AGC_TARGET, 0x28, 0x20 }; + static const u8 gpp_ctl_cfg [] = { GPP_CTL, 0x33 }; + static const u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +static int dntv_live_dvbt_demod_init(struct dvb_frontend* fe) +{ + static const u8 clock_config [] = { 0x89, 0x38, 0x39 }; + static const u8 reset [] = { 0x50, 0x80 }; + static const u8 adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static const u8 agc_cfg [] = { 0x67, 0x10, 0x23, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x40, 0x40 }; + static const u8 dntv_extra[] = { 0xB5, 0x7A }; + static const u8 capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(2000); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + udelay(2000); + mt352_write(fe, dntv_extra, sizeof(dntv_extra)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +static const struct mt352_config dvico_fusionhdtv = { + .demod_address = 0x0f, + .demod_init = dvico_fusionhdtv_demod_init, +}; + +static const struct mt352_config dntv_live_dvbt_config = { + .demod_address = 0x0f, + .demod_init = dntv_live_dvbt_demod_init, +}; + +static const struct mt352_config dvico_fusionhdtv_dual = { + .demod_address = 0x0f, + .demod_init = dvico_dual_demod_init, +}; + +static const struct zl10353_config cx88_terratec_cinergy_ht_pci_mkii_config = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .if2 = 45600, +}; + +static struct mb86a16_config twinhan_vp1027 = { + .demod_address = 0x08, +}; + +#if defined(CONFIG_VIDEO_CX88_VP3054) || (defined(CONFIG_VIDEO_CX88_VP3054_MODULE) && defined(MODULE)) +static int dntv_live_dvbt_pro_demod_init(struct dvb_frontend* fe) +{ + static const u8 clock_config [] = { 0x89, 0x38, 0x38 }; + static const u8 reset [] = { 0x50, 0x80 }; + static const u8 adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static const u8 agc_cfg [] = { 0x67, 0x10, 0x20, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x40, 0x40 }; + static const u8 dntv_extra[] = { 0xB5, 0x7A }; + static const u8 capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(2000); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + udelay(2000); + mt352_write(fe, dntv_extra, sizeof(dntv_extra)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +static const struct mt352_config dntv_live_dvbt_pro_config = { + .demod_address = 0x0f, + .no_tuner = 1, + .demod_init = dntv_live_dvbt_pro_demod_init, +}; +#endif + +static const struct zl10353_config dvico_fusionhdtv_hybrid = { + .demod_address = 0x0f, + .no_tuner = 1, +}; + +static const struct zl10353_config dvico_fusionhdtv_xc3028 = { + .demod_address = 0x0f, + .if2 = 45600, + .no_tuner = 1, +}; + +static const struct mt352_config dvico_fusionhdtv_mt352_xc3028 = { + .demod_address = 0x0f, + .if2 = 4560, + .no_tuner = 1, + .demod_init = dvico_fusionhdtv_demod_init, +}; + +static const struct zl10353_config dvico_fusionhdtv_plus_v1_1 = { + .demod_address = 0x0f, +}; + +static const struct cx22702_config connexant_refboard_config = { + .demod_address = 0x43, + .output_mode = CX22702_SERIAL_OUTPUT, +}; + +static const struct cx22702_config hauppauge_hvr_config = { + .demod_address = 0x63, + .output_mode = CX22702_SERIAL_OUTPUT, +}; + +static int or51132_set_ts_param(struct dvb_frontend* fe, int is_punctured) +{ + struct cx8802_dev *dev= fe->dvb->priv; + dev->ts_gen_cntrl = is_punctured ? 0x04 : 0x00; + return 0; +} + +static const struct or51132_config pchdtv_hd3000 = { + .demod_address = 0x15, + .set_ts_params = or51132_set_ts_param, +}; + +static int lgdt330x_pll_rf_set(struct dvb_frontend* fe, int index) +{ + struct cx8802_dev *dev= fe->dvb->priv; + struct cx88_core *core = dev->core; + + dprintk(1, "%s: index = %d\n", __func__, index); + if (index == 0) + cx_clear(MO_GP0_IO, 8); + else + cx_set(MO_GP0_IO, 8); + return 0; +} + +static int lgdt330x_set_ts_param(struct dvb_frontend* fe, int is_punctured) +{ + struct cx8802_dev *dev= fe->dvb->priv; + if (is_punctured) + dev->ts_gen_cntrl |= 0x04; + else + dev->ts_gen_cntrl &= ~0x04; + return 0; +} + +static struct lgdt330x_config fusionhdtv_3_gold = { + .demod_address = 0x0e, + .demod_chip = LGDT3302, + .serial_mpeg = 0x04, /* TPSERIAL for 3302 in TOP_CONTROL */ + .set_ts_params = lgdt330x_set_ts_param, +}; + +static const struct lgdt330x_config fusionhdtv_5_gold = { + .demod_address = 0x0e, + .demod_chip = LGDT3303, + .serial_mpeg = 0x40, /* TPSERIAL for 3303 in TOP_CONTROL */ + .set_ts_params = lgdt330x_set_ts_param, +}; + +static const struct lgdt330x_config pchdtv_hd5500 = { + .demod_address = 0x59, + .demod_chip = LGDT3303, + .serial_mpeg = 0x40, /* TPSERIAL for 3303 in TOP_CONTROL */ + .set_ts_params = lgdt330x_set_ts_param, +}; + +static int nxt200x_set_ts_param(struct dvb_frontend* fe, int is_punctured) +{ + struct cx8802_dev *dev= fe->dvb->priv; + dev->ts_gen_cntrl = is_punctured ? 0x04 : 0x00; + return 0; +} + +static const struct nxt200x_config ati_hdtvwonder = { + .demod_address = 0x0a, + .set_ts_params = nxt200x_set_ts_param, +}; + +static int cx24123_set_ts_param(struct dvb_frontend* fe, + int is_punctured) +{ + struct cx8802_dev *dev= fe->dvb->priv; + dev->ts_gen_cntrl = 0x02; + return 0; +} + +static int kworld_dvbs_100_set_voltage(struct dvb_frontend* fe, + fe_sec_voltage_t voltage) +{ + struct cx8802_dev *dev= fe->dvb->priv; + struct cx88_core *core = dev->core; + + if (voltage == SEC_VOLTAGE_OFF) + cx_write(MO_GP0_IO, 0x000006fb); + else + cx_write(MO_GP0_IO, 0x000006f9); + + if (core->prev_set_voltage) + return core->prev_set_voltage(fe, voltage); + return 0; +} + +static int geniatech_dvbs_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct cx8802_dev *dev= fe->dvb->priv; + struct cx88_core *core = dev->core; + + if (voltage == SEC_VOLTAGE_OFF) { + dprintk(1,"LNB Voltage OFF\n"); + cx_write(MO_GP0_IO, 0x0000efff); + } + + if (core->prev_set_voltage) + return core->prev_set_voltage(fe, voltage); + return 0; +} + +static int tevii_dvbs_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct cx8802_dev *dev= fe->dvb->priv; + struct cx88_core *core = dev->core; + + cx_set(MO_GP0_IO, 0x6040); + switch (voltage) { + case SEC_VOLTAGE_13: + cx_clear(MO_GP0_IO, 0x20); + break; + case SEC_VOLTAGE_18: + cx_set(MO_GP0_IO, 0x20); + break; + case SEC_VOLTAGE_OFF: + cx_clear(MO_GP0_IO, 0x20); + break; + } + + if (core->prev_set_voltage) + return core->prev_set_voltage(fe, voltage); + return 0; +} + +static int vp1027_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + switch (voltage) { + case SEC_VOLTAGE_13: + dprintk(1, "LNB SEC Voltage=13\n"); + cx_write(MO_GP0_IO, 0x00001220); + break; + case SEC_VOLTAGE_18: + dprintk(1, "LNB SEC Voltage=18\n"); + cx_write(MO_GP0_IO, 0x00001222); + break; + case SEC_VOLTAGE_OFF: + dprintk(1, "LNB Voltage OFF\n"); + cx_write(MO_GP0_IO, 0x00001230); + break; + } + + if (core->prev_set_voltage) + return core->prev_set_voltage(fe, voltage); + return 0; +} + +static const struct cx24123_config geniatech_dvbs_config = { + .demod_address = 0x55, + .set_ts_params = cx24123_set_ts_param, +}; + +static const struct cx24123_config hauppauge_novas_config = { + .demod_address = 0x55, + .set_ts_params = cx24123_set_ts_param, +}; + +static const struct cx24123_config kworld_dvbs_100_config = { + .demod_address = 0x15, + .set_ts_params = cx24123_set_ts_param, + .lnb_polarity = 1, +}; + +static const struct s5h1409_config pinnacle_pctv_hd_800i_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_PARALLEL_OUTPUT, + .gpio = S5H1409_GPIO_ON, + .qam_if = 44000, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_NONCONTINOUS_NONINVERTING_CLOCK, +}; + +static const struct s5h1409_config dvico_hdtv5_pci_nano_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static const struct s5h1409_config kworld_atsc_120_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_SERIAL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .inversion = S5H1409_INVERSION_OFF, + .status_mode = S5H1409_DEMODLOCKING, + .mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + +static const struct xc5000_config pinnacle_pctv_hd_800i_tuner_config = { + .i2c_address = 0x64, + .if_khz = 5380, +}; + +static const struct zl10353_config cx88_pinnacle_hybrid_pctv = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .if2 = 45600, +}; + +static const struct zl10353_config cx88_geniatech_x8000_mt = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static const struct s5h1411_config dvico_fusionhdtv7_config = { + .output_mode = S5H1411_SERIAL_OUTPUT, + .gpio = S5H1411_GPIO_ON, + .mpeg_timing = S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, + .qam_if = S5H1411_IF_44000, + .vsb_if = S5H1411_IF_44000, + .inversion = S5H1411_INVERSION_OFF, + .status_mode = S5H1411_DEMODLOCKING +}; + +static const struct xc5000_config dvico_fusionhdtv7_tuner_config = { + .i2c_address = 0xc2 >> 1, + .if_khz = 5380, +}; + +static int attach_xc3028(u8 addr, struct cx8802_dev *dev) +{ + struct dvb_frontend *fe; + struct videobuf_dvb_frontend *fe0 = NULL; + struct xc2028_ctrl ctl; + struct xc2028_config cfg = { + .i2c_adap = &dev->core->i2c_adap, + .i2c_addr = addr, + .ctrl = &ctl, + }; + + /* Get the first frontend */ + fe0 = videobuf_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + return -EINVAL; + + if (!fe0->dvb.frontend) { + printk(KERN_ERR "%s/2: dvb frontend not attached. " + "Can't attach xc3028\n", + dev->core->name); + return -EINVAL; + } + + /* + * Some xc3028 devices may be hidden by an I2C gate. This is known + * to happen with some s5h1409-based devices. + * Now that I2C gate is open, sets up xc3028 configuration + */ + cx88_setup_xc3028(dev->core, &ctl); + + fe = dvb_attach(xc2028_attach, fe0->dvb.frontend, &cfg); + if (!fe) { + printk(KERN_ERR "%s/2: xc3028 attach failed\n", + dev->core->name); + dvb_frontend_detach(fe0->dvb.frontend); + dvb_unregister_frontend(fe0->dvb.frontend); + fe0->dvb.frontend = NULL; + return -EINVAL; + } + + printk(KERN_INFO "%s/2: xc3028 attached\n", + dev->core->name); + + return 0; +} + +static int attach_xc4000(struct cx8802_dev *dev, struct xc4000_config *cfg) +{ + struct dvb_frontend *fe; + struct videobuf_dvb_frontend *fe0 = NULL; + + /* Get the first frontend */ + fe0 = videobuf_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + return -EINVAL; + + if (!fe0->dvb.frontend) { + printk(KERN_ERR "%s/2: dvb frontend not attached. " + "Can't attach xc4000\n", + dev->core->name); + return -EINVAL; + } + + fe = dvb_attach(xc4000_attach, fe0->dvb.frontend, &dev->core->i2c_adap, + cfg); + if (!fe) { + printk(KERN_ERR "%s/2: xc4000 attach failed\n", + dev->core->name); + dvb_frontend_detach(fe0->dvb.frontend); + dvb_unregister_frontend(fe0->dvb.frontend); + fe0->dvb.frontend = NULL; + return -EINVAL; + } + + printk(KERN_INFO "%s/2: xc4000 attached\n", dev->core->name); + + return 0; +} + +static int cx24116_set_ts_param(struct dvb_frontend *fe, + int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + dev->ts_gen_cntrl = 0x2; + + return 0; +} + +static int stv0900_set_ts_param(struct dvb_frontend *fe, + int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + dev->ts_gen_cntrl = 0; + + return 0; +} + +static int cx24116_reset_device(struct dvb_frontend *fe) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + /* Reset the part */ + /* Put the cx24116 into reset */ + cx_write(MO_SRST_IO, 0); + msleep(10); + /* Take the cx24116 out of reset */ + cx_write(MO_SRST_IO, 1); + msleep(10); + + return 0; +} + +static const struct cx24116_config hauppauge_hvr4000_config = { + .demod_address = 0x05, + .set_ts_params = cx24116_set_ts_param, + .reset_device = cx24116_reset_device, +}; + +static const struct cx24116_config tevii_s460_config = { + .demod_address = 0x55, + .set_ts_params = cx24116_set_ts_param, + .reset_device = cx24116_reset_device, +}; + +static int ds3000_set_ts_param(struct dvb_frontend *fe, + int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + dev->ts_gen_cntrl = 4; + + return 0; +} + +static struct ds3000_config tevii_ds3000_config = { + .demod_address = 0x68, + .set_ts_params = ds3000_set_ts_param, +}; + +static const struct stv0900_config prof_7301_stv0900_config = { + .demod_address = 0x6a, +/* demod_mode = 0,*/ + .xtal = 27000000, + .clkmode = 3,/* 0-CLKI, 2-XTALI, else AUTO */ + .diseqc_mode = 2,/* 2/3 PWM */ + .tun1_maddress = 0,/* 0x60 */ + .tun1_adc = 0,/* 2 Vpp */ + .path1_mode = 3, + .set_ts_params = stv0900_set_ts_param, +}; + +static const struct stb6100_config prof_7301_stb6100_config = { + .tuner_address = 0x60, + .refclock = 27000000, +}; + +static const struct stv0299_config tevii_tuner_sharp_config = { + .demod_address = 0x68, + .inittab = sharp_z0194a_inittab, + .mclk = 88000000UL, + .invert = 1, + .skip_reinit = 0, + .lock_output = 1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = sharp_z0194a_set_symbol_rate, + .set_ts_params = cx24116_set_ts_param, +}; + +static const struct stv0288_config tevii_tuner_earda_config = { + .demod_address = 0x68, + .min_delay_ms = 100, + .set_ts_params = cx24116_set_ts_param, +}; + +static int cx8802_alloc_frontends(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + struct videobuf_dvb_frontend *fe = NULL; + int i; + + mutex_init(&dev->frontends.lock); + INIT_LIST_HEAD(&dev->frontends.felist); + + if (!core->board.num_frontends) + return -ENODEV; + + printk(KERN_INFO "%s() allocating %d frontend(s)\n", __func__, + core->board.num_frontends); + for (i = 1; i <= core->board.num_frontends; i++) { + fe = videobuf_dvb_alloc_frontend(&dev->frontends, i); + if (!fe) { + printk(KERN_ERR "%s() failed to alloc\n", __func__); + videobuf_dvb_dealloc_frontends(&dev->frontends); + return -ENOMEM; + } + } + return 0; +} + + + +static const u8 samsung_smt_7020_inittab[] = { + 0x01, 0x15, + 0x02, 0x00, + 0x03, 0x00, + 0x04, 0x7D, + 0x05, 0x0F, + 0x06, 0x02, + 0x07, 0x00, + 0x08, 0x60, + + 0x0A, 0xC2, + 0x0B, 0x00, + 0x0C, 0x01, + 0x0D, 0x81, + 0x0E, 0x44, + 0x0F, 0x09, + 0x10, 0x3C, + 0x11, 0x84, + 0x12, 0xDA, + 0x13, 0x99, + 0x14, 0x8D, + 0x15, 0xCE, + 0x16, 0xE8, + 0x17, 0x43, + 0x18, 0x1C, + 0x19, 0x1B, + 0x1A, 0x1D, + + 0x1C, 0x12, + 0x1D, 0x00, + 0x1E, 0x00, + 0x1F, 0x00, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + + 0x28, 0x02, + 0x29, 0x28, + 0x2A, 0x14, + 0x2B, 0x0F, + 0x2C, 0x09, + 0x2D, 0x05, + + 0x31, 0x1F, + 0x32, 0x19, + 0x33, 0xFC, + 0x34, 0x13, + 0xff, 0xff, +}; + + +static int samsung_smt_7020_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct cx8802_dev *dev = fe->dvb->priv; + u8 buf[4]; + u32 div; + struct i2c_msg msg = { + .addr = 0x61, + .flags = 0, + .buf = buf, + .len = sizeof(buf) }; + + div = c->frequency / 125; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x84; /* 0xC4 */ + buf[3] = 0x00; + + if (c->frequency < 1500000) + buf[3] |= 0x10; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (i2c_transfer(&dev->core->i2c_adap, &msg, 1) != 1) + return -EIO; + + return 0; +} + +static int samsung_smt_7020_set_tone(struct dvb_frontend *fe, + fe_sec_tone_mode_t tone) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + cx_set(MO_GP0_IO, 0x0800); + + switch (tone) { + case SEC_TONE_ON: + cx_set(MO_GP0_IO, 0x08); + break; + case SEC_TONE_OFF: + cx_clear(MO_GP0_IO, 0x08); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int samsung_smt_7020_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + u8 data; + struct i2c_msg msg = { + .addr = 8, + .flags = 0, + .buf = &data, + .len = sizeof(data) }; + + cx_set(MO_GP0_IO, 0x8000); + + switch (voltage) { + case SEC_VOLTAGE_OFF: + break; + case SEC_VOLTAGE_13: + data = ISL6421_EN1 | ISL6421_LLC1; + cx_clear(MO_GP0_IO, 0x80); + break; + case SEC_VOLTAGE_18: + data = ISL6421_EN1 | ISL6421_LLC1 | ISL6421_VSEL1; + cx_clear(MO_GP0_IO, 0x80); + break; + default: + return -EINVAL; + }; + + return (i2c_transfer(&dev->core->i2c_adap, &msg, 1) == 1) ? 0 : -EIO; +} + +static int samsung_smt_7020_stv0299_set_symbol_rate(struct dvb_frontend *fe, + u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { + aclk = 0xb7; + bclk = 0x47; + } else if (srate < 3000000) { + aclk = 0xb7; + bclk = 0x4b; + } else if (srate < 7000000) { + aclk = 0xb7; + bclk = 0x4f; + } else if (srate < 14000000) { + aclk = 0xb7; + bclk = 0x53; + } else if (srate < 30000000) { + aclk = 0xb6; + bclk = 0x53; + } else if (srate < 45000000) { + aclk = 0xb4; + bclk = 0x51; + } + + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, ratio & 0xf0); + + return 0; +} + + +static const struct stv0299_config samsung_stv0299_config = { + .demod_address = 0x68, + .inittab = samsung_smt_7020_inittab, + .mclk = 88000000UL, + .invert = 0, + .skip_reinit = 0, + .lock_output = STV0299_LOCKOUTPUT_LK, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = samsung_smt_7020_stv0299_set_symbol_rate, +}; + +static int dvb_register(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + struct videobuf_dvb_frontend *fe0, *fe1 = NULL; + int mfe_shared = 0; /* bus not shared by default */ + int res = -EINVAL; + + if (0 != core->i2c_rc) { + printk(KERN_ERR "%s/2: no i2c-bus available, cannot attach dvb drivers\n", core->name); + goto frontend_detach; + } + + /* Get the first frontend */ + fe0 = videobuf_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + goto frontend_detach; + + /* multi-frontend gate control is undefined or defaults to fe0 */ + dev->frontends.gate = 0; + + /* Sets the gate control callback to be used by i2c command calls */ + core->gate_ctrl = cx88_dvb_gate_ctrl; + + /* init frontend(s) */ + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_DVB_T1: + fe0->dvb.frontend = dvb_attach(cx22702_attach, + &connexant_refboard_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, &core->i2c_adap, + DVB_PLL_THOMSON_DTT759X)) + goto frontend_detach; + } + break; + case CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1: + case CX88_BOARD_CONEXANT_DVB_T1: + case CX88_BOARD_KWORLD_DVB_T_CX22702: + case CX88_BOARD_WINFAST_DTV1000: + fe0->dvb.frontend = dvb_attach(cx22702_attach, + &connexant_refboard_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x60, &core->i2c_adap, + DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + } + break; + case CX88_BOARD_WINFAST_DTV2000H: + case CX88_BOARD_HAUPPAUGE_HVR1100: + case CX88_BOARD_HAUPPAUGE_HVR1100LP: + case CX88_BOARD_HAUPPAUGE_HVR1300: + fe0->dvb.frontend = dvb_attach(cx22702_attach, + &hauppauge_hvr_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_PHILIPS_FMD1216ME_MK3)) + goto frontend_detach; + } + break; + case CX88_BOARD_WINFAST_DTV2000H_J: + fe0->dvb.frontend = dvb_attach(cx22702_attach, + &hauppauge_hvr_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_PHILIPS_FMD1216MEX_MK3)) + goto frontend_detach; + } + break; + case CX88_BOARD_HAUPPAUGE_HVR3000: + /* MFE frontend 1 */ + mfe_shared = 1; + dev->frontends.gate = 2; + /* DVB-S init */ + fe0->dvb.frontend = dvb_attach(cx24123_attach, + &hauppauge_novas_config, + &dev->core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(isl6421_attach, + fe0->dvb.frontend, + &dev->core->i2c_adap, + 0x08, ISL6421_DCL, 0x00)) + goto frontend_detach; + } + /* MFE frontend 2 */ + fe1 = videobuf_dvb_get_frontend(&dev->frontends, 2); + if (!fe1) + goto frontend_detach; + /* DVB-T init */ + fe1->dvb.frontend = dvb_attach(cx22702_attach, + &hauppauge_hvr_config, + &dev->core->i2c_adap); + if (fe1->dvb.frontend) { + fe1->dvb.frontend->id = 1; + if (!dvb_attach(simple_tuner_attach, + fe1->dvb.frontend, + &dev->core->i2c_adap, + 0x61, TUNER_PHILIPS_FMD1216ME_MK3)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS: + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dvico_fusionhdtv, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x60, NULL, DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + break; + } + /* ZL10353 replaces MT352 on later cards */ + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_plus_v1_1, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x60, NULL, DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL: + /* The tin box says DEE1601, but it seems to be DTT7579 + * compatible, with a slightly different MT352 AGC gain. */ + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dvico_fusionhdtv_dual, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, NULL, DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + break; + } + /* ZL10353 replaces MT352 on later cards */ + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_plus_v1_1, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, NULL, DVB_PLL_THOMSON_DTT7579)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1: + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dvico_fusionhdtv, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, NULL, DVB_PLL_LG_Z201)) + goto frontend_detach; + } + break; + case CX88_BOARD_KWORLD_DVB_T: + case CX88_BOARD_DNTV_LIVE_DVB_T: + case CX88_BOARD_ADSTECH_DVB_T_PCI: + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dntv_live_dvbt_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, + 0x61, NULL, DVB_PLL_UNKNOWN_1)) + goto frontend_detach; + } + break; + case CX88_BOARD_DNTV_LIVE_DVB_T_PRO: +#if defined(CONFIG_VIDEO_CX88_VP3054) || (defined(CONFIG_VIDEO_CX88_VP3054_MODULE) && defined(MODULE)) + /* MT352 is on a secondary I2C bus made from some GPIO lines */ + fe0->dvb.frontend = dvb_attach(mt352_attach, &dntv_live_dvbt_pro_config, + &dev->vp3054->adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_PHILIPS_FMD1216ME_MK3)) + goto frontend_detach; + } +#else + printk(KERN_ERR "%s/2: built without vp3054 support\n", + core->name); +#endif + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_hybrid, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_THOMSON_FE6600)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &dvico_fusionhdtv_xc3028, + &core->i2c_adap); + if (fe0->dvb.frontend == NULL) + fe0->dvb.frontend = dvb_attach(mt352_attach, + &dvico_fusionhdtv_mt352_xc3028, + &core->i2c_adap); + /* + * On this board, the demod provides the I2C bus pullup. + * We must not permit gate_ctrl to be performed, or + * the xc3028 cannot communicate on the bus. + */ + if (fe0->dvb.frontend) + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + break; + case CX88_BOARD_PCHDTV_HD3000: + fe0->dvb.frontend = dvb_attach(or51132_attach, &pchdtv_hd3000, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_THOMSON_DTT761X)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q: + dev->ts_gen_cntrl = 0x08; + + /* Do a hardware reset of chip before using it. */ + cx_clear(MO_GP0_IO, 1); + mdelay(100); + cx_set(MO_GP0_IO, 1); + mdelay(200); + + /* Select RF connector callback */ + fusionhdtv_3_gold.pll_rf_set = lgdt330x_pll_rf_set; + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_3_gold, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_MICROTUNE_4042FI5)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T: + dev->ts_gen_cntrl = 0x08; + + /* Do a hardware reset of chip before using it. */ + cx_clear(MO_GP0_IO, 1); + mdelay(100); + cx_set(MO_GP0_IO, 9); + mdelay(200); + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_3_gold, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_THOMSON_DTT761X)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD: + dev->ts_gen_cntrl = 0x08; + + /* Do a hardware reset of chip before using it. */ + cx_clear(MO_GP0_IO, 1); + mdelay(100); + cx_set(MO_GP0_IO, 1); + mdelay(200); + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &fusionhdtv_5_gold, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_LG_TDVS_H06XF)) + goto frontend_detach; + if (!dvb_attach(tda9887_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x43)) + goto frontend_detach; + } + break; + case CX88_BOARD_PCHDTV_HD5500: + dev->ts_gen_cntrl = 0x08; + + /* Do a hardware reset of chip before using it. */ + cx_clear(MO_GP0_IO, 1); + mdelay(100); + cx_set(MO_GP0_IO, 1); + mdelay(200); + fe0->dvb.frontend = dvb_attach(lgdt330x_attach, + &pchdtv_hd5500, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_LG_TDVS_H06XF)) + goto frontend_detach; + if (!dvb_attach(tda9887_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x43)) + goto frontend_detach; + } + break; + case CX88_BOARD_ATI_HDTVWONDER: + fe0->dvb.frontend = dvb_attach(nxt200x_attach, + &ati_hdtvwonder, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x61, + TUNER_PHILIPS_TUV1236D)) + goto frontend_detach; + } + break; + case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: + case CX88_BOARD_HAUPPAUGE_NOVASE2_S1: + fe0->dvb.frontend = dvb_attach(cx24123_attach, + &hauppauge_novas_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(isl6421_attach, fe0->dvb.frontend, + &core->i2c_adap, 0x08, ISL6421_DCL, 0x00)) + goto frontend_detach; + } + break; + case CX88_BOARD_KWORLD_DVBS_100: + fe0->dvb.frontend = dvb_attach(cx24123_attach, + &kworld_dvbs_100_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = kworld_dvbs_100_set_voltage; + } + break; + case CX88_BOARD_GENIATECH_DVBS: + fe0->dvb.frontend = dvb_attach(cx24123_attach, + &geniatech_dvbs_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = geniatech_dvbs_set_voltage; + } + break; + case CX88_BOARD_PINNACLE_PCTV_HD_800i: + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &pinnacle_pctv_hd_800i_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(xc5000_attach, fe0->dvb.frontend, + &core->i2c_adap, + &pinnacle_pctv_hd_800i_tuner_config)) + goto frontend_detach; + } + break; + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &dvico_hdtv5_pci_nano_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &core->i2c_adap, + .i2c_addr = 0x61, + }; + static struct xc2028_ctrl ctl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + .scode_table = XC3028_FE_OREN538, + }; + + fe = dvb_attach(xc2028_attach, + fe0->dvb.frontend, &cfg); + if (fe != NULL && fe->ops.tuner_ops.set_config != NULL) + fe->ops.tuner_ops.set_config(fe, &ctl); + } + break; + case CX88_BOARD_PINNACLE_HYBRID_PCTV: + case CX88_BOARD_WINFAST_DTV1800H: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &cx88_pinnacle_hybrid_pctv, + &core->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + } + break; + case CX88_BOARD_WINFAST_DTV1800H_XC4000: + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &cx88_pinnacle_hybrid_pctv, + &core->i2c_adap); + if (fe0->dvb.frontend) { + struct xc4000_config cfg = { + .i2c_address = 0x61, + .default_pm = 0, + .dvb_amplitude = 134, + .set_smoothedcvbs = 1, + .if_khz = 4560 + }; + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (attach_xc4000(dev, &cfg) < 0) + goto frontend_detach; + } + break; + case CX88_BOARD_GENIATECH_X8000_MT: + dev->ts_gen_cntrl = 0x00; + + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &cx88_geniatech_x8000_mt, + &core->i2c_adap); + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + break; + case CX88_BOARD_KWORLD_ATSC_120: + fe0->dvb.frontend = dvb_attach(s5h1409_attach, + &kworld_atsc_120_config, + &core->i2c_adap); + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + break; + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &dvico_fusionhdtv7_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(xc5000_attach, fe0->dvb.frontend, + &core->i2c_adap, + &dvico_fusionhdtv7_tuner_config)) + goto frontend_detach; + } + break; + case CX88_BOARD_HAUPPAUGE_HVR4000: + /* MFE frontend 1 */ + mfe_shared = 1; + dev->frontends.gate = 2; + /* DVB-S/S2 Init */ + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &hauppauge_hvr4000_config, + &dev->core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(isl6421_attach, + fe0->dvb.frontend, + &dev->core->i2c_adap, + 0x08, ISL6421_DCL, 0x00)) + goto frontend_detach; + } + /* MFE frontend 2 */ + fe1 = videobuf_dvb_get_frontend(&dev->frontends, 2); + if (!fe1) + goto frontend_detach; + /* DVB-T Init */ + fe1->dvb.frontend = dvb_attach(cx22702_attach, + &hauppauge_hvr_config, + &dev->core->i2c_adap); + if (fe1->dvb.frontend) { + fe1->dvb.frontend->id = 1; + if (!dvb_attach(simple_tuner_attach, + fe1->dvb.frontend, + &dev->core->i2c_adap, + 0x61, TUNER_PHILIPS_FMD1216ME_MK3)) + goto frontend_detach; + } + break; + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &hauppauge_hvr4000_config, + &dev->core->i2c_adap); + if (fe0->dvb.frontend) { + if (!dvb_attach(isl6421_attach, + fe0->dvb.frontend, + &dev->core->i2c_adap, + 0x08, ISL6421_DCL, 0x00)) + goto frontend_detach; + } + break; + case CX88_BOARD_PROF_6200: + case CX88_BOARD_TBS_8910: + case CX88_BOARD_TEVII_S420: + fe0->dvb.frontend = dvb_attach(stv0299_attach, + &tevii_tuner_sharp_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, fe0->dvb.frontend, 0x60, + &core->i2c_adap, DVB_PLL_OPERA1)) + goto frontend_detach; + core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + + } else { + fe0->dvb.frontend = dvb_attach(stv0288_attach, + &tevii_tuner_earda_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(stb6000_attach, fe0->dvb.frontend, 0x61, + &core->i2c_adap)) + goto frontend_detach; + core->prev_set_voltage = fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + } + } + break; + case CX88_BOARD_TEVII_S460: + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &tevii_s460_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) + fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + break; + case CX88_BOARD_TEVII_S464: + fe0->dvb.frontend = dvb_attach(ds3000_attach, + &tevii_ds3000_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) + fe0->dvb.frontend->ops.set_voltage = + tevii_dvbs_set_voltage; + break; + case CX88_BOARD_OMICOM_SS4_PCI: + case CX88_BOARD_TBS_8920: + case CX88_BOARD_PROF_7300: + case CX88_BOARD_SATTRADE_ST4200: + fe0->dvb.frontend = dvb_attach(cx24116_attach, + &hauppauge_hvr4000_config, + &core->i2c_adap); + if (fe0->dvb.frontend != NULL) + fe0->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + break; + case CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &cx88_terratec_cinergy_ht_pci_mkii_config, + &core->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.i2c_gate_ctrl = NULL; + if (attach_xc3028(0x61, dev) < 0) + goto frontend_detach; + } + break; + case CX88_BOARD_PROF_7301:{ + struct dvb_tuner_ops *tuner_ops = NULL; + + fe0->dvb.frontend = dvb_attach(stv0900_attach, + &prof_7301_stv0900_config, + &core->i2c_adap, 0); + if (fe0->dvb.frontend != NULL) { + if (!dvb_attach(stb6100_attach, fe0->dvb.frontend, + &prof_7301_stb6100_config, + &core->i2c_adap)) + goto frontend_detach; + + tuner_ops = &fe0->dvb.frontend->ops.tuner_ops; + tuner_ops->set_frequency = stb6100_set_freq; + tuner_ops->get_frequency = stb6100_get_freq; + tuner_ops->set_bandwidth = stb6100_set_bandw; + tuner_ops->get_bandwidth = stb6100_get_bandw; + + core->prev_set_voltage = + fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = + tevii_dvbs_set_voltage; + } + break; + } + case CX88_BOARD_SAMSUNG_SMT_7020: + dev->ts_gen_cntrl = 0x08; + + cx_set(MO_GP0_IO, 0x0101); + + cx_clear(MO_GP0_IO, 0x01); + mdelay(100); + cx_set(MO_GP0_IO, 0x01); + mdelay(200); + + fe0->dvb.frontend = dvb_attach(stv0299_attach, + &samsung_stv0299_config, + &dev->core->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.set_params = + samsung_smt_7020_tuner_set_params; + fe0->dvb.frontend->tuner_priv = + &dev->core->i2c_adap; + fe0->dvb.frontend->ops.set_voltage = + samsung_smt_7020_set_voltage; + fe0->dvb.frontend->ops.set_tone = + samsung_smt_7020_set_tone; + } + + break; + case CX88_BOARD_TWINHAN_VP1027_DVBS: + dev->ts_gen_cntrl = 0x00; + fe0->dvb.frontend = dvb_attach(mb86a16_attach, + &twinhan_vp1027, + &core->i2c_adap); + if (fe0->dvb.frontend) { + core->prev_set_voltage = + fe0->dvb.frontend->ops.set_voltage; + fe0->dvb.frontend->ops.set_voltage = + vp1027_set_voltage; + } + break; + + default: + printk(KERN_ERR "%s/2: The frontend of your DVB/ATSC card isn't supported yet\n", + core->name); + break; + } + + if ( (NULL == fe0->dvb.frontend) || (fe1 && NULL == fe1->dvb.frontend) ) { + printk(KERN_ERR + "%s/2: frontend initialization failed\n", + core->name); + goto frontend_detach; + } + /* define general-purpose callback pointer */ + fe0->dvb.frontend->callback = cx88_tuner_callback; + + /* Ensure all frontends negotiate bus access */ + fe0->dvb.frontend->ops.ts_bus_ctrl = cx88_dvb_bus_ctrl; + if (fe1) + fe1->dvb.frontend->ops.ts_bus_ctrl = cx88_dvb_bus_ctrl; + + /* Put the analog decoder in standby to keep it quiet */ + call_all(core, core, s_power, 0); + + /* register everything */ + res = videobuf_dvb_register_bus(&dev->frontends, THIS_MODULE, dev, + &dev->pci->dev, adapter_nr, mfe_shared); + if (res) + goto frontend_detach; + return res; + +frontend_detach: + core->gate_ctrl = NULL; + videobuf_dvb_dealloc_frontends(&dev->frontends); + return res; +} + +/* ----------------------------------------------------------- */ + +/* CX8802 MPEG -> mini driver - We have been given the hardware */ +static int cx8802_dvb_advise_acquire(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + int err = 0; + dprintk( 1, "%s\n", __func__); + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* We arrive here with either the cx23416 or the cx22702 + * on the bus. Take the bus from the cx23416 and enable the + * cx22702 demod + */ + /* Toggle reset on cx22702 leaving i2c active */ + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000080); + udelay(50); + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + /* enable the cx22702 pins */ + cx_clear(MO_GP0_IO, 0x00000004); + udelay(1000); + break; + + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + /* Toggle reset on cx22702 leaving i2c active */ + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + cx_clear(MO_GP0_IO, 0x00000080); + udelay(50); + cx_set(MO_GP0_IO, 0x00000080); + udelay(1000); + switch (core->dvbdev->frontends.active_fe_id) { + case 1: /* DVB-S/S2 Enabled */ + /* tri-state the cx22702 pins */ + cx_set(MO_GP0_IO, 0x00000004); + /* Take the cx24116/cx24123 out of reset */ + cx_write(MO_SRST_IO, 1); + core->dvbdev->ts_gen_cntrl = 0x02; /* Parallel IO */ + break; + case 2: /* DVB-T Enabled */ + /* Put the cx24116/cx24123 into reset */ + cx_write(MO_SRST_IO, 0); + /* enable the cx22702 pins */ + cx_clear(MO_GP0_IO, 0x00000004); + core->dvbdev->ts_gen_cntrl = 0x0c; /* Serial IO */ + break; + } + udelay(1000); + break; + + case CX88_BOARD_WINFAST_DTV2000H_PLUS: + /* set RF input to AIR for DVB-T (GPIO 16) */ + cx_write(MO_GP2_IO, 0x0101); + break; + + default: + err = -ENODEV; + } + return err; +} + +/* CX8802 MPEG -> mini driver - We no longer have the hardware */ +static int cx8802_dvb_advise_release(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + int err = 0; + dprintk( 1, "%s\n", __func__); + + switch (core->boardnr) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + /* Do Nothing, leave the cx22702 on the bus. */ + break; + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + break; + default: + err = -ENODEV; + } + return err; +} + +static int cx8802_dvb_probe(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + struct cx8802_dev *dev = drv->core->dvbdev; + int err; + struct videobuf_dvb_frontend *fe; + int i; + + dprintk( 1, "%s\n", __func__); + dprintk( 1, " ->being probed by Card=%d Name=%s, PCI %02x:%02x\n", + core->boardnr, + core->name, + core->pci_bus, + core->pci_slot); + + err = -ENODEV; + if (!(core->board.mpeg & CX88_MPEG_DVB)) + goto fail_core; + + /* If vp3054 isn't enabled, a stub will just return 0 */ + err = vp3054_i2c_probe(dev); + if (0 != err) + goto fail_core; + + /* dvb stuff */ + printk(KERN_INFO "%s/2: cx2388x based DVB/ATSC card\n", core->name); + dev->ts_gen_cntrl = 0x0c; + + err = cx8802_alloc_frontends(dev); + if (err) + goto fail_core; + + err = -ENODEV; + for (i = 1; i <= core->board.num_frontends; i++) { + fe = videobuf_dvb_get_frontend(&core->dvbdev->frontends, i); + if (fe == NULL) { + printk(KERN_ERR "%s() failed to get frontend(%d)\n", + __func__, i); + goto fail_probe; + } + videobuf_queue_sg_init(&fe->dvb.dvbq, &dvb_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_TOP, + sizeof(struct cx88_buffer), + dev, NULL); + /* init struct videobuf_dvb */ + fe->dvb.name = dev->core->name; + } + + err = dvb_register(dev); + if (err) + /* frontends/adapter de-allocated in dvb_register */ + printk(KERN_ERR "%s/2: dvb_register failed (err = %d)\n", + core->name, err); + return err; +fail_probe: + videobuf_dvb_dealloc_frontends(&core->dvbdev->frontends); +fail_core: + return err; +} + +static int cx8802_dvb_remove(struct cx8802_driver *drv) +{ + struct cx88_core *core = drv->core; + struct cx8802_dev *dev = drv->core->dvbdev; + + dprintk( 1, "%s\n", __func__); + + videobuf_dvb_unregister_bus(&dev->frontends); + + vp3054_i2c_remove(dev); + + core->gate_ctrl = NULL; + + return 0; +} + +static struct cx8802_driver cx8802_dvb_driver = { + .type_id = CX88_MPEG_DVB, + .hw_access = CX8802_DRVCTL_SHARED, + .probe = cx8802_dvb_probe, + .remove = cx8802_dvb_remove, + .advise_acquire = cx8802_dvb_advise_acquire, + .advise_release = cx8802_dvb_advise_release, +}; + +static int __init dvb_init(void) +{ + printk(KERN_INFO "cx88/2: cx2388x dvb driver version %s loaded\n", + CX88_VERSION); + return cx8802_register_driver(&cx8802_dvb_driver); +} + +static void __exit dvb_fini(void) +{ + cx8802_unregister_driver(&cx8802_dvb_driver); +} + +module_init(dvb_init); +module_exit(dvb_fini); diff --git a/drivers/media/video/cx88/cx88-i2c.c b/drivers/media/pci/cx88/cx88-i2c.c similarity index 100% rename from drivers/media/video/cx88/cx88-i2c.c rename to drivers/media/pci/cx88/cx88-i2c.c diff --git a/drivers/media/video/cx88/cx88-input.c b/drivers/media/pci/cx88/cx88-input.c similarity index 100% rename from drivers/media/video/cx88/cx88-input.c rename to drivers/media/pci/cx88/cx88-input.c diff --git a/drivers/media/video/cx88/cx88-mpeg.c b/drivers/media/pci/cx88/cx88-mpeg.c similarity index 100% rename from drivers/media/video/cx88/cx88-mpeg.c rename to drivers/media/pci/cx88/cx88-mpeg.c diff --git a/drivers/media/video/cx88/cx88-reg.h b/drivers/media/pci/cx88/cx88-reg.h similarity index 100% rename from drivers/media/video/cx88/cx88-reg.h rename to drivers/media/pci/cx88/cx88-reg.h diff --git a/drivers/media/video/cx88/cx88-tvaudio.c b/drivers/media/pci/cx88/cx88-tvaudio.c similarity index 100% rename from drivers/media/video/cx88/cx88-tvaudio.c rename to drivers/media/pci/cx88/cx88-tvaudio.c diff --git a/drivers/media/video/cx88/cx88-vbi.c b/drivers/media/pci/cx88/cx88-vbi.c similarity index 100% rename from drivers/media/video/cx88/cx88-vbi.c rename to drivers/media/pci/cx88/cx88-vbi.c diff --git a/drivers/media/pci/cx88/cx88-video.c b/drivers/media/pci/cx88/cx88-video.c new file mode 100644 index 000000000000..a146d50d7795 --- /dev/null +++ b/drivers/media/pci/cx88/cx88-video.c @@ -0,0 +1,2075 @@ +/* + * + * device driver for Conexant 2388x based TV cards + * video4linux video interface + * + * (c) 2003-04 Gerd Knorr [SuSE Labs] + * + * (c) 2005-2006 Mauro Carvalho Chehab + * - Multituner support + * - video_ioctl2 conversion + * - PAL/M fixes + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx88.h" +#include +#include +#include +#include + +MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CX88_VERSION); + +/* ------------------------------------------------------------------ */ + +static unsigned int video_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int vbi_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; + +module_param_array(video_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr,"video device numbers"); +MODULE_PARM_DESC(vbi_nr,"vbi device numbers"); +MODULE_PARM_DESC(radio_nr,"radio device numbers"); + +static unsigned int video_debug; +module_param(video_debug,int,0644); +MODULE_PARM_DESC(video_debug,"enable debug messages [video]"); + +static unsigned int irq_debug; +module_param(irq_debug,int,0644); +MODULE_PARM_DESC(irq_debug,"enable debug messages [IRQ handler]"); + +static unsigned int vid_limit = 16; +module_param(vid_limit,int,0644); +MODULE_PARM_DESC(vid_limit,"capture memory limit in megabytes"); + +#define dprintk(level,fmt, arg...) if (video_debug >= level) \ + printk(KERN_DEBUG "%s/0: " fmt, core->name , ## arg) + +/* ------------------------------------------------------------------- */ +/* static data */ + +static const struct cx8800_fmt formats[] = { + { + .name = "8 bpp, gray", + .fourcc = V4L2_PIX_FMT_GREY, + .cxformat = ColorFormatY8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .cxformat = ColorFormatRGB15, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .cxformat = ColorFormatRGB15 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .cxformat = ColorFormatRGB16, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .cxformat = ColorFormatRGB16 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .cxformat = ColorFormatRGB24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .cxformat = ColorFormatRGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .cxformat = ColorFormatRGB32 | ColorFormatBSWAP | ColorFormatWSWAP, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .cxformat = ColorFormatYUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .cxformat = ColorFormatYUY2 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, +}; + +static const struct cx8800_fmt* format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + +/* ------------------------------------------------------------------- */ + +struct cx88_ctrl { + /* control information */ + u32 id; + s32 minimum; + s32 maximum; + u32 step; + s32 default_value; + + /* control register information */ + u32 off; + u32 reg; + u32 sreg; + u32 mask; + u32 shift; +}; + +static const struct cx88_ctrl cx8800_vid_ctls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .minimum = 0x00, + .maximum = 0xff, + .step = 1, + .default_value = 0x7f, + .off = 128, + .reg = MO_CONTR_BRIGHT, + .mask = 0x00ff, + .shift = 0, + },{ + .id = V4L2_CID_CONTRAST, + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0x3f, + .off = 0, + .reg = MO_CONTR_BRIGHT, + .mask = 0xff00, + .shift = 8, + },{ + .id = V4L2_CID_HUE, + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0x7f, + .off = 128, + .reg = MO_HUE, + .mask = 0x00ff, + .shift = 0, + },{ + /* strictly, this only describes only U saturation. + * V saturation is handled specially through code. + */ + .id = V4L2_CID_SATURATION, + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0x7f, + .off = 0, + .reg = MO_UV_SATURATION, + .mask = 0x00ff, + .shift = 0, + }, { + .id = V4L2_CID_SHARPNESS, + .minimum = 0, + .maximum = 4, + .step = 1, + .default_value = 0x0, + .off = 0, + /* NOTE: the value is converted and written to both even + and odd registers in the code */ + .reg = MO_FILTER_ODD, + .mask = 7 << 7, + .shift = 7, + }, { + .id = V4L2_CID_CHROMA_AGC, + .minimum = 0, + .maximum = 1, + .default_value = 0x1, + .reg = MO_INPUT_FORMAT, + .mask = 1 << 10, + .shift = 10, + }, { + .id = V4L2_CID_COLOR_KILLER, + .minimum = 0, + .maximum = 1, + .default_value = 0x1, + .reg = MO_INPUT_FORMAT, + .mask = 1 << 9, + .shift = 9, + }, { + .id = V4L2_CID_BAND_STOP_FILTER, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0x0, + .off = 0, + .reg = MO_HTOTAL, + .mask = 3 << 11, + .shift = 11, + } +}; + +static const struct cx88_ctrl cx8800_aud_ctls[] = { + { + /* --- audio --- */ + .id = V4L2_CID_AUDIO_MUTE, + .minimum = 0, + .maximum = 1, + .default_value = 1, + .reg = AUD_VOL_CTL, + .sreg = SHADOW_AUD_VOL_CTL, + .mask = (1 << 6), + .shift = 6, + },{ + .id = V4L2_CID_AUDIO_VOLUME, + .minimum = 0, + .maximum = 0x3f, + .step = 1, + .default_value = 0x3f, + .reg = AUD_VOL_CTL, + .sreg = SHADOW_AUD_VOL_CTL, + .mask = 0x3f, + .shift = 0, + },{ + .id = V4L2_CID_AUDIO_BALANCE, + .minimum = 0, + .maximum = 0x7f, + .step = 1, + .default_value = 0x40, + .reg = AUD_BAL_CTL, + .sreg = SHADOW_AUD_BAL_CTL, + .mask = 0x7f, + .shift = 0, + } +}; + +enum { + CX8800_VID_CTLS = ARRAY_SIZE(cx8800_vid_ctls), + CX8800_AUD_CTLS = ARRAY_SIZE(cx8800_aud_ctls), +}; + +/* ------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bit) +{ + struct cx88_core *core = dev->core; + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + mutex_lock(&core->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + mutex_unlock(&core->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dprintk(1,"res: get %d\n",bit); + mutex_unlock(&core->lock); + return 1; +} + +static +int res_check(struct cx8800_fh *fh, unsigned int bit) +{ + return (fh->resources & bit); +} + +static +int res_locked(struct cx8800_dev *dev, unsigned int bit) +{ + return (dev->resources & bit); +} + +static +void res_free(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bits) +{ + struct cx88_core *core = dev->core; + BUG_ON((fh->resources & bits) != bits); + + mutex_lock(&core->lock); + fh->resources &= ~bits; + dev->resources &= ~bits; + dprintk(1,"res: put %d\n",bits); + mutex_unlock(&core->lock); +} + +/* ------------------------------------------------------------------ */ + +int cx88_video_mux(struct cx88_core *core, unsigned int input) +{ + /* struct cx88_core *core = dev->core; */ + + dprintk(1,"video_mux: %d [vmux=%d,gpio=0x%x,0x%x,0x%x,0x%x]\n", + input, INPUT(input).vmux, + INPUT(input).gpio0,INPUT(input).gpio1, + INPUT(input).gpio2,INPUT(input).gpio3); + core->input = input; + cx_andor(MO_INPUT_FORMAT, 0x03 << 14, INPUT(input).vmux << 14); + cx_write(MO_GP3_IO, INPUT(input).gpio3); + cx_write(MO_GP0_IO, INPUT(input).gpio0); + cx_write(MO_GP1_IO, INPUT(input).gpio1); + cx_write(MO_GP2_IO, INPUT(input).gpio2); + + switch (INPUT(input).type) { + case CX88_VMUX_SVIDEO: + cx_set(MO_AFECFG_IO, 0x00000001); + cx_set(MO_INPUT_FORMAT, 0x00010010); + cx_set(MO_FILTER_EVEN, 0x00002020); + cx_set(MO_FILTER_ODD, 0x00002020); + break; + default: + cx_clear(MO_AFECFG_IO, 0x00000001); + cx_clear(MO_INPUT_FORMAT, 0x00010010); + cx_clear(MO_FILTER_EVEN, 0x00002020); + cx_clear(MO_FILTER_ODD, 0x00002020); + break; + } + + /* if there are audioroutes defined, we have an external + ADC to deal with audio */ + if (INPUT(input).audioroute) { + /* The wm8775 module has the "2" route hardwired into + the initialization. Some boards may use different + routes for different inputs. HVR-1300 surely does */ + if (core->board.audio_chip && + core->board.audio_chip == V4L2_IDENT_WM8775) { + call_all(core, audio, s_routing, + INPUT(input).audioroute, 0, 0); + } + /* cx2388's C-ADC is connected to the tuner only. + When used with S-Video, that ADC is busy dealing with + chroma, so an external must be used for baseband audio */ + if (INPUT(input).type != CX88_VMUX_TELEVISION && + INPUT(input).type != CX88_VMUX_CABLE) { + /* "I2S ADC mode" */ + core->tvaudio = WW_I2SADC; + cx88_set_tvaudio(core); + } else { + /* Normal mode */ + cx_write(AUD_I2SCNTL, 0x0); + cx_clear(AUD_CTL, EN_I2SIN_ENABLE); + } + } + + return 0; +} +EXPORT_SYMBOL(cx88_video_mux); + +/* ------------------------------------------------------------------ */ + +static int start_video_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + struct cx88_core *core = dev->core; + + /* setup fifo + format */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21], + buf->bpl, buf->risc.dma); + cx88_set_scale(core, buf->vb.width, buf->vb.height, buf->vb.field); + cx_write(MO_COLOR_CTRL, buf->fmt->cxformat | ColorFormatGamma); + + /* reset counter */ + cx_write(MO_VIDY_GPCNTRL,GP_COUNT_CONTROL_RESET); + q->count = 1; + + /* enable irqs */ + cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_VIDINT); + + /* Enables corresponding bits at PCI_INT_STAT: + bits 0 to 4: video, audio, transport stream, VIP, Host + bit 7: timer + bits 8 and 9: DMA complete for: SRC, DST + bits 10 and 11: BERR signal asserted for RISC: RD, WR + bits 12 to 15: BERR signal asserted for: BRDG, SRC, DST, IPB + */ + cx_set(MO_VID_INTMSK, 0x0f0011); + + /* enable capture */ + cx_set(VID_CAPTURE_CONTROL,0x06); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1<<5)); + cx_set(MO_VID_DMACNTRL, 0x11); /* Planar Y and packed FIFO and RISC enable */ + + return 0; +} + +#ifdef CONFIG_PM +static int stop_video_dma(struct cx8800_dev *dev) +{ + struct cx88_core *core = dev->core; + + /* stop dma */ + cx_clear(MO_VID_DMACNTRL, 0x11); + + /* disable capture */ + cx_clear(VID_CAPTURE_CONTROL,0x06); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, PCI_INT_VIDINT); + cx_clear(MO_VID_INTMSK, 0x0f0011); + return 0; +} +#endif + +static int restart_video_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_core *core = dev->core; + struct cx88_buffer *buf, *prev; + + if (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + dprintk(2,"restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + start_video_dma(dev, q, buf); + list_for_each_entry(buf, &q->active, vb.queue) + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; + } + + prev = NULL; + for (;;) { + if (list_empty(&q->queued)) + return 0; + buf = list_entry(q->queued.next, struct cx88_buffer, vb.queue); + if (NULL == prev) { + list_move_tail(&buf->vb.queue, &q->active); + start_video_dma(dev, q, buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] restart_queue - first active\n", + buf,buf->vb.i); + + } else if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_move_tail(&buf->vb.queue, &q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] restart_queue - move to active\n", + buf,buf->vb.i); + } else { + return 0; + } + prev = buf; + } +} + +/* ------------------------------------------------------------------ */ + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct cx8800_fh *fh = q->priv_data; + struct cx8800_dev *dev = fh->dev; + + *size = dev->fmt->depth * dev->width * dev->height >> 3; + if (0 == *count) + *count = 32; + if (*size * *count > vid_limit * 1024 * 1024) + *count = (vid_limit * 1024 * 1024) / *size; + return 0; +} + +static int +buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8800_fh *fh = q->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + int rc, init_buffer = 0; + + BUG_ON(NULL == dev->fmt); + if (dev->width < 48 || dev->width > norm_maxw(core->tvnorm) || + dev->height < 32 || dev->height > norm_maxh(core->tvnorm)) + return -EINVAL; + buf->vb.size = (dev->width * dev->height * dev->fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != dev->fmt || + buf->vb.width != dev->width || + buf->vb.height != dev->height || + buf->vb.field != field) { + buf->fmt = dev->fmt; + buf->vb.width = dev->width; + buf->vb.height = dev->height; + buf->vb.field = field; + init_buffer = 1; + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + init_buffer = 1; + if (0 != (rc = videobuf_iolock(q,&buf->vb,NULL))) + goto fail; + } + + if (init_buffer) { + buf->bpl = buf->vb.width * buf->fmt->depth >> 3; + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, 0, UNSET, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_BOTTOM: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, UNSET, 0, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_INTERLACED: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, 0, buf->bpl, + buf->bpl, buf->bpl, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + 0, buf->bpl * (buf->vb.height >> 1), + buf->bpl, 0, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + cx88_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + buf->bpl * (buf->vb.height >> 1), 0, + buf->bpl, 0, + buf->vb.height >> 1); + break; + default: + BUG(); + } + } + dprintk(2,"[%p/%d] buffer_prepare - %dx%d %dbpp \"%s\" - dma=0x%08lx\n", + buf, buf->vb.i, + dev->width, dev->height, dev->fmt->depth, dev->fmt->name, + (unsigned long)buf->risc.dma); + + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + + fail: + cx88_free_buffer(q,buf); + return rc; +} + +static void +buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct cx88_buffer *prev; + struct cx8800_fh *fh = vq->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *q = &dev->vidq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + if (!list_empty(&q->queued)) { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - append to queued\n", + buf, buf->vb.i); + + } else if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue,&q->active); + start_video_dma(dev, q, buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] buffer_queue - first active\n", + buf, buf->vb.i); + + } else { + prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); + if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + + } else { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - first queued\n", + buf, buf->vb.i); + } + } +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + + cx88_free_buffer(q,buf); +} + +static const struct videobuf_queue_ops cx8800_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + + +/* ------------------------------------------------------------------ */ + +static struct videobuf_queue *get_queue(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = file->private_data; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + return &fh->vidq; + case VFL_TYPE_VBI: + return &fh->vbiq; + default: + BUG(); + return NULL; + } +} + +static int get_resource(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + return RESOURCE_VIDEO; + case VFL_TYPE_VBI: + return RESOURCE_VBI; + default: + BUG(); + return 0; + } +} + +static int video_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_dev *dev = video_drvdata(file); + struct cx88_core *core = dev->core; + struct cx8800_fh *fh; + enum v4l2_buf_type type = 0; + int radio = 0; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + case VFL_TYPE_VBI: + type = V4L2_BUF_TYPE_VBI_CAPTURE; + break; + case VFL_TYPE_RADIO: + radio = 1; + break; + } + + dprintk(1, "open dev=%s radio=%d type=%s\n", + video_device_node_name(vdev), radio, v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh),GFP_KERNEL); + if (unlikely(!fh)) + return -ENOMEM; + + v4l2_fh_init(&fh->fh, vdev); + file->private_data = fh; + fh->dev = dev; + + mutex_lock(&core->lock); + + videobuf_queue_sg_init(&fh->vidq, &cx8800_video_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx88_buffer), + fh, NULL); + videobuf_queue_sg_init(&fh->vbiq, &cx8800_vbi_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct cx88_buffer), + fh, NULL); + + if (vdev->vfl_type == VFL_TYPE_RADIO) { + dprintk(1,"video_open: setting radio device\n"); + cx_write(MO_GP3_IO, core->board.radio.gpio3); + cx_write(MO_GP0_IO, core->board.radio.gpio0); + cx_write(MO_GP1_IO, core->board.radio.gpio1); + cx_write(MO_GP2_IO, core->board.radio.gpio2); + if (core->board.radio.audioroute) { + if(core->board.audio_chip && + core->board.audio_chip == V4L2_IDENT_WM8775) { + call_all(core, audio, s_routing, + core->board.radio.audioroute, 0, 0); + } + /* "I2S ADC mode" */ + core->tvaudio = WW_I2SADC; + cx88_set_tvaudio(core); + } else { + /* FM Mode */ + core->tvaudio = WW_FM; + cx88_set_tvaudio(core); + cx88_set_stereo(core,V4L2_TUNER_MODE_STEREO,1); + } + call_all(core, tuner, s_radio); + } + + core->users++; + mutex_unlock(&core->lock); + v4l2_fh_add(&fh->fh); + + return 0; +} + +static ssize_t +video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = file->private_data; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + if (res_locked(fh->dev,RESOURCE_VIDEO)) + return -EBUSY; + return videobuf_read_one(&fh->vidq, data, count, ppos, + file->f_flags & O_NONBLOCK); + case VFL_TYPE_VBI: + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return -EBUSY; + return videobuf_read_stream(&fh->vbiq, data, count, ppos, 1, + file->f_flags & O_NONBLOCK); + default: + BUG(); + return 0; + } +} + +static unsigned int +video_poll(struct file *file, struct poll_table_struct *wait) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = file->private_data; + struct cx88_buffer *buf; + unsigned int rc = v4l2_ctrl_poll(file, wait); + + if (vdev->vfl_type == VFL_TYPE_VBI) { + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return rc | POLLERR; + return rc | videobuf_poll_stream(file, &fh->vbiq, wait); + } + mutex_lock(&fh->vidq.vb_lock); + if (res_check(fh,RESOURCE_VIDEO)) { + /* streaming capture */ + if (list_empty(&fh->vidq.stream)) + goto done; + buf = list_entry(fh->vidq.stream.next,struct cx88_buffer,vb.stream); + } else { + /* read() capture */ + buf = (struct cx88_buffer*)fh->vidq.read_buf; + if (NULL == buf) + goto done; + } + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == VIDEOBUF_DONE || + buf->vb.state == VIDEOBUF_ERROR) + rc |= POLLIN|POLLRDNORM; +done: + mutex_unlock(&fh->vidq.vb_lock); + return rc; +} + +static int video_release(struct file *file) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + + /* turn off overlay */ + if (res_check(fh, RESOURCE_OVERLAY)) { + /* FIXME */ + res_free(dev,fh,RESOURCE_OVERLAY); + } + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + videobuf_queue_cancel(&fh->vidq); + res_free(dev,fh,RESOURCE_VIDEO); + } + if (fh->vidq.read_buf) { + buffer_release(&fh->vidq,fh->vidq.read_buf); + kfree(fh->vidq.read_buf); + } + + /* stop vbi capture */ + if (res_check(fh, RESOURCE_VBI)) { + videobuf_stop(&fh->vbiq); + res_free(dev,fh,RESOURCE_VBI); + } + + videobuf_mmap_free(&fh->vidq); + videobuf_mmap_free(&fh->vbiq); + + mutex_lock(&dev->core->lock); + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + file->private_data = NULL; + kfree(fh); + + dev->core->users--; + if (!dev->core->users) + call_all(dev->core, core, s_power, 0); + mutex_unlock(&dev->core->lock); + + return 0; +} + +static int +video_mmap(struct file *file, struct vm_area_struct * vma) +{ + return videobuf_mmap_mapper(get_queue(file), vma); +} + +/* ------------------------------------------------------------------ */ +/* VIDEO CTRL IOCTLS */ + +static int cx8800_s_vid_ctrl(struct v4l2_ctrl *ctrl) +{ + struct cx88_core *core = + container_of(ctrl->handler, struct cx88_core, video_hdl); + const struct cx88_ctrl *cc = ctrl->priv; + u32 value, mask; + + mask = cc->mask; + switch (ctrl->id) { + case V4L2_CID_SATURATION: + /* special v_sat handling */ + + value = ((ctrl->val - cc->off) << cc->shift) & cc->mask; + + if (core->tvnorm & V4L2_STD_SECAM) { + /* For SECAM, both U and V sat should be equal */ + value = value << 8 | value; + } else { + /* Keeps U Saturation proportional to V Sat */ + value = (value * 0x5a) / 0x7f << 8 | value; + } + mask = 0xffff; + break; + case V4L2_CID_SHARPNESS: + /* 0b000, 0b100, 0b101, 0b110, or 0b111 */ + value = (ctrl->val < 1 ? 0 : ((ctrl->val + 3) << 7)); + /* needs to be set for both fields */ + cx_andor(MO_FILTER_EVEN, mask, value); + break; + case V4L2_CID_CHROMA_AGC: + value = ((ctrl->val - cc->off) << cc->shift) & cc->mask; + break; + default: + value = ((ctrl->val - cc->off) << cc->shift) & cc->mask; + break; + } + dprintk(1, "set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n", + ctrl->id, ctrl->name, ctrl->val, cc->reg, value, + mask, cc->sreg ? " [shadowed]" : ""); + if (cc->sreg) + cx_sandor(cc->sreg, cc->reg, mask, value); + else + cx_andor(cc->reg, mask, value); + return 0; +} + +static int cx8800_s_aud_ctrl(struct v4l2_ctrl *ctrl) +{ + struct cx88_core *core = + container_of(ctrl->handler, struct cx88_core, audio_hdl); + const struct cx88_ctrl *cc = ctrl->priv; + u32 value,mask; + + /* Pass changes onto any WM8775 */ + if (core->board.audio_chip == V4L2_IDENT_WM8775) { + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + wm8775_s_ctrl(core, ctrl->id, ctrl->val); + break; + case V4L2_CID_AUDIO_VOLUME: + wm8775_s_ctrl(core, ctrl->id, (ctrl->val) ? + (0x90 + ctrl->val) << 8 : 0); + break; + case V4L2_CID_AUDIO_BALANCE: + wm8775_s_ctrl(core, ctrl->id, ctrl->val << 9); + break; + default: + break; + } + } + + mask = cc->mask; + switch (ctrl->id) { + case V4L2_CID_AUDIO_BALANCE: + value = (ctrl->val < 0x40) ? (0x7f - ctrl->val) : (ctrl->val - 0x40); + break; + case V4L2_CID_AUDIO_VOLUME: + value = 0x3f - (ctrl->val & 0x3f); + break; + default: + value = ((ctrl->val - cc->off) << cc->shift) & cc->mask; + break; + } + dprintk(1,"set_control id=0x%X(%s) ctrl=0x%02x, reg=0x%02x val=0x%02x (mask 0x%02x)%s\n", + ctrl->id, ctrl->name, ctrl->val, cc->reg, value, + mask, cc->sreg ? " [shadowed]" : ""); + if (cc->sreg) + cx_sandor(cc->sreg, cc->reg, mask, value); + else + cx_andor(cc->reg, mask, value); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* VIDEO IOCTLS */ + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8800_fh *fh = priv; + struct cx8800_dev *dev = fh->dev; + + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.field = fh->vidq.field; + f->fmt.pix.pixelformat = dev->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * dev->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + const struct cx8800_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = norm_maxw(core->tvnorm); + maxh = norm_maxh(core->tvnorm); + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2, + &f->fmt.pix.height, 32, maxh, 0, 0); + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cx8800_fh *fh = priv; + struct cx8800_dev *dev = fh->dev; + int err = vidioc_try_fmt_vid_cap (file,priv,f); + + if (0 != err) + return err; + dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + dev->width = f->fmt.pix.width; + dev->height = f->fmt.pix.height; + fh->vidq.field = f->fmt.pix.field; + return 0; +} + +void cx88_querycap(struct file *file, struct cx88_core *core, + struct v4l2_capability *cap) +{ + struct video_device *vdev = video_devdata(file); + + strlcpy(cap->card, core->board.name, sizeof(cap->card)); + cap->device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + if (UNSET != core->board.tuner_type) + cap->device_caps |= V4L2_CAP_TUNER; + switch (vdev->vfl_type) { + case VFL_TYPE_RADIO: + cap->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER; + break; + case VFL_TYPE_GRABBER: + cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE; + break; + case VFL_TYPE_VBI: + cap->device_caps |= V4L2_CAP_VBI_CAPTURE; + break; + } + cap->capabilities = cap->device_caps | V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VBI_CAPTURE | V4L2_CAP_DEVICE_CAPS; + if (core->board.radio.type == CX88_RADIO) + cap->capabilities |= V4L2_CAP_RADIO; +} +EXPORT_SYMBOL(cx88_querycap); + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx8800_dev *dev = ((struct cx8800_fh *)priv)->dev; + struct cx88_core *core = dev->core; + + strcpy(cap->driver, "cx8800"); + sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); + cx88_querycap(file, core, cap); + return 0; +} + +static int vidioc_enum_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (unlikely(f->index >= ARRAY_SIZE(formats))) + return -EINVAL; + + strlcpy(f->description,formats[f->index].name,sizeof(f->description)); + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static int vidioc_reqbufs (struct file *file, void *priv, struct v4l2_requestbuffers *p) +{ + return videobuf_reqbufs(get_queue(file), p); +} + +static int vidioc_querybuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + return videobuf_querybuf(get_queue(file), p); +} + +static int vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + return videobuf_qbuf(get_queue(file), p); +} + +static int vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + return videobuf_dqbuf(get_queue(file), p, + file->f_flags & O_NONBLOCK); +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = priv; + struct cx8800_dev *dev = fh->dev; + + if ((vdev->vfl_type == VFL_TYPE_GRABBER && i != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (vdev->vfl_type == VFL_TYPE_VBI && i != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + if (unlikely(!res_get(dev, fh, get_resource(file)))) + return -EBUSY; + return videobuf_streamon(get_queue(file)); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct video_device *vdev = video_devdata(file); + struct cx8800_fh *fh = priv; + struct cx8800_dev *dev = fh->dev; + int err, res; + + if ((vdev->vfl_type == VFL_TYPE_GRABBER && i != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (vdev->vfl_type == VFL_TYPE_VBI && i != V4L2_BUF_TYPE_VBI_CAPTURE)) + return -EINVAL; + + res = get_resource(file); + err = videobuf_streamoff(get_queue(file)); + if (err < 0) + return err; + res_free(dev,fh,res); + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + *tvnorm = core->tvnorm; + return 0; +} + +static int vidioc_s_std (struct file *file, void *priv, v4l2_std_id *tvnorms) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + mutex_lock(&core->lock); + cx88_set_tvnorm(core,*tvnorms); + mutex_unlock(&core->lock); + + return 0; +} + +/* only one input in this sample driver */ +int cx88_enum_input (struct cx88_core *core,struct v4l2_input *i) +{ + static const char * const iname[] = { + [ CX88_VMUX_COMPOSITE1 ] = "Composite1", + [ CX88_VMUX_COMPOSITE2 ] = "Composite2", + [ CX88_VMUX_COMPOSITE3 ] = "Composite3", + [ CX88_VMUX_COMPOSITE4 ] = "Composite4", + [ CX88_VMUX_SVIDEO ] = "S-Video", + [ CX88_VMUX_TELEVISION ] = "Television", + [ CX88_VMUX_CABLE ] = "Cable TV", + [ CX88_VMUX_DVB ] = "DVB", + [ CX88_VMUX_DEBUG ] = "for debug only", + }; + unsigned int n = i->index; + + if (n >= 4) + return -EINVAL; + if (0 == INPUT(n).type) + return -EINVAL; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name,iname[INPUT(n).type]); + if ((CX88_VMUX_TELEVISION == INPUT(n).type) || + (CX88_VMUX_CABLE == INPUT(n).type)) { + i->type = V4L2_INPUT_TYPE_TUNER; + } + i->std = CX88_NORMS; + return 0; +} +EXPORT_SYMBOL(cx88_enum_input); + +static int vidioc_enum_input (struct file *file, void *priv, + struct v4l2_input *i) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + return cx88_enum_input (core,i); +} + +static int vidioc_g_input (struct file *file, void *priv, unsigned int *i) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + *i = core->input; + return 0; +} + +static int vidioc_s_input (struct file *file, void *priv, unsigned int i) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + if (i >= 4) + return -EINVAL; + if (0 == INPUT(i).type) + return -EINVAL; + + mutex_lock(&core->lock); + cx88_newstation(core); + cx88_video_mux(core,i); + mutex_unlock(&core->lock); + return 0; +} + +static int vidioc_g_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + u32 reg; + + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + strcpy(t->name, "Television"); + t->capability = V4L2_TUNER_CAP_NORM; + t->rangehigh = 0xffffffffUL; + call_all(core, tuner, g_tuner, t); + + cx88_get_stereo(core ,t); + reg = cx_read(MO_DEVICE_STATUS); + t->signal = (reg & (1<<5)) ? 0xffff : 0x0000; + return 0; +} + +static int vidioc_s_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + if (UNSET == core->board.tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + cx88_set_stereo(core, t->audmode, 1); + return 0; +} + +static int vidioc_g_frequency (struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx8800_fh *fh = priv; + struct cx88_core *core = fh->dev->core; + + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (f->tuner) + return -EINVAL; + + f->frequency = core->freq; + + call_all(core, tuner, g_frequency, f); + + return 0; +} + +int cx88_set_freq (struct cx88_core *core, + struct v4l2_frequency *f) +{ + if (unlikely(UNSET == core->board.tuner_type)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + + mutex_lock(&core->lock); + cx88_newstation(core); + call_all(core, tuner, s_frequency, f); + call_all(core, tuner, g_frequency, f); + core->freq = f->frequency; + + /* When changing channels it is required to reset TVAUDIO */ + msleep (10); + cx88_set_tvaudio(core); + + mutex_unlock(&core->lock); + + return 0; +} +EXPORT_SYMBOL(cx88_set_freq); + +static int vidioc_s_frequency (struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct cx8800_fh *fh = priv; + struct cx88_core *core = fh->dev->core; + + return cx88_set_freq(core, f); +} + +static int vidioc_g_chip_ident(struct file *file, void *priv, + struct v4l2_dbg_chip_ident *chip) +{ + if (!v4l2_chip_match_host(&chip->match)) + return -EINVAL; + chip->revision = 0; + chip->ident = V4L2_IDENT_UNKNOWN; + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register (struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx88_core *core = ((struct cx8800_fh*)fh)->dev->core; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + /* cx2388x has a 24-bit register space */ + reg->val = cx_read(reg->reg & 0xffffff); + reg->size = 4; + return 0; +} + +static int vidioc_s_register (struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct cx88_core *core = ((struct cx8800_fh*)fh)->dev->core; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + cx_write(reg->reg & 0xffffff, reg->val); + return 0; +} +#endif + +/* ----------------------------------------------------------- */ +/* RADIO ESPECIFIC IOCTLS */ +/* ----------------------------------------------------------- */ + +static int radio_g_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + if (unlikely(t->index > 0)) + return -EINVAL; + + strcpy(t->name, "Radio"); + + call_all(core, tuner, g_tuner, t); + return 0; +} + +/* FIXME: Should add a standard for radio */ + +static int radio_s_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx88_core *core = ((struct cx8800_fh *)priv)->dev->core; + + if (0 != t->index) + return -EINVAL; + if (t->audmode > V4L2_TUNER_MODE_STEREO) + t->audmode = V4L2_TUNER_MODE_STEREO; + + call_all(core, tuner, s_tuner, t); + + return 0; +} + +/* ----------------------------------------------------------- */ + +static void cx8800_vid_timeout(unsigned long data) +{ + struct cx8800_dev *dev = (struct cx8800_dev*)data; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *q = &dev->vidq; + struct cx88_buffer *buf; + unsigned long flags; + + cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH21]); + + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + + spin_lock_irqsave(&dev->slock,flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + printk("%s/0: [%p/%d] timeout - dma=0x%08lx\n", core->name, + buf, buf->vb.i, (unsigned long)buf->risc.dma); + } + restart_video_queue(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +static const char *cx88_vid_irqs[32] = { + "y_risci1", "u_risci1", "v_risci1", "vbi_risc1", + "y_risci2", "u_risci2", "v_risci2", "vbi_risc2", + "y_oflow", "u_oflow", "v_oflow", "vbi_oflow", + "y_sync", "u_sync", "v_sync", "vbi_sync", + "opc_err", "par_err", "rip_err", "pci_abort", +}; + +static void cx8800_vid_irq(struct cx8800_dev *dev) +{ + struct cx88_core *core = dev->core; + u32 status, mask, count; + + status = cx_read(MO_VID_INTSTAT); + mask = cx_read(MO_VID_INTMSK); + if (0 == (status & mask)) + return; + cx_write(MO_VID_INTSTAT, status); + if (irq_debug || (status & mask & ~0xff)) + cx88_print_irqbits(core->name, "irq vid", + cx88_vid_irqs, ARRAY_SIZE(cx88_vid_irqs), + status, mask); + + /* risc op code error */ + if (status & (1 << 16)) { + printk(KERN_WARNING "%s/0: video risc op code error\n",core->name); + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + cx88_sram_channel_dump(core, &cx88_sram_channels[SRAM_CH21]); + } + + /* risc1 y */ + if (status & 0x01) { + spin_lock(&dev->slock); + count = cx_read(MO_VIDY_GPCNT); + cx88_wakeup(core, &dev->vidq, count); + spin_unlock(&dev->slock); + } + + /* risc1 vbi */ + if (status & 0x08) { + spin_lock(&dev->slock); + count = cx_read(MO_VBI_GPCNT); + cx88_wakeup(core, &dev->vbiq, count); + spin_unlock(&dev->slock); + } + + /* risc2 y */ + if (status & 0x10) { + dprintk(2,"stopper video\n"); + spin_lock(&dev->slock); + restart_video_queue(dev,&dev->vidq); + spin_unlock(&dev->slock); + } + + /* risc2 vbi */ + if (status & 0x80) { + dprintk(2,"stopper vbi\n"); + spin_lock(&dev->slock); + cx8800_restart_vbi_queue(dev,&dev->vbiq); + spin_unlock(&dev->slock); + } +} + +static irqreturn_t cx8800_irq(int irq, void *dev_id) +{ + struct cx8800_dev *dev = dev_id; + struct cx88_core *core = dev->core; + u32 status; + int loop, handled = 0; + + for (loop = 0; loop < 10; loop++) { + status = cx_read(MO_PCI_INTSTAT) & + (core->pci_irqmask | PCI_INT_VIDINT); + if (0 == status) + goto out; + cx_write(MO_PCI_INTSTAT, status); + handled = 1; + + if (status & core->pci_irqmask) + cx88_core_irq(core,status); + if (status & PCI_INT_VIDINT) + cx8800_vid_irq(dev); + }; + if (10 == loop) { + printk(KERN_WARNING "%s/0: irq loop -- clearing mask\n", + core->name); + cx_write(MO_PCI_INTMSK,0); + } + + out: + return IRQ_RETVAL(handled); +} + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +static const struct v4l2_file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_g_chip_ident = vidioc_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +static const struct video_device cx8800_video_template = { + .name = "cx8800-video", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = CX88_NORMS, +}; + +static const struct v4l2_ioctl_ops vbi_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_fmt_vbi_cap = cx8800_vbi_fmt, + .vidioc_try_fmt_vbi_cap = cx8800_vbi_fmt, + .vidioc_s_fmt_vbi_cap = cx8800_vbi_fmt, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_g_chip_ident = vidioc_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +static const struct video_device cx8800_vbi_template = { + .name = "cx8800-vbi", + .fops = &video_fops, + .ioctl_ops = &vbi_ioctl_ops, + .tvnorms = CX88_NORMS, +}; + +static const struct v4l2_file_operations radio_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .poll = v4l2_ctrl_poll, + .release = video_release, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops radio_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_tuner = radio_g_tuner, + .vidioc_s_tuner = radio_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_g_chip_ident = vidioc_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +static const struct video_device cx8800_radio_template = { + .name = "cx8800-radio", + .fops = &radio_fops, + .ioctl_ops = &radio_ioctl_ops, +}; + +static const struct v4l2_ctrl_ops cx8800_ctrl_vid_ops = { + .s_ctrl = cx8800_s_vid_ctrl, +}; + +static const struct v4l2_ctrl_ops cx8800_ctrl_aud_ops = { + .s_ctrl = cx8800_s_aud_ctrl, +}; + +/* ----------------------------------------------------------- */ + +static void cx8800_unregister_video(struct cx8800_dev *dev) +{ + if (dev->radio_dev) { + if (video_is_registered(dev->radio_dev)) + video_unregister_device(dev->radio_dev); + else + video_device_release(dev->radio_dev); + dev->radio_dev = NULL; + } + if (dev->vbi_dev) { + if (video_is_registered(dev->vbi_dev)) + video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + } + if (dev->video_dev) { + if (video_is_registered(dev->video_dev)) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } +} + +static int __devinit cx8800_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx8800_dev *dev; + struct cx88_core *core; + int err; + int i; + + dev = kzalloc(sizeof(*dev),GFP_KERNEL); + if (NULL == dev) + return -ENOMEM; + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail_free; + } + core = cx88_core_get(dev->pci); + if (NULL == core) { + err = -EINVAL; + goto fail_free; + } + dev->core = core; + + /* print pci info */ + dev->pci_rev = pci_dev->revision; + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s/0: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%llx\n", core->name, + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat,(unsigned long long)pci_resource_start(pci_dev,0)); + + pci_set_master(pci_dev); + if (!pci_dma_supported(pci_dev,DMA_BIT_MASK(32))) { + printk("%s/0: Oops: no 32bit PCI DMA ???\n",core->name); + err = -EIO; + goto fail_core; + } + + /* initialize driver struct */ + spin_lock_init(&dev->slock); + core->tvnorm = V4L2_STD_NTSC_M; + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + INIT_LIST_HEAD(&dev->vidq.queued); + dev->vidq.timeout.function = cx8800_vid_timeout; + dev->vidq.timeout.data = (unsigned long)dev; + init_timer(&dev->vidq.timeout); + cx88_risc_stopper(dev->pci,&dev->vidq.stopper, + MO_VID_DMACNTRL,0x11,0x00); + + /* init vbi dma queues */ + INIT_LIST_HEAD(&dev->vbiq.active); + INIT_LIST_HEAD(&dev->vbiq.queued); + dev->vbiq.timeout.function = cx8800_vbi_timeout; + dev->vbiq.timeout.data = (unsigned long)dev; + init_timer(&dev->vbiq.timeout); + cx88_risc_stopper(dev->pci,&dev->vbiq.stopper, + MO_VID_DMACNTRL,0x88,0x00); + + /* get irq */ + err = request_irq(pci_dev->irq, cx8800_irq, + IRQF_SHARED | IRQF_DISABLED, core->name, dev); + if (err < 0) { + printk(KERN_ERR "%s/0: can't get IRQ %d\n", + core->name,pci_dev->irq); + goto fail_core; + } + cx_set(MO_PCI_INTMSK, core->pci_irqmask); + + for (i = 0; i < CX8800_AUD_CTLS; i++) { + const struct cx88_ctrl *cc = &cx8800_aud_ctls[i]; + struct v4l2_ctrl *vc; + + vc = v4l2_ctrl_new_std(&core->audio_hdl, &cx8800_ctrl_aud_ops, + cc->id, cc->minimum, cc->maximum, cc->step, cc->default_value); + if (vc == NULL) { + err = core->audio_hdl.error; + goto fail_core; + } + vc->priv = (void *)cc; + } + + for (i = 0; i < CX8800_VID_CTLS; i++) { + const struct cx88_ctrl *cc = &cx8800_vid_ctls[i]; + struct v4l2_ctrl *vc; + + vc = v4l2_ctrl_new_std(&core->video_hdl, &cx8800_ctrl_vid_ops, + cc->id, cc->minimum, cc->maximum, cc->step, cc->default_value); + if (vc == NULL) { + err = core->video_hdl.error; + goto fail_core; + } + vc->priv = (void *)cc; + if (vc->id == V4L2_CID_CHROMA_AGC) + core->chroma_agc = vc; + } + v4l2_ctrl_add_handler(&core->video_hdl, &core->audio_hdl, NULL); + + /* load and configure helper modules */ + + if (core->board.audio_chip == V4L2_IDENT_WM8775) { + struct i2c_board_info wm8775_info = { + .type = "wm8775", + .addr = 0x36 >> 1, + .platform_data = &core->wm8775_data, + }; + struct v4l2_subdev *sd; + + if (core->boardnr == CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1) + core->wm8775_data.is_nova_s = true; + else + core->wm8775_data.is_nova_s = false; + + sd = v4l2_i2c_new_subdev_board(&core->v4l2_dev, &core->i2c_adap, + &wm8775_info, NULL); + if (sd != NULL) { + core->sd_wm8775 = sd; + sd->grp_id = WM8775_GID; + } + } + + if (core->board.audio_chip == V4L2_IDENT_TVAUDIO) { + /* This probes for a tda9874 as is used on some + Pixelview Ultra boards. */ + v4l2_i2c_new_subdev(&core->v4l2_dev, &core->i2c_adap, + "tvaudio", 0, I2C_ADDRS(0xb0 >> 1)); + } + + switch (core->boardnr) { + case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD: + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: { + static const struct i2c_board_info rtc_info = { + I2C_BOARD_INFO("isl1208", 0x6f) + }; + + request_module("rtc-isl1208"); + core->i2c_rtc = i2c_new_device(&core->i2c_adap, &rtc_info); + } + /* break intentionally omitted */ + case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: + request_module("ir-kbd-i2c"); + } + + /* Sets device info at pci_dev */ + pci_set_drvdata(pci_dev, dev); + + dev->width = 320; + dev->height = 240; + dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + + /* initial device configuration */ + mutex_lock(&core->lock); + cx88_set_tvnorm(core, core->tvnorm); + v4l2_ctrl_handler_setup(&core->video_hdl); + v4l2_ctrl_handler_setup(&core->audio_hdl); + cx88_video_mux(core, 0); + + /* register v4l devices */ + dev->video_dev = cx88_vdev_init(core,dev->pci, + &cx8800_video_template,"video"); + video_set_drvdata(dev->video_dev, dev); + dev->video_dev->ctrl_handler = &core->video_hdl; + err = video_register_device(dev->video_dev,VFL_TYPE_GRABBER, + video_nr[core->nr]); + if (err < 0) { + printk(KERN_ERR "%s/0: can't register video device\n", + core->name); + goto fail_unreg; + } + printk(KERN_INFO "%s/0: registered device %s [v4l2]\n", + core->name, video_device_node_name(dev->video_dev)); + + dev->vbi_dev = cx88_vdev_init(core,dev->pci,&cx8800_vbi_template,"vbi"); + video_set_drvdata(dev->vbi_dev, dev); + err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI, + vbi_nr[core->nr]); + if (err < 0) { + printk(KERN_ERR "%s/0: can't register vbi device\n", + core->name); + goto fail_unreg; + } + printk(KERN_INFO "%s/0: registered device %s\n", + core->name, video_device_node_name(dev->vbi_dev)); + + if (core->board.radio.type == CX88_RADIO) { + dev->radio_dev = cx88_vdev_init(core,dev->pci, + &cx8800_radio_template,"radio"); + video_set_drvdata(dev->radio_dev, dev); + dev->radio_dev->ctrl_handler = &core->audio_hdl; + err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO, + radio_nr[core->nr]); + if (err < 0) { + printk(KERN_ERR "%s/0: can't register radio device\n", + core->name); + goto fail_unreg; + } + printk(KERN_INFO "%s/0: registered device %s\n", + core->name, video_device_node_name(dev->radio_dev)); + } + + /* start tvaudio thread */ + if (core->board.tuner_type != TUNER_ABSENT) { + core->kthread = kthread_run(cx88_audio_thread, core, "cx88 tvaudio"); + if (IS_ERR(core->kthread)) { + err = PTR_ERR(core->kthread); + printk(KERN_ERR "%s/0: failed to create cx88 audio thread, err=%d\n", + core->name, err); + } + } + mutex_unlock(&core->lock); + + return 0; + +fail_unreg: + cx8800_unregister_video(dev); + free_irq(pci_dev->irq, dev); + mutex_unlock(&core->lock); +fail_core: + cx88_core_put(core,dev->pci); +fail_free: + kfree(dev); + return err; +} + +static void __devexit cx8800_finidev(struct pci_dev *pci_dev) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + + /* stop thread */ + if (core->kthread) { + kthread_stop(core->kthread); + core->kthread = NULL; + } + + if (core->ir) + cx88_ir_stop(core); + + cx88_shutdown(core); /* FIXME */ + pci_disable_device(pci_dev); + + /* unregister stuff */ + + free_irq(pci_dev->irq, dev); + cx8800_unregister_video(dev); + pci_set_drvdata(pci_dev, NULL); + + /* free memory */ + btcx_riscmem_free(dev->pci,&dev->vidq.stopper); + cx88_core_put(core,dev->pci); + kfree(dev); +} + +#ifdef CONFIG_PM +static int cx8800_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + + /* stop video+vbi capture */ + spin_lock(&dev->slock); + if (!list_empty(&dev->vidq.active)) { + printk("%s/0: suspend video\n", core->name); + stop_video_dma(dev); + del_timer(&dev->vidq.timeout); + } + if (!list_empty(&dev->vbiq.active)) { + printk("%s/0: suspend vbi\n", core->name); + cx8800_stop_vbi_dma(dev); + del_timer(&dev->vbiq.timeout); + } + spin_unlock(&dev->slock); + + if (core->ir) + cx88_ir_stop(core); + /* FIXME -- shutdown device */ + cx88_shutdown(core); + + pci_save_state(pci_dev); + if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) { + pci_disable_device(pci_dev); + dev->state.disabled = 1; + } + return 0; +} + +static int cx8800_resume(struct pci_dev *pci_dev) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + int err; + + if (dev->state.disabled) { + err=pci_enable_device(pci_dev); + if (err) { + printk(KERN_ERR "%s/0: can't enable device\n", + core->name); + return err; + } + + dev->state.disabled = 0; + } + err= pci_set_power_state(pci_dev, PCI_D0); + if (err) { + printk(KERN_ERR "%s/0: can't set power state\n", core->name); + pci_disable_device(pci_dev); + dev->state.disabled = 1; + + return err; + } + pci_restore_state(pci_dev); + + /* FIXME: re-initialize hardware */ + cx88_reset(core); + if (core->ir) + cx88_ir_start(core); + + cx_set(MO_PCI_INTMSK, core->pci_irqmask); + + /* restart video+vbi capture */ + spin_lock(&dev->slock); + if (!list_empty(&dev->vidq.active)) { + printk("%s/0: resume video\n", core->name); + restart_video_queue(dev,&dev->vidq); + } + if (!list_empty(&dev->vbiq.active)) { + printk("%s/0: resume vbi\n", core->name); + cx8800_restart_vbi_queue(dev,&dev->vbiq); + } + spin_unlock(&dev->slock); + + return 0; +} +#endif + +/* ----------------------------------------------------------- */ + +static const struct pci_device_id cx8800_pci_tbl[] = { + { + .vendor = 0x14f1, + .device = 0x8800, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + },{ + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, cx8800_pci_tbl); + +static struct pci_driver cx8800_pci_driver = { + .name = "cx8800", + .id_table = cx8800_pci_tbl, + .probe = cx8800_initdev, + .remove = __devexit_p(cx8800_finidev), +#ifdef CONFIG_PM + .suspend = cx8800_suspend, + .resume = cx8800_resume, +#endif +}; + +static int __init cx8800_init(void) +{ + printk(KERN_INFO "cx88/0: cx2388x v4l2 driver version %s loaded\n", + CX88_VERSION); + return pci_register_driver(&cx8800_pci_driver); +} + +static void __exit cx8800_fini(void) +{ + pci_unregister_driver(&cx8800_pci_driver); +} + +module_init(cx8800_init); +module_exit(cx8800_fini); diff --git a/drivers/media/video/cx88/cx88-vp3054-i2c.c b/drivers/media/pci/cx88/cx88-vp3054-i2c.c similarity index 100% rename from drivers/media/video/cx88/cx88-vp3054-i2c.c rename to drivers/media/pci/cx88/cx88-vp3054-i2c.c diff --git a/drivers/media/video/cx88/cx88-vp3054-i2c.h b/drivers/media/pci/cx88/cx88-vp3054-i2c.h similarity index 100% rename from drivers/media/video/cx88/cx88-vp3054-i2c.h rename to drivers/media/pci/cx88/cx88-vp3054-i2c.h diff --git a/drivers/media/pci/cx88/cx88.h b/drivers/media/pci/cx88/cx88.h new file mode 100644 index 000000000000..44ffc8b3d45f --- /dev/null +++ b/drivers/media/pci/cx88/cx88.h @@ -0,0 +1,748 @@ +/* + * + * v4l2 device driver for cx2388x based TV cards + * + * (c) 2003,04 Gerd Knorr [SUSE Labs] + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "btcx-risc.h" +#include "cx88-reg.h" +#include "tuner-xc2028.h" + +#include + +#define CX88_VERSION "0.0.9" + +#define UNSET (-1U) + +#define CX88_MAXBOARDS 8 + +/* Max number of inputs by card */ +#define MAX_CX88_INPUT 8 + +/* ----------------------------------------------------------- */ +/* defines and enums */ + +/* Currently unsupported by the driver: PAL/H, NTSC/Kr, SECAM/LC */ +#define CX88_NORMS (V4L2_STD_ALL \ + & ~V4L2_STD_PAL_H \ + & ~V4L2_STD_NTSC_M_KR \ + & ~V4L2_STD_SECAM_LC) + +#define FORMAT_FLAGS_PACKED 0x01 +#define FORMAT_FLAGS_PLANAR 0x02 + +#define VBI_LINE_COUNT 17 +#define VBI_LINE_LENGTH 2048 + +#define AUD_RDS_LINES 4 + +/* need "shadow" registers for some write-only ones ... */ +#define SHADOW_AUD_VOL_CTL 1 +#define SHADOW_AUD_BAL_CTL 2 +#define SHADOW_MAX 3 + +/* FM Radio deemphasis type */ +enum cx88_deemph_type { + FM_NO_DEEMPH = 0, + FM_DEEMPH_50, + FM_DEEMPH_75 +}; + +enum cx88_board_type { + CX88_BOARD_NONE = 0, + CX88_MPEG_DVB, + CX88_MPEG_BLACKBIRD +}; + +enum cx8802_board_access { + CX8802_DRVCTL_SHARED = 1, + CX8802_DRVCTL_EXCLUSIVE = 2, +}; + +/* ----------------------------------------------------------- */ +/* tv norms */ + +static unsigned int inline norm_maxw(v4l2_std_id norm) +{ + return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 720 : 768; +} + + +static unsigned int inline norm_maxh(v4l2_std_id norm) +{ + return (norm & V4L2_STD_625_50) ? 576 : 480; +} + +/* ----------------------------------------------------------- */ +/* static data */ + +struct cx8800_fmt { + const char *name; + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; + u32 cxformat; +}; + +/* ----------------------------------------------------------- */ +/* SRAM memory management data (see cx88-core.c) */ + +#define SRAM_CH21 0 /* video */ +#define SRAM_CH22 1 +#define SRAM_CH23 2 +#define SRAM_CH24 3 /* vbi */ +#define SRAM_CH25 4 /* audio */ +#define SRAM_CH26 5 +#define SRAM_CH28 6 /* mpeg */ +#define SRAM_CH27 7 /* audio rds */ +/* more */ + +struct sram_channel { + const char *name; + u32 cmds_start; + u32 ctrl_start; + u32 cdt; + u32 fifo_start; + u32 fifo_size; + u32 ptr1_reg; + u32 ptr2_reg; + u32 cnt1_reg; + u32 cnt2_reg; +}; +extern const struct sram_channel cx88_sram_channels[]; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define CX88_BOARD_NOAUTO UNSET +#define CX88_BOARD_UNKNOWN 0 +#define CX88_BOARD_HAUPPAUGE 1 +#define CX88_BOARD_GDI 2 +#define CX88_BOARD_PIXELVIEW 3 +#define CX88_BOARD_ATI_WONDER_PRO 4 +#define CX88_BOARD_WINFAST2000XP_EXPERT 5 +#define CX88_BOARD_AVERTV_STUDIO_303 6 +#define CX88_BOARD_MSI_TVANYWHERE_MASTER 7 +#define CX88_BOARD_WINFAST_DV2000 8 +#define CX88_BOARD_LEADTEK_PVR2000 9 +#define CX88_BOARD_IODATA_GVVCP3PCI 10 +#define CX88_BOARD_PROLINK_PLAYTVPVR 11 +#define CX88_BOARD_ASUS_PVR_416 12 +#define CX88_BOARD_MSI_TVANYWHERE 13 +#define CX88_BOARD_KWORLD_DVB_T 14 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1 15 +#define CX88_BOARD_KWORLD_LTV883 16 +#define CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q 17 +#define CX88_BOARD_HAUPPAUGE_DVB_T1 18 +#define CX88_BOARD_CONEXANT_DVB_T1 19 +#define CX88_BOARD_PROVIDEO_PV259 20 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS 21 +#define CX88_BOARD_PCHDTV_HD3000 22 +#define CX88_BOARD_DNTV_LIVE_DVB_T 23 +#define CX88_BOARD_HAUPPAUGE_ROSLYN 24 +#define CX88_BOARD_DIGITALLOGIC_MEC 25 +#define CX88_BOARD_IODATA_GVBCTV7E 26 +#define CX88_BOARD_PIXELVIEW_PLAYTV_ULTRA_PRO 27 +#define CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T 28 +#define CX88_BOARD_ADSTECH_DVB_T_PCI 29 +#define CX88_BOARD_TERRATEC_CINERGY_1400_DVB_T1 30 +#define CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD 31 +#define CX88_BOARD_AVERMEDIA_ULTRATV_MC_550 32 +#define CX88_BOARD_KWORLD_VSTREAM_EXPERT_DVD 33 +#define CX88_BOARD_ATI_HDTVWONDER 34 +#define CX88_BOARD_WINFAST_DTV1000 35 +#define CX88_BOARD_AVERTV_303 36 +#define CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1 37 +#define CX88_BOARD_HAUPPAUGE_NOVASE2_S1 38 +#define CX88_BOARD_KWORLD_DVBS_100 39 +#define CX88_BOARD_HAUPPAUGE_HVR1100 40 +#define CX88_BOARD_HAUPPAUGE_HVR1100LP 41 +#define CX88_BOARD_DNTV_LIVE_DVB_T_PRO 42 +#define CX88_BOARD_KWORLD_DVB_T_CX22702 43 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL 44 +#define CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT 45 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_HYBRID 46 +#define CX88_BOARD_PCHDTV_HD5500 47 +#define CX88_BOARD_KWORLD_MCE200_DELUXE 48 +#define CX88_BOARD_PIXELVIEW_PLAYTV_P7000 49 +#define CX88_BOARD_NPGTECH_REALTV_TOP10FM 50 +#define CX88_BOARD_WINFAST_DTV2000H 51 +#define CX88_BOARD_GENIATECH_DVBS 52 +#define CX88_BOARD_HAUPPAUGE_HVR3000 53 +#define CX88_BOARD_NORWOOD_MICRO 54 +#define CX88_BOARD_TE_DTV_250_OEM_SWANN 55 +#define CX88_BOARD_HAUPPAUGE_HVR1300 56 +#define CX88_BOARD_ADSTECH_PTV_390 57 +#define CX88_BOARD_PINNACLE_PCTV_HD_800i 58 +#define CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO 59 +#define CX88_BOARD_PINNACLE_HYBRID_PCTV 60 +#define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL 61 +#define CX88_BOARD_POWERCOLOR_REAL_ANGEL 62 +#define CX88_BOARD_GENIATECH_X8000_MT 63 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PRO 64 +#define CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD 65 +#define CX88_BOARD_PROLINK_PV_8000GT 66 +#define CX88_BOARD_KWORLD_ATSC_120 67 +#define CX88_BOARD_HAUPPAUGE_HVR4000 68 +#define CX88_BOARD_HAUPPAUGE_HVR4000LITE 69 +#define CX88_BOARD_TEVII_S460 70 +#define CX88_BOARD_OMICOM_SS4_PCI 71 +#define CX88_BOARD_TBS_8920 72 +#define CX88_BOARD_TEVII_S420 73 +#define CX88_BOARD_PROLINK_PV_GLOBAL_XTREME 74 +#define CX88_BOARD_PROF_7300 75 +#define CX88_BOARD_SATTRADE_ST4200 76 +#define CX88_BOARD_TBS_8910 77 +#define CX88_BOARD_PROF_6200 78 +#define CX88_BOARD_TERRATEC_CINERGY_HT_PCI_MKII 79 +#define CX88_BOARD_HAUPPAUGE_IRONLY 80 +#define CX88_BOARD_WINFAST_DTV1800H 81 +#define CX88_BOARD_WINFAST_DTV2000H_J 82 +#define CX88_BOARD_PROF_7301 83 +#define CX88_BOARD_SAMSUNG_SMT_7020 84 +#define CX88_BOARD_TWINHAN_VP1027_DVBS 85 +#define CX88_BOARD_TEVII_S464 86 +#define CX88_BOARD_WINFAST_DTV2000H_PLUS 87 +#define CX88_BOARD_WINFAST_DTV1800H_XC4000 88 +#define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F36 89 +#define CX88_BOARD_WINFAST_TV2000_XP_GLOBAL_6F43 90 + +enum cx88_itype { + CX88_VMUX_COMPOSITE1 = 1, + CX88_VMUX_COMPOSITE2, + CX88_VMUX_COMPOSITE3, + CX88_VMUX_COMPOSITE4, + CX88_VMUX_SVIDEO, + CX88_VMUX_TELEVISION, + CX88_VMUX_CABLE, + CX88_VMUX_DVB, + CX88_VMUX_DEBUG, + CX88_RADIO, +}; + +struct cx88_input { + enum cx88_itype type; + u32 gpio0, gpio1, gpio2, gpio3; + unsigned int vmux:2; + unsigned int audioroute:4; +}; + +struct cx88_board { + const char *name; + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + int tda9887_conf; + struct cx88_input input[MAX_CX88_INPUT]; + struct cx88_input radio; + enum cx88_board_type mpeg; + unsigned int audio_chip; + int num_frontends; + + /* Used for I2S devices */ + int i2sinputcntl; +}; + +struct cx88_subid { + u16 subvendor; + u16 subdevice; + u32 card; +}; + +enum cx88_tvaudio { + WW_NONE = 1, + WW_BTSC, + WW_BG, + WW_DK, + WW_I, + WW_L, + WW_EIAJ, + WW_I2SPT, + WW_FM, + WW_I2SADC, + WW_M +}; + +#define INPUT(nr) (core->board.input[nr]) + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +#define BUFFER_TIMEOUT msecs_to_jiffies(2000) + +/* buffer for one video frame */ +struct cx88_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* cx88 specific */ + unsigned int bpl; + struct btcx_riscmem risc; + const struct cx8800_fmt *fmt; + u32 count; +}; + +struct cx88_dmaqueue { + struct list_head active; + struct list_head queued; + struct timer_list timeout; + struct btcx_riscmem stopper; + u32 count; +}; + +struct cx88_core { + struct list_head devlist; + atomic_t refcount; + + /* board name */ + int nr; + char name[32]; + + /* pci stuff */ + int pci_bus; + int pci_slot; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + u32 shadow[SHADOW_MAX]; + int pci_irqmask; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + u32 i2c_state, i2c_rc; + + /* config info -- analog */ + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler video_hdl; + struct v4l2_ctrl *chroma_agc; + struct v4l2_ctrl_handler audio_hdl; + struct v4l2_subdev *sd_wm8775; + struct i2c_client *i2c_rtc; + unsigned int boardnr; + struct cx88_board board; + + /* Supported V4L _STD_ tuner formats */ + unsigned int tuner_formats; + + /* config info -- dvb */ +#if defined(CONFIG_VIDEO_CX88_DVB) || defined(CONFIG_VIDEO_CX88_DVB_MODULE) + int (*prev_set_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage); +#endif + void (*gate_ctrl)(struct cx88_core *core, int open); + + /* state info */ + struct task_struct *kthread; + v4l2_std_id tvnorm; + enum cx88_tvaudio tvaudio; + u32 audiomode_manual; + u32 audiomode_current; + u32 input; + u32 last_analog_input; + u32 astat; + u32 use_nicam; + unsigned long last_change; + + /* IR remote control state */ + struct cx88_IR *ir; + + /* I2C remote data */ + struct IR_i2c_init_data init_data; + struct wm8775_platform_data wm8775_data; + + struct mutex lock; + /* various v4l controls */ + u32 freq; + int users; + int mpeg_users; + + /* cx88-video needs to access cx8802 for hybrid tuner pll access. */ + struct cx8802_dev *dvbdev; + enum cx88_board_type active_type_id; + int active_ref; + int active_fe_id; +}; + +static inline struct cx88_core *to_core(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct cx88_core, v4l2_dev); +} + +#define call_hw(core, grpid, o, f, args...) \ + do { \ + if (!core->i2c_rc) { \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 1); \ + v4l2_device_call_all(&core->v4l2_dev, grpid, o, f, ##args); \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 0); \ + } \ + } while (0) + +#define call_all(core, o, f, args...) call_hw(core, 0, o, f, ##args) + +#define WM8775_GID (1 << 0) + +#define wm8775_s_ctrl(core, id, val) \ + do { \ + struct v4l2_ctrl *ctrl_ = \ + v4l2_ctrl_find(core->sd_wm8775->ctrl_handler, id); \ + if (ctrl_ && !core->i2c_rc) { \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 1); \ + v4l2_ctrl_s_ctrl(ctrl_, val); \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 0); \ + } \ + } while (0) + +#define wm8775_g_ctrl(core, id) \ + ({ \ + struct v4l2_ctrl *ctrl_ = \ + v4l2_ctrl_find(core->sd_wm8775->ctrl_handler, id); \ + s32 val = 0; \ + if (ctrl_ && !core->i2c_rc) { \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 1); \ + val = v4l2_ctrl_g_ctrl(ctrl_); \ + if (core->gate_ctrl) \ + core->gate_ctrl(core, 0); \ + } \ + val; \ + }) + +struct cx8800_dev; +struct cx8802_dev; + +/* ----------------------------------------------------------- */ +/* function 0: video stuff */ + +struct cx8800_fh { + struct v4l2_fh fh; + struct cx8800_dev *dev; + unsigned int resources; + + /* video capture */ + struct videobuf_queue vidq; + + /* vbi capture */ + struct videobuf_queue vbiq; +}; + +struct cx8800_suspend_state { + int disabled; +}; + +struct cx8800_dev { + struct cx88_core *core; + spinlock_t slock; + + /* various device info */ + unsigned int resources; + struct video_device *video_dev; + struct video_device *vbi_dev; + struct video_device *radio_dev; + + /* pci i/o */ + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; + + const struct cx8800_fmt *fmt; + unsigned int width, height; + + /* capture queues */ + struct cx88_dmaqueue vidq; + struct cx88_dmaqueue vbiq; + + /* various v4l controls */ + + /* other global state info */ + struct cx8800_suspend_state state; +}; + +/* ----------------------------------------------------------- */ +/* function 1: audio/alsa stuff */ +/* =============> moved to cx88-alsa.c <====================== */ + + +/* ----------------------------------------------------------- */ +/* function 2: mpeg stuff */ + +struct cx8802_fh { + struct v4l2_fh fh; + struct cx8802_dev *dev; + struct videobuf_queue mpegq; +}; + +struct cx8802_suspend_state { + int disabled; +}; + +struct cx8802_driver { + struct cx88_core *core; + + /* List of drivers attached to device */ + struct list_head drvlist; + + /* Type of driver and access required */ + enum cx88_board_type type_id; + enum cx8802_board_access hw_access; + + /* MPEG 8802 internal only */ + int (*suspend)(struct pci_dev *pci_dev, pm_message_t state); + int (*resume)(struct pci_dev *pci_dev); + + /* Callers to the following functions must hold core->lock */ + + /* MPEG 8802 -> mini driver - Driver probe and configuration */ + int (*probe)(struct cx8802_driver *drv); + int (*remove)(struct cx8802_driver *drv); + + /* MPEG 8802 -> mini driver - Access for hardware control */ + int (*advise_acquire)(struct cx8802_driver *drv); + int (*advise_release)(struct cx8802_driver *drv); + + /* MPEG 8802 <- mini driver - Access for hardware control */ + int (*request_acquire)(struct cx8802_driver *drv); + int (*request_release)(struct cx8802_driver *drv); +}; + +struct cx8802_dev { + struct cx88_core *core; + spinlock_t slock; + + /* pci i/o */ + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; + + /* dma queues */ + struct cx88_dmaqueue mpegq; + u32 ts_packet_size; + u32 ts_packet_count; + + /* other global state info */ + struct cx8802_suspend_state state; + + /* for blackbird only */ + struct list_head devlist; +#if defined(CONFIG_VIDEO_CX88_BLACKBIRD) || \ + defined(CONFIG_VIDEO_CX88_BLACKBIRD_MODULE) + struct video_device *mpeg_dev; + u32 mailbox; + int width; + int height; + unsigned char mpeg_active; /* nonzero if mpeg encoder is active */ + + /* mpeg params */ + struct cx2341x_handler cxhdl; +#endif + +#if defined(CONFIG_VIDEO_CX88_DVB) || defined(CONFIG_VIDEO_CX88_DVB_MODULE) + /* for dvb only */ + struct videobuf_dvb_frontends frontends; +#endif + +#if defined(CONFIG_VIDEO_CX88_VP3054) || \ + defined(CONFIG_VIDEO_CX88_VP3054_MODULE) + /* For VP3045 secondary I2C bus support */ + struct vp3054_i2c_state *vp3054; +#endif + /* for switching modulation types */ + unsigned char ts_gen_cntrl; + + /* List of attached drivers; must hold core->lock to access */ + struct list_head drvlist; + + struct work_struct request_module_wk; +}; + +/* ----------------------------------------------------------- */ + +#define cx_read(reg) readl(core->lmmio + ((reg)>>2)) +#define cx_write(reg,value) writel((value), core->lmmio + ((reg)>>2)) +#define cx_writeb(reg,value) writeb((value), core->bmmio + (reg)) + +#define cx_andor(reg,mask,value) \ + writel((readl(core->lmmio+((reg)>>2)) & ~(mask)) |\ + ((value) & (mask)), core->lmmio+((reg)>>2)) +#define cx_set(reg,bit) cx_andor((reg),(bit),(bit)) +#define cx_clear(reg,bit) cx_andor((reg),(bit),0) + +#define cx_wait(d) { if (need_resched()) schedule(); else udelay(d); } + +/* shadow registers */ +#define cx_sread(sreg) (core->shadow[sreg]) +#define cx_swrite(sreg,reg,value) \ + (core->shadow[sreg] = value, \ + writel(core->shadow[sreg], core->lmmio + ((reg)>>2))) +#define cx_sandor(sreg,reg,mask,value) \ + (core->shadow[sreg] = (core->shadow[sreg] & ~(mask)) | ((value) & (mask)), \ + writel(core->shadow[sreg], core->lmmio + ((reg)>>2))) + +/* ----------------------------------------------------------- */ +/* cx88-core.c */ + +extern void cx88_print_irqbits(const char *name, const char *tag, const char *strings[], + int len, u32 bits, u32 mask); + +extern int cx88_core_irq(struct cx88_core *core, u32 status); +extern void cx88_wakeup(struct cx88_core *core, + struct cx88_dmaqueue *q, u32 count); +extern void cx88_shutdown(struct cx88_core *core); +extern int cx88_reset(struct cx88_core *core); + +extern int +cx88_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines); +extern int +cx88_risc_databuffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, unsigned int bpl, + unsigned int lines, unsigned int lpi); +extern int +cx88_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc, + u32 reg, u32 mask, u32 value); +extern void +cx88_free_buffer(struct videobuf_queue *q, struct cx88_buffer *buf); + +extern void cx88_risc_disasm(struct cx88_core *core, + struct btcx_riscmem *risc); +extern int cx88_sram_channel_setup(struct cx88_core *core, + const struct sram_channel *ch, + unsigned int bpl, u32 risc); +extern void cx88_sram_channel_dump(struct cx88_core *core, + const struct sram_channel *ch); + +extern int cx88_set_scale(struct cx88_core *core, unsigned int width, + unsigned int height, enum v4l2_field field); +extern int cx88_set_tvnorm(struct cx88_core *core, v4l2_std_id norm); + +extern struct video_device *cx88_vdev_init(struct cx88_core *core, + struct pci_dev *pci, + const struct video_device *template_, + const char *type); +extern struct cx88_core* cx88_core_get(struct pci_dev *pci); +extern void cx88_core_put(struct cx88_core *core, + struct pci_dev *pci); + +extern int cx88_start_audio_dma(struct cx88_core *core); +extern int cx88_stop_audio_dma(struct cx88_core *core); + + +/* ----------------------------------------------------------- */ +/* cx88-vbi.c */ + +/* Can be used as g_vbi_fmt, try_vbi_fmt and s_vbi_fmt */ +int cx8800_vbi_fmt (struct file *file, void *priv, + struct v4l2_format *f); + +/* +int cx8800_start_vbi_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf); +*/ +int cx8800_stop_vbi_dma(struct cx8800_dev *dev); +int cx8800_restart_vbi_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q); +void cx8800_vbi_timeout(unsigned long data); + +extern const struct videobuf_queue_ops cx8800_vbi_qops; + +/* ----------------------------------------------------------- */ +/* cx88-i2c.c */ + +extern int cx88_i2c_init(struct cx88_core *core, struct pci_dev *pci); + + +/* ----------------------------------------------------------- */ +/* cx88-cards.c */ + +extern int cx88_tuner_callback(void *dev, int component, int command, int arg); +extern int cx88_get_resources(const struct cx88_core *core, + struct pci_dev *pci); +extern struct cx88_core *cx88_core_create(struct pci_dev *pci, int nr); +extern void cx88_setup_xc3028(struct cx88_core *core, struct xc2028_ctrl *ctl); + +/* ----------------------------------------------------------- */ +/* cx88-tvaudio.c */ + +void cx88_set_tvaudio(struct cx88_core *core); +void cx88_newstation(struct cx88_core *core); +void cx88_get_stereo(struct cx88_core *core, struct v4l2_tuner *t); +void cx88_set_stereo(struct cx88_core *core, u32 mode, int manual); +int cx88_audio_thread(void *data); + +int cx8802_register_driver(struct cx8802_driver *drv); +int cx8802_unregister_driver(struct cx8802_driver *drv); + +/* Caller must hold core->lock */ +struct cx8802_driver * cx8802_get_driver(struct cx8802_dev *dev, enum cx88_board_type btype); + +/* ----------------------------------------------------------- */ +/* cx88-dsp.c */ + +s32 cx88_dsp_detect_stereo_sap(struct cx88_core *core); + +/* ----------------------------------------------------------- */ +/* cx88-input.c */ + +int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci); +int cx88_ir_fini(struct cx88_core *core); +void cx88_ir_irq(struct cx88_core *core); +int cx88_ir_start(struct cx88_core *core); +void cx88_ir_stop(struct cx88_core *core); +extern void cx88_i2c_init_ir(struct cx88_core *core); + +/* ----------------------------------------------------------- */ +/* cx88-mpeg.c */ + +int cx8802_buf_prepare(struct videobuf_queue *q,struct cx8802_dev *dev, + struct cx88_buffer *buf, enum v4l2_field field); +void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf); +void cx8802_cancel_buffers(struct cx8802_dev *dev); + +/* ----------------------------------------------------------- */ +/* cx88-video.c*/ +int cx88_enum_input (struct cx88_core *core,struct v4l2_input *i); +int cx88_set_freq (struct cx88_core *core,struct v4l2_frequency *f); +int cx88_video_mux(struct cx88_core *core, unsigned int input); +void cx88_querycap(struct file *file, struct cx88_core *core, + struct v4l2_capability *cap); diff --git a/drivers/media/pci/ddbridge/Kconfig b/drivers/media/pci/ddbridge/Kconfig new file mode 100644 index 000000000000..44e5dc15e60a --- /dev/null +++ b/drivers/media/pci/ddbridge/Kconfig @@ -0,0 +1,18 @@ +config DVB_DDBRIDGE + tristate "Digital Devices bridge support" + depends on DVB_CORE && PCI && I2C + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA18271C2DD if MEDIA_SUBDRV_AUTOSELECT + ---help--- + Support for cards with the Digital Devices PCI express bridge: + - Octopus PCIe Bridge + - Octopus mini PCIe Bridge + - Octopus LE + - DuoFlex S2 Octopus + - DuoFlex CT Octopus + - cineS2(v6) + + Say Y if you own such a card and want to use it. diff --git a/drivers/media/pci/ddbridge/Makefile b/drivers/media/pci/ddbridge/Makefile new file mode 100644 index 000000000000..7446c8b677b5 --- /dev/null +++ b/drivers/media/pci/ddbridge/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for the ddbridge device driver +# + +ddbridge-objs := ddbridge-core.o + +obj-$(CONFIG_DVB_DDBRIDGE) += ddbridge.o + +ccflags-y += -Idrivers/media/dvb-core/ +ccflags-y += -Idrivers/media/dvb-frontends/ +ccflags-y += -Idrivers/media/tuners/ + +# For the staging CI driver cxd2099 +ccflags-y += -Idrivers/staging/media/cxd2099/ diff --git a/drivers/media/pci/ddbridge/ddbridge-core.c b/drivers/media/pci/ddbridge/ddbridge-core.c new file mode 100644 index 000000000000..feff57ee5a08 --- /dev/null +++ b/drivers/media/pci/ddbridge/ddbridge-core.c @@ -0,0 +1,1730 @@ +/* + * ddbridge.c: Digital Devices PCIe bridge driver + * + * Copyright (C) 2010-2011 Digital Devices GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * 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 + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ddbridge.h" + +#include "ddbridge-regs.h" + +#include "tda18271c2dd.h" +#include "stv6110x.h" +#include "stv090x.h" +#include "lnbh24.h" +#include "drxk.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +/* MSI had problems with lost interrupts, fixed but needs testing */ +#undef CONFIG_PCI_MSI + +/******************************************************************************/ + +static int i2c_read(struct i2c_adapter *adapter, u8 adr, u8 *val) +{ + struct i2c_msg msgs[1] = {{.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1 } }; + return (i2c_transfer(adapter, msgs, 1) == 1) ? 0 : -1; +} + +static int i2c_read_reg(struct i2c_adapter *adapter, u8 adr, u8 reg, u8 *val) +{ + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = ®, .len = 1 }, + {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1 } }; + return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; +} + +static int i2c_read_reg16(struct i2c_adapter *adapter, u8 adr, + u16 reg, u8 *val) +{ + u8 msg[2] = {reg>>8, reg&0xff}; + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = msg, .len = 2}, + {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1} }; + return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; +} + +static int ddb_i2c_cmd(struct ddb_i2c *i2c, u32 adr, u32 cmd) +{ + struct ddb *dev = i2c->dev; + int stat; + u32 val; + + i2c->done = 0; + ddbwritel((adr << 9) | cmd, i2c->regs + I2C_COMMAND); + stat = wait_event_timeout(i2c->wq, i2c->done == 1, HZ); + if (stat <= 0) { + printk(KERN_ERR "I2C timeout\n"); + { /* MSI debugging*/ + u32 istat = ddbreadl(INTERRUPT_STATUS); + printk(KERN_ERR "IRS %08x\n", istat); + ddbwritel(istat, INTERRUPT_ACK); + } + return -EIO; + } + val = ddbreadl(i2c->regs+I2C_COMMAND); + if (val & 0x70000) + return -EIO; + return 0; +} + +static int ddb_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg msg[], int num) +{ + struct ddb_i2c *i2c = (struct ddb_i2c *)i2c_get_adapdata(adapter); + struct ddb *dev = i2c->dev; + u8 addr = 0; + + if (num) + addr = msg[0].addr; + + if (num == 2 && msg[1].flags & I2C_M_RD && + !(msg[0].flags & I2C_M_RD)) { + memcpy_toio(dev->regs + I2C_TASKMEM_BASE + i2c->wbuf, + msg[0].buf, msg[0].len); + ddbwritel(msg[0].len|(msg[1].len << 16), + i2c->regs+I2C_TASKLENGTH); + if (!ddb_i2c_cmd(i2c, addr, 1)) { + memcpy_fromio(msg[1].buf, + dev->regs + I2C_TASKMEM_BASE + i2c->rbuf, + msg[1].len); + return num; + } + } + + if (num == 1 && !(msg[0].flags & I2C_M_RD)) { + ddbcpyto(I2C_TASKMEM_BASE + i2c->wbuf, msg[0].buf, msg[0].len); + ddbwritel(msg[0].len, i2c->regs + I2C_TASKLENGTH); + if (!ddb_i2c_cmd(i2c, addr, 2)) + return num; + } + if (num == 1 && (msg[0].flags & I2C_M_RD)) { + ddbwritel(msg[0].len << 16, i2c->regs + I2C_TASKLENGTH); + if (!ddb_i2c_cmd(i2c, addr, 3)) { + ddbcpyfrom(msg[0].buf, + I2C_TASKMEM_BASE + i2c->rbuf, msg[0].len); + return num; + } + } + return -EIO; +} + + +static u32 ddb_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +struct i2c_algorithm ddb_i2c_algo = { + .master_xfer = ddb_i2c_master_xfer, + .functionality = ddb_i2c_functionality, +}; + +static void ddb_i2c_release(struct ddb *dev) +{ + int i; + struct ddb_i2c *i2c; + struct i2c_adapter *adap; + + for (i = 0; i < dev->info->port_num; i++) { + i2c = &dev->i2c[i]; + adap = &i2c->adap; + i2c_del_adapter(adap); + } +} + +static int ddb_i2c_init(struct ddb *dev) +{ + int i, j, stat = 0; + struct ddb_i2c *i2c; + struct i2c_adapter *adap; + + for (i = 0; i < dev->info->port_num; i++) { + i2c = &dev->i2c[i]; + i2c->dev = dev; + i2c->nr = i; + i2c->wbuf = i * (I2C_TASKMEM_SIZE / 4); + i2c->rbuf = i2c->wbuf + (I2C_TASKMEM_SIZE / 8); + i2c->regs = 0x80 + i * 0x20; + ddbwritel(I2C_SPEED_100, i2c->regs + I2C_TIMING); + ddbwritel((i2c->rbuf << 16) | i2c->wbuf, + i2c->regs + I2C_TASKADDRESS); + init_waitqueue_head(&i2c->wq); + + adap = &i2c->adap; + i2c_set_adapdata(adap, i2c); +#ifdef I2C_ADAP_CLASS_TV_DIGITAL + adap->class = I2C_ADAP_CLASS_TV_DIGITAL|I2C_CLASS_TV_ANALOG; +#else +#ifdef I2C_CLASS_TV_ANALOG + adap->class = I2C_CLASS_TV_ANALOG; +#endif +#endif + strcpy(adap->name, "ddbridge"); + adap->algo = &ddb_i2c_algo; + adap->algo_data = (void *)i2c; + adap->dev.parent = &dev->pdev->dev; + stat = i2c_add_adapter(adap); + if (stat) + break; + } + if (stat) + for (j = 0; j < i; j++) { + i2c = &dev->i2c[j]; + adap = &i2c->adap; + i2c_del_adapter(adap); + } + return stat; +} + + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +#if 0 +static void set_table(struct ddb *dev, u32 off, + dma_addr_t *pbuf, u32 num) +{ + u32 i, base; + u64 mem; + + base = DMA_BASE_ADDRESS_TABLE + off; + for (i = 0; i < num; i++) { + mem = pbuf[i]; + ddbwritel(mem & 0xffffffff, base + i * 8); + ddbwritel(mem >> 32, base + i * 8 + 4); + } +} +#endif + +static void ddb_address_table(struct ddb *dev) +{ + u32 i, j, base; + u64 mem; + dma_addr_t *pbuf; + + for (i = 0; i < dev->info->port_num * 2; i++) { + base = DMA_BASE_ADDRESS_TABLE + i * 0x100; + pbuf = dev->input[i].pbuf; + for (j = 0; j < dev->input[i].dma_buf_num; j++) { + mem = pbuf[j]; + ddbwritel(mem & 0xffffffff, base + j * 8); + ddbwritel(mem >> 32, base + j * 8 + 4); + } + } + for (i = 0; i < dev->info->port_num; i++) { + base = DMA_BASE_ADDRESS_TABLE + 0x800 + i * 0x100; + pbuf = dev->output[i].pbuf; + for (j = 0; j < dev->output[i].dma_buf_num; j++) { + mem = pbuf[j]; + ddbwritel(mem & 0xffffffff, base + j * 8); + ddbwritel(mem >> 32, base + j * 8 + 4); + } + } +} + +static void io_free(struct pci_dev *pdev, u8 **vbuf, + dma_addr_t *pbuf, u32 size, int num) +{ + int i; + + for (i = 0; i < num; i++) { + if (vbuf[i]) { + pci_free_consistent(pdev, size, vbuf[i], pbuf[i]); + vbuf[i] = 0; + } + } +} + +static int io_alloc(struct pci_dev *pdev, u8 **vbuf, + dma_addr_t *pbuf, u32 size, int num) +{ + int i; + + for (i = 0; i < num; i++) { + vbuf[i] = pci_alloc_consistent(pdev, size, &pbuf[i]); + if (!vbuf[i]) + return -ENOMEM; + } + return 0; +} + +static int ddb_buffers_alloc(struct ddb *dev) +{ + int i; + struct ddb_port *port; + + for (i = 0; i < dev->info->port_num; i++) { + port = &dev->port[i]; + switch (port->class) { + case DDB_PORT_TUNER: + if (io_alloc(dev->pdev, port->input[0]->vbuf, + port->input[0]->pbuf, + port->input[0]->dma_buf_size, + port->input[0]->dma_buf_num) < 0) + return -1; + if (io_alloc(dev->pdev, port->input[1]->vbuf, + port->input[1]->pbuf, + port->input[1]->dma_buf_size, + port->input[1]->dma_buf_num) < 0) + return -1; + break; + case DDB_PORT_CI: + if (io_alloc(dev->pdev, port->input[0]->vbuf, + port->input[0]->pbuf, + port->input[0]->dma_buf_size, + port->input[0]->dma_buf_num) < 0) + return -1; + if (io_alloc(dev->pdev, port->output->vbuf, + port->output->pbuf, + port->output->dma_buf_size, + port->output->dma_buf_num) < 0) + return -1; + break; + default: + break; + } + } + ddb_address_table(dev); + return 0; +} + +static void ddb_buffers_free(struct ddb *dev) +{ + int i; + struct ddb_port *port; + + for (i = 0; i < dev->info->port_num; i++) { + port = &dev->port[i]; + io_free(dev->pdev, port->input[0]->vbuf, + port->input[0]->pbuf, + port->input[0]->dma_buf_size, + port->input[0]->dma_buf_num); + io_free(dev->pdev, port->input[1]->vbuf, + port->input[1]->pbuf, + port->input[1]->dma_buf_size, + port->input[1]->dma_buf_num); + io_free(dev->pdev, port->output->vbuf, + port->output->pbuf, + port->output->dma_buf_size, + port->output->dma_buf_num); + } +} + +static void ddb_input_start(struct ddb_input *input) +{ + struct ddb *dev = input->port->dev; + + spin_lock_irq(&input->lock); + input->cbuf = 0; + input->coff = 0; + + /* reset */ + ddbwritel(0, TS_INPUT_CONTROL(input->nr)); + ddbwritel(2, TS_INPUT_CONTROL(input->nr)); + ddbwritel(0, TS_INPUT_CONTROL(input->nr)); + + ddbwritel((1 << 16) | + (input->dma_buf_num << 11) | + (input->dma_buf_size >> 7), + DMA_BUFFER_SIZE(input->nr)); + ddbwritel(0, DMA_BUFFER_ACK(input->nr)); + + ddbwritel(1, DMA_BASE_WRITE); + ddbwritel(3, DMA_BUFFER_CONTROL(input->nr)); + ddbwritel(9, TS_INPUT_CONTROL(input->nr)); + input->running = 1; + spin_unlock_irq(&input->lock); +} + +static void ddb_input_stop(struct ddb_input *input) +{ + struct ddb *dev = input->port->dev; + + spin_lock_irq(&input->lock); + ddbwritel(0, TS_INPUT_CONTROL(input->nr)); + ddbwritel(0, DMA_BUFFER_CONTROL(input->nr)); + input->running = 0; + spin_unlock_irq(&input->lock); +} + +static void ddb_output_start(struct ddb_output *output) +{ + struct ddb *dev = output->port->dev; + + spin_lock_irq(&output->lock); + output->cbuf = 0; + output->coff = 0; + ddbwritel(0, TS_OUTPUT_CONTROL(output->nr)); + ddbwritel(2, TS_OUTPUT_CONTROL(output->nr)); + ddbwritel(0, TS_OUTPUT_CONTROL(output->nr)); + ddbwritel(0x3c, TS_OUTPUT_CONTROL(output->nr)); + ddbwritel((1 << 16) | + (output->dma_buf_num << 11) | + (output->dma_buf_size >> 7), + DMA_BUFFER_SIZE(output->nr + 8)); + ddbwritel(0, DMA_BUFFER_ACK(output->nr + 8)); + + ddbwritel(1, DMA_BASE_READ); + ddbwritel(3, DMA_BUFFER_CONTROL(output->nr + 8)); + /* ddbwritel(0xbd, TS_OUTPUT_CONTROL(output->nr)); */ + ddbwritel(0x1d, TS_OUTPUT_CONTROL(output->nr)); + output->running = 1; + spin_unlock_irq(&output->lock); +} + +static void ddb_output_stop(struct ddb_output *output) +{ + struct ddb *dev = output->port->dev; + + spin_lock_irq(&output->lock); + ddbwritel(0, TS_OUTPUT_CONTROL(output->nr)); + ddbwritel(0, DMA_BUFFER_CONTROL(output->nr + 8)); + output->running = 0; + spin_unlock_irq(&output->lock); +} + +static u32 ddb_output_free(struct ddb_output *output) +{ + u32 idx, off, stat = output->stat; + s32 diff; + + idx = (stat >> 11) & 0x1f; + off = (stat & 0x7ff) << 7; + + if (output->cbuf != idx) { + if ((((output->cbuf + 1) % output->dma_buf_num) == idx) && + (output->dma_buf_size - output->coff <= 188)) + return 0; + return 188; + } + diff = off - output->coff; + if (diff <= 0 || diff > 188) + return 188; + return 0; +} + +static ssize_t ddb_output_write(struct ddb_output *output, + const u8 *buf, size_t count) +{ + struct ddb *dev = output->port->dev; + u32 idx, off, stat = output->stat; + u32 left = count, len; + + idx = (stat >> 11) & 0x1f; + off = (stat & 0x7ff) << 7; + + while (left) { + len = output->dma_buf_size - output->coff; + if ((((output->cbuf + 1) % output->dma_buf_num) == idx) && + (off == 0)) { + if (len <= 188) + break; + len -= 188; + } + if (output->cbuf == idx) { + if (off > output->coff) { +#if 1 + len = off - output->coff; + len -= (len % 188); + if (len <= 188) + +#endif + break; + len -= 188; + } + } + if (len > left) + len = left; + if (copy_from_user(output->vbuf[output->cbuf] + output->coff, + buf, len)) + return -EIO; + left -= len; + buf += len; + output->coff += len; + if (output->coff == output->dma_buf_size) { + output->coff = 0; + output->cbuf = ((output->cbuf + 1) % output->dma_buf_num); + } + ddbwritel((output->cbuf << 11) | (output->coff >> 7), + DMA_BUFFER_ACK(output->nr + 8)); + } + return count - left; +} + +static u32 ddb_input_avail(struct ddb_input *input) +{ + struct ddb *dev = input->port->dev; + u32 idx, off, stat = input->stat; + u32 ctrl = ddbreadl(DMA_BUFFER_CONTROL(input->nr)); + + idx = (stat >> 11) & 0x1f; + off = (stat & 0x7ff) << 7; + + if (ctrl & 4) { + printk(KERN_ERR "IA %d %d %08x\n", idx, off, ctrl); + ddbwritel(input->stat, DMA_BUFFER_ACK(input->nr)); + return 0; + } + if (input->cbuf != idx) + return 188; + return 0; +} + +static ssize_t ddb_input_read(struct ddb_input *input, u8 *buf, size_t count) +{ + struct ddb *dev = input->port->dev; + u32 left = count; + u32 idx, free, stat = input->stat; + int ret; + + idx = (stat >> 11) & 0x1f; + + while (left) { + if (input->cbuf == idx) + return count - left; + free = input->dma_buf_size - input->coff; + if (free > left) + free = left; + ret = copy_to_user(buf, input->vbuf[input->cbuf] + + input->coff, free); + if (ret) + return -EFAULT; + input->coff += free; + if (input->coff == input->dma_buf_size) { + input->coff = 0; + input->cbuf = (input->cbuf+1) % input->dma_buf_num; + } + left -= free; + ddbwritel((input->cbuf << 11) | (input->coff >> 7), + DMA_BUFFER_ACK(input->nr)); + } + return count; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +#if 0 +static struct ddb_input *fe2input(struct ddb *dev, struct dvb_frontend *fe) +{ + int i; + + for (i = 0; i < dev->info->port_num * 2; i++) { + if (dev->input[i].fe == fe) + return &dev->input[i]; + } + return NULL; +} +#endif + +static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct ddb_input *input = fe->sec_priv; + struct ddb_port *port = input->port; + int status; + + if (enable) { + mutex_lock(&port->i2c_gate_lock); + status = input->gate_ctrl(fe, 1); + } else { + status = input->gate_ctrl(fe, 0); + mutex_unlock(&port->i2c_gate_lock); + } + return status; +} + +static int demod_attach_drxk(struct ddb_input *input) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct dvb_frontend *fe; + struct drxk_config config; + + memset(&config, 0, sizeof(config)); + config.microcode_name = "drxk_a3.mc"; + config.qam_demod_parameter_count = 4; + config.adr = 0x29 + (input->nr & 1); + + fe = input->fe = dvb_attach(drxk_attach, &config, i2c); + if (!input->fe) { + printk(KERN_ERR "No DRXK found!\n"); + return -ENODEV; + } + fe->sec_priv = input; + input->gate_ctrl = fe->ops.i2c_gate_ctrl; + fe->ops.i2c_gate_ctrl = drxk_gate_ctrl; + return 0; +} + +static int tuner_attach_tda18271(struct ddb_input *input) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct dvb_frontend *fe; + + if (input->fe->ops.i2c_gate_ctrl) + input->fe->ops.i2c_gate_ctrl(input->fe, 1); + fe = dvb_attach(tda18271c2dd_attach, input->fe, i2c, 0x60); + if (!fe) { + printk(KERN_ERR "No TDA18271 found!\n"); + return -ENODEV; + } + if (input->fe->ops.i2c_gate_ctrl) + input->fe->ops.i2c_gate_ctrl(input->fe, 0); + return 0; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +static struct stv090x_config stv0900 = { + .device = STV0900, + .demod_mode = STV090x_DUAL, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 27000000, + .address = 0x69, + + .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + + .repeater_level = STV090x_RPTLEVEL_16, + + .adc1_range = STV090x_ADC_1Vpp, + .adc2_range = STV090x_ADC_1Vpp, + + .diseqc_envelope_mode = true, +}; + +static struct stv090x_config stv0900_aa = { + .device = STV0900, + .demod_mode = STV090x_DUAL, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 27000000, + .address = 0x68, + + .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + + .repeater_level = STV090x_RPTLEVEL_16, + + .adc1_range = STV090x_ADC_1Vpp, + .adc2_range = STV090x_ADC_1Vpp, + + .diseqc_envelope_mode = true, +}; + +static struct stv6110x_config stv6110a = { + .addr = 0x60, + .refclk = 27000000, + .clk_div = 1, +}; + +static struct stv6110x_config stv6110b = { + .addr = 0x63, + .refclk = 27000000, + .clk_div = 1, +}; + +static int demod_attach_stv0900(struct ddb_input *input, int type) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct stv090x_config *feconf = type ? &stv0900_aa : &stv0900; + + input->fe = dvb_attach(stv090x_attach, feconf, i2c, + (input->nr & 1) ? STV090x_DEMODULATOR_1 + : STV090x_DEMODULATOR_0); + if (!input->fe) { + printk(KERN_ERR "No STV0900 found!\n"); + return -ENODEV; + } + if (!dvb_attach(lnbh24_attach, input->fe, i2c, 0, + 0, (input->nr & 1) ? + (0x09 - type) : (0x0b - type))) { + printk(KERN_ERR "No LNBH24 found!\n"); + return -ENODEV; + } + return 0; +} + +static int tuner_attach_stv6110(struct ddb_input *input, int type) +{ + struct i2c_adapter *i2c = &input->port->i2c->adap; + struct stv090x_config *feconf = type ? &stv0900_aa : &stv0900; + struct stv6110x_config *tunerconf = (input->nr & 1) ? + &stv6110b : &stv6110a; + struct stv6110x_devctl *ctl; + + ctl = dvb_attach(stv6110x_attach, input->fe, tunerconf, i2c); + if (!ctl) { + printk(KERN_ERR "No STV6110X found!\n"); + return -ENODEV; + } + printk(KERN_INFO "attach tuner input %d adr %02x\n", + input->nr, tunerconf->addr); + + feconf->tuner_init = ctl->tuner_init; + feconf->tuner_sleep = ctl->tuner_sleep; + feconf->tuner_set_mode = ctl->tuner_set_mode; + feconf->tuner_set_frequency = ctl->tuner_set_frequency; + feconf->tuner_get_frequency = ctl->tuner_get_frequency; + feconf->tuner_set_bandwidth = ctl->tuner_set_bandwidth; + feconf->tuner_get_bandwidth = ctl->tuner_get_bandwidth; + feconf->tuner_set_bbgain = ctl->tuner_set_bbgain; + feconf->tuner_get_bbgain = ctl->tuner_get_bbgain; + feconf->tuner_set_refclk = ctl->tuner_set_refclk; + feconf->tuner_get_status = ctl->tuner_get_status; + + return 0; +} + +static int my_dvb_dmx_ts_card_init(struct dvb_demux *dvbdemux, char *id, + int (*start_feed)(struct dvb_demux_feed *), + int (*stop_feed)(struct dvb_demux_feed *), + void *priv) +{ + dvbdemux->priv = priv; + + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = start_feed; + dvbdemux->stop_feed = stop_feed; + dvbdemux->write_to_decoder = NULL; + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING); + return dvb_dmx_init(dvbdemux); +} + +static int my_dvb_dmxdev_ts_card_init(struct dmxdev *dmxdev, + struct dvb_demux *dvbdemux, + struct dmx_frontend *hw_frontend, + struct dmx_frontend *mem_frontend, + struct dvb_adapter *dvb_adapter) +{ + int ret; + + dmxdev->filternum = 256; + dmxdev->demux = &dvbdemux->dmx; + dmxdev->capabilities = 0; + ret = dvb_dmxdev_init(dmxdev, dvb_adapter); + if (ret < 0) + return ret; + + hw_frontend->source = DMX_FRONTEND_0; + dvbdemux->dmx.add_frontend(&dvbdemux->dmx, hw_frontend); + mem_frontend->source = DMX_MEMORY_FE; + dvbdemux->dmx.add_frontend(&dvbdemux->dmx, mem_frontend); + return dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, hw_frontend); +} + +static int start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct ddb_input *input = dvbdmx->priv; + + if (!input->users) + ddb_input_start(input); + + return ++input->users; +} + +static int stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct ddb_input *input = dvbdmx->priv; + + if (--input->users) + return input->users; + + ddb_input_stop(input); + return 0; +} + + +static void dvb_input_detach(struct ddb_input *input) +{ + struct dvb_adapter *adap = &input->adap; + struct dvb_demux *dvbdemux = &input->demux; + + switch (input->attached) { + case 5: + if (input->fe2) + dvb_unregister_frontend(input->fe2); + if (input->fe) { + dvb_unregister_frontend(input->fe); + dvb_frontend_detach(input->fe); + input->fe = NULL; + } + case 4: + dvb_net_release(&input->dvbnet); + + case 3: + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, + &input->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, + &input->mem_frontend); + dvb_dmxdev_release(&input->dmxdev); + + case 2: + dvb_dmx_release(&input->demux); + + case 1: + dvb_unregister_adapter(adap); + } + input->attached = 0; +} + +static int dvb_input_attach(struct ddb_input *input) +{ + int ret; + struct ddb_port *port = input->port; + struct dvb_adapter *adap = &input->adap; + struct dvb_demux *dvbdemux = &input->demux; + + ret = dvb_register_adapter(adap, "DDBridge", THIS_MODULE, + &input->port->dev->pdev->dev, + adapter_nr); + if (ret < 0) { + printk(KERN_ERR "ddbridge: Could not register adapter." + "Check if you enabled enough adapters in dvb-core!\n"); + return ret; + } + input->attached = 1; + + ret = my_dvb_dmx_ts_card_init(dvbdemux, "SW demux", + start_feed, + stop_feed, input); + if (ret < 0) + return ret; + input->attached = 2; + + ret = my_dvb_dmxdev_ts_card_init(&input->dmxdev, &input->demux, + &input->hw_frontend, + &input->mem_frontend, adap); + if (ret < 0) + return ret; + input->attached = 3; + + ret = dvb_net_init(adap, &input->dvbnet, input->dmxdev.demux); + if (ret < 0) + return ret; + input->attached = 4; + + input->fe = 0; + switch (port->type) { + case DDB_TUNER_DVBS_ST: + if (demod_attach_stv0900(input, 0) < 0) + return -ENODEV; + if (tuner_attach_stv6110(input, 0) < 0) + return -ENODEV; + if (input->fe) { + if (dvb_register_frontend(adap, input->fe) < 0) + return -ENODEV; + } + break; + case DDB_TUNER_DVBS_ST_AA: + if (demod_attach_stv0900(input, 1) < 0) + return -ENODEV; + if (tuner_attach_stv6110(input, 1) < 0) + return -ENODEV; + if (input->fe) { + if (dvb_register_frontend(adap, input->fe) < 0) + return -ENODEV; + } + break; + case DDB_TUNER_DVBCT_TR: + if (demod_attach_drxk(input) < 0) + return -ENODEV; + if (tuner_attach_tda18271(input) < 0) + return -ENODEV; + if (input->fe) { + if (dvb_register_frontend(adap, input->fe) < 0) + return -ENODEV; + } + if (input->fe2) { + if (dvb_register_frontend(adap, input->fe2) < 0) + return -ENODEV; + input->fe2->tuner_priv = input->fe->tuner_priv; + memcpy(&input->fe2->ops.tuner_ops, + &input->fe->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + } + break; + } + input->attached = 5; + return 0; +} + +/****************************************************************************/ +/****************************************************************************/ + +static ssize_t ts_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct ddb_output *output = dvbdev->priv; + size_t left = count; + int stat; + + while (left) { + if (ddb_output_free(output) < 188) { + if (file->f_flags & O_NONBLOCK) + break; + if (wait_event_interruptible( + output->wq, ddb_output_free(output) >= 188) < 0) + break; + } + stat = ddb_output_write(output, buf, left); + if (stat < 0) + break; + buf += stat; + left -= stat; + } + return (left == count) ? -EAGAIN : (count - left); +} + +static ssize_t ts_read(struct file *file, char *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct ddb_output *output = dvbdev->priv; + struct ddb_input *input = output->port->input[0]; + int left, read; + + count -= count % 188; + left = count; + while (left) { + if (ddb_input_avail(input) < 188) { + if (file->f_flags & O_NONBLOCK) + break; + if (wait_event_interruptible( + input->wq, ddb_input_avail(input) >= 188) < 0) + break; + } + read = ddb_input_read(input, buf, left); + if (read < 0) + return read; + left -= read; + buf += read; + } + return (left == count) ? -EAGAIN : (count - left); +} + +static unsigned int ts_poll(struct file *file, poll_table *wait) +{ + /* + struct dvb_device *dvbdev = file->private_data; + struct ddb_output *output = dvbdev->priv; + struct ddb_input *input = output->port->input[0]; + */ + unsigned int mask = 0; + +#if 0 + if (data_avail_to_read) + mask |= POLLIN | POLLRDNORM; + if (data_avail_to_write) + mask |= POLLOUT | POLLWRNORM; + + poll_wait(file, &read_queue, wait); + poll_wait(file, &write_queue, wait); +#endif + return mask; +} + +static const struct file_operations ci_fops = { + .owner = THIS_MODULE, + .read = ts_read, + .write = ts_write, + .open = dvb_generic_open, + .release = dvb_generic_release, + .poll = ts_poll, + .mmap = 0, +}; + +static struct dvb_device dvbdev_ci = { + .priv = 0, + .readers = -1, + .writers = -1, + .users = -1, + .fops = &ci_fops, +}; + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static void input_tasklet(unsigned long data) +{ + struct ddb_input *input = (struct ddb_input *) data; + struct ddb *dev = input->port->dev; + + spin_lock(&input->lock); + if (!input->running) { + spin_unlock(&input->lock); + return; + } + input->stat = ddbreadl(DMA_BUFFER_CURRENT(input->nr)); + + if (input->port->class == DDB_PORT_TUNER) { + if (4&ddbreadl(DMA_BUFFER_CONTROL(input->nr))) + printk(KERN_ERR "Overflow input %d\n", input->nr); + while (input->cbuf != ((input->stat >> 11) & 0x1f) + || (4&ddbreadl(DMA_BUFFER_CONTROL(input->nr)))) { + dvb_dmx_swfilter_packets(&input->demux, + input->vbuf[input->cbuf], + input->dma_buf_size / 188); + + input->cbuf = (input->cbuf + 1) % input->dma_buf_num; + ddbwritel((input->cbuf << 11), + DMA_BUFFER_ACK(input->nr)); + input->stat = ddbreadl(DMA_BUFFER_CURRENT(input->nr)); + } + } + if (input->port->class == DDB_PORT_CI) + wake_up(&input->wq); + spin_unlock(&input->lock); +} + +static void output_tasklet(unsigned long data) +{ + struct ddb_output *output = (struct ddb_output *) data; + struct ddb *dev = output->port->dev; + + spin_lock(&output->lock); + if (!output->running) { + spin_unlock(&output->lock); + return; + } + output->stat = ddbreadl(DMA_BUFFER_CURRENT(output->nr + 8)); + wake_up(&output->wq); + spin_unlock(&output->lock); +} + + +struct cxd2099_cfg cxd_cfg = { + .bitrate = 62000, + .adr = 0x40, + .polarity = 1, + .clock_mode = 1, +}; + +static int ddb_ci_attach(struct ddb_port *port) +{ + int ret; + + ret = dvb_register_adapter(&port->output->adap, + "DDBridge", + THIS_MODULE, + &port->dev->pdev->dev, + adapter_nr); + if (ret < 0) + return ret; + port->en = cxd2099_attach(&cxd_cfg, port, &port->i2c->adap); + if (!port->en) { + dvb_unregister_adapter(&port->output->adap); + return -ENODEV; + } + ddb_input_start(port->input[0]); + ddb_output_start(port->output); + dvb_ca_en50221_init(&port->output->adap, + port->en, 0, 1); + ret = dvb_register_device(&port->output->adap, &port->output->dev, + &dvbdev_ci, (void *) port->output, + DVB_DEVICE_SEC); + return ret; +} + +static int ddb_port_attach(struct ddb_port *port) +{ + int ret = 0; + + switch (port->class) { + case DDB_PORT_TUNER: + ret = dvb_input_attach(port->input[0]); + if (ret < 0) + break; + ret = dvb_input_attach(port->input[1]); + break; + case DDB_PORT_CI: + ret = ddb_ci_attach(port); + break; + default: + break; + } + if (ret < 0) + printk(KERN_ERR "port_attach on port %d failed\n", port->nr); + return ret; +} + +static int ddb_ports_attach(struct ddb *dev) +{ + int i, ret = 0; + struct ddb_port *port; + + for (i = 0; i < dev->info->port_num; i++) { + port = &dev->port[i]; + ret = ddb_port_attach(port); + if (ret < 0) + break; + } + return ret; +} + +static void ddb_ports_detach(struct ddb *dev) +{ + int i; + struct ddb_port *port; + + for (i = 0; i < dev->info->port_num; i++) { + port = &dev->port[i]; + switch (port->class) { + case DDB_PORT_TUNER: + dvb_input_detach(port->input[0]); + dvb_input_detach(port->input[1]); + break; + case DDB_PORT_CI: + if (port->output->dev) + dvb_unregister_device(port->output->dev); + if (port->en) { + ddb_input_stop(port->input[0]); + ddb_output_stop(port->output); + dvb_ca_en50221_release(port->en); + kfree(port->en); + port->en = 0; + dvb_unregister_adapter(&port->output->adap); + } + break; + } + } +} + +/****************************************************************************/ +/****************************************************************************/ + +static int port_has_ci(struct ddb_port *port) +{ + u8 val; + return i2c_read_reg(&port->i2c->adap, 0x40, 0, &val) ? 0 : 1; +} + +static int port_has_stv0900(struct ddb_port *port) +{ + u8 val; + if (i2c_read_reg16(&port->i2c->adap, 0x69, 0xf100, &val) < 0) + return 0; + return 1; +} + +static int port_has_stv0900_aa(struct ddb_port *port) +{ + u8 val; + if (i2c_read_reg16(&port->i2c->adap, 0x68, 0xf100, &val) < 0) + return 0; + return 1; +} + +static int port_has_drxks(struct ddb_port *port) +{ + u8 val; + if (i2c_read(&port->i2c->adap, 0x29, &val) < 0) + return 0; + if (i2c_read(&port->i2c->adap, 0x2a, &val) < 0) + return 0; + return 1; +} + +static void ddb_port_probe(struct ddb_port *port) +{ + struct ddb *dev = port->dev; + char *modname = "NO MODULE"; + + port->class = DDB_PORT_NONE; + + if (port_has_ci(port)) { + modname = "CI"; + port->class = DDB_PORT_CI; + ddbwritel(I2C_SPEED_400, port->i2c->regs + I2C_TIMING); + } else if (port_has_stv0900(port)) { + modname = "DUAL DVB-S2"; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_DVBS_ST; + ddbwritel(I2C_SPEED_100, port->i2c->regs + I2C_TIMING); + } else if (port_has_stv0900_aa(port)) { + modname = "DUAL DVB-S2"; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_DVBS_ST_AA; + ddbwritel(I2C_SPEED_100, port->i2c->regs + I2C_TIMING); + } else if (port_has_drxks(port)) { + modname = "DUAL DVB-C/T"; + port->class = DDB_PORT_TUNER; + port->type = DDB_TUNER_DVBCT_TR; + ddbwritel(I2C_SPEED_400, port->i2c->regs + I2C_TIMING); + } + printk(KERN_INFO "Port %d (TAB %d): %s\n", + port->nr, port->nr+1, modname); +} + +static void ddb_input_init(struct ddb_port *port, int nr) +{ + struct ddb *dev = port->dev; + struct ddb_input *input = &dev->input[nr]; + + input->nr = nr; + input->port = port; + input->dma_buf_num = INPUT_DMA_BUFS; + input->dma_buf_size = INPUT_DMA_SIZE; + ddbwritel(0, TS_INPUT_CONTROL(nr)); + ddbwritel(2, TS_INPUT_CONTROL(nr)); + ddbwritel(0, TS_INPUT_CONTROL(nr)); + ddbwritel(0, DMA_BUFFER_ACK(nr)); + tasklet_init(&input->tasklet, input_tasklet, (unsigned long) input); + spin_lock_init(&input->lock); + init_waitqueue_head(&input->wq); +} + +static void ddb_output_init(struct ddb_port *port, int nr) +{ + struct ddb *dev = port->dev; + struct ddb_output *output = &dev->output[nr]; + output->nr = nr; + output->port = port; + output->dma_buf_num = OUTPUT_DMA_BUFS; + output->dma_buf_size = OUTPUT_DMA_SIZE; + + ddbwritel(0, TS_OUTPUT_CONTROL(nr)); + ddbwritel(2, TS_OUTPUT_CONTROL(nr)); + ddbwritel(0, TS_OUTPUT_CONTROL(nr)); + tasklet_init(&output->tasklet, output_tasklet, (unsigned long) output); + init_waitqueue_head(&output->wq); +} + +static void ddb_ports_init(struct ddb *dev) +{ + int i; + struct ddb_port *port; + + for (i = 0; i < dev->info->port_num; i++) { + port = &dev->port[i]; + port->dev = dev; + port->nr = i; + port->i2c = &dev->i2c[i]; + port->input[0] = &dev->input[2 * i]; + port->input[1] = &dev->input[2 * i + 1]; + port->output = &dev->output[i]; + + mutex_init(&port->i2c_gate_lock); + ddb_port_probe(port); + ddb_input_init(port, 2 * i); + ddb_input_init(port, 2 * i + 1); + ddb_output_init(port, i); + } +} + +static void ddb_ports_release(struct ddb *dev) +{ + int i; + struct ddb_port *port; + + for (i = 0; i < dev->info->port_num; i++) { + port = &dev->port[i]; + port->dev = dev; + tasklet_kill(&port->input[0]->tasklet); + tasklet_kill(&port->input[1]->tasklet); + tasklet_kill(&port->output->tasklet); + } +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static void irq_handle_i2c(struct ddb *dev, int n) +{ + struct ddb_i2c *i2c = &dev->i2c[n]; + + i2c->done = 1; + wake_up(&i2c->wq); +} + +static irqreturn_t irq_handler(int irq, void *dev_id) +{ + struct ddb *dev = (struct ddb *) dev_id; + u32 s = ddbreadl(INTERRUPT_STATUS); + + if (!s) + return IRQ_NONE; + + do { + ddbwritel(s, INTERRUPT_ACK); + + if (s & 0x00000001) + irq_handle_i2c(dev, 0); + if (s & 0x00000002) + irq_handle_i2c(dev, 1); + if (s & 0x00000004) + irq_handle_i2c(dev, 2); + if (s & 0x00000008) + irq_handle_i2c(dev, 3); + + if (s & 0x00000100) + tasklet_schedule(&dev->input[0].tasklet); + if (s & 0x00000200) + tasklet_schedule(&dev->input[1].tasklet); + if (s & 0x00000400) + tasklet_schedule(&dev->input[2].tasklet); + if (s & 0x00000800) + tasklet_schedule(&dev->input[3].tasklet); + if (s & 0x00001000) + tasklet_schedule(&dev->input[4].tasklet); + if (s & 0x00002000) + tasklet_schedule(&dev->input[5].tasklet); + if (s & 0x00004000) + tasklet_schedule(&dev->input[6].tasklet); + if (s & 0x00008000) + tasklet_schedule(&dev->input[7].tasklet); + + if (s & 0x00010000) + tasklet_schedule(&dev->output[0].tasklet); + if (s & 0x00020000) + tasklet_schedule(&dev->output[1].tasklet); + if (s & 0x00040000) + tasklet_schedule(&dev->output[2].tasklet); + if (s & 0x00080000) + tasklet_schedule(&dev->output[3].tasklet); + + /* if (s & 0x000f0000) printk(KERN_DEBUG "%08x\n", istat); */ + } while ((s = ddbreadl(INTERRUPT_STATUS))); + + return IRQ_HANDLED; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +static int flashio(struct ddb *dev, u8 *wbuf, u32 wlen, u8 *rbuf, u32 rlen) +{ + u32 data, shift; + + if (wlen > 4) + ddbwritel(1, SPI_CONTROL); + while (wlen > 4) { + /* FIXME: check for big-endian */ + data = swab32(*(u32 *)wbuf); + wbuf += 4; + wlen -= 4; + ddbwritel(data, SPI_DATA); + while (ddbreadl(SPI_CONTROL) & 0x0004) + ; + } + + if (rlen) + ddbwritel(0x0001 | ((wlen << (8 + 3)) & 0x1f00), SPI_CONTROL); + else + ddbwritel(0x0003 | ((wlen << (8 + 3)) & 0x1f00), SPI_CONTROL); + + data = 0; + shift = ((4 - wlen) * 8); + while (wlen) { + data <<= 8; + data |= *wbuf; + wlen--; + wbuf++; + } + if (shift) + data <<= shift; + ddbwritel(data, SPI_DATA); + while (ddbreadl(SPI_CONTROL) & 0x0004) + ; + + if (!rlen) { + ddbwritel(0, SPI_CONTROL); + return 0; + } + if (rlen > 4) + ddbwritel(1, SPI_CONTROL); + + while (rlen > 4) { + ddbwritel(0xffffffff, SPI_DATA); + while (ddbreadl(SPI_CONTROL) & 0x0004) + ; + data = ddbreadl(SPI_DATA); + *(u32 *) rbuf = swab32(data); + rbuf += 4; + rlen -= 4; + } + ddbwritel(0x0003 | ((rlen << (8 + 3)) & 0x1F00), SPI_CONTROL); + ddbwritel(0xffffffff, SPI_DATA); + while (ddbreadl(SPI_CONTROL) & 0x0004) + ; + + data = ddbreadl(SPI_DATA); + ddbwritel(0, SPI_CONTROL); + + if (rlen < 4) + data <<= ((4 - rlen) * 8); + + while (rlen > 0) { + *rbuf = ((data >> 24) & 0xff); + data <<= 8; + rbuf++; + rlen--; + } + return 0; +} + +#define DDB_MAGIC 'd' + +struct ddb_flashio { + __u8 *write_buf; + __u32 write_len; + __u8 *read_buf; + __u32 read_len; +}; + +#define IOCTL_DDB_FLASHIO _IOWR(DDB_MAGIC, 0x00, struct ddb_flashio) + +#define DDB_NAME "ddbridge" + +static u32 ddb_num; +static struct ddb *ddbs[32]; +static struct class *ddb_class; +static int ddb_major; + +static int ddb_open(struct inode *inode, struct file *file) +{ + struct ddb *dev = ddbs[iminor(inode)]; + + file->private_data = dev; + return 0; +} + +static long ddb_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ddb *dev = file->private_data; + void *parg = (void *)arg; + int res; + + switch (cmd) { + case IOCTL_DDB_FLASHIO: + { + struct ddb_flashio fio; + u8 *rbuf, *wbuf; + + if (copy_from_user(&fio, parg, sizeof(fio))) + return -EFAULT; + + if (fio.write_len > 1028 || fio.read_len > 1028) + return -EINVAL; + if (fio.write_len + fio.read_len > 1028) + return -EINVAL; + + wbuf = &dev->iobuf[0]; + rbuf = wbuf + fio.write_len; + + if (copy_from_user(wbuf, fio.write_buf, fio.write_len)) + return -EFAULT; + res = flashio(dev, wbuf, fio.write_len, rbuf, fio.read_len); + if (res) + return res; + if (copy_to_user(fio.read_buf, rbuf, fio.read_len)) + return -EFAULT; + break; + } + default: + return -ENOTTY; + } + return 0; +} + +static const struct file_operations ddb_fops = { + .unlocked_ioctl = ddb_ioctl, + .open = ddb_open, +}; + +static char *ddb_devnode(struct device *device, umode_t *mode) +{ + struct ddb *dev = dev_get_drvdata(device); + + return kasprintf(GFP_KERNEL, "ddbridge/card%d", dev->nr); +} + +static int ddb_class_create(void) +{ + ddb_major = register_chrdev(0, DDB_NAME, &ddb_fops); + if (ddb_major < 0) + return ddb_major; + + ddb_class = class_create(THIS_MODULE, DDB_NAME); + if (IS_ERR(ddb_class)) { + unregister_chrdev(ddb_major, DDB_NAME); + return PTR_ERR(ddb_class); + } + ddb_class->devnode = ddb_devnode; + return 0; +} + +static void ddb_class_destroy(void) +{ + class_destroy(ddb_class); + unregister_chrdev(ddb_major, DDB_NAME); +} + +static int ddb_device_create(struct ddb *dev) +{ + dev->nr = ddb_num++; + dev->ddb_dev = device_create(ddb_class, NULL, + MKDEV(ddb_major, dev->nr), + dev, "ddbridge%d", dev->nr); + ddbs[dev->nr] = dev; + if (IS_ERR(dev->ddb_dev)) + return -1; + return 0; +} + +static void ddb_device_destroy(struct ddb *dev) +{ + ddb_num--; + if (IS_ERR(dev->ddb_dev)) + return; + device_destroy(ddb_class, MKDEV(ddb_major, 0)); +} + + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static void ddb_unmap(struct ddb *dev) +{ + if (dev->regs) + iounmap(dev->regs); + vfree(dev); +} + + +static void __devexit ddb_remove(struct pci_dev *pdev) +{ + struct ddb *dev = (struct ddb *) pci_get_drvdata(pdev); + + ddb_ports_detach(dev); + ddb_i2c_release(dev); + + ddbwritel(0, INTERRUPT_ENABLE); + free_irq(dev->pdev->irq, dev); +#ifdef CONFIG_PCI_MSI + if (dev->msi) + pci_disable_msi(dev->pdev); +#endif + ddb_ports_release(dev); + ddb_buffers_free(dev); + ddb_device_destroy(dev); + + ddb_unmap(dev); + pci_set_drvdata(pdev, 0); + pci_disable_device(pdev); +} + + +static int __devinit ddb_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct ddb *dev; + int stat = 0; + int irq_flag = IRQF_SHARED; + + if (pci_enable_device(pdev) < 0) + return -ENODEV; + + dev = vmalloc(sizeof(struct ddb)); + if (dev == NULL) + return -ENOMEM; + memset(dev, 0, sizeof(struct ddb)); + + dev->pdev = pdev; + pci_set_drvdata(pdev, dev); + dev->info = (struct ddb_info *) id->driver_data; + printk(KERN_INFO "DDBridge driver detected: %s\n", dev->info->name); + + dev->regs = ioremap(pci_resource_start(dev->pdev, 0), + pci_resource_len(dev->pdev, 0)); + if (!dev->regs) { + stat = -ENOMEM; + goto fail; + } + printk(KERN_INFO "HW %08x FW %08x\n", ddbreadl(0), ddbreadl(4)); + +#ifdef CONFIG_PCI_MSI + if (pci_msi_enabled()) + stat = pci_enable_msi(dev->pdev); + if (stat) { + printk(KERN_INFO ": MSI not available.\n"); + } else { + irq_flag = 0; + dev->msi = 1; + } +#endif + stat = request_irq(dev->pdev->irq, irq_handler, + irq_flag, "DDBridge", (void *) dev); + if (stat < 0) + goto fail1; + ddbwritel(0, DMA_BASE_WRITE); + ddbwritel(0, DMA_BASE_READ); + ddbwritel(0xffffffff, INTERRUPT_ACK); + ddbwritel(0xfff0f, INTERRUPT_ENABLE); + ddbwritel(0, MSI1_ENABLE); + + if (ddb_i2c_init(dev) < 0) + goto fail1; + ddb_ports_init(dev); + if (ddb_buffers_alloc(dev) < 0) { + printk(KERN_INFO ": Could not allocate buffer memory\n"); + goto fail2; + } + if (ddb_ports_attach(dev) < 0) + goto fail3; + ddb_device_create(dev); + return 0; + +fail3: + ddb_ports_detach(dev); + printk(KERN_ERR "fail3\n"); + ddb_ports_release(dev); +fail2: + printk(KERN_ERR "fail2\n"); + ddb_buffers_free(dev); +fail1: + printk(KERN_ERR "fail1\n"); + if (dev->msi) + pci_disable_msi(dev->pdev); + free_irq(dev->pdev->irq, dev); +fail: + printk(KERN_ERR "fail\n"); + ddb_unmap(dev); + pci_set_drvdata(pdev, 0); + pci_disable_device(pdev); + return -1; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +static struct ddb_info ddb_none = { + .type = DDB_NONE, + .name = "Digital Devices PCIe bridge", +}; + +static struct ddb_info ddb_octopus = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Octopus DVB adapter", + .port_num = 4, +}; + +static struct ddb_info ddb_octopus_le = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Octopus LE DVB adapter", + .port_num = 2, +}; + +static struct ddb_info ddb_v6 = { + .type = DDB_OCTOPUS, + .name = "Digital Devices Cine S2 V6 DVB adapter", + .port_num = 3, +}; + +#define DDVID 0xdd01 /* Digital Devices Vendor ID */ + +#define DDB_ID(_vend, _dev, _subvend, _subdev, _driverdata) { \ + .vendor = _vend, .device = _dev, \ + .subvendor = _subvend, .subdevice = _subdev, \ + .driver_data = (unsigned long)&_driverdata } + +static const struct pci_device_id ddb_id_tbl[] __devinitdata = { + DDB_ID(DDVID, 0x0002, DDVID, 0x0001, ddb_octopus), + DDB_ID(DDVID, 0x0003, DDVID, 0x0001, ddb_octopus), + DDB_ID(DDVID, 0x0003, DDVID, 0x0002, ddb_octopus_le), + DDB_ID(DDVID, 0x0003, DDVID, 0x0010, ddb_octopus), + DDB_ID(DDVID, 0x0003, DDVID, 0x0020, ddb_v6), + /* in case sub-ids got deleted in flash */ + DDB_ID(DDVID, 0x0003, PCI_ANY_ID, PCI_ANY_ID, ddb_none), + {0} +}; +MODULE_DEVICE_TABLE(pci, ddb_id_tbl); + + +static struct pci_driver ddb_pci_driver = { + .name = "DDBridge", + .id_table = ddb_id_tbl, + .probe = ddb_probe, + .remove = __devexit_p(ddb_remove), +}; + +static __init int module_init_ddbridge(void) +{ + int ret; + + printk(KERN_INFO "Digital Devices PCIE bridge driver, " + "Copyright (C) 2010-11 Digital Devices GmbH\n"); + + ret = ddb_class_create(); + if (ret < 0) + return ret; + ret = pci_register_driver(&ddb_pci_driver); + if (ret < 0) + ddb_class_destroy(); + return ret; +} + +static __exit void module_exit_ddbridge(void) +{ + pci_unregister_driver(&ddb_pci_driver); + ddb_class_destroy(); +} + +module_init(module_init_ddbridge); +module_exit(module_exit_ddbridge); + +MODULE_DESCRIPTION("Digital Devices PCIe Bridge"); +MODULE_AUTHOR("Ralph Metzler"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.5"); diff --git a/drivers/media/dvb/ddbridge/ddbridge-regs.h b/drivers/media/pci/ddbridge/ddbridge-regs.h similarity index 100% rename from drivers/media/dvb/ddbridge/ddbridge-regs.h rename to drivers/media/pci/ddbridge/ddbridge-regs.h diff --git a/drivers/media/dvb/ddbridge/ddbridge.h b/drivers/media/pci/ddbridge/ddbridge.h similarity index 100% rename from drivers/media/dvb/ddbridge/ddbridge.h rename to drivers/media/pci/ddbridge/ddbridge.h diff --git a/drivers/media/pci/dm1105/Kconfig b/drivers/media/pci/dm1105/Kconfig new file mode 100644 index 000000000000..013df4e015cd --- /dev/null +++ b/drivers/media/pci/dm1105/Kconfig @@ -0,0 +1,20 @@ +config DVB_DM1105 + tristate "SDMC DM1105 based PCI cards" + depends on DVB_CORE && PCI && I2C + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT + select DVB_SI21XX if MEDIA_SUBDRV_AUTOSELECT + select DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT + depends on RC_CORE + help + Support for cards based on the SDMC DM1105 PCI chip like + DvbWorld 2002 + + Since these cards have no MPEG decoder onboard, they transmit + only compressed MPEG data over the PCI bus, so you need + an external software decoder to watch TV on your computer. + + Say Y or M if you own such a device and want to use it. diff --git a/drivers/media/pci/dm1105/Makefile b/drivers/media/pci/dm1105/Makefile new file mode 100644 index 000000000000..327585143c83 --- /dev/null +++ b/drivers/media/pci/dm1105/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_DVB_DM1105) += dm1105.o + +ccflags-y += -Idrivers/media/dvb-core/ -Idrivers/media/dvb-frontends diff --git a/drivers/media/dvb/dm1105/dm1105.c b/drivers/media/pci/dm1105/dm1105.c similarity index 100% rename from drivers/media/dvb/dm1105/dm1105.c rename to drivers/media/pci/dm1105/dm1105.c diff --git a/drivers/media/pci/ivtv/Kconfig b/drivers/media/pci/ivtv/Kconfig new file mode 100644 index 000000000000..dd6ee57e3a4c --- /dev/null +++ b/drivers/media/pci/ivtv/Kconfig @@ -0,0 +1,61 @@ +config VIDEO_IVTV + tristate "Conexant cx23416/cx23415 MPEG encoder/decoder support" + depends on VIDEO_V4L2 && PCI && I2C + select I2C_ALGOBIT + depends on RC_CORE + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_CX2341X + select VIDEO_CX25840 + select VIDEO_MSP3400 + select VIDEO_SAA711X + select VIDEO_SAA717X + select VIDEO_SAA7127 + select VIDEO_CS53L32A + select VIDEO_M52790 + select VIDEO_WM8775 + select VIDEO_WM8739 + select VIDEO_VP27SMPX + select VIDEO_UPD64031A + select VIDEO_UPD64083 + ---help--- + This is a video4linux driver for Conexant cx23416 or cx23415 based + PCI personal video recorder devices. + + This is used in devices such as the Hauppauge PVR-150/250/350/500 + cards. There is a driver homepage at . + + To compile this driver as a module, choose M here: the + module will be called ivtv. + +config VIDEO_IVTV_ALSA + tristate "Conexant cx23415/cx23416 ALSA interface for PCM audio capture" + depends on VIDEO_IVTV && SND + select SND_PCM + ---help--- + This driver provides an ALSA interface as another method for user + applications to obtain PCM audio data from Conexant cx23415/cx23416 + based PCI TV cards supported by the ivtv driver. + + The ALSA interface has much wider use in user applications performing + PCM audio capture, than the V4L2 "/dev/video24" PCM audio interface + provided by the main ivtv driver. + + To compile this driver as a module, choose M here: the + module will be called ivtv-alsa. + +config VIDEO_FB_IVTV + tristate "Conexant cx23415 framebuffer support" + depends on VIDEO_IVTV && FB + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + This is a framebuffer driver for the Conexant cx23415 MPEG + encoder/decoder. + + This is used in the Hauppauge PVR-350 card. There is a driver + homepage at . + + To compile this driver as a module, choose M here: the + module will be called ivtvfb. diff --git a/drivers/media/pci/ivtv/Makefile b/drivers/media/pci/ivtv/Makefile new file mode 100644 index 000000000000..0eaa88298b7e --- /dev/null +++ b/drivers/media/pci/ivtv/Makefile @@ -0,0 +1,16 @@ +ivtv-objs := ivtv-routing.o ivtv-cards.o ivtv-controls.o \ + ivtv-driver.o ivtv-fileops.o ivtv-firmware.o \ + ivtv-gpio.o ivtv-i2c.o ivtv-ioctl.o ivtv-irq.o \ + ivtv-mailbox.o ivtv-queue.o ivtv-streams.o ivtv-udma.o \ + ivtv-vbi.o ivtv-yuv.o +ivtv-alsa-objs := ivtv-alsa-main.o ivtv-alsa-pcm.o + +obj-$(CONFIG_VIDEO_IVTV) += ivtv.o +obj-$(CONFIG_VIDEO_IVTV_ALSA) += ivtv-alsa.o +obj-$(CONFIG_VIDEO_FB_IVTV) += ivtvfb.o + +ccflags-y += -I$(srctree)/drivers/media/i2c +ccflags-y += -I$(srctree)/drivers/media/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb-frontends + diff --git a/drivers/media/pci/ivtv/ivtv-alsa-main.c b/drivers/media/pci/ivtv/ivtv-alsa-main.c new file mode 100644 index 000000000000..8deab1629b3b --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-main.c @@ -0,0 +1,303 @@ +/* + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + * Copyright (C) 2009 Devin Heitmueller + * + * Portions of this work were sponsored by ONELAN Limited for the cx18 driver + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "ivtv-driver.h" +#include "ivtv-version.h" +#include "ivtv-alsa.h" +#include "ivtv-alsa-mixer.h" +#include "ivtv-alsa-pcm.h" + +int ivtv_alsa_debug; + +#define IVTV_DEBUG_ALSA_INFO(fmt, arg...) \ + do { \ + if (ivtv_alsa_debug & 2) \ + pr_info("%s: " fmt, "ivtv-alsa", ## arg); \ + } while (0) + +module_param_named(debug, ivtv_alsa_debug, int, 0644); +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: 0\n" + "\t\t\t 1/0x0001: warning\n" + "\t\t\t 2/0x0002: info\n"); + +MODULE_AUTHOR("Andy Walls"); +MODULE_DESCRIPTION("CX23415/CX23416 ALSA Interface"); +MODULE_SUPPORTED_DEVICE("CX23415/CX23416 MPEG2 encoder"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(IVTV_VERSION); + +static inline +struct snd_ivtv_card *to_snd_ivtv_card(struct v4l2_device *v4l2_dev) +{ + return to_ivtv(v4l2_dev)->alsa; +} + +static inline +struct snd_ivtv_card *p_to_snd_ivtv_card(struct v4l2_device **v4l2_dev) +{ + return container_of(v4l2_dev, struct snd_ivtv_card, v4l2_dev); +} + +static void snd_ivtv_card_free(struct snd_ivtv_card *itvsc) +{ + if (itvsc == NULL) + return; + + if (itvsc->v4l2_dev != NULL) + to_ivtv(itvsc->v4l2_dev)->alsa = NULL; + + /* FIXME - take any other stopping actions needed */ + + kfree(itvsc); +} + +static void snd_ivtv_card_private_free(struct snd_card *sc) +{ + if (sc == NULL) + return; + snd_ivtv_card_free(sc->private_data); + sc->private_data = NULL; + sc->private_free = NULL; +} + +static int snd_ivtv_card_create(struct v4l2_device *v4l2_dev, + struct snd_card *sc, + struct snd_ivtv_card **itvsc) +{ + *itvsc = kzalloc(sizeof(struct snd_ivtv_card), GFP_KERNEL); + if (*itvsc == NULL) + return -ENOMEM; + + (*itvsc)->v4l2_dev = v4l2_dev; + (*itvsc)->sc = sc; + + sc->private_data = *itvsc; + sc->private_free = snd_ivtv_card_private_free; + + return 0; +} + +static int snd_ivtv_card_set_names(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + struct snd_card *sc = itvsc->sc; + + /* sc->driver is used by alsa-lib's configurator: simple, unique */ + strlcpy(sc->driver, "CX2341[56]", sizeof(sc->driver)); + + /* sc->shortname is a symlink in /proc/asound: IVTV-M -> cardN */ + snprintf(sc->shortname, sizeof(sc->shortname), "IVTV-%d", + itv->instance); + + /* sc->longname is read from /proc/asound/cards */ + snprintf(sc->longname, sizeof(sc->longname), + "CX2341[56] #%d %s TV/FM Radio/Line-In Capture", + itv->instance, itv->card_name); + + return 0; +} + +static int snd_ivtv_init(struct v4l2_device *v4l2_dev) +{ + struct ivtv *itv = to_ivtv(v4l2_dev); + struct snd_card *sc = NULL; + struct snd_ivtv_card *itvsc; + int ret; + + /* Numbrs steps from "Writing an ALSA Driver" by Takashi Iwai */ + + /* (1) Check and increment the device index */ + /* This is a no-op for us. We'll use the itv->instance */ + + /* (2) Create a card instance */ + ret = snd_card_create(SNDRV_DEFAULT_IDX1, /* use first available id */ + SNDRV_DEFAULT_STR1, /* xid from end of shortname*/ + THIS_MODULE, 0, &sc); + if (ret) { + IVTV_ALSA_ERR("%s: snd_card_create() failed with err %d\n", + __func__, ret); + goto err_exit; + } + + /* (3) Create a main component */ + ret = snd_ivtv_card_create(v4l2_dev, sc, &itvsc); + if (ret) { + IVTV_ALSA_ERR("%s: snd_ivtv_card_create() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + + /* (4) Set the driver ID and name strings */ + snd_ivtv_card_set_names(itvsc); + + /* (5) Create other components: mixer, PCM, & proc files */ +#if 0 + ret = snd_ivtv_mixer_create(itvsc); + if (ret) { + IVTV_ALSA_WARN("%s: snd_ivtv_mixer_create() failed with err %d:" + " proceeding anyway\n", __func__, ret); + } +#endif + + ret = snd_ivtv_pcm_create(itvsc); + if (ret) { + IVTV_ALSA_ERR("%s: snd_ivtv_pcm_create() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + /* FIXME - proc files */ + + /* (7) Set the driver data and return 0 */ + /* We do this out of normal order for PCI drivers to avoid races */ + itv->alsa = itvsc; + + /* (6) Register the card instance */ + ret = snd_card_register(sc); + if (ret) { + itv->alsa = NULL; + IVTV_ALSA_ERR("%s: snd_card_register() failed with err %d\n", + __func__, ret); + goto err_exit_free; + } + + return 0; + +err_exit_free: + if (sc != NULL) + snd_card_free(sc); + kfree(itvsc); +err_exit: + return ret; +} + +int ivtv_alsa_load(struct ivtv *itv) +{ + struct v4l2_device *v4l2_dev = &itv->v4l2_dev; + struct ivtv_stream *s; + + if (v4l2_dev == NULL) { + pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + itv = to_ivtv(v4l2_dev); + if (itv == NULL) { + pr_err("ivtv-alsa itv is NULL\n"); + return 0; + } + + s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + if (s->vdev == NULL) { + IVTV_DEBUG_ALSA_INFO("%s: PCM stream for card is disabled - " + "skipping\n", __func__); + return 0; + } + + if (itv->alsa != NULL) { + IVTV_ALSA_ERR("%s: struct snd_ivtv_card * already exists\n", + __func__); + return 0; + } + + if (snd_ivtv_init(v4l2_dev)) { + IVTV_ALSA_ERR("%s: failed to create struct snd_ivtv_card\n", + __func__); + } else { + IVTV_DEBUG_ALSA_INFO("%s: created ivtv ALSA interface instance " + "\n", __func__); + } + return 0; +} + +static int __init ivtv_alsa_init(void) +{ + pr_info("ivtv-alsa: module loading...\n"); + ivtv_ext_init = &ivtv_alsa_load; + return 0; +} + +static void __exit snd_ivtv_exit(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + + /* FIXME - pointer checks & shutdown itvsc */ + + snd_card_free(itvsc->sc); + itv->alsa = NULL; +} + +static int __exit ivtv_alsa_exit_callback(struct device *dev, void *data) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct snd_ivtv_card *itvsc; + + if (v4l2_dev == NULL) { + pr_err("ivtv-alsa: %s: struct v4l2_device * is NULL\n", + __func__); + return 0; + } + + itvsc = to_snd_ivtv_card(v4l2_dev); + if (itvsc == NULL) { + IVTV_ALSA_WARN("%s: struct snd_ivtv_card * is NULL\n", + __func__); + return 0; + } + + snd_ivtv_exit(itvsc); + return 0; +} + +static void __exit ivtv_alsa_exit(void) +{ + struct device_driver *drv; + int ret; + + pr_info("ivtv-alsa: module unloading...\n"); + + drv = driver_find("ivtv", &pci_bus_type); + ret = driver_for_each_device(drv, NULL, NULL, ivtv_alsa_exit_callback); + (void)ret; /* suppress compiler warning */ + + ivtv_ext_init = NULL; + pr_info("ivtv-alsa: module unload complete\n"); +} + +module_init(ivtv_alsa_init); +module_exit(ivtv_alsa_exit); diff --git a/drivers/media/pci/ivtv/ivtv-alsa-mixer.c b/drivers/media/pci/ivtv/ivtv-alsa-mixer.c new file mode 100644 index 000000000000..33ec05b09af3 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-mixer.c @@ -0,0 +1,175 @@ +/* + * ALSA mixer controls for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "ivtv-alsa.h" +#include "ivtv-driver.h" + +/* + * Note the cx25840-core volume scale is funny, due to the alignment of the + * scale with another chip's range: + * + * v4l2_control value /512 indicated dB actual dB reg 0x8d4 + * 0x0000 - 0x01ff 0 -119 -96 228 + * 0x0200 - 0x02ff 1 -118 -96 228 + * ... + * 0x2c00 - 0x2dff 22 -97 -96 228 + * 0x2e00 - 0x2fff 23 -96 -96 228 + * 0x3000 - 0x31ff 24 -95 -95 226 + * ... + * 0xee00 - 0xefff 119 0 0 36 + * ... + * 0xfe00 - 0xffff 127 +8 +8 20 + */ +static inline int dB_to_cx25840_vol(int dB) +{ + if (dB < -96) + dB = -96; + else if (dB > 8) + dB = 8; + return (dB + 119) << 9; +} + +static inline int cx25840_vol_to_dB(int v) +{ + if (v < (23 << 9)) + v = (23 << 9); + else if (v > (127 << 9)) + v = (127 << 9); + return (v >> 9) - 119; +} + +static int snd_ivtv_mixer_tv_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + /* We're already translating values, just keep this control in dB */ + uinfo->value.integer.min = -96; + uinfo->value.integer.max = 8; + uinfo->value.integer.step = 1; + return 0; +} + +static int snd_ivtv_mixer_tv_vol_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_ivtv_card *itvsc = snd_kcontrol_chip(kctl); + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + struct v4l2_control vctrl; + int ret; + + vctrl.id = V4L2_CID_AUDIO_VOLUME; + vctrl.value = dB_to_cx25840_vol(uctl->value.integer.value[0]); + + snd_ivtv_lock(itvsc); + ret = v4l2_subdev_call(itv->sd_audio, core, g_ctrl, &vctrl); + snd_ivtv_unlock(itvsc); + + if (!ret) + uctl->value.integer.value[0] = cx25840_vol_to_dB(vctrl.value); + return ret; +} + +static int snd_ivtv_mixer_tv_vol_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_ivtv_card *itvsc = snd_kcontrol_chip(kctl); + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + struct v4l2_control vctrl; + int ret; + + vctrl.id = V4L2_CID_AUDIO_VOLUME; + vctrl.value = dB_to_cx25840_vol(uctl->value.integer.value[0]); + + snd_ivtv_lock(itvsc); + + /* Fetch current state */ + ret = v4l2_subdev_call(itv->sd_audio, core, g_ctrl, &vctrl); + + if (ret || + (cx25840_vol_to_dB(vctrl.value) != uctl->value.integer.value[0])) { + + /* Set, if needed */ + vctrl.value = dB_to_cx25840_vol(uctl->value.integer.value[0]); + ret = v4l2_subdev_call(itv->sd_audio, core, s_ctrl, &vctrl); + if (!ret) + ret = 1; /* Indicate control was changed w/o error */ + } + snd_ivtv_unlock(itvsc); + + return ret; +} + + +/* This is a bit of overkill, the slider is already in dB internally */ +static DECLARE_TLV_DB_SCALE(snd_ivtv_mixer_tv_vol_db_scale, -9600, 100, 0); + +static struct snd_kcontrol_new snd_ivtv_mixer_tv_vol __initdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog TV Capture Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_ivtv_mixer_tv_volume_info, + .get = snd_ivtv_mixer_tv_volume_get, + .put = snd_ivtv_mixer_tv_volume_put, + .tlv.p = snd_ivtv_mixer_tv_vol_db_scale +}; + +/* FIXME - add mute switch and balance, bass, treble sliders: + V4L2_CID_AUDIO_MUTE + + V4L2_CID_AUDIO_BALANCE + + V4L2_CID_AUDIO_BASS + V4L2_CID_AUDIO_TREBLE +*/ + +/* FIXME - add stereo, lang1, lang2, mono menu */ +/* FIXME - add I2S volume */ + +int __init snd_ivtv_mixer_create(struct snd_ivtv_card *itvsc) +{ + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct snd_card *sc = itvsc->sc; + int ret; + + strlcpy(sc->mixername, "CX2341[56] Mixer", sizeof(sc->mixername)); + + ret = snd_ctl_add(sc, snd_ctl_new1(snd_ivtv_mixer_tv_vol, itvsc)); + if (ret) { + IVTV_ALSA_WARN("%s: failed to add %s control, err %d\n", + __func__, snd_ivtv_mixer_tv_vol.name, ret); + } + return ret; +} diff --git a/drivers/media/pci/ivtv/ivtv-alsa-mixer.h b/drivers/media/pci/ivtv/ivtv-alsa-mixer.h new file mode 100644 index 000000000000..cdde36704d53 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-mixer.h @@ -0,0 +1,23 @@ +/* + * ALSA mixer controls for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +int __init snd_ivtv_mixer_create(struct snd_ivtv_card *itvsc); diff --git a/drivers/media/pci/ivtv/ivtv-alsa-pcm.c b/drivers/media/pci/ivtv/ivtv-alsa-pcm.c new file mode 100644 index 000000000000..f7022bd58ffd --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-pcm.c @@ -0,0 +1,356 @@ +/* + * ALSA PCM device for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + * Copyright (C) 2009 Devin Heitmueller + * + * Portions of this work were sponsored by ONELAN Limited for the cx18 driver + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include +#include +#include + +#include + +#include +#include + +#include "ivtv-driver.h" +#include "ivtv-queue.h" +#include "ivtv-streams.h" +#include "ivtv-fileops.h" +#include "ivtv-alsa.h" + +static unsigned int pcm_debug; +module_param(pcm_debug, int, 0644); +MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); + +#define dprintk(fmt, arg...) \ + do { \ + if (pcm_debug) \ + pr_info("ivtv-alsa-pcm %s: " fmt, __func__, ##arg); \ + } while (0) + +static struct snd_pcm_hardware snd_ivtv_hw_capture = { + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_48000, + + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ + .period_bytes_min = 64, /* 12544/2, */ + .period_bytes_max = 12544, + .periods_min = 2, + .periods_max = 98, /* 12544, */ +}; + +void ivtv_alsa_announce_pcm_data(struct snd_ivtv_card *itvsc, u8 *pcm_data, + size_t num_bytes) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int oldptr; + unsigned int stride; + int period_elapsed = 0; + int length; + + dprintk("ivtv alsa announce ptr=%p data=%p num_bytes=%zd\n", itvsc, + pcm_data, num_bytes); + + substream = itvsc->capture_pcm_substream; + if (substream == NULL) { + dprintk("substream was NULL\n"); + return; + } + + runtime = substream->runtime; + if (runtime == NULL) { + dprintk("runtime was NULL\n"); + return; + } + + stride = runtime->frame_bits >> 3; + if (stride == 0) { + dprintk("stride is zero\n"); + return; + } + + length = num_bytes / stride; + if (length == 0) { + dprintk("%s: length was zero\n", __func__); + return; + } + + if (runtime->dma_area == NULL) { + dprintk("dma area was NULL - ignoring\n"); + return; + } + + oldptr = itvsc->hwptr_done_capture; + if (oldptr + length >= runtime->buffer_size) { + unsigned int cnt = + runtime->buffer_size - oldptr; + memcpy(runtime->dma_area + oldptr * stride, pcm_data, + cnt * stride); + memcpy(runtime->dma_area, pcm_data + cnt * stride, + length * stride - cnt * stride); + } else { + memcpy(runtime->dma_area + oldptr * stride, pcm_data, + length * stride); + } + snd_pcm_stream_lock(substream); + + itvsc->hwptr_done_capture += length; + if (itvsc->hwptr_done_capture >= + runtime->buffer_size) + itvsc->hwptr_done_capture -= + runtime->buffer_size; + + itvsc->capture_transfer_done += length; + if (itvsc->capture_transfer_done >= + runtime->period_size) { + itvsc->capture_transfer_done -= + runtime->period_size; + period_elapsed = 1; + } + + snd_pcm_stream_unlock(substream); + + if (period_elapsed) + snd_pcm_period_elapsed(substream); +} + +static int snd_ivtv_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct ivtv *itv = to_ivtv(v4l2_dev); + struct ivtv_stream *s; + struct ivtv_open_id item; + int ret; + + /* Instruct the CX2341[56] to start sending packets */ + snd_ivtv_lock(itvsc); + s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + + v4l2_fh_init(&item.fh, s->vdev); + item.itv = itv; + item.type = s->type; + + /* See if the stream is available */ + if (ivtv_claim_stream(&item, item.type)) { + /* No, it's already in use */ + snd_ivtv_unlock(itvsc); + return -EBUSY; + } + + if (test_bit(IVTV_F_S_STREAMOFF, &s->s_flags) || + test_and_set_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + /* We're already streaming. No additional action required */ + snd_ivtv_unlock(itvsc); + return 0; + } + + + runtime->hw = snd_ivtv_hw_capture; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + itvsc->capture_pcm_substream = substream; + runtime->private_data = itv; + + itv->pcm_announce_callback = ivtv_alsa_announce_pcm_data; + + /* Not currently streaming, so start it up */ + set_bit(IVTV_F_S_STREAMING, &s->s_flags); + ret = ivtv_start_v4l2_encode_stream(s); + snd_ivtv_unlock(itvsc); + + return ret; +} + +static int snd_ivtv_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct ivtv *itv = to_ivtv(v4l2_dev); + struct ivtv_stream *s; + + /* Instruct the ivtv to stop sending packets */ + snd_ivtv_lock(itvsc); + s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + ivtv_stop_v4l2_encode_stream(s, 0); + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + + ivtv_release_stream(s); + + itv->pcm_announce_callback = NULL; + snd_ivtv_unlock(itvsc); + + return 0; +} + +static int snd_ivtv_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + int ret; + + snd_ivtv_lock(itvsc); + ret = snd_pcm_lib_ioctl(substream, cmd, arg); + snd_ivtv_unlock(itvsc); + return ret; +} + + +static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, + size_t size) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + + dprintk("Allocating vbuffer\n"); + if (runtime->dma_area) { + if (runtime->dma_bytes > size) + return 0; + + vfree(runtime->dma_area); + } + runtime->dma_area = vmalloc(size); + if (!runtime->dma_area) + return -ENOMEM; + + runtime->dma_bytes = size; + + return 0; +} + +static int snd_ivtv_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + dprintk("%s called\n", __func__); + + return snd_pcm_alloc_vmalloc_buffer(substream, + params_buffer_bytes(params)); +} + +static int snd_ivtv_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + unsigned long flags; + + spin_lock_irqsave(&itvsc->slock, flags); + if (substream->runtime->dma_area) { + dprintk("freeing pcm capture region\n"); + vfree(substream->runtime->dma_area); + substream->runtime->dma_area = NULL; + } + spin_unlock_irqrestore(&itvsc->slock, flags); + + return 0; +} + +static int snd_ivtv_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + + itvsc->hwptr_done_capture = 0; + itvsc->capture_transfer_done = 0; + + return 0; +} + +static int snd_ivtv_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return 0; +} + +static +snd_pcm_uframes_t snd_ivtv_pcm_pointer(struct snd_pcm_substream *substream) +{ + unsigned long flags; + snd_pcm_uframes_t hwptr_done; + struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); + + spin_lock_irqsave(&itvsc->slock, flags); + hwptr_done = itvsc->hwptr_done_capture; + spin_unlock_irqrestore(&itvsc->slock, flags); + + return hwptr_done; +} + +static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, + unsigned long offset) +{ + void *pageptr = subs->runtime->dma_area + offset; + + return vmalloc_to_page(pageptr); +} + +static struct snd_pcm_ops snd_ivtv_pcm_capture_ops = { + .open = snd_ivtv_pcm_capture_open, + .close = snd_ivtv_pcm_capture_close, + .ioctl = snd_ivtv_pcm_ioctl, + .hw_params = snd_ivtv_pcm_hw_params, + .hw_free = snd_ivtv_pcm_hw_free, + .prepare = snd_ivtv_pcm_prepare, + .trigger = snd_ivtv_pcm_trigger, + .pointer = snd_ivtv_pcm_pointer, + .page = snd_pcm_get_vmalloc_page, +}; + +int snd_ivtv_pcm_create(struct snd_ivtv_card *itvsc) +{ + struct snd_pcm *sp; + struct snd_card *sc = itvsc->sc; + struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; + struct ivtv *itv = to_ivtv(v4l2_dev); + int ret; + + ret = snd_pcm_new(sc, "CX2341[56] PCM", + 0, /* PCM device 0, the only one for this card */ + 0, /* 0 playback substreams */ + 1, /* 1 capture substream */ + &sp); + if (ret) { + IVTV_ALSA_ERR("%s: snd_ivtv_pcm_create() failed with err %d\n", + __func__, ret); + goto err_exit; + } + + spin_lock_init(&itvsc->slock); + + snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, + &snd_ivtv_pcm_capture_ops); + sp->info_flags = 0; + sp->private_data = itvsc; + strlcpy(sp->name, itv->card_name, sizeof(sp->name)); + + return 0; + +err_exit: + return ret; +} diff --git a/drivers/media/pci/ivtv/ivtv-alsa-pcm.h b/drivers/media/pci/ivtv/ivtv-alsa-pcm.h new file mode 100644 index 000000000000..5ab18319ea4d --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa-pcm.h @@ -0,0 +1,27 @@ +/* + * ALSA PCM device for the + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +int __init snd_ivtv_pcm_create(struct snd_ivtv_card *itvsc); + +/* Used by ivtv driver to announce the PCM data to the module */ +void ivtv_alsa_announce_pcm_data(struct snd_ivtv_card *card, u8 *pcm_data, + size_t num_bytes); diff --git a/drivers/media/pci/ivtv/ivtv-alsa.h b/drivers/media/pci/ivtv/ivtv-alsa.h new file mode 100644 index 000000000000..4a0d8f2c254d --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-alsa.h @@ -0,0 +1,75 @@ +/* + * ALSA interface to ivtv PCM capture streams + * + * Copyright (C) 2009,2012 Andy Walls + * Copyright (C) 2009 Devin Heitmueller + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +struct snd_card; + +struct snd_ivtv_card { + struct v4l2_device *v4l2_dev; + struct snd_card *sc; + unsigned int capture_transfer_done; + unsigned int hwptr_done_capture; + struct snd_pcm_substream *capture_pcm_substream; + spinlock_t slock; +}; + +extern int ivtv_alsa_debug; + +/* + * File operations that manipulate the encoder or video or audio subdevices + * need to be serialized. Use the same lock we use for v4l2 file ops. + */ +static inline void snd_ivtv_lock(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + mutex_lock(&itv->serialize_lock); +} + +static inline void snd_ivtv_unlock(struct snd_ivtv_card *itvsc) +{ + struct ivtv *itv = to_ivtv(itvsc->v4l2_dev); + mutex_unlock(&itv->serialize_lock); +} + +#define IVTV_ALSA_DBGFLG_WARN (1 << 0) +#define IVTV_ALSA_DBGFLG_INFO (1 << 1) + +#define IVTV_ALSA_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & ivtv_alsa_debug) \ + pr_info("%s-alsa: " type ": " fmt, \ + v4l2_dev->name , ## args); \ + } while (0) + +#define IVTV_ALSA_DEBUG_WARN(fmt, args...) \ + IVTV_ALSA_DEBUG(IVTV_ALSA_DBGFLG_WARN, "warning", fmt , ## args) + +#define IVTV_ALSA_DEBUG_INFO(fmt, args...) \ + IVTV_ALSA_DEBUG(IVTV_ALSA_DBGFLG_INFO, "info", fmt , ## args) + +#define IVTV_ALSA_ERR(fmt, args...) \ + pr_err("%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define IVTV_ALSA_WARN(fmt, args...) \ + pr_warn("%s-alsa: " fmt, v4l2_dev->name , ## args) + +#define IVTV_ALSA_INFO(fmt, args...) \ + pr_info("%s-alsa: " fmt, v4l2_dev->name , ## args) diff --git a/drivers/media/video/ivtv/ivtv-cards.c b/drivers/media/pci/ivtv/ivtv-cards.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-cards.c rename to drivers/media/pci/ivtv/ivtv-cards.c diff --git a/drivers/media/video/ivtv/ivtv-cards.h b/drivers/media/pci/ivtv/ivtv-cards.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-cards.h rename to drivers/media/pci/ivtv/ivtv-cards.h diff --git a/drivers/media/video/ivtv/ivtv-controls.c b/drivers/media/pci/ivtv/ivtv-controls.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-controls.c rename to drivers/media/pci/ivtv/ivtv-controls.c diff --git a/drivers/media/video/ivtv/ivtv-controls.h b/drivers/media/pci/ivtv/ivtv-controls.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-controls.h rename to drivers/media/pci/ivtv/ivtv-controls.h diff --git a/drivers/media/pci/ivtv/ivtv-driver.c b/drivers/media/pci/ivtv/ivtv-driver.c new file mode 100644 index 000000000000..74e9a5032364 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-driver.c @@ -0,0 +1,1536 @@ +/* + ivtv driver initialization and card probing + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Main Driver file for the ivtv project: + * Driver for the Conexant CX23415/CX23416 chip. + * Author: Kevin Thayer (nufan_wfk at yahoo.com) + * License: GPL + * http://www.ivtvdriver.org + * + * ----- + * MPG600/MPG160 support by T.Adachi + * and Takeru KOMORIYA + * + * AVerMedia M179 GPIO info by Chris Pinkham + * using information provided by Jiun-Kuei Jung @ AVerMedia. + * + * Kurouto Sikou CX23416GYC-STVLP tested by K.Ohta + * using information from T.Adachi,Takeru KOMORIYA and others :-) + * + * Nagase TRANSGEAR 5000TV, Aopen VA2000MAX-STN6 and I/O data GV-MVP/RX + * version by T.Adachi. Special thanks Mr.Suzuki + */ + +#include "ivtv-driver.h" +#include "ivtv-version.h" +#include "ivtv-fileops.h" +#include "ivtv-i2c.h" +#include "ivtv-firmware.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-mailbox.h" +#include "ivtv-streams.h" +#include "ivtv-ioctl.h" +#include "ivtv-cards.h" +#include "ivtv-vbi.h" +#include "ivtv-routing.h" +#include "ivtv-controls.h" +#include "ivtv-gpio.h" +#include +#include +#include +#include +#include "tuner-xc2028.h" + +/* If you have already X v4l cards, then set this to X. This way + the device numbers stay matched. Example: you have a WinTV card + without radio and a PVR-350 with. Normally this would give a + video1 device together with a radio0 device for the PVR. By + setting this to 1 you ensure that radio0 is now also radio1. */ +int ivtv_first_minor; + +/* Callback for registering extensions */ +int (*ivtv_ext_init)(struct ivtv *); +EXPORT_SYMBOL(ivtv_ext_init); + +/* add your revision and whatnot here */ +static struct pci_device_id ivtv_pci_tbl[] __devinitdata = { + {PCI_VENDOR_ID_ICOMP, PCI_DEVICE_ID_IVTV15, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_ICOMP, PCI_DEVICE_ID_IVTV16, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci,ivtv_pci_tbl); + +/* ivtv instance counter */ +static atomic_t ivtv_instance = ATOMIC_INIT(0); + +/* Parameter declarations */ +static int cardtype[IVTV_MAX_CARDS]; +static int tuner[IVTV_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int radio[IVTV_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int i2c_clock_period[IVTV_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; + +static unsigned int cardtype_c = 1; +static unsigned int tuner_c = 1; +static int radio_c = 1; +static unsigned int i2c_clock_period_c = 1; +static char pal[] = "---"; +static char secam[] = "--"; +static char ntsc[] = "-"; + +/* Buffers */ + +/* DMA Buffers, Default size in MB allocated */ +#define IVTV_DEFAULT_ENC_MPG_BUFFERS 4 +#define IVTV_DEFAULT_ENC_YUV_BUFFERS 2 +#define IVTV_DEFAULT_ENC_VBI_BUFFERS 1 +/* Exception: size in kB for this stream (MB is overkill) */ +#define IVTV_DEFAULT_ENC_PCM_BUFFERS 320 +#define IVTV_DEFAULT_DEC_MPG_BUFFERS 1 +#define IVTV_DEFAULT_DEC_YUV_BUFFERS 1 +/* Exception: size in kB for this stream (MB is way overkill) */ +#define IVTV_DEFAULT_DEC_VBI_BUFFERS 64 + +static int enc_mpg_buffers = IVTV_DEFAULT_ENC_MPG_BUFFERS; +static int enc_yuv_buffers = IVTV_DEFAULT_ENC_YUV_BUFFERS; +static int enc_vbi_buffers = IVTV_DEFAULT_ENC_VBI_BUFFERS; +static int enc_pcm_buffers = IVTV_DEFAULT_ENC_PCM_BUFFERS; +static int dec_mpg_buffers = IVTV_DEFAULT_DEC_MPG_BUFFERS; +static int dec_yuv_buffers = IVTV_DEFAULT_DEC_YUV_BUFFERS; +static int dec_vbi_buffers = IVTV_DEFAULT_DEC_VBI_BUFFERS; + +static int ivtv_yuv_mode; +static int ivtv_yuv_threshold = -1; +static int ivtv_pci_latency = 1; + +int ivtv_debug; +#ifdef CONFIG_VIDEO_ADV_DEBUG +int ivtv_fw_debug; +#endif + +static int tunertype = -1; +static int newi2c = -1; + +module_param_array(tuner, int, &tuner_c, 0644); +module_param_array(radio, int, &radio_c, 0644); +module_param_array(cardtype, int, &cardtype_c, 0644); +module_param_string(pal, pal, sizeof(pal), 0644); +module_param_string(secam, secam, sizeof(secam), 0644); +module_param_string(ntsc, ntsc, sizeof(ntsc), 0644); +module_param_named(debug,ivtv_debug, int, 0644); +#ifdef CONFIG_VIDEO_ADV_DEBUG +module_param_named(fw_debug, ivtv_fw_debug, int, 0644); +#endif +module_param(ivtv_pci_latency, int, 0644); +module_param(ivtv_yuv_mode, int, 0644); +module_param(ivtv_yuv_threshold, int, 0644); +module_param(ivtv_first_minor, int, 0644); + +module_param(enc_mpg_buffers, int, 0644); +module_param(enc_yuv_buffers, int, 0644); +module_param(enc_vbi_buffers, int, 0644); +module_param(enc_pcm_buffers, int, 0644); +module_param(dec_mpg_buffers, int, 0644); +module_param(dec_yuv_buffers, int, 0644); +module_param(dec_vbi_buffers, int, 0644); + +module_param(tunertype, int, 0644); +module_param(newi2c, int, 0644); +module_param_array(i2c_clock_period, int, &i2c_clock_period_c, 0644); + +MODULE_PARM_DESC(tuner, "Tuner type selection,\n" + "\t\t\tsee tuner.h for values"); +MODULE_PARM_DESC(radio, + "Enable or disable the radio. Use only if autodetection\n" + "\t\t\tfails. 0 = disable, 1 = enable"); +MODULE_PARM_DESC(cardtype, + "Only use this option if your card is not detected properly.\n" + "\t\tSpecify card type:\n" + "\t\t\t 1 = WinTV PVR 250\n" + "\t\t\t 2 = WinTV PVR 350\n" + "\t\t\t 3 = WinTV PVR-150 or PVR-500\n" + "\t\t\t 4 = AVerMedia M179\n" + "\t\t\t 5 = YUAN MPG600/Kuroutoshikou iTVC16-STVLP\n" + "\t\t\t 6 = YUAN MPG160/Kuroutoshikou iTVC15-STVLP\n" + "\t\t\t 7 = YUAN PG600/DIAMONDMM PVR-550 (CX Falcon 2)\n" + "\t\t\t 8 = Adaptec AVC-2410\n" + "\t\t\t 9 = Adaptec AVC-2010\n" + "\t\t\t10 = NAGASE TRANSGEAR 5000TV\n" + "\t\t\t11 = AOpen VA2000MAX-STN6\n" + "\t\t\t12 = YUAN MPG600GR/Kuroutoshikou CX23416GYC-STVLP\n" + "\t\t\t13 = I/O Data GV-MVP/RX\n" + "\t\t\t14 = I/O Data GV-MVP/RX2E\n" + "\t\t\t15 = GOTVIEW PCI DVD\n" + "\t\t\t16 = GOTVIEW PCI DVD2 Deluxe\n" + "\t\t\t17 = Yuan MPC622\n" + "\t\t\t18 = Digital Cowboy DCT-MTVP1\n" + "\t\t\t19 = Yuan PG600V2/GotView PCI DVD Lite\n" + "\t\t\t20 = Club3D ZAP-TV1x01\n" + "\t\t\t21 = AverTV MCE 116 Plus\n" + "\t\t\t22 = ASUS Falcon2\n" + "\t\t\t23 = AverMedia PVR-150 Plus\n" + "\t\t\t24 = AverMedia EZMaker PCI Deluxe\n" + "\t\t\t25 = AverMedia M104 (not yet working)\n" + "\t\t\t26 = Buffalo PC-MV5L/PCI\n" + "\t\t\t27 = AVerMedia UltraTV 1500 MCE\n" + "\t\t\t28 = Sony VAIO Giga Pocket (ENX Kikyou)\n" + "\t\t\t 0 = Autodetect (default)\n" + "\t\t\t-1 = Ignore this card\n\t\t"); +MODULE_PARM_DESC(pal, "Set PAL standard: BGH, DK, I, M, N, Nc, 60"); +MODULE_PARM_DESC(secam, "Set SECAM standard: BGH, DK, L, LC"); +MODULE_PARM_DESC(ntsc, "Set NTSC standard: M, J (Japan), K (South Korea)"); +MODULE_PARM_DESC(tunertype, + "Specify tuner type:\n" + "\t\t\t 0 = tuner for PAL-B/G/H/D/K/I, SECAM-B/G/H/D/K/L/Lc\n" + "\t\t\t 1 = tuner for NTSC-M/J/K, PAL-M/N/Nc\n" + "\t\t\t-1 = Autodetect (default)\n"); +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: 0\n" + "\t\t\t 1/0x0001: warning\n" + "\t\t\t 2/0x0002: info\n" + "\t\t\t 4/0x0004: mailbox\n" + "\t\t\t 8/0x0008: ioctl\n" + "\t\t\t 16/0x0010: file\n" + "\t\t\t 32/0x0020: dma\n" + "\t\t\t 64/0x0040: irq\n" + "\t\t\t 128/0x0080: decoder\n" + "\t\t\t 256/0x0100: yuv\n" + "\t\t\t 512/0x0200: i2c\n" + "\t\t\t1024/0x0400: high volume\n"); +#ifdef CONFIG_VIDEO_ADV_DEBUG +MODULE_PARM_DESC(fw_debug, + "Enable code for debugging firmware problems. Default: 0\n"); +#endif +MODULE_PARM_DESC(ivtv_pci_latency, + "Change the PCI latency to 64 if lower: 0 = No, 1 = Yes,\n" + "\t\t\tDefault: Yes"); +MODULE_PARM_DESC(ivtv_yuv_mode, + "Specify the yuv playback mode:\n" + "\t\t\t0 = interlaced\n\t\t\t1 = progressive\n\t\t\t2 = auto\n" + "\t\t\tDefault: 0 (interlaced)"); +MODULE_PARM_DESC(ivtv_yuv_threshold, + "If ivtv_yuv_mode is 2 (auto) then playback content as\n\t\tprogressive if src height <= ivtv_yuvthreshold\n" + "\t\t\tDefault: 480"); +MODULE_PARM_DESC(enc_mpg_buffers, + "Encoder MPG Buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_MPG_BUFFERS)); +MODULE_PARM_DESC(enc_yuv_buffers, + "Encoder YUV Buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_YUV_BUFFERS)); +MODULE_PARM_DESC(enc_vbi_buffers, + "Encoder VBI Buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_VBI_BUFFERS)); +MODULE_PARM_DESC(enc_pcm_buffers, + "Encoder PCM buffers (in kB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_PCM_BUFFERS)); +MODULE_PARM_DESC(dec_mpg_buffers, + "Decoder MPG buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_DEC_MPG_BUFFERS)); +MODULE_PARM_DESC(dec_yuv_buffers, + "Decoder YUV buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_DEC_YUV_BUFFERS)); +MODULE_PARM_DESC(dec_vbi_buffers, + "Decoder VBI buffers (in kB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_DEC_VBI_BUFFERS)); +MODULE_PARM_DESC(newi2c, + "Use new I2C implementation\n" + "\t\t\t-1 is autodetect, 0 is off, 1 is on\n" + "\t\t\tDefault is autodetect"); +MODULE_PARM_DESC(i2c_clock_period, + "Period of SCL for the I2C bus controlled by the CX23415/6\n" + "\t\t\tMin: 10 usec (100 kHz), Max: 4500 usec (222 Hz)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_I2C_CLOCK_PERIOD)); + +MODULE_PARM_DESC(ivtv_first_minor, "Set device node number assigned to first card"); + +MODULE_AUTHOR("Kevin Thayer, Chris Kennedy, Hans Verkuil"); +MODULE_DESCRIPTION("CX23415/CX23416 driver"); +MODULE_SUPPORTED_DEVICE + ("CX23415/CX23416 MPEG2 encoder (WinTV PVR-150/250/350/500,\n" + "\t\t\tYuan MPG series and similar)"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(IVTV_VERSION); + +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct ivtv *dev = container_of(work, struct ivtv, request_module_wk); + + /* Make sure ivtv-alsa module is loaded */ + request_module("ivtv-alsa"); + + /* Initialize ivtv-alsa for this instance of the cx18 device */ + if (ivtv_ext_init != NULL) + ivtv_ext_init(dev); +} + +static void request_modules(struct ivtv *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} + +static void flush_request_modules(struct ivtv *dev) +{ + flush_work_sync(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#define flush_request_modules(dev) +#endif /* CONFIG_MODULES */ + +void ivtv_clear_irq_mask(struct ivtv *itv, u32 mask) +{ + itv->irqmask &= ~mask; + write_reg_sync(itv->irqmask, IVTV_REG_IRQMASK); +} + +void ivtv_set_irq_mask(struct ivtv *itv, u32 mask) +{ + itv->irqmask |= mask; + write_reg_sync(itv->irqmask, IVTV_REG_IRQMASK); +} + +int ivtv_set_output_mode(struct ivtv *itv, int mode) +{ + int old_mode; + + spin_lock(&itv->lock); + old_mode = itv->output_mode; + if (old_mode == 0) + itv->output_mode = old_mode = mode; + spin_unlock(&itv->lock); + return old_mode; +} + +struct ivtv_stream *ivtv_get_output_stream(struct ivtv *itv) +{ + switch (itv->output_mode) { + case OUT_MPG: + return &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + case OUT_YUV: + return &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + default: + return NULL; + } +} + +int ivtv_waitq(wait_queue_head_t *waitq) +{ + DEFINE_WAIT(wait); + + prepare_to_wait(waitq, &wait, TASK_INTERRUPTIBLE); + schedule(); + finish_wait(waitq, &wait); + return signal_pending(current) ? -EINTR : 0; +} + +/* Generic utility functions */ +int ivtv_msleep_timeout(unsigned int msecs, int intr) +{ + int timeout = msecs_to_jiffies(msecs); + + do { + set_current_state(intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + timeout = schedule_timeout(timeout); + if (intr) { + int ret = signal_pending(current); + + if (ret) + return ret; + } + } while (timeout); + return 0; +} + +/* Release ioremapped memory */ +static void ivtv_iounmap(struct ivtv *itv) +{ + if (itv == NULL) + return; + + /* Release registers memory */ + if (itv->reg_mem != NULL) { + IVTV_DEBUG_INFO("releasing reg_mem\n"); + iounmap(itv->reg_mem); + itv->reg_mem = NULL; + } + /* Release io memory */ + if (itv->has_cx23415 && itv->dec_mem != NULL) { + IVTV_DEBUG_INFO("releasing dec_mem\n"); + iounmap(itv->dec_mem); + } + itv->dec_mem = NULL; + + /* Release io memory */ + if (itv->enc_mem != NULL) { + IVTV_DEBUG_INFO("releasing enc_mem\n"); + iounmap(itv->enc_mem); + itv->enc_mem = NULL; + } +} + +/* Hauppauge card? get values from tveeprom */ +void ivtv_read_eeprom(struct ivtv *itv, struct tveeprom *tv) +{ + u8 eedata[256]; + + itv->i2c_client.addr = 0xA0 >> 1; + tveeprom_read(&itv->i2c_client, eedata, sizeof(eedata)); + tveeprom_hauppauge_analog(&itv->i2c_client, tv, eedata); +} + +static void ivtv_process_eeprom(struct ivtv *itv) +{ + struct tveeprom tv; + int pci_slot = PCI_SLOT(itv->pdev->devfn); + + ivtv_read_eeprom(itv, &tv); + + /* Many thanks to Steven Toth from Hauppauge for providing the + model numbers */ + switch (tv.model) { + /* In a few cases the PCI subsystem IDs do not correctly + identify the card. A better method is to check the + model number from the eeprom instead. */ + case 30012 ... 30039: /* Low profile PVR250 */ + case 32000 ... 32999: + case 48000 ... 48099: /* 48??? range are PVR250s with a cx23415 */ + case 48400 ... 48599: + itv->card = ivtv_get_card(IVTV_CARD_PVR_250); + break; + case 48100 ... 48399: + case 48600 ... 48999: + itv->card = ivtv_get_card(IVTV_CARD_PVR_350); + break; + case 23000 ... 23999: /* PVR500 */ + case 25000 ... 25999: /* Low profile PVR150 */ + case 26000 ... 26999: /* Regular PVR150 */ + itv->card = ivtv_get_card(IVTV_CARD_PVR_150); + break; + case 0: + IVTV_ERR("Invalid EEPROM\n"); + return; + default: + IVTV_ERR("Unknown model %d, defaulting to PVR-150\n", tv.model); + itv->card = ivtv_get_card(IVTV_CARD_PVR_150); + break; + } + + switch (tv.model) { + /* Old style PVR350 (with an saa7114) uses this input for + the tuner. */ + case 48254: + itv->card = ivtv_get_card(IVTV_CARD_PVR_350_V1); + break; + default: + break; + } + + itv->v4l2_cap = itv->card->v4l2_capabilities; + itv->card_name = itv->card->name; + itv->card_i2c = itv->card->i2c; + + /* If this is a PVR500 then it should be possible to detect whether it is the + first or second unit by looking at the subsystem device ID: is bit 4 is + set, then it is the second unit (according to info from Hauppauge). + + However, while this works for most cards, I have seen a few PVR500 cards + where both units have the same subsystem ID. + + So instead I look at the reported 'PCI slot' (which is the slot on the PVR500 + PCI bridge) and if it is 8, then it is assumed to be the first unit, otherwise + it is the second unit. It is possible that it is a different slot when ivtv is + used in Xen, in that case I ignore this card here. The worst that can happen + is that the card presents itself with a non-working radio device. + + This detection is needed since the eeprom reports incorrectly that a radio is + present on the second unit. */ + if (tv.model / 1000 == 23) { + static const struct ivtv_card_tuner_i2c ivtv_i2c_radio = { + .radio = { 0x60, I2C_CLIENT_END }, + .demod = { 0x43, I2C_CLIENT_END }, + .tv = { 0x61, I2C_CLIENT_END }, + }; + + itv->card_name = "WinTV PVR 500"; + itv->card_i2c = &ivtv_i2c_radio; + if (pci_slot == 8 || pci_slot == 9) { + int is_first = (pci_slot & 1) == 0; + + itv->card_name = is_first ? "WinTV PVR 500 (unit #1)" : + "WinTV PVR 500 (unit #2)"; + if (!is_first) { + IVTV_INFO("Correcting tveeprom data: no radio present on second unit\n"); + tv.has_radio = 0; + } + } + } + IVTV_INFO("Autodetected %s\n", itv->card_name); + + switch (tv.tuner_hauppauge_model) { + case 85: + case 99: + case 112: + itv->pvr150_workaround = 1; + break; + default: + break; + } + if (tv.tuner_type == TUNER_ABSENT) + IVTV_ERR("tveeprom cannot autodetect tuner!\n"); + + if (itv->options.tuner == -1) + itv->options.tuner = tv.tuner_type; + if (itv->options.radio == -1) + itv->options.radio = (tv.has_radio != 0); + /* only enable newi2c if an IR blaster is present */ + if (itv->options.newi2c == -1 && tv.has_ir) { + itv->options.newi2c = (tv.has_ir & 4) ? 1 : 0; + if (itv->options.newi2c) { + IVTV_INFO("Reopen i2c bus for IR-blaster support\n"); + exit_ivtv_i2c(itv); + init_ivtv_i2c(itv); + } + } + + if (itv->std != 0) + /* user specified tuner standard */ + return; + + /* autodetect tuner standard */ + if (tv.tuner_formats & V4L2_STD_PAL) { + IVTV_DEBUG_INFO("PAL tuner detected\n"); + itv->std |= V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + } else if (tv.tuner_formats & V4L2_STD_NTSC) { + IVTV_DEBUG_INFO("NTSC tuner detected\n"); + itv->std |= V4L2_STD_NTSC_M; + } else if (tv.tuner_formats & V4L2_STD_SECAM) { + IVTV_DEBUG_INFO("SECAM tuner detected\n"); + itv->std |= V4L2_STD_SECAM_L; + } else { + IVTV_INFO("No tuner detected, default to NTSC-M\n"); + itv->std |= V4L2_STD_NTSC_M; + } +} + +static v4l2_std_id ivtv_parse_std(struct ivtv *itv) +{ + switch (pal[0]) { + case '6': + tunertype = 0; + return V4L2_STD_PAL_60; + case 'b': + case 'B': + case 'g': + case 'G': + case 'h': + case 'H': + tunertype = 0; + return V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + case 'n': + case 'N': + tunertype = 1; + if (pal[1] == 'c' || pal[1] == 'C') + return V4L2_STD_PAL_Nc; + return V4L2_STD_PAL_N; + case 'i': + case 'I': + tunertype = 0; + return V4L2_STD_PAL_I; + case 'd': + case 'D': + case 'k': + case 'K': + tunertype = 0; + return V4L2_STD_PAL_DK; + case 'M': + case 'm': + tunertype = 1; + return V4L2_STD_PAL_M; + case '-': + break; + default: + IVTV_WARN("pal= argument not recognised\n"); + return 0; + } + + switch (secam[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + case 'h': + case 'H': + tunertype = 0; + return V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H; + case 'd': + case 'D': + case 'k': + case 'K': + tunertype = 0; + return V4L2_STD_SECAM_DK; + case 'l': + case 'L': + tunertype = 0; + if (secam[1] == 'C' || secam[1] == 'c') + return V4L2_STD_SECAM_LC; + return V4L2_STD_SECAM_L; + case '-': + break; + default: + IVTV_WARN("secam= argument not recognised\n"); + return 0; + } + + switch (ntsc[0]) { + case 'm': + case 'M': + tunertype = 1; + return V4L2_STD_NTSC_M; + case 'j': + case 'J': + tunertype = 1; + return V4L2_STD_NTSC_M_JP; + case 'k': + case 'K': + tunertype = 1; + return V4L2_STD_NTSC_M_KR; + case '-': + break; + default: + IVTV_WARN("ntsc= argument not recognised\n"); + return 0; + } + + /* no match found */ + return 0; +} + +static void ivtv_process_options(struct ivtv *itv) +{ + const char *chipname; + int i, j; + + itv->options.kilobytes[IVTV_ENC_STREAM_TYPE_MPG] = enc_mpg_buffers * 1024; + itv->options.kilobytes[IVTV_ENC_STREAM_TYPE_YUV] = enc_yuv_buffers * 1024; + itv->options.kilobytes[IVTV_ENC_STREAM_TYPE_VBI] = enc_vbi_buffers * 1024; + itv->options.kilobytes[IVTV_ENC_STREAM_TYPE_PCM] = enc_pcm_buffers; + itv->options.kilobytes[IVTV_DEC_STREAM_TYPE_MPG] = dec_mpg_buffers * 1024; + itv->options.kilobytes[IVTV_DEC_STREAM_TYPE_YUV] = dec_yuv_buffers * 1024; + itv->options.kilobytes[IVTV_DEC_STREAM_TYPE_VBI] = dec_vbi_buffers; + itv->options.cardtype = cardtype[itv->instance]; + itv->options.tuner = tuner[itv->instance]; + itv->options.radio = radio[itv->instance]; + + itv->options.i2c_clock_period = i2c_clock_period[itv->instance]; + if (itv->options.i2c_clock_period == -1) + itv->options.i2c_clock_period = IVTV_DEFAULT_I2C_CLOCK_PERIOD; + else if (itv->options.i2c_clock_period < 10) + itv->options.i2c_clock_period = 10; + else if (itv->options.i2c_clock_period > 4500) + itv->options.i2c_clock_period = 4500; + + itv->options.newi2c = newi2c; + if (tunertype < -1 || tunertype > 1) { + IVTV_WARN("Invalid tunertype argument, will autodetect instead\n"); + tunertype = -1; + } + itv->std = ivtv_parse_std(itv); + if (itv->std == 0 && tunertype >= 0) + itv->std = tunertype ? V4L2_STD_MN : (V4L2_STD_ALL & ~V4L2_STD_MN); + itv->has_cx23415 = (itv->pdev->device == PCI_DEVICE_ID_IVTV15); + chipname = itv->has_cx23415 ? "cx23415" : "cx23416"; + if (itv->options.cardtype == -1) { + IVTV_INFO("Ignore card (detected %s based chip)\n", chipname); + return; + } + if ((itv->card = ivtv_get_card(itv->options.cardtype - 1))) { + IVTV_INFO("User specified %s card (detected %s based chip)\n", + itv->card->name, chipname); + } else if (itv->options.cardtype != 0) { + IVTV_ERR("Unknown user specified type, trying to autodetect card\n"); + } + if (itv->card == NULL) { + if (itv->pdev->subsystem_vendor == IVTV_PCI_ID_HAUPPAUGE || + itv->pdev->subsystem_vendor == IVTV_PCI_ID_HAUPPAUGE_ALT1 || + itv->pdev->subsystem_vendor == IVTV_PCI_ID_HAUPPAUGE_ALT2) { + itv->card = ivtv_get_card(itv->has_cx23415 ? IVTV_CARD_PVR_350 : IVTV_CARD_PVR_150); + IVTV_INFO("Autodetected Hauppauge card (%s based)\n", + chipname); + } + } + if (itv->card == NULL) { + for (i = 0; (itv->card = ivtv_get_card(i)); i++) { + if (itv->card->pci_list == NULL) + continue; + for (j = 0; itv->card->pci_list[j].device; j++) { + if (itv->pdev->device != + itv->card->pci_list[j].device) + continue; + if (itv->pdev->subsystem_vendor != + itv->card->pci_list[j].subsystem_vendor) + continue; + if (itv->pdev->subsystem_device != + itv->card->pci_list[j].subsystem_device) + continue; + IVTV_INFO("Autodetected %s card (%s based)\n", + itv->card->name, chipname); + goto done; + } + } + } +done: + + if (itv->card == NULL) { + itv->card = ivtv_get_card(IVTV_CARD_PVR_150); + IVTV_ERR("Unknown card: vendor/device: [%04x:%04x]\n", + itv->pdev->vendor, itv->pdev->device); + IVTV_ERR(" subsystem vendor/device: [%04x:%04x]\n", + itv->pdev->subsystem_vendor, itv->pdev->subsystem_device); + IVTV_ERR(" %s based\n", chipname); + IVTV_ERR("Defaulting to %s card\n", itv->card->name); + IVTV_ERR("Please mail the vendor/device and subsystem vendor/device IDs and what kind of\n"); + IVTV_ERR("card you have to the ivtv-devel mailinglist (www.ivtvdriver.org)\n"); + IVTV_ERR("Prefix your subject line with [UNKNOWN IVTV CARD].\n"); + } + itv->v4l2_cap = itv->card->v4l2_capabilities; + itv->card_name = itv->card->name; + itv->card_i2c = itv->card->i2c; +} + +/* Precondition: the ivtv structure has been memset to 0. Only + the dev and num fields have been filled in. + No assumptions on the card type may be made here (see ivtv_init_struct2 + for that). + */ +static int __devinit ivtv_init_struct1(struct ivtv *itv) +{ + struct sched_param param = { .sched_priority = 99 }; + + itv->base_addr = pci_resource_start(itv->pdev, 0); + itv->enc_mbox.max_mbox = 2; /* the encoder has 3 mailboxes (0-2) */ + itv->dec_mbox.max_mbox = 1; /* the decoder has 2 mailboxes (0-1) */ + + mutex_init(&itv->serialize_lock); + mutex_init(&itv->i2c_bus_lock); + mutex_init(&itv->udma.lock); + + spin_lock_init(&itv->lock); + spin_lock_init(&itv->dma_reg_lock); + + init_kthread_worker(&itv->irq_worker); + itv->irq_worker_task = kthread_run(kthread_worker_fn, &itv->irq_worker, + itv->v4l2_dev.name); + if (IS_ERR(itv->irq_worker_task)) { + IVTV_ERR("Could not create ivtv task\n"); + return -1; + } + /* must use the FIFO scheduler as it is realtime sensitive */ + sched_setscheduler(itv->irq_worker_task, SCHED_FIFO, ¶m); + + init_kthread_work(&itv->irq_work, ivtv_irq_work_handler); + + /* Initial settings */ + itv->cxhdl.port = CX2341X_PORT_MEMORY; + itv->cxhdl.capabilities = CX2341X_CAP_HAS_SLICED_VBI; + init_waitqueue_head(&itv->eos_waitq); + init_waitqueue_head(&itv->event_waitq); + init_waitqueue_head(&itv->vsync_waitq); + init_waitqueue_head(&itv->dma_waitq); + init_timer(&itv->dma_timer); + itv->dma_timer.function = ivtv_unfinished_dma; + itv->dma_timer.data = (unsigned long)itv; + + itv->cur_dma_stream = -1; + itv->cur_pio_stream = -1; + + /* Ctrls */ + itv->speed = 1000; + + /* VBI */ + itv->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE; + itv->vbi.sliced_in = &itv->vbi.in.fmt.sliced; + + /* Init the sg table for osd/yuv output */ + sg_init_table(itv->udma.SGlist, IVTV_DMA_SG_OSD_ENT); + + /* OSD */ + itv->osd_global_alpha_state = 1; + itv->osd_global_alpha = 255; + + /* YUV */ + atomic_set(&itv->yuv_info.next_dma_frame, -1); + itv->yuv_info.lace_mode = ivtv_yuv_mode; + itv->yuv_info.lace_threshold = ivtv_yuv_threshold; + itv->yuv_info.max_frames_buffered = 3; + itv->yuv_info.track_osd = 1; + return 0; +} + +/* Second initialization part. Here the card type has been + autodetected. */ +static void __devinit ivtv_init_struct2(struct ivtv *itv) +{ + int i; + + for (i = 0; i < IVTV_CARD_MAX_VIDEO_INPUTS; i++) + if (itv->card->video_inputs[i].video_type == 0) + break; + itv->nof_inputs = i; + for (i = 0; i < IVTV_CARD_MAX_AUDIO_INPUTS; i++) + if (itv->card->audio_inputs[i].audio_type == 0) + break; + itv->nof_audio_inputs = i; + + if (itv->card->hw_all & IVTV_HW_CX25840) { + itv->vbi.sliced_size = 288; /* multiple of 16, real size = 284 */ + } else { + itv->vbi.sliced_size = 64; /* multiple of 16, real size = 52 */ + } + + /* Find tuner input */ + for (i = 0; i < itv->nof_inputs; i++) { + if (itv->card->video_inputs[i].video_type == + IVTV_CARD_INPUT_VID_TUNER) + break; + } + if (i == itv->nof_inputs) + i = 0; + itv->active_input = i; + itv->audio_input = itv->card->video_inputs[i].audio_index; +} + +static int ivtv_setup_pci(struct ivtv *itv, struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + u16 cmd; + unsigned char pci_latency; + + IVTV_DEBUG_INFO("Enabling pci device\n"); + + if (pci_enable_device(pdev)) { + IVTV_ERR("Can't enable device!\n"); + return -EIO; + } + if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { + IVTV_ERR("No suitable DMA available.\n"); + return -EIO; + } + if (!request_mem_region(itv->base_addr, IVTV_ENCODER_SIZE, "ivtv encoder")) { + IVTV_ERR("Cannot request encoder memory region.\n"); + return -EIO; + } + + if (!request_mem_region(itv->base_addr + IVTV_REG_OFFSET, + IVTV_REG_SIZE, "ivtv registers")) { + IVTV_ERR("Cannot request register memory region.\n"); + release_mem_region(itv->base_addr, IVTV_ENCODER_SIZE); + return -EIO; + } + + if (itv->has_cx23415 && + !request_mem_region(itv->base_addr + IVTV_DECODER_OFFSET, + IVTV_DECODER_SIZE, "ivtv decoder")) { + IVTV_ERR("Cannot request decoder memory region.\n"); + release_mem_region(itv->base_addr, IVTV_ENCODER_SIZE); + release_mem_region(itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + return -EIO; + } + + /* Check for bus mastering */ + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_MASTER)) { + IVTV_DEBUG_INFO("Attempting to enable Bus Mastering\n"); + pci_set_master(pdev); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_MASTER)) { + IVTV_ERR("Bus Mastering is not enabled\n"); + return -ENXIO; + } + } + IVTV_DEBUG_INFO("Bus Mastering Enabled.\n"); + + pci_read_config_byte(pdev, PCI_LATENCY_TIMER, &pci_latency); + + if (pci_latency < 64 && ivtv_pci_latency) { + IVTV_INFO("Unreasonably low latency timer, " + "setting to 64 (was %d)\n", pci_latency); + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 64); + pci_read_config_byte(pdev, PCI_LATENCY_TIMER, &pci_latency); + } + /* This config space value relates to DMA latencies. The + default value 0x8080 is too low however and will lead + to DMA errors. 0xffff is the max value which solves + these problems. */ + pci_write_config_dword(pdev, 0x40, 0xffff); + + IVTV_DEBUG_INFO("%d (rev %d) at %02x:%02x.%x, " + "irq: %d, latency: %d, memory: 0x%llx\n", + pdev->device, pdev->revision, pdev->bus->number, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), + pdev->irq, pci_latency, (u64)itv->base_addr); + + return 0; +} + +static void ivtv_load_and_init_modules(struct ivtv *itv) +{ + u32 hw = itv->card->hw_all; + unsigned i; + + /* check which i2c devices are actually found */ + for (i = 0; i < 32; i++) { + u32 device = 1 << i; + + if (!(device & hw)) + continue; + if (device == IVTV_HW_GPIO || device == IVTV_HW_TVEEPROM) { + /* GPIO and TVEEPROM do not use i2c probing */ + itv->hw_flags |= device; + continue; + } + if (ivtv_i2c_register(itv, i) == 0) + itv->hw_flags |= device; + } + + /* probe for legacy IR controllers that aren't in card definitions */ + if ((itv->hw_flags & IVTV_HW_IR_ANY) == 0) + ivtv_i2c_new_ir_legacy(itv); + + if (itv->card->hw_all & IVTV_HW_CX25840) + itv->sd_video = ivtv_find_hw(itv, IVTV_HW_CX25840); + else if (itv->card->hw_all & IVTV_HW_SAA717X) + itv->sd_video = ivtv_find_hw(itv, IVTV_HW_SAA717X); + else if (itv->card->hw_all & IVTV_HW_SAA7114) + itv->sd_video = ivtv_find_hw(itv, IVTV_HW_SAA7114); + else + itv->sd_video = ivtv_find_hw(itv, IVTV_HW_SAA7115); + itv->sd_audio = ivtv_find_hw(itv, itv->card->hw_audio_ctrl); + itv->sd_muxer = ivtv_find_hw(itv, itv->card->hw_muxer); + + hw = itv->hw_flags; + + if (itv->card->type == IVTV_CARD_CX23416GYC) { + /* Several variations of this card exist, detect which card + type should be used. */ + if ((hw & (IVTV_HW_UPD64031A | IVTV_HW_UPD6408X)) == 0) + itv->card = ivtv_get_card(IVTV_CARD_CX23416GYC_NOGRYCS); + else if ((hw & IVTV_HW_UPD64031A) == 0) + itv->card = ivtv_get_card(IVTV_CARD_CX23416GYC_NOGR); + } + else if (itv->card->type == IVTV_CARD_GV_MVPRX || + itv->card->type == IVTV_CARD_GV_MVPRX2E) { + /* The crystal frequency of GVMVPRX is 24.576MHz */ + v4l2_subdev_call(itv->sd_video, video, s_crystal_freq, + SAA7115_FREQ_24_576_MHZ, SAA7115_FREQ_FL_UCGC); + } + + if (hw & IVTV_HW_CX25840) { + itv->vbi.raw_decoder_line_size = 1444; + itv->vbi.raw_decoder_sav_odd_field = 0x20; + itv->vbi.raw_decoder_sav_even_field = 0x60; + itv->vbi.sliced_decoder_line_size = 272; + itv->vbi.sliced_decoder_sav_odd_field = 0xB0; + itv->vbi.sliced_decoder_sav_even_field = 0xF0; + } + + if (hw & IVTV_HW_SAA711X) { + struct v4l2_dbg_chip_ident v; + + /* determine the exact saa711x model */ + itv->hw_flags &= ~IVTV_HW_SAA711X; + + v.match.type = V4L2_CHIP_MATCH_I2C_DRIVER; + strlcpy(v.match.name, "saa7115", sizeof(v.match.name)); + ivtv_call_hw(itv, IVTV_HW_SAA711X, core, g_chip_ident, &v); + if (v.ident == V4L2_IDENT_SAA7114) { + itv->hw_flags |= IVTV_HW_SAA7114; + /* VBI is not yet supported by the saa7114 driver. */ + itv->v4l2_cap &= ~(V4L2_CAP_SLICED_VBI_CAPTURE|V4L2_CAP_VBI_CAPTURE); + } else { + itv->hw_flags |= IVTV_HW_SAA7115; + } + itv->vbi.raw_decoder_line_size = 1443; + itv->vbi.raw_decoder_sav_odd_field = 0x25; + itv->vbi.raw_decoder_sav_even_field = 0x62; + itv->vbi.sliced_decoder_line_size = 51; + itv->vbi.sliced_decoder_sav_odd_field = 0xAB; + itv->vbi.sliced_decoder_sav_even_field = 0xEC; + } + + if (hw & IVTV_HW_SAA717X) { + itv->vbi.raw_decoder_line_size = 1443; + itv->vbi.raw_decoder_sav_odd_field = 0x25; + itv->vbi.raw_decoder_sav_even_field = 0x62; + itv->vbi.sliced_decoder_line_size = 51; + itv->vbi.sliced_decoder_sav_odd_field = 0xAB; + itv->vbi.sliced_decoder_sav_even_field = 0xEC; + } +} + +static int __devinit ivtv_probe(struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + int retval = 0; + int vbi_buf_size; + struct ivtv *itv; + + itv = kzalloc(sizeof(struct ivtv), GFP_ATOMIC); + if (itv == NULL) + return -ENOMEM; + itv->pdev = pdev; + itv->instance = v4l2_device_set_name(&itv->v4l2_dev, "ivtv", + &ivtv_instance); + + retval = v4l2_device_register(&pdev->dev, &itv->v4l2_dev); + if (retval) { + kfree(itv); + return retval; + } + IVTV_INFO("Initializing card %d\n", itv->instance); + + ivtv_process_options(itv); + if (itv->options.cardtype == -1) { + retval = -ENODEV; + goto err; + } + if (ivtv_init_struct1(itv)) { + retval = -ENOMEM; + goto err; + } + retval = cx2341x_handler_init(&itv->cxhdl, 50); + if (retval) + goto err; + itv->v4l2_dev.ctrl_handler = &itv->cxhdl.hdl; + itv->cxhdl.ops = &ivtv_cxhdl_ops; + itv->cxhdl.priv = itv; + itv->cxhdl.func = ivtv_api_func; + + IVTV_DEBUG_INFO("base addr: 0x%llx\n", (u64)itv->base_addr); + + /* PCI Device Setup */ + retval = ivtv_setup_pci(itv, pdev, pci_id); + if (retval == -EIO) + goto free_worker; + if (retval == -ENXIO) + goto free_mem; + + /* map io memory */ + IVTV_DEBUG_INFO("attempting ioremap at 0x%llx len 0x%08x\n", + (u64)itv->base_addr + IVTV_ENCODER_OFFSET, IVTV_ENCODER_SIZE); + itv->enc_mem = ioremap_nocache(itv->base_addr + IVTV_ENCODER_OFFSET, + IVTV_ENCODER_SIZE); + if (!itv->enc_mem) { + IVTV_ERR("ioremap failed. Can't get a window into CX23415/6 " + "encoder memory\n"); + IVTV_ERR("Each capture card with a CX23415/6 needs 8 MB of " + "vmalloc address space for this window\n"); + IVTV_ERR("Check the output of 'grep Vmalloc /proc/meminfo'\n"); + IVTV_ERR("Use the vmalloc= kernel command line option to set " + "VmallocTotal to a larger value\n"); + retval = -ENOMEM; + goto free_mem; + } + + if (itv->has_cx23415) { + IVTV_DEBUG_INFO("attempting ioremap at 0x%llx len 0x%08x\n", + (u64)itv->base_addr + IVTV_DECODER_OFFSET, IVTV_DECODER_SIZE); + itv->dec_mem = ioremap_nocache(itv->base_addr + IVTV_DECODER_OFFSET, + IVTV_DECODER_SIZE); + if (!itv->dec_mem) { + IVTV_ERR("ioremap failed. Can't get a window into " + "CX23415 decoder memory\n"); + IVTV_ERR("Each capture card with a CX23415 needs 8 MB " + "of vmalloc address space for this window\n"); + IVTV_ERR("Check the output of 'grep Vmalloc " + "/proc/meminfo'\n"); + IVTV_ERR("Use the vmalloc= kernel command line option " + "to set VmallocTotal to a larger value\n"); + retval = -ENOMEM; + goto free_mem; + } + } + else { + itv->dec_mem = itv->enc_mem; + } + + /* map registers memory */ + IVTV_DEBUG_INFO("attempting ioremap at 0x%llx len 0x%08x\n", + (u64)itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + itv->reg_mem = + ioremap_nocache(itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + if (!itv->reg_mem) { + IVTV_ERR("ioremap failed. Can't get a window into CX23415/6 " + "register space\n"); + IVTV_ERR("Each capture card with a CX23415/6 needs 64 kB of " + "vmalloc address space for this window\n"); + IVTV_ERR("Check the output of 'grep Vmalloc /proc/meminfo'\n"); + IVTV_ERR("Use the vmalloc= kernel command line option to set " + "VmallocTotal to a larger value\n"); + retval = -ENOMEM; + goto free_io; + } + + retval = ivtv_gpio_init(itv); + if (retval) + goto free_io; + + /* active i2c */ + IVTV_DEBUG_INFO("activating i2c...\n"); + if (init_ivtv_i2c(itv)) { + IVTV_ERR("Could not initialize i2c\n"); + goto free_io; + } + + if (itv->card->hw_all & IVTV_HW_TVEEPROM) { + /* Based on the model number the cardtype may be changed. + The PCI IDs are not always reliable. */ + ivtv_process_eeprom(itv); + } + if (itv->card->comment) + IVTV_INFO("%s", itv->card->comment); + if (itv->card->v4l2_capabilities == 0) { + /* card was detected but is not supported */ + retval = -ENODEV; + goto free_i2c; + } + + if (itv->std == 0) { + itv->std = V4L2_STD_NTSC_M; + } + + if (itv->options.tuner == -1) { + int i; + + for (i = 0; i < IVTV_CARD_MAX_TUNERS; i++) { + if ((itv->std & itv->card->tuners[i].std) == 0) + continue; + itv->options.tuner = itv->card->tuners[i].tuner; + break; + } + } + /* if no tuner was found, then pick the first tuner in the card list */ + if (itv->options.tuner == -1 && itv->card->tuners[0].std) { + itv->std = itv->card->tuners[0].std; + if (itv->std & V4L2_STD_PAL) + itv->std = V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + else if (itv->std & V4L2_STD_NTSC) + itv->std = V4L2_STD_NTSC_M; + else if (itv->std & V4L2_STD_SECAM) + itv->std = V4L2_STD_SECAM_L; + itv->options.tuner = itv->card->tuners[0].tuner; + } + if (itv->options.radio == -1) + itv->options.radio = (itv->card->radio_input.audio_type != 0); + + /* The card is now fully identified, continue with card-specific + initialization. */ + ivtv_init_struct2(itv); + + ivtv_load_and_init_modules(itv); + + if (itv->std & V4L2_STD_525_60) { + itv->is_60hz = 1; + itv->is_out_60hz = 1; + } else { + itv->is_50hz = 1; + itv->is_out_50hz = 1; + } + + itv->yuv_info.osd_full_w = 720; + itv->yuv_info.osd_full_h = itv->is_out_50hz ? 576 : 480; + itv->yuv_info.v4l2_src_w = itv->yuv_info.osd_full_w; + itv->yuv_info.v4l2_src_h = itv->yuv_info.osd_full_h; + + cx2341x_handler_set_50hz(&itv->cxhdl, itv->is_50hz); + + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_MPG] = 0x08000; + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_PCM] = 0x01200; + itv->stream_buf_size[IVTV_DEC_STREAM_TYPE_MPG] = 0x10000; + itv->stream_buf_size[IVTV_DEC_STREAM_TYPE_YUV] = 0x10000; + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_YUV] = 0x08000; + + /* Setup VBI Raw Size. Should be big enough to hold PAL. + It is possible to switch between PAL and NTSC, so we need to + take the largest size here. */ + /* 1456 is multiple of 16, real size = 1444 */ + itv->vbi.raw_size = 1456; + /* We use a buffer size of 1/2 of the total size needed for a + frame. This is actually very useful, since we now receive + a field at a time and that makes 'compressing' the raw data + down to size by stripping off the SAV codes a lot easier. + Note: having two different buffer sizes prevents standard + switching on the fly. We need to find a better solution... */ + vbi_buf_size = itv->vbi.raw_size * (itv->is_60hz ? 24 : 36) / 2; + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_VBI] = vbi_buf_size; + itv->stream_buf_size[IVTV_DEC_STREAM_TYPE_VBI] = sizeof(struct v4l2_sliced_vbi_data) * 36; + + if (itv->options.radio > 0) + itv->v4l2_cap |= V4L2_CAP_RADIO; + + if (itv->options.tuner > -1) { + struct tuner_setup setup; + + setup.addr = ADDR_UNSET; + setup.type = itv->options.tuner; + setup.mode_mask = T_ANALOG_TV; /* matches TV tuners */ + if (itv->options.radio > 0) + setup.mode_mask |= T_RADIO; + setup.tuner_callback = (setup.type == TUNER_XC2028) ? + ivtv_reset_tuner_gpio : NULL; + ivtv_call_all(itv, tuner, s_type_addr, &setup); + if (setup.type == TUNER_XC2028) { + static struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64, + }; + struct v4l2_priv_tun_config cfg = { + .tuner = itv->options.tuner, + .priv = &ctrl, + }; + ivtv_call_all(itv, tuner, s_config, &cfg); + } + } + + /* The tuner is fixed to the standard. The other inputs (e.g. S-Video) + are not. */ + itv->tuner_std = itv->std; + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + struct v4l2_ctrl_handler *hdl = itv->v4l2_dev.ctrl_handler; + + itv->ctrl_pts = v4l2_ctrl_new_std(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_VIDEO_DEC_PTS, 0, 0, 0, 0); + itv->ctrl_frame = v4l2_ctrl_new_std(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_VIDEO_DEC_FRAME, 0, 0, 0, 0); + /* Note: V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO is not supported, + mask that menu item. */ + itv->ctrl_audio_playback = + v4l2_ctrl_new_std_menu(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_SWAPPED_STEREO, + 1 << V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_STEREO); + itv->ctrl_audio_multilingual_playback = + v4l2_ctrl_new_std_menu(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_SWAPPED_STEREO, + 1 << V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_LEFT); + if (hdl->error) { + retval = hdl->error; + goto free_i2c; + } + v4l2_ctrl_cluster(2, &itv->ctrl_pts); + v4l2_ctrl_cluster(2, &itv->ctrl_audio_playback); + ivtv_call_all(itv, video, s_std_output, itv->std); + /* Turn off the output signal. The mpeg decoder is not yet + active so without this you would get a green image until the + mpeg decoder becomes active. */ + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 0); + } + + /* clear interrupt mask, effectively disabling interrupts */ + ivtv_set_irq_mask(itv, 0xffffffff); + + /* Register IRQ */ + retval = request_irq(itv->pdev->irq, ivtv_irq_handler, + IRQF_SHARED | IRQF_DISABLED, itv->v4l2_dev.name, (void *)itv); + if (retval) { + IVTV_ERR("Failed to register irq %d\n", retval); + goto free_i2c; + } + + retval = ivtv_streams_setup(itv); + if (retval) { + IVTV_ERR("Error %d setting up streams\n", retval); + goto free_irq; + } + retval = ivtv_streams_register(itv); + if (retval) { + IVTV_ERR("Error %d registering devices\n", retval); + goto free_streams; + } + IVTV_INFO("Initialized card: %s\n", itv->card_name); + + /* Load ivtv submodules (ivtv-alsa) */ + request_modules(itv); + return 0; + +free_streams: + ivtv_streams_cleanup(itv, 1); +free_irq: + free_irq(itv->pdev->irq, (void *)itv); +free_i2c: + v4l2_ctrl_handler_free(&itv->cxhdl.hdl); + exit_ivtv_i2c(itv); +free_io: + ivtv_iounmap(itv); +free_mem: + release_mem_region(itv->base_addr, IVTV_ENCODER_SIZE); + release_mem_region(itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + if (itv->has_cx23415) + release_mem_region(itv->base_addr + IVTV_DECODER_OFFSET, IVTV_DECODER_SIZE); +free_worker: + kthread_stop(itv->irq_worker_task); +err: + if (retval == 0) + retval = -ENODEV; + IVTV_ERR("Error %d on initialization\n", retval); + + v4l2_device_unregister(&itv->v4l2_dev); + kfree(itv); + return retval; +} + +int ivtv_init_on_first_open(struct ivtv *itv) +{ + struct v4l2_frequency vf; + /* Needed to call ioctls later */ + struct ivtv_open_id fh; + int fw_retry_count = 3; + int video_input; + + fh.itv = itv; + fh.type = IVTV_ENC_STREAM_TYPE_MPG; + + if (test_bit(IVTV_F_I_FAILED, &itv->i_flags)) + return -ENXIO; + + if (test_and_set_bit(IVTV_F_I_INITED, &itv->i_flags)) + return 0; + + while (--fw_retry_count > 0) { + /* load firmware */ + if (ivtv_firmware_init(itv) == 0) + break; + if (fw_retry_count > 1) + IVTV_WARN("Retry loading firmware\n"); + } + + if (fw_retry_count == 0) { + set_bit(IVTV_F_I_FAILED, &itv->i_flags); + return -ENXIO; + } + + /* Try and get firmware versions */ + IVTV_DEBUG_INFO("Getting firmware version..\n"); + ivtv_firmware_versions(itv); + + if (itv->card->hw_all & IVTV_HW_CX25840) + v4l2_subdev_call(itv->sd_video, core, load_fw); + + vf.tuner = 0; + vf.type = V4L2_TUNER_ANALOG_TV; + vf.frequency = 6400; /* the tuner 'baseline' frequency */ + + /* Set initial frequency. For PAL/SECAM broadcasts no + 'default' channel exists AFAIK. */ + if (itv->std == V4L2_STD_NTSC_M_JP) { + vf.frequency = 1460; /* ch. 1 91250*16/1000 */ + } + else if (itv->std & V4L2_STD_NTSC_M) { + vf.frequency = 1076; /* ch. 4 67250*16/1000 */ + } + + video_input = itv->active_input; + itv->active_input++; /* Force update of input */ + ivtv_s_input(NULL, &fh, video_input); + + /* Let the VIDIOC_S_STD ioctl do all the work, keeps the code + in one place. */ + itv->std++; /* Force full standard initialization */ + itv->std_out = itv->std; + ivtv_s_frequency(NULL, &fh, &vf); + + if (itv->card->v4l2_capabilities & V4L2_CAP_VIDEO_OUTPUT) { + /* Turn on the TV-out: ivtv_init_mpeg_decoder() initializes + the mpeg decoder so now the saa7127 receives a proper + signal. */ + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 1); + ivtv_init_mpeg_decoder(itv); + } + + /* On a cx23416 this seems to be able to enable DMA to the chip? */ + if (!itv->has_cx23415) + write_reg_sync(0x03, IVTV_REG_DMACONTROL); + + ivtv_s_std_enc(itv, &itv->tuner_std); + + /* Default interrupts enabled. For the PVR350 this includes the + decoder VSYNC interrupt, which is always on. It is not only used + during decoding but also by the OSD. + Some old PVR250 cards had a cx23415, so testing for that is too + general. Instead test if the card has video output capability. */ + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_INIT | IVTV_IRQ_DEC_VSYNC); + ivtv_set_osd_alpha(itv); + ivtv_s_std_dec(itv, &itv->tuner_std); + } else { + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_INIT); + } + + /* Setup initial controls */ + cx2341x_handler_setup(&itv->cxhdl); + return 0; +} + +static void ivtv_remove(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); + struct ivtv *itv = to_ivtv(v4l2_dev); + int i; + + IVTV_DEBUG_INFO("Removing card\n"); + + flush_request_modules(itv); + + if (test_bit(IVTV_F_I_INITED, &itv->i_flags)) { + /* Stop all captures */ + IVTV_DEBUG_INFO("Stopping all streams\n"); + if (atomic_read(&itv->capturing) > 0) + ivtv_stop_all_captures(itv); + + /* Stop all decoding */ + IVTV_DEBUG_INFO("Stopping decoding\n"); + + /* Turn off the TV-out */ + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 0); + if (atomic_read(&itv->decoding) > 0) { + int type; + + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) + type = IVTV_DEC_STREAM_TYPE_YUV; + else + type = IVTV_DEC_STREAM_TYPE_MPG; + ivtv_stop_v4l2_decode_stream(&itv->streams[type], + V4L2_DEC_CMD_STOP_TO_BLACK | V4L2_DEC_CMD_STOP_IMMEDIATELY, 0); + } + ivtv_halt_firmware(itv); + } + + /* Interrupts */ + ivtv_set_irq_mask(itv, 0xffffffff); + del_timer_sync(&itv->dma_timer); + + /* Kill irq worker */ + flush_kthread_worker(&itv->irq_worker); + kthread_stop(itv->irq_worker_task); + + ivtv_streams_cleanup(itv, 1); + ivtv_udma_free(itv); + + v4l2_ctrl_handler_free(&itv->cxhdl.hdl); + + exit_ivtv_i2c(itv); + + free_irq(itv->pdev->irq, (void *)itv); + ivtv_iounmap(itv); + + release_mem_region(itv->base_addr, IVTV_ENCODER_SIZE); + release_mem_region(itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + if (itv->has_cx23415) + release_mem_region(itv->base_addr + IVTV_DECODER_OFFSET, IVTV_DECODER_SIZE); + + pci_disable_device(itv->pdev); + for (i = 0; i < IVTV_VBI_FRAMES; i++) + kfree(itv->vbi.sliced_mpeg_data[i]); + + printk(KERN_INFO "ivtv: Removed %s\n", itv->card_name); + + v4l2_device_unregister(&itv->v4l2_dev); + kfree(itv); +} + +/* define a pci_driver for card detection */ +static struct pci_driver ivtv_pci_driver = { + .name = "ivtv", + .id_table = ivtv_pci_tbl, + .probe = ivtv_probe, + .remove = ivtv_remove, +}; + +static int __init module_start(void) +{ + printk(KERN_INFO "ivtv: Start initialization, version %s\n", IVTV_VERSION); + + /* Validate parameters */ + if (ivtv_first_minor < 0 || ivtv_first_minor >= IVTV_MAX_CARDS) { + printk(KERN_ERR "ivtv: Exiting, ivtv_first_minor must be between 0 and %d\n", + IVTV_MAX_CARDS - 1); + return -1; + } + + if (ivtv_debug < 0 || ivtv_debug > 2047) { + ivtv_debug = 0; + printk(KERN_INFO "ivtv: Debug value must be >= 0 and <= 2047\n"); + } + + if (pci_register_driver(&ivtv_pci_driver)) { + printk(KERN_ERR "ivtv: Error detecting PCI card\n"); + return -ENODEV; + } + printk(KERN_INFO "ivtv: End initialization\n"); + return 0; +} + +static void __exit module_cleanup(void) +{ + pci_unregister_driver(&ivtv_pci_driver); +} + +/* Note: These symbols are exported because they are used by the ivtvfb + framebuffer module and an infrared module for the IR-blaster. */ +EXPORT_SYMBOL(ivtv_set_irq_mask); +EXPORT_SYMBOL(ivtv_api); +EXPORT_SYMBOL(ivtv_vapi); +EXPORT_SYMBOL(ivtv_vapi_result); +EXPORT_SYMBOL(ivtv_clear_irq_mask); +EXPORT_SYMBOL(ivtv_debug); +#ifdef CONFIG_VIDEO_ADV_DEBUG +EXPORT_SYMBOL(ivtv_fw_debug); +#endif +EXPORT_SYMBOL(ivtv_reset_ir_gpio); +EXPORT_SYMBOL(ivtv_udma_setup); +EXPORT_SYMBOL(ivtv_udma_unmap); +EXPORT_SYMBOL(ivtv_udma_alloc); +EXPORT_SYMBOL(ivtv_udma_prepare); +EXPORT_SYMBOL(ivtv_init_on_first_open); +EXPORT_SYMBOL(ivtv_firmware_check); + +module_init(module_start); +module_exit(module_cleanup); diff --git a/drivers/media/pci/ivtv/ivtv-driver.h b/drivers/media/pci/ivtv/ivtv-driver.h new file mode 100644 index 000000000000..bc309f42c8ed --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-driver.h @@ -0,0 +1,850 @@ +/* + ivtv driver internal defines and structures + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef IVTV_DRIVER_H +#define IVTV_DRIVER_H + +/* Internal header for ivtv project: + * Driver for the cx23415/6 chip. + * Author: Kevin Thayer (nufan_wfk at yahoo.com) + * License: GPL + * http://www.ivtvdriver.org + * + * ----- + * MPG600/MPG160 support by T.Adachi + * and Takeru KOMORIYA + * + * AVerMedia M179 GPIO info by Chris Pinkham + * using information provided by Jiun-Kuei Jung @ AVerMedia. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Memory layout */ +#define IVTV_ENCODER_OFFSET 0x00000000 +#define IVTV_ENCODER_SIZE 0x00800000 /* Total size is 0x01000000, but only first half is used */ +#define IVTV_DECODER_OFFSET 0x01000000 +#define IVTV_DECODER_SIZE 0x00800000 /* Total size is 0x01000000, but only first half is used */ +#define IVTV_REG_OFFSET 0x02000000 +#define IVTV_REG_SIZE 0x00010000 + +/* Maximum ivtv driver instances. Some people have a huge number of + capture cards, so set this to a high value. */ +#define IVTV_MAX_CARDS 32 + +#define IVTV_ENC_STREAM_TYPE_MPG 0 +#define IVTV_ENC_STREAM_TYPE_YUV 1 +#define IVTV_ENC_STREAM_TYPE_VBI 2 +#define IVTV_ENC_STREAM_TYPE_PCM 3 +#define IVTV_ENC_STREAM_TYPE_RAD 4 +#define IVTV_DEC_STREAM_TYPE_MPG 5 +#define IVTV_DEC_STREAM_TYPE_VBI 6 +#define IVTV_DEC_STREAM_TYPE_VOUT 7 +#define IVTV_DEC_STREAM_TYPE_YUV 8 +#define IVTV_MAX_STREAMS 9 + +#define IVTV_DMA_SG_OSD_ENT (2883584/PAGE_SIZE) /* sg entities */ + +/* DMA Registers */ +#define IVTV_REG_DMAXFER (0x0000) +#define IVTV_REG_DMASTATUS (0x0004) +#define IVTV_REG_DECDMAADDR (0x0008) +#define IVTV_REG_ENCDMAADDR (0x000c) +#define IVTV_REG_DMACONTROL (0x0010) +#define IVTV_REG_IRQSTATUS (0x0040) +#define IVTV_REG_IRQMASK (0x0048) + +/* Setup Registers */ +#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8) +#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC) +#define IVTV_REG_DEC_SDRAM_REFRESH (0x08F8) +#define IVTV_REG_DEC_SDRAM_PRECHARGE (0x08FC) +#define IVTV_REG_VDM (0x2800) +#define IVTV_REG_AO (0x2D00) +#define IVTV_REG_BYTEFLUSH (0x2D24) +#define IVTV_REG_SPU (0x9050) +#define IVTV_REG_HW_BLOCKS (0x9054) +#define IVTV_REG_VPU (0x9058) +#define IVTV_REG_APU (0xA064) + +/* Other registers */ +#define IVTV_REG_DEC_LINE_FIELD (0x28C0) + +/* debugging */ +extern int ivtv_debug; +#ifdef CONFIG_VIDEO_ADV_DEBUG +extern int ivtv_fw_debug; +#endif + +#define IVTV_DBGFLG_WARN (1 << 0) +#define IVTV_DBGFLG_INFO (1 << 1) +#define IVTV_DBGFLG_MB (1 << 2) +#define IVTV_DBGFLG_IOCTL (1 << 3) +#define IVTV_DBGFLG_FILE (1 << 4) +#define IVTV_DBGFLG_DMA (1 << 5) +#define IVTV_DBGFLG_IRQ (1 << 6) +#define IVTV_DBGFLG_DEC (1 << 7) +#define IVTV_DBGFLG_YUV (1 << 8) +#define IVTV_DBGFLG_I2C (1 << 9) +/* Flag to turn on high volume debugging */ +#define IVTV_DBGFLG_HIGHVOL (1 << 10) + +#define IVTV_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & ivtv_debug) \ + v4l2_info(&itv->v4l2_dev, " " type ": " fmt , ##args); \ + } while (0) +#define IVTV_DEBUG_WARN(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_WARN, "warn", fmt , ## args) +#define IVTV_DEBUG_INFO(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_INFO, "info", fmt , ## args) +#define IVTV_DEBUG_MB(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_MB, "mb", fmt , ## args) +#define IVTV_DEBUG_DMA(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_DMA, "dma", fmt , ## args) +#define IVTV_DEBUG_IOCTL(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_IOCTL, "ioctl", fmt , ## args) +#define IVTV_DEBUG_FILE(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_FILE, "file", fmt , ## args) +#define IVTV_DEBUG_I2C(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_I2C, "i2c", fmt , ## args) +#define IVTV_DEBUG_IRQ(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_IRQ, "irq", fmt , ## args) +#define IVTV_DEBUG_DEC(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_DEC, "dec", fmt , ## args) +#define IVTV_DEBUG_YUV(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_YUV, "yuv", fmt , ## args) + +#define IVTV_DEBUG_HIGH_VOL(x, type, fmt, args...) \ + do { \ + if (((x) & ivtv_debug) && (ivtv_debug & IVTV_DBGFLG_HIGHVOL)) \ + v4l2_info(&itv->v4l2_dev, " " type ": " fmt , ##args); \ + } while (0) +#define IVTV_DEBUG_HI_WARN(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_WARN, "warn", fmt , ## args) +#define IVTV_DEBUG_HI_INFO(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_INFO, "info", fmt , ## args) +#define IVTV_DEBUG_HI_MB(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_MB, "mb", fmt , ## args) +#define IVTV_DEBUG_HI_DMA(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_DMA, "dma", fmt , ## args) +#define IVTV_DEBUG_HI_IOCTL(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_IOCTL, "ioctl", fmt , ## args) +#define IVTV_DEBUG_HI_FILE(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_FILE, "file", fmt , ## args) +#define IVTV_DEBUG_HI_I2C(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_I2C, "i2c", fmt , ## args) +#define IVTV_DEBUG_HI_IRQ(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_IRQ, "irq", fmt , ## args) +#define IVTV_DEBUG_HI_DEC(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_DEC, "dec", fmt , ## args) +#define IVTV_DEBUG_HI_YUV(fmt, args...) IVTV_DEBUG_HIGH_VOL(IVTV_DBGFLG_YUV, "yuv", fmt , ## args) + +/* Standard kernel messages */ +#define IVTV_ERR(fmt, args...) v4l2_err(&itv->v4l2_dev, fmt , ## args) +#define IVTV_WARN(fmt, args...) v4l2_warn(&itv->v4l2_dev, fmt , ## args) +#define IVTV_INFO(fmt, args...) v4l2_info(&itv->v4l2_dev, fmt , ## args) + +/* output modes (cx23415 only) */ +#define OUT_NONE 0 +#define OUT_MPG 1 +#define OUT_YUV 2 +#define OUT_UDMA_YUV 3 +#define OUT_PASSTHROUGH 4 + +#define IVTV_MAX_PGM_INDEX (400) + +/* Default I2C SCL period in microseconds */ +#define IVTV_DEFAULT_I2C_CLOCK_PERIOD 20 + +struct ivtv_options { + int kilobytes[IVTV_MAX_STREAMS]; /* size in kilobytes of each stream */ + int cardtype; /* force card type on load */ + int tuner; /* set tuner on load */ + int radio; /* enable/disable radio */ + int newi2c; /* new I2C algorithm */ + int i2c_clock_period; /* period of SCL for I2C bus */ +}; + +/* ivtv-specific mailbox template */ +struct ivtv_mailbox { + u32 flags; + u32 cmd; + u32 retval; + u32 timeout; + u32 data[CX2341X_MBOX_MAX_DATA]; +}; + +struct ivtv_api_cache { + unsigned long last_jiffies; /* when last command was issued */ + u32 data[CX2341X_MBOX_MAX_DATA]; /* last sent api data */ +}; + +struct ivtv_mailbox_data { + volatile struct ivtv_mailbox __iomem *mbox; + /* Bits 0-2 are for the encoder mailboxes, 0-1 are for the decoder mailboxes. + If the bit is set, then the corresponding mailbox is in use by the driver. */ + unsigned long busy; + u8 max_mbox; +}; + +/* per-buffer bit flags */ +#define IVTV_F_B_NEED_BUF_SWAP (1 << 0) /* this buffer should be byte swapped */ + +/* per-stream, s_flags */ +#define IVTV_F_S_DMA_PENDING 0 /* this stream has pending DMA */ +#define IVTV_F_S_DMA_HAS_VBI 1 /* the current DMA request also requests VBI data */ +#define IVTV_F_S_NEEDS_DATA 2 /* this decoding stream needs more data */ + +#define IVTV_F_S_CLAIMED 3 /* this stream is claimed */ +#define IVTV_F_S_STREAMING 4 /* the fw is decoding/encoding this stream */ +#define IVTV_F_S_INTERNAL_USE 5 /* this stream is used internally (sliced VBI processing) */ +#define IVTV_F_S_PASSTHROUGH 6 /* this stream is in passthrough mode */ +#define IVTV_F_S_STREAMOFF 7 /* signal end of stream EOS */ +#define IVTV_F_S_APPL_IO 8 /* this stream is used read/written by an application */ + +#define IVTV_F_S_PIO_PENDING 9 /* this stream has pending PIO */ +#define IVTV_F_S_PIO_HAS_VBI 1 /* the current PIO request also requests VBI data */ + +/* per-ivtv, i_flags */ +#define IVTV_F_I_DMA 0 /* DMA in progress */ +#define IVTV_F_I_UDMA 1 /* UDMA in progress */ +#define IVTV_F_I_UDMA_PENDING 2 /* UDMA pending */ +#define IVTV_F_I_SPEED_CHANGE 3 /* a speed change is in progress */ +#define IVTV_F_I_EOS 4 /* end of encoder stream reached */ +#define IVTV_F_I_RADIO_USER 5 /* the radio tuner is selected */ +#define IVTV_F_I_DIG_RST 6 /* reset digitizer */ +#define IVTV_F_I_DEC_YUV 7 /* YUV instead of MPG is being decoded */ +#define IVTV_F_I_UPDATE_CC 9 /* CC should be updated */ +#define IVTV_F_I_UPDATE_WSS 10 /* WSS should be updated */ +#define IVTV_F_I_UPDATE_VPS 11 /* VPS should be updated */ +#define IVTV_F_I_DECODING_YUV 12 /* this stream is YUV frame decoding */ +#define IVTV_F_I_ENC_PAUSED 13 /* the encoder is paused */ +#define IVTV_F_I_VALID_DEC_TIMINGS 14 /* last_dec_timing is valid */ +#define IVTV_F_I_HAVE_WORK 15 /* used in the interrupt handler: there is work to be done */ +#define IVTV_F_I_WORK_HANDLER_VBI 16 /* there is work to be done for VBI */ +#define IVTV_F_I_WORK_HANDLER_YUV 17 /* there is work to be done for YUV */ +#define IVTV_F_I_WORK_HANDLER_PIO 18 /* there is work to be done for PIO */ +#define IVTV_F_I_PIO 19 /* PIO in progress */ +#define IVTV_F_I_DEC_PAUSED 20 /* the decoder is paused */ +#define IVTV_F_I_INITED 21 /* set after first open */ +#define IVTV_F_I_FAILED 22 /* set if first open failed */ +#define IVTV_F_I_WORK_HANDLER_PCM 23 /* there is work to be done for PCM */ + +/* Event notifications */ +#define IVTV_F_I_EV_DEC_STOPPED 28 /* decoder stopped event */ +#define IVTV_F_I_EV_VSYNC 29 /* VSYNC event */ +#define IVTV_F_I_EV_VSYNC_FIELD 30 /* VSYNC event field (0 = first, 1 = second field) */ +#define IVTV_F_I_EV_VSYNC_ENABLED 31 /* VSYNC event enabled */ + +/* Scatter-Gather array element, used in DMA transfers */ +struct ivtv_sg_element { + __le32 src; + __le32 dst; + __le32 size; +}; + +struct ivtv_sg_host_element { + u32 src; + u32 dst; + u32 size; +}; + +struct ivtv_user_dma { + struct mutex lock; + int page_count; + struct page *map[IVTV_DMA_SG_OSD_ENT]; + /* Needed when dealing with highmem userspace buffers */ + struct page *bouncemap[IVTV_DMA_SG_OSD_ENT]; + + /* Base Dev SG Array for cx23415/6 */ + struct ivtv_sg_element SGarray[IVTV_DMA_SG_OSD_ENT]; + dma_addr_t SG_handle; + int SG_length; + + /* SG List of Buffers */ + struct scatterlist SGlist[IVTV_DMA_SG_OSD_ENT]; +}; + +struct ivtv_dma_page_info { + unsigned long uaddr; + unsigned long first; + unsigned long last; + unsigned int offset; + unsigned int tail; + int page_count; +}; + +struct ivtv_buffer { + struct list_head list; + dma_addr_t dma_handle; + unsigned short b_flags; + unsigned short dma_xfer_cnt; + char *buf; + u32 bytesused; + u32 readpos; +}; + +struct ivtv_queue { + struct list_head list; /* the list of buffers in this queue */ + u32 buffers; /* number of buffers in this queue */ + u32 length; /* total number of bytes of available buffer space */ + u32 bytesused; /* total number of bytes used in this queue */ +}; + +struct ivtv; /* forward reference */ + +struct ivtv_stream { + /* These first four fields are always set, even if the stream + is not actually created. */ + struct video_device *vdev; /* NULL when stream not created */ + struct ivtv *itv; /* for ease of use */ + const char *name; /* name of the stream */ + int type; /* stream type */ + u32 caps; /* V4L2 capabilities */ + + struct v4l2_fh *fh; /* pointer to the streaming filehandle */ + spinlock_t qlock; /* locks access to the queues */ + unsigned long s_flags; /* status flags, see above */ + int dma; /* can be PCI_DMA_TODEVICE, PCI_DMA_FROMDEVICE or PCI_DMA_NONE */ + u32 pending_offset; + u32 pending_backup; + u64 pending_pts; + + u32 dma_offset; + u32 dma_backup; + u64 dma_pts; + + int subtype; + wait_queue_head_t waitq; + u32 dma_last_offset; + + /* Buffer Stats */ + u32 buffers; + u32 buf_size; + u32 buffers_stolen; + + /* Buffer Queues */ + struct ivtv_queue q_free; /* free buffers */ + struct ivtv_queue q_full; /* full buffers */ + struct ivtv_queue q_io; /* waiting for I/O */ + struct ivtv_queue q_dma; /* waiting for DMA */ + struct ivtv_queue q_predma; /* waiting for DMA */ + + /* DMA xfer counter, buffers belonging to the same DMA + xfer will have the same dma_xfer_cnt. */ + u16 dma_xfer_cnt; + + /* Base Dev SG Array for cx23415/6 */ + struct ivtv_sg_host_element *sg_pending; + struct ivtv_sg_host_element *sg_processing; + struct ivtv_sg_element *sg_dma; + dma_addr_t sg_handle; + int sg_pending_size; + int sg_processing_size; + int sg_processed; + + /* SG List of Buffers */ + struct scatterlist *SGlist; +}; + +struct ivtv_open_id { + struct v4l2_fh fh; + int type; /* stream type */ + int yuv_frames; /* 1: started OUT_UDMA_YUV output mode */ + struct ivtv *itv; +}; + +static inline struct ivtv_open_id *fh2id(struct v4l2_fh *fh) +{ + return container_of(fh, struct ivtv_open_id, fh); +} + +struct yuv_frame_info +{ + u32 update; + s32 src_x; + s32 src_y; + u32 src_w; + u32 src_h; + s32 dst_x; + s32 dst_y; + u32 dst_w; + u32 dst_h; + s32 pan_x; + s32 pan_y; + u32 vis_w; + u32 vis_h; + u32 interlaced_y; + u32 interlaced_uv; + s32 tru_x; + u32 tru_w; + u32 tru_h; + u32 offset_y; + s32 lace_mode; + u32 sync_field; + u32 delay; + u32 interlaced; +}; + +#define IVTV_YUV_MODE_INTERLACED 0x00 +#define IVTV_YUV_MODE_PROGRESSIVE 0x01 +#define IVTV_YUV_MODE_AUTO 0x02 +#define IVTV_YUV_MODE_MASK 0x03 + +#define IVTV_YUV_SYNC_EVEN 0x00 +#define IVTV_YUV_SYNC_ODD 0x04 +#define IVTV_YUV_SYNC_MASK 0x04 + +#define IVTV_YUV_BUFFERS 8 + +struct yuv_playback_info +{ + u32 reg_2834; + u32 reg_2838; + u32 reg_283c; + u32 reg_2840; + u32 reg_2844; + u32 reg_2848; + u32 reg_2854; + u32 reg_285c; + u32 reg_2864; + + u32 reg_2870; + u32 reg_2874; + u32 reg_2890; + u32 reg_2898; + u32 reg_289c; + + u32 reg_2918; + u32 reg_291c; + u32 reg_2920; + u32 reg_2924; + u32 reg_2928; + u32 reg_292c; + u32 reg_2930; + + u32 reg_2934; + + u32 reg_2938; + u32 reg_293c; + u32 reg_2940; + u32 reg_2944; + u32 reg_2948; + u32 reg_294c; + u32 reg_2950; + u32 reg_2954; + u32 reg_2958; + u32 reg_295c; + u32 reg_2960; + u32 reg_2964; + u32 reg_2968; + u32 reg_296c; + + u32 reg_2970; + + int v_filter_1; + int v_filter_2; + int h_filter; + + u8 track_osd; /* Should yuv output track the OSD size & position */ + + u32 osd_x_offset; + u32 osd_y_offset; + + u32 osd_x_pan; + u32 osd_y_pan; + + u32 osd_vis_w; + u32 osd_vis_h; + + u32 osd_full_w; + u32 osd_full_h; + + int decode_height; + + int lace_mode; + int lace_threshold; + int lace_sync_field; + + atomic_t next_dma_frame; + atomic_t next_fill_frame; + + u32 yuv_forced_update; + int update_frame; + + u8 fields_lapsed; /* Counter used when delaying a frame */ + + struct yuv_frame_info new_frame_info[IVTV_YUV_BUFFERS]; + struct yuv_frame_info old_frame_info; + struct yuv_frame_info old_frame_info_args; + + void *blanking_ptr; + dma_addr_t blanking_dmaptr; + + int stream_size; + + u8 draw_frame; /* PVR350 buffer to draw into */ + u8 max_frames_buffered; /* Maximum number of frames to buffer */ + + struct v4l2_rect main_rect; + u32 v4l2_src_w; + u32 v4l2_src_h; + + u8 running; /* Have any frames been displayed */ +}; + +#define IVTV_VBI_FRAMES 32 + +/* VBI data */ +struct vbi_cc { + u8 odd[2]; /* two-byte payload of odd field */ + u8 even[2]; /* two-byte payload of even field */; +}; + +struct vbi_vps { + u8 data[5]; /* five-byte VPS payload */ +}; + +struct vbi_info { + /* VBI general data, does not change during streaming */ + + u32 raw_decoder_line_size; /* raw VBI line size from digitizer */ + u8 raw_decoder_sav_odd_field; /* raw VBI Start Active Video digitizer code of odd field */ + u8 raw_decoder_sav_even_field; /* raw VBI Start Active Video digitizer code of even field */ + u32 sliced_decoder_line_size; /* sliced VBI line size from digitizer */ + u8 sliced_decoder_sav_odd_field; /* sliced VBI Start Active Video digitizer code of odd field */ + u8 sliced_decoder_sav_even_field; /* sliced VBI Start Active Video digitizer code of even field */ + + u32 start[2]; /* start of first VBI line in the odd/even fields */ + u32 count; /* number of VBI lines per field */ + u32 raw_size; /* size of raw VBI line from the digitizer */ + u32 sliced_size; /* size of sliced VBI line from the digitizer */ + + u32 dec_start; /* start in decoder memory of VBI re-insertion buffers */ + u32 enc_start; /* start in encoder memory of VBI capture buffers */ + u32 enc_size; /* size of VBI capture area */ + int fpi; /* number of VBI frames per interrupt */ + + struct v4l2_format in; /* current VBI capture format */ + struct v4l2_sliced_vbi_format *sliced_in; /* convenience pointer to sliced struct in vbi.in union */ + int insert_mpeg; /* if non-zero, then embed VBI data in MPEG stream */ + + /* Raw VBI compatibility hack */ + + u32 frame; /* frame counter hack needed for backwards compatibility + of old VBI software */ + + /* Sliced VBI output data */ + + struct vbi_cc cc_payload[256]; /* sliced VBI CC payload array: it is an array to + prevent dropping CC data if they couldn't be + processed fast enough */ + int cc_payload_idx; /* index in cc_payload */ + u8 cc_missing_cnt; /* counts number of frames without CC for passthrough mode */ + int wss_payload; /* sliced VBI WSS payload */ + u8 wss_missing_cnt; /* counts number of frames without WSS for passthrough mode */ + struct vbi_vps vps_payload; /* sliced VBI VPS payload */ + + /* Sliced VBI capture data */ + + struct v4l2_sliced_vbi_data sliced_data[36]; /* sliced VBI storage for VBI encoder stream */ + struct v4l2_sliced_vbi_data sliced_dec_data[36];/* sliced VBI storage for VBI decoder stream */ + + /* VBI Embedding data */ + + /* Buffer for VBI data inserted into MPEG stream. + The first byte is a dummy byte that's never used. + The next 16 bytes contain the MPEG header for the VBI data, + the remainder is the actual VBI data. + The max size accepted by the MPEG VBI reinsertion turns out + to be 1552 bytes, which happens to be 4 + (1 + 42) * (2 * 18) bytes, + where 4 is a four byte header, 42 is the max sliced VBI payload, 1 is + a single line header byte and 2 * 18 is the number of VBI lines per frame. + + However, it seems that the data must be 1K aligned, so we have to + pad the data until the 1 or 2 K boundary. + + This pointer array will allocate 2049 bytes to store each VBI frame. */ + u8 *sliced_mpeg_data[IVTV_VBI_FRAMES]; + u32 sliced_mpeg_size[IVTV_VBI_FRAMES]; + struct ivtv_buffer sliced_mpeg_buf; /* temporary buffer holding data from sliced_mpeg_data */ + u32 inserted_frame; /* index in sliced_mpeg_size of next sliced data + to be inserted in the MPEG stream */ +}; + +/* forward declaration of struct defined in ivtv-cards.h */ +struct ivtv_card; + +/* Struct to hold info about ivtv cards */ +struct ivtv { + /* General fixed card data */ + struct pci_dev *pdev; /* PCI device */ + const struct ivtv_card *card; /* card information */ + const char *card_name; /* full name of the card */ + const struct ivtv_card_tuner_i2c *card_i2c; /* i2c addresses to probe for tuner */ + u8 has_cx23415; /* 1 if it is a cx23415 based card, 0 for cx23416 */ + u8 pvr150_workaround; /* 1 if the cx25840 needs to workaround a PVR150 bug */ + u8 nof_inputs; /* number of video inputs */ + u8 nof_audio_inputs; /* number of audio inputs */ + u32 v4l2_cap; /* V4L2 capabilities of card */ + u32 hw_flags; /* hardware description of the board */ + v4l2_std_id tuner_std; /* the norm of the card's tuner (fixed) */ + struct v4l2_subdev *sd_video; /* controlling video decoder subdev */ + struct v4l2_subdev *sd_audio; /* controlling audio subdev */ + struct v4l2_subdev *sd_muxer; /* controlling audio muxer subdev */ + resource_size_t base_addr; /* PCI resource base address */ + volatile void __iomem *enc_mem; /* pointer to mapped encoder memory */ + volatile void __iomem *dec_mem; /* pointer to mapped decoder memory */ + volatile void __iomem *reg_mem; /* pointer to mapped registers */ + struct ivtv_options options; /* user options */ + + struct v4l2_device v4l2_dev; + struct cx2341x_handler cxhdl; + struct { + /* PTS/Frame count control cluster */ + struct v4l2_ctrl *ctrl_pts; + struct v4l2_ctrl *ctrl_frame; + }; + struct { + /* Audio Playback control cluster */ + struct v4l2_ctrl *ctrl_audio_playback; + struct v4l2_ctrl *ctrl_audio_multilingual_playback; + }; + struct v4l2_ctrl_handler hdl_gpio; + struct v4l2_subdev sd_gpio; /* GPIO sub-device */ + u16 instance; + + /* High-level state info */ + unsigned long i_flags; /* global ivtv flags */ + u8 is_50hz; /* 1 if the current capture standard is 50 Hz */ + u8 is_60hz /* 1 if the current capture standard is 60 Hz */; + u8 is_out_50hz /* 1 if the current TV output standard is 50 Hz */; + u8 is_out_60hz /* 1 if the current TV output standard is 60 Hz */; + int output_mode; /* decoder output mode: NONE, MPG, YUV, UDMA YUV, passthrough */ + u32 audio_input; /* current audio input */ + u32 active_input; /* current video input */ + u32 active_output; /* current video output */ + v4l2_std_id std; /* current capture TV standard */ + v4l2_std_id std_out; /* current TV output standard */ + u8 audio_stereo_mode; /* decoder setting how to handle stereo MPEG audio */ + u8 audio_bilingual_mode; /* decoder setting how to handle bilingual MPEG audio */ + + /* Locking */ + spinlock_t lock; /* lock access to this struct */ + struct mutex serialize_lock; /* mutex used to serialize open/close/start/stop/ioctl operations */ + + /* Streams */ + int stream_buf_size[IVTV_MAX_STREAMS]; /* stream buffer size */ + struct ivtv_stream streams[IVTV_MAX_STREAMS]; /* stream data */ + atomic_t capturing; /* count number of active capture streams */ + atomic_t decoding; /* count number of active decoding streams */ + + /* ALSA interface for PCM capture stream */ + struct snd_ivtv_card *alsa; + void (*pcm_announce_callback)(struct snd_ivtv_card *card, u8 *pcm_data, + size_t num_bytes); + + /* Used for ivtv-alsa module loading */ + struct work_struct request_module_wk; + + /* Interrupts & DMA */ + u32 irqmask; /* active interrupts */ + u32 irq_rr_idx; /* round-robin stream index */ + struct kthread_worker irq_worker; /* kthread worker for PIO/YUV/VBI actions */ + struct task_struct *irq_worker_task; /* task for irq_worker */ + struct kthread_work irq_work; /* kthread work entry */ + spinlock_t dma_reg_lock; /* lock access to DMA engine registers */ + int cur_dma_stream; /* index of current stream doing DMA (-1 if none) */ + int cur_pio_stream; /* index of current stream doing PIO (-1 if none) */ + u32 dma_data_req_offset; /* store offset in decoder memory of current DMA request */ + u32 dma_data_req_size; /* store size of current DMA request */ + int dma_retries; /* current DMA retry attempt */ + struct ivtv_user_dma udma; /* user based DMA for OSD */ + struct timer_list dma_timer; /* timer used to catch unfinished DMAs */ + u32 last_vsync_field; /* last seen vsync field */ + wait_queue_head_t dma_waitq; /* wake up when the current DMA is finished */ + wait_queue_head_t eos_waitq; /* wake up when EOS arrives */ + wait_queue_head_t event_waitq; /* wake up when the next decoder event arrives */ + wait_queue_head_t vsync_waitq; /* wake up when the next decoder vsync arrives */ + + + /* Mailbox */ + struct ivtv_mailbox_data enc_mbox; /* encoder mailboxes */ + struct ivtv_mailbox_data dec_mbox; /* decoder mailboxes */ + struct ivtv_api_cache api_cache[256]; /* cached API commands */ + + + /* I2C */ + struct i2c_adapter i2c_adap; + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + int i2c_state; /* i2c bit state */ + struct mutex i2c_bus_lock; /* lock i2c bus */ + + struct IR_i2c_init_data ir_i2c_init_data; + + /* Program Index information */ + u32 pgm_info_offset; /* start of pgm info in encoder memory */ + u32 pgm_info_num; /* number of elements in the pgm cyclic buffer in encoder memory */ + u32 pgm_info_write_idx; /* last index written by the card that was transferred to pgm_info[] */ + u32 pgm_info_read_idx; /* last index in pgm_info read by the application */ + struct v4l2_enc_idx_entry pgm_info[IVTV_MAX_PGM_INDEX]; /* filled from the pgm cyclic buffer on the card */ + + + /* Miscellaneous */ + u32 open_id; /* incremented each time an open occurs, is >= 1 */ + int search_pack_header; /* 1 if ivtv_copy_buf_to_user() is scanning for a pack header (0xba) */ + int speed; /* current playback speed setting */ + u8 speed_mute_audio; /* 1 if audio should be muted when fast forward */ + u64 mpg_data_received; /* number of bytes received from the MPEG stream */ + u64 vbi_data_inserted; /* number of VBI bytes inserted into the MPEG stream */ + u32 last_dec_timing[3]; /* cache last retrieved pts/scr/frame values */ + unsigned long dualwatch_jiffies;/* jiffies value of the previous dualwatch check */ + u32 dualwatch_stereo_mode; /* current detected dualwatch stereo mode */ + + + /* VBI state info */ + struct vbi_info vbi; /* VBI-specific data */ + + + /* YUV playback */ + struct yuv_playback_info yuv_info; /* YUV playback data */ + + + /* OSD support */ + unsigned long osd_video_pbase; + int osd_global_alpha_state; /* 1 = global alpha is on */ + int osd_local_alpha_state; /* 1 = local alpha is on */ + int osd_chroma_key_state; /* 1 = chroma-keying is on */ + u8 osd_global_alpha; /* current global alpha */ + u32 osd_chroma_key; /* current chroma key */ + struct v4l2_rect osd_rect; /* current OSD position and size */ + struct v4l2_rect main_rect; /* current Main window position and size */ + struct osd_info *osd_info; /* ivtvfb private OSD info */ + void (*ivtvfb_restore)(struct ivtv *itv); /* Used for a warm start */ +}; + +static inline struct ivtv *to_ivtv(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct ivtv, v4l2_dev); +} + +/* ivtv extensions to be loaded */ +extern int (*ivtv_ext_init)(struct ivtv *); + +/* Globals */ +extern int ivtv_first_minor; + +/*==============Prototypes==================*/ + +/* Hardware/IRQ */ +void ivtv_set_irq_mask(struct ivtv *itv, u32 mask); +void ivtv_clear_irq_mask(struct ivtv *itv, u32 mask); + +/* try to set output mode, return current mode. */ +int ivtv_set_output_mode(struct ivtv *itv, int mode); + +/* return current output stream based on current mode */ +struct ivtv_stream *ivtv_get_output_stream(struct ivtv *itv); + +/* Return non-zero if a signal is pending */ +int ivtv_msleep_timeout(unsigned int msecs, int intr); + +/* Wait on queue, returns -EINTR if interrupted */ +int ivtv_waitq(wait_queue_head_t *waitq); + +/* Read Hauppauge eeprom */ +struct tveeprom; /* forward reference */ +void ivtv_read_eeprom(struct ivtv *itv, struct tveeprom *tv); + +/* First-open initialization: load firmware, init cx25840, etc. */ +int ivtv_init_on_first_open(struct ivtv *itv); + +/* Test if the current VBI mode is raw (1) or sliced (0) */ +static inline int ivtv_raw_vbi(const struct ivtv *itv) +{ + return itv->vbi.in.type == V4L2_BUF_TYPE_VBI_CAPTURE; +} + +/* This is a PCI post thing, where if the pci register is not read, then + the write doesn't always take effect right away. By reading back the + register any pending PCI writes will be performed (in order), and so + you can be sure that the writes are guaranteed to be done. + + Rarely needed, only in some timing sensitive cases. + Apparently if this is not done some motherboards seem + to kill the firmware and get into the broken state until computer is + rebooted. */ +#define write_sync(val, reg) \ + do { writel(val, reg); readl(reg); } while (0) + +#define read_reg(reg) readl(itv->reg_mem + (reg)) +#define write_reg(val, reg) writel(val, itv->reg_mem + (reg)) +#define write_reg_sync(val, reg) \ + do { write_reg(val, reg); read_reg(reg); } while (0) + +#define read_enc(addr) readl(itv->enc_mem + (u32)(addr)) +#define write_enc(val, addr) writel(val, itv->enc_mem + (u32)(addr)) +#define write_enc_sync(val, addr) \ + do { write_enc(val, addr); read_enc(addr); } while (0) + +#define read_dec(addr) readl(itv->dec_mem + (u32)(addr)) +#define write_dec(val, addr) writel(val, itv->dec_mem + (u32)(addr)) +#define write_dec_sync(val, addr) \ + do { write_dec(val, addr); read_dec(addr); } while (0) + +/* Call the specified callback for all subdevs matching hw (if 0, then + match them all). Ignore any errors. */ +#define ivtv_call_hw(itv, hw, o, f, args...) \ + do { \ + struct v4l2_subdev *__sd; \ + __v4l2_device_call_subdevs_p(&(itv)->v4l2_dev, __sd, \ + !(hw) || (__sd->grp_id & (hw)), o, f , ##args); \ + } while (0) + +#define ivtv_call_all(itv, o, f, args...) ivtv_call_hw(itv, 0, o, f , ##args) + +/* Call the specified callback for all subdevs matching hw (if 0, then + match them all). If the callback returns an error other than 0 or + -ENOIOCTLCMD, then return with that error code. */ +#define ivtv_call_hw_err(itv, hw, o, f, args...) \ +({ \ + struct v4l2_subdev *__sd; \ + __v4l2_device_call_subdevs_until_err_p(&(itv)->v4l2_dev, __sd, \ + !(hw) || (__sd->grp_id & (hw)), o, f , ##args); \ +}) + +#define ivtv_call_all_err(itv, o, f, args...) ivtv_call_hw_err(itv, 0, o, f , ##args) + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-fileops.c b/drivers/media/pci/ivtv/ivtv-fileops.c new file mode 100644 index 000000000000..9caffd8aa995 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-fileops.c @@ -0,0 +1,1073 @@ +/* + file operation functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-fileops.h" +#include "ivtv-i2c.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-vbi.h" +#include "ivtv-mailbox.h" +#include "ivtv-routing.h" +#include "ivtv-streams.h" +#include "ivtv-yuv.h" +#include "ivtv-ioctl.h" +#include "ivtv-cards.h" +#include "ivtv-firmware.h" +#include +#include + +/* This function tries to claim the stream for a specific file descriptor. + If no one else is using this stream then the stream is claimed and + associated VBI streams are also automatically claimed. + Possible error returns: -EBUSY if someone else has claimed + the stream or 0 on success. */ +int ivtv_claim_stream(struct ivtv_open_id *id, int type) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[type]; + struct ivtv_stream *s_vbi; + int vbi_type; + + if (test_and_set_bit(IVTV_F_S_CLAIMED, &s->s_flags)) { + /* someone already claimed this stream */ + if (s->fh == &id->fh) { + /* yes, this file descriptor did. So that's OK. */ + return 0; + } + if (s->fh == NULL && (type == IVTV_DEC_STREAM_TYPE_VBI || + type == IVTV_ENC_STREAM_TYPE_VBI)) { + /* VBI is handled already internally, now also assign + the file descriptor to this stream for external + reading of the stream. */ + s->fh = &id->fh; + IVTV_DEBUG_INFO("Start Read VBI\n"); + return 0; + } + /* someone else is using this stream already */ + IVTV_DEBUG_INFO("Stream %d is busy\n", type); + return -EBUSY; + } + s->fh = &id->fh; + if (type == IVTV_DEC_STREAM_TYPE_VBI) { + /* Enable reinsertion interrupt */ + ivtv_clear_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + } + + /* IVTV_DEC_STREAM_TYPE_MPG needs to claim IVTV_DEC_STREAM_TYPE_VBI, + IVTV_ENC_STREAM_TYPE_MPG needs to claim IVTV_ENC_STREAM_TYPE_VBI + (provided VBI insertion is on and sliced VBI is selected), for all + other streams we're done */ + if (type == IVTV_DEC_STREAM_TYPE_MPG) { + vbi_type = IVTV_DEC_STREAM_TYPE_VBI; + } else if (type == IVTV_ENC_STREAM_TYPE_MPG && + itv->vbi.insert_mpeg && !ivtv_raw_vbi(itv)) { + vbi_type = IVTV_ENC_STREAM_TYPE_VBI; + } else { + return 0; + } + s_vbi = &itv->streams[vbi_type]; + + if (!test_and_set_bit(IVTV_F_S_CLAIMED, &s_vbi->s_flags)) { + /* Enable reinsertion interrupt */ + if (vbi_type == IVTV_DEC_STREAM_TYPE_VBI) + ivtv_clear_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + } + /* mark that it is used internally */ + set_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags); + return 0; +} +EXPORT_SYMBOL(ivtv_claim_stream); + +/* This function releases a previously claimed stream. It will take into + account associated VBI streams. */ +void ivtv_release_stream(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + struct ivtv_stream *s_vbi; + + s->fh = NULL; + if ((s->type == IVTV_DEC_STREAM_TYPE_VBI || s->type == IVTV_ENC_STREAM_TYPE_VBI) && + test_bit(IVTV_F_S_INTERNAL_USE, &s->s_flags)) { + /* this stream is still in use internally */ + return; + } + if (!test_and_clear_bit(IVTV_F_S_CLAIMED, &s->s_flags)) { + IVTV_DEBUG_WARN("Release stream %s not in use!\n", s->name); + return; + } + + ivtv_flush_queues(s); + + /* disable reinsertion interrupt */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) + ivtv_set_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + + /* IVTV_DEC_STREAM_TYPE_MPG needs to release IVTV_DEC_STREAM_TYPE_VBI, + IVTV_ENC_STREAM_TYPE_MPG needs to release IVTV_ENC_STREAM_TYPE_VBI, + for all other streams we're done */ + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + s_vbi = &itv->streams[IVTV_DEC_STREAM_TYPE_VBI]; + else if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + else + return; + + /* clear internal use flag */ + if (!test_and_clear_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags)) { + /* was already cleared */ + return; + } + if (s_vbi->fh) { + /* VBI stream still claimed by a file descriptor */ + return; + } + /* disable reinsertion interrupt */ + if (s_vbi->type == IVTV_DEC_STREAM_TYPE_VBI) + ivtv_set_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + clear_bit(IVTV_F_S_CLAIMED, &s_vbi->s_flags); + ivtv_flush_queues(s_vbi); +} +EXPORT_SYMBOL(ivtv_release_stream); + +static void ivtv_dualwatch(struct ivtv *itv) +{ + struct v4l2_tuner vt; + u32 new_stereo_mode; + const u32 dual = 0x02; + + new_stereo_mode = v4l2_ctrl_g_ctrl(itv->cxhdl.audio_mode); + memset(&vt, 0, sizeof(vt)); + ivtv_call_all(itv, tuner, g_tuner, &vt); + if (vt.audmode == V4L2_TUNER_MODE_LANG1_LANG2 && (vt.rxsubchans & V4L2_TUNER_SUB_LANG2)) + new_stereo_mode = dual; + + if (new_stereo_mode == itv->dualwatch_stereo_mode) + return; + + IVTV_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x.\n", + itv->dualwatch_stereo_mode, new_stereo_mode); + if (v4l2_ctrl_s_ctrl(itv->cxhdl.audio_mode, new_stereo_mode)) + IVTV_DEBUG_INFO("dualwatch: changing stereo flag failed\n"); +} + +static void ivtv_update_pgm_info(struct ivtv *itv) +{ + u32 wr_idx = (read_enc(itv->pgm_info_offset) - itv->pgm_info_offset - 4) / 24; + int cnt; + int i = 0; + + if (wr_idx >= itv->pgm_info_num) { + IVTV_DEBUG_WARN("Invalid PGM index %d (>= %d)\n", wr_idx, itv->pgm_info_num); + return; + } + cnt = (wr_idx + itv->pgm_info_num - itv->pgm_info_write_idx) % itv->pgm_info_num; + while (i < cnt) { + int idx = (itv->pgm_info_write_idx + i) % itv->pgm_info_num; + struct v4l2_enc_idx_entry *e = itv->pgm_info + idx; + u32 addr = itv->pgm_info_offset + 4 + idx * 24; + const int mapping[8] = { -1, V4L2_ENC_IDX_FRAME_I, V4L2_ENC_IDX_FRAME_P, -1, + V4L2_ENC_IDX_FRAME_B, -1, -1, -1 }; + // 1=I, 2=P, 4=B + + e->offset = read_enc(addr + 4) + ((u64)read_enc(addr + 8) << 32); + if (e->offset > itv->mpg_data_received) { + break; + } + e->offset += itv->vbi_data_inserted; + e->length = read_enc(addr); + e->pts = read_enc(addr + 16) + ((u64)(read_enc(addr + 20) & 1) << 32); + e->flags = mapping[read_enc(addr + 12) & 7]; + i++; + } + itv->pgm_info_write_idx = (itv->pgm_info_write_idx + i) % itv->pgm_info_num; +} + +static struct ivtv_buffer *ivtv_get_buffer(struct ivtv_stream *s, int non_block, int *err) +{ + struct ivtv *itv = s->itv; + struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + struct ivtv_buffer *buf; + DEFINE_WAIT(wait); + + *err = 0; + while (1) { + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) { + /* Process pending program info updates and pending VBI data */ + ivtv_update_pgm_info(itv); + + if (time_after(jiffies, + itv->dualwatch_jiffies + + msecs_to_jiffies(1000))) { + itv->dualwatch_jiffies = jiffies; + ivtv_dualwatch(itv); + } + + if (test_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags) && + !test_bit(IVTV_F_S_APPL_IO, &s_vbi->s_flags)) { + while ((buf = ivtv_dequeue(s_vbi, &s_vbi->q_full))) { + /* byteswap and process VBI data */ + ivtv_process_vbi_data(itv, buf, s_vbi->dma_pts, s_vbi->type); + ivtv_enqueue(s_vbi, buf, &s_vbi->q_free); + } + } + buf = &itv->vbi.sliced_mpeg_buf; + if (buf->readpos != buf->bytesused) { + return buf; + } + } + + /* do we have leftover data? */ + buf = ivtv_dequeue(s, &s->q_io); + if (buf) + return buf; + + /* do we have new data? */ + buf = ivtv_dequeue(s, &s->q_full); + if (buf) { + if ((buf->b_flags & IVTV_F_B_NEED_BUF_SWAP) == 0) + return buf; + buf->b_flags &= ~IVTV_F_B_NEED_BUF_SWAP; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + /* byteswap MPG data */ + ivtv_buf_swap(buf); + else if (s->type != IVTV_DEC_STREAM_TYPE_VBI) { + /* byteswap and process VBI data */ + ivtv_process_vbi_data(itv, buf, s->dma_pts, s->type); + } + return buf; + } + + /* return if end of stream */ + if (s->type != IVTV_DEC_STREAM_TYPE_VBI && !test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + IVTV_DEBUG_INFO("EOS %s\n", s->name); + return NULL; + } + + /* return if file was opened with O_NONBLOCK */ + if (non_block) { + *err = -EAGAIN; + return NULL; + } + + /* wait for more data to arrive */ + mutex_unlock(&itv->serialize_lock); + prepare_to_wait(&s->waitq, &wait, TASK_INTERRUPTIBLE); + /* New buffers might have become available before we were added to the waitqueue */ + if (!s->q_full.buffers) + schedule(); + finish_wait(&s->waitq, &wait); + mutex_lock(&itv->serialize_lock); + if (signal_pending(current)) { + /* return if a signal was received */ + IVTV_DEBUG_INFO("User stopped %s\n", s->name); + *err = -EINTR; + return NULL; + } + } +} + +static void ivtv_setup_sliced_vbi_buf(struct ivtv *itv) +{ + int idx = itv->vbi.inserted_frame % IVTV_VBI_FRAMES; + + itv->vbi.sliced_mpeg_buf.buf = itv->vbi.sliced_mpeg_data[idx]; + itv->vbi.sliced_mpeg_buf.bytesused = itv->vbi.sliced_mpeg_size[idx]; + itv->vbi.sliced_mpeg_buf.readpos = 0; +} + +static size_t ivtv_copy_buf_to_user(struct ivtv_stream *s, struct ivtv_buffer *buf, + char __user *ubuf, size_t ucount) +{ + struct ivtv *itv = s->itv; + size_t len = buf->bytesused - buf->readpos; + + if (len > ucount) len = ucount; + if (itv->vbi.insert_mpeg && s->type == IVTV_ENC_STREAM_TYPE_MPG && + !ivtv_raw_vbi(itv) && buf != &itv->vbi.sliced_mpeg_buf) { + const char *start = buf->buf + buf->readpos; + const char *p = start + 1; + const u8 *q; + u8 ch = itv->search_pack_header ? 0xba : 0xe0; + int stuffing, i; + + while (start + len > p && (q = memchr(p, 0, start + len - p))) { + p = q + 1; + if ((char *)q + 15 >= buf->buf + buf->bytesused || + q[1] != 0 || q[2] != 1 || q[3] != ch) { + continue; + } + if (!itv->search_pack_header) { + if ((q[6] & 0xc0) != 0x80) + continue; + if (((q[7] & 0xc0) == 0x80 && (q[9] & 0xf0) == 0x20) || + ((q[7] & 0xc0) == 0xc0 && (q[9] & 0xf0) == 0x30)) { + ch = 0xba; + itv->search_pack_header = 1; + p = q + 9; + } + continue; + } + stuffing = q[13] & 7; + /* all stuffing bytes must be 0xff */ + for (i = 0; i < stuffing; i++) + if (q[14 + i] != 0xff) + break; + if (i == stuffing && (q[4] & 0xc4) == 0x44 && (q[12] & 3) == 3 && + q[14 + stuffing] == 0 && q[15 + stuffing] == 0 && + q[16 + stuffing] == 1) { + itv->search_pack_header = 0; + len = (char *)q - start; + ivtv_setup_sliced_vbi_buf(itv); + break; + } + } + } + if (copy_to_user(ubuf, (u8 *)buf->buf + buf->readpos, len)) { + IVTV_DEBUG_WARN("copy %zd bytes to user failed for %s\n", len, s->name); + return -EFAULT; + } + /*IVTV_INFO("copied %lld %d %d %d %d %d vbi %d\n", itv->mpg_data_received, len, ucount, + buf->readpos, buf->bytesused, buf->bytesused - buf->readpos - len, + buf == &itv->vbi.sliced_mpeg_buf); */ + buf->readpos += len; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && buf != &itv->vbi.sliced_mpeg_buf) + itv->mpg_data_received += len; + return len; +} + +static ssize_t ivtv_read(struct ivtv_stream *s, char __user *ubuf, size_t tot_count, int non_block) +{ + struct ivtv *itv = s->itv; + size_t tot_written = 0; + int single_frame = 0; + + if (atomic_read(&itv->capturing) == 0 && s->fh == NULL) { + /* shouldn't happen */ + IVTV_DEBUG_WARN("Stream %s not initialized before read\n", s->name); + return -EIO; + } + + /* Each VBI buffer is one frame, the v4l2 API says that for VBI the frames should + arrive one-by-one, so make sure we never output more than one VBI frame at a time */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI || + (s->type == IVTV_ENC_STREAM_TYPE_VBI && !ivtv_raw_vbi(itv))) + single_frame = 1; + + for (;;) { + struct ivtv_buffer *buf; + int rc; + + buf = ivtv_get_buffer(s, non_block, &rc); + /* if there is no data available... */ + if (buf == NULL) { + /* if we got data, then return that regardless */ + if (tot_written) + break; + /* EOS condition */ + if (rc == 0) { + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + ivtv_release_stream(s); + } + /* set errno */ + return rc; + } + rc = ivtv_copy_buf_to_user(s, buf, ubuf + tot_written, tot_count - tot_written); + if (buf != &itv->vbi.sliced_mpeg_buf) { + ivtv_enqueue(s, buf, (buf->readpos == buf->bytesused) ? &s->q_free : &s->q_io); + } + else if (buf->readpos == buf->bytesused) { + int idx = itv->vbi.inserted_frame % IVTV_VBI_FRAMES; + itv->vbi.sliced_mpeg_size[idx] = 0; + itv->vbi.inserted_frame++; + itv->vbi_data_inserted += buf->bytesused; + } + if (rc < 0) + return rc; + tot_written += rc; + + if (tot_written == tot_count || single_frame) + break; + } + return tot_written; +} + +static ssize_t ivtv_read_pos(struct ivtv_stream *s, char __user *ubuf, size_t count, + loff_t *pos, int non_block) +{ + ssize_t rc = count ? ivtv_read(s, ubuf, count, non_block) : 0; + struct ivtv *itv = s->itv; + + IVTV_DEBUG_HI_FILE("read %zd from %s, got %zd\n", count, s->name, rc); + if (rc > 0) + pos += rc; + return rc; +} + +int ivtv_start_capture(struct ivtv_open_id *id) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + struct ivtv_stream *s_vbi; + + if (s->type == IVTV_ENC_STREAM_TYPE_RAD || + s->type == IVTV_DEC_STREAM_TYPE_MPG || + s->type == IVTV_DEC_STREAM_TYPE_YUV || + s->type == IVTV_DEC_STREAM_TYPE_VOUT) { + /* you cannot read from these stream types. */ + return -EINVAL; + } + + /* Try to claim this stream. */ + if (ivtv_claim_stream(id, s->type)) + return -EBUSY; + + /* This stream does not need to start capturing */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) { + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return 0; + } + + /* If capture is already in progress, then we also have to + do nothing extra. */ + if (test_bit(IVTV_F_S_STREAMOFF, &s->s_flags) || test_and_set_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return 0; + } + + /* Start VBI capture if required */ + s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags) && + !test_and_set_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags)) { + /* Note: the IVTV_ENC_STREAM_TYPE_VBI is claimed + automatically when the MPG stream is claimed. + We only need to start the VBI capturing. */ + if (ivtv_start_v4l2_encode_stream(s_vbi)) { + IVTV_DEBUG_WARN("VBI capture start failed\n"); + + /* Failure, clean up and return an error */ + clear_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags); + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + /* also releases the associated VBI stream */ + ivtv_release_stream(s); + return -EIO; + } + IVTV_DEBUG_INFO("VBI insertion started\n"); + } + + /* Tell the card to start capturing */ + if (!ivtv_start_v4l2_encode_stream(s)) { + /* We're done */ + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + /* Resume a possibly paused encoder */ + if (test_and_clear_bit(IVTV_F_I_ENC_PAUSED, &itv->i_flags)) + ivtv_vapi(itv, CX2341X_ENC_PAUSE_ENCODER, 1, 1); + return 0; + } + + /* failure, clean up */ + IVTV_DEBUG_WARN("Failed to start capturing for stream %s\n", s->name); + + /* Note: the IVTV_ENC_STREAM_TYPE_VBI is released + automatically when the MPG stream is released. + We only need to stop the VBI capturing. */ + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags)) { + ivtv_stop_v4l2_encode_stream(s_vbi, 0); + clear_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags); + } + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + ivtv_release_stream(s); + return -EIO; +} + +ssize_t ivtv_v4l2_read(struct file * filp, char __user *buf, size_t count, loff_t * pos) +{ + struct ivtv_open_id *id = fh2id(filp->private_data); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + ssize_t rc; + + IVTV_DEBUG_HI_FILE("read %zd bytes from %s\n", count, s->name); + + if (mutex_lock_interruptible(&itv->serialize_lock)) + return -ERESTARTSYS; + rc = ivtv_start_capture(id); + if (!rc) + rc = ivtv_read_pos(s, buf, count, pos, filp->f_flags & O_NONBLOCK); + mutex_unlock(&itv->serialize_lock); + return rc; +} + +int ivtv_start_decoding(struct ivtv_open_id *id, int speed) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + int rc; + + if (atomic_read(&itv->decoding) == 0) { + if (ivtv_claim_stream(id, s->type)) { + /* someone else is using this stream already */ + IVTV_DEBUG_WARN("start decode, stream already claimed\n"); + return -EBUSY; + } + rc = ivtv_start_v4l2_decode_stream(s, 0); + if (rc < 0) { + if (rc == -EAGAIN) + rc = ivtv_start_v4l2_decode_stream(s, 0); + if (rc < 0) + return rc; + } + } + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + return ivtv_set_speed(itv, speed); + return 0; +} + +static ssize_t ivtv_write(struct file *filp, const char __user *user_buf, size_t count, loff_t *pos) +{ + struct ivtv_open_id *id = fh2id(filp->private_data); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + struct yuv_playback_info *yi = &itv->yuv_info; + struct ivtv_buffer *buf; + struct ivtv_queue q; + int bytes_written = 0; + int mode; + int rc; + DEFINE_WAIT(wait); + + IVTV_DEBUG_HI_FILE("write %zd bytes to %s\n", count, s->name); + + if (s->type != IVTV_DEC_STREAM_TYPE_MPG && + s->type != IVTV_DEC_STREAM_TYPE_YUV && + s->type != IVTV_DEC_STREAM_TYPE_VOUT) + /* not decoder streams */ + return -EINVAL; + + /* Try to claim this stream */ + if (ivtv_claim_stream(id, s->type)) + return -EBUSY; + + /* This stream does not need to start any decoding */ + if (s->type == IVTV_DEC_STREAM_TYPE_VOUT) { + int elems = count / sizeof(struct v4l2_sliced_vbi_data); + + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return ivtv_write_vbi_from_user(itv, + (const struct v4l2_sliced_vbi_data __user *)user_buf, elems); + } + + mode = s->type == IVTV_DEC_STREAM_TYPE_MPG ? OUT_MPG : OUT_YUV; + + if (ivtv_set_output_mode(itv, mode) != mode) { + ivtv_release_stream(s); + return -EBUSY; + } + ivtv_queue_init(&q); + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + + /* Start decoder (returns 0 if already started) */ + rc = ivtv_start_decoding(id, itv->speed); + if (rc) { + IVTV_DEBUG_WARN("Failed start decode stream %s\n", s->name); + + /* failure, clean up */ + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return rc; + } + +retry: + /* If possible, just DMA the entire frame - Check the data transfer size + since we may get here before the stream has been fully set-up */ + if (mode == OUT_YUV && s->q_full.length == 0 && itv->dma_data_req_size) { + while (count >= itv->dma_data_req_size) { + rc = ivtv_yuv_udma_stream_frame(itv, (void __user *)user_buf); + + if (rc < 0) + return rc; + + bytes_written += itv->dma_data_req_size; + user_buf += itv->dma_data_req_size; + count -= itv->dma_data_req_size; + } + if (count == 0) { + IVTV_DEBUG_HI_FILE("Wrote %d bytes to %s (%d)\n", bytes_written, s->name, s->q_full.bytesused); + return bytes_written; + } + } + + for (;;) { + /* Gather buffers */ + while (q.length - q.bytesused < count && (buf = ivtv_dequeue(s, &s->q_io))) + ivtv_enqueue(s, buf, &q); + while (q.length - q.bytesused < count && (buf = ivtv_dequeue(s, &s->q_free))) { + ivtv_enqueue(s, buf, &q); + } + if (q.buffers) + break; + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + mutex_unlock(&itv->serialize_lock); + prepare_to_wait(&s->waitq, &wait, TASK_INTERRUPTIBLE); + /* New buffers might have become free before we were added to the waitqueue */ + if (!s->q_free.buffers) + schedule(); + finish_wait(&s->waitq, &wait); + mutex_lock(&itv->serialize_lock); + if (signal_pending(current)) { + IVTV_DEBUG_INFO("User stopped %s\n", s->name); + return -EINTR; + } + } + + /* copy user data into buffers */ + while ((buf = ivtv_dequeue(s, &q))) { + /* yuv is a pain. Don't copy more data than needed for a single + frame, otherwise we lose sync with the incoming stream */ + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && + yi->stream_size + count > itv->dma_data_req_size) + rc = ivtv_buf_copy_from_user(s, buf, user_buf, + itv->dma_data_req_size - yi->stream_size); + else + rc = ivtv_buf_copy_from_user(s, buf, user_buf, count); + + /* Make sure we really got all the user data */ + if (rc < 0) { + ivtv_queue_move(s, &q, NULL, &s->q_free, 0); + return rc; + } + user_buf += rc; + count -= rc; + bytes_written += rc; + + if (s->type == IVTV_DEC_STREAM_TYPE_YUV) { + yi->stream_size += rc; + /* If we have a complete yuv frame, break loop now */ + if (yi->stream_size == itv->dma_data_req_size) { + ivtv_enqueue(s, buf, &s->q_full); + yi->stream_size = 0; + break; + } + } + + if (buf->bytesused != s->buf_size) { + /* incomplete, leave in q_io for next time */ + ivtv_enqueue(s, buf, &s->q_io); + break; + } + /* Byteswap MPEG buffer */ + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + ivtv_buf_swap(buf); + ivtv_enqueue(s, buf, &s->q_full); + } + + if (test_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags)) { + if (s->q_full.length >= itv->dma_data_req_size) { + int got_sig; + + if (mode == OUT_YUV) + ivtv_yuv_setup_stream_frame(itv); + + mutex_unlock(&itv->serialize_lock); + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + while (!(got_sig = signal_pending(current)) && + test_bit(IVTV_F_S_DMA_PENDING, &s->s_flags)) { + schedule(); + } + finish_wait(&itv->dma_waitq, &wait); + mutex_lock(&itv->serialize_lock); + if (got_sig) { + IVTV_DEBUG_INFO("User interrupted %s\n", s->name); + return -EINTR; + } + + clear_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + ivtv_queue_move(s, &s->q_full, NULL, &s->q_predma, itv->dma_data_req_size); + ivtv_dma_stream_dec_prepare(s, itv->dma_data_req_offset + IVTV_DECODER_OFFSET, 1); + } + } + /* more user data is available, wait until buffers become free + to transfer the rest. */ + if (count && !(filp->f_flags & O_NONBLOCK)) + goto retry; + IVTV_DEBUG_HI_FILE("Wrote %d bytes to %s (%d)\n", bytes_written, s->name, s->q_full.bytesused); + return bytes_written; +} + +ssize_t ivtv_v4l2_write(struct file *filp, const char __user *user_buf, size_t count, loff_t *pos) +{ + struct ivtv_open_id *id = fh2id(filp->private_data); + struct ivtv *itv = id->itv; + ssize_t res; + + if (mutex_lock_interruptible(&itv->serialize_lock)) + return -ERESTARTSYS; + res = ivtv_write(filp, user_buf, count, pos); + mutex_unlock(&itv->serialize_lock); + return res; +} + +unsigned int ivtv_v4l2_dec_poll(struct file *filp, poll_table *wait) +{ + struct ivtv_open_id *id = fh2id(filp->private_data); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + int res = 0; + + /* add stream's waitq to the poll list */ + IVTV_DEBUG_HI_FILE("Decoder poll\n"); + + /* If there are subscribed events, then only use the new event + API instead of the old video.h based API. */ + if (!list_empty(&id->fh.subscribed)) { + poll_wait(filp, &id->fh.wait, wait); + /* Turn off the old-style vsync events */ + clear_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags); + if (v4l2_event_pending(&id->fh)) + res = POLLPRI; + } else { + /* This is the old-style API which is here only for backwards + compatibility. */ + poll_wait(filp, &s->waitq, wait); + set_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags); + if (test_bit(IVTV_F_I_EV_VSYNC, &itv->i_flags) || + test_bit(IVTV_F_I_EV_DEC_STOPPED, &itv->i_flags)) + res = POLLPRI; + } + + /* Allow write if buffers are available for writing */ + if (s->q_free.buffers) + res |= POLLOUT | POLLWRNORM; + return res; +} + +unsigned int ivtv_v4l2_enc_poll(struct file *filp, poll_table *wait) +{ + unsigned long req_events = poll_requested_events(wait); + struct ivtv_open_id *id = fh2id(filp->private_data); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + int eof = test_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + unsigned res = 0; + + /* Start a capture if there is none */ + if (!eof && !test_bit(IVTV_F_S_STREAMING, &s->s_flags) && + s->type != IVTV_ENC_STREAM_TYPE_RAD && + (req_events & (POLLIN | POLLRDNORM))) { + int rc; + + mutex_lock(&itv->serialize_lock); + rc = ivtv_start_capture(id); + mutex_unlock(&itv->serialize_lock); + if (rc) { + IVTV_DEBUG_INFO("Could not start capture for %s (%d)\n", + s->name, rc); + return POLLERR; + } + IVTV_DEBUG_FILE("Encoder poll started capture\n"); + } + + /* add stream's waitq to the poll list */ + IVTV_DEBUG_HI_FILE("Encoder poll\n"); + poll_wait(filp, &s->waitq, wait); + if (v4l2_event_pending(&id->fh)) + res |= POLLPRI; + else + poll_wait(filp, &id->fh.wait, wait); + + if (s->q_full.length || s->q_io.length) + return res | POLLIN | POLLRDNORM; + if (eof) + return res | POLLHUP; + return res; +} + +void ivtv_stop_capture(struct ivtv_open_id *id, int gop_end) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + IVTV_DEBUG_FILE("close() of %s\n", s->name); + + /* 'Unclaim' this stream */ + + /* Stop capturing */ + if (test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + + IVTV_DEBUG_INFO("close stopping capture\n"); + /* Special case: a running VBI capture for VBI insertion + in the mpeg stream. Need to stop that too. */ + if (id->type == IVTV_ENC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags) && + !test_bit(IVTV_F_S_APPL_IO, &s_vbi->s_flags)) { + IVTV_DEBUG_INFO("close stopping embedded VBI capture\n"); + ivtv_stop_v4l2_encode_stream(s_vbi, 0); + } + if ((id->type == IVTV_DEC_STREAM_TYPE_VBI || + id->type == IVTV_ENC_STREAM_TYPE_VBI) && + test_bit(IVTV_F_S_INTERNAL_USE, &s->s_flags)) { + /* Also used internally, don't stop capturing */ + s->fh = NULL; + } + else { + ivtv_stop_v4l2_encode_stream(s, gop_end); + } + } + if (!gop_end) { + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + ivtv_release_stream(s); + } +} + +static void ivtv_stop_decoding(struct ivtv_open_id *id, int flags, u64 pts) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + IVTV_DEBUG_FILE("close() of %s\n", s->name); + + if (id->type == IVTV_DEC_STREAM_TYPE_YUV && + test_bit(IVTV_F_I_DECODING_YUV, &itv->i_flags)) { + /* Restore registers we've changed & clean up any mess */ + ivtv_yuv_close(itv); + } + + /* Stop decoding */ + if (test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + IVTV_DEBUG_INFO("close stopping decode\n"); + + ivtv_stop_v4l2_decode_stream(s, flags, pts); + itv->output_mode = OUT_NONE; + } + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + if (itv->output_mode == OUT_UDMA_YUV && id->yuv_frames) + itv->output_mode = OUT_NONE; + + itv->speed = 0; + clear_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags); + ivtv_release_stream(s); +} + +int ivtv_v4l2_close(struct file *filp) +{ + struct v4l2_fh *fh = filp->private_data; + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + IVTV_DEBUG_FILE("close %s\n", s->name); + + mutex_lock(&itv->serialize_lock); + + /* Stop radio */ + if (id->type == IVTV_ENC_STREAM_TYPE_RAD && + v4l2_fh_is_singular_file(filp)) { + /* Closing radio device, return to TV mode */ + ivtv_mute(itv); + /* Mark that the radio is no longer in use */ + clear_bit(IVTV_F_I_RADIO_USER, &itv->i_flags); + /* Switch tuner to TV */ + ivtv_call_all(itv, core, s_std, itv->std); + /* Select correct audio input (i.e. TV tuner or Line in) */ + ivtv_audio_set_io(itv); + if (itv->hw_flags & IVTV_HW_SAA711X) { + ivtv_call_hw(itv, IVTV_HW_SAA711X, video, s_crystal_freq, + SAA7115_FREQ_32_11_MHZ, 0); + } + if (atomic_read(&itv->capturing) > 0) { + /* Undo video mute */ + ivtv_vapi(itv, CX2341X_ENC_MUTE_VIDEO, 1, + v4l2_ctrl_g_ctrl(itv->cxhdl.video_mute) | + (v4l2_ctrl_g_ctrl(itv->cxhdl.video_mute_yuv) << 8)); + } + /* Done! Unmute and continue. */ + ivtv_unmute(itv); + } + + v4l2_fh_del(fh); + v4l2_fh_exit(fh); + + /* Easy case first: this stream was never claimed by us */ + if (s->fh != &id->fh) + goto close_done; + + /* 'Unclaim' this stream */ + + if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) { + struct ivtv_stream *s_vout = &itv->streams[IVTV_DEC_STREAM_TYPE_VOUT]; + + ivtv_stop_decoding(id, V4L2_DEC_CMD_STOP_TO_BLACK | V4L2_DEC_CMD_STOP_IMMEDIATELY, 0); + + /* If all output streams are closed, and if the user doesn't have + IVTV_DEC_STREAM_TYPE_VOUT open, then disable CC on TV-out. */ + if (itv->output_mode == OUT_NONE && !test_bit(IVTV_F_S_APPL_IO, &s_vout->s_flags)) { + /* disable CC on TV-out */ + ivtv_disable_cc(itv); + } + } else { + ivtv_stop_capture(id, 0); + } +close_done: + kfree(id); + mutex_unlock(&itv->serialize_lock); + return 0; +} + +static int ivtv_open(struct file *filp) +{ + struct video_device *vdev = video_devdata(filp); + struct ivtv_stream *s = video_get_drvdata(vdev); + struct ivtv *itv = s->itv; + struct ivtv_open_id *item; + int res = 0; + + IVTV_DEBUG_FILE("open %s\n", s->name); + + if (ivtv_init_on_first_open(itv)) { + IVTV_ERR("Failed to initialize on device %s\n", + video_device_node_name(vdev)); + return -ENXIO; + } + +#ifdef CONFIG_VIDEO_ADV_DEBUG + /* Unless ivtv_fw_debug is set, error out if firmware dead. */ + if (ivtv_fw_debug) { + IVTV_WARN("Opening %s with dead firmware lockout disabled\n", + video_device_node_name(vdev)); + IVTV_WARN("Selected firmware errors will be ignored\n"); + } else { +#else + if (1) { +#endif + res = ivtv_firmware_check(itv, "ivtv_serialized_open"); + if (res == -EAGAIN) + res = ivtv_firmware_check(itv, "ivtv_serialized_open"); + if (res < 0) + return -EIO; + } + + if (s->type == IVTV_DEC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_CLAIMED, &itv->streams[IVTV_DEC_STREAM_TYPE_YUV].s_flags)) + return -EBUSY; + + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && + test_bit(IVTV_F_S_CLAIMED, &itv->streams[IVTV_DEC_STREAM_TYPE_MPG].s_flags)) + return -EBUSY; + + if (s->type == IVTV_DEC_STREAM_TYPE_YUV) { + if (read_reg(0x82c) == 0) { + IVTV_ERR("Tried to open YUV output device but need to send data to mpeg decoder before it can be used\n"); + /* return -ENODEV; */ + } + ivtv_udma_alloc(itv); + } + + /* Allocate memory */ + item = kzalloc(sizeof(struct ivtv_open_id), GFP_KERNEL); + if (NULL == item) { + IVTV_DEBUG_WARN("nomem on v4l2 open\n"); + return -ENOMEM; + } + v4l2_fh_init(&item->fh, s->vdev); + item->itv = itv; + item->type = s->type; + + filp->private_data = &item->fh; + v4l2_fh_add(&item->fh); + + if (item->type == IVTV_ENC_STREAM_TYPE_RAD && + v4l2_fh_is_singular_file(filp)) { + if (!test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags)) { + if (atomic_read(&itv->capturing) > 0) { + /* switching to radio while capture is + in progress is not polite */ + v4l2_fh_del(&item->fh); + v4l2_fh_exit(&item->fh); + kfree(item); + return -EBUSY; + } + } + /* Mark that the radio is being used. */ + set_bit(IVTV_F_I_RADIO_USER, &itv->i_flags); + /* We have the radio */ + ivtv_mute(itv); + /* Switch tuner to radio */ + ivtv_call_all(itv, tuner, s_radio); + /* Select the correct audio input (i.e. radio tuner) */ + ivtv_audio_set_io(itv); + if (itv->hw_flags & IVTV_HW_SAA711X) { + ivtv_call_hw(itv, IVTV_HW_SAA711X, video, s_crystal_freq, + SAA7115_FREQ_32_11_MHZ, SAA7115_FREQ_FL_APLL); + } + /* Done! Unmute and continue. */ + ivtv_unmute(itv); + } + + /* YUV or MPG Decoding Mode? */ + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) { + clear_bit(IVTV_F_I_DEC_YUV, &itv->i_flags); + } else if (s->type == IVTV_DEC_STREAM_TYPE_YUV) { + set_bit(IVTV_F_I_DEC_YUV, &itv->i_flags); + /* For yuv, we need to know the dma size before we start */ + itv->dma_data_req_size = + 1080 * ((itv->yuv_info.v4l2_src_h + 31) & ~31); + itv->yuv_info.stream_size = 0; + } + return 0; +} + +int ivtv_v4l2_open(struct file *filp) +{ + struct video_device *vdev = video_devdata(filp); + int res; + + if (mutex_lock_interruptible(vdev->lock)) + return -ERESTARTSYS; + res = ivtv_open(filp); + mutex_unlock(vdev->lock); + return res; +} + +void ivtv_mute(struct ivtv *itv) +{ + if (atomic_read(&itv->capturing)) + ivtv_vapi(itv, CX2341X_ENC_MUTE_AUDIO, 1, 1); + IVTV_DEBUG_INFO("Mute\n"); +} + +void ivtv_unmute(struct ivtv *itv) +{ + if (atomic_read(&itv->capturing)) { + ivtv_msleep_timeout(100, 0); + ivtv_vapi(itv, CX2341X_ENC_MISC, 1, 12); + ivtv_vapi(itv, CX2341X_ENC_MUTE_AUDIO, 1, 0); + } + IVTV_DEBUG_INFO("Unmute\n"); +} diff --git a/drivers/media/pci/ivtv/ivtv-fileops.h b/drivers/media/pci/ivtv/ivtv-fileops.h new file mode 100644 index 000000000000..5e08800772ca --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-fileops.h @@ -0,0 +1,44 @@ +/* + file operation functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef IVTV_FILEOPS_H +#define IVTV_FILEOPS_H + +/* Testing/Debugging */ +int ivtv_v4l2_open(struct file *filp); +ssize_t ivtv_v4l2_read(struct file *filp, char __user *buf, size_t count, + loff_t * pos); +ssize_t ivtv_v4l2_write(struct file *filp, const char __user *buf, size_t count, + loff_t * pos); +int ivtv_v4l2_close(struct file *filp); +unsigned int ivtv_v4l2_enc_poll(struct file *filp, poll_table * wait); +unsigned int ivtv_v4l2_dec_poll(struct file *filp, poll_table * wait); +int ivtv_start_capture(struct ivtv_open_id *id); +void ivtv_stop_capture(struct ivtv_open_id *id, int gop_end); +int ivtv_start_decoding(struct ivtv_open_id *id, int speed); +void ivtv_mute(struct ivtv *itv); +void ivtv_unmute(struct ivtv *itv); + +/* Utilities */ +/* Shared with ivtv-alsa module */ +int ivtv_claim_stream(struct ivtv_open_id *id, int type); +void ivtv_release_stream(struct ivtv_stream *s); + +#endif diff --git a/drivers/media/pci/ivtv/ivtv-firmware.c b/drivers/media/pci/ivtv/ivtv-firmware.c new file mode 100644 index 000000000000..6ec7705af555 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-firmware.c @@ -0,0 +1,402 @@ +/* + ivtv firmware functions. + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-mailbox.h" +#include "ivtv-firmware.h" +#include "ivtv-yuv.h" +#include "ivtv-ioctl.h" +#include "ivtv-cards.h" +#include +#include + +#define IVTV_MASK_SPU_ENABLE 0xFFFFFFFE +#define IVTV_MASK_VPU_ENABLE15 0xFFFFFFF6 +#define IVTV_MASK_VPU_ENABLE16 0xFFFFFFFB +#define IVTV_CMD_VDM_STOP 0x00000000 +#define IVTV_CMD_AO_STOP 0x00000005 +#define IVTV_CMD_APU_PING 0x00000000 +#define IVTV_CMD_VPU_STOP15 0xFFFFFFFE +#define IVTV_CMD_VPU_STOP16 0xFFFFFFEE +#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF +#define IVTV_CMD_SPU_STOP 0x00000001 +#define IVTV_CMD_SDRAM_PRECHARGE_INIT 0x0000001A +#define IVTV_CMD_SDRAM_REFRESH_INIT 0x80000640 +#define IVTV_SDRAM_SLEEPTIME 600 + +#define IVTV_DECODE_INIT_MPEG_FILENAME "v4l-cx2341x-init.mpg" +#define IVTV_DECODE_INIT_MPEG_SIZE (152*1024) + +/* Encoder/decoder firmware sizes */ +#define IVTV_FW_ENC_SIZE (376836) +#define IVTV_FW_DEC_SIZE (256*1024) + +static int load_fw_direct(const char *fn, volatile u8 __iomem *mem, struct ivtv *itv, long size) +{ + const struct firmware *fw = NULL; + int retries = 3; + +retry: + if (retries && request_firmware(&fw, fn, &itv->pdev->dev) == 0) { + int i; + volatile u32 __iomem *dst = (volatile u32 __iomem *)mem; + const u32 *src = (const u32 *)fw->data; + + if (fw->size != size) { + /* Due to race conditions in firmware loading (esp. with udev <0.95) + the wrong file was sometimes loaded. So we check filesizes to + see if at least the right-sized file was loaded. If not, then we + retry. */ + IVTV_INFO("Retry: file loaded was not %s (expected size %ld, got %zd)\n", fn, size, fw->size); + release_firmware(fw); + retries--; + goto retry; + } + for (i = 0; i < fw->size; i += 4) { + /* no need for endianness conversion on the ppc */ + __raw_writel(*src, dst); + dst++; + src++; + } + IVTV_INFO("Loaded %s firmware (%zd bytes)\n", fn, fw->size); + release_firmware(fw); + return size; + } + IVTV_ERR("Unable to open firmware %s (must be %ld bytes)\n", fn, size); + IVTV_ERR("Did you put the firmware in the hotplug firmware directory?\n"); + return -ENOMEM; +} + +void ivtv_halt_firmware(struct ivtv *itv) +{ + IVTV_DEBUG_INFO("Preparing for firmware halt.\n"); + if (itv->has_cx23415 && itv->dec_mbox.mbox) + ivtv_vapi(itv, CX2341X_DEC_HALT_FW, 0); + if (itv->enc_mbox.mbox) + ivtv_vapi(itv, CX2341X_ENC_HALT_FW, 0); + + ivtv_msleep_timeout(10, 0); + itv->enc_mbox.mbox = itv->dec_mbox.mbox = NULL; + + IVTV_DEBUG_INFO("Stopping VDM\n"); + write_reg(IVTV_CMD_VDM_STOP, IVTV_REG_VDM); + + IVTV_DEBUG_INFO("Stopping AO\n"); + write_reg(IVTV_CMD_AO_STOP, IVTV_REG_AO); + + IVTV_DEBUG_INFO("pinging (?) APU\n"); + write_reg(IVTV_CMD_APU_PING, IVTV_REG_APU); + + IVTV_DEBUG_INFO("Stopping VPU\n"); + if (!itv->has_cx23415) + write_reg(IVTV_CMD_VPU_STOP16, IVTV_REG_VPU); + else + write_reg(IVTV_CMD_VPU_STOP15, IVTV_REG_VPU); + + IVTV_DEBUG_INFO("Resetting Hw Blocks\n"); + write_reg(IVTV_CMD_HW_BLOCKS_RST, IVTV_REG_HW_BLOCKS); + + IVTV_DEBUG_INFO("Stopping SPU\n"); + write_reg(IVTV_CMD_SPU_STOP, IVTV_REG_SPU); + + ivtv_msleep_timeout(10, 0); + + IVTV_DEBUG_INFO("init Encoder SDRAM pre-charge\n"); + write_reg(IVTV_CMD_SDRAM_PRECHARGE_INIT, IVTV_REG_ENC_SDRAM_PRECHARGE); + + IVTV_DEBUG_INFO("init Encoder SDRAM refresh to 1us\n"); + write_reg(IVTV_CMD_SDRAM_REFRESH_INIT, IVTV_REG_ENC_SDRAM_REFRESH); + + if (itv->has_cx23415) { + IVTV_DEBUG_INFO("init Decoder SDRAM pre-charge\n"); + write_reg(IVTV_CMD_SDRAM_PRECHARGE_INIT, IVTV_REG_DEC_SDRAM_PRECHARGE); + + IVTV_DEBUG_INFO("init Decoder SDRAM refresh to 1us\n"); + write_reg(IVTV_CMD_SDRAM_REFRESH_INIT, IVTV_REG_DEC_SDRAM_REFRESH); + } + + IVTV_DEBUG_INFO("Sleeping for %dms\n", IVTV_SDRAM_SLEEPTIME); + ivtv_msleep_timeout(IVTV_SDRAM_SLEEPTIME, 0); +} + +void ivtv_firmware_versions(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + + /* Encoder */ + ivtv_vapi_result(itv, data, CX2341X_ENC_GET_VERSION, 0); + IVTV_INFO("Encoder revision: 0x%08x\n", data[0]); + + if (data[0] != 0x02060039) + IVTV_WARN("Recommended firmware version is 0x02060039.\n"); + + if (itv->has_cx23415) { + /* Decoder */ + ivtv_vapi_result(itv, data, CX2341X_DEC_GET_VERSION, 0); + IVTV_INFO("Decoder revision: 0x%08x\n", data[0]); + } +} + +static int ivtv_firmware_copy(struct ivtv *itv) +{ + IVTV_DEBUG_INFO("Loading encoder image\n"); + if (load_fw_direct(CX2341X_FIRM_ENC_FILENAME, + itv->enc_mem, itv, IVTV_FW_ENC_SIZE) != IVTV_FW_ENC_SIZE) { + IVTV_DEBUG_WARN("failed loading encoder firmware\n"); + return -3; + } + if (!itv->has_cx23415) + return 0; + + IVTV_DEBUG_INFO("Loading decoder image\n"); + if (load_fw_direct(CX2341X_FIRM_DEC_FILENAME, + itv->dec_mem, itv, IVTV_FW_DEC_SIZE) != IVTV_FW_DEC_SIZE) { + IVTV_DEBUG_WARN("failed loading decoder firmware\n"); + return -1; + } + return 0; +} + +static volatile struct ivtv_mailbox __iomem *ivtv_search_mailbox(const volatile u8 __iomem *mem, u32 size) +{ + int i; + + /* mailbox is preceded by a 16 byte 'magic cookie' starting at a 256-byte + address boundary */ + for (i = 0; i < size; i += 0x100) { + if (readl(mem + i) == 0x12345678 && + readl(mem + i + 4) == 0x34567812 && + readl(mem + i + 8) == 0x56781234 && + readl(mem + i + 12) == 0x78123456) { + return (volatile struct ivtv_mailbox __iomem *)(mem + i + 16); + } + } + return NULL; +} + +int ivtv_firmware_init(struct ivtv *itv) +{ + int err; + + ivtv_halt_firmware(itv); + + /* load firmware */ + err = ivtv_firmware_copy(itv); + if (err) { + IVTV_DEBUG_WARN("Error %d loading firmware\n", err); + return err; + } + + /* start firmware */ + write_reg(read_reg(IVTV_REG_SPU) & IVTV_MASK_SPU_ENABLE, IVTV_REG_SPU); + ivtv_msleep_timeout(100, 0); + if (itv->has_cx23415) + write_reg(read_reg(IVTV_REG_VPU) & IVTV_MASK_VPU_ENABLE15, IVTV_REG_VPU); + else + write_reg(read_reg(IVTV_REG_VPU) & IVTV_MASK_VPU_ENABLE16, IVTV_REG_VPU); + ivtv_msleep_timeout(100, 0); + + /* find mailboxes and ping firmware */ + itv->enc_mbox.mbox = ivtv_search_mailbox(itv->enc_mem, IVTV_ENCODER_SIZE); + if (itv->enc_mbox.mbox == NULL) + IVTV_ERR("Encoder mailbox not found\n"); + else if (ivtv_vapi(itv, CX2341X_ENC_PING_FW, 0)) { + IVTV_ERR("Encoder firmware dead!\n"); + itv->enc_mbox.mbox = NULL; + } + if (itv->enc_mbox.mbox == NULL) + return -ENODEV; + + if (!itv->has_cx23415) + return 0; + + itv->dec_mbox.mbox = ivtv_search_mailbox(itv->dec_mem, IVTV_DECODER_SIZE); + if (itv->dec_mbox.mbox == NULL) { + IVTV_ERR("Decoder mailbox not found\n"); + } else if (itv->has_cx23415 && ivtv_vapi(itv, CX2341X_DEC_PING_FW, 0)) { + IVTV_ERR("Decoder firmware dead!\n"); + itv->dec_mbox.mbox = NULL; + } else { + /* Firmware okay, so check yuv output filter table */ + ivtv_yuv_filter_check(itv); + } + return itv->dec_mbox.mbox ? 0 : -ENODEV; +} + +void ivtv_init_mpeg_decoder(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + long readbytes; + volatile u8 __iomem *mem_offset; + + data[0] = 0; + data[1] = itv->cxhdl.width; /* YUV source width */ + data[2] = itv->cxhdl.height; + data[3] = itv->cxhdl.audio_properties; /* Audio settings to use, + bitmap. see docs. */ + if (ivtv_api(itv, CX2341X_DEC_SET_DECODER_SOURCE, 4, data)) { + IVTV_ERR("ivtv_init_mpeg_decoder failed to set decoder source\n"); + return; + } + + if (ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, 0, 1) != 0) { + IVTV_ERR("ivtv_init_mpeg_decoder failed to start playback\n"); + return; + } + ivtv_api_get_data(&itv->dec_mbox, IVTV_MBOX_DMA, 2, data); + mem_offset = itv->dec_mem + data[1]; + + if ((readbytes = load_fw_direct(IVTV_DECODE_INIT_MPEG_FILENAME, + mem_offset, itv, IVTV_DECODE_INIT_MPEG_SIZE)) <= 0) { + IVTV_DEBUG_WARN("failed to read mpeg decoder initialisation file %s\n", + IVTV_DECODE_INIT_MPEG_FILENAME); + } else { + ivtv_vapi(itv, CX2341X_DEC_SCHED_DMA_FROM_HOST, 3, 0, readbytes, 0); + ivtv_msleep_timeout(100, 0); + } + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 4, 0, 0, 0, 1); +} + +/* Try to restart the card & restore previous settings */ +int ivtv_firmware_restart(struct ivtv *itv) +{ + int rc = 0; + v4l2_std_id std; + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) + /* Display test image during restart */ + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_routing, + SAA7127_INPUT_TYPE_TEST_IMAGE, + itv->card->video_outputs[itv->active_output].video_output, + 0); + + mutex_lock(&itv->udma.lock); + + rc = ivtv_firmware_init(itv); + if (rc) { + mutex_unlock(&itv->udma.lock); + return rc; + } + + /* Allow settings to reload */ + ivtv_mailbox_cache_invalidate(itv); + + /* Restore encoder video standard */ + std = itv->std; + itv->std = 0; + ivtv_s_std_enc(itv, &std); + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + ivtv_init_mpeg_decoder(itv); + + /* Restore decoder video standard */ + std = itv->std_out; + itv->std_out = 0; + ivtv_s_std_dec(itv, &std); + + /* Restore framebuffer if active */ + if (itv->ivtvfb_restore) + itv->ivtvfb_restore(itv); + + /* Restore alpha settings */ + ivtv_set_osd_alpha(itv); + + /* Restore normal output */ + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_routing, + SAA7127_INPUT_TYPE_NORMAL, + itv->card->video_outputs[itv->active_output].video_output, + 0); + } + + mutex_unlock(&itv->udma.lock); + return rc; +} + +/* Check firmware running state. The checks fall through + allowing multiple failures to be logged. */ +int ivtv_firmware_check(struct ivtv *itv, char *where) +{ + int res = 0; + + /* Check encoder is still running */ + if (ivtv_vapi(itv, CX2341X_ENC_PING_FW, 0) < 0) { + IVTV_WARN("Encoder has died : %s\n", where); + res = -1; + } + + /* Also check audio. Only check if not in use & encoder is okay */ + if (!res && !atomic_read(&itv->capturing) && + (!atomic_read(&itv->decoding) || + (atomic_read(&itv->decoding) < 2 && test_bit(IVTV_F_I_DEC_YUV, + &itv->i_flags)))) { + + if (ivtv_vapi(itv, CX2341X_ENC_MISC, 1, 12) < 0) { + IVTV_WARN("Audio has died (Encoder OK) : %s\n", where); + res = -2; + } + } + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + /* Second audio check. Skip if audio already failed */ + if (res != -2 && read_dec(0x100) != read_dec(0x104)) { + /* Wait & try again to be certain. */ + ivtv_msleep_timeout(14, 0); + if (read_dec(0x100) != read_dec(0x104)) { + IVTV_WARN("Audio has died (Decoder) : %s\n", + where); + res = -1; + } + } + + /* Check decoder is still running */ + if (ivtv_vapi(itv, CX2341X_DEC_PING_FW, 0) < 0) { + IVTV_WARN("Decoder has died : %s\n", where); + res = -1; + } + } + + /* If something failed & currently idle, try to reload */ + if (res && !atomic_read(&itv->capturing) && + !atomic_read(&itv->decoding)) { + IVTV_INFO("Detected in %s that firmware had failed - " + "Reloading\n", where); + res = ivtv_firmware_restart(itv); + /* + * Even if restarted ok, still signal a problem had occurred. + * The caller can come through this function again to check + * if things are really ok after the restart. + */ + if (!res) { + IVTV_INFO("Firmware restart okay\n"); + res = -EAGAIN; + } else { + IVTV_INFO("Firmware restart failed\n"); + } + } else if (res) { + res = -EIO; + } + + return res; +} + +MODULE_FIRMWARE(CX2341X_FIRM_ENC_FILENAME); +MODULE_FIRMWARE(CX2341X_FIRM_DEC_FILENAME); +MODULE_FIRMWARE(IVTV_DECODE_INIT_MPEG_FILENAME); diff --git a/drivers/media/video/ivtv/ivtv-firmware.h b/drivers/media/pci/ivtv/ivtv-firmware.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-firmware.h rename to drivers/media/pci/ivtv/ivtv-firmware.h diff --git a/drivers/media/video/ivtv/ivtv-gpio.c b/drivers/media/pci/ivtv/ivtv-gpio.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-gpio.c rename to drivers/media/pci/ivtv/ivtv-gpio.c diff --git a/drivers/media/video/ivtv/ivtv-gpio.h b/drivers/media/pci/ivtv/ivtv-gpio.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-gpio.h rename to drivers/media/pci/ivtv/ivtv-gpio.h diff --git a/drivers/media/video/ivtv/ivtv-i2c.c b/drivers/media/pci/ivtv/ivtv-i2c.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-i2c.c rename to drivers/media/pci/ivtv/ivtv-i2c.c diff --git a/drivers/media/video/ivtv/ivtv-i2c.h b/drivers/media/pci/ivtv/ivtv-i2c.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-i2c.h rename to drivers/media/pci/ivtv/ivtv-i2c.h diff --git a/drivers/media/pci/ivtv/ivtv-ioctl.c b/drivers/media/pci/ivtv/ivtv-ioctl.c new file mode 100644 index 000000000000..949ae230e119 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-ioctl.c @@ -0,0 +1,1927 @@ +/* + ioctl system call + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2005-2007 Hans Verkuil + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-version.h" +#include "ivtv-mailbox.h" +#include "ivtv-i2c.h" +#include "ivtv-queue.h" +#include "ivtv-fileops.h" +#include "ivtv-vbi.h" +#include "ivtv-routing.h" +#include "ivtv-streams.h" +#include "ivtv-yuv.h" +#include "ivtv-ioctl.h" +#include "ivtv-gpio.h" +#include "ivtv-controls.h" +#include "ivtv-cards.h" +#include +#include +#include +#include +#include + +u16 ivtv_service2vbi(int type) +{ + switch (type) { + case V4L2_SLICED_TELETEXT_B: + return IVTV_SLICED_TYPE_TELETEXT_B; + case V4L2_SLICED_CAPTION_525: + return IVTV_SLICED_TYPE_CAPTION_525; + case V4L2_SLICED_WSS_625: + return IVTV_SLICED_TYPE_WSS_625; + case V4L2_SLICED_VPS: + return IVTV_SLICED_TYPE_VPS; + default: + return 0; + } +} + +static int valid_service_line(int field, int line, int is_pal) +{ + return (is_pal && line >= 6 && (line != 23 || field == 0)) || + (!is_pal && line >= 10 && line < 22); +} + +static u16 select_service_from_set(int field, int line, u16 set, int is_pal) +{ + u16 valid_set = (is_pal ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525); + int i; + + set = set & valid_set; + if (set == 0 || !valid_service_line(field, line, is_pal)) { + return 0; + } + if (!is_pal) { + if (line == 21 && (set & V4L2_SLICED_CAPTION_525)) + return V4L2_SLICED_CAPTION_525; + } + else { + if (line == 16 && field == 0 && (set & V4L2_SLICED_VPS)) + return V4L2_SLICED_VPS; + if (line == 23 && field == 0 && (set & V4L2_SLICED_WSS_625)) + return V4L2_SLICED_WSS_625; + if (line == 23) + return 0; + } + for (i = 0; i < 32; i++) { + if ((1 << i) & set) + return 1 << i; + } + return 0; +} + +void ivtv_expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + u16 set = fmt->service_set; + int f, l; + + fmt->service_set = 0; + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + fmt->service_lines[f][l] = select_service_from_set(f, l, set, is_pal); + } + } +} + +static void check_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + int f, l; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + fmt->service_lines[f][l] = select_service_from_set(f, l, fmt->service_lines[f][l], is_pal); + } + } +} + +u16 ivtv_get_service_set(struct v4l2_sliced_vbi_format *fmt) +{ + int f, l; + u16 set = 0; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + set |= fmt->service_lines[f][l]; + } + } + return set; +} + +void ivtv_set_osd_alpha(struct ivtv *itv) +{ + ivtv_vapi(itv, CX2341X_OSD_SET_GLOBAL_ALPHA, 3, + itv->osd_global_alpha_state, itv->osd_global_alpha, !itv->osd_local_alpha_state); + ivtv_vapi(itv, CX2341X_OSD_SET_CHROMA_KEY, 2, itv->osd_chroma_key_state, itv->osd_chroma_key); +} + +int ivtv_set_speed(struct ivtv *itv, int speed) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + int single_step = (speed == 1 || speed == -1); + DEFINE_WAIT(wait); + + if (speed == 0) speed = 1000; + + /* No change? */ + if (speed == itv->speed && !single_step) + return 0; + + if (single_step && (speed < 0) == (itv->speed < 0)) { + /* Single step video and no need to change direction */ + ivtv_vapi(itv, CX2341X_DEC_STEP_VIDEO, 1, 0); + itv->speed = speed; + return 0; + } + if (single_step) + /* Need to change direction */ + speed = speed < 0 ? -1000 : 1000; + + data[0] = (speed > 1000 || speed < -1000) ? 0x80000000 : 0; + data[0] |= (speed > 1000 || speed < -1500) ? 0x40000000 : 0; + data[1] = (speed < 0); + data[2] = speed < 0 ? 3 : 7; + data[3] = v4l2_ctrl_g_ctrl(itv->cxhdl.video_b_frames); + data[4] = (speed == 1500 || speed == 500) ? itv->speed_mute_audio : 0; + data[5] = 0; + data[6] = 0; + + if (speed == 1500 || speed == -1500) data[0] |= 1; + else if (speed == 2000 || speed == -2000) data[0] |= 2; + else if (speed > -1000 && speed < 0) data[0] |= (-1000 / speed); + else if (speed < 1000 && speed > 0) data[0] |= (1000 / speed); + + /* If not decoding, just change speed setting */ + if (atomic_read(&itv->decoding) > 0) { + int got_sig = 0; + + /* Stop all DMA and decoding activity */ + ivtv_vapi(itv, CX2341X_DEC_PAUSE_PLAYBACK, 1, 0); + + /* Wait for any DMA to finish */ + mutex_unlock(&itv->serialize_lock); + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + while (test_bit(IVTV_F_I_DMA, &itv->i_flags)) { + got_sig = signal_pending(current); + if (got_sig) + break; + got_sig = 0; + schedule(); + } + finish_wait(&itv->dma_waitq, &wait); + mutex_lock(&itv->serialize_lock); + if (got_sig) + return -EINTR; + + /* Change Speed safely */ + ivtv_api(itv, CX2341X_DEC_SET_PLAYBACK_SPEED, 7, data); + IVTV_DEBUG_INFO("Setting Speed to 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", + data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + } + if (single_step) { + speed = (speed < 0) ? -1 : 1; + ivtv_vapi(itv, CX2341X_DEC_STEP_VIDEO, 1, 0); + } + itv->speed = speed; + return 0; +} + +static int ivtv_validate_speed(int cur_speed, int new_speed) +{ + int fact = new_speed < 0 ? -1 : 1; + int s; + + if (cur_speed == 0) + cur_speed = 1000; + if (new_speed < 0) + new_speed = -new_speed; + if (cur_speed < 0) + cur_speed = -cur_speed; + + if (cur_speed <= new_speed) { + if (new_speed > 1500) + return fact * 2000; + if (new_speed > 1000) + return fact * 1500; + } + else { + if (new_speed >= 2000) + return fact * 2000; + if (new_speed >= 1500) + return fact * 1500; + if (new_speed >= 1000) + return fact * 1000; + } + if (new_speed == 0) + return 1000; + if (new_speed == 1 || new_speed == 1000) + return fact * new_speed; + + s = new_speed; + new_speed = 1000 / new_speed; + if (1000 / cur_speed == new_speed) + new_speed += (cur_speed < s) ? -1 : 1; + if (new_speed > 60) return 1000 / (fact * 60); + return 1000 / (fact * new_speed); +} + +static int ivtv_video_command(struct ivtv *itv, struct ivtv_open_id *id, + struct v4l2_decoder_cmd *dc, int try) +{ + struct ivtv_stream *s = &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + switch (dc->cmd) { + case V4L2_DEC_CMD_START: { + dc->flags &= V4L2_DEC_CMD_START_MUTE_AUDIO; + dc->start.speed = ivtv_validate_speed(itv->speed, dc->start.speed); + if (dc->start.speed < 0) + dc->start.format = V4L2_DEC_START_FMT_GOP; + else + dc->start.format = V4L2_DEC_START_FMT_NONE; + if (dc->start.speed != 500 && dc->start.speed != 1500) + dc->flags = dc->start.speed == 1000 ? 0 : + V4L2_DEC_CMD_START_MUTE_AUDIO; + if (try) break; + + itv->speed_mute_audio = dc->flags & V4L2_DEC_CMD_START_MUTE_AUDIO; + if (ivtv_set_output_mode(itv, OUT_MPG) != OUT_MPG) + return -EBUSY; + if (test_and_clear_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags)) { + /* forces ivtv_set_speed to be called */ + itv->speed = 0; + } + return ivtv_start_decoding(id, dc->start.speed); + } + + case V4L2_DEC_CMD_STOP: + dc->flags &= V4L2_DEC_CMD_STOP_IMMEDIATELY | V4L2_DEC_CMD_STOP_TO_BLACK; + if (dc->flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) + dc->stop.pts = 0; + if (try) break; + if (atomic_read(&itv->decoding) == 0) + return 0; + if (itv->output_mode != OUT_MPG) + return -EBUSY; + + itv->output_mode = OUT_NONE; + return ivtv_stop_v4l2_decode_stream(s, dc->flags, dc->stop.pts); + + case V4L2_DEC_CMD_PAUSE: + dc->flags &= V4L2_DEC_CMD_PAUSE_TO_BLACK; + if (try) break; + if (!atomic_read(&itv->decoding)) + return -EPERM; + if (itv->output_mode != OUT_MPG) + return -EBUSY; + if (atomic_read(&itv->decoding) > 0) { + ivtv_vapi(itv, CX2341X_DEC_PAUSE_PLAYBACK, 1, + (dc->flags & V4L2_DEC_CMD_PAUSE_TO_BLACK) ? 1 : 0); + set_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags); + } + break; + + case V4L2_DEC_CMD_RESUME: + dc->flags = 0; + if (try) break; + if (!atomic_read(&itv->decoding)) + return -EPERM; + if (itv->output_mode != OUT_MPG) + return -EBUSY; + if (test_and_clear_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags)) { + int speed = itv->speed; + itv->speed = 0; + return ivtv_start_decoding(id, speed); + } + break; + + default: + return -EINVAL; + } + return 0; +} + +static int ivtv_g_fmt_sliced_vbi_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = fh2id(fh)->itv; + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + if (!(itv->v4l2_cap & V4L2_CAP_SLICED_VBI_OUTPUT)) + return -EINVAL; + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + memset(vbifmt->service_lines, 0, sizeof(vbifmt->service_lines)); + if (itv->is_60hz) { + vbifmt->service_lines[0][21] = V4L2_SLICED_CAPTION_525; + vbifmt->service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } else { + vbifmt->service_lines[0][23] = V4L2_SLICED_WSS_625; + vbifmt->service_lines[0][16] = V4L2_SLICED_VPS; + } + vbifmt->service_set = ivtv_get_service_set(vbifmt); + return 0; +} + +static int ivtv_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + pixfmt->width = itv->cxhdl.width; + pixfmt->height = itv->cxhdl.height; + pixfmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + pixfmt->field = V4L2_FIELD_INTERLACED; + pixfmt->priv = 0; + if (id->type == IVTV_ENC_STREAM_TYPE_YUV) { + pixfmt->pixelformat = V4L2_PIX_FMT_HM12; + /* YUV size is (Y=(h*720) + UV=(h*(720/2))) */ + pixfmt->sizeimage = pixfmt->height * 720 * 3 / 2; + pixfmt->bytesperline = 720; + } else { + pixfmt->pixelformat = V4L2_PIX_FMT_MPEG; + pixfmt->sizeimage = 128 * 1024; + pixfmt->bytesperline = 0; + } + return 0; +} + +static int ivtv_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = fh2id(fh)->itv; + struct v4l2_vbi_format *vbifmt = &fmt->fmt.vbi; + + vbifmt->sampling_rate = 27000000; + vbifmt->offset = 248; + vbifmt->samples_per_line = itv->vbi.raw_decoder_line_size - 4; + vbifmt->sample_format = V4L2_PIX_FMT_GREY; + vbifmt->start[0] = itv->vbi.start[0]; + vbifmt->start[1] = itv->vbi.start[1]; + vbifmt->count[0] = vbifmt->count[1] = itv->vbi.count; + vbifmt->flags = 0; + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + return 0; +} + +static int ivtv_g_fmt_sliced_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + + if (id->type == IVTV_DEC_STREAM_TYPE_VBI) { + vbifmt->service_set = itv->is_50hz ? V4L2_SLICED_VBI_625 : + V4L2_SLICED_VBI_525; + ivtv_expand_service_set(vbifmt, itv->is_50hz); + vbifmt->service_set = ivtv_get_service_set(vbifmt); + return 0; + } + + v4l2_subdev_call(itv->sd_video, vbi, g_sliced_fmt, vbifmt); + vbifmt->service_set = ivtv_get_service_set(vbifmt); + return 0; +} + +static int ivtv_g_fmt_vid_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + pixfmt->width = itv->main_rect.width; + pixfmt->height = itv->main_rect.height; + pixfmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + pixfmt->field = V4L2_FIELD_INTERLACED; + pixfmt->priv = 0; + if (id->type == IVTV_DEC_STREAM_TYPE_YUV) { + switch (itv->yuv_info.lace_mode & IVTV_YUV_MODE_MASK) { + case IVTV_YUV_MODE_INTERLACED: + pixfmt->field = (itv->yuv_info.lace_mode & IVTV_YUV_SYNC_MASK) ? + V4L2_FIELD_INTERLACED_BT : V4L2_FIELD_INTERLACED_TB; + break; + case IVTV_YUV_MODE_PROGRESSIVE: + pixfmt->field = V4L2_FIELD_NONE; + break; + default: + pixfmt->field = V4L2_FIELD_ANY; + break; + } + pixfmt->pixelformat = V4L2_PIX_FMT_HM12; + pixfmt->bytesperline = 720; + pixfmt->width = itv->yuv_info.v4l2_src_w; + pixfmt->height = itv->yuv_info.v4l2_src_h; + /* YUV size is (Y=(h*w) + UV=(h*(w/2))) */ + pixfmt->sizeimage = + 1080 * ((pixfmt->height + 31) & ~31); + } else { + pixfmt->pixelformat = V4L2_PIX_FMT_MPEG; + pixfmt->sizeimage = 128 * 1024; + pixfmt->bytesperline = 0; + } + return 0; +} + +static int ivtv_g_fmt_vid_out_overlay(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = fh2id(fh)->itv; + struct v4l2_window *winfmt = &fmt->fmt.win; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + winfmt->chromakey = itv->osd_chroma_key; + winfmt->global_alpha = itv->osd_global_alpha; + winfmt->field = V4L2_FIELD_INTERLACED; + winfmt->clips = NULL; + winfmt->clipcount = 0; + winfmt->bitmap = NULL; + winfmt->w.top = winfmt->w.left = 0; + winfmt->w.width = itv->osd_rect.width; + winfmt->w.height = itv->osd_rect.height; + return 0; +} + +static int ivtv_try_fmt_sliced_vbi_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + return ivtv_g_fmt_sliced_vbi_out(file, fh, fmt); +} + +static int ivtv_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + int w = fmt->fmt.pix.width; + int h = fmt->fmt.pix.height; + int min_h = 2; + + w = min(w, 720); + w = max(w, 2); + if (id->type == IVTV_ENC_STREAM_TYPE_YUV) { + /* YUV height must be a multiple of 32 */ + h &= ~0x1f; + min_h = 32; + } + h = min(h, itv->is_50hz ? 576 : 480); + h = max(h, min_h); + ivtv_g_fmt_vid_cap(file, fh, fmt); + fmt->fmt.pix.width = w; + fmt->fmt.pix.height = h; + return 0; +} + +static int ivtv_try_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + return ivtv_g_fmt_vbi_cap(file, fh, fmt); +} + +static int ivtv_try_fmt_sliced_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + + if (id->type == IVTV_DEC_STREAM_TYPE_VBI) + return ivtv_g_fmt_sliced_vbi_cap(file, fh, fmt); + + /* set sliced VBI capture format */ + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + vbifmt->reserved[0] = 0; + vbifmt->reserved[1] = 0; + + if (vbifmt->service_set) + ivtv_expand_service_set(vbifmt, itv->is_50hz); + check_service_set(vbifmt, itv->is_50hz); + vbifmt->service_set = ivtv_get_service_set(vbifmt); + return 0; +} + +static int ivtv_try_fmt_vid_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = fh2id(fh); + s32 w = fmt->fmt.pix.width; + s32 h = fmt->fmt.pix.height; + int field = fmt->fmt.pix.field; + int ret = ivtv_g_fmt_vid_out(file, fh, fmt); + + w = min(w, 720); + w = max(w, 2); + /* Why can the height be 576 even when the output is NTSC? + + Internally the buffers of the PVR350 are always set to 720x576. The + decoded video frame will always be placed in the top left corner of + this buffer. For any video which is not 720x576, the buffer will + then be cropped to remove the unused right and lower areas, with + the remaining image being scaled by the hardware to fit the display + area. The video can be scaled both up and down, so a 720x480 video + can be displayed full-screen on PAL and a 720x576 video can be + displayed without cropping on NTSC. + + Note that the scaling only occurs on the video stream, the osd + resolution is locked to the broadcast standard and not scaled. + + Thanks to Ian Armstrong for this explanation. */ + h = min(h, 576); + h = max(h, 2); + if (id->type == IVTV_DEC_STREAM_TYPE_YUV) + fmt->fmt.pix.field = field; + fmt->fmt.pix.width = w; + fmt->fmt.pix.height = h; + return ret; +} + +static int ivtv_try_fmt_vid_out_overlay(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = fh2id(fh)->itv; + u32 chromakey = fmt->fmt.win.chromakey; + u8 global_alpha = fmt->fmt.win.global_alpha; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + ivtv_g_fmt_vid_out_overlay(file, fh, fmt); + fmt->fmt.win.chromakey = chromakey; + fmt->fmt.win.global_alpha = global_alpha; + return 0; +} + +static int ivtv_s_fmt_sliced_vbi_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + return ivtv_g_fmt_sliced_vbi_out(file, fh, fmt); +} + +static int ivtv_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + struct v4l2_mbus_framefmt mbus_fmt; + int ret = ivtv_try_fmt_vid_cap(file, fh, fmt); + int w = fmt->fmt.pix.width; + int h = fmt->fmt.pix.height; + + if (ret) + return ret; + + if (itv->cxhdl.width == w && itv->cxhdl.height == h) + return 0; + + if (atomic_read(&itv->capturing) > 0) + return -EBUSY; + + itv->cxhdl.width = w; + itv->cxhdl.height = h; + if (v4l2_ctrl_g_ctrl(itv->cxhdl.video_encoding) == V4L2_MPEG_VIDEO_ENCODING_MPEG_1) + fmt->fmt.pix.width /= 2; + mbus_fmt.width = fmt->fmt.pix.width; + mbus_fmt.height = h; + mbus_fmt.code = V4L2_MBUS_FMT_FIXED; + v4l2_subdev_call(itv->sd_video, video, s_mbus_fmt, &mbus_fmt); + return ivtv_g_fmt_vid_cap(file, fh, fmt); +} + +static int ivtv_s_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if (!ivtv_raw_vbi(itv) && atomic_read(&itv->capturing) > 0) + return -EBUSY; + itv->vbi.sliced_in->service_set = 0; + itv->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE; + v4l2_subdev_call(itv->sd_video, vbi, s_raw_fmt, &fmt->fmt.vbi); + return ivtv_g_fmt_vbi_cap(file, fh, fmt); +} + +static int ivtv_s_fmt_sliced_vbi_cap(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + int ret = ivtv_try_fmt_sliced_vbi_cap(file, fh, fmt); + + if (ret || id->type == IVTV_DEC_STREAM_TYPE_VBI) + return ret; + + check_service_set(vbifmt, itv->is_50hz); + if (ivtv_raw_vbi(itv) && atomic_read(&itv->capturing) > 0) + return -EBUSY; + itv->vbi.in.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE; + v4l2_subdev_call(itv->sd_video, vbi, s_sliced_fmt, vbifmt); + memcpy(itv->vbi.sliced_in, vbifmt, sizeof(*itv->vbi.sliced_in)); + return 0; +} + +static int ivtv_s_fmt_vid_out(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + int ret = ivtv_try_fmt_vid_out(file, fh, fmt); + + if (ret) + return ret; + + if (id->type != IVTV_DEC_STREAM_TYPE_YUV) + return 0; + + /* Return now if we already have some frame data */ + if (yi->stream_size) + return -EBUSY; + + yi->v4l2_src_w = fmt->fmt.pix.width; + yi->v4l2_src_h = fmt->fmt.pix.height; + + switch (fmt->fmt.pix.field) { + case V4L2_FIELD_NONE: + yi->lace_mode = IVTV_YUV_MODE_PROGRESSIVE; + break; + case V4L2_FIELD_ANY: + yi->lace_mode = IVTV_YUV_MODE_AUTO; + break; + case V4L2_FIELD_INTERLACED_BT: + yi->lace_mode = + IVTV_YUV_MODE_INTERLACED|IVTV_YUV_SYNC_ODD; + break; + case V4L2_FIELD_INTERLACED_TB: + default: + yi->lace_mode = IVTV_YUV_MODE_INTERLACED; + break; + } + yi->lace_sync_field = (yi->lace_mode & IVTV_YUV_SYNC_MASK) == IVTV_YUV_SYNC_EVEN ? 0 : 1; + + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) + itv->dma_data_req_size = + 1080 * ((yi->v4l2_src_h + 31) & ~31); + + return 0; +} + +static int ivtv_s_fmt_vid_out_overlay(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct ivtv *itv = fh2id(fh)->itv; + int ret = ivtv_try_fmt_vid_out_overlay(file, fh, fmt); + + if (ret == 0) { + itv->osd_chroma_key = fmt->fmt.win.chromakey; + itv->osd_global_alpha = fmt->fmt.win.global_alpha; + ivtv_set_osd_alpha(itv); + } + return ret; +} + +static int ivtv_g_chip_ident(struct file *file, void *fh, struct v4l2_dbg_chip_ident *chip) +{ + struct ivtv *itv = fh2id(fh)->itv; + + chip->ident = V4L2_IDENT_NONE; + chip->revision = 0; + if (chip->match.type == V4L2_CHIP_MATCH_HOST) { + if (v4l2_chip_match_host(&chip->match)) + chip->ident = itv->has_cx23415 ? V4L2_IDENT_CX23415 : V4L2_IDENT_CX23416; + return 0; + } + if (chip->match.type != V4L2_CHIP_MATCH_I2C_DRIVER && + chip->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + /* TODO: is this correct? */ + return ivtv_call_all_err(itv, core, g_chip_ident, chip); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ivtv_itvc(struct ivtv *itv, unsigned int cmd, void *arg) +{ + struct v4l2_dbg_register *regs = arg; + volatile u8 __iomem *reg_start; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (regs->reg >= IVTV_REG_OFFSET && regs->reg < IVTV_REG_OFFSET + IVTV_REG_SIZE) + reg_start = itv->reg_mem - IVTV_REG_OFFSET; + else if (itv->has_cx23415 && regs->reg >= IVTV_DECODER_OFFSET && + regs->reg < IVTV_DECODER_OFFSET + IVTV_DECODER_SIZE) + reg_start = itv->dec_mem - IVTV_DECODER_OFFSET; + else if (regs->reg < IVTV_ENCODER_SIZE) + reg_start = itv->enc_mem; + else + return -EINVAL; + + regs->size = 4; + if (cmd == VIDIOC_DBG_G_REGISTER) + regs->val = readl(regs->reg + reg_start); + else + writel(regs->val, regs->reg + reg_start); + return 0; +} + +static int ivtv_g_register(struct file *file, void *fh, struct v4l2_dbg_register *reg) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if (v4l2_chip_match_host(®->match)) + return ivtv_itvc(itv, VIDIOC_DBG_G_REGISTER, reg); + /* TODO: subdev errors should not be ignored, this should become a + subdev helper function. */ + ivtv_call_all(itv, core, g_register, reg); + return 0; +} + +static int ivtv_s_register(struct file *file, void *fh, struct v4l2_dbg_register *reg) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if (v4l2_chip_match_host(®->match)) + return ivtv_itvc(itv, VIDIOC_DBG_S_REGISTER, reg); + /* TODO: subdev errors should not be ignored, this should become a + subdev helper function. */ + ivtv_call_all(itv, core, s_register, reg); + return 0; +} +#endif + +static int ivtv_querycap(struct file *file, void *fh, struct v4l2_capability *vcap) +{ + struct ivtv_open_id *id = fh2id(file->private_data); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + strlcpy(vcap->driver, IVTV_DRIVER_NAME, sizeof(vcap->driver)); + strlcpy(vcap->card, itv->card_name, sizeof(vcap->card)); + snprintf(vcap->bus_info, sizeof(vcap->bus_info), "PCI:%s", pci_name(itv->pdev)); + vcap->capabilities = itv->v4l2_cap | V4L2_CAP_DEVICE_CAPS; + vcap->device_caps = s->caps; + return 0; +} + +static int ivtv_enumaudio(struct file *file, void *fh, struct v4l2_audio *vin) +{ + struct ivtv *itv = fh2id(fh)->itv; + + return ivtv_get_audio_input(itv, vin->index, vin); +} + +static int ivtv_g_audio(struct file *file, void *fh, struct v4l2_audio *vin) +{ + struct ivtv *itv = fh2id(fh)->itv; + + vin->index = itv->audio_input; + return ivtv_get_audio_input(itv, vin->index, vin); +} + +static int ivtv_s_audio(struct file *file, void *fh, const struct v4l2_audio *vout) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if (vout->index >= itv->nof_audio_inputs) + return -EINVAL; + + itv->audio_input = vout->index; + ivtv_audio_set_io(itv); + + return 0; +} + +static int ivtv_enumaudout(struct file *file, void *fh, struct v4l2_audioout *vin) +{ + struct ivtv *itv = fh2id(fh)->itv; + + /* set it to defaults from our table */ + return ivtv_get_audio_output(itv, vin->index, vin); +} + +static int ivtv_g_audout(struct file *file, void *fh, struct v4l2_audioout *vin) +{ + struct ivtv *itv = fh2id(fh)->itv; + + vin->index = 0; + return ivtv_get_audio_output(itv, vin->index, vin); +} + +static int ivtv_s_audout(struct file *file, void *fh, const struct v4l2_audioout *vout) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if (itv->card->video_outputs == NULL || vout->index != 0) + return -EINVAL; + return 0; +} + +static int ivtv_enum_input(struct file *file, void *fh, struct v4l2_input *vin) +{ + struct ivtv *itv = fh2id(fh)->itv; + + /* set it to defaults from our table */ + return ivtv_get_input(itv, vin->index, vin); +} + +static int ivtv_enum_output(struct file *file, void *fh, struct v4l2_output *vout) +{ + struct ivtv *itv = fh2id(fh)->itv; + + return ivtv_get_output(itv, vout->index, vout); +} + +static int ivtv_cropcap(struct file *file, void *fh, struct v4l2_cropcap *cropcap) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + int streamtype; + + streamtype = id->type; + + if (cropcap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + cropcap->bounds.top = cropcap->bounds.left = 0; + cropcap->bounds.width = 720; + if (cropcap->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + cropcap->bounds.height = itv->is_50hz ? 576 : 480; + cropcap->pixelaspect.numerator = itv->is_50hz ? 59 : 10; + cropcap->pixelaspect.denominator = itv->is_50hz ? 54 : 11; + } else if (streamtype == IVTV_DEC_STREAM_TYPE_YUV) { + if (yi->track_osd) { + cropcap->bounds.width = yi->osd_full_w; + cropcap->bounds.height = yi->osd_full_h; + } else { + cropcap->bounds.width = 720; + cropcap->bounds.height = + itv->is_out_50hz ? 576 : 480; + } + cropcap->pixelaspect.numerator = itv->is_out_50hz ? 59 : 10; + cropcap->pixelaspect.denominator = itv->is_out_50hz ? 54 : 11; + } else { + cropcap->bounds.height = itv->is_out_50hz ? 576 : 480; + cropcap->pixelaspect.numerator = itv->is_out_50hz ? 59 : 10; + cropcap->pixelaspect.denominator = itv->is_out_50hz ? 54 : 11; + } + cropcap->defrect = cropcap->bounds; + return 0; +} + +static int ivtv_s_crop(struct file *file, void *fh, const struct v4l2_crop *crop) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + int streamtype; + + streamtype = id->type; + + if (crop->type == V4L2_BUF_TYPE_VIDEO_OUTPUT && + (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) { + if (streamtype == IVTV_DEC_STREAM_TYPE_YUV) { + yi->main_rect = crop->c; + return 0; + } else { + if (!ivtv_vapi(itv, CX2341X_OSD_SET_FRAMEBUFFER_WINDOW, 4, + crop->c.width, crop->c.height, crop->c.left, crop->c.top)) { + itv->main_rect = crop->c; + return 0; + } + } + return -EINVAL; + } + return -EINVAL; +} + +static int ivtv_g_crop(struct file *file, void *fh, struct v4l2_crop *crop) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + int streamtype; + + streamtype = id->type; + + if (crop->type == V4L2_BUF_TYPE_VIDEO_OUTPUT && + (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) { + if (streamtype == IVTV_DEC_STREAM_TYPE_YUV) + crop->c = yi->main_rect; + else + crop->c = itv->main_rect; + return 0; + } + return -EINVAL; +} + +static int ivtv_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) +{ + static const struct v4l2_fmtdesc hm12 = { + 0, V4L2_BUF_TYPE_VIDEO_CAPTURE, 0, + "HM12 (YUV 4:2:0)", V4L2_PIX_FMT_HM12, + { 0, 0, 0, 0 } + }; + static const struct v4l2_fmtdesc mpeg = { + 0, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FMT_FLAG_COMPRESSED, + "MPEG", V4L2_PIX_FMT_MPEG, + { 0, 0, 0, 0 } + }; + struct ivtv *itv = fh2id(fh)->itv; + struct ivtv_stream *s = &itv->streams[fh2id(fh)->type]; + + if (fmt->index) + return -EINVAL; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + *fmt = mpeg; + else if (s->type == IVTV_ENC_STREAM_TYPE_YUV) + *fmt = hm12; + else + return -EINVAL; + return 0; +} + +static int ivtv_enum_fmt_vid_out(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) +{ + static const struct v4l2_fmtdesc hm12 = { + 0, V4L2_BUF_TYPE_VIDEO_OUTPUT, 0, + "HM12 (YUV 4:2:0)", V4L2_PIX_FMT_HM12, + { 0, 0, 0, 0 } + }; + static const struct v4l2_fmtdesc mpeg = { + 0, V4L2_BUF_TYPE_VIDEO_OUTPUT, V4L2_FMT_FLAG_COMPRESSED, + "MPEG", V4L2_PIX_FMT_MPEG, + { 0, 0, 0, 0 } + }; + struct ivtv *itv = fh2id(fh)->itv; + struct ivtv_stream *s = &itv->streams[fh2id(fh)->type]; + + if (fmt->index) + return -EINVAL; + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + *fmt = mpeg; + else if (s->type == IVTV_DEC_STREAM_TYPE_YUV) + *fmt = hm12; + else + return -EINVAL; + return 0; +} + +static int ivtv_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct ivtv *itv = fh2id(fh)->itv; + + *i = itv->active_input; + + return 0; +} + +int ivtv_s_input(struct file *file, void *fh, unsigned int inp) +{ + struct ivtv *itv = fh2id(fh)->itv; + v4l2_std_id std; + int i; + + if (inp < 0 || inp >= itv->nof_inputs) + return -EINVAL; + + if (inp == itv->active_input) { + IVTV_DEBUG_INFO("Input unchanged\n"); + return 0; + } + + if (atomic_read(&itv->capturing) > 0) { + return -EBUSY; + } + + IVTV_DEBUG_INFO("Changing input from %d to %d\n", + itv->active_input, inp); + + itv->active_input = inp; + /* Set the audio input to whatever is appropriate for the + input type. */ + itv->audio_input = itv->card->video_inputs[inp].audio_index; + + if (itv->card->video_inputs[inp].video_type == IVTV_CARD_INPUT_VID_TUNER) + std = itv->tuner_std; + else + std = V4L2_STD_ALL; + for (i = 0; i <= IVTV_ENC_STREAM_TYPE_VBI; i++) + itv->streams[i].vdev->tvnorms = std; + + /* prevent others from messing with the streams until + we're finished changing inputs. */ + ivtv_mute(itv); + ivtv_video_set_io(itv); + ivtv_audio_set_io(itv); + ivtv_unmute(itv); + + return 0; +} + +static int ivtv_g_output(struct file *file, void *fh, unsigned int *i) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + *i = itv->active_output; + + return 0; +} + +static int ivtv_s_output(struct file *file, void *fh, unsigned int outp) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if (outp >= itv->card->nof_outputs) + return -EINVAL; + + if (outp == itv->active_output) { + IVTV_DEBUG_INFO("Output unchanged\n"); + return 0; + } + IVTV_DEBUG_INFO("Changing output from %d to %d\n", + itv->active_output, outp); + + itv->active_output = outp; + ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_routing, + SAA7127_INPUT_TYPE_NORMAL, + itv->card->video_outputs[outp].video_output, 0); + + return 0; +} + +static int ivtv_g_frequency(struct file *file, void *fh, struct v4l2_frequency *vf) +{ + struct ivtv *itv = fh2id(fh)->itv; + struct ivtv_stream *s = &itv->streams[fh2id(fh)->type]; + + if (s->vdev->vfl_dir) + return -ENOTTY; + if (vf->tuner != 0) + return -EINVAL; + + ivtv_call_all(itv, tuner, g_frequency, vf); + return 0; +} + +int ivtv_s_frequency(struct file *file, void *fh, struct v4l2_frequency *vf) +{ + struct ivtv *itv = fh2id(fh)->itv; + struct ivtv_stream *s = &itv->streams[fh2id(fh)->type]; + + if (s->vdev->vfl_dir) + return -ENOTTY; + if (vf->tuner != 0) + return -EINVAL; + + ivtv_mute(itv); + IVTV_DEBUG_INFO("v4l2 ioctl: set frequency %d\n", vf->frequency); + ivtv_call_all(itv, tuner, s_frequency, vf); + ivtv_unmute(itv); + return 0; +} + +static int ivtv_g_std(struct file *file, void *fh, v4l2_std_id *std) +{ + struct ivtv *itv = fh2id(fh)->itv; + + *std = itv->std; + return 0; +} + +void ivtv_s_std_enc(struct ivtv *itv, v4l2_std_id *std) +{ + itv->std = *std; + itv->is_60hz = (*std & V4L2_STD_525_60) ? 1 : 0; + itv->is_50hz = !itv->is_60hz; + cx2341x_handler_set_50hz(&itv->cxhdl, itv->is_50hz); + itv->cxhdl.width = 720; + itv->cxhdl.height = itv->is_50hz ? 576 : 480; + itv->vbi.count = itv->is_50hz ? 18 : 12; + itv->vbi.start[0] = itv->is_50hz ? 6 : 10; + itv->vbi.start[1] = itv->is_50hz ? 318 : 273; + + if (itv->hw_flags & IVTV_HW_CX25840) + itv->vbi.sliced_decoder_line_size = itv->is_60hz ? 272 : 284; + + /* Tuner */ + ivtv_call_all(itv, core, s_std, itv->std); +} + +void ivtv_s_std_dec(struct ivtv *itv, v4l2_std_id *std) +{ + struct yuv_playback_info *yi = &itv->yuv_info; + DEFINE_WAIT(wait); + int f; + + /* set display standard */ + itv->std_out = *std; + itv->is_out_60hz = (*std & V4L2_STD_525_60) ? 1 : 0; + itv->is_out_50hz = !itv->is_out_60hz; + ivtv_call_all(itv, video, s_std_output, itv->std_out); + + /* + * The next firmware call is time sensitive. Time it to + * avoid risk of a hard lock, by trying to ensure the call + * happens within the first 100 lines of the top field. + * Make 4 attempts to sync to the decoder before giving up. + */ + mutex_unlock(&itv->serialize_lock); + for (f = 0; f < 4; f++) { + prepare_to_wait(&itv->vsync_waitq, &wait, + TASK_UNINTERRUPTIBLE); + if ((read_reg(IVTV_REG_DEC_LINE_FIELD) >> 16) < 100) + break; + schedule_timeout(msecs_to_jiffies(25)); + } + finish_wait(&itv->vsync_waitq, &wait); + mutex_lock(&itv->serialize_lock); + + if (f == 4) + IVTV_WARN("Mode change failed to sync to decoder\n"); + + ivtv_vapi(itv, CX2341X_DEC_SET_STANDARD, 1, itv->is_out_50hz); + itv->main_rect.left = 0; + itv->main_rect.top = 0; + itv->main_rect.width = 720; + itv->main_rect.height = itv->is_out_50hz ? 576 : 480; + ivtv_vapi(itv, CX2341X_OSD_SET_FRAMEBUFFER_WINDOW, 4, + 720, itv->main_rect.height, 0, 0); + yi->main_rect = itv->main_rect; + if (!itv->osd_info) { + yi->osd_full_w = 720; + yi->osd_full_h = itv->is_out_50hz ? 576 : 480; + } +} + +int ivtv_s_std(struct file *file, void *fh, v4l2_std_id *std) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if ((*std & V4L2_STD_ALL) == 0) + return -EINVAL; + + if (*std == itv->std) + return 0; + + if (test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags) || + atomic_read(&itv->capturing) > 0 || + atomic_read(&itv->decoding) > 0) { + /* Switching standard would mess with already running + streams, prevent that by returning EBUSY. */ + return -EBUSY; + } + + IVTV_DEBUG_INFO("Switching standard to %llx.\n", + (unsigned long long)itv->std); + + ivtv_s_std_enc(itv, std); + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) + ivtv_s_std_dec(itv, std); + + return 0; +} + +static int ivtv_s_tuner(struct file *file, void *fh, struct v4l2_tuner *vt) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + + if (vt->index != 0) + return -EINVAL; + + ivtv_call_all(itv, tuner, s_tuner, vt); + + return 0; +} + +static int ivtv_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if (vt->index != 0) + return -EINVAL; + + ivtv_call_all(itv, tuner, g_tuner, vt); + + if (vt->type == V4L2_TUNER_RADIO) + strlcpy(vt->name, "ivtv Radio Tuner", sizeof(vt->name)); + else + strlcpy(vt->name, "ivtv TV Tuner", sizeof(vt->name)); + return 0; +} + +static int ivtv_g_sliced_vbi_cap(struct file *file, void *fh, struct v4l2_sliced_vbi_cap *cap) +{ + struct ivtv *itv = fh2id(fh)->itv; + int set = itv->is_50hz ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525; + int f, l; + + if (cap->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) { + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + if (valid_service_line(f, l, itv->is_50hz)) + cap->service_lines[f][l] = set; + } + } + } else if (cap->type == V4L2_BUF_TYPE_SLICED_VBI_OUTPUT) { + if (!(itv->v4l2_cap & V4L2_CAP_SLICED_VBI_OUTPUT)) + return -EINVAL; + if (itv->is_60hz) { + cap->service_lines[0][21] = V4L2_SLICED_CAPTION_525; + cap->service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } else { + cap->service_lines[0][23] = V4L2_SLICED_WSS_625; + cap->service_lines[0][16] = V4L2_SLICED_VPS; + } + } else { + return -EINVAL; + } + + set = 0; + for (f = 0; f < 2; f++) + for (l = 0; l < 24; l++) + set |= cap->service_lines[f][l]; + cap->service_set = set; + return 0; +} + +static int ivtv_g_enc_index(struct file *file, void *fh, struct v4l2_enc_idx *idx) +{ + struct ivtv *itv = fh2id(fh)->itv; + struct v4l2_enc_idx_entry *e = idx->entry; + int entries; + int i; + + entries = (itv->pgm_info_write_idx + IVTV_MAX_PGM_INDEX - itv->pgm_info_read_idx) % + IVTV_MAX_PGM_INDEX; + if (entries > V4L2_ENC_IDX_ENTRIES) + entries = V4L2_ENC_IDX_ENTRIES; + idx->entries = 0; + idx->entries_cap = IVTV_MAX_PGM_INDEX; + if (!atomic_read(&itv->capturing)) + return 0; + for (i = 0; i < entries; i++) { + *e = itv->pgm_info[(itv->pgm_info_read_idx + i) % IVTV_MAX_PGM_INDEX]; + if ((e->flags & V4L2_ENC_IDX_FRAME_MASK) <= V4L2_ENC_IDX_FRAME_B) { + idx->entries++; + e++; + } + } + itv->pgm_info_read_idx = (itv->pgm_info_read_idx + idx->entries) % IVTV_MAX_PGM_INDEX; + return 0; +} + +static int ivtv_encoder_cmd(struct file *file, void *fh, struct v4l2_encoder_cmd *enc) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + + + switch (enc->cmd) { + case V4L2_ENC_CMD_START: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_START\n"); + enc->flags = 0; + return ivtv_start_capture(id); + + case V4L2_ENC_CMD_STOP: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_STOP\n"); + enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END; + ivtv_stop_capture(id, enc->flags & V4L2_ENC_CMD_STOP_AT_GOP_END); + return 0; + + case V4L2_ENC_CMD_PAUSE: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_PAUSE\n"); + enc->flags = 0; + + if (!atomic_read(&itv->capturing)) + return -EPERM; + if (test_and_set_bit(IVTV_F_I_ENC_PAUSED, &itv->i_flags)) + return 0; + + ivtv_mute(itv); + ivtv_vapi(itv, CX2341X_ENC_PAUSE_ENCODER, 1, 0); + break; + + case V4L2_ENC_CMD_RESUME: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_RESUME\n"); + enc->flags = 0; + + if (!atomic_read(&itv->capturing)) + return -EPERM; + + if (!test_and_clear_bit(IVTV_F_I_ENC_PAUSED, &itv->i_flags)) + return 0; + + ivtv_vapi(itv, CX2341X_ENC_PAUSE_ENCODER, 1, 1); + ivtv_unmute(itv); + break; + default: + IVTV_DEBUG_IOCTL("Unknown cmd %d\n", enc->cmd); + return -EINVAL; + } + + return 0; +} + +static int ivtv_try_encoder_cmd(struct file *file, void *fh, struct v4l2_encoder_cmd *enc) +{ + struct ivtv *itv = fh2id(fh)->itv; + + switch (enc->cmd) { + case V4L2_ENC_CMD_START: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_START\n"); + enc->flags = 0; + return 0; + + case V4L2_ENC_CMD_STOP: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_STOP\n"); + enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END; + return 0; + + case V4L2_ENC_CMD_PAUSE: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_PAUSE\n"); + enc->flags = 0; + return 0; + + case V4L2_ENC_CMD_RESUME: + IVTV_DEBUG_IOCTL("V4L2_ENC_CMD_RESUME\n"); + enc->flags = 0; + return 0; + default: + IVTV_DEBUG_IOCTL("Unknown cmd %d\n", enc->cmd); + return -EINVAL; + } +} + +static int ivtv_g_fbuf(struct file *file, void *fh, struct v4l2_framebuffer *fb) +{ + struct ivtv *itv = fh2id(fh)->itv; + u32 data[CX2341X_MBOX_MAX_DATA]; + struct yuv_playback_info *yi = &itv->yuv_info; + + int pixfmt; + static u32 pixel_format[16] = { + V4L2_PIX_FMT_PAL8, /* Uses a 256-entry RGB colormap */ + V4L2_PIX_FMT_RGB565, + V4L2_PIX_FMT_RGB555, + V4L2_PIX_FMT_RGB444, + V4L2_PIX_FMT_RGB32, + 0, + 0, + 0, + V4L2_PIX_FMT_PAL8, /* Uses a 256-entry YUV colormap */ + V4L2_PIX_FMT_YUV565, + V4L2_PIX_FMT_YUV555, + V4L2_PIX_FMT_YUV444, + V4L2_PIX_FMT_YUV32, + 0, + 0, + 0, + }; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + return -EINVAL; + if (!itv->osd_video_pbase) + return -EINVAL; + + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY | V4L2_FBUF_CAP_CHROMAKEY | + V4L2_FBUF_CAP_GLOBAL_ALPHA; + + ivtv_vapi_result(itv, data, CX2341X_OSD_GET_STATE, 0); + data[0] |= (read_reg(0x2a00) >> 7) & 0x40; + pixfmt = (data[0] >> 3) & 0xf; + + fb->fmt.pixelformat = pixel_format[pixfmt]; + fb->fmt.width = itv->osd_rect.width; + fb->fmt.height = itv->osd_rect.height; + fb->fmt.field = V4L2_FIELD_INTERLACED; + fb->fmt.bytesperline = fb->fmt.width; + fb->fmt.colorspace = V4L2_COLORSPACE_SMPTE170M; + fb->fmt.field = V4L2_FIELD_INTERLACED; + fb->fmt.priv = 0; + if (fb->fmt.pixelformat != V4L2_PIX_FMT_PAL8) + fb->fmt.bytesperline *= 2; + if (fb->fmt.pixelformat == V4L2_PIX_FMT_RGB32 || + fb->fmt.pixelformat == V4L2_PIX_FMT_YUV32) + fb->fmt.bytesperline *= 2; + fb->fmt.sizeimage = fb->fmt.bytesperline * fb->fmt.height; + fb->base = (void *)itv->osd_video_pbase; + fb->flags = 0; + + if (itv->osd_chroma_key_state) + fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY; + + if (itv->osd_global_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA; + + if (yi->track_osd) + fb->flags |= V4L2_FBUF_FLAG_OVERLAY; + + pixfmt &= 7; + + /* no local alpha for RGB565 or unknown formats */ + if (pixfmt == 1 || pixfmt > 4) + return 0; + + /* 16-bit formats have inverted local alpha */ + if (pixfmt == 2 || pixfmt == 3) + fb->capability |= V4L2_FBUF_CAP_LOCAL_INV_ALPHA; + else + fb->capability |= V4L2_FBUF_CAP_LOCAL_ALPHA; + + if (itv->osd_local_alpha_state) { + /* 16-bit formats have inverted local alpha */ + if (pixfmt == 2 || pixfmt == 3) + fb->flags |= V4L2_FBUF_FLAG_LOCAL_INV_ALPHA; + else + fb->flags |= V4L2_FBUF_FLAG_LOCAL_ALPHA; + } + + return 0; +} + +static int ivtv_s_fbuf(struct file *file, void *fh, const struct v4l2_framebuffer *fb) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + return -EINVAL; + if (!itv->osd_video_pbase) + return -EINVAL; + + itv->osd_global_alpha_state = (fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0; + itv->osd_local_alpha_state = + (fb->flags & (V4L2_FBUF_FLAG_LOCAL_ALPHA|V4L2_FBUF_FLAG_LOCAL_INV_ALPHA)) != 0; + itv->osd_chroma_key_state = (fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0; + ivtv_set_osd_alpha(itv); + yi->track_osd = (fb->flags & V4L2_FBUF_FLAG_OVERLAY) != 0; + return 0; +} + +static int ivtv_overlay(struct file *file, void *fh, unsigned int on) +{ + struct ivtv_open_id *id = fh2id(fh); + struct ivtv *itv = id->itv; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + return -EINVAL; + + ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, on != 0); + + return 0; +} + +static int ivtv_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_VSYNC: + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + case V4L2_EVENT_CTRL: + return v4l2_event_subscribe(fh, sub, 0, &v4l2_ctrl_sub_ev_ops); + default: + return -EINVAL; + } +} + +static int ivtv_log_status(struct file *file, void *fh) +{ + struct ivtv *itv = fh2id(fh)->itv; + u32 data[CX2341X_MBOX_MAX_DATA]; + + int has_output = itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT; + struct v4l2_input vidin; + struct v4l2_audio audin; + int i; + + IVTV_INFO("Version: %s Card: %s\n", IVTV_VERSION, itv->card_name); + if (itv->hw_flags & IVTV_HW_TVEEPROM) { + struct tveeprom tv; + + ivtv_read_eeprom(itv, &tv); + } + ivtv_call_all(itv, core, log_status); + ivtv_get_input(itv, itv->active_input, &vidin); + ivtv_get_audio_input(itv, itv->audio_input, &audin); + IVTV_INFO("Video Input: %s\n", vidin.name); + IVTV_INFO("Audio Input: %s%s\n", audin.name, + (itv->dualwatch_stereo_mode & ~0x300) == 0x200 ? " (Bilingual)" : ""); + if (has_output) { + struct v4l2_output vidout; + struct v4l2_audioout audout; + int mode = itv->output_mode; + static const char * const output_modes[5] = { + "None", + "MPEG Streaming", + "YUV Streaming", + "YUV Frames", + "Passthrough", + }; + static const char * const alpha_mode[4] = { + "None", + "Global", + "Local", + "Global and Local" + }; + static const char * const pixel_format[16] = { + "ARGB Indexed", + "RGB 5:6:5", + "ARGB 1:5:5:5", + "ARGB 1:4:4:4", + "ARGB 8:8:8:8", + "5", + "6", + "7", + "AYUV Indexed", + "YUV 5:6:5", + "AYUV 1:5:5:5", + "AYUV 1:4:4:4", + "AYUV 8:8:8:8", + "13", + "14", + "15", + }; + + ivtv_get_output(itv, itv->active_output, &vidout); + ivtv_get_audio_output(itv, 0, &audout); + IVTV_INFO("Video Output: %s\n", vidout.name); + if (mode < 0 || mode > OUT_PASSTHROUGH) + mode = OUT_NONE; + IVTV_INFO("Output Mode: %s\n", output_modes[mode]); + ivtv_vapi_result(itv, data, CX2341X_OSD_GET_STATE, 0); + data[0] |= (read_reg(0x2a00) >> 7) & 0x40; + IVTV_INFO("Overlay: %s, Alpha: %s, Pixel Format: %s\n", + data[0] & 1 ? "On" : "Off", + alpha_mode[(data[0] >> 1) & 0x3], + pixel_format[(data[0] >> 3) & 0xf]); + } + IVTV_INFO("Tuner: %s\n", + test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags) ? "Radio" : "TV"); + v4l2_ctrl_handler_log_status(&itv->cxhdl.hdl, itv->v4l2_dev.name); + IVTV_INFO("Status flags: 0x%08lx\n", itv->i_flags); + for (i = 0; i < IVTV_MAX_STREAMS; i++) { + struct ivtv_stream *s = &itv->streams[i]; + + if (s->vdev == NULL || s->buffers == 0) + continue; + IVTV_INFO("Stream %s: status 0x%04lx, %d%% of %d KiB (%d buffers) in use\n", s->name, s->s_flags, + (s->buffers - s->q_free.buffers) * 100 / s->buffers, + (s->buffers * s->buf_size) / 1024, s->buffers); + } + + IVTV_INFO("Read MPG/VBI: %lld/%lld bytes\n", + (long long)itv->mpg_data_received, + (long long)itv->vbi_data_inserted); + return 0; +} + +static int ivtv_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *dec) +{ + struct ivtv_open_id *id = fh2id(file->private_data); + struct ivtv *itv = id->itv; + + IVTV_DEBUG_IOCTL("VIDIOC_DECODER_CMD %d\n", dec->cmd); + return ivtv_video_command(itv, id, dec, false); +} + +static int ivtv_try_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *dec) +{ + struct ivtv_open_id *id = fh2id(file->private_data); + struct ivtv *itv = id->itv; + + IVTV_DEBUG_IOCTL("VIDIOC_TRY_DECODER_CMD %d\n", dec->cmd); + return ivtv_video_command(itv, id, dec, true); +} + +static int ivtv_decoder_ioctls(struct file *filp, unsigned int cmd, void *arg) +{ + struct ivtv_open_id *id = fh2id(filp->private_data); + struct ivtv *itv = id->itv; + int nonblocking = filp->f_flags & O_NONBLOCK; + struct ivtv_stream *s = &itv->streams[id->type]; + unsigned long iarg = (unsigned long)arg; + + switch (cmd) { + case IVTV_IOC_DMA_FRAME: { + struct ivtv_dma_frame *args = arg; + + IVTV_DEBUG_IOCTL("IVTV_IOC_DMA_FRAME\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + if (args->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + if (itv->output_mode == OUT_UDMA_YUV && args->y_source == NULL) + return 0; + if (ivtv_start_decoding(id, id->type)) { + return -EBUSY; + } + if (ivtv_set_output_mode(itv, OUT_UDMA_YUV) != OUT_UDMA_YUV) { + ivtv_release_stream(s); + return -EBUSY; + } + /* Mark that this file handle started the UDMA_YUV mode */ + id->yuv_frames = 1; + if (args->y_source == NULL) + return 0; + return ivtv_yuv_prep_frame(itv, args); + } + + case IVTV_IOC_PASSTHROUGH_MODE: + IVTV_DEBUG_IOCTL("IVTV_IOC_PASSTHROUGH_MODE\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + return ivtv_passthrough_mode(itv, *(int *)arg != 0); + + case VIDEO_GET_PTS: { + s64 *pts = arg; + s64 frame; + + IVTV_DEBUG_IOCTL("VIDEO_GET_PTS\n"); + if (s->type < IVTV_DEC_STREAM_TYPE_MPG) { + *pts = s->dma_pts; + break; + } + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + return ivtv_g_pts_frame(itv, pts, &frame); + } + + case VIDEO_GET_FRAME_COUNT: { + s64 *frame = arg; + s64 pts; + + IVTV_DEBUG_IOCTL("VIDEO_GET_FRAME_COUNT\n"); + if (s->type < IVTV_DEC_STREAM_TYPE_MPG) { + *frame = 0; + break; + } + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + return ivtv_g_pts_frame(itv, &pts, frame); + } + + case VIDEO_PLAY: { + struct v4l2_decoder_cmd dc; + + IVTV_DEBUG_IOCTL("VIDEO_PLAY\n"); + memset(&dc, 0, sizeof(dc)); + dc.cmd = V4L2_DEC_CMD_START; + return ivtv_video_command(itv, id, &dc, 0); + } + + case VIDEO_STOP: { + struct v4l2_decoder_cmd dc; + + IVTV_DEBUG_IOCTL("VIDEO_STOP\n"); + memset(&dc, 0, sizeof(dc)); + dc.cmd = V4L2_DEC_CMD_STOP; + dc.flags = V4L2_DEC_CMD_STOP_TO_BLACK | V4L2_DEC_CMD_STOP_IMMEDIATELY; + return ivtv_video_command(itv, id, &dc, 0); + } + + case VIDEO_FREEZE: { + struct v4l2_decoder_cmd dc; + + IVTV_DEBUG_IOCTL("VIDEO_FREEZE\n"); + memset(&dc, 0, sizeof(dc)); + dc.cmd = V4L2_DEC_CMD_PAUSE; + return ivtv_video_command(itv, id, &dc, 0); + } + + case VIDEO_CONTINUE: { + struct v4l2_decoder_cmd dc; + + IVTV_DEBUG_IOCTL("VIDEO_CONTINUE\n"); + memset(&dc, 0, sizeof(dc)); + dc.cmd = V4L2_DEC_CMD_RESUME; + return ivtv_video_command(itv, id, &dc, 0); + } + + case VIDEO_COMMAND: + case VIDEO_TRY_COMMAND: { + /* Note: struct v4l2_decoder_cmd has the same layout as + struct video_command */ + struct v4l2_decoder_cmd *dc = arg; + int try = (cmd == VIDEO_TRY_COMMAND); + + if (try) + IVTV_DEBUG_IOCTL("VIDEO_TRY_COMMAND %d\n", dc->cmd); + else + IVTV_DEBUG_IOCTL("VIDEO_COMMAND %d\n", dc->cmd); + return ivtv_video_command(itv, id, dc, try); + } + + case VIDEO_GET_EVENT: { + struct video_event *ev = arg; + DEFINE_WAIT(wait); + + IVTV_DEBUG_IOCTL("VIDEO_GET_EVENT\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + memset(ev, 0, sizeof(*ev)); + set_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags); + + while (1) { + if (test_and_clear_bit(IVTV_F_I_EV_DEC_STOPPED, &itv->i_flags)) + ev->type = VIDEO_EVENT_DECODER_STOPPED; + else if (test_and_clear_bit(IVTV_F_I_EV_VSYNC, &itv->i_flags)) { + ev->type = VIDEO_EVENT_VSYNC; + ev->u.vsync_field = test_bit(IVTV_F_I_EV_VSYNC_FIELD, &itv->i_flags) ? + VIDEO_VSYNC_FIELD_ODD : VIDEO_VSYNC_FIELD_EVEN; + if (itv->output_mode == OUT_UDMA_YUV && + (itv->yuv_info.lace_mode & IVTV_YUV_MODE_MASK) == + IVTV_YUV_MODE_PROGRESSIVE) { + ev->u.vsync_field = VIDEO_VSYNC_FIELD_PROGRESSIVE; + } + } + if (ev->type) + return 0; + if (nonblocking) + return -EAGAIN; + /* Wait for event. Note that serialize_lock is locked, + so to allow other processes to access the driver while + we are waiting unlock first and later lock again. */ + mutex_unlock(&itv->serialize_lock); + prepare_to_wait(&itv->event_waitq, &wait, TASK_INTERRUPTIBLE); + if (!test_bit(IVTV_F_I_EV_DEC_STOPPED, &itv->i_flags) && + !test_bit(IVTV_F_I_EV_VSYNC, &itv->i_flags)) + schedule(); + finish_wait(&itv->event_waitq, &wait); + mutex_lock(&itv->serialize_lock); + if (signal_pending(current)) { + /* return if a signal was received */ + IVTV_DEBUG_INFO("User stopped wait for event\n"); + return -EINTR; + } + } + break; + } + + case VIDEO_SELECT_SOURCE: + IVTV_DEBUG_IOCTL("VIDEO_SELECT_SOURCE\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + return ivtv_passthrough_mode(itv, iarg == VIDEO_SOURCE_DEMUX); + + case AUDIO_SET_MUTE: + IVTV_DEBUG_IOCTL("AUDIO_SET_MUTE\n"); + itv->speed_mute_audio = iarg; + return 0; + + case AUDIO_CHANNEL_SELECT: + IVTV_DEBUG_IOCTL("AUDIO_CHANNEL_SELECT\n"); + if (iarg > AUDIO_STEREO_SWAPPED) + return -EINVAL; + return v4l2_ctrl_s_ctrl(itv->ctrl_audio_playback, iarg + 1); + + case AUDIO_BILINGUAL_CHANNEL_SELECT: + IVTV_DEBUG_IOCTL("AUDIO_BILINGUAL_CHANNEL_SELECT\n"); + if (iarg > AUDIO_STEREO_SWAPPED) + return -EINVAL; + return v4l2_ctrl_s_ctrl(itv->ctrl_audio_multilingual_playback, iarg + 1); + + default: + return -EINVAL; + } + return 0; +} + +static long ivtv_default(struct file *file, void *fh, bool valid_prio, + int cmd, void *arg) +{ + struct ivtv *itv = fh2id(fh)->itv; + + if (!valid_prio) { + switch (cmd) { + case IVTV_IOC_PASSTHROUGH_MODE: + case VIDEO_PLAY: + case VIDEO_STOP: + case VIDEO_FREEZE: + case VIDEO_CONTINUE: + case VIDEO_COMMAND: + case VIDEO_SELECT_SOURCE: + case AUDIO_SET_MUTE: + case AUDIO_CHANNEL_SELECT: + case AUDIO_BILINGUAL_CHANNEL_SELECT: + return -EBUSY; + } + } + + switch (cmd) { + case VIDIOC_INT_RESET: { + u32 val = *(u32 *)arg; + + if ((val == 0 && itv->options.newi2c) || (val & 0x01)) + ivtv_reset_ir_gpio(itv); + if (val & 0x02) + v4l2_subdev_call(itv->sd_video, core, reset, 0); + break; + } + + case IVTV_IOC_DMA_FRAME: + case IVTV_IOC_PASSTHROUGH_MODE: + case VIDEO_GET_PTS: + case VIDEO_GET_FRAME_COUNT: + case VIDEO_GET_EVENT: + case VIDEO_PLAY: + case VIDEO_STOP: + case VIDEO_FREEZE: + case VIDEO_CONTINUE: + case VIDEO_COMMAND: + case VIDEO_TRY_COMMAND: + case VIDEO_SELECT_SOURCE: + case AUDIO_SET_MUTE: + case AUDIO_CHANNEL_SELECT: + case AUDIO_BILINGUAL_CHANNEL_SELECT: + return ivtv_decoder_ioctls(file, cmd, (void *)arg); + + default: + return -ENOTTY; + } + return 0; +} + +static const struct v4l2_ioctl_ops ivtv_ioctl_ops = { + .vidioc_querycap = ivtv_querycap, + .vidioc_s_audio = ivtv_s_audio, + .vidioc_g_audio = ivtv_g_audio, + .vidioc_enumaudio = ivtv_enumaudio, + .vidioc_s_audout = ivtv_s_audout, + .vidioc_g_audout = ivtv_g_audout, + .vidioc_enum_input = ivtv_enum_input, + .vidioc_enum_output = ivtv_enum_output, + .vidioc_enumaudout = ivtv_enumaudout, + .vidioc_cropcap = ivtv_cropcap, + .vidioc_s_crop = ivtv_s_crop, + .vidioc_g_crop = ivtv_g_crop, + .vidioc_g_input = ivtv_g_input, + .vidioc_s_input = ivtv_s_input, + .vidioc_g_output = ivtv_g_output, + .vidioc_s_output = ivtv_s_output, + .vidioc_g_frequency = ivtv_g_frequency, + .vidioc_s_frequency = ivtv_s_frequency, + .vidioc_s_tuner = ivtv_s_tuner, + .vidioc_g_tuner = ivtv_g_tuner, + .vidioc_g_enc_index = ivtv_g_enc_index, + .vidioc_g_fbuf = ivtv_g_fbuf, + .vidioc_s_fbuf = ivtv_s_fbuf, + .vidioc_g_std = ivtv_g_std, + .vidioc_s_std = ivtv_s_std, + .vidioc_overlay = ivtv_overlay, + .vidioc_log_status = ivtv_log_status, + .vidioc_enum_fmt_vid_cap = ivtv_enum_fmt_vid_cap, + .vidioc_encoder_cmd = ivtv_encoder_cmd, + .vidioc_try_encoder_cmd = ivtv_try_encoder_cmd, + .vidioc_decoder_cmd = ivtv_decoder_cmd, + .vidioc_try_decoder_cmd = ivtv_try_decoder_cmd, + .vidioc_enum_fmt_vid_out = ivtv_enum_fmt_vid_out, + .vidioc_g_fmt_vid_cap = ivtv_g_fmt_vid_cap, + .vidioc_g_fmt_vbi_cap = ivtv_g_fmt_vbi_cap, + .vidioc_g_fmt_sliced_vbi_cap = ivtv_g_fmt_sliced_vbi_cap, + .vidioc_g_fmt_vid_out = ivtv_g_fmt_vid_out, + .vidioc_g_fmt_vid_out_overlay = ivtv_g_fmt_vid_out_overlay, + .vidioc_g_fmt_sliced_vbi_out = ivtv_g_fmt_sliced_vbi_out, + .vidioc_s_fmt_vid_cap = ivtv_s_fmt_vid_cap, + .vidioc_s_fmt_vbi_cap = ivtv_s_fmt_vbi_cap, + .vidioc_s_fmt_sliced_vbi_cap = ivtv_s_fmt_sliced_vbi_cap, + .vidioc_s_fmt_vid_out = ivtv_s_fmt_vid_out, + .vidioc_s_fmt_vid_out_overlay = ivtv_s_fmt_vid_out_overlay, + .vidioc_s_fmt_sliced_vbi_out = ivtv_s_fmt_sliced_vbi_out, + .vidioc_try_fmt_vid_cap = ivtv_try_fmt_vid_cap, + .vidioc_try_fmt_vbi_cap = ivtv_try_fmt_vbi_cap, + .vidioc_try_fmt_sliced_vbi_cap = ivtv_try_fmt_sliced_vbi_cap, + .vidioc_try_fmt_vid_out = ivtv_try_fmt_vid_out, + .vidioc_try_fmt_vid_out_overlay = ivtv_try_fmt_vid_out_overlay, + .vidioc_try_fmt_sliced_vbi_out = ivtv_try_fmt_sliced_vbi_out, + .vidioc_g_sliced_vbi_cap = ivtv_g_sliced_vbi_cap, + .vidioc_g_chip_ident = ivtv_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = ivtv_g_register, + .vidioc_s_register = ivtv_s_register, +#endif + .vidioc_default = ivtv_default, + .vidioc_subscribe_event = ivtv_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +void ivtv_set_funcs(struct video_device *vdev) +{ + vdev->ioctl_ops = &ivtv_ioctl_ops; +} diff --git a/drivers/media/video/ivtv/ivtv-ioctl.h b/drivers/media/pci/ivtv/ivtv-ioctl.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-ioctl.h rename to drivers/media/pci/ivtv/ivtv-ioctl.h diff --git a/drivers/media/pci/ivtv/ivtv-irq.c b/drivers/media/pci/ivtv/ivtv-irq.c new file mode 100644 index 000000000000..19a7c9b990a3 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-irq.c @@ -0,0 +1,1088 @@ +/* interrupt handling + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-mailbox.h" +#include "ivtv-vbi.h" +#include "ivtv-yuv.h" +#include + +#define DMA_MAGIC_COOKIE 0x000001fe + +static void ivtv_dma_dec_start(struct ivtv_stream *s); + +static const int ivtv_stream_map[] = { + IVTV_ENC_STREAM_TYPE_MPG, + IVTV_ENC_STREAM_TYPE_YUV, + IVTV_ENC_STREAM_TYPE_PCM, + IVTV_ENC_STREAM_TYPE_VBI, +}; + +static void ivtv_pcm_work_handler(struct ivtv *itv) +{ + struct ivtv_stream *s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; + struct ivtv_buffer *buf; + + /* Pass the PCM data to ivtv-alsa */ + + while (1) { + /* + * Users should not be using both the ALSA and V4L2 PCM audio + * capture interfaces at the same time. If the user is doing + * this, there maybe a buffer in q_io to grab, use, and put + * back in rotation. + */ + buf = ivtv_dequeue(s, &s->q_io); + if (buf == NULL) + buf = ivtv_dequeue(s, &s->q_full); + if (buf == NULL) + break; + + if (buf->readpos < buf->bytesused) + itv->pcm_announce_callback(itv->alsa, + (u8 *)(buf->buf + buf->readpos), + (size_t)(buf->bytesused - buf->readpos)); + + ivtv_enqueue(s, buf, &s->q_free); + } +} + +static void ivtv_pio_work_handler(struct ivtv *itv) +{ + struct ivtv_stream *s = &itv->streams[itv->cur_pio_stream]; + struct ivtv_buffer *buf; + int i = 0; + + IVTV_DEBUG_HI_DMA("ivtv_pio_work_handler\n"); + if (itv->cur_pio_stream < 0 || itv->cur_pio_stream >= IVTV_MAX_STREAMS || + s->vdev == NULL || !ivtv_use_pio(s)) { + itv->cur_pio_stream = -1; + /* trigger PIO complete user interrupt */ + write_reg(IVTV_IRQ_ENC_PIO_COMPLETE, 0x44); + return; + } + IVTV_DEBUG_HI_DMA("Process PIO %s\n", s->name); + list_for_each_entry(buf, &s->q_dma.list, list) { + u32 size = s->sg_processing[i].size & 0x3ffff; + + /* Copy the data from the card to the buffer */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) { + memcpy_fromio(buf->buf, itv->dec_mem + s->sg_processing[i].src - IVTV_DECODER_OFFSET, size); + } + else { + memcpy_fromio(buf->buf, itv->enc_mem + s->sg_processing[i].src, size); + } + i++; + if (i == s->sg_processing_size) + break; + } + write_reg(IVTV_IRQ_ENC_PIO_COMPLETE, 0x44); +} + +void ivtv_irq_work_handler(struct kthread_work *work) +{ + struct ivtv *itv = container_of(work, struct ivtv, irq_work); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_PIO, &itv->i_flags)) + ivtv_pio_work_handler(itv); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_VBI, &itv->i_flags)) + ivtv_vbi_work_handler(itv); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_YUV, &itv->i_flags)) + ivtv_yuv_work_handler(itv); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_PCM, &itv->i_flags)) + ivtv_pcm_work_handler(itv); +} + +/* Determine the required DMA size, setup enough buffers in the predma queue and + actually copy the data from the card to the buffers in case a PIO transfer is + required for this stream. + */ +static int stream_enc_dma_append(struct ivtv_stream *s, u32 data[CX2341X_MBOX_MAX_DATA]) +{ + struct ivtv *itv = s->itv; + struct ivtv_buffer *buf; + u32 bytes_needed = 0; + u32 offset, size; + u32 UVoffset = 0, UVsize = 0; + int skip_bufs = s->q_predma.buffers; + int idx = s->sg_pending_size; + int rc; + + /* sanity checks */ + if (s->vdev == NULL) { + IVTV_DEBUG_WARN("Stream %s not started\n", s->name); + return -1; + } + if (!test_bit(IVTV_F_S_CLAIMED, &s->s_flags)) { + IVTV_DEBUG_WARN("Stream %s not open\n", s->name); + return -1; + } + + /* determine offset, size and PTS for the various streams */ + switch (s->type) { + case IVTV_ENC_STREAM_TYPE_MPG: + offset = data[1]; + size = data[2]; + s->pending_pts = 0; + break; + + case IVTV_ENC_STREAM_TYPE_YUV: + offset = data[1]; + size = data[2]; + UVoffset = data[3]; + UVsize = data[4]; + s->pending_pts = ((u64) data[5] << 32) | data[6]; + break; + + case IVTV_ENC_STREAM_TYPE_PCM: + offset = data[1] + 12; + size = data[2] - 12; + s->pending_pts = read_dec(offset - 8) | + ((u64)(read_dec(offset - 12)) << 32); + if (itv->has_cx23415) + offset += IVTV_DECODER_OFFSET; + break; + + case IVTV_ENC_STREAM_TYPE_VBI: + size = itv->vbi.enc_size * itv->vbi.fpi; + offset = read_enc(itv->vbi.enc_start - 4) + 12; + if (offset == 12) { + IVTV_DEBUG_INFO("VBI offset == 0\n"); + return -1; + } + s->pending_pts = read_enc(offset - 4) | ((u64)read_enc(offset - 8) << 32); + break; + + case IVTV_DEC_STREAM_TYPE_VBI: + size = read_dec(itv->vbi.dec_start + 4) + 8; + offset = read_dec(itv->vbi.dec_start) + itv->vbi.dec_start; + s->pending_pts = 0; + offset += IVTV_DECODER_OFFSET; + break; + default: + /* shouldn't happen */ + return -1; + } + + /* if this is the start of the DMA then fill in the magic cookie */ + if (s->sg_pending_size == 0 && ivtv_use_dma(s)) { + if (itv->has_cx23415 && (s->type == IVTV_ENC_STREAM_TYPE_PCM || + s->type == IVTV_DEC_STREAM_TYPE_VBI)) { + s->pending_backup = read_dec(offset - IVTV_DECODER_OFFSET); + write_dec_sync(cpu_to_le32(DMA_MAGIC_COOKIE), offset - IVTV_DECODER_OFFSET); + } + else { + s->pending_backup = read_enc(offset); + write_enc_sync(cpu_to_le32(DMA_MAGIC_COOKIE), offset); + } + s->pending_offset = offset; + } + + bytes_needed = size; + if (s->type == IVTV_ENC_STREAM_TYPE_YUV) { + /* The size for the Y samples needs to be rounded upwards to a + multiple of the buf_size. The UV samples then start in the + next buffer. */ + bytes_needed = s->buf_size * ((bytes_needed + s->buf_size - 1) / s->buf_size); + bytes_needed += UVsize; + } + + IVTV_DEBUG_HI_DMA("%s %s: 0x%08x bytes at 0x%08x\n", + ivtv_use_pio(s) ? "PIO" : "DMA", s->name, bytes_needed, offset); + + rc = ivtv_queue_move(s, &s->q_free, &s->q_full, &s->q_predma, bytes_needed); + if (rc < 0) { /* Insufficient buffers */ + IVTV_DEBUG_WARN("Cannot obtain %d bytes for %s data transfer\n", + bytes_needed, s->name); + return -1; + } + if (rc && !s->buffers_stolen && test_bit(IVTV_F_S_APPL_IO, &s->s_flags)) { + IVTV_WARN("All %s stream buffers are full. Dropping data.\n", s->name); + IVTV_WARN("Cause: the application is not reading fast enough.\n"); + } + s->buffers_stolen = rc; + + /* got the buffers, now fill in sg_pending */ + buf = list_entry(s->q_predma.list.next, struct ivtv_buffer, list); + memset(buf->buf, 0, 128); + list_for_each_entry(buf, &s->q_predma.list, list) { + if (skip_bufs-- > 0) + continue; + s->sg_pending[idx].dst = buf->dma_handle; + s->sg_pending[idx].src = offset; + s->sg_pending[idx].size = s->buf_size; + buf->bytesused = min(size, s->buf_size); + buf->dma_xfer_cnt = s->dma_xfer_cnt; + + s->q_predma.bytesused += buf->bytesused; + size -= buf->bytesused; + offset += s->buf_size; + + /* Sync SG buffers */ + ivtv_buf_sync_for_device(s, buf); + + if (size == 0) { /* YUV */ + /* process the UV section */ + offset = UVoffset; + size = UVsize; + } + idx++; + } + s->sg_pending_size = idx; + return 0; +} + +static void dma_post(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + struct ivtv_buffer *buf = NULL; + struct list_head *p; + u32 offset; + __le32 *u32buf; + int x = 0; + + IVTV_DEBUG_HI_DMA("%s %s completed (%x)\n", ivtv_use_pio(s) ? "PIO" : "DMA", + s->name, s->dma_offset); + list_for_each(p, &s->q_dma.list) { + buf = list_entry(p, struct ivtv_buffer, list); + u32buf = (__le32 *)buf->buf; + + /* Sync Buffer */ + ivtv_buf_sync_for_cpu(s, buf); + + if (x == 0 && ivtv_use_dma(s)) { + offset = s->dma_last_offset; + if (u32buf[offset / 4] != DMA_MAGIC_COOKIE) + { + for (offset = 0; offset < 64; offset++) { + if (u32buf[offset] == DMA_MAGIC_COOKIE) { + break; + } + } + offset *= 4; + if (offset == 256) { + IVTV_DEBUG_WARN("%s: Couldn't find start of buffer within the first 256 bytes\n", s->name); + offset = s->dma_last_offset; + } + if (s->dma_last_offset != offset) + IVTV_DEBUG_WARN("%s: offset %d -> %d\n", s->name, s->dma_last_offset, offset); + s->dma_last_offset = offset; + } + if (itv->has_cx23415 && (s->type == IVTV_ENC_STREAM_TYPE_PCM || + s->type == IVTV_DEC_STREAM_TYPE_VBI)) { + write_dec_sync(0, s->dma_offset - IVTV_DECODER_OFFSET); + } + else { + write_enc_sync(0, s->dma_offset); + } + if (offset) { + buf->bytesused -= offset; + memcpy(buf->buf, buf->buf + offset, buf->bytesused + offset); + } + *u32buf = cpu_to_le32(s->dma_backup); + } + x++; + /* flag byteswap ABCD -> DCBA for MPG & VBI data outside irq */ + if (s->type == IVTV_ENC_STREAM_TYPE_MPG || + s->type == IVTV_ENC_STREAM_TYPE_VBI) + buf->b_flags |= IVTV_F_B_NEED_BUF_SWAP; + } + if (buf) + buf->bytesused += s->dma_last_offset; + if (buf && s->type == IVTV_DEC_STREAM_TYPE_VBI) { + list_for_each_entry(buf, &s->q_dma.list, list) { + /* Parse and Groom VBI Data */ + s->q_dma.bytesused -= buf->bytesused; + ivtv_process_vbi_data(itv, buf, 0, s->type); + s->q_dma.bytesused += buf->bytesused; + } + if (s->fh == NULL) { + ivtv_queue_move(s, &s->q_dma, NULL, &s->q_free, 0); + return; + } + } + + ivtv_queue_move(s, &s->q_dma, NULL, &s->q_full, s->q_dma.bytesused); + + if (s->type == IVTV_ENC_STREAM_TYPE_PCM && + itv->pcm_announce_callback != NULL) { + /* + * Set up the work handler to pass the data to ivtv-alsa. + * + * We just use q_full and let the work handler race with users + * making ivtv-fileops.c calls on the PCM device node. + * + * Users should not be using both the ALSA and V4L2 PCM audio + * capture interfaces at the same time. If the user does this, + * fragments of data will just go out each interface as they + * race for PCM data. + */ + set_bit(IVTV_F_I_WORK_HANDLER_PCM, &itv->i_flags); + set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags); + } + + if (s->fh) + wake_up(&s->waitq); +} + +void ivtv_dma_stream_dec_prepare(struct ivtv_stream *s, u32 offset, int lock) +{ + struct ivtv *itv = s->itv; + struct yuv_playback_info *yi = &itv->yuv_info; + u8 frame = yi->draw_frame; + struct yuv_frame_info *f = &yi->new_frame_info[frame]; + struct ivtv_buffer *buf; + u32 y_size = 720 * ((f->src_h + 31) & ~31); + u32 uv_offset = offset + IVTV_YUV_BUFFER_UV_OFFSET; + int y_done = 0; + int bytes_written = 0; + unsigned long flags = 0; + int idx = 0; + + IVTV_DEBUG_HI_DMA("DEC PREPARE DMA %s: %08x %08x\n", s->name, s->q_predma.bytesused, offset); + + /* Insert buffer block for YUV if needed */ + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && f->offset_y) { + if (yi->blanking_dmaptr) { + s->sg_pending[idx].src = yi->blanking_dmaptr; + s->sg_pending[idx].dst = offset; + s->sg_pending[idx].size = 720 * 16; + } + offset += 720 * 16; + idx++; + } + + list_for_each_entry(buf, &s->q_predma.list, list) { + /* YUV UV Offset from Y Buffer */ + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && !y_done && + (bytes_written + buf->bytesused) >= y_size) { + s->sg_pending[idx].src = buf->dma_handle; + s->sg_pending[idx].dst = offset; + s->sg_pending[idx].size = y_size - bytes_written; + offset = uv_offset; + if (s->sg_pending[idx].size != buf->bytesused) { + idx++; + s->sg_pending[idx].src = + buf->dma_handle + s->sg_pending[idx - 1].size; + s->sg_pending[idx].dst = offset; + s->sg_pending[idx].size = + buf->bytesused - s->sg_pending[idx - 1].size; + offset += s->sg_pending[idx].size; + } + y_done = 1; + } else { + s->sg_pending[idx].src = buf->dma_handle; + s->sg_pending[idx].dst = offset; + s->sg_pending[idx].size = buf->bytesused; + offset += buf->bytesused; + } + bytes_written += buf->bytesused; + + /* Sync SG buffers */ + ivtv_buf_sync_for_device(s, buf); + idx++; + } + s->sg_pending_size = idx; + + /* Sync Hardware SG List of buffers */ + ivtv_stream_sync_for_device(s); + if (lock) + spin_lock_irqsave(&itv->dma_reg_lock, flags); + if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) { + ivtv_dma_dec_start(s); + } + else { + set_bit(IVTV_F_S_DMA_PENDING, &s->s_flags); + } + if (lock) + spin_unlock_irqrestore(&itv->dma_reg_lock, flags); +} + +static void ivtv_dma_enc_start_xfer(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + + s->sg_dma->src = cpu_to_le32(s->sg_processing[s->sg_processed].src); + s->sg_dma->dst = cpu_to_le32(s->sg_processing[s->sg_processed].dst); + s->sg_dma->size = cpu_to_le32(s->sg_processing[s->sg_processed].size | 0x80000000); + s->sg_processed++; + /* Sync Hardware SG List of buffers */ + ivtv_stream_sync_for_device(s); + write_reg(s->sg_handle, IVTV_REG_ENCDMAADDR); + write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x02, IVTV_REG_DMAXFER); + itv->dma_timer.expires = jiffies + msecs_to_jiffies(300); + add_timer(&itv->dma_timer); +} + +static void ivtv_dma_dec_start_xfer(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + + s->sg_dma->src = cpu_to_le32(s->sg_processing[s->sg_processed].src); + s->sg_dma->dst = cpu_to_le32(s->sg_processing[s->sg_processed].dst); + s->sg_dma->size = cpu_to_le32(s->sg_processing[s->sg_processed].size | 0x80000000); + s->sg_processed++; + /* Sync Hardware SG List of buffers */ + ivtv_stream_sync_for_device(s); + write_reg(s->sg_handle, IVTV_REG_DECDMAADDR); + write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x01, IVTV_REG_DMAXFER); + itv->dma_timer.expires = jiffies + msecs_to_jiffies(300); + add_timer(&itv->dma_timer); +} + +/* start the encoder DMA */ +static void ivtv_dma_enc_start(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + int i; + + IVTV_DEBUG_HI_DMA("start %s for %s\n", ivtv_use_dma(s) ? "DMA" : "PIO", s->name); + + if (s->q_predma.bytesused) + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused); + + if (ivtv_use_dma(s)) + s->sg_pending[s->sg_pending_size - 1].size += 256; + + /* If this is an MPEG stream, and VBI data is also pending, then append the + VBI DMA to the MPEG DMA and transfer both sets of data at once. + + VBI DMA is a second class citizen compared to MPEG and mixing them together + will confuse the firmware (the end of a VBI DMA is seen as the end of a + MPEG DMA, thus effectively dropping an MPEG frame). So instead we make + sure we only use the MPEG DMA to transfer the VBI DMA if both are in + use. This way no conflicts occur. */ + clear_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags); + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && s_vbi->sg_pending_size && + s->sg_pending_size + s_vbi->sg_pending_size <= s->buffers) { + ivtv_queue_move(s_vbi, &s_vbi->q_predma, NULL, &s_vbi->q_dma, s_vbi->q_predma.bytesused); + if (ivtv_use_dma(s_vbi)) + s_vbi->sg_pending[s_vbi->sg_pending_size - 1].size += 256; + for (i = 0; i < s_vbi->sg_pending_size; i++) { + s->sg_pending[s->sg_pending_size++] = s_vbi->sg_pending[i]; + } + s_vbi->dma_offset = s_vbi->pending_offset; + s_vbi->sg_pending_size = 0; + s_vbi->dma_xfer_cnt++; + set_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags); + IVTV_DEBUG_HI_DMA("include DMA for %s\n", s_vbi->name); + } + + s->dma_xfer_cnt++; + memcpy(s->sg_processing, s->sg_pending, sizeof(struct ivtv_sg_host_element) * s->sg_pending_size); + s->sg_processing_size = s->sg_pending_size; + s->sg_pending_size = 0; + s->sg_processed = 0; + s->dma_offset = s->pending_offset; + s->dma_backup = s->pending_backup; + s->dma_pts = s->pending_pts; + + if (ivtv_use_pio(s)) { + set_bit(IVTV_F_I_WORK_HANDLER_PIO, &itv->i_flags); + set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags); + set_bit(IVTV_F_I_PIO, &itv->i_flags); + itv->cur_pio_stream = s->type; + } + else { + itv->dma_retries = 0; + ivtv_dma_enc_start_xfer(s); + set_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = s->type; + } +} + +static void ivtv_dma_dec_start(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + + if (s->q_predma.bytesused) + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused); + s->dma_xfer_cnt++; + memcpy(s->sg_processing, s->sg_pending, sizeof(struct ivtv_sg_host_element) * s->sg_pending_size); + s->sg_processing_size = s->sg_pending_size; + s->sg_pending_size = 0; + s->sg_processed = 0; + + IVTV_DEBUG_HI_DMA("start DMA for %s\n", s->name); + itv->dma_retries = 0; + ivtv_dma_dec_start_xfer(s); + set_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = s->type; +} + +static void ivtv_irq_dma_read(struct ivtv *itv) +{ + struct ivtv_stream *s = NULL; + struct ivtv_buffer *buf; + int hw_stream_type = 0; + + IVTV_DEBUG_HI_IRQ("DEC DMA READ\n"); + + del_timer(&itv->dma_timer); + + if (!test_bit(IVTV_F_I_UDMA, &itv->i_flags) && itv->cur_dma_stream < 0) + return; + + if (!test_bit(IVTV_F_I_UDMA, &itv->i_flags)) { + s = &itv->streams[itv->cur_dma_stream]; + ivtv_stream_sync_for_cpu(s); + + if (read_reg(IVTV_REG_DMASTATUS) & 0x14) { + IVTV_DEBUG_WARN("DEC DMA ERROR %x (xfer %d of %d, retry %d)\n", + read_reg(IVTV_REG_DMASTATUS), + s->sg_processed, s->sg_processing_size, itv->dma_retries); + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + if (itv->dma_retries == 3) { + /* Too many retries, give up on this frame */ + itv->dma_retries = 0; + s->sg_processed = s->sg_processing_size; + } + else { + /* Retry, starting with the first xfer segment. + Just retrying the current segment is not sufficient. */ + s->sg_processed = 0; + itv->dma_retries++; + } + } + if (s->sg_processed < s->sg_processing_size) { + /* DMA next buffer */ + ivtv_dma_dec_start_xfer(s); + return; + } + if (s->type == IVTV_DEC_STREAM_TYPE_YUV) + hw_stream_type = 2; + IVTV_DEBUG_HI_DMA("DEC DATA READ %s: %d\n", s->name, s->q_dma.bytesused); + + /* For some reason must kick the firmware, like PIO mode, + I think this tells the firmware we are done and the size + of the xfer so it can calculate what we need next. + I think we can do this part ourselves but would have to + fully calculate xfer info ourselves and not use interrupts + */ + ivtv_vapi(itv, CX2341X_DEC_SCHED_DMA_FROM_HOST, 3, 0, s->q_dma.bytesused, + hw_stream_type); + + /* Free last DMA call */ + while ((buf = ivtv_dequeue(s, &s->q_dma)) != NULL) { + ivtv_buf_sync_for_cpu(s, buf); + ivtv_enqueue(s, buf, &s->q_free); + } + wake_up(&s->waitq); + } + clear_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_enc_dma_complete(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + ivtv_api_get_data(&itv->enc_mbox, IVTV_MBOX_DMA_END, 2, data); + IVTV_DEBUG_HI_IRQ("ENC DMA COMPLETE %x %d (%d)\n", data[0], data[1], itv->cur_dma_stream); + + del_timer(&itv->dma_timer); + + if (itv->cur_dma_stream < 0) + return; + + s = &itv->streams[itv->cur_dma_stream]; + ivtv_stream_sync_for_cpu(s); + + if (data[0] & 0x18) { + IVTV_DEBUG_WARN("ENC DMA ERROR %x (offset %08x, xfer %d of %d, retry %d)\n", data[0], + s->dma_offset, s->sg_processed, s->sg_processing_size, itv->dma_retries); + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + if (itv->dma_retries == 3) { + /* Too many retries, give up on this frame */ + itv->dma_retries = 0; + s->sg_processed = s->sg_processing_size; + } + else { + /* Retry, starting with the first xfer segment. + Just retrying the current segment is not sufficient. */ + s->sg_processed = 0; + itv->dma_retries++; + } + } + if (s->sg_processed < s->sg_processing_size) { + /* DMA next buffer */ + ivtv_dma_enc_start_xfer(s); + return; + } + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + dma_post(s); + if (test_and_clear_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags)) { + s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + dma_post(s); + } + s->sg_processing_size = 0; + s->sg_processed = 0; + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_enc_pio_complete(struct ivtv *itv) +{ + struct ivtv_stream *s; + + if (itv->cur_pio_stream < 0 || itv->cur_pio_stream >= IVTV_MAX_STREAMS) { + itv->cur_pio_stream = -1; + return; + } + s = &itv->streams[itv->cur_pio_stream]; + IVTV_DEBUG_HI_IRQ("ENC PIO COMPLETE %s\n", s->name); + clear_bit(IVTV_F_I_PIO, &itv->i_flags); + itv->cur_pio_stream = -1; + dma_post(s); + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, 0); + else if (s->type == IVTV_ENC_STREAM_TYPE_YUV) + ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, 1); + else if (s->type == IVTV_ENC_STREAM_TYPE_PCM) + ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, 2); + clear_bit(IVTV_F_I_PIO, &itv->i_flags); + if (test_and_clear_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags)) { + s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + dma_post(s); + } + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_dma_err(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + u32 status; + + del_timer(&itv->dma_timer); + + ivtv_api_get_data(&itv->enc_mbox, IVTV_MBOX_DMA_END, 2, data); + status = read_reg(IVTV_REG_DMASTATUS); + IVTV_DEBUG_WARN("DMA ERROR %08x %08x %08x %d\n", data[0], data[1], + status, itv->cur_dma_stream); + /* + * We do *not* write back to the IVTV_REG_DMASTATUS register to + * clear the error status, if either the encoder write (0x02) or + * decoder read (0x01) bus master DMA operation do not indicate + * completed. We can race with the DMA engine, which may have + * transitioned to completed status *after* we read the register. + * Setting a IVTV_REG_DMASTATUS flag back to "busy" status, after the + * DMA engine has completed, will cause the DMA engine to stop working. + */ + status &= 0x3; + if (status == 0x3) + write_reg(status, IVTV_REG_DMASTATUS); + + if (!test_bit(IVTV_F_I_UDMA, &itv->i_flags) && + itv->cur_dma_stream >= 0 && itv->cur_dma_stream < IVTV_MAX_STREAMS) { + struct ivtv_stream *s = &itv->streams[itv->cur_dma_stream]; + + if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) { + /* retry */ + /* + * FIXME - handle cases of DMA error similar to + * encoder below, except conditioned on status & 0x1 + */ + ivtv_dma_dec_start(s); + return; + } else { + if ((status & 0x2) == 0) { + /* + * CX2341x Bus Master DMA write is ongoing. + * Reset the timer and let it complete. + */ + itv->dma_timer.expires = + jiffies + msecs_to_jiffies(600); + add_timer(&itv->dma_timer); + return; + } + + if (itv->dma_retries < 3) { + /* + * CX2341x Bus Master DMA write has ended. + * Retry the write, starting with the first + * xfer segment. Just retrying the current + * segment is not sufficient. + */ + s->sg_processed = 0; + itv->dma_retries++; + ivtv_dma_enc_start_xfer(s); + return; + } + /* Too many retries, give up on this one */ + } + + } + if (test_bit(IVTV_F_I_UDMA, &itv->i_flags)) { + ivtv_udma_start(itv); + return; + } + clear_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_enc_start_cap(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + /* Get DMA destination and size arguments from card */ + ivtv_api_get_data(&itv->enc_mbox, IVTV_MBOX_DMA, 7, data); + IVTV_DEBUG_HI_IRQ("ENC START CAP %d: %08x %08x\n", data[0], data[1], data[2]); + + if (data[0] > 2 || data[1] == 0 || data[2] == 0) { + IVTV_DEBUG_WARN("Unknown input: %08x %08x %08x\n", + data[0], data[1], data[2]); + return; + } + s = &itv->streams[ivtv_stream_map[data[0]]]; + if (!stream_enc_dma_append(s, data)) { + set_bit(ivtv_use_pio(s) ? IVTV_F_S_PIO_PENDING : IVTV_F_S_DMA_PENDING, &s->s_flags); + } +} + +static void ivtv_irq_enc_vbi_cap(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + IVTV_DEBUG_HI_IRQ("ENC START VBI CAP\n"); + s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + + if (!stream_enc_dma_append(s, data)) + set_bit(ivtv_use_pio(s) ? IVTV_F_S_PIO_PENDING : IVTV_F_S_DMA_PENDING, &s->s_flags); +} + +static void ivtv_irq_dec_vbi_reinsert(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s = &itv->streams[IVTV_DEC_STREAM_TYPE_VBI]; + + IVTV_DEBUG_HI_IRQ("DEC VBI REINSERT\n"); + if (test_bit(IVTV_F_S_CLAIMED, &s->s_flags) && + !stream_enc_dma_append(s, data)) { + set_bit(IVTV_F_S_PIO_PENDING, &s->s_flags); + } +} + +static void ivtv_irq_dec_data_req(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + /* YUV or MPG */ + + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) { + ivtv_api_get_data(&itv->dec_mbox, IVTV_MBOX_DMA, 2, data); + itv->dma_data_req_size = + 1080 * ((itv->yuv_info.v4l2_src_h + 31) & ~31); + itv->dma_data_req_offset = data[1]; + if (atomic_read(&itv->yuv_info.next_dma_frame) >= 0) + ivtv_yuv_frame_complete(itv); + s = &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + } + else { + ivtv_api_get_data(&itv->dec_mbox, IVTV_MBOX_DMA, 3, data); + itv->dma_data_req_size = min_t(u32, data[2], 0x10000); + itv->dma_data_req_offset = data[1]; + s = &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + } + IVTV_DEBUG_HI_IRQ("DEC DATA REQ %s: %d %08x %u\n", s->name, s->q_full.bytesused, + itv->dma_data_req_offset, itv->dma_data_req_size); + if (itv->dma_data_req_size == 0 || s->q_full.bytesused < itv->dma_data_req_size) { + set_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + } + else { + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) + ivtv_yuv_setup_stream_frame(itv); + clear_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + ivtv_queue_move(s, &s->q_full, NULL, &s->q_predma, itv->dma_data_req_size); + ivtv_dma_stream_dec_prepare(s, itv->dma_data_req_offset + IVTV_DECODER_OFFSET, 0); + } +} + +static void ivtv_irq_vsync(struct ivtv *itv) +{ + /* The vsync interrupt is unusual in that it won't clear until + * the end of the first line for the current field, at which + * point it clears itself. This can result in repeated vsync + * interrupts, or a missed vsync. Read some of the registers + * to determine the line being displayed and ensure we handle + * one vsync per frame. + */ + unsigned int frame = read_reg(IVTV_REG_DEC_LINE_FIELD) & 1; + struct yuv_playback_info *yi = &itv->yuv_info; + int last_dma_frame = atomic_read(&yi->next_dma_frame); + struct yuv_frame_info *f = &yi->new_frame_info[last_dma_frame]; + + if (0) IVTV_DEBUG_IRQ("DEC VSYNC\n"); + + if (((frame ^ f->sync_field) == 0 && + ((itv->last_vsync_field & 1) ^ f->sync_field)) || + (frame != (itv->last_vsync_field & 1) && !f->interlaced)) { + int next_dma_frame = last_dma_frame; + + if (!(f->interlaced && f->delay && yi->fields_lapsed < 1)) { + if (next_dma_frame >= 0 && next_dma_frame != atomic_read(&yi->next_fill_frame)) { + write_reg(yuv_offset[next_dma_frame] >> 4, 0x82c); + write_reg((yuv_offset[next_dma_frame] + IVTV_YUV_BUFFER_UV_OFFSET) >> 4, 0x830); + write_reg(yuv_offset[next_dma_frame] >> 4, 0x834); + write_reg((yuv_offset[next_dma_frame] + IVTV_YUV_BUFFER_UV_OFFSET) >> 4, 0x838); + next_dma_frame = (next_dma_frame + 1) % IVTV_YUV_BUFFERS; + atomic_set(&yi->next_dma_frame, next_dma_frame); + yi->fields_lapsed = -1; + yi->running = 1; + } + } + } + if (frame != (itv->last_vsync_field & 1)) { + static const struct v4l2_event evtop = { + .type = V4L2_EVENT_VSYNC, + .u.vsync.field = V4L2_FIELD_TOP, + }; + static const struct v4l2_event evbottom = { + .type = V4L2_EVENT_VSYNC, + .u.vsync.field = V4L2_FIELD_BOTTOM, + }; + struct ivtv_stream *s = ivtv_get_output_stream(itv); + + itv->last_vsync_field += 1; + if (frame == 0) { + clear_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags); + clear_bit(IVTV_F_I_EV_VSYNC_FIELD, &itv->i_flags); + } + else { + set_bit(IVTV_F_I_EV_VSYNC_FIELD, &itv->i_flags); + } + if (test_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags)) { + set_bit(IVTV_F_I_EV_VSYNC, &itv->i_flags); + wake_up(&itv->event_waitq); + if (s) + wake_up(&s->waitq); + } + if (s && s->vdev) + v4l2_event_queue(s->vdev, frame ? &evtop : &evbottom); + wake_up(&itv->vsync_waitq); + + /* Send VBI to saa7127 */ + if (frame && (itv->output_mode == OUT_PASSTHROUGH || + test_bit(IVTV_F_I_UPDATE_WSS, &itv->i_flags) || + test_bit(IVTV_F_I_UPDATE_VPS, &itv->i_flags) || + test_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags))) { + set_bit(IVTV_F_I_WORK_HANDLER_VBI, &itv->i_flags); + set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags); + } + + /* Check if we need to update the yuv registers */ + if (yi->running && (yi->yuv_forced_update || f->update)) { + if (!f->update) { + last_dma_frame = + (u8)(atomic_read(&yi->next_dma_frame) - + 1) % IVTV_YUV_BUFFERS; + f = &yi->new_frame_info[last_dma_frame]; + } + + if (f->src_w) { + yi->update_frame = last_dma_frame; + f->update = 0; + yi->yuv_forced_update = 0; + set_bit(IVTV_F_I_WORK_HANDLER_YUV, &itv->i_flags); + set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags); + } + } + + yi->fields_lapsed++; + } +} + +#define IVTV_IRQ_DMA (IVTV_IRQ_DMA_READ | IVTV_IRQ_ENC_DMA_COMPLETE | IVTV_IRQ_DMA_ERR | IVTV_IRQ_ENC_START_CAP | IVTV_IRQ_ENC_VBI_CAP | IVTV_IRQ_DEC_DATA_REQ | IVTV_IRQ_DEC_VBI_RE_INSERT) + +irqreturn_t ivtv_irq_handler(int irq, void *dev_id) +{ + struct ivtv *itv = (struct ivtv *)dev_id; + u32 combo; + u32 stat; + int i; + u8 vsync_force = 0; + + spin_lock(&itv->dma_reg_lock); + /* get contents of irq status register */ + stat = read_reg(IVTV_REG_IRQSTATUS); + + combo = ~itv->irqmask & stat; + + /* Clear out IRQ */ + if (combo) write_reg(combo, IVTV_REG_IRQSTATUS); + + if (0 == combo) { + /* The vsync interrupt is unusual and clears itself. If we + * took too long, we may have missed it. Do some checks + */ + if (~itv->irqmask & IVTV_IRQ_DEC_VSYNC) { + /* vsync is enabled, see if we're in a new field */ + if ((itv->last_vsync_field & 1) != + (read_reg(IVTV_REG_DEC_LINE_FIELD) & 1)) { + /* New field, looks like we missed it */ + IVTV_DEBUG_YUV("VSync interrupt missed %d\n", + read_reg(IVTV_REG_DEC_LINE_FIELD) >> 16); + vsync_force = 1; + } + } + + if (!vsync_force) { + /* No Vsync expected, wasn't for us */ + spin_unlock(&itv->dma_reg_lock); + return IRQ_NONE; + } + } + + /* Exclude interrupts noted below from the output, otherwise the log is flooded with + these messages */ + if (combo & ~0xff6d0400) + IVTV_DEBUG_HI_IRQ("======= valid IRQ bits: 0x%08x ======\n", combo); + + if (combo & IVTV_IRQ_DEC_DMA_COMPLETE) { + IVTV_DEBUG_HI_IRQ("DEC DMA COMPLETE\n"); + } + + if (combo & IVTV_IRQ_DMA_READ) { + ivtv_irq_dma_read(itv); + } + + if (combo & IVTV_IRQ_ENC_DMA_COMPLETE) { + ivtv_irq_enc_dma_complete(itv); + } + + if (combo & IVTV_IRQ_ENC_PIO_COMPLETE) { + ivtv_irq_enc_pio_complete(itv); + } + + if (combo & IVTV_IRQ_DMA_ERR) { + ivtv_irq_dma_err(itv); + } + + if (combo & IVTV_IRQ_ENC_START_CAP) { + ivtv_irq_enc_start_cap(itv); + } + + if (combo & IVTV_IRQ_ENC_VBI_CAP) { + ivtv_irq_enc_vbi_cap(itv); + } + + if (combo & IVTV_IRQ_DEC_VBI_RE_INSERT) { + ivtv_irq_dec_vbi_reinsert(itv); + } + + if (combo & IVTV_IRQ_ENC_EOS) { + IVTV_DEBUG_IRQ("ENC EOS\n"); + set_bit(IVTV_F_I_EOS, &itv->i_flags); + wake_up(&itv->eos_waitq); + } + + if (combo & IVTV_IRQ_DEC_DATA_REQ) { + ivtv_irq_dec_data_req(itv); + } + + /* Decoder Vertical Sync - We can't rely on 'combo', so check if vsync enabled */ + if (~itv->irqmask & IVTV_IRQ_DEC_VSYNC) { + ivtv_irq_vsync(itv); + } + + if (combo & IVTV_IRQ_ENC_VIM_RST) { + IVTV_DEBUG_IRQ("VIM RST\n"); + /*ivtv_vapi(itv, CX2341X_ENC_REFRESH_INPUT, 0); */ + } + + if (combo & IVTV_IRQ_DEC_AUD_MODE_CHG) { + IVTV_DEBUG_INFO("Stereo mode changed\n"); + } + + if ((combo & IVTV_IRQ_DMA) && !test_bit(IVTV_F_I_DMA, &itv->i_flags)) { + itv->irq_rr_idx++; + for (i = 0; i < IVTV_MAX_STREAMS; i++) { + int idx = (i + itv->irq_rr_idx) % IVTV_MAX_STREAMS; + struct ivtv_stream *s = &itv->streams[idx]; + + if (!test_and_clear_bit(IVTV_F_S_DMA_PENDING, &s->s_flags)) + continue; + if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) + ivtv_dma_dec_start(s); + else + ivtv_dma_enc_start(s); + break; + } + + if (i == IVTV_MAX_STREAMS && + test_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags)) + ivtv_udma_start(itv); + } + + if ((combo & IVTV_IRQ_DMA) && !test_bit(IVTV_F_I_PIO, &itv->i_flags)) { + itv->irq_rr_idx++; + for (i = 0; i < IVTV_MAX_STREAMS; i++) { + int idx = (i + itv->irq_rr_idx) % IVTV_MAX_STREAMS; + struct ivtv_stream *s = &itv->streams[idx]; + + if (!test_and_clear_bit(IVTV_F_S_PIO_PENDING, &s->s_flags)) + continue; + if (s->type == IVTV_DEC_STREAM_TYPE_VBI || s->type < IVTV_DEC_STREAM_TYPE_MPG) + ivtv_dma_enc_start(s); + break; + } + } + + if (test_and_clear_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags)) { + queue_kthread_work(&itv->irq_worker, &itv->irq_work); + } + + spin_unlock(&itv->dma_reg_lock); + + /* If we've just handled a 'forced' vsync, it's safest to say it + * wasn't ours. Another device may have triggered it at just + * the right time. + */ + return vsync_force ? IRQ_NONE : IRQ_HANDLED; +} + +void ivtv_unfinished_dma(unsigned long arg) +{ + struct ivtv *itv = (struct ivtv *)arg; + + if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) + return; + IVTV_ERR("DMA TIMEOUT %08x %d\n", read_reg(IVTV_REG_DMASTATUS), itv->cur_dma_stream); + + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + clear_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + wake_up(&itv->dma_waitq); +} diff --git a/drivers/media/video/ivtv/ivtv-irq.h b/drivers/media/pci/ivtv/ivtv-irq.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-irq.h rename to drivers/media/pci/ivtv/ivtv-irq.h diff --git a/drivers/media/video/ivtv/ivtv-mailbox.c b/drivers/media/pci/ivtv/ivtv-mailbox.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-mailbox.c rename to drivers/media/pci/ivtv/ivtv-mailbox.c diff --git a/drivers/media/video/ivtv/ivtv-mailbox.h b/drivers/media/pci/ivtv/ivtv-mailbox.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-mailbox.h rename to drivers/media/pci/ivtv/ivtv-mailbox.h diff --git a/drivers/media/video/ivtv/ivtv-queue.c b/drivers/media/pci/ivtv/ivtv-queue.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-queue.c rename to drivers/media/pci/ivtv/ivtv-queue.c diff --git a/drivers/media/video/ivtv/ivtv-queue.h b/drivers/media/pci/ivtv/ivtv-queue.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-queue.h rename to drivers/media/pci/ivtv/ivtv-queue.h diff --git a/drivers/media/video/ivtv/ivtv-routing.c b/drivers/media/pci/ivtv/ivtv-routing.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-routing.c rename to drivers/media/pci/ivtv/ivtv-routing.c diff --git a/drivers/media/video/ivtv/ivtv-routing.h b/drivers/media/pci/ivtv/ivtv-routing.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-routing.h rename to drivers/media/pci/ivtv/ivtv-routing.h diff --git a/drivers/media/pci/ivtv/ivtv-streams.c b/drivers/media/pci/ivtv/ivtv-streams.c new file mode 100644 index 000000000000..70dad588a677 --- /dev/null +++ b/drivers/media/pci/ivtv/ivtv-streams.c @@ -0,0 +1,1039 @@ +/* + init/start/stop/exit stream functions + Copyright (C) 2003-2004 Kevin Thayer + Copyright (C) 2004 Chris Kennedy + Copyright (C) 2005-2007 Hans Verkuil + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* License: GPL + * Author: Kevin Thayer + * + * This file will hold API related functions, both internal (firmware api) + * and external (v4l2, etc) + * + * ----- + * MPG600/MPG160 support by T.Adachi + * and Takeru KOMORIYA + * + * AVerMedia M179 GPIO info by Chris Pinkham + * using information provided by Jiun-Kuei Jung @ AVerMedia. + */ + +#include "ivtv-driver.h" +#include "ivtv-fileops.h" +#include "ivtv-queue.h" +#include "ivtv-mailbox.h" +#include "ivtv-ioctl.h" +#include "ivtv-irq.h" +#include "ivtv-yuv.h" +#include "ivtv-cards.h" +#include "ivtv-streams.h" +#include "ivtv-firmware.h" +#include + +static const struct v4l2_file_operations ivtv_v4l2_enc_fops = { + .owner = THIS_MODULE, + .read = ivtv_v4l2_read, + .write = ivtv_v4l2_write, + .open = ivtv_v4l2_open, + .unlocked_ioctl = video_ioctl2, + .release = ivtv_v4l2_close, + .poll = ivtv_v4l2_enc_poll, +}; + +static const struct v4l2_file_operations ivtv_v4l2_dec_fops = { + .owner = THIS_MODULE, + .read = ivtv_v4l2_read, + .write = ivtv_v4l2_write, + .open = ivtv_v4l2_open, + .unlocked_ioctl = video_ioctl2, + .release = ivtv_v4l2_close, + .poll = ivtv_v4l2_dec_poll, +}; + +static const struct v4l2_file_operations ivtv_v4l2_radio_fops = { + .owner = THIS_MODULE, + .open = ivtv_v4l2_open, + .unlocked_ioctl = video_ioctl2, + .release = ivtv_v4l2_close, + .poll = ivtv_v4l2_enc_poll, +}; + +#define IVTV_V4L2_DEC_MPG_OFFSET 16 /* offset from 0 to register decoder mpg v4l2 minors on */ +#define IVTV_V4L2_ENC_PCM_OFFSET 24 /* offset from 0 to register pcm v4l2 minors on */ +#define IVTV_V4L2_ENC_YUV_OFFSET 32 /* offset from 0 to register yuv v4l2 minors on */ +#define IVTV_V4L2_DEC_YUV_OFFSET 48 /* offset from 0 to register decoder yuv v4l2 minors on */ +#define IVTV_V4L2_DEC_VBI_OFFSET 8 /* offset from 0 to register decoder vbi input v4l2 minors on */ +#define IVTV_V4L2_DEC_VOUT_OFFSET 16 /* offset from 0 to register vbi output v4l2 minors on */ + +static struct { + const char *name; + int vfl_type; + int num_offset; + int dma, pio; + u32 v4l2_caps; + const struct v4l2_file_operations *fops; +} ivtv_stream_info[] = { + { /* IVTV_ENC_STREAM_TYPE_MPG */ + "encoder MPG", + VFL_TYPE_GRABBER, 0, + PCI_DMA_FROMDEVICE, 0, + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_YUV */ + "encoder YUV", + VFL_TYPE_GRABBER, IVTV_V4L2_ENC_YUV_OFFSET, + PCI_DMA_FROMDEVICE, 0, + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_VBI */ + "encoder VBI", + VFL_TYPE_VBI, 0, + PCI_DMA_FROMDEVICE, 0, + V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_PCM */ + "encoder PCM", + VFL_TYPE_GRABBER, IVTV_V4L2_ENC_PCM_OFFSET, + PCI_DMA_FROMDEVICE, 0, + V4L2_CAP_TUNER | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_RAD */ + "encoder radio", + VFL_TYPE_RADIO, 0, + PCI_DMA_NONE, 1, + V4L2_CAP_RADIO | V4L2_CAP_TUNER, + &ivtv_v4l2_radio_fops + }, + { /* IVTV_DEC_STREAM_TYPE_MPG */ + "decoder MPG", + VFL_TYPE_GRABBER, IVTV_V4L2_DEC_MPG_OFFSET, + PCI_DMA_TODEVICE, 0, + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_dec_fops + }, + { /* IVTV_DEC_STREAM_TYPE_VBI */ + "decoder VBI", + VFL_TYPE_VBI, IVTV_V4L2_DEC_VBI_OFFSET, + PCI_DMA_NONE, 1, + V4L2_CAP_SLICED_VBI_CAPTURE | V4L2_CAP_READWRITE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_DEC_STREAM_TYPE_VOUT */ + "decoder VOUT", + VFL_TYPE_VBI, IVTV_V4L2_DEC_VOUT_OFFSET, + PCI_DMA_NONE, 1, + V4L2_CAP_SLICED_VBI_OUTPUT | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_dec_fops + }, + { /* IVTV_DEC_STREAM_TYPE_YUV */ + "decoder YUV", + VFL_TYPE_GRABBER, IVTV_V4L2_DEC_YUV_OFFSET, + PCI_DMA_TODEVICE, 0, + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, + &ivtv_v4l2_dec_fops + } +}; + +static void ivtv_stream_init(struct ivtv *itv, int type) +{ + struct ivtv_stream *s = &itv->streams[type]; + struct video_device *vdev = s->vdev; + + /* we need to keep vdev, so restore it afterwards */ + memset(s, 0, sizeof(*s)); + s->vdev = vdev; + + /* initialize ivtv_stream fields */ + s->itv = itv; + s->type = type; + s->name = ivtv_stream_info[type].name; + s->caps = ivtv_stream_info[type].v4l2_caps; + + if (ivtv_stream_info[type].pio) + s->dma = PCI_DMA_NONE; + else + s->dma = ivtv_stream_info[type].dma; + s->buf_size = itv->stream_buf_size[type]; + if (s->buf_size) + s->buffers = (itv->options.kilobytes[type] * 1024 + s->buf_size - 1) / s->buf_size; + spin_lock_init(&s->qlock); + init_waitqueue_head(&s->waitq); + s->sg_handle = IVTV_DMA_UNMAPPED; + ivtv_queue_init(&s->q_free); + ivtv_queue_init(&s->q_full); + ivtv_queue_init(&s->q_dma); + ivtv_queue_init(&s->q_predma); + ivtv_queue_init(&s->q_io); +} + +static int ivtv_prep_dev(struct ivtv *itv, int type) +{ + struct ivtv_stream *s = &itv->streams[type]; + int num_offset = ivtv_stream_info[type].num_offset; + int num = itv->instance + ivtv_first_minor + num_offset; + + /* These four fields are always initialized. If vdev == NULL, then + this stream is not in use. In that case no other fields but these + four can be used. */ + s->vdev = NULL; + s->itv = itv; + s->type = type; + s->name = ivtv_stream_info[type].name; + + /* Check whether the radio is supported */ + if (type == IVTV_ENC_STREAM_TYPE_RAD && !(itv->v4l2_cap & V4L2_CAP_RADIO)) + return 0; + if (type >= IVTV_DEC_STREAM_TYPE_MPG && !(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return 0; + + /* User explicitly selected 0 buffers for these streams, so don't + create them. */ + if (ivtv_stream_info[type].dma != PCI_DMA_NONE && + itv->options.kilobytes[type] == 0) { + IVTV_INFO("Disabled %s device\n", ivtv_stream_info[type].name); + return 0; + } + + ivtv_stream_init(itv, type); + + /* allocate and initialize the v4l2 video device structure */ + s->vdev = video_device_alloc(); + if (s->vdev == NULL) { + IVTV_ERR("Couldn't allocate v4l2 video_device for %s\n", s->name); + return -ENOMEM; + } + + snprintf(s->vdev->name, sizeof(s->vdev->name), "%s %s", + itv->v4l2_dev.name, s->name); + + s->vdev->num = num; + s->vdev->v4l2_dev = &itv->v4l2_dev; + if (ivtv_stream_info[type].v4l2_caps & + (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_SLICED_VBI_OUTPUT)) + s->vdev->vfl_dir = VFL_DIR_TX; + s->vdev->fops = ivtv_stream_info[type].fops; + s->vdev->ctrl_handler = itv->v4l2_dev.ctrl_handler; + s->vdev->release = video_device_release; + s->vdev->tvnorms = V4L2_STD_ALL; + s->vdev->lock = &itv->serialize_lock; + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) { + v4l2_disable_ioctl(s->vdev, VIDIOC_S_AUDIO); + v4l2_disable_ioctl(s->vdev, VIDIOC_G_AUDIO); + v4l2_disable_ioctl(s->vdev, VIDIOC_ENUMAUDIO); + v4l2_disable_ioctl(s->vdev, VIDIOC_ENUMINPUT); + v4l2_disable_ioctl(s->vdev, VIDIOC_S_INPUT); + v4l2_disable_ioctl(s->vdev, VIDIOC_G_INPUT); + v4l2_disable_ioctl(s->vdev, VIDIOC_S_FREQUENCY); + v4l2_disable_ioctl(s->vdev, VIDIOC_G_FREQUENCY); + v4l2_disable_ioctl(s->vdev, VIDIOC_S_TUNER); + v4l2_disable_ioctl(s->vdev, VIDIOC_G_TUNER); + v4l2_disable_ioctl(s->vdev, VIDIOC_S_STD); + } + set_bit(V4L2_FL_USE_FH_PRIO, &s->vdev->flags); + ivtv_set_funcs(s->vdev); + return 0; +} + +/* Initialize v4l2 variables and prepare v4l2 devices */ +int ivtv_streams_setup(struct ivtv *itv) +{ + int type; + + /* Setup V4L2 Devices */ + for (type = 0; type < IVTV_MAX_STREAMS; type++) { + /* Prepare device */ + if (ivtv_prep_dev(itv, type)) + break; + + if (itv->streams[type].vdev == NULL) + continue; + + /* Allocate Stream */ + if (ivtv_stream_alloc(&itv->streams[type])) + break; + } + if (type == IVTV_MAX_STREAMS) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + ivtv_streams_cleanup(itv, 0); + return -ENOMEM; +} + +static int ivtv_reg_dev(struct ivtv *itv, int type) +{ + struct ivtv_stream *s = &itv->streams[type]; + int vfl_type = ivtv_stream_info[type].vfl_type; + const char *name; + int num; + + if (s->vdev == NULL) + return 0; + + num = s->vdev->num; + /* card number + user defined offset + device offset */ + if (type != IVTV_ENC_STREAM_TYPE_MPG) { + struct ivtv_stream *s_mpg = &itv->streams[IVTV_ENC_STREAM_TYPE_MPG]; + + if (s_mpg->vdev) + num = s_mpg->vdev->num + ivtv_stream_info[type].num_offset; + } + video_set_drvdata(s->vdev, s); + + /* Register device. First try the desired minor, then any free one. */ + if (video_register_device_no_warn(s->vdev, vfl_type, num)) { + IVTV_ERR("Couldn't register v4l2 device for %s (device node number %d)\n", + s->name, num); + video_device_release(s->vdev); + s->vdev = NULL; + return -ENOMEM; + } + name = video_device_node_name(s->vdev); + + switch (vfl_type) { + case VFL_TYPE_GRABBER: + IVTV_INFO("Registered device %s for %s (%d kB)\n", + name, s->name, itv->options.kilobytes[type]); + break; + case VFL_TYPE_RADIO: + IVTV_INFO("Registered device %s for %s\n", + name, s->name); + break; + case VFL_TYPE_VBI: + if (itv->options.kilobytes[type]) + IVTV_INFO("Registered device %s for %s (%d kB)\n", + name, s->name, itv->options.kilobytes[type]); + else + IVTV_INFO("Registered device %s for %s\n", + name, s->name); + break; + } + return 0; +} + +/* Register v4l2 devices */ +int ivtv_streams_register(struct ivtv *itv) +{ + int type; + int err = 0; + + /* Register V4L2 devices */ + for (type = 0; type < IVTV_MAX_STREAMS; type++) + err |= ivtv_reg_dev(itv, type); + + if (err == 0) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + ivtv_streams_cleanup(itv, 1); + return -ENOMEM; +} + +/* Unregister v4l2 devices */ +void ivtv_streams_cleanup(struct ivtv *itv, int unregister) +{ + int type; + + /* Teardown all streams */ + for (type = 0; type < IVTV_MAX_STREAMS; type++) { + struct video_device *vdev = itv->streams[type].vdev; + + itv->streams[type].vdev = NULL; + if (vdev == NULL) + continue; + + ivtv_stream_free(&itv->streams[type]); + /* Unregister or release device */ + if (unregister) + video_unregister_device(vdev); + else + video_device_release(vdev); + } +} + +static void ivtv_vbi_setup(struct ivtv *itv) +{ + int raw = ivtv_raw_vbi(itv); + u32 data[CX2341X_MBOX_MAX_DATA]; + int lines; + int i; + + /* Reset VBI */ + ivtv_vapi(itv, CX2341X_ENC_SET_VBI_LINE, 5, 0xffff , 0, 0, 0, 0); + + /* setup VBI registers */ + if (raw) + v4l2_subdev_call(itv->sd_video, vbi, s_raw_fmt, &itv->vbi.in.fmt.vbi); + else + v4l2_subdev_call(itv->sd_video, vbi, s_sliced_fmt, &itv->vbi.in.fmt.sliced); + + /* determine number of lines and total number of VBI bytes. + A raw line takes 1443 bytes: 2 * 720 + 4 byte frame header - 1 + The '- 1' byte is probably an unused U or V byte. Or something... + A sliced line takes 51 bytes: 4 byte frame header, 4 byte internal + header, 42 data bytes + checksum (to be confirmed) */ + if (raw) { + lines = itv->vbi.count * 2; + } else { + lines = itv->is_60hz ? 24 : 38; + if (itv->is_60hz && (itv->hw_flags & IVTV_HW_CX25840)) + lines += 2; + } + + itv->vbi.enc_size = lines * (raw ? itv->vbi.raw_size : itv->vbi.sliced_size); + + /* Note: sliced vs raw flag doesn't seem to have any effect + TODO: check mode (0x02) value with older ivtv versions. */ + data[0] = raw | 0x02 | (0xbd << 8); + + /* Every X number of frames a VBI interrupt arrives (frames as in 25 or 30 fps) */ + data[1] = 1; + /* The VBI frames are stored in a ringbuffer with this size (with a VBI frame as unit) */ + data[2] = raw ? 4 : 4 * (itv->vbi.raw_size / itv->vbi.enc_size); + /* The start/stop codes determine which VBI lines end up in the raw VBI data area. + The codes are from table 24 in the saa7115 datasheet. Each raw/sliced/video line + is framed with codes FF0000XX where XX is the SAV/EAV (Start/End of Active Video) + code. These values for raw VBI are obtained from a driver disassembly. The sliced + start/stop codes was deduced from this, but they do not appear in the driver. + Other code pairs that I found are: 0x250E6249/0x13545454 and 0x25256262/0x38137F54. + However, I have no idea what these values are for. */ + if (itv->hw_flags & IVTV_HW_CX25840) { + /* Setup VBI for the cx25840 digitizer */ + if (raw) { + data[3] = 0x20602060; + data[4] = 0x30703070; + } else { + data[3] = 0xB0F0B0F0; + data[4] = 0xA0E0A0E0; + } + /* Lines per frame */ + data[5] = lines; + /* bytes per line */ + data[6] = (raw ? itv->vbi.raw_size : itv->vbi.sliced_size); + } else { + /* Setup VBI for the saa7115 digitizer */ + if (raw) { + data[3] = 0x25256262; + data[4] = 0x387F7F7F; + } else { + data[3] = 0xABABECEC; + data[4] = 0xB6F1F1F1; + } + /* Lines per frame */ + data[5] = lines; + /* bytes per line */ + data[6] = itv->vbi.enc_size / lines; + } + + IVTV_DEBUG_INFO( + "Setup VBI API header 0x%08x pkts %d buffs %d ln %d sz %d\n", + data[0], data[1], data[2], data[5], data[6]); + + ivtv_api(itv, CX2341X_ENC_SET_VBI_CONFIG, 7, data); + + /* returns the VBI encoder memory area. */ + itv->vbi.enc_start = data[2]; + itv->vbi.fpi = data[0]; + if (!itv->vbi.fpi) + itv->vbi.fpi = 1; + + IVTV_DEBUG_INFO("Setup VBI start 0x%08x frames %d fpi %d\n", + itv->vbi.enc_start, data[1], itv->vbi.fpi); + + /* select VBI lines. + Note that the sliced argument seems to have no effect. */ + for (i = 2; i <= 24; i++) { + int valid; + + if (itv->is_60hz) { + valid = i >= 10 && i < 22; + } else { + valid = i >= 6 && i < 24; + } + ivtv_vapi(itv, CX2341X_ENC_SET_VBI_LINE, 5, i - 1, + valid, 0 , 0, 0); + ivtv_vapi(itv, CX2341X_ENC_SET_VBI_LINE, 5, (i - 1) | 0x80000000, + valid, 0, 0, 0); + } + + /* Remaining VBI questions: + - Is it possible to select particular VBI lines only for inclusion in the MPEG + stream? Currently you can only get the first X lines. + - Is mixed raw and sliced VBI possible? + - What's the meaning of the raw/sliced flag? + - What's the meaning of params 2, 3 & 4 of the Select VBI command? */ +} + +int ivtv_start_v4l2_encode_stream(struct ivtv_stream *s) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv *itv = s->itv; + int captype = 0, subtype = 0; + int enable_passthrough = 0; + + if (s->vdev == NULL) + return -EINVAL; + + IVTV_DEBUG_INFO("Start encoder stream %s\n", s->name); + + switch (s->type) { + case IVTV_ENC_STREAM_TYPE_MPG: + captype = 0; + subtype = 3; + + /* Stop Passthrough */ + if (itv->output_mode == OUT_PASSTHROUGH) { + ivtv_passthrough_mode(itv, 0); + enable_passthrough = 1; + } + itv->mpg_data_received = itv->vbi_data_inserted = 0; + itv->dualwatch_jiffies = jiffies; + itv->dualwatch_stereo_mode = v4l2_ctrl_g_ctrl(itv->cxhdl.audio_mode); + itv->search_pack_header = 0; + break; + + case IVTV_ENC_STREAM_TYPE_YUV: + if (itv->output_mode == OUT_PASSTHROUGH) { + captype = 2; + subtype = 11; /* video+audio+decoder */ + break; + } + captype = 1; + subtype = 1; + break; + case IVTV_ENC_STREAM_TYPE_PCM: + captype = 1; + subtype = 2; + break; + case IVTV_ENC_STREAM_TYPE_VBI: + captype = 1; + subtype = 4; + + itv->vbi.frame = 0; + itv->vbi.inserted_frame = 0; + memset(itv->vbi.sliced_mpeg_size, + 0, sizeof(itv->vbi.sliced_mpeg_size)); + break; + default: + return -EINVAL; + } + s->subtype = subtype; + s->buffers_stolen = 0; + + /* Clear Streamoff flags in case left from last capture */ + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + if (atomic_read(&itv->capturing) == 0) { + int digitizer; + + /* Always use frame based mode. Experiments have demonstrated that byte + stream based mode results in dropped frames and corruption. Not often, + but occasionally. Many thanks go to Leonard Orb who spent a lot of + effort and time trying to trace the cause of the drop outs. */ + /* 1 frame per DMA */ + /*ivtv_vapi(itv, CX2341X_ENC_SET_DMA_BLOCK_SIZE, 2, 128, 0); */ + ivtv_vapi(itv, CX2341X_ENC_SET_DMA_BLOCK_SIZE, 2, 1, 1); + + /* Stuff from Windows, we don't know what it is */ + ivtv_vapi(itv, CX2341X_ENC_SET_VERT_CROP_LINE, 1, 0); + /* According to the docs, this should be correct. However, this is + untested. I don't dare enable this without having tested it. + Only very few old cards actually have this hardware combination. + ivtv_vapi(itv, CX2341X_ENC_SET_VERT_CROP_LINE, 1, + ((itv->hw_flags & IVTV_HW_SAA7114) && itv->is_60hz) ? 10001 : 0); + */ + ivtv_vapi(itv, CX2341X_ENC_MISC, 2, 3, !itv->has_cx23415); + ivtv_vapi(itv, CX2341X_ENC_MISC, 2, 8, 0); + ivtv_vapi(itv, CX2341X_ENC_MISC, 2, 4, 1); + ivtv_vapi(itv, CX2341X_ENC_MISC, 1, 12); + + /* assign placeholder */ + ivtv_vapi(itv, CX2341X_ENC_SET_PLACEHOLDER, 12, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + if (itv->card->hw_all & (IVTV_HW_SAA7115 | IVTV_HW_SAA717X)) + digitizer = 0xF1; + else if (itv->card->hw_all & IVTV_HW_SAA7114) + digitizer = 0xEF; + else /* cx25840 */ + digitizer = 0x140; + + ivtv_vapi(itv, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, digitizer, digitizer); + + /* Setup VBI */ + if (itv->v4l2_cap & V4L2_CAP_VBI_CAPTURE) { + ivtv_vbi_setup(itv); + } + + /* assign program index info. Mask 7: select I/P/B, Num_req: 400 max */ + ivtv_vapi_result(itv, data, CX2341X_ENC_SET_PGM_INDEX_INFO, 2, 7, 400); + itv->pgm_info_offset = data[0]; + itv->pgm_info_num = data[1]; + itv->pgm_info_write_idx = 0; + itv->pgm_info_read_idx = 0; + + IVTV_DEBUG_INFO("PGM Index at 0x%08x with %d elements\n", + itv->pgm_info_offset, itv->pgm_info_num); + + /* Setup API for Stream */ + cx2341x_handler_setup(&itv->cxhdl); + + /* mute if capturing radio */ + if (test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags)) + ivtv_vapi(itv, CX2341X_ENC_MUTE_VIDEO, 1, + 1 | (v4l2_ctrl_g_ctrl(itv->cxhdl.video_mute_yuv) << 8)); + } + + /* Vsync Setup */ + if (itv->has_cx23415 && !test_and_set_bit(IVTV_F_I_DIG_RST, &itv->i_flags)) { + /* event notification (on) */ + ivtv_vapi(itv, CX2341X_ENC_SET_EVENT_NOTIFICATION, 4, 0, 1, IVTV_IRQ_ENC_VIM_RST, -1); + ivtv_clear_irq_mask(itv, IVTV_IRQ_ENC_VIM_RST); + } + + if (atomic_read(&itv->capturing) == 0) { + /* Clear all Pending Interrupts */ + ivtv_set_irq_mask(itv, IVTV_IRQ_MASK_CAPTURE); + + clear_bit(IVTV_F_I_EOS, &itv->i_flags); + + cx2341x_handler_set_busy(&itv->cxhdl, 1); + + /* Initialize Digitizer for Capture */ + /* Avoid tinny audio problem - ensure audio clocks are going */ + v4l2_subdev_call(itv->sd_audio, audio, s_stream, 1); + /* Avoid unpredictable PCI bus hang - disable video clocks */ + v4l2_subdev_call(itv->sd_video, video, s_stream, 0); + ivtv_msleep_timeout(300, 0); + ivtv_vapi(itv, CX2341X_ENC_INITIALIZE_INPUT, 0); + v4l2_subdev_call(itv->sd_video, video, s_stream, 1); + } + + /* begin_capture */ + if (ivtv_vapi(itv, CX2341X_ENC_START_CAPTURE, 2, captype, subtype)) + { + IVTV_DEBUG_WARN( "Error starting capture!\n"); + return -EINVAL; + } + + /* Start Passthrough */ + if (enable_passthrough) { + ivtv_passthrough_mode(itv, 1); + } + + if (s->type == IVTV_ENC_STREAM_TYPE_VBI) + ivtv_clear_irq_mask(itv, IVTV_IRQ_ENC_VBI_CAP); + else + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_CAPTURE); + + /* you're live! sit back and await interrupts :) */ + atomic_inc(&itv->capturing); + return 0; +} +EXPORT_SYMBOL(ivtv_start_v4l2_encode_stream); + +static int ivtv_setup_v4l2_decode_stream(struct ivtv_stream *s) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv *itv = s->itv; + int datatype; + u16 width; + u16 height; + + if (s->vdev == NULL) + return -EINVAL; + + IVTV_DEBUG_INFO("Setting some initial decoder settings\n"); + + width = itv->cxhdl.width; + height = itv->cxhdl.height; + + /* set audio mode to left/stereo for dual/stereo mode. */ + ivtv_vapi(itv, CX2341X_DEC_SET_AUDIO_MODE, 2, itv->audio_bilingual_mode, itv->audio_stereo_mode); + + /* set number of internal decoder buffers */ + ivtv_vapi(itv, CX2341X_DEC_SET_DISPLAY_BUFFERS, 1, 0); + + /* prebuffering */ + ivtv_vapi(itv, CX2341X_DEC_SET_PREBUFFERING, 1, 1); + + /* extract from user packets */ + ivtv_vapi_result(itv, data, CX2341X_DEC_EXTRACT_VBI, 1, 1); + itv->vbi.dec_start = data[0]; + + IVTV_DEBUG_INFO("Decoder VBI RE-Insert start 0x%08x size 0x%08x\n", + itv->vbi.dec_start, data[1]); + + /* set decoder source settings */ + /* Data type: 0 = mpeg from host, + 1 = yuv from encoder, + 2 = yuv_from_host */ + switch (s->type) { + case IVTV_DEC_STREAM_TYPE_YUV: + if (itv->output_mode == OUT_PASSTHROUGH) { + datatype = 1; + } else { + /* Fake size to avoid switching video standard */ + datatype = 2; + width = 720; + height = itv->is_out_50hz ? 576 : 480; + } + IVTV_DEBUG_INFO("Setup DEC YUV Stream data[0] = %d\n", datatype); + break; + case IVTV_DEC_STREAM_TYPE_MPG: + default: + datatype = 0; + break; + } + if (ivtv_vapi(itv, CX2341X_DEC_SET_DECODER_SOURCE, 4, datatype, + width, height, itv->cxhdl.audio_properties)) { + IVTV_DEBUG_WARN("Couldn't initialize decoder source\n"); + } + + /* Decoder sometimes dies here, so wait a moment */ + ivtv_msleep_timeout(10, 0); + + /* Known failure point for firmware, so check */ + return ivtv_firmware_check(itv, "ivtv_setup_v4l2_decode_stream"); +} + +int ivtv_start_v4l2_decode_stream(struct ivtv_stream *s, int gop_offset) +{ + struct ivtv *itv = s->itv; + int rc; + + if (s->vdev == NULL) + return -EINVAL; + + if (test_and_set_bit(IVTV_F_S_STREAMING, &s->s_flags)) + return 0; /* already started */ + + IVTV_DEBUG_INFO("Starting decode stream %s (gop_offset %d)\n", s->name, gop_offset); + + rc = ivtv_setup_v4l2_decode_stream(s); + if (rc < 0) { + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + return rc; + } + + /* set dma size to 65536 bytes */ + ivtv_vapi(itv, CX2341X_DEC_SET_DMA_BLOCK_SIZE, 1, 65536); + + /* Clear Streamoff */ + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + /* Zero out decoder counters */ + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA_END].data[0]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA_END].data[1]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA_END].data[2]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA_END].data[3]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[0]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[1]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[2]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[3]); + + /* turn on notification of dual/stereo mode change */ + ivtv_vapi(itv, CX2341X_DEC_SET_EVENT_NOTIFICATION, 4, 0, 1, IVTV_IRQ_DEC_AUD_MODE_CHG, -1); + + /* start playback */ + ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, gop_offset, 0); + + /* Let things settle before we actually start */ + ivtv_msleep_timeout(10, 0); + + /* Clear the following Interrupt mask bits for decoding */ + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_DECODE); + IVTV_DEBUG_IRQ("IRQ Mask is now: 0x%08x\n", itv->irqmask); + + /* you're live! sit back and await interrupts :) */ + atomic_inc(&itv->decoding); + return 0; +} + +void ivtv_stop_all_captures(struct ivtv *itv) +{ + int i; + + for (i = IVTV_MAX_STREAMS - 1; i >= 0; i--) { + struct ivtv_stream *s = &itv->streams[i]; + + if (s->vdev == NULL) + continue; + if (test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + ivtv_stop_v4l2_encode_stream(s, 0); + } + } +} + +int ivtv_stop_v4l2_encode_stream(struct ivtv_stream *s, int gop_end) +{ + struct ivtv *itv = s->itv; + DECLARE_WAITQUEUE(wait, current); + int cap_type; + int stopmode; + + if (s->vdev == NULL) + return -EINVAL; + + /* This function assumes that you are allowed to stop the capture + and that we are actually capturing */ + + IVTV_DEBUG_INFO("Stop Capture\n"); + + if (s->type == IVTV_DEC_STREAM_TYPE_VOUT) + return 0; + if (atomic_read(&itv->capturing) == 0) + return 0; + + switch (s->type) { + case IVTV_ENC_STREAM_TYPE_YUV: + cap_type = 1; + break; + case IVTV_ENC_STREAM_TYPE_PCM: + cap_type = 1; + break; + case IVTV_ENC_STREAM_TYPE_VBI: + cap_type = 1; + break; + case IVTV_ENC_STREAM_TYPE_MPG: + default: + cap_type = 0; + break; + } + + /* Stop Capture Mode */ + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && gop_end) { + stopmode = 0; + } else { + stopmode = 1; + } + + /* end_capture */ + /* when: 0 = end of GOP 1 = NOW!, type: 0 = mpeg, subtype: 3 = video+audio */ + ivtv_vapi(itv, CX2341X_ENC_STOP_CAPTURE, 3, stopmode, cap_type, s->subtype); + + if (!test_bit(IVTV_F_S_PASSTHROUGH, &s->s_flags)) { + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && gop_end) { + /* only run these if we're shutting down the last cap */ + unsigned long duration; + unsigned long then = jiffies; + + add_wait_queue(&itv->eos_waitq, &wait); + + set_current_state(TASK_INTERRUPTIBLE); + + /* wait 2s for EOS interrupt */ + while (!test_bit(IVTV_F_I_EOS, &itv->i_flags) && + time_before(jiffies, + then + msecs_to_jiffies(2000))) { + schedule_timeout(msecs_to_jiffies(10)); + } + + /* To convert jiffies to ms, we must multiply by 1000 + * and divide by HZ. To avoid runtime division, we + * convert this to multiplication by 1000/HZ. + * Since integer division truncates, we get the best + * accuracy if we do a rounding calculation of the constant. + * Think of the case where HZ is 1024. + */ + duration = ((1000 + HZ / 2) / HZ) * (jiffies - then); + + if (!test_bit(IVTV_F_I_EOS, &itv->i_flags)) { + IVTV_DEBUG_WARN("%s: EOS interrupt not received! stopping anyway.\n", s->name); + IVTV_DEBUG_WARN("%s: waited %lu ms.\n", s->name, duration); + } else { + IVTV_DEBUG_INFO("%s: EOS took %lu ms to occur.\n", s->name, duration); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&itv->eos_waitq, &wait); + set_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + } + + /* Handle any pending interrupts */ + ivtv_msleep_timeout(100, 0); + } + + atomic_dec(&itv->capturing); + + /* Clear capture and no-read bits */ + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + + if (s->type == IVTV_ENC_STREAM_TYPE_VBI) + ivtv_set_irq_mask(itv, IVTV_IRQ_ENC_VBI_CAP); + + if (atomic_read(&itv->capturing) > 0) { + return 0; + } + + cx2341x_handler_set_busy(&itv->cxhdl, 0); + + /* Set the following Interrupt mask bits for capture */ + ivtv_set_irq_mask(itv, IVTV_IRQ_MASK_CAPTURE); + del_timer(&itv->dma_timer); + + /* event notification (off) */ + if (test_and_clear_bit(IVTV_F_I_DIG_RST, &itv->i_flags)) { + /* type: 0 = refresh */ + /* on/off: 0 = off, intr: 0x10000000, mbox_id: -1: none */ + ivtv_vapi(itv, CX2341X_ENC_SET_EVENT_NOTIFICATION, 4, 0, 0, IVTV_IRQ_ENC_VIM_RST, -1); + ivtv_set_irq_mask(itv, IVTV_IRQ_ENC_VIM_RST); + } + + /* Raw-passthrough is implied on start. Make sure it's stopped so + the encoder will re-initialize when next started */ + ivtv_vapi(itv, CX2341X_ENC_STOP_CAPTURE, 3, 1, 2, 7); + + wake_up(&s->waitq); + + return 0; +} +EXPORT_SYMBOL(ivtv_stop_v4l2_encode_stream); + +int ivtv_stop_v4l2_decode_stream(struct ivtv_stream *s, int flags, u64 pts) +{ + static const struct v4l2_event ev = { + .type = V4L2_EVENT_EOS, + }; + struct ivtv *itv = s->itv; + + if (s->vdev == NULL) + return -EINVAL; + + if (s->type != IVTV_DEC_STREAM_TYPE_YUV && s->type != IVTV_DEC_STREAM_TYPE_MPG) + return -EINVAL; + + if (!test_bit(IVTV_F_S_STREAMING, &s->s_flags)) + return 0; + + IVTV_DEBUG_INFO("Stop Decode at %llu, flags: %x\n", (unsigned long long)pts, flags); + + /* Stop Decoder */ + if (!(flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) || pts) { + u32 tmp = 0; + + /* Wait until the decoder is no longer running */ + if (pts) { + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, + 0, (u32)(pts & 0xffffffff), (u32)(pts >> 32)); + } + while (1) { + u32 data[CX2341X_MBOX_MAX_DATA]; + ivtv_vapi_result(itv, data, CX2341X_DEC_GET_XFER_INFO, 0); + if (s->q_full.buffers + s->q_dma.buffers == 0) { + if (tmp == data[3]) + break; + tmp = data[3]; + } + if (ivtv_msleep_timeout(100, 1)) + break; + } + } + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, flags & V4L2_DEC_CMD_STOP_TO_BLACK, 0, 0); + + /* turn off notification of dual/stereo mode change */ + ivtv_vapi(itv, CX2341X_DEC_SET_EVENT_NOTIFICATION, 4, 0, 0, IVTV_IRQ_DEC_AUD_MODE_CHG, -1); + + ivtv_set_irq_mask(itv, IVTV_IRQ_MASK_DECODE); + del_timer(&itv->dma_timer); + + clear_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + ivtv_flush_queues(s); + + /* decoder needs time to settle */ + ivtv_msleep_timeout(40, 0); + + /* decrement decoding */ + atomic_dec(&itv->decoding); + + set_bit(IVTV_F_I_EV_DEC_STOPPED, &itv->i_flags); + wake_up(&itv->event_waitq); + v4l2_event_queue(s->vdev, &ev); + + /* wake up wait queues */ + wake_up(&s->waitq); + + return 0; +} + +int ivtv_passthrough_mode(struct ivtv *itv, int enable) +{ + struct ivtv_stream *yuv_stream = &itv->streams[IVTV_ENC_STREAM_TYPE_YUV]; + struct ivtv_stream *dec_stream = &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + + if (yuv_stream->vdev == NULL || dec_stream->vdev == NULL) + return -EINVAL; + + IVTV_DEBUG_INFO("ivtv ioctl: Select passthrough mode\n"); + + /* Prevent others from starting/stopping streams while we + initiate/terminate passthrough mode */ + if (enable) { + if (itv->output_mode == OUT_PASSTHROUGH) { + return 0; + } + if (ivtv_set_output_mode(itv, OUT_PASSTHROUGH) != OUT_PASSTHROUGH) + return -EBUSY; + + /* Fully initialize stream, and then unflag init */ + set_bit(IVTV_F_S_PASSTHROUGH, &dec_stream->s_flags); + set_bit(IVTV_F_S_STREAMING, &dec_stream->s_flags); + + /* Setup YUV Decoder */ + ivtv_setup_v4l2_decode_stream(dec_stream); + + /* Start Decoder */ + ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, 0, 1); + atomic_inc(&itv->decoding); + + /* Setup capture if not already done */ + if (atomic_read(&itv->capturing) == 0) { + cx2341x_handler_setup(&itv->cxhdl); + cx2341x_handler_set_busy(&itv->cxhdl, 1); + } + + /* Start Passthrough Mode */ + ivtv_vapi(itv, CX2341X_ENC_START_CAPTURE, 2, 2, 11); + atomic_inc(&itv->capturing); + return 0; + } + + if (itv->output_mode != OUT_PASSTHROUGH) + return 0; + + /* Stop Passthrough Mode */ + ivtv_vapi(itv, CX2341X_ENC_STOP_CAPTURE, 3, 1, 2, 11); + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, 1, 0, 0); + + atomic_dec(&itv->capturing); + atomic_dec(&itv->decoding); + clear_bit(IVTV_F_S_PASSTHROUGH, &dec_stream->s_flags); + clear_bit(IVTV_F_S_STREAMING, &dec_stream->s_flags); + itv->output_mode = OUT_NONE; + if (atomic_read(&itv->capturing) == 0) + cx2341x_handler_set_busy(&itv->cxhdl, 0); + + return 0; +} diff --git a/drivers/media/video/ivtv/ivtv-streams.h b/drivers/media/pci/ivtv/ivtv-streams.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-streams.h rename to drivers/media/pci/ivtv/ivtv-streams.h diff --git a/drivers/media/video/ivtv/ivtv-udma.c b/drivers/media/pci/ivtv/ivtv-udma.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-udma.c rename to drivers/media/pci/ivtv/ivtv-udma.c diff --git a/drivers/media/video/ivtv/ivtv-udma.h b/drivers/media/pci/ivtv/ivtv-udma.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-udma.h rename to drivers/media/pci/ivtv/ivtv-udma.h diff --git a/drivers/media/video/ivtv/ivtv-vbi.c b/drivers/media/pci/ivtv/ivtv-vbi.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-vbi.c rename to drivers/media/pci/ivtv/ivtv-vbi.c diff --git a/drivers/media/video/ivtv/ivtv-vbi.h b/drivers/media/pci/ivtv/ivtv-vbi.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-vbi.h rename to drivers/media/pci/ivtv/ivtv-vbi.h diff --git a/drivers/media/video/ivtv/ivtv-version.h b/drivers/media/pci/ivtv/ivtv-version.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-version.h rename to drivers/media/pci/ivtv/ivtv-version.h diff --git a/drivers/media/video/ivtv/ivtv-yuv.c b/drivers/media/pci/ivtv/ivtv-yuv.c similarity index 100% rename from drivers/media/video/ivtv/ivtv-yuv.c rename to drivers/media/pci/ivtv/ivtv-yuv.c diff --git a/drivers/media/video/ivtv/ivtv-yuv.h b/drivers/media/pci/ivtv/ivtv-yuv.h similarity index 100% rename from drivers/media/video/ivtv/ivtv-yuv.h rename to drivers/media/pci/ivtv/ivtv-yuv.h diff --git a/drivers/media/video/ivtv/ivtvfb.c b/drivers/media/pci/ivtv/ivtvfb.c similarity index 100% rename from drivers/media/video/ivtv/ivtvfb.c rename to drivers/media/pci/ivtv/ivtvfb.c diff --git a/drivers/media/pci/mantis/Kconfig b/drivers/media/pci/mantis/Kconfig new file mode 100644 index 000000000000..d3cc21633b94 --- /dev/null +++ b/drivers/media/pci/mantis/Kconfig @@ -0,0 +1,38 @@ +config MANTIS_CORE + tristate "Mantis/Hopper PCI bridge based devices" + depends on PCI && I2C && INPUT && RC_CORE + + help + Support for PCI cards based on the Mantis and Hopper PCi bridge. + + Say Y if you own such a device and want to use it. + +config DVB_MANTIS + tristate "MANTIS based cards" + depends on MANTIS_CORE && DVB_CORE && PCI && I2C + select DVB_MB86A16 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA665x if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10021 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT + select DVB_PLL + help + Support for PCI cards based on the Mantis PCI bridge. + Say Y when you have a Mantis based DVB card and want to use it. + + If unsure say N. + +config DVB_HOPPER + tristate "HOPPER based cards" + depends on MANTIS_CORE && DVB_CORE && PCI && I2C + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_PLL + help + Support for PCI cards based on the Hopper PCI bridge. + Say Y when you have a Hopper based DVB card and want to use it. + + If unsure say N diff --git a/drivers/media/pci/mantis/Makefile b/drivers/media/pci/mantis/Makefile new file mode 100644 index 000000000000..f715051e4453 --- /dev/null +++ b/drivers/media/pci/mantis/Makefile @@ -0,0 +1,28 @@ +mantis_core-objs := mantis_ioc.o \ + mantis_uart.o \ + mantis_dma.o \ + mantis_pci.o \ + mantis_i2c.o \ + mantis_dvb.o \ + mantis_evm.o \ + mantis_hif.o \ + mantis_ca.o \ + mantis_pcmcia.o \ + mantis_input.o + +mantis-objs := mantis_cards.o \ + mantis_vp1033.o \ + mantis_vp1034.o \ + mantis_vp1041.o \ + mantis_vp2033.o \ + mantis_vp2040.o \ + mantis_vp3030.o + +hopper-objs := hopper_cards.o \ + hopper_vp3028.o + +obj-$(CONFIG_MANTIS_CORE) += mantis_core.o +obj-$(CONFIG_DVB_MANTIS) += mantis.o +obj-$(CONFIG_DVB_HOPPER) += hopper.o + +ccflags-y += -Idrivers/media/dvb-core/ -Idrivers/media/dvb-frontends/ diff --git a/drivers/media/dvb/mantis/hopper_cards.c b/drivers/media/pci/mantis/hopper_cards.c similarity index 100% rename from drivers/media/dvb/mantis/hopper_cards.c rename to drivers/media/pci/mantis/hopper_cards.c diff --git a/drivers/media/dvb/mantis/hopper_vp3028.c b/drivers/media/pci/mantis/hopper_vp3028.c similarity index 100% rename from drivers/media/dvb/mantis/hopper_vp3028.c rename to drivers/media/pci/mantis/hopper_vp3028.c diff --git a/drivers/media/dvb/mantis/hopper_vp3028.h b/drivers/media/pci/mantis/hopper_vp3028.h similarity index 100% rename from drivers/media/dvb/mantis/hopper_vp3028.h rename to drivers/media/pci/mantis/hopper_vp3028.h diff --git a/drivers/media/dvb/mantis/mantis_ca.c b/drivers/media/pci/mantis/mantis_ca.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_ca.c rename to drivers/media/pci/mantis/mantis_ca.c diff --git a/drivers/media/dvb/mantis/mantis_ca.h b/drivers/media/pci/mantis/mantis_ca.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_ca.h rename to drivers/media/pci/mantis/mantis_ca.h diff --git a/drivers/media/pci/mantis/mantis_cards.c b/drivers/media/pci/mantis/mantis_cards.c new file mode 100644 index 000000000000..0207d1f064e0 --- /dev/null +++ b/drivers/media/pci/mantis/mantis_cards.c @@ -0,0 +1,307 @@ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "dmxdev.h" +#include "dvbdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dvb_net.h" + +#include "mantis_common.h" + +#include "mantis_vp1033.h" +#include "mantis_vp1034.h" +#include "mantis_vp1041.h" +#include "mantis_vp2033.h" +#include "mantis_vp2040.h" +#include "mantis_vp3030.h" + +#include "mantis_dma.h" +#include "mantis_ca.h" +#include "mantis_dvb.h" +#include "mantis_uart.h" +#include "mantis_ioc.h" +#include "mantis_pci.h" +#include "mantis_i2c.h" +#include "mantis_reg.h" + +static unsigned int verbose; +module_param(verbose, int, 0644); +MODULE_PARM_DESC(verbose, "verbose startup messages, default is 0 (no)"); + +static int devs; + +#define DRIVER_NAME "Mantis" + +static char *label[10] = { + "DMA", + "IRQ-0", + "IRQ-1", + "OCERR", + "PABRT", + "RIPRR", + "PPERR", + "FTRGT", + "RISCI", + "RACK" +}; + +static irqreturn_t mantis_irq_handler(int irq, void *dev_id) +{ + u32 stat = 0, mask = 0; + u32 rst_stat = 0, rst_mask = 0; + + struct mantis_pci *mantis; + struct mantis_ca *ca; + + mantis = (struct mantis_pci *) dev_id; + if (unlikely(mantis == NULL)) { + dprintk(MANTIS_ERROR, 1, "Mantis == NULL"); + return IRQ_NONE; + } + ca = mantis->mantis_ca; + + stat = mmread(MANTIS_INT_STAT); + mask = mmread(MANTIS_INT_MASK); + if (!(stat & mask)) + return IRQ_NONE; + + rst_mask = MANTIS_GPIF_WRACK | + MANTIS_GPIF_OTHERR | + MANTIS_SBUF_WSTO | + MANTIS_GPIF_EXTIRQ; + + rst_stat = mmread(MANTIS_GPIF_STATUS); + rst_stat &= rst_mask; + mmwrite(rst_stat, MANTIS_GPIF_STATUS); + + mantis->mantis_int_stat = stat; + mantis->mantis_int_mask = mask; + dprintk(MANTIS_DEBUG, 0, "\n-- Stat=<%02x> Mask=<%02x> --", stat, mask); + if (stat & MANTIS_INT_RISCEN) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[0]); + } + if (stat & MANTIS_INT_IRQ0) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[1]); + mantis->gpif_status = rst_stat; + wake_up(&ca->hif_write_wq); + schedule_work(&ca->hif_evm_work); + } + if (stat & MANTIS_INT_IRQ1) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[2]); + schedule_work(&mantis->uart_work); + } + if (stat & MANTIS_INT_OCERR) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[3]); + } + if (stat & MANTIS_INT_PABORT) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[4]); + } + if (stat & MANTIS_INT_RIPERR) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[5]); + } + if (stat & MANTIS_INT_PPERR) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[6]); + } + if (stat & MANTIS_INT_FTRGT) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[7]); + } + if (stat & MANTIS_INT_RISCI) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[8]); + mantis->busy_block = (stat & MANTIS_INT_RISCSTAT) >> 28; + tasklet_schedule(&mantis->tasklet); + } + if (stat & MANTIS_INT_I2CDONE) { + dprintk(MANTIS_DEBUG, 0, "<%s>", label[9]); + wake_up(&mantis->i2c_wq); + } + mmwrite(stat, MANTIS_INT_STAT); + stat &= ~(MANTIS_INT_RISCEN | MANTIS_INT_I2CDONE | + MANTIS_INT_I2CRACK | MANTIS_INT_PCMCIA7 | + MANTIS_INT_PCMCIA6 | MANTIS_INT_PCMCIA5 | + MANTIS_INT_PCMCIA4 | MANTIS_INT_PCMCIA3 | + MANTIS_INT_PCMCIA2 | MANTIS_INT_PCMCIA1 | + MANTIS_INT_PCMCIA0 | MANTIS_INT_IRQ1 | + MANTIS_INT_IRQ0 | MANTIS_INT_OCERR | + MANTIS_INT_PABORT | MANTIS_INT_RIPERR | + MANTIS_INT_PPERR | MANTIS_INT_FTRGT | + MANTIS_INT_RISCI); + + if (stat) + dprintk(MANTIS_DEBUG, 0, " Stat=<%02x> Mask=<%02x>", stat, mask); + + dprintk(MANTIS_DEBUG, 0, "\n"); + return IRQ_HANDLED; +} + +static int __devinit mantis_pci_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) +{ + struct mantis_pci *mantis; + struct mantis_hwconfig *config; + int err = 0; + + mantis = kzalloc(sizeof(struct mantis_pci), GFP_KERNEL); + if (mantis == NULL) { + printk(KERN_ERR "%s ERROR: Out of memory\n", __func__); + err = -ENOMEM; + goto fail0; + } + + mantis->num = devs; + mantis->verbose = verbose; + mantis->pdev = pdev; + config = (struct mantis_hwconfig *) pci_id->driver_data; + config->irq_handler = &mantis_irq_handler; + mantis->hwconfig = config; + + err = mantis_pci_init(mantis); + if (err) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis PCI initialization failed <%d>", err); + goto fail1; + } + + err = mantis_stream_control(mantis, STREAM_TO_HIF); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis stream control failed <%d>", err); + goto fail1; + } + + err = mantis_i2c_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis I2C initialization failed <%d>", err); + goto fail2; + } + + err = mantis_get_mac(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis MAC address read failed <%d>", err); + goto fail2; + } + + err = mantis_dma_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DMA initialization failed <%d>", err); + goto fail3; + } + + err = mantis_dvb_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DVB initialization failed <%d>", err); + goto fail4; + } + err = mantis_uart_init(mantis); + if (err < 0) { + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis UART initialization failed <%d>", err); + goto fail6; + } + + devs++; + + return err; + + + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis UART exit! <%d>", err); + mantis_uart_exit(mantis); + +fail6: +fail4: + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis DMA exit! <%d>", err); + mantis_dma_exit(mantis); + +fail3: + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis I2C exit! <%d>", err); + mantis_i2c_exit(mantis); + +fail2: + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis PCI exit! <%d>", err); + mantis_pci_exit(mantis); + +fail1: + dprintk(MANTIS_ERROR, 1, "ERROR: Mantis free! <%d>", err); + kfree(mantis); + +fail0: + return err; +} + +static void __devexit mantis_pci_remove(struct pci_dev *pdev) +{ + struct mantis_pci *mantis = pci_get_drvdata(pdev); + + if (mantis) { + + mantis_uart_exit(mantis); + mantis_dvb_exit(mantis); + mantis_dma_exit(mantis); + mantis_i2c_exit(mantis); + mantis_pci_exit(mantis); + kfree(mantis); + } + return; +} + +static struct pci_device_id mantis_pci_table[] = { + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_1033_DVB_S, &vp1033_config), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_1034_DVB_S, &vp1034_config), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_1041_DVB_S2, &vp1041_config), + MAKE_ENTRY(TECHNISAT, SKYSTAR_HD2_10, &vp1041_config), + MAKE_ENTRY(TECHNISAT, SKYSTAR_HD2_20, &vp1041_config), + MAKE_ENTRY(TERRATEC, CINERGY_S2_PCI_HD, &vp1041_config), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_2033_DVB_C, &vp2033_config), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_2040_DVB_C, &vp2040_config), + MAKE_ENTRY(TECHNISAT, CABLESTAR_HD2, &vp2040_config), + MAKE_ENTRY(TERRATEC, CINERGY_C, &vp2040_config), + MAKE_ENTRY(TWINHAN_TECHNOLOGIES, MANTIS_VP_3030_DVB_T, &vp3030_config), + { } +}; + +MODULE_DEVICE_TABLE(pci, mantis_pci_table); + +static struct pci_driver mantis_pci_driver = { + .name = DRIVER_NAME, + .id_table = mantis_pci_table, + .probe = mantis_pci_probe, + .remove = mantis_pci_remove, +}; + +static int __devinit mantis_init(void) +{ + return pci_register_driver(&mantis_pci_driver); +} + +static void __devexit mantis_exit(void) +{ + return pci_unregister_driver(&mantis_pci_driver); +} + +module_init(mantis_init); +module_exit(mantis_exit); + +MODULE_DESCRIPTION("MANTIS driver"); +MODULE_AUTHOR("Manu Abraham"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/mantis/mantis_common.h b/drivers/media/pci/mantis/mantis_common.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_common.h rename to drivers/media/pci/mantis/mantis_common.h diff --git a/drivers/media/pci/mantis/mantis_core.c b/drivers/media/pci/mantis/mantis_core.c new file mode 100644 index 000000000000..684d9061fe2a --- /dev/null +++ b/drivers/media/pci/mantis/mantis_core.c @@ -0,0 +1,235 @@ +/* + Mantis PCI bridge driver + + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "mantis_common.h" +#include "mantis_core.h" +#include "mantis_vp1033.h" +#include "mantis_vp1034.h" +#include "mantis_vp1041.h" +#include "mantis_vp2033.h" +#include "mantis_vp2040.h" +#include "mantis_vp3030.h" + +static int read_eeprom_byte(struct mantis_pci *mantis, u8 *data, u8 length) +{ + int err; + struct i2c_msg msg[] = { + { + .addr = 0x50, + .flags = 0, + .buf = data, + .len = 1 + }, { + .addr = 0x50, + .flags = I2C_M_RD, + .buf = data, + .len = length + }, + }; + + err = i2c_transfer(&mantis->adapter, msg, 2); + if (err < 0) { + dprintk(verbose, MANTIS_ERROR, 1, + "ERROR: i2c read: < err=%i d0=0x%02x d1=0x%02x >", + err, data[0], data[1]); + + return err; + } + + return 0; +} + +static int write_eeprom_byte(struct mantis_pci *mantis, u8 *data, u8 length) +{ + int err; + + struct i2c_msg msg = { + .addr = 0x50, + .flags = 0, + .buf = data, + .len = length + }; + + err = i2c_transfer(&mantis->adapter, &msg, 1); + if (err < 0) { + dprintk(verbose, MANTIS_ERROR, 1, + "ERROR: i2c write: < err=%i length=0x%02x d0=0x%02x, d1=0x%02x >", + err, length, data[0], data[1]); + + return err; + } + + return 0; +} + +static int get_mac_address(struct mantis_pci *mantis) +{ + int err; + + mantis->mac_address[0] = 0x08; + err = read_eeprom_byte(mantis, &mantis->mac_address[0], 6); + if (err < 0) { + dprintk(verbose, MANTIS_ERROR, 1, "Mantis EEPROM read error"); + + return err; + } + dprintk(verbose, MANTIS_ERROR, 0, + " MAC Address=[%pM]\n", mantis->mac_address); + + return 0; +} + +#define MANTIS_MODEL_UNKNOWN "UNKNOWN" +#define MANTIS_DEV_UNKNOWN "UNKNOWN" + +struct mantis_hwconfig unknown_device = { + .model_name = MANTIS_MODEL_UNKNOWN, + .dev_type = MANTIS_DEV_UNKNOWN, +}; + +static void mantis_load_config(struct mantis_pci *mantis) +{ + switch (mantis->subsystem_device) { + case MANTIS_VP_1033_DVB_S: /* VP-1033 */ + mantis->hwconfig = &vp1033_mantis_config; + break; + case MANTIS_VP_1034_DVB_S: /* VP-1034 */ + mantis->hwconfig = &vp1034_mantis_config; + break; + case MANTIS_VP_1041_DVB_S2: /* VP-1041 */ + case TECHNISAT_SKYSTAR_HD2: + mantis->hwconfig = &vp1041_mantis_config; + break; + case MANTIS_VP_2033_DVB_C: /* VP-2033 */ + mantis->hwconfig = &vp2033_mantis_config; + break; + case MANTIS_VP_2040_DVB_C: /* VP-2040 */ + case CINERGY_C: /* VP-2040 clone */ + case TECHNISAT_CABLESTAR_HD2: + mantis->hwconfig = &vp2040_mantis_config; + break; + case MANTIS_VP_3030_DVB_T: /* VP-3030 */ + mantis->hwconfig = &vp3030_mantis_config; + break; + default: + mantis->hwconfig = &unknown_device; + break; + } +} + +int mantis_core_init(struct mantis_pci *mantis) +{ + int err = 0; + + mantis_load_config(mantis); + dprintk(verbose, MANTIS_ERROR, 0, "found a %s PCI %s device on (%02x:%02x.%x),\n", + mantis->hwconfig->model_name, mantis->hwconfig->dev_type, + mantis->pdev->bus->number, PCI_SLOT(mantis->pdev->devfn), PCI_FUNC(mantis->pdev->devfn)); + dprintk(verbose, MANTIS_ERROR, 0, " Mantis Rev %d [%04x:%04x], ", + mantis->revision, + mantis->subsystem_vendor, mantis->subsystem_device); + dprintk(verbose, MANTIS_ERROR, 0, + "irq: %d, latency: %d\n memory: 0x%lx, mmio: 0x%p\n", + mantis->pdev->irq, mantis->latency, + mantis->mantis_addr, mantis->mantis_mmio); + + err = mantis_i2c_init(mantis); + if (err < 0) { + dprintk(verbose, MANTIS_ERROR, 1, "Mantis I2C init failed"); + return err; + } + err = get_mac_address(mantis); + if (err < 0) { + dprintk(verbose, MANTIS_ERROR, 1, "get MAC address failed"); + return err; + } + err = mantis_dma_init(mantis); + if (err < 0) { + dprintk(verbose, MANTIS_ERROR, 1, "Mantis DMA init failed"); + return err; + } + err = mantis_dvb_init(mantis); + if (err < 0) { + dprintk(verbose, MANTIS_DEBUG, 1, "Mantis DVB init failed"); + return err; + } + err = mantis_uart_init(mantis); + if (err < 0) { + dprintk(verbose, MANTIS_DEBUG, 1, "Mantis UART init failed"); + return err; + } + + return 0; +} + +int mantis_core_exit(struct mantis_pci *mantis) +{ + mantis_dma_stop(mantis); + dprintk(verbose, MANTIS_ERROR, 1, "DMA engine stopping"); + + mantis_uart_exit(mantis); + dprintk(verbose, MANTIS_ERROR, 1, "UART exit failed"); + + if (mantis_dma_exit(mantis) < 0) + dprintk(verbose, MANTIS_ERROR, 1, "DMA exit failed"); + if (mantis_dvb_exit(mantis) < 0) + dprintk(verbose, MANTIS_ERROR, 1, "DVB exit failed"); + if (mantis_i2c_exit(mantis) < 0) + dprintk(verbose, MANTIS_ERROR, 1, "I2C adapter delete.. failed"); + + return 0; +} + +/* Turn the given bit on or off. */ +void gpio_set_bits(struct mantis_pci *mantis, u32 bitpos, u8 value) +{ + u32 cur; + + cur = mmread(MANTIS_GPIF_ADDR); + if (value) + mantis->gpio_status = cur | (1 << bitpos); + else + mantis->gpio_status = cur & (~(1 << bitpos)); + + mmwrite(mantis->gpio_status, MANTIS_GPIF_ADDR); + mmwrite(0x00, MANTIS_GPIF_DOUT); + udelay(100); +} + +/* direction = 0 , no CI passthrough ; 1 , CI passthrough */ +void mantis_set_direction(struct mantis_pci *mantis, int direction) +{ + u32 reg; + + reg = mmread(0x28); + dprintk(verbose, MANTIS_DEBUG, 1, "TS direction setup"); + if (direction == 0x01) { + /* to CI */ + reg |= 0x04; + mmwrite(reg, 0x28); + reg &= 0xff - 0x04; + mmwrite(reg, 0x28); + } else { + reg &= 0xff - 0x04; + mmwrite(reg, 0x28); + reg |= 0x04; + mmwrite(reg, 0x28); + } +} diff --git a/drivers/media/dvb/mantis/mantis_core.h b/drivers/media/pci/mantis/mantis_core.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_core.h rename to drivers/media/pci/mantis/mantis_core.h diff --git a/drivers/media/dvb/mantis/mantis_dma.c b/drivers/media/pci/mantis/mantis_dma.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_dma.c rename to drivers/media/pci/mantis/mantis_dma.c diff --git a/drivers/media/dvb/mantis/mantis_dma.h b/drivers/media/pci/mantis/mantis_dma.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_dma.h rename to drivers/media/pci/mantis/mantis_dma.h diff --git a/drivers/media/pci/mantis/mantis_dvb.c b/drivers/media/pci/mantis/mantis_dvb.c new file mode 100644 index 000000000000..5d15c6b74d9b --- /dev/null +++ b/drivers/media/pci/mantis/mantis_dvb.c @@ -0,0 +1,301 @@ +/* + Mantis PCI bridge driver + Copyright (C) Manu Abraham (abraham.manu@gmail.com) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include + +#include +#include +#include +#include +#include + +#include "dmxdev.h" +#include "dvbdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dvb_net.h" + +#include "mantis_common.h" +#include "mantis_dma.h" +#include "mantis_ca.h" +#include "mantis_ioc.h" +#include "mantis_dvb.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +int mantis_frontend_power(struct mantis_pci *mantis, enum mantis_power power) +{ + struct mantis_hwconfig *config = mantis->hwconfig; + + switch (power) { + case POWER_ON: + dprintk(MANTIS_DEBUG, 1, "Power ON"); + mantis_gpio_set_bits(mantis, config->power, POWER_ON); + msleep(100); + mantis_gpio_set_bits(mantis, config->power, POWER_ON); + msleep(100); + break; + + case POWER_OFF: + dprintk(MANTIS_DEBUG, 1, "Power OFF"); + mantis_gpio_set_bits(mantis, config->power, POWER_OFF); + msleep(100); + break; + + default: + dprintk(MANTIS_DEBUG, 1, "Unknown state <%02x>", power); + return -1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_frontend_power); + +void mantis_frontend_soft_reset(struct mantis_pci *mantis) +{ + struct mantis_hwconfig *config = mantis->hwconfig; + + dprintk(MANTIS_DEBUG, 1, "Frontend RESET"); + mantis_gpio_set_bits(mantis, config->reset, 0); + msleep(100); + mantis_gpio_set_bits(mantis, config->reset, 0); + msleep(100); + mantis_gpio_set_bits(mantis, config->reset, 1); + msleep(100); + mantis_gpio_set_bits(mantis, config->reset, 1); + msleep(100); + + return; +} +EXPORT_SYMBOL_GPL(mantis_frontend_soft_reset); + +static int mantis_frontend_shutdown(struct mantis_pci *mantis) +{ + int err; + + mantis_frontend_soft_reset(mantis); + err = mantis_frontend_power(mantis, POWER_OFF); + if (err != 0) { + dprintk(MANTIS_ERROR, 1, "Frontend POWER OFF failed! <%d>", err); + return 1; + } + + return 0; +} + +static int mantis_dvb_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct mantis_pci *mantis = dvbdmx->priv; + + dprintk(MANTIS_DEBUG, 1, "Mantis DVB Start feed"); + if (!dvbdmx->dmx.frontend) { + dprintk(MANTIS_DEBUG, 1, "no frontend ?"); + return -EINVAL; + } + + mantis->feeds++; + dprintk(MANTIS_DEBUG, 1, "mantis start feed, feeds=%d", mantis->feeds); + + if (mantis->feeds == 1) { + dprintk(MANTIS_DEBUG, 1, "mantis start feed & dma"); + mantis_dma_start(mantis); + tasklet_enable(&mantis->tasklet); + } + + return mantis->feeds; +} + +static int mantis_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct mantis_pci *mantis = dvbdmx->priv; + + dprintk(MANTIS_DEBUG, 1, "Mantis DVB Stop feed"); + if (!dvbdmx->dmx.frontend) { + dprintk(MANTIS_DEBUG, 1, "no frontend ?"); + return -EINVAL; + } + + mantis->feeds--; + if (mantis->feeds == 0) { + dprintk(MANTIS_DEBUG, 1, "mantis stop feed and dma"); + tasklet_disable(&mantis->tasklet); + mantis_dma_stop(mantis); + } + + return 0; +} + +int __devinit mantis_dvb_init(struct mantis_pci *mantis) +{ + struct mantis_hwconfig *config = mantis->hwconfig; + int result = -1; + + dprintk(MANTIS_DEBUG, 1, "dvb_register_adapter"); + + result = dvb_register_adapter(&mantis->dvb_adapter, + "Mantis DVB adapter", + THIS_MODULE, + &mantis->pdev->dev, + adapter_nr); + + if (result < 0) { + + dprintk(MANTIS_ERROR, 1, "Error registering adapter"); + return -ENODEV; + } + + mantis->dvb_adapter.priv = mantis; + mantis->demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + + mantis->demux.priv = mantis; + mantis->demux.filternum = 256; + mantis->demux.feednum = 256; + mantis->demux.start_feed = mantis_dvb_start_feed; + mantis->demux.stop_feed = mantis_dvb_stop_feed; + mantis->demux.write_to_decoder = NULL; + + dprintk(MANTIS_DEBUG, 1, "dvb_dmx_init"); + result = dvb_dmx_init(&mantis->demux); + if (result < 0) { + dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); + + goto err0; + } + + mantis->dmxdev.filternum = 256; + mantis->dmxdev.demux = &mantis->demux.dmx; + mantis->dmxdev.capabilities = 0; + dprintk(MANTIS_DEBUG, 1, "dvb_dmxdev_init"); + + result = dvb_dmxdev_init(&mantis->dmxdev, &mantis->dvb_adapter); + if (result < 0) { + + dprintk(MANTIS_ERROR, 1, "dvb_dmxdev_init failed, ERROR=%d", result); + goto err1; + } + + mantis->fe_hw.source = DMX_FRONTEND_0; + result = mantis->demux.dmx.add_frontend(&mantis->demux.dmx, &mantis->fe_hw); + if (result < 0) { + + dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); + goto err2; + } + + mantis->fe_mem.source = DMX_MEMORY_FE; + result = mantis->demux.dmx.add_frontend(&mantis->demux.dmx, &mantis->fe_mem); + if (result < 0) { + dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); + goto err3; + } + + result = mantis->demux.dmx.connect_frontend(&mantis->demux.dmx, &mantis->fe_hw); + if (result < 0) { + dprintk(MANTIS_ERROR, 1, "dvb_dmx_init failed, ERROR=%d", result); + goto err4; + } + + dvb_net_init(&mantis->dvb_adapter, &mantis->dvbnet, &mantis->demux.dmx); + tasklet_init(&mantis->tasklet, mantis_dma_xfer, (unsigned long) mantis); + tasklet_disable(&mantis->tasklet); + if (mantis->hwconfig) { + result = config->frontend_init(mantis, mantis->fe); + if (result < 0) { + dprintk(MANTIS_ERROR, 1, "!!! NO Frontends found !!!"); + goto err5; + } else { + if (mantis->fe == NULL) { + dprintk(MANTIS_ERROR, 1, "FE "); + goto err5; + } + + if (dvb_register_frontend(&mantis->dvb_adapter, mantis->fe)) { + dprintk(MANTIS_ERROR, 1, "ERROR: Frontend registration failed"); + + if (mantis->fe->ops.release) + mantis->fe->ops.release(mantis->fe); + + mantis->fe = NULL; + goto err5; + } + } + } + + return 0; + + /* Error conditions .. */ +err5: + tasklet_kill(&mantis->tasklet); + dvb_net_release(&mantis->dvbnet); + if (mantis->fe) { + dvb_unregister_frontend(mantis->fe); + dvb_frontend_detach(mantis->fe); + } +err4: + mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_mem); + +err3: + mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_hw); + +err2: + dvb_dmxdev_release(&mantis->dmxdev); + +err1: + dvb_dmx_release(&mantis->demux); + +err0: + dvb_unregister_adapter(&mantis->dvb_adapter); + + return result; +} +EXPORT_SYMBOL_GPL(mantis_dvb_init); + +int __devexit mantis_dvb_exit(struct mantis_pci *mantis) +{ + int err; + + if (mantis->fe) { + /* mantis_ca_exit(mantis); */ + err = mantis_frontend_shutdown(mantis); + if (err != 0) + dprintk(MANTIS_ERROR, 1, "Frontend exit while POWER ON! <%d>", err); + dvb_unregister_frontend(mantis->fe); + dvb_frontend_detach(mantis->fe); + } + + tasklet_kill(&mantis->tasklet); + dvb_net_release(&mantis->dvbnet); + + mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_mem); + mantis->demux.dmx.remove_frontend(&mantis->demux.dmx, &mantis->fe_hw); + + dvb_dmxdev_release(&mantis->dmxdev); + dvb_dmx_release(&mantis->demux); + + dprintk(MANTIS_DEBUG, 1, "dvb_unregister_adapter"); + dvb_unregister_adapter(&mantis->dvb_adapter); + + return 0; +} +EXPORT_SYMBOL_GPL(mantis_dvb_exit); diff --git a/drivers/media/dvb/mantis/mantis_dvb.h b/drivers/media/pci/mantis/mantis_dvb.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_dvb.h rename to drivers/media/pci/mantis/mantis_dvb.h diff --git a/drivers/media/dvb/mantis/mantis_evm.c b/drivers/media/pci/mantis/mantis_evm.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_evm.c rename to drivers/media/pci/mantis/mantis_evm.c diff --git a/drivers/media/dvb/mantis/mantis_hif.c b/drivers/media/pci/mantis/mantis_hif.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_hif.c rename to drivers/media/pci/mantis/mantis_hif.c diff --git a/drivers/media/dvb/mantis/mantis_hif.h b/drivers/media/pci/mantis/mantis_hif.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_hif.h rename to drivers/media/pci/mantis/mantis_hif.h diff --git a/drivers/media/dvb/mantis/mantis_i2c.c b/drivers/media/pci/mantis/mantis_i2c.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_i2c.c rename to drivers/media/pci/mantis/mantis_i2c.c diff --git a/drivers/media/dvb/mantis/mantis_i2c.h b/drivers/media/pci/mantis/mantis_i2c.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_i2c.h rename to drivers/media/pci/mantis/mantis_i2c.h diff --git a/drivers/media/dvb/mantis/mantis_input.c b/drivers/media/pci/mantis/mantis_input.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_input.c rename to drivers/media/pci/mantis/mantis_input.c diff --git a/drivers/media/dvb/mantis/mantis_ioc.c b/drivers/media/pci/mantis/mantis_ioc.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_ioc.c rename to drivers/media/pci/mantis/mantis_ioc.c diff --git a/drivers/media/dvb/mantis/mantis_ioc.h b/drivers/media/pci/mantis/mantis_ioc.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_ioc.h rename to drivers/media/pci/mantis/mantis_ioc.h diff --git a/drivers/media/dvb/mantis/mantis_link.h b/drivers/media/pci/mantis/mantis_link.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_link.h rename to drivers/media/pci/mantis/mantis_link.h diff --git a/drivers/media/dvb/mantis/mantis_pci.c b/drivers/media/pci/mantis/mantis_pci.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_pci.c rename to drivers/media/pci/mantis/mantis_pci.c diff --git a/drivers/media/dvb/mantis/mantis_pci.h b/drivers/media/pci/mantis/mantis_pci.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_pci.h rename to drivers/media/pci/mantis/mantis_pci.h diff --git a/drivers/media/dvb/mantis/mantis_pcmcia.c b/drivers/media/pci/mantis/mantis_pcmcia.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_pcmcia.c rename to drivers/media/pci/mantis/mantis_pcmcia.c diff --git a/drivers/media/dvb/mantis/mantis_reg.h b/drivers/media/pci/mantis/mantis_reg.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_reg.h rename to drivers/media/pci/mantis/mantis_reg.h diff --git a/drivers/media/dvb/mantis/mantis_uart.c b/drivers/media/pci/mantis/mantis_uart.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_uart.c rename to drivers/media/pci/mantis/mantis_uart.c diff --git a/drivers/media/dvb/mantis/mantis_uart.h b/drivers/media/pci/mantis/mantis_uart.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_uart.h rename to drivers/media/pci/mantis/mantis_uart.h diff --git a/drivers/media/dvb/mantis/mantis_vp1033.c b/drivers/media/pci/mantis/mantis_vp1033.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp1033.c rename to drivers/media/pci/mantis/mantis_vp1033.c diff --git a/drivers/media/dvb/mantis/mantis_vp1033.h b/drivers/media/pci/mantis/mantis_vp1033.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp1033.h rename to drivers/media/pci/mantis/mantis_vp1033.h diff --git a/drivers/media/dvb/mantis/mantis_vp1034.c b/drivers/media/pci/mantis/mantis_vp1034.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp1034.c rename to drivers/media/pci/mantis/mantis_vp1034.c diff --git a/drivers/media/dvb/mantis/mantis_vp1034.h b/drivers/media/pci/mantis/mantis_vp1034.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp1034.h rename to drivers/media/pci/mantis/mantis_vp1034.h diff --git a/drivers/media/dvb/mantis/mantis_vp1041.c b/drivers/media/pci/mantis/mantis_vp1041.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp1041.c rename to drivers/media/pci/mantis/mantis_vp1041.c diff --git a/drivers/media/dvb/mantis/mantis_vp1041.h b/drivers/media/pci/mantis/mantis_vp1041.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp1041.h rename to drivers/media/pci/mantis/mantis_vp1041.h diff --git a/drivers/media/dvb/mantis/mantis_vp2033.c b/drivers/media/pci/mantis/mantis_vp2033.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp2033.c rename to drivers/media/pci/mantis/mantis_vp2033.c diff --git a/drivers/media/dvb/mantis/mantis_vp2033.h b/drivers/media/pci/mantis/mantis_vp2033.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp2033.h rename to drivers/media/pci/mantis/mantis_vp2033.h diff --git a/drivers/media/dvb/mantis/mantis_vp2040.c b/drivers/media/pci/mantis/mantis_vp2040.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp2040.c rename to drivers/media/pci/mantis/mantis_vp2040.c diff --git a/drivers/media/dvb/mantis/mantis_vp2040.h b/drivers/media/pci/mantis/mantis_vp2040.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp2040.h rename to drivers/media/pci/mantis/mantis_vp2040.h diff --git a/drivers/media/dvb/mantis/mantis_vp3028.c b/drivers/media/pci/mantis/mantis_vp3028.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp3028.c rename to drivers/media/pci/mantis/mantis_vp3028.c diff --git a/drivers/media/dvb/mantis/mantis_vp3028.h b/drivers/media/pci/mantis/mantis_vp3028.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp3028.h rename to drivers/media/pci/mantis/mantis_vp3028.h diff --git a/drivers/media/dvb/mantis/mantis_vp3030.c b/drivers/media/pci/mantis/mantis_vp3030.c similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp3030.c rename to drivers/media/pci/mantis/mantis_vp3030.c diff --git a/drivers/media/dvb/mantis/mantis_vp3030.h b/drivers/media/pci/mantis/mantis_vp3030.h similarity index 100% rename from drivers/media/dvb/mantis/mantis_vp3030.h rename to drivers/media/pci/mantis/mantis_vp3030.h diff --git a/drivers/media/pci/meye/Kconfig b/drivers/media/pci/meye/Kconfig new file mode 100644 index 000000000000..b4bf848be5a0 --- /dev/null +++ b/drivers/media/pci/meye/Kconfig @@ -0,0 +1,13 @@ +config VIDEO_MEYE + tristate "Sony Vaio Picturebook Motion Eye Video For Linux" + depends on PCI && SONY_LAPTOP && VIDEO_V4L2 + ---help--- + This is the video4linux driver for the Motion Eye camera found + in the Vaio Picturebook laptops. Please read the material in + for more information. + + If you say Y or M here, you need to say Y or M to "Sony Laptop + Extras" in the misc device section. + + To compile this driver as a module, choose M here: the + module will be called meye. diff --git a/drivers/media/pci/meye/Makefile b/drivers/media/pci/meye/Makefile new file mode 100644 index 000000000000..49388518cd01 --- /dev/null +++ b/drivers/media/pci/meye/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_VIDEO_MEYE) += meye.o diff --git a/drivers/media/video/meye.c b/drivers/media/pci/meye/meye.c similarity index 100% rename from drivers/media/video/meye.c rename to drivers/media/pci/meye/meye.c diff --git a/drivers/media/video/meye.h b/drivers/media/pci/meye/meye.h similarity index 100% rename from drivers/media/video/meye.h rename to drivers/media/pci/meye/meye.h diff --git a/drivers/media/pci/ngene/Kconfig b/drivers/media/pci/ngene/Kconfig new file mode 100644 index 000000000000..637d506b23c5 --- /dev/null +++ b/drivers/media/pci/ngene/Kconfig @@ -0,0 +1,13 @@ +config DVB_NGENE + tristate "Micronas nGene support" + depends on DVB_CORE && PCI && I2C + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA18271C2DD if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2131 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + Support for Micronas PCI express cards with nGene bridge. + diff --git a/drivers/media/pci/ngene/Makefile b/drivers/media/pci/ngene/Makefile new file mode 100644 index 000000000000..5c0b5d6b9d69 --- /dev/null +++ b/drivers/media/pci/ngene/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for the nGene device driver +# + +ngene-objs := ngene-core.o ngene-i2c.o ngene-cards.o ngene-dvb.o + +obj-$(CONFIG_DVB_NGENE) += ngene.o + +ccflags-y += -Idrivers/media/dvb-core/ +ccflags-y += -Idrivers/media/dvb-frontends/ +ccflags-y += -Idrivers/media/tuners/ + +# For the staging CI driver cxd2099 +ccflags-y += -Idrivers/staging/media/cxd2099/ diff --git a/drivers/media/pci/ngene/ngene-cards.c b/drivers/media/pci/ngene/ngene-cards.c new file mode 100644 index 000000000000..96a13ed197d0 --- /dev/null +++ b/drivers/media/pci/ngene/ngene-cards.c @@ -0,0 +1,823 @@ +/* + * ngene-cards.c: nGene PCIe bridge driver - card specific info + * + * Copyright (C) 2005-2007 Micronas + * + * Copyright (C) 2008-2009 Ralph Metzler + * Modifications for new nGene firmware, + * support for EEPROM-copying, + * support for new dual DVB-S2 card prototype + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * 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 + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include + +#include "ngene.h" + +/* demods/tuners */ +#include "stv6110x.h" +#include "stv090x.h" +#include "lnbh24.h" +#include "lgdt330x.h" +#include "mt2131.h" +#include "tda18271c2dd.h" +#include "drxk.h" +#include "drxd.h" +#include "dvb-pll.h" + + +/****************************************************************************/ +/* Demod/tuner attachment ***************************************************/ +/****************************************************************************/ + +static int tuner_attach_stv6110(struct ngene_channel *chan) +{ + struct i2c_adapter *i2c; + struct stv090x_config *feconf = (struct stv090x_config *) + chan->dev->card_info->fe_config[chan->number]; + struct stv6110x_config *tunerconf = (struct stv6110x_config *) + chan->dev->card_info->tuner_config[chan->number]; + struct stv6110x_devctl *ctl; + + /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ + if (chan->number < 2) + i2c = &chan->dev->channel[0].i2c_adapter; + else + i2c = &chan->dev->channel[1].i2c_adapter; + + ctl = dvb_attach(stv6110x_attach, chan->fe, tunerconf, i2c); + if (ctl == NULL) { + printk(KERN_ERR DEVICE_NAME ": No STV6110X found!\n"); + return -ENODEV; + } + + feconf->tuner_init = ctl->tuner_init; + feconf->tuner_sleep = ctl->tuner_sleep; + feconf->tuner_set_mode = ctl->tuner_set_mode; + feconf->tuner_set_frequency = ctl->tuner_set_frequency; + feconf->tuner_get_frequency = ctl->tuner_get_frequency; + feconf->tuner_set_bandwidth = ctl->tuner_set_bandwidth; + feconf->tuner_get_bandwidth = ctl->tuner_get_bandwidth; + feconf->tuner_set_bbgain = ctl->tuner_set_bbgain; + feconf->tuner_get_bbgain = ctl->tuner_get_bbgain; + feconf->tuner_set_refclk = ctl->tuner_set_refclk; + feconf->tuner_get_status = ctl->tuner_get_status; + + return 0; +} + + +static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct ngene_channel *chan = fe->sec_priv; + int status; + + if (enable) { + down(&chan->dev->pll_mutex); + status = chan->gate_ctrl(fe, 1); + } else { + status = chan->gate_ctrl(fe, 0); + up(&chan->dev->pll_mutex); + } + return status; +} + +static int tuner_attach_tda18271(struct ngene_channel *chan) +{ + struct i2c_adapter *i2c; + struct dvb_frontend *fe; + + i2c = &chan->dev->channel[0].i2c_adapter; + if (chan->fe->ops.i2c_gate_ctrl) + chan->fe->ops.i2c_gate_ctrl(chan->fe, 1); + fe = dvb_attach(tda18271c2dd_attach, chan->fe, i2c, 0x60); + if (chan->fe->ops.i2c_gate_ctrl) + chan->fe->ops.i2c_gate_ctrl(chan->fe, 0); + if (!fe) { + printk(KERN_ERR "No TDA18271 found!\n"); + return -ENODEV; + } + + return 0; +} + +static int tuner_attach_probe(struct ngene_channel *chan) +{ + if (chan->demod_type == 0) + return tuner_attach_stv6110(chan); + if (chan->demod_type == 1) + return tuner_attach_tda18271(chan); + return -EINVAL; +} + +static int demod_attach_stv0900(struct ngene_channel *chan) +{ + struct i2c_adapter *i2c; + struct stv090x_config *feconf = (struct stv090x_config *) + chan->dev->card_info->fe_config[chan->number]; + + /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ + /* Note: Both adapters share the same i2c bus, but the demod */ + /* driver requires that each demod has its own i2c adapter */ + if (chan->number < 2) + i2c = &chan->dev->channel[0].i2c_adapter; + else + i2c = &chan->dev->channel[1].i2c_adapter; + + chan->fe = dvb_attach(stv090x_attach, feconf, i2c, + (chan->number & 1) == 0 ? STV090x_DEMODULATOR_0 + : STV090x_DEMODULATOR_1); + if (chan->fe == NULL) { + printk(KERN_ERR DEVICE_NAME ": No STV0900 found!\n"); + return -ENODEV; + } + + /* store channel info */ + if (feconf->tuner_i2c_lock) + chan->fe->analog_demod_priv = chan; + + if (!dvb_attach(lnbh24_attach, chan->fe, i2c, 0, + 0, chan->dev->card_info->lnb[chan->number])) { + printk(KERN_ERR DEVICE_NAME ": No LNBH24 found!\n"); + dvb_frontend_detach(chan->fe); + chan->fe = NULL; + return -ENODEV; + } + + return 0; +} + +static void cineS2_tuner_i2c_lock(struct dvb_frontend *fe, int lock) +{ + struct ngene_channel *chan = fe->analog_demod_priv; + + if (lock) + down(&chan->dev->pll_mutex); + else + up(&chan->dev->pll_mutex); +} + +static int i2c_read(struct i2c_adapter *adapter, u8 adr, u8 *val) +{ + struct i2c_msg msgs[1] = {{.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1 } }; + return (i2c_transfer(adapter, msgs, 1) == 1) ? 0 : -1; +} + +static int i2c_read_reg16(struct i2c_adapter *adapter, u8 adr, + u16 reg, u8 *val) +{ + u8 msg[2] = {reg>>8, reg&0xff}; + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = msg, .len = 2}, + {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1} }; + return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1; +} + +static int port_has_stv0900(struct i2c_adapter *i2c, int port) +{ + u8 val; + if (i2c_read_reg16(i2c, 0x68+port/2, 0xf100, &val) < 0) + return 0; + return 1; +} + +static int port_has_drxk(struct i2c_adapter *i2c, int port) +{ + u8 val; + + if (i2c_read(i2c, 0x29+port, &val) < 0) + return 0; + return 1; +} + +static int demod_attach_drxk(struct ngene_channel *chan, + struct i2c_adapter *i2c) +{ + struct drxk_config config; + + memset(&config, 0, sizeof(config)); + config.microcode_name = "drxk_a3.mc"; + config.qam_demod_parameter_count = 4; + config.adr = 0x29 + (chan->number ^ 2); + + chan->fe = dvb_attach(drxk_attach, &config, i2c); + if (!chan->fe) { + printk(KERN_ERR "No DRXK found!\n"); + return -ENODEV; + } + chan->fe->sec_priv = chan; + chan->gate_ctrl = chan->fe->ops.i2c_gate_ctrl; + chan->fe->ops.i2c_gate_ctrl = drxk_gate_ctrl; + return 0; +} + +static int cineS2_probe(struct ngene_channel *chan) +{ + struct i2c_adapter *i2c; + struct stv090x_config *fe_conf; + u8 buf[3]; + struct i2c_msg i2c_msg = { .flags = 0, .buf = buf }; + int rc; + + /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ + if (chan->number < 2) + i2c = &chan->dev->channel[0].i2c_adapter; + else + i2c = &chan->dev->channel[1].i2c_adapter; + + if (port_has_stv0900(i2c, chan->number)) { + chan->demod_type = 0; + fe_conf = chan->dev->card_info->fe_config[chan->number]; + /* demod found, attach it */ + rc = demod_attach_stv0900(chan); + if (rc < 0 || chan->number < 2) + return rc; + + /* demod #2: reprogram outputs DPN1 & DPN2 */ + i2c_msg.addr = fe_conf->address; + i2c_msg.len = 3; + buf[0] = 0xf1; + switch (chan->number) { + case 2: + buf[1] = 0x5c; + buf[2] = 0xc2; + break; + case 3: + buf[1] = 0x61; + buf[2] = 0xcc; + break; + default: + return -ENODEV; + } + rc = i2c_transfer(i2c, &i2c_msg, 1); + if (rc != 1) { + printk(KERN_ERR DEVICE_NAME ": could not setup DPNx\n"); + return -EIO; + } + } else if (port_has_drxk(i2c, chan->number^2)) { + chan->demod_type = 1; + demod_attach_drxk(chan, i2c); + } else { + printk(KERN_ERR "No demod found on chan %d\n", chan->number); + return -ENODEV; + } + return 0; +} + + +static struct lgdt330x_config aver_m780 = { + .demod_address = 0xb2 >> 1, + .demod_chip = LGDT3303, + .serial_mpeg = 0x00, /* PARALLEL */ + .clock_polarity_flip = 1, +}; + +static struct mt2131_config m780_tunerconfig = { + 0xc0 >> 1 +}; + +/* A single func to attach the demo and tuner, rather than + * use two sep funcs like the current design mandates. + */ +static int demod_attach_lg330x(struct ngene_channel *chan) +{ + chan->fe = dvb_attach(lgdt330x_attach, &aver_m780, &chan->i2c_adapter); + if (chan->fe == NULL) { + printk(KERN_ERR DEVICE_NAME ": No LGDT330x found!\n"); + return -ENODEV; + } + + dvb_attach(mt2131_attach, chan->fe, &chan->i2c_adapter, + &m780_tunerconfig, 0); + + return (chan->fe) ? 0 : -ENODEV; +} + +static int demod_attach_drxd(struct ngene_channel *chan) +{ + struct drxd_config *feconf; + + feconf = chan->dev->card_info->fe_config[chan->number]; + + chan->fe = dvb_attach(drxd_attach, feconf, chan, + &chan->i2c_adapter, &chan->dev->pci_dev->dev); + if (!chan->fe) { + pr_err("No DRXD found!\n"); + return -ENODEV; + } + + if (!dvb_attach(dvb_pll_attach, chan->fe, feconf->pll_address, + &chan->i2c_adapter, + feconf->pll_type)) { + pr_err("No pll(%d) found!\n", feconf->pll_type); + return -ENODEV; + } + return 0; +} + +/****************************************************************************/ +/* EEPROM TAGS **************************************************************/ +/****************************************************************************/ + +#define MICNG_EE_START 0x0100 +#define MICNG_EE_END 0x0FF0 + +#define MICNG_EETAG_END0 0x0000 +#define MICNG_EETAG_END1 0xFFFF + +/* 0x0001 - 0x000F reserved for housekeeping */ +/* 0xFFFF - 0xFFFE reserved for housekeeping */ + +/* Micronas assigned tags + EEProm tags for hardware support */ + +#define MICNG_EETAG_DRXD1_OSCDEVIATION 0x1000 /* 2 Bytes data */ +#define MICNG_EETAG_DRXD2_OSCDEVIATION 0x1001 /* 2 Bytes data */ + +#define MICNG_EETAG_MT2060_1_1STIF 0x1100 /* 2 Bytes data */ +#define MICNG_EETAG_MT2060_2_1STIF 0x1101 /* 2 Bytes data */ + +/* Tag range for OEMs */ + +#define MICNG_EETAG_OEM_FIRST 0xC000 +#define MICNG_EETAG_OEM_LAST 0xFFEF + +static int i2c_write_eeprom(struct i2c_adapter *adapter, + u8 adr, u16 reg, u8 data) +{ + u8 m[3] = {(reg >> 8), (reg & 0xff), data}; + struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = m, + .len = sizeof(m)}; + + if (i2c_transfer(adapter, &msg, 1) != 1) { + pr_err(DEVICE_NAME ": Error writing EEPROM!\n"); + return -EIO; + } + return 0; +} + +static int i2c_read_eeprom(struct i2c_adapter *adapter, + u8 adr, u16 reg, u8 *data, int len) +{ + u8 msg[2] = {(reg >> 8), (reg & 0xff)}; + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = msg, .len = 2 }, + {.addr = adr, .flags = I2C_M_RD, + .buf = data, .len = len} }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + pr_err(DEVICE_NAME ": Error reading EEPROM\n"); + return -EIO; + } + return 0; +} + +static int ReadEEProm(struct i2c_adapter *adapter, + u16 Tag, u32 MaxLen, u8 *data, u32 *pLength) +{ + int status = 0; + u16 Addr = MICNG_EE_START, Length, tag = 0; + u8 EETag[3]; + + while (Addr + sizeof(u16) + 1 < MICNG_EE_END) { + if (i2c_read_eeprom(adapter, 0x50, Addr, EETag, sizeof(EETag))) + return -1; + tag = (EETag[0] << 8) | EETag[1]; + if (tag == MICNG_EETAG_END0 || tag == MICNG_EETAG_END1) + return -1; + if (tag == Tag) + break; + Addr += sizeof(u16) + 1 + EETag[2]; + } + if (Addr + sizeof(u16) + 1 + EETag[2] > MICNG_EE_END) { + pr_err(DEVICE_NAME + ": Reached EOEE @ Tag = %04x Length = %3d\n", + tag, EETag[2]); + return -1; + } + Length = EETag[2]; + if (Length > MaxLen) + Length = (u16) MaxLen; + if (Length > 0) { + Addr += sizeof(u16) + 1; + status = i2c_read_eeprom(adapter, 0x50, Addr, data, Length); + if (!status) { + *pLength = EETag[2]; + if (Length < EETag[2]) + ; /*status=STATUS_BUFFER_OVERFLOW; */ + } + } + return status; +} + +static int WriteEEProm(struct i2c_adapter *adapter, + u16 Tag, u32 Length, u8 *data) +{ + int status = 0; + u16 Addr = MICNG_EE_START; + u8 EETag[3]; + u16 tag = 0; + int retry, i; + + while (Addr + sizeof(u16) + 1 < MICNG_EE_END) { + if (i2c_read_eeprom(adapter, 0x50, Addr, EETag, sizeof(EETag))) + return -1; + tag = (EETag[0] << 8) | EETag[1]; + if (tag == MICNG_EETAG_END0 || tag == MICNG_EETAG_END1) + return -1; + if (tag == Tag) + break; + Addr += sizeof(u16) + 1 + EETag[2]; + } + if (Addr + sizeof(u16) + 1 + EETag[2] > MICNG_EE_END) { + pr_err(DEVICE_NAME + ": Reached EOEE @ Tag = %04x Length = %3d\n", + tag, EETag[2]); + return -1; + } + + if (Length > EETag[2]) + return -EINVAL; + /* Note: We write the data one byte at a time to avoid + issues with page sizes. (which are different for + each manufacture and eeprom size) + */ + Addr += sizeof(u16) + 1; + for (i = 0; i < Length; i++, Addr++) { + status = i2c_write_eeprom(adapter, 0x50, Addr, data[i]); + + if (status) + break; + + /* Poll for finishing write cycle */ + retry = 10; + while (retry) { + u8 Tmp; + + msleep(50); + status = i2c_read_eeprom(adapter, 0x50, Addr, &Tmp, 1); + if (status) + break; + if (Tmp != data[i]) + pr_err(DEVICE_NAME + "eeprom write error\n"); + retry -= 1; + } + if (status) { + pr_err(DEVICE_NAME + ": Timeout polling eeprom\n"); + break; + } + } + return status; +} + +static int eeprom_read_ushort(struct i2c_adapter *adapter, u16 tag, u16 *data) +{ + int stat; + u8 buf[2]; + u32 len = 0; + + stat = ReadEEProm(adapter, tag, 2, buf, &len); + if (stat) + return stat; + if (len != 2) + return -EINVAL; + + *data = (buf[0] << 8) | buf[1]; + return 0; +} + +static int eeprom_write_ushort(struct i2c_adapter *adapter, u16 tag, u16 data) +{ + int stat; + u8 buf[2]; + + buf[0] = data >> 8; + buf[1] = data & 0xff; + stat = WriteEEProm(adapter, tag, 2, buf); + if (stat) + return stat; + return 0; +} + +static s16 osc_deviation(void *priv, s16 deviation, int flag) +{ + struct ngene_channel *chan = priv; + struct i2c_adapter *adap = &chan->i2c_adapter; + u16 data = 0; + + if (flag) { + data = (u16) deviation; + pr_info(DEVICE_NAME ": write deviation %d\n", + deviation); + eeprom_write_ushort(adap, 0x1000 + chan->number, data); + } else { + if (eeprom_read_ushort(adap, 0x1000 + chan->number, &data)) + data = 0; + pr_info(DEVICE_NAME ": read deviation %d\n", + (s16) data); + } + + return (s16) data; +} + +/****************************************************************************/ +/* Switch control (I2C gates, etc.) *****************************************/ +/****************************************************************************/ + + +static struct stv090x_config fe_cineS2 = { + .device = STV0900, + .demod_mode = STV090x_DUAL, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 27000000, + .address = 0x68, + + .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + + .repeater_level = STV090x_RPTLEVEL_16, + + .adc1_range = STV090x_ADC_1Vpp, + .adc2_range = STV090x_ADC_1Vpp, + + .diseqc_envelope_mode = true, + + .tuner_i2c_lock = cineS2_tuner_i2c_lock, +}; + +static struct stv090x_config fe_cineS2_2 = { + .device = STV0900, + .demod_mode = STV090x_DUAL, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 27000000, + .address = 0x69, + + .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + + .repeater_level = STV090x_RPTLEVEL_16, + + .adc1_range = STV090x_ADC_1Vpp, + .adc2_range = STV090x_ADC_1Vpp, + + .diseqc_envelope_mode = true, + + .tuner_i2c_lock = cineS2_tuner_i2c_lock, +}; + +static struct stv6110x_config tuner_cineS2_0 = { + .addr = 0x60, + .refclk = 27000000, + .clk_div = 1, +}; + +static struct stv6110x_config tuner_cineS2_1 = { + .addr = 0x63, + .refclk = 27000000, + .clk_div = 1, +}; + +static struct ngene_info ngene_info_cineS2 = { + .type = NGENE_SIDEWINDER, + .name = "Linux4Media cineS2 DVB-S2 Twin Tuner", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110}, + .fe_config = {&fe_cineS2, &fe_cineS2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0b, 0x08}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + +static struct ngene_info ngene_info_satixS2 = { + .type = NGENE_SIDEWINDER, + .name = "Mystique SaTiX-S2 Dual", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110}, + .fe_config = {&fe_cineS2, &fe_cineS2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0b, 0x08}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + +static struct ngene_info ngene_info_satixS2v2 = { + .type = NGENE_SIDEWINDER, + .name = "Mystique SaTiX-S2 Dual (v2)", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, + NGENE_IO_TSOUT}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_probe, tuner_attach_probe}, + .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0a, 0x08, 0x0b, 0x09}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + +static struct ngene_info ngene_info_cineS2v5 = { + .type = NGENE_SIDEWINDER, + .name = "Linux4Media cineS2 DVB-S2 Twin Tuner (v5)", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, + NGENE_IO_TSOUT}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_probe, tuner_attach_probe}, + .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0a, 0x08, 0x0b, 0x09}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + + +static struct ngene_info ngene_info_duoFlex = { + .type = NGENE_SIDEWINDER, + .name = "Digital Devices DuoFlex PCIe or miniPCIe", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, + NGENE_IO_TSOUT}, + .demod_attach = {cineS2_probe, cineS2_probe, cineS2_probe, cineS2_probe}, + .tuner_attach = {tuner_attach_probe, tuner_attach_probe, tuner_attach_probe, tuner_attach_probe}, + .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0a, 0x08, 0x0b, 0x09}, + .tsf = {3, 3}, + .fw_version = 18, + .msi_supported = true, +}; + +static struct ngene_info ngene_info_m780 = { + .type = NGENE_APP, + .name = "Aver M780 ATSC/QAM-B", + + /* Channel 0 is analog, which is currently unsupported */ + .io_type = { NGENE_IO_NONE, NGENE_IO_TSIN }, + .demod_attach = { NULL, demod_attach_lg330x }, + + /* Ensure these are NULL else the frame will call them (as funcs) */ + .tuner_attach = { 0, 0, 0, 0 }, + .fe_config = { NULL, &aver_m780 }, + .avf = { 0 }, + + /* A custom electrical interface config for the demod to bridge */ + .tsf = { 4, 4 }, + .fw_version = 15, +}; + +static struct drxd_config fe_terratec_dvbt_0 = { + .index = 0, + .demod_address = 0x70, + .demod_revision = 0xa2, + .demoda_address = 0x00, + .pll_address = 0x60, + .pll_type = DVB_PLL_THOMSON_DTT7520X, + .clock = 20000, + .osc_deviation = osc_deviation, +}; + +static struct drxd_config fe_terratec_dvbt_1 = { + .index = 1, + .demod_address = 0x71, + .demod_revision = 0xa2, + .demoda_address = 0x00, + .pll_address = 0x60, + .pll_type = DVB_PLL_THOMSON_DTT7520X, + .clock = 20000, + .osc_deviation = osc_deviation, +}; + +static struct ngene_info ngene_info_terratec = { + .type = NGENE_TERRATEC, + .name = "Terratec Integra/Cinergy2400i Dual DVB-T", + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, + .demod_attach = {demod_attach_drxd, demod_attach_drxd}, + .fe_config = {&fe_terratec_dvbt_0, &fe_terratec_dvbt_1}, + .i2c_access = 1, +}; + +/****************************************************************************/ + + + +/****************************************************************************/ +/* PCI Subsystem ID *********************************************************/ +/****************************************************************************/ + +#define NGENE_ID(_subvend, _subdev, _driverdata) { \ + .vendor = NGENE_VID, .device = NGENE_PID, \ + .subvendor = _subvend, .subdevice = _subdev, \ + .driver_data = (unsigned long) &_driverdata } + +/****************************************************************************/ + +static const struct pci_device_id ngene_id_tbl[] __devinitdata = { + NGENE_ID(0x18c3, 0xabc3, ngene_info_cineS2), + NGENE_ID(0x18c3, 0xabc4, ngene_info_cineS2), + NGENE_ID(0x18c3, 0xdb01, ngene_info_satixS2), + NGENE_ID(0x18c3, 0xdb02, ngene_info_satixS2v2), + NGENE_ID(0x18c3, 0xdd00, ngene_info_cineS2v5), + NGENE_ID(0x18c3, 0xdd10, ngene_info_duoFlex), + NGENE_ID(0x18c3, 0xdd20, ngene_info_duoFlex), + NGENE_ID(0x1461, 0x062e, ngene_info_m780), + NGENE_ID(0x153b, 0x1167, ngene_info_terratec), + {0} +}; +MODULE_DEVICE_TABLE(pci, ngene_id_tbl); + +/****************************************************************************/ +/* Init/Exit ****************************************************************/ +/****************************************************************************/ + +static pci_ers_result_t ngene_error_detected(struct pci_dev *dev, + enum pci_channel_state state) +{ + printk(KERN_ERR DEVICE_NAME ": PCI error\n"); + if (state == pci_channel_io_perm_failure) + return PCI_ERS_RESULT_DISCONNECT; + if (state == pci_channel_io_frozen) + return PCI_ERS_RESULT_NEED_RESET; + return PCI_ERS_RESULT_CAN_RECOVER; +} + +static pci_ers_result_t ngene_link_reset(struct pci_dev *dev) +{ + printk(KERN_INFO DEVICE_NAME ": link reset\n"); + return 0; +} + +static pci_ers_result_t ngene_slot_reset(struct pci_dev *dev) +{ + printk(KERN_INFO DEVICE_NAME ": slot reset\n"); + return 0; +} + +static void ngene_resume(struct pci_dev *dev) +{ + printk(KERN_INFO DEVICE_NAME ": resume\n"); +} + +static const struct pci_error_handlers ngene_errors = { + .error_detected = ngene_error_detected, + .link_reset = ngene_link_reset, + .slot_reset = ngene_slot_reset, + .resume = ngene_resume, +}; + +static struct pci_driver ngene_pci_driver = { + .name = "ngene", + .id_table = ngene_id_tbl, + .probe = ngene_probe, + .remove = __devexit_p(ngene_remove), + .err_handler = &ngene_errors, + .shutdown = ngene_shutdown, +}; + +static __init int module_init_ngene(void) +{ + printk(KERN_INFO + "nGene PCIE bridge driver, Copyright (C) 2005-2007 Micronas\n"); + return pci_register_driver(&ngene_pci_driver); +} + +static __exit void module_exit_ngene(void) +{ + pci_unregister_driver(&ngene_pci_driver); +} + +module_init(module_init_ngene); +module_exit(module_exit_ngene); + +MODULE_DESCRIPTION("nGene"); +MODULE_AUTHOR("Micronas, Ralph Metzler, Manfred Voelkel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/ngene/ngene-core.c b/drivers/media/pci/ngene/ngene-core.c new file mode 100644 index 000000000000..c8e0d5b99d4c --- /dev/null +++ b/drivers/media/pci/ngene/ngene-core.c @@ -0,0 +1,1707 @@ +/* + * ngene.c: nGene PCIe bridge driver + * + * Copyright (C) 2005-2007 Micronas + * + * Copyright (C) 2008-2009 Ralph Metzler + * Modifications for new nGene firmware, + * support for EEPROM-copying, + * support for new dual DVB-S2 card prototype + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * 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 + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ngene.h" + +static int one_adapter; +module_param(one_adapter, int, 0444); +MODULE_PARM_DESC(one_adapter, "Use only one adapter."); + +static int shutdown_workaround; +module_param(shutdown_workaround, int, 0644); +MODULE_PARM_DESC(shutdown_workaround, "Activate workaround for shutdown problem with some chipsets."); + +static int debug; +module_param(debug, int, 0444); +MODULE_PARM_DESC(debug, "Print debugging information."); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define dprintk if (debug) printk + +#define ngwriteb(dat, adr) writeb((dat), (char *)(dev->iomem + (adr))) +#define ngwritel(dat, adr) writel((dat), (char *)(dev->iomem + (adr))) +#define ngwriteb(dat, adr) writeb((dat), (char *)(dev->iomem + (adr))) +#define ngreadl(adr) readl(dev->iomem + (adr)) +#define ngreadb(adr) readb(dev->iomem + (adr)) +#define ngcpyto(adr, src, count) memcpy_toio((char *) \ + (dev->iomem + (adr)), (src), (count)) +#define ngcpyfrom(dst, adr, count) memcpy_fromio((dst), (char *) \ + (dev->iomem + (adr)), (count)) + +/****************************************************************************/ +/* nGene interrupt handler **************************************************/ +/****************************************************************************/ + +static void event_tasklet(unsigned long data) +{ + struct ngene *dev = (struct ngene *)data; + + while (dev->EventQueueReadIndex != dev->EventQueueWriteIndex) { + struct EVENT_BUFFER Event = + dev->EventQueue[dev->EventQueueReadIndex]; + dev->EventQueueReadIndex = + (dev->EventQueueReadIndex + 1) & (EVENT_QUEUE_SIZE - 1); + + if ((Event.UARTStatus & 0x01) && (dev->TxEventNotify)) + dev->TxEventNotify(dev, Event.TimeStamp); + if ((Event.UARTStatus & 0x02) && (dev->RxEventNotify)) + dev->RxEventNotify(dev, Event.TimeStamp, + Event.RXCharacter); + } +} + +static void demux_tasklet(unsigned long data) +{ + struct ngene_channel *chan = (struct ngene_channel *)data; + struct SBufferHeader *Cur = chan->nextBuffer; + + spin_lock_irq(&chan->state_lock); + + while (Cur->ngeneBuffer.SR.Flags & 0x80) { + if (chan->mode & NGENE_IO_TSOUT) { + u32 Flags = chan->DataFormatFlags; + if (Cur->ngeneBuffer.SR.Flags & 0x20) + Flags |= BEF_OVERFLOW; + if (chan->pBufferExchange) { + if (!chan->pBufferExchange(chan, + Cur->Buffer1, + chan->Capture1Length, + Cur->ngeneBuffer.SR. + Clock, Flags)) { + /* + We didn't get data + Clear in service flag to make sure we + get called on next interrupt again. + leave fill/empty (0x80) flag alone + to avoid hardware running out of + buffers during startup, we hold only + in run state ( the source may be late + delivering data ) + */ + + if (chan->HWState == HWSTATE_RUN) { + Cur->ngeneBuffer.SR.Flags &= + ~0x40; + break; + /* Stop processing stream */ + } + } else { + /* We got a valid buffer, + so switch to run state */ + chan->HWState = HWSTATE_RUN; + } + } else { + printk(KERN_ERR DEVICE_NAME ": OOPS\n"); + if (chan->HWState == HWSTATE_RUN) { + Cur->ngeneBuffer.SR.Flags &= ~0x40; + break; /* Stop processing stream */ + } + } + if (chan->AudioDTOUpdated) { + printk(KERN_INFO DEVICE_NAME + ": Update AudioDTO = %d\n", + chan->AudioDTOValue); + Cur->ngeneBuffer.SR.DTOUpdate = + chan->AudioDTOValue; + chan->AudioDTOUpdated = 0; + } + } else { + if (chan->HWState == HWSTATE_RUN) { + u32 Flags = chan->DataFormatFlags; + IBufferExchange *exch1 = chan->pBufferExchange; + IBufferExchange *exch2 = chan->pBufferExchange2; + if (Cur->ngeneBuffer.SR.Flags & 0x01) + Flags |= BEF_EVEN_FIELD; + if (Cur->ngeneBuffer.SR.Flags & 0x20) + Flags |= BEF_OVERFLOW; + spin_unlock_irq(&chan->state_lock); + if (exch1) + exch1(chan, Cur->Buffer1, + chan->Capture1Length, + Cur->ngeneBuffer.SR.Clock, + Flags); + if (exch2) + exch2(chan, Cur->Buffer2, + chan->Capture2Length, + Cur->ngeneBuffer.SR.Clock, + Flags); + spin_lock_irq(&chan->state_lock); + } else if (chan->HWState != HWSTATE_STOP) + chan->HWState = HWSTATE_RUN; + } + Cur->ngeneBuffer.SR.Flags = 0x00; + Cur = Cur->Next; + } + chan->nextBuffer = Cur; + + spin_unlock_irq(&chan->state_lock); +} + +static irqreturn_t irq_handler(int irq, void *dev_id) +{ + struct ngene *dev = (struct ngene *)dev_id; + u32 icounts = 0; + irqreturn_t rc = IRQ_NONE; + u32 i = MAX_STREAM; + u8 *tmpCmdDoneByte; + + if (dev->BootFirmware) { + icounts = ngreadl(NGENE_INT_COUNTS); + if (icounts != dev->icounts) { + ngwritel(0, FORCE_NMI); + dev->cmd_done = 1; + wake_up(&dev->cmd_wq); + dev->icounts = icounts; + rc = IRQ_HANDLED; + } + return rc; + } + + ngwritel(0, FORCE_NMI); + + spin_lock(&dev->cmd_lock); + tmpCmdDoneByte = dev->CmdDoneByte; + if (tmpCmdDoneByte && + (*tmpCmdDoneByte || + (dev->ngenetohost[0] == 1 && dev->ngenetohost[1] != 0))) { + dev->CmdDoneByte = NULL; + dev->cmd_done = 1; + wake_up(&dev->cmd_wq); + rc = IRQ_HANDLED; + } + spin_unlock(&dev->cmd_lock); + + if (dev->EventBuffer->EventStatus & 0x80) { + u8 nextWriteIndex = + (dev->EventQueueWriteIndex + 1) & + (EVENT_QUEUE_SIZE - 1); + if (nextWriteIndex != dev->EventQueueReadIndex) { + dev->EventQueue[dev->EventQueueWriteIndex] = + *(dev->EventBuffer); + dev->EventQueueWriteIndex = nextWriteIndex; + } else { + printk(KERN_ERR DEVICE_NAME ": event overflow\n"); + dev->EventQueueOverflowCount += 1; + dev->EventQueueOverflowFlag = 1; + } + dev->EventBuffer->EventStatus &= ~0x80; + tasklet_schedule(&dev->event_tasklet); + rc = IRQ_HANDLED; + } + + while (i > 0) { + i--; + spin_lock(&dev->channel[i].state_lock); + /* if (dev->channel[i].State>=KSSTATE_RUN) { */ + if (dev->channel[i].nextBuffer) { + if ((dev->channel[i].nextBuffer-> + ngeneBuffer.SR.Flags & 0xC0) == 0x80) { + dev->channel[i].nextBuffer-> + ngeneBuffer.SR.Flags |= 0x40; + tasklet_schedule( + &dev->channel[i].demux_tasklet); + rc = IRQ_HANDLED; + } + } + spin_unlock(&dev->channel[i].state_lock); + } + + /* Request might have been processed by a previous call. */ + return IRQ_HANDLED; +} + +/****************************************************************************/ +/* nGene command interface **************************************************/ +/****************************************************************************/ + +static void dump_command_io(struct ngene *dev) +{ + u8 buf[8], *b; + + ngcpyfrom(buf, HOST_TO_NGENE, 8); + printk(KERN_ERR "host_to_ngene (%04x): %*ph\n", HOST_TO_NGENE, 8, buf); + + ngcpyfrom(buf, NGENE_TO_HOST, 8); + printk(KERN_ERR "ngene_to_host (%04x): %*ph\n", NGENE_TO_HOST, 8, buf); + + b = dev->hosttongene; + printk(KERN_ERR "dev->hosttongene (%p): %*ph\n", b, 8, b); + + b = dev->ngenetohost; + printk(KERN_ERR "dev->ngenetohost (%p): %*ph\n", b, 8, b); +} + +static int ngene_command_mutex(struct ngene *dev, struct ngene_command *com) +{ + int ret; + u8 *tmpCmdDoneByte; + + dev->cmd_done = 0; + + if (com->cmd.hdr.Opcode == CMD_FWLOAD_PREPARE) { + dev->BootFirmware = 1; + dev->icounts = ngreadl(NGENE_INT_COUNTS); + ngwritel(0, NGENE_COMMAND); + ngwritel(0, NGENE_COMMAND_HI); + ngwritel(0, NGENE_STATUS); + ngwritel(0, NGENE_STATUS_HI); + ngwritel(0, NGENE_EVENT); + ngwritel(0, NGENE_EVENT_HI); + } else if (com->cmd.hdr.Opcode == CMD_FWLOAD_FINISH) { + u64 fwio = dev->PAFWInterfaceBuffer; + + ngwritel(fwio & 0xffffffff, NGENE_COMMAND); + ngwritel(fwio >> 32, NGENE_COMMAND_HI); + ngwritel((fwio + 256) & 0xffffffff, NGENE_STATUS); + ngwritel((fwio + 256) >> 32, NGENE_STATUS_HI); + ngwritel((fwio + 512) & 0xffffffff, NGENE_EVENT); + ngwritel((fwio + 512) >> 32, NGENE_EVENT_HI); + } + + memcpy(dev->FWInterfaceBuffer, com->cmd.raw8, com->in_len + 2); + + if (dev->BootFirmware) + ngcpyto(HOST_TO_NGENE, com->cmd.raw8, com->in_len + 2); + + spin_lock_irq(&dev->cmd_lock); + tmpCmdDoneByte = dev->ngenetohost + com->out_len; + if (!com->out_len) + tmpCmdDoneByte++; + *tmpCmdDoneByte = 0; + dev->ngenetohost[0] = 0; + dev->ngenetohost[1] = 0; + dev->CmdDoneByte = tmpCmdDoneByte; + spin_unlock_irq(&dev->cmd_lock); + + /* Notify 8051. */ + ngwritel(1, FORCE_INT); + + ret = wait_event_timeout(dev->cmd_wq, dev->cmd_done == 1, 2 * HZ); + if (!ret) { + /*ngwritel(0, FORCE_NMI);*/ + + printk(KERN_ERR DEVICE_NAME + ": Command timeout cmd=%02x prev=%02x\n", + com->cmd.hdr.Opcode, dev->prev_cmd); + dump_command_io(dev); + return -1; + } + if (com->cmd.hdr.Opcode == CMD_FWLOAD_FINISH) + dev->BootFirmware = 0; + + dev->prev_cmd = com->cmd.hdr.Opcode; + + if (!com->out_len) + return 0; + + memcpy(com->cmd.raw8, dev->ngenetohost, com->out_len); + + return 0; +} + +int ngene_command(struct ngene *dev, struct ngene_command *com) +{ + int result; + + down(&dev->cmd_mutex); + result = ngene_command_mutex(dev, com); + up(&dev->cmd_mutex); + return result; +} + + +static int ngene_command_load_firmware(struct ngene *dev, + u8 *ngene_fw, u32 size) +{ +#define FIRSTCHUNK (1024) + u32 cleft; + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_FWLOAD_PREPARE; + com.cmd.hdr.Length = 0; + com.in_len = 0; + com.out_len = 0; + + ngene_command(dev, &com); + + cleft = (size + 3) & ~3; + if (cleft > FIRSTCHUNK) { + ngcpyto(PROGRAM_SRAM + FIRSTCHUNK, ngene_fw + FIRSTCHUNK, + cleft - FIRSTCHUNK); + cleft = FIRSTCHUNK; + } + ngcpyto(DATA_FIFO_AREA, ngene_fw, cleft); + + memset(&com, 0, sizeof(struct ngene_command)); + com.cmd.hdr.Opcode = CMD_FWLOAD_FINISH; + com.cmd.hdr.Length = 4; + com.cmd.FWLoadFinish.Address = DATA_FIFO_AREA; + com.cmd.FWLoadFinish.Length = (unsigned short)cleft; + com.in_len = 4; + com.out_len = 0; + + return ngene_command(dev, &com); +} + + +static int ngene_command_config_buf(struct ngene *dev, u8 config) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_CONFIGURE_BUFFER; + com.cmd.hdr.Length = 1; + com.cmd.ConfigureBuffers.config = config; + com.in_len = 1; + com.out_len = 0; + + if (ngene_command(dev, &com) < 0) + return -EIO; + return 0; +} + +static int ngene_command_config_free_buf(struct ngene *dev, u8 *config) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_CONFIGURE_FREE_BUFFER; + com.cmd.hdr.Length = 6; + memcpy(&com.cmd.ConfigureBuffers.config, config, 6); + com.in_len = 6; + com.out_len = 0; + + if (ngene_command(dev, &com) < 0) + return -EIO; + + return 0; +} + +int ngene_command_gpio_set(struct ngene *dev, u8 select, u8 level) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_SET_GPIO_PIN; + com.cmd.hdr.Length = 1; + com.cmd.SetGpioPin.select = select | (level << 7); + com.in_len = 1; + com.out_len = 0; + + return ngene_command(dev, &com); +} + + +/* + 02000640 is sample on rising edge. + 02000740 is sample on falling edge. + 02000040 is ignore "valid" signal + + 0: FD_CTL1 Bit 7,6 must be 0,1 + 7 disable(fw controlled) + 6 0-AUX,1-TS + 5 0-par,1-ser + 4 0-lsb/1-msb + 3,2 reserved + 1,0 0-no sync, 1-use ext. start, 2-use 0x47, 3-both + 1: FD_CTL2 has 3-valid must be hi, 2-use valid, 1-edge + 2: FD_STA is read-only. 0-sync + 3: FD_INSYNC is number of 47s to trigger "in sync". + 4: FD_OUTSYNC is number of 47s to trigger "out of sync". + 5: FD_MAXBYTE1 is low-order of bytes per packet. + 6: FD_MAXBYTE2 is high-order of bytes per packet. + 7: Top byte is unused. +*/ + +/****************************************************************************/ + +static u8 TSFeatureDecoderSetup[8 * 5] = { + 0x42, 0x00, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, + 0x40, 0x06, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* DRXH */ + 0x71, 0x07, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* DRXHser */ + 0x72, 0x00, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* S2ser */ + 0x40, 0x07, 0x00, 0x02, 0x02, 0xbc, 0x00, 0x00, /* LGDT3303 */ +}; + +/* Set NGENE I2S Config to 16 bit packed */ +static u8 I2SConfiguration[] = { + 0x00, 0x10, 0x00, 0x00, + 0x80, 0x10, 0x00, 0x00, +}; + +static u8 SPDIFConfiguration[10] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* Set NGENE I2S Config to transport stream compatible mode */ + +static u8 TS_I2SConfiguration[4] = { 0x3E, 0x18, 0x00, 0x00 }; + +static u8 TS_I2SOutConfiguration[4] = { 0x80, 0x04, 0x00, 0x00 }; + +static u8 ITUDecoderSetup[4][16] = { + {0x1c, 0x13, 0x01, 0x68, 0x3d, 0x90, 0x14, 0x20, /* SDTV */ + 0x00, 0x00, 0x01, 0xb0, 0x9c, 0x00, 0x00, 0x00}, + {0x9c, 0x03, 0x23, 0xC0, 0x60, 0x0E, 0x13, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00}, + {0x9f, 0x00, 0x23, 0xC0, 0x60, 0x0F, 0x13, 0x00, /* HDTV 1080i50 */ + 0x00, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00}, + {0x9c, 0x01, 0x23, 0xC0, 0x60, 0x0E, 0x13, 0x00, /* HDTV 1080i60 */ + 0x00, 0x00, 0x00, 0x01, 0xB0, 0x00, 0x00, 0x00}, +}; + +/* + * 50 48 60 gleich + * 27p50 9f 00 22 80 42 69 18 ... + * 27p60 93 00 22 80 82 69 1c ... + */ + +/* Maxbyte to 1144 (for raw data) */ +static u8 ITUFeatureDecoderSetup[8] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x04, 0x00 +}; + +void FillTSBuffer(void *Buffer, int Length, u32 Flags) +{ + u32 *ptr = Buffer; + + memset(Buffer, TS_FILLER, Length); + while (Length > 0) { + if (Flags & DF_SWAP32) + *ptr = 0x471FFF10; + else + *ptr = 0x10FF1F47; + ptr += (188 / 4); + Length -= 188; + } +} + + +static void flush_buffers(struct ngene_channel *chan) +{ + u8 val; + + do { + msleep(1); + spin_lock_irq(&chan->state_lock); + val = chan->nextBuffer->ngeneBuffer.SR.Flags & 0x80; + spin_unlock_irq(&chan->state_lock); + } while (val); +} + +static void clear_buffers(struct ngene_channel *chan) +{ + struct SBufferHeader *Cur = chan->nextBuffer; + + do { + memset(&Cur->ngeneBuffer.SR, 0, sizeof(Cur->ngeneBuffer.SR)); + if (chan->mode & NGENE_IO_TSOUT) + FillTSBuffer(Cur->Buffer1, + chan->Capture1Length, + chan->DataFormatFlags); + Cur = Cur->Next; + } while (Cur != chan->nextBuffer); + + if (chan->mode & NGENE_IO_TSOUT) { + chan->nextBuffer->ngeneBuffer.SR.DTOUpdate = + chan->AudioDTOValue; + chan->AudioDTOUpdated = 0; + + Cur = chan->TSIdleBuffer.Head; + + do { + memset(&Cur->ngeneBuffer.SR, 0, + sizeof(Cur->ngeneBuffer.SR)); + FillTSBuffer(Cur->Buffer1, + chan->Capture1Length, + chan->DataFormatFlags); + Cur = Cur->Next; + } while (Cur != chan->TSIdleBuffer.Head); + } +} + +static int ngene_command_stream_control(struct ngene *dev, u8 stream, + u8 control, u8 mode, u8 flags) +{ + struct ngene_channel *chan = &dev->channel[stream]; + struct ngene_command com; + u16 BsUVI = ((stream & 1) ? 0x9400 : 0x9300); + u16 BsSDI = ((stream & 1) ? 0x9600 : 0x9500); + u16 BsSPI = ((stream & 1) ? 0x9800 : 0x9700); + u16 BsSDO = 0x9B00; + + down(&dev->stream_mutex); + memset(&com, 0, sizeof(com)); + com.cmd.hdr.Opcode = CMD_CONTROL; + com.cmd.hdr.Length = sizeof(struct FW_STREAM_CONTROL) - 2; + com.cmd.StreamControl.Stream = stream | (control ? 8 : 0); + if (chan->mode & NGENE_IO_TSOUT) + com.cmd.StreamControl.Stream |= 0x07; + com.cmd.StreamControl.Control = control | + (flags & SFLAG_ORDER_LUMA_CHROMA); + com.cmd.StreamControl.Mode = mode; + com.in_len = sizeof(struct FW_STREAM_CONTROL); + com.out_len = 0; + + dprintk(KERN_INFO DEVICE_NAME + ": Stream=%02x, Control=%02x, Mode=%02x\n", + com.cmd.StreamControl.Stream, com.cmd.StreamControl.Control, + com.cmd.StreamControl.Mode); + + chan->Mode = mode; + + if (!(control & 0x80)) { + spin_lock_irq(&chan->state_lock); + if (chan->State == KSSTATE_RUN) { + chan->State = KSSTATE_ACQUIRE; + chan->HWState = HWSTATE_STOP; + spin_unlock_irq(&chan->state_lock); + if (ngene_command(dev, &com) < 0) { + up(&dev->stream_mutex); + return -1; + } + /* clear_buffers(chan); */ + flush_buffers(chan); + up(&dev->stream_mutex); + return 0; + } + spin_unlock_irq(&chan->state_lock); + up(&dev->stream_mutex); + return 0; + } + + if (mode & SMODE_AUDIO_CAPTURE) { + com.cmd.StreamControl.CaptureBlockCount = + chan->Capture1Length / AUDIO_BLOCK_SIZE; + com.cmd.StreamControl.Buffer_Address = chan->RingBuffer.PAHead; + } else if (mode & SMODE_TRANSPORT_STREAM) { + com.cmd.StreamControl.CaptureBlockCount = + chan->Capture1Length / TS_BLOCK_SIZE; + com.cmd.StreamControl.MaxLinesPerField = + chan->Capture1Length / TS_BLOCK_SIZE; + com.cmd.StreamControl.Buffer_Address = + chan->TSRingBuffer.PAHead; + if (chan->mode & NGENE_IO_TSOUT) { + com.cmd.StreamControl.BytesPerVBILine = + chan->Capture1Length / TS_BLOCK_SIZE; + com.cmd.StreamControl.Stream |= 0x07; + } + } else { + com.cmd.StreamControl.BytesPerVideoLine = chan->nBytesPerLine; + com.cmd.StreamControl.MaxLinesPerField = chan->nLines; + com.cmd.StreamControl.MinLinesPerField = 100; + com.cmd.StreamControl.Buffer_Address = chan->RingBuffer.PAHead; + + if (mode & SMODE_VBI_CAPTURE) { + com.cmd.StreamControl.MaxVBILinesPerField = + chan->nVBILines; + com.cmd.StreamControl.MinVBILinesPerField = 0; + com.cmd.StreamControl.BytesPerVBILine = + chan->nBytesPerVBILine; + } + if (flags & SFLAG_COLORBAR) + com.cmd.StreamControl.Stream |= 0x04; + } + + spin_lock_irq(&chan->state_lock); + if (mode & SMODE_AUDIO_CAPTURE) { + chan->nextBuffer = chan->RingBuffer.Head; + if (mode & SMODE_AUDIO_SPDIF) { + com.cmd.StreamControl.SetupDataLen = + sizeof(SPDIFConfiguration); + com.cmd.StreamControl.SetupDataAddr = BsSPI; + memcpy(com.cmd.StreamControl.SetupData, + SPDIFConfiguration, sizeof(SPDIFConfiguration)); + } else { + com.cmd.StreamControl.SetupDataLen = 4; + com.cmd.StreamControl.SetupDataAddr = BsSDI; + memcpy(com.cmd.StreamControl.SetupData, + I2SConfiguration + + 4 * dev->card_info->i2s[stream], 4); + } + } else if (mode & SMODE_TRANSPORT_STREAM) { + chan->nextBuffer = chan->TSRingBuffer.Head; + if (stream >= STREAM_AUDIOIN1) { + if (chan->mode & NGENE_IO_TSOUT) { + com.cmd.StreamControl.SetupDataLen = + sizeof(TS_I2SOutConfiguration); + com.cmd.StreamControl.SetupDataAddr = BsSDO; + memcpy(com.cmd.StreamControl.SetupData, + TS_I2SOutConfiguration, + sizeof(TS_I2SOutConfiguration)); + } else { + com.cmd.StreamControl.SetupDataLen = + sizeof(TS_I2SConfiguration); + com.cmd.StreamControl.SetupDataAddr = BsSDI; + memcpy(com.cmd.StreamControl.SetupData, + TS_I2SConfiguration, + sizeof(TS_I2SConfiguration)); + } + } else { + com.cmd.StreamControl.SetupDataLen = 8; + com.cmd.StreamControl.SetupDataAddr = BsUVI + 0x10; + memcpy(com.cmd.StreamControl.SetupData, + TSFeatureDecoderSetup + + 8 * dev->card_info->tsf[stream], 8); + } + } else { + chan->nextBuffer = chan->RingBuffer.Head; + com.cmd.StreamControl.SetupDataLen = + 16 + sizeof(ITUFeatureDecoderSetup); + com.cmd.StreamControl.SetupDataAddr = BsUVI; + memcpy(com.cmd.StreamControl.SetupData, + ITUDecoderSetup[chan->itumode], 16); + memcpy(com.cmd.StreamControl.SetupData + 16, + ITUFeatureDecoderSetup, sizeof(ITUFeatureDecoderSetup)); + } + clear_buffers(chan); + chan->State = KSSTATE_RUN; + if (mode & SMODE_TRANSPORT_STREAM) + chan->HWState = HWSTATE_RUN; + else + chan->HWState = HWSTATE_STARTUP; + spin_unlock_irq(&chan->state_lock); + + if (ngene_command(dev, &com) < 0) { + up(&dev->stream_mutex); + return -1; + } + up(&dev->stream_mutex); + return 0; +} + +void set_transfer(struct ngene_channel *chan, int state) +{ + u8 control = 0, mode = 0, flags = 0; + struct ngene *dev = chan->dev; + int ret; + + /* + printk(KERN_INFO DEVICE_NAME ": st %d\n", state); + msleep(100); + */ + + if (state) { + if (chan->running) { + printk(KERN_INFO DEVICE_NAME ": already running\n"); + return; + } + } else { + if (!chan->running) { + printk(KERN_INFO DEVICE_NAME ": already stopped\n"); + return; + } + } + + if (dev->card_info->switch_ctrl) + dev->card_info->switch_ctrl(chan, 1, state ^ 1); + + if (state) { + spin_lock_irq(&chan->state_lock); + + /* printk(KERN_INFO DEVICE_NAME ": lock=%08x\n", + ngreadl(0x9310)); */ + dvb_ringbuffer_flush(&dev->tsout_rbuf); + control = 0x80; + if (chan->mode & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { + chan->Capture1Length = 512 * 188; + mode = SMODE_TRANSPORT_STREAM; + } + if (chan->mode & NGENE_IO_TSOUT) { + chan->pBufferExchange = tsout_exchange; + /* 0x66666666 = 50MHz *2^33 /250MHz */ + chan->AudioDTOValue = 0x80000000; + chan->AudioDTOUpdated = 1; + } + if (chan->mode & NGENE_IO_TSIN) + chan->pBufferExchange = tsin_exchange; + spin_unlock_irq(&chan->state_lock); + } else + ;/* printk(KERN_INFO DEVICE_NAME ": lock=%08x\n", + ngreadl(0x9310)); */ + + ret = ngene_command_stream_control(dev, chan->number, + control, mode, flags); + if (!ret) + chan->running = state; + else + printk(KERN_ERR DEVICE_NAME ": set_transfer %d failed\n", + state); + if (!state) { + spin_lock_irq(&chan->state_lock); + chan->pBufferExchange = NULL; + dvb_ringbuffer_flush(&dev->tsout_rbuf); + spin_unlock_irq(&chan->state_lock); + } +} + + +/****************************************************************************/ +/* nGene hardware init and release functions ********************************/ +/****************************************************************************/ + +static void free_ringbuffer(struct ngene *dev, struct SRingBufferDescriptor *rb) +{ + struct SBufferHeader *Cur = rb->Head; + u32 j; + + if (!Cur) + return; + + for (j = 0; j < rb->NumBuffers; j++, Cur = Cur->Next) { + if (Cur->Buffer1) + pci_free_consistent(dev->pci_dev, + rb->Buffer1Length, + Cur->Buffer1, + Cur->scList1->Address); + + if (Cur->Buffer2) + pci_free_consistent(dev->pci_dev, + rb->Buffer2Length, + Cur->Buffer2, + Cur->scList2->Address); + } + + if (rb->SCListMem) + pci_free_consistent(dev->pci_dev, rb->SCListMemSize, + rb->SCListMem, rb->PASCListMem); + + pci_free_consistent(dev->pci_dev, rb->MemSize, rb->Head, rb->PAHead); +} + +static void free_idlebuffer(struct ngene *dev, + struct SRingBufferDescriptor *rb, + struct SRingBufferDescriptor *tb) +{ + int j; + struct SBufferHeader *Cur = tb->Head; + + if (!rb->Head) + return; + free_ringbuffer(dev, rb); + for (j = 0; j < tb->NumBuffers; j++, Cur = Cur->Next) { + Cur->Buffer2 = NULL; + Cur->scList2 = NULL; + Cur->ngeneBuffer.Address_of_first_entry_2 = 0; + Cur->ngeneBuffer.Number_of_entries_2 = 0; + } +} + +static void free_common_buffers(struct ngene *dev) +{ + u32 i; + struct ngene_channel *chan; + + for (i = STREAM_VIDEOIN1; i < MAX_STREAM; i++) { + chan = &dev->channel[i]; + free_idlebuffer(dev, &chan->TSIdleBuffer, &chan->TSRingBuffer); + free_ringbuffer(dev, &chan->RingBuffer); + free_ringbuffer(dev, &chan->TSRingBuffer); + } + + if (dev->OverflowBuffer) + pci_free_consistent(dev->pci_dev, + OVERFLOW_BUFFER_SIZE, + dev->OverflowBuffer, dev->PAOverflowBuffer); + + if (dev->FWInterfaceBuffer) + pci_free_consistent(dev->pci_dev, + 4096, + dev->FWInterfaceBuffer, + dev->PAFWInterfaceBuffer); +} + +/****************************************************************************/ +/* Ring buffer handling *****************************************************/ +/****************************************************************************/ + +static int create_ring_buffer(struct pci_dev *pci_dev, + struct SRingBufferDescriptor *descr, u32 NumBuffers) +{ + dma_addr_t tmp; + struct SBufferHeader *Head; + u32 i; + u32 MemSize = SIZEOF_SBufferHeader * NumBuffers; + u64 PARingBufferHead; + u64 PARingBufferCur; + u64 PARingBufferNext; + struct SBufferHeader *Cur, *Next; + + descr->Head = NULL; + descr->MemSize = 0; + descr->PAHead = 0; + descr->NumBuffers = 0; + + if (MemSize < 4096) + MemSize = 4096; + + Head = pci_alloc_consistent(pci_dev, MemSize, &tmp); + PARingBufferHead = tmp; + + if (!Head) + return -ENOMEM; + + memset(Head, 0, MemSize); + + PARingBufferCur = PARingBufferHead; + Cur = Head; + + for (i = 0; i < NumBuffers - 1; i++) { + Next = (struct SBufferHeader *) + (((u8 *) Cur) + SIZEOF_SBufferHeader); + PARingBufferNext = PARingBufferCur + SIZEOF_SBufferHeader; + Cur->Next = Next; + Cur->ngeneBuffer.Next = PARingBufferNext; + Cur = Next; + PARingBufferCur = PARingBufferNext; + } + /* Last Buffer points back to first one */ + Cur->Next = Head; + Cur->ngeneBuffer.Next = PARingBufferHead; + + descr->Head = Head; + descr->MemSize = MemSize; + descr->PAHead = PARingBufferHead; + descr->NumBuffers = NumBuffers; + + return 0; +} + +static int AllocateRingBuffers(struct pci_dev *pci_dev, + dma_addr_t of, + struct SRingBufferDescriptor *pRingBuffer, + u32 Buffer1Length, u32 Buffer2Length) +{ + dma_addr_t tmp; + u32 i, j; + int status = 0; + u32 SCListMemSize = pRingBuffer->NumBuffers + * ((Buffer2Length != 0) ? (NUM_SCATTER_GATHER_ENTRIES * 2) : + NUM_SCATTER_GATHER_ENTRIES) + * sizeof(struct HW_SCATTER_GATHER_ELEMENT); + + u64 PASCListMem; + struct HW_SCATTER_GATHER_ELEMENT *SCListEntry; + u64 PASCListEntry; + struct SBufferHeader *Cur; + void *SCListMem; + + if (SCListMemSize < 4096) + SCListMemSize = 4096; + + SCListMem = pci_alloc_consistent(pci_dev, SCListMemSize, &tmp); + + PASCListMem = tmp; + if (SCListMem == NULL) + return -ENOMEM; + + memset(SCListMem, 0, SCListMemSize); + + pRingBuffer->SCListMem = SCListMem; + pRingBuffer->PASCListMem = PASCListMem; + pRingBuffer->SCListMemSize = SCListMemSize; + pRingBuffer->Buffer1Length = Buffer1Length; + pRingBuffer->Buffer2Length = Buffer2Length; + + SCListEntry = SCListMem; + PASCListEntry = PASCListMem; + Cur = pRingBuffer->Head; + + for (i = 0; i < pRingBuffer->NumBuffers; i += 1, Cur = Cur->Next) { + u64 PABuffer; + + void *Buffer = pci_alloc_consistent(pci_dev, Buffer1Length, + &tmp); + PABuffer = tmp; + + if (Buffer == NULL) + return -ENOMEM; + + Cur->Buffer1 = Buffer; + + SCListEntry->Address = PABuffer; + SCListEntry->Length = Buffer1Length; + + Cur->scList1 = SCListEntry; + Cur->ngeneBuffer.Address_of_first_entry_1 = PASCListEntry; + Cur->ngeneBuffer.Number_of_entries_1 = + NUM_SCATTER_GATHER_ENTRIES; + + SCListEntry += 1; + PASCListEntry += sizeof(struct HW_SCATTER_GATHER_ELEMENT); + +#if NUM_SCATTER_GATHER_ENTRIES > 1 + for (j = 0; j < NUM_SCATTER_GATHER_ENTRIES - 1; j += 1) { + SCListEntry->Address = of; + SCListEntry->Length = OVERFLOW_BUFFER_SIZE; + SCListEntry += 1; + PASCListEntry += + sizeof(struct HW_SCATTER_GATHER_ELEMENT); + } +#endif + + if (!Buffer2Length) + continue; + + Buffer = pci_alloc_consistent(pci_dev, Buffer2Length, &tmp); + PABuffer = tmp; + + if (Buffer == NULL) + return -ENOMEM; + + Cur->Buffer2 = Buffer; + + SCListEntry->Address = PABuffer; + SCListEntry->Length = Buffer2Length; + + Cur->scList2 = SCListEntry; + Cur->ngeneBuffer.Address_of_first_entry_2 = PASCListEntry; + Cur->ngeneBuffer.Number_of_entries_2 = + NUM_SCATTER_GATHER_ENTRIES; + + SCListEntry += 1; + PASCListEntry += sizeof(struct HW_SCATTER_GATHER_ELEMENT); + +#if NUM_SCATTER_GATHER_ENTRIES > 1 + for (j = 0; j < NUM_SCATTER_GATHER_ENTRIES - 1; j++) { + SCListEntry->Address = of; + SCListEntry->Length = OVERFLOW_BUFFER_SIZE; + SCListEntry += 1; + PASCListEntry += + sizeof(struct HW_SCATTER_GATHER_ELEMENT); + } +#endif + + } + + return status; +} + +static int FillTSIdleBuffer(struct SRingBufferDescriptor *pIdleBuffer, + struct SRingBufferDescriptor *pRingBuffer) +{ + int status = 0; + + /* Copy pointer to scatter gather list in TSRingbuffer + structure for buffer 2 + Load number of buffer + */ + u32 n = pRingBuffer->NumBuffers; + + /* Point to first buffer entry */ + struct SBufferHeader *Cur = pRingBuffer->Head; + int i; + /* Loop thru all buffer and set Buffer 2 pointers to TSIdlebuffer */ + for (i = 0; i < n; i++) { + Cur->Buffer2 = pIdleBuffer->Head->Buffer1; + Cur->scList2 = pIdleBuffer->Head->scList1; + Cur->ngeneBuffer.Address_of_first_entry_2 = + pIdleBuffer->Head->ngeneBuffer. + Address_of_first_entry_1; + Cur->ngeneBuffer.Number_of_entries_2 = + pIdleBuffer->Head->ngeneBuffer.Number_of_entries_1; + Cur = Cur->Next; + } + return status; +} + +static u32 RingBufferSizes[MAX_STREAM] = { + RING_SIZE_VIDEO, + RING_SIZE_VIDEO, + RING_SIZE_AUDIO, + RING_SIZE_AUDIO, + RING_SIZE_AUDIO, +}; + +static u32 Buffer1Sizes[MAX_STREAM] = { + MAX_VIDEO_BUFFER_SIZE, + MAX_VIDEO_BUFFER_SIZE, + MAX_AUDIO_BUFFER_SIZE, + MAX_AUDIO_BUFFER_SIZE, + MAX_AUDIO_BUFFER_SIZE +}; + +static u32 Buffer2Sizes[MAX_STREAM] = { + MAX_VBI_BUFFER_SIZE, + MAX_VBI_BUFFER_SIZE, + 0, + 0, + 0 +}; + + +static int AllocCommonBuffers(struct ngene *dev) +{ + int status = 0, i; + + dev->FWInterfaceBuffer = pci_alloc_consistent(dev->pci_dev, 4096, + &dev->PAFWInterfaceBuffer); + if (!dev->FWInterfaceBuffer) + return -ENOMEM; + dev->hosttongene = dev->FWInterfaceBuffer; + dev->ngenetohost = dev->FWInterfaceBuffer + 256; + dev->EventBuffer = dev->FWInterfaceBuffer + 512; + + dev->OverflowBuffer = pci_alloc_consistent(dev->pci_dev, + OVERFLOW_BUFFER_SIZE, + &dev->PAOverflowBuffer); + if (!dev->OverflowBuffer) + return -ENOMEM; + memset(dev->OverflowBuffer, 0, OVERFLOW_BUFFER_SIZE); + + for (i = STREAM_VIDEOIN1; i < MAX_STREAM; i++) { + int type = dev->card_info->io_type[i]; + + dev->channel[i].State = KSSTATE_STOP; + + if (type & (NGENE_IO_TV | NGENE_IO_HDTV | NGENE_IO_AIN)) { + status = create_ring_buffer(dev->pci_dev, + &dev->channel[i].RingBuffer, + RingBufferSizes[i]); + if (status < 0) + break; + + if (type & (NGENE_IO_TV | NGENE_IO_AIN)) { + status = AllocateRingBuffers(dev->pci_dev, + dev-> + PAOverflowBuffer, + &dev->channel[i]. + RingBuffer, + Buffer1Sizes[i], + Buffer2Sizes[i]); + if (status < 0) + break; + } else if (type & NGENE_IO_HDTV) { + status = AllocateRingBuffers(dev->pci_dev, + dev-> + PAOverflowBuffer, + &dev->channel[i]. + RingBuffer, + MAX_HDTV_BUFFER_SIZE, + 0); + if (status < 0) + break; + } + } + + if (type & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { + + status = create_ring_buffer(dev->pci_dev, + &dev->channel[i]. + TSRingBuffer, RING_SIZE_TS); + if (status < 0) + break; + + status = AllocateRingBuffers(dev->pci_dev, + dev->PAOverflowBuffer, + &dev->channel[i]. + TSRingBuffer, + MAX_TS_BUFFER_SIZE, 0); + if (status) + break; + } + + if (type & NGENE_IO_TSOUT) { + status = create_ring_buffer(dev->pci_dev, + &dev->channel[i]. + TSIdleBuffer, 1); + if (status < 0) + break; + status = AllocateRingBuffers(dev->pci_dev, + dev->PAOverflowBuffer, + &dev->channel[i]. + TSIdleBuffer, + MAX_TS_BUFFER_SIZE, 0); + if (status) + break; + FillTSIdleBuffer(&dev->channel[i].TSIdleBuffer, + &dev->channel[i].TSRingBuffer); + } + } + return status; +} + +static void ngene_release_buffers(struct ngene *dev) +{ + if (dev->iomem) + iounmap(dev->iomem); + free_common_buffers(dev); + vfree(dev->tsout_buf); + vfree(dev->tsin_buf); + vfree(dev->ain_buf); + vfree(dev->vin_buf); + vfree(dev); +} + +static int ngene_get_buffers(struct ngene *dev) +{ + if (AllocCommonBuffers(dev)) + return -ENOMEM; + if (dev->card_info->io_type[4] & NGENE_IO_TSOUT) { + dev->tsout_buf = vmalloc(TSOUT_BUF_SIZE); + if (!dev->tsout_buf) + return -ENOMEM; + dvb_ringbuffer_init(&dev->tsout_rbuf, + dev->tsout_buf, TSOUT_BUF_SIZE); + } + if (dev->card_info->io_type[2]&NGENE_IO_TSIN) { + dev->tsin_buf = vmalloc(TSIN_BUF_SIZE); + if (!dev->tsin_buf) + return -ENOMEM; + dvb_ringbuffer_init(&dev->tsin_rbuf, + dev->tsin_buf, TSIN_BUF_SIZE); + } + if (dev->card_info->io_type[2] & NGENE_IO_AIN) { + dev->ain_buf = vmalloc(AIN_BUF_SIZE); + if (!dev->ain_buf) + return -ENOMEM; + dvb_ringbuffer_init(&dev->ain_rbuf, dev->ain_buf, AIN_BUF_SIZE); + } + if (dev->card_info->io_type[0] & NGENE_IO_HDTV) { + dev->vin_buf = vmalloc(VIN_BUF_SIZE); + if (!dev->vin_buf) + return -ENOMEM; + dvb_ringbuffer_init(&dev->vin_rbuf, dev->vin_buf, VIN_BUF_SIZE); + } + dev->iomem = ioremap(pci_resource_start(dev->pci_dev, 0), + pci_resource_len(dev->pci_dev, 0)); + if (!dev->iomem) + return -ENOMEM; + + return 0; +} + +static void ngene_init(struct ngene *dev) +{ + int i; + + tasklet_init(&dev->event_tasklet, event_tasklet, (unsigned long)dev); + + memset_io(dev->iomem + 0xc000, 0x00, 0x220); + memset_io(dev->iomem + 0xc400, 0x00, 0x100); + + for (i = 0; i < MAX_STREAM; i++) { + dev->channel[i].dev = dev; + dev->channel[i].number = i; + } + + dev->fw_interface_version = 0; + + ngwritel(0, NGENE_INT_ENABLE); + + dev->icounts = ngreadl(NGENE_INT_COUNTS); + + dev->device_version = ngreadl(DEV_VER) & 0x0f; + printk(KERN_INFO DEVICE_NAME ": Device version %d\n", + dev->device_version); +} + +static int ngene_load_firm(struct ngene *dev) +{ + u32 size; + const struct firmware *fw = NULL; + u8 *ngene_fw; + char *fw_name; + int err, version; + + version = dev->card_info->fw_version; + + switch (version) { + default: + case 15: + version = 15; + size = 23466; + fw_name = "ngene_15.fw"; + dev->cmd_timeout_workaround = true; + break; + case 16: + size = 23498; + fw_name = "ngene_16.fw"; + dev->cmd_timeout_workaround = true; + break; + case 17: + size = 24446; + fw_name = "ngene_17.fw"; + dev->cmd_timeout_workaround = true; + break; + case 18: + size = 0; + fw_name = "ngene_18.fw"; + break; + } + + if (request_firmware(&fw, fw_name, &dev->pci_dev->dev) < 0) { + printk(KERN_ERR DEVICE_NAME + ": Could not load firmware file %s.\n", fw_name); + printk(KERN_INFO DEVICE_NAME + ": Copy %s to your hotplug directory!\n", fw_name); + return -1; + } + if (size == 0) + size = fw->size; + if (size != fw->size) { + printk(KERN_ERR DEVICE_NAME + ": Firmware %s has invalid size!", fw_name); + err = -1; + } else { + printk(KERN_INFO DEVICE_NAME + ": Loading firmware file %s.\n", fw_name); + ngene_fw = (u8 *) fw->data; + err = ngene_command_load_firmware(dev, ngene_fw, size); + } + + release_firmware(fw); + + return err; +} + +static void ngene_stop(struct ngene *dev) +{ + down(&dev->cmd_mutex); + i2c_del_adapter(&(dev->channel[0].i2c_adapter)); + i2c_del_adapter(&(dev->channel[1].i2c_adapter)); + ngwritel(0, NGENE_INT_ENABLE); + ngwritel(0, NGENE_COMMAND); + ngwritel(0, NGENE_COMMAND_HI); + ngwritel(0, NGENE_STATUS); + ngwritel(0, NGENE_STATUS_HI); + ngwritel(0, NGENE_EVENT); + ngwritel(0, NGENE_EVENT_HI); + free_irq(dev->pci_dev->irq, dev); +#ifdef CONFIG_PCI_MSI + if (dev->msi_enabled) + pci_disable_msi(dev->pci_dev); +#endif +} + +static int ngene_buffer_config(struct ngene *dev) +{ + int stat; + + if (dev->card_info->fw_version >= 17) { + u8 tsin12_config[6] = { 0x60, 0x60, 0x00, 0x00, 0x00, 0x00 }; + u8 tsin1234_config[6] = { 0x30, 0x30, 0x00, 0x30, 0x30, 0x00 }; + u8 tsio1235_config[6] = { 0x30, 0x30, 0x00, 0x28, 0x00, 0x38 }; + u8 *bconf = tsin12_config; + + if (dev->card_info->io_type[2]&NGENE_IO_TSIN && + dev->card_info->io_type[3]&NGENE_IO_TSIN) { + bconf = tsin1234_config; + if (dev->card_info->io_type[4]&NGENE_IO_TSOUT && + dev->ci.en) + bconf = tsio1235_config; + } + stat = ngene_command_config_free_buf(dev, bconf); + } else { + int bconf = BUFFER_CONFIG_4422; + + if (dev->card_info->io_type[3] == NGENE_IO_TSIN) + bconf = BUFFER_CONFIG_3333; + stat = ngene_command_config_buf(dev, bconf); + } + return stat; +} + + +static int ngene_start(struct ngene *dev) +{ + int stat; + int i; + + pci_set_master(dev->pci_dev); + ngene_init(dev); + + stat = request_irq(dev->pci_dev->irq, irq_handler, + IRQF_SHARED, "nGene", + (void *)dev); + if (stat < 0) + return stat; + + init_waitqueue_head(&dev->cmd_wq); + init_waitqueue_head(&dev->tx_wq); + init_waitqueue_head(&dev->rx_wq); + sema_init(&dev->cmd_mutex, 1); + sema_init(&dev->stream_mutex, 1); + sema_init(&dev->pll_mutex, 1); + sema_init(&dev->i2c_switch_mutex, 1); + spin_lock_init(&dev->cmd_lock); + for (i = 0; i < MAX_STREAM; i++) + spin_lock_init(&dev->channel[i].state_lock); + ngwritel(1, TIMESTAMPS); + + ngwritel(1, NGENE_INT_ENABLE); + + stat = ngene_load_firm(dev); + if (stat < 0) + goto fail; + +#ifdef CONFIG_PCI_MSI + /* enable MSI if kernel and card support it */ + if (pci_msi_enabled() && dev->card_info->msi_supported) { + unsigned long flags; + + ngwritel(0, NGENE_INT_ENABLE); + free_irq(dev->pci_dev->irq, dev); + stat = pci_enable_msi(dev->pci_dev); + if (stat) { + printk(KERN_INFO DEVICE_NAME + ": MSI not available\n"); + flags = IRQF_SHARED; + } else { + flags = 0; + dev->msi_enabled = true; + } + stat = request_irq(dev->pci_dev->irq, irq_handler, + flags, "nGene", dev); + if (stat < 0) + goto fail2; + ngwritel(1, NGENE_INT_ENABLE); + } +#endif + + stat = ngene_i2c_init(dev, 0); + if (stat < 0) + goto fail; + + stat = ngene_i2c_init(dev, 1); + if (stat < 0) + goto fail; + + return 0; + +fail: + ngwritel(0, NGENE_INT_ENABLE); + free_irq(dev->pci_dev->irq, dev); +#ifdef CONFIG_PCI_MSI +fail2: + if (dev->msi_enabled) + pci_disable_msi(dev->pci_dev); +#endif + return stat; +} + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +static void release_channel(struct ngene_channel *chan) +{ + struct dvb_demux *dvbdemux = &chan->demux; + struct ngene *dev = chan->dev; + + if (chan->running) + set_transfer(chan, 0); + + tasklet_kill(&chan->demux_tasklet); + + if (chan->ci_dev) { + dvb_unregister_device(chan->ci_dev); + chan->ci_dev = NULL; + } + + if (chan->fe2) + dvb_unregister_frontend(chan->fe2); + + if (chan->fe) { + dvb_unregister_frontend(chan->fe); + dvb_frontend_detach(chan->fe); + chan->fe = NULL; + } + + if (chan->has_demux) { + dvb_net_release(&chan->dvbnet); + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, + &chan->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, + &chan->mem_frontend); + dvb_dmxdev_release(&chan->dmxdev); + dvb_dmx_release(&chan->demux); + chan->has_demux = false; + } + + if (chan->has_adapter) { + dvb_unregister_adapter(&dev->adapter[chan->number]); + chan->has_adapter = false; + } +} + +static int init_channel(struct ngene_channel *chan) +{ + int ret = 0, nr = chan->number; + struct dvb_adapter *adapter = NULL; + struct dvb_demux *dvbdemux = &chan->demux; + struct ngene *dev = chan->dev; + struct ngene_info *ni = dev->card_info; + int io = ni->io_type[nr]; + + tasklet_init(&chan->demux_tasklet, demux_tasklet, (unsigned long)chan); + chan->users = 0; + chan->type = io; + chan->mode = chan->type; /* for now only one mode */ + + if (io & NGENE_IO_TSIN) { + chan->fe = NULL; + if (ni->demod_attach[nr]) { + ret = ni->demod_attach[nr](chan); + if (ret < 0) + goto err; + } + if (chan->fe && ni->tuner_attach[nr]) { + ret = ni->tuner_attach[nr](chan); + if (ret < 0) + goto err; + } + } + + if (!dev->ci.en && (io & NGENE_IO_TSOUT)) + return 0; + + if (io & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { + if (nr >= STREAM_AUDIOIN1) + chan->DataFormatFlags = DF_SWAP32; + + if (nr == 0 || !one_adapter || dev->first_adapter == NULL) { + adapter = &dev->adapter[nr]; + ret = dvb_register_adapter(adapter, "nGene", + THIS_MODULE, + &chan->dev->pci_dev->dev, + adapter_nr); + if (ret < 0) + goto err; + if (dev->first_adapter == NULL) + dev->first_adapter = adapter; + chan->has_adapter = true; + } else + adapter = dev->first_adapter; + } + + if (dev->ci.en && (io & NGENE_IO_TSOUT)) { + dvb_ca_en50221_init(adapter, dev->ci.en, 0, 1); + set_transfer(chan, 1); + chan->dev->channel[2].DataFormatFlags = DF_SWAP32; + set_transfer(&chan->dev->channel[2], 1); + dvb_register_device(adapter, &chan->ci_dev, + &ngene_dvbdev_ci, (void *) chan, + DVB_DEVICE_SEC); + if (!chan->ci_dev) + goto err; + } + + if (chan->fe) { + if (dvb_register_frontend(adapter, chan->fe) < 0) + goto err; + chan->has_demux = true; + } + if (chan->fe2) { + if (dvb_register_frontend(adapter, chan->fe2) < 0) + goto err; + chan->fe2->tuner_priv = chan->fe->tuner_priv; + memcpy(&chan->fe2->ops.tuner_ops, + &chan->fe->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + } + + if (chan->has_demux) { + ret = my_dvb_dmx_ts_card_init(dvbdemux, "SW demux", + ngene_start_feed, + ngene_stop_feed, chan); + ret = my_dvb_dmxdev_ts_card_init(&chan->dmxdev, &chan->demux, + &chan->hw_frontend, + &chan->mem_frontend, adapter); + ret = dvb_net_init(adapter, &chan->dvbnet, &chan->demux.dmx); + } + + return ret; + +err: + if (chan->fe) { + dvb_frontend_detach(chan->fe); + chan->fe = NULL; + } + release_channel(chan); + return 0; +} + +static int init_channels(struct ngene *dev) +{ + int i, j; + + for (i = 0; i < MAX_STREAM; i++) { + dev->channel[i].number = i; + if (init_channel(&dev->channel[i]) < 0) { + for (j = i - 1; j >= 0; j--) + release_channel(&dev->channel[j]); + return -1; + } + } + return 0; +} + +static struct cxd2099_cfg cxd_cfg = { + .bitrate = 62000, + .adr = 0x40, + .polarity = 0, + .clock_mode = 0, +}; + +static void cxd_attach(struct ngene *dev) +{ + struct ngene_ci *ci = &dev->ci; + + ci->en = cxd2099_attach(&cxd_cfg, dev, &dev->channel[0].i2c_adapter); + ci->dev = dev; + return; +} + +static void cxd_detach(struct ngene *dev) +{ + struct ngene_ci *ci = &dev->ci; + + dvb_ca_en50221_release(ci->en); + kfree(ci->en); + ci->en = 0; +} + +/***********************************/ +/* workaround for shutdown failure */ +/***********************************/ + +static void ngene_unlink(struct ngene *dev) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_MEM_WRITE; + com.cmd.hdr.Length = 3; + com.cmd.MemoryWrite.address = 0x910c; + com.cmd.MemoryWrite.data = 0xff; + com.in_len = 3; + com.out_len = 1; + + down(&dev->cmd_mutex); + ngwritel(0, NGENE_INT_ENABLE); + ngene_command_mutex(dev, &com); + up(&dev->cmd_mutex); +} + +void ngene_shutdown(struct pci_dev *pdev) +{ + struct ngene *dev = (struct ngene *)pci_get_drvdata(pdev); + + if (!dev || !shutdown_workaround) + return; + + printk(KERN_INFO DEVICE_NAME ": shutdown workaround...\n"); + ngene_unlink(dev); + pci_disable_device(pdev); +} + +/****************************************************************************/ +/* device probe/remove calls ************************************************/ +/****************************************************************************/ + +void __devexit ngene_remove(struct pci_dev *pdev) +{ + struct ngene *dev = pci_get_drvdata(pdev); + int i; + + tasklet_kill(&dev->event_tasklet); + for (i = MAX_STREAM - 1; i >= 0; i--) + release_channel(&dev->channel[i]); + if (dev->ci.en) + cxd_detach(dev); + ngene_stop(dev); + ngene_release_buffers(dev); + pci_set_drvdata(pdev, NULL); + pci_disable_device(pdev); +} + +int __devinit ngene_probe(struct pci_dev *pci_dev, + const struct pci_device_id *id) +{ + struct ngene *dev; + int stat = 0; + + if (pci_enable_device(pci_dev) < 0) + return -ENODEV; + + dev = vzalloc(sizeof(struct ngene)); + if (dev == NULL) { + stat = -ENOMEM; + goto fail0; + } + + dev->pci_dev = pci_dev; + dev->card_info = (struct ngene_info *)id->driver_data; + printk(KERN_INFO DEVICE_NAME ": Found %s\n", dev->card_info->name); + + pci_set_drvdata(pci_dev, dev); + + /* Alloc buffers and start nGene */ + stat = ngene_get_buffers(dev); + if (stat < 0) + goto fail1; + stat = ngene_start(dev); + if (stat < 0) + goto fail1; + + cxd_attach(dev); + + stat = ngene_buffer_config(dev); + if (stat < 0) + goto fail1; + + + dev->i2c_current_bus = -1; + + /* Register DVB adapters and devices for both channels */ + if (init_channels(dev) < 0) + goto fail2; + + return 0; + +fail2: + ngene_stop(dev); +fail1: + ngene_release_buffers(dev); +fail0: + pci_disable_device(pci_dev); + pci_set_drvdata(pci_dev, NULL); + return stat; +} diff --git a/drivers/media/dvb/ngene/ngene-dvb.c b/drivers/media/pci/ngene/ngene-dvb.c similarity index 100% rename from drivers/media/dvb/ngene/ngene-dvb.c rename to drivers/media/pci/ngene/ngene-dvb.c diff --git a/drivers/media/dvb/ngene/ngene-i2c.c b/drivers/media/pci/ngene/ngene-i2c.c similarity index 100% rename from drivers/media/dvb/ngene/ngene-i2c.c rename to drivers/media/pci/ngene/ngene-i2c.c diff --git a/drivers/media/dvb/ngene/ngene.h b/drivers/media/pci/ngene/ngene.h similarity index 100% rename from drivers/media/dvb/ngene/ngene.h rename to drivers/media/pci/ngene/ngene.h diff --git a/drivers/media/dvb/pluto2/Kconfig b/drivers/media/pci/pluto2/Kconfig similarity index 100% rename from drivers/media/dvb/pluto2/Kconfig rename to drivers/media/pci/pluto2/Kconfig diff --git a/drivers/media/pci/pluto2/Makefile b/drivers/media/pci/pluto2/Makefile new file mode 100644 index 000000000000..524bf841f42b --- /dev/null +++ b/drivers/media/pci/pluto2/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_DVB_PLUTO2) += pluto2.o + +ccflags-y += -Idrivers/media/dvb-core/ -Idrivers/media/dvb-frontends/ diff --git a/drivers/media/dvb/pluto2/pluto2.c b/drivers/media/pci/pluto2/pluto2.c similarity index 100% rename from drivers/media/dvb/pluto2/pluto2.c rename to drivers/media/pci/pluto2/pluto2.c diff --git a/drivers/media/dvb/pt1/Kconfig b/drivers/media/pci/pt1/Kconfig similarity index 100% rename from drivers/media/dvb/pt1/Kconfig rename to drivers/media/pci/pt1/Kconfig diff --git a/drivers/media/pci/pt1/Makefile b/drivers/media/pci/pt1/Makefile new file mode 100644 index 000000000000..98e391295afe --- /dev/null +++ b/drivers/media/pci/pt1/Makefile @@ -0,0 +1,5 @@ +earth-pt1-objs := pt1.o va1j5jf8007s.o va1j5jf8007t.o + +obj-$(CONFIG_DVB_PT1) += earth-pt1.o + +ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends diff --git a/drivers/media/dvb/pt1/pt1.c b/drivers/media/pci/pt1/pt1.c similarity index 100% rename from drivers/media/dvb/pt1/pt1.c rename to drivers/media/pci/pt1/pt1.c diff --git a/drivers/media/pci/pt1/va1j5jf8007s.c b/drivers/media/pci/pt1/va1j5jf8007s.c new file mode 100644 index 000000000000..1b637b74ef58 --- /dev/null +++ b/drivers/media/pci/pt1/va1j5jf8007s.c @@ -0,0 +1,736 @@ +/* + * ISDB-S driver for VA1J5JF8007/VA1J5JF8011 + * + * Copyright (C) 2009 HIRANO Takahito + * + * based on pt1dvr - http://pt1dvr.sourceforge.jp/ + * by Tomoaki Ishikawa + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include "dvb_frontend.h" +#include "va1j5jf8007s.h" + +enum va1j5jf8007s_tune_state { + VA1J5JF8007S_IDLE, + VA1J5JF8007S_SET_FREQUENCY_1, + VA1J5JF8007S_SET_FREQUENCY_2, + VA1J5JF8007S_SET_FREQUENCY_3, + VA1J5JF8007S_CHECK_FREQUENCY, + VA1J5JF8007S_SET_MODULATION, + VA1J5JF8007S_CHECK_MODULATION, + VA1J5JF8007S_SET_TS_ID, + VA1J5JF8007S_CHECK_TS_ID, + VA1J5JF8007S_TRACK, +}; + +struct va1j5jf8007s_state { + const struct va1j5jf8007s_config *config; + struct i2c_adapter *adap; + struct dvb_frontend fe; + enum va1j5jf8007s_tune_state tune_state; +}; + +static int va1j5jf8007s_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct va1j5jf8007s_state *state; + u8 addr; + int i; + u8 write_buf[1], read_buf[1]; + struct i2c_msg msgs[2]; + s32 word, x1, x2, x3, x4, x5, y; + + state = fe->demodulator_priv; + addr = state->config->demod_address; + + word = 0; + for (i = 0; i < 2; i++) { + write_buf[0] = 0xbc + i; + + msgs[0].addr = addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(write_buf); + msgs[0].buf = write_buf; + + msgs[1].addr = addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(read_buf); + msgs[1].buf = read_buf; + + if (i2c_transfer(state->adap, msgs, 2) != 2) + return -EREMOTEIO; + + word <<= 8; + word |= read_buf[0]; + } + + word -= 3000; + if (word < 0) + word = 0; + + x1 = int_sqrt(word << 16) * ((15625ll << 21) / 1000000); + x2 = (s64)x1 * x1 >> 31; + x3 = (s64)x2 * x1 >> 31; + x4 = (s64)x2 * x2 >> 31; + x5 = (s64)x4 * x1 >> 31; + + y = (58857ll << 23) / 1000; + y -= (s64)x1 * ((89565ll << 24) / 1000) >> 30; + y += (s64)x2 * ((88977ll << 24) / 1000) >> 28; + y -= (s64)x3 * ((50259ll << 25) / 1000) >> 27; + y += (s64)x4 * ((14341ll << 27) / 1000) >> 27; + y -= (s64)x5 * ((16346ll << 30) / 10000) >> 28; + + *snr = y < 0 ? 0 : y >> 15; + return 0; +} + +static int va1j5jf8007s_get_frontend_algo(struct dvb_frontend *fe) +{ + return DVBFE_ALGO_HW; +} + +static int +va1j5jf8007s_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct va1j5jf8007s_state *state; + + state = fe->demodulator_priv; + + switch (state->tune_state) { + case VA1J5JF8007S_IDLE: + case VA1J5JF8007S_SET_FREQUENCY_1: + case VA1J5JF8007S_SET_FREQUENCY_2: + case VA1J5JF8007S_SET_FREQUENCY_3: + case VA1J5JF8007S_CHECK_FREQUENCY: + *status = 0; + return 0; + + + case VA1J5JF8007S_SET_MODULATION: + case VA1J5JF8007S_CHECK_MODULATION: + *status |= FE_HAS_SIGNAL; + return 0; + + case VA1J5JF8007S_SET_TS_ID: + case VA1J5JF8007S_CHECK_TS_ID: + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER; + return 0; + + case VA1J5JF8007S_TRACK: + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK; + return 0; + } + + BUG(); +} + +struct va1j5jf8007s_cb_map { + u32 frequency; + u8 cb; +}; + +static const struct va1j5jf8007s_cb_map va1j5jf8007s_cb_maps[] = { + { 986000, 0xb2 }, + { 1072000, 0xd2 }, + { 1154000, 0xe2 }, + { 1291000, 0x20 }, + { 1447000, 0x40 }, + { 1615000, 0x60 }, + { 1791000, 0x80 }, + { 1972000, 0xa0 }, +}; + +static u8 va1j5jf8007s_lookup_cb(u32 frequency) +{ + int i; + const struct va1j5jf8007s_cb_map *map; + + for (i = 0; i < ARRAY_SIZE(va1j5jf8007s_cb_maps); i++) { + map = &va1j5jf8007s_cb_maps[i]; + if (frequency < map->frequency) + return map->cb; + } + return 0xc0; +} + +static int va1j5jf8007s_set_frequency_1(struct va1j5jf8007s_state *state) +{ + u32 frequency; + u16 word; + u8 buf[6]; + struct i2c_msg msg; + + frequency = state->fe.dtv_property_cache.frequency; + + word = (frequency + 500) / 1000; + if (frequency < 1072000) + word = (word << 1 & ~0x1f) | (word & 0x0f); + + buf[0] = 0xfe; + buf[1] = 0xc0; + buf[2] = 0x40 | word >> 8; + buf[3] = word; + buf[4] = 0xe0; + buf[5] = va1j5jf8007s_lookup_cb(frequency); + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + if (i2c_transfer(state->adap, &msg, 1) != 1) + return -EREMOTEIO; + + return 0; +} + +static int va1j5jf8007s_set_frequency_2(struct va1j5jf8007s_state *state) +{ + u8 buf[3]; + struct i2c_msg msg; + + buf[0] = 0xfe; + buf[1] = 0xc0; + buf[2] = 0xe4; + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + if (i2c_transfer(state->adap, &msg, 1) != 1) + return -EREMOTEIO; + + return 0; +} + +static int va1j5jf8007s_set_frequency_3(struct va1j5jf8007s_state *state) +{ + u32 frequency; + u8 buf[4]; + struct i2c_msg msg; + + frequency = state->fe.dtv_property_cache.frequency; + + buf[0] = 0xfe; + buf[1] = 0xc0; + buf[2] = 0xf4; + buf[3] = va1j5jf8007s_lookup_cb(frequency) | 0x4; + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + if (i2c_transfer(state->adap, &msg, 1) != 1) + return -EREMOTEIO; + + return 0; +} + +static int +va1j5jf8007s_check_frequency(struct va1j5jf8007s_state *state, int *lock) +{ + u8 addr; + u8 write_buf[2], read_buf[1]; + struct i2c_msg msgs[2]; + + addr = state->config->demod_address; + + write_buf[0] = 0xfe; + write_buf[1] = 0xc1; + + msgs[0].addr = addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(write_buf); + msgs[0].buf = write_buf; + + msgs[1].addr = addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(read_buf); + msgs[1].buf = read_buf; + + if (i2c_transfer(state->adap, msgs, 2) != 2) + return -EREMOTEIO; + + *lock = read_buf[0] & 0x40; + return 0; +} + +static int va1j5jf8007s_set_modulation(struct va1j5jf8007s_state *state) +{ + u8 buf[2]; + struct i2c_msg msg; + + buf[0] = 0x03; + buf[1] = 0x01; + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + if (i2c_transfer(state->adap, &msg, 1) != 1) + return -EREMOTEIO; + + return 0; +} + +static int +va1j5jf8007s_check_modulation(struct va1j5jf8007s_state *state, int *lock) +{ + u8 addr; + u8 write_buf[1], read_buf[1]; + struct i2c_msg msgs[2]; + + addr = state->config->demod_address; + + write_buf[0] = 0xc3; + + msgs[0].addr = addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(write_buf); + msgs[0].buf = write_buf; + + msgs[1].addr = addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(read_buf); + msgs[1].buf = read_buf; + + if (i2c_transfer(state->adap, msgs, 2) != 2) + return -EREMOTEIO; + + *lock = !(read_buf[0] & 0x10); + return 0; +} + +static int +va1j5jf8007s_set_ts_id(struct va1j5jf8007s_state *state) +{ + u32 ts_id; + u8 buf[3]; + struct i2c_msg msg; + + ts_id = state->fe.dtv_property_cache.stream_id; + if (!ts_id || ts_id == NO_STREAM_ID_FILTER) + return 0; + + buf[0] = 0x8f; + buf[1] = ts_id >> 8; + buf[2] = ts_id; + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + if (i2c_transfer(state->adap, &msg, 1) != 1) + return -EREMOTEIO; + + return 0; +} + +static int +va1j5jf8007s_check_ts_id(struct va1j5jf8007s_state *state, int *lock) +{ + u8 addr; + u8 write_buf[1], read_buf[2]; + struct i2c_msg msgs[2]; + u32 ts_id; + + ts_id = state->fe.dtv_property_cache.stream_id; + if (!ts_id || ts_id == NO_STREAM_ID_FILTER) { + *lock = 1; + return 0; + } + + addr = state->config->demod_address; + + write_buf[0] = 0xe6; + + msgs[0].addr = addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(write_buf); + msgs[0].buf = write_buf; + + msgs[1].addr = addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(read_buf); + msgs[1].buf = read_buf; + + if (i2c_transfer(state->adap, msgs, 2) != 2) + return -EREMOTEIO; + + *lock = (read_buf[0] << 8 | read_buf[1]) == ts_id; + return 0; +} + +static int +va1j5jf8007s_tune(struct dvb_frontend *fe, + bool re_tune, + unsigned int mode_flags, unsigned int *delay, + fe_status_t *status) +{ + struct va1j5jf8007s_state *state; + int ret; + int lock = 0; + + state = fe->demodulator_priv; + + if (re_tune) + state->tune_state = VA1J5JF8007S_SET_FREQUENCY_1; + + switch (state->tune_state) { + case VA1J5JF8007S_IDLE: + *delay = 3 * HZ; + *status = 0; + return 0; + + case VA1J5JF8007S_SET_FREQUENCY_1: + ret = va1j5jf8007s_set_frequency_1(state); + if (ret < 0) + return ret; + + state->tune_state = VA1J5JF8007S_SET_FREQUENCY_2; + *delay = 0; + *status = 0; + return 0; + + case VA1J5JF8007S_SET_FREQUENCY_2: + ret = va1j5jf8007s_set_frequency_2(state); + if (ret < 0) + return ret; + + state->tune_state = VA1J5JF8007S_SET_FREQUENCY_3; + *delay = (HZ + 99) / 100; + *status = 0; + return 0; + + case VA1J5JF8007S_SET_FREQUENCY_3: + ret = va1j5jf8007s_set_frequency_3(state); + if (ret < 0) + return ret; + + state->tune_state = VA1J5JF8007S_CHECK_FREQUENCY; + *delay = 0; + *status = 0; + return 0; + + case VA1J5JF8007S_CHECK_FREQUENCY: + ret = va1j5jf8007s_check_frequency(state, &lock); + if (ret < 0) + return ret; + + if (!lock) { + *delay = (HZ + 999) / 1000; + *status = 0; + return 0; + } + + state->tune_state = VA1J5JF8007S_SET_MODULATION; + *delay = 0; + *status = FE_HAS_SIGNAL; + return 0; + + case VA1J5JF8007S_SET_MODULATION: + ret = va1j5jf8007s_set_modulation(state); + if (ret < 0) + return ret; + + state->tune_state = VA1J5JF8007S_CHECK_MODULATION; + *delay = 0; + *status = FE_HAS_SIGNAL; + return 0; + + case VA1J5JF8007S_CHECK_MODULATION: + ret = va1j5jf8007s_check_modulation(state, &lock); + if (ret < 0) + return ret; + + if (!lock) { + *delay = (HZ + 49) / 50; + *status = FE_HAS_SIGNAL; + return 0; + } + + state->tune_state = VA1J5JF8007S_SET_TS_ID; + *delay = 0; + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; + return 0; + + case VA1J5JF8007S_SET_TS_ID: + ret = va1j5jf8007s_set_ts_id(state); + if (ret < 0) + return ret; + + state->tune_state = VA1J5JF8007S_CHECK_TS_ID; + return 0; + + case VA1J5JF8007S_CHECK_TS_ID: + ret = va1j5jf8007s_check_ts_id(state, &lock); + if (ret < 0) + return ret; + + if (!lock) { + *delay = (HZ + 99) / 100; + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; + return 0; + } + + state->tune_state = VA1J5JF8007S_TRACK; + /* fall through */ + + case VA1J5JF8007S_TRACK: + *delay = 3 * HZ; + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK; + return 0; + } + + BUG(); +} + +static int va1j5jf8007s_init_frequency(struct va1j5jf8007s_state *state) +{ + u8 buf[4]; + struct i2c_msg msg; + + buf[0] = 0xfe; + buf[1] = 0xc0; + buf[2] = 0xf0; + buf[3] = 0x04; + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + if (i2c_transfer(state->adap, &msg, 1) != 1) + return -EREMOTEIO; + + return 0; +} + +static int va1j5jf8007s_set_sleep(struct va1j5jf8007s_state *state, int sleep) +{ + u8 buf[2]; + struct i2c_msg msg; + + buf[0] = 0x17; + buf[1] = sleep ? 0x01 : 0x00; + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + if (i2c_transfer(state->adap, &msg, 1) != 1) + return -EREMOTEIO; + + return 0; +} + +static int va1j5jf8007s_sleep(struct dvb_frontend *fe) +{ + struct va1j5jf8007s_state *state; + int ret; + + state = fe->demodulator_priv; + + ret = va1j5jf8007s_init_frequency(state); + if (ret < 0) + return ret; + + return va1j5jf8007s_set_sleep(state, 1); +} + +static int va1j5jf8007s_init(struct dvb_frontend *fe) +{ + struct va1j5jf8007s_state *state; + + state = fe->demodulator_priv; + state->tune_state = VA1J5JF8007S_IDLE; + + return va1j5jf8007s_set_sleep(state, 0); +} + +static void va1j5jf8007s_release(struct dvb_frontend *fe) +{ + struct va1j5jf8007s_state *state; + state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops va1j5jf8007s_ops = { + .delsys = { SYS_ISDBS }, + .info = { + .name = "VA1J5JF8007/VA1J5JF8011 ISDB-S", + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 1000, + .caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | + FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO | + FE_CAN_MULTISTREAM, + }, + + .read_snr = va1j5jf8007s_read_snr, + .get_frontend_algo = va1j5jf8007s_get_frontend_algo, + .read_status = va1j5jf8007s_read_status, + .tune = va1j5jf8007s_tune, + .sleep = va1j5jf8007s_sleep, + .init = va1j5jf8007s_init, + .release = va1j5jf8007s_release, +}; + +static int va1j5jf8007s_prepare_1(struct va1j5jf8007s_state *state) +{ + u8 addr; + u8 write_buf[1], read_buf[1]; + struct i2c_msg msgs[2]; + + addr = state->config->demod_address; + + write_buf[0] = 0x07; + + msgs[0].addr = addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(write_buf); + msgs[0].buf = write_buf; + + msgs[1].addr = addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(read_buf); + msgs[1].buf = read_buf; + + if (i2c_transfer(state->adap, msgs, 2) != 2) + return -EREMOTEIO; + + if (read_buf[0] != 0x41) + return -EIO; + + return 0; +} + +static const u8 va1j5jf8007s_20mhz_prepare_bufs[][2] = { + {0x04, 0x02}, {0x0d, 0x55}, {0x11, 0x40}, {0x13, 0x80}, {0x17, 0x01}, + {0x1c, 0x0a}, {0x1d, 0xaa}, {0x1e, 0x20}, {0x1f, 0x88}, {0x51, 0xb0}, + {0x52, 0x89}, {0x53, 0xb3}, {0x5a, 0x2d}, {0x5b, 0xd3}, {0x85, 0x69}, + {0x87, 0x04}, {0x8e, 0x02}, {0xa3, 0xf7}, {0xa5, 0xc0}, +}; + +static const u8 va1j5jf8007s_25mhz_prepare_bufs[][2] = { + {0x04, 0x02}, {0x11, 0x40}, {0x13, 0x80}, {0x17, 0x01}, {0x1c, 0x0a}, + {0x1d, 0xaa}, {0x1e, 0x20}, {0x1f, 0x88}, {0x51, 0xb0}, {0x52, 0x89}, + {0x53, 0xb3}, {0x5a, 0x2d}, {0x5b, 0xd3}, {0x85, 0x69}, {0x87, 0x04}, + {0x8e, 0x26}, {0xa3, 0xf7}, {0xa5, 0xc0}, +}; + +static int va1j5jf8007s_prepare_2(struct va1j5jf8007s_state *state) +{ + const u8 (*bufs)[2]; + int size; + u8 addr; + u8 buf[2]; + struct i2c_msg msg; + int i; + + switch (state->config->frequency) { + case VA1J5JF8007S_20MHZ: + bufs = va1j5jf8007s_20mhz_prepare_bufs; + size = ARRAY_SIZE(va1j5jf8007s_20mhz_prepare_bufs); + break; + case VA1J5JF8007S_25MHZ: + bufs = va1j5jf8007s_25mhz_prepare_bufs; + size = ARRAY_SIZE(va1j5jf8007s_25mhz_prepare_bufs); + break; + default: + return -EINVAL; + } + + addr = state->config->demod_address; + + msg.addr = addr; + msg.flags = 0; + msg.len = 2; + msg.buf = buf; + for (i = 0; i < size; i++) { + memcpy(buf, bufs[i], sizeof(buf)); + if (i2c_transfer(state->adap, &msg, 1) != 1) + return -EREMOTEIO; + } + + return 0; +} + +/* must be called after va1j5jf8007t_attach */ +int va1j5jf8007s_prepare(struct dvb_frontend *fe) +{ + struct va1j5jf8007s_state *state; + int ret; + + state = fe->demodulator_priv; + + ret = va1j5jf8007s_prepare_1(state); + if (ret < 0) + return ret; + + ret = va1j5jf8007s_prepare_2(state); + if (ret < 0) + return ret; + + return va1j5jf8007s_init_frequency(state); +} + +struct dvb_frontend * +va1j5jf8007s_attach(const struct va1j5jf8007s_config *config, + struct i2c_adapter *adap) +{ + struct va1j5jf8007s_state *state; + struct dvb_frontend *fe; + u8 buf[2]; + struct i2c_msg msg; + + state = kzalloc(sizeof(struct va1j5jf8007s_state), GFP_KERNEL); + if (!state) + return NULL; + + state->config = config; + state->adap = adap; + + fe = &state->fe; + memcpy(&fe->ops, &va1j5jf8007s_ops, sizeof(struct dvb_frontend_ops)); + fe->demodulator_priv = state; + + buf[0] = 0x01; + buf[1] = 0x80; + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + if (i2c_transfer(state->adap, &msg, 1) != 1) { + kfree(state); + return NULL; + } + + return fe; +} diff --git a/drivers/media/dvb/pt1/va1j5jf8007s.h b/drivers/media/pci/pt1/va1j5jf8007s.h similarity index 100% rename from drivers/media/dvb/pt1/va1j5jf8007s.h rename to drivers/media/pci/pt1/va1j5jf8007s.h diff --git a/drivers/media/dvb/pt1/va1j5jf8007t.c b/drivers/media/pci/pt1/va1j5jf8007t.c similarity index 100% rename from drivers/media/dvb/pt1/va1j5jf8007t.c rename to drivers/media/pci/pt1/va1j5jf8007t.c diff --git a/drivers/media/dvb/pt1/va1j5jf8007t.h b/drivers/media/pci/pt1/va1j5jf8007t.h similarity index 100% rename from drivers/media/dvb/pt1/va1j5jf8007t.h rename to drivers/media/pci/pt1/va1j5jf8007t.h diff --git a/drivers/media/pci/saa7134/Kconfig b/drivers/media/pci/saa7134/Kconfig new file mode 100644 index 000000000000..15b90d6e9130 --- /dev/null +++ b/drivers/media/pci/saa7134/Kconfig @@ -0,0 +1,64 @@ +config VIDEO_SAA7134 + tristate "Philips SAA7134 support" + depends on VIDEO_DEV && PCI && I2C + select VIDEOBUF_DMA_SG + select VIDEO_TUNER + select VIDEO_TVEEPROM + select CRC32 + select VIDEO_SAA6588 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + This is a video4linux driver for Philips SAA713x based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called saa7134. + +config VIDEO_SAA7134_ALSA + tristate "Philips SAA7134 DMA audio support" + depends on VIDEO_SAA7134 && SND + select SND_PCM + ---help--- + This is a video4linux driver for direct (DMA) audio in + Philips SAA713x based TV cards using ALSA + + To compile this driver as a module, choose M here: the + module will be called saa7134-alsa. + +config VIDEO_SAA7134_RC + bool "Philips SAA7134 Remote Controller support" + depends on RC_CORE + depends on VIDEO_SAA7134 + depends on !(RC_CORE=m && VIDEO_SAA7134=y) + default y + ---help--- + Enables Remote Controller support on saa7134 driver. + +config VIDEO_SAA7134_DVB + tristate "DVB/ATSC Support for saa7134 based TV cards" + depends on VIDEO_SAA7134 && DVB_CORE + select VIDEOBUF_DVB + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_NXT200X if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10086 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA826X if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6421 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6405 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA827X if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10036 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MT312 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT + select DVB_ZL10039 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + This adds support for DVB cards based on the + Philips saa7134 chip. + + To compile this driver as a module, choose M here: the + module will be called saa7134-dvb. diff --git a/drivers/media/pci/saa7134/Makefile b/drivers/media/pci/saa7134/Makefile new file mode 100644 index 000000000000..35375480ed4d --- /dev/null +++ b/drivers/media/pci/saa7134/Makefile @@ -0,0 +1,16 @@ + +saa7134-y += saa7134-cards.o saa7134-core.o saa7134-i2c.o +saa7134-y += saa7134-ts.o saa7134-tvaudio.o saa7134-vbi.o +saa7134-y += saa7134-video.o +saa7134-$(CONFIG_VIDEO_SAA7134_RC) += saa7134-input.o + +obj-$(CONFIG_VIDEO_SAA7134) += saa6752hs.o saa7134.o saa7134-empress.o + +obj-$(CONFIG_VIDEO_SAA7134_ALSA) += saa7134-alsa.o + +obj-$(CONFIG_VIDEO_SAA7134_DVB) += saa7134-dvb.o + +ccflags-y += -I$(srctree)/drivers/media/i2c +ccflags-y += -I$(srctree)/drivers/media/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb-frontends diff --git a/drivers/media/video/saa7134/saa6752hs.c b/drivers/media/pci/saa7134/saa6752hs.c similarity index 100% rename from drivers/media/video/saa7134/saa6752hs.c rename to drivers/media/pci/saa7134/saa6752hs.c diff --git a/drivers/media/video/saa7134/saa7134-alsa.c b/drivers/media/pci/saa7134/saa7134-alsa.c similarity index 100% rename from drivers/media/video/saa7134/saa7134-alsa.c rename to drivers/media/pci/saa7134/saa7134-alsa.c diff --git a/drivers/media/video/saa7134/saa7134-cards.c b/drivers/media/pci/saa7134/saa7134-cards.c similarity index 100% rename from drivers/media/video/saa7134/saa7134-cards.c rename to drivers/media/pci/saa7134/saa7134-cards.c diff --git a/drivers/media/video/saa7134/saa7134-core.c b/drivers/media/pci/saa7134/saa7134-core.c similarity index 100% rename from drivers/media/video/saa7134/saa7134-core.c rename to drivers/media/pci/saa7134/saa7134-core.c diff --git a/drivers/media/pci/saa7134/saa7134-dvb.c b/drivers/media/pci/saa7134/saa7134-dvb.c new file mode 100644 index 000000000000..b209de40a4f8 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-dvb.c @@ -0,0 +1,1936 @@ +/* + * + * (c) 2004 Gerd Knorr [SuSE Labs] + * + * Extended 3 / 2005 by Hartmut Hackmann to support various + * cards with the tda10046 DVB-T channel decoder + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "saa7134-reg.h" +#include "saa7134.h" +#include +#include "dvb-pll.h" +#include + +#include "mt352.h" +#include "mt352_priv.h" /* FIXME */ +#include "tda1004x.h" +#include "nxt200x.h" +#include "tuner-xc2028.h" +#include "xc5000.h" + +#include "tda10086.h" +#include "tda826x.h" +#include "tda827x.h" +#include "isl6421.h" +#include "isl6405.h" +#include "lnbp21.h" +#include "tuner-simple.h" +#include "tda10048.h" +#include "tda18271.h" +#include "lgdt3305.h" +#include "tda8290.h" +#include "mb86a20s.h" +#include "lgs8gxx.h" + +#include "zl10353.h" +#include "qt1010.h" + +#include "zl10036.h" +#include "zl10039.h" +#include "mt312.h" +#include "s5h1411.h" + +MODULE_AUTHOR("Gerd Knorr [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +static unsigned int antenna_pwr; + +module_param(antenna_pwr, int, 0444); +MODULE_PARM_DESC(antenna_pwr,"enable antenna power (Pinnacle 300i)"); + +static int use_frontend; +module_param(use_frontend, int, 0644); +MODULE_PARM_DESC(use_frontend,"for cards with multiple frontends (0: terrestrial, 1: satellite)"); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off module debugging (default:off)."); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +#define dprintk(fmt, arg...) do { if (debug) \ + printk(KERN_DEBUG "%s/dvb: " fmt, dev->name , ## arg); } while(0) + +/* Print a warning */ +#define wprintk(fmt, arg...) \ + printk(KERN_WARNING "%s/dvb: " fmt, dev->name, ## arg) + +/* ------------------------------------------------------------------ + * mt352 based DVB-T cards + */ + +static int pinnacle_antenna_pwr(struct saa7134_dev *dev, int on) +{ + u32 ok; + + if (!on) { + saa_setl(SAA7134_GPIO_GPMODE0 >> 2, (1 << 26)); + saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); + return 0; + } + + saa_setl(SAA7134_GPIO_GPMODE0 >> 2, (1 << 26)); + saa_setl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); + udelay(10); + + saa_setl(SAA7134_GPIO_GPMODE0 >> 2, (1 << 28)); + saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 28)); + udelay(10); + saa_setl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 28)); + udelay(10); + ok = saa_readl(SAA7134_GPIO_GPSTATUS0) & (1 << 27); + dprintk("%s %s\n", __func__, ok ? "on" : "off"); + + if (!ok) + saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); + return ok; +} + +static int mt352_pinnacle_init(struct dvb_frontend* fe) +{ + static u8 clock_config [] = { CLOCK_CTL, 0x3d, 0x28 }; + static u8 reset [] = { RESET, 0x80 }; + static u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static u8 agc_cfg [] = { AGC_TARGET, 0x28, 0xa0 }; + static u8 capt_range_cfg[] = { CAPT_RANGE, 0x31 }; + static u8 fsm_ctl_cfg[] = { 0x7b, 0x04 }; + static u8 gpp_ctl_cfg [] = { GPP_CTL, 0x0f }; + static u8 scan_ctl_cfg [] = { SCAN_CTL, 0x0d }; + static u8 irq_cfg [] = { INTERRUPT_EN_0, 0x00, 0x00, 0x00, 0x00 }; + struct saa7134_dev *dev= fe->dvb->priv; + + dprintk("%s called\n", __func__); + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); + + mt352_write(fe, fsm_ctl_cfg, sizeof(fsm_ctl_cfg)); + mt352_write(fe, scan_ctl_cfg, sizeof(scan_ctl_cfg)); + mt352_write(fe, irq_cfg, sizeof(irq_cfg)); + + return 0; +} + +static int mt352_aver777_init(struct dvb_frontend* fe) +{ + static u8 clock_config [] = { CLOCK_CTL, 0x38, 0x2d }; + static u8 reset [] = { RESET, 0x80 }; + static u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static u8 agc_cfg [] = { AGC_TARGET, 0x28, 0xa0 }; + static u8 capt_range_cfg[] = { CAPT_RANGE, 0x33 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +static int mt352_avermedia_xc3028_init(struct dvb_frontend *fe) +{ + static u8 clock_config [] = { CLOCK_CTL, 0x38, 0x2d }; + static u8 reset [] = { RESET, 0x80 }; + static u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static u8 agc_cfg [] = { AGC_TARGET, 0xe }; + static u8 capt_range_cfg[] = { CAPT_RANGE, 0x33 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + return 0; +} + +static int mt352_pinnacle_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u8 off[] = { 0x00, 0xf1}; + u8 on[] = { 0x00, 0x71}; + struct i2c_msg msg = {.addr=0x43, .flags=0, .buf=off, .len = sizeof(off)}; + + struct saa7134_dev *dev = fe->dvb->priv; + struct v4l2_frequency f; + + /* set frequency (mt2050) */ + f.tuner = 0; + f.type = V4L2_TUNER_DIGITAL_TV; + f.frequency = c->frequency / 1000 * 16 / 1000; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(&dev->i2c_adap, &msg, 1); + saa_call_all(dev, tuner, s_frequency, &f); + msg.buf = on; + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(&dev->i2c_adap, &msg, 1); + + pinnacle_antenna_pwr(dev, antenna_pwr); + + /* mt352 setup */ + return mt352_pinnacle_init(fe); +} + +static struct mt352_config pinnacle_300i = { + .demod_address = 0x3c >> 1, + .adc_clock = 20333, + .if2 = 36150, + .no_tuner = 1, + .demod_init = mt352_pinnacle_init, +}; + +static struct mt352_config avermedia_777 = { + .demod_address = 0xf, + .demod_init = mt352_aver777_init, +}; + +static struct mt352_config avermedia_xc3028_mt352_dev = { + .demod_address = (0x1e >> 1), + .no_tuner = 1, + .demod_init = mt352_avermedia_xc3028_init, +}; + +static struct tda18271_std_map mb86a20s_tda18271_std_map = { + .dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4, + .if_lvl = 7, .rfagc_top = 0x37, }, +}; + +static struct tda18271_config kworld_tda18271_config = { + .std_map = &mb86a20s_tda18271_std_map, + .gate = TDA18271_GATE_DIGITAL, + .config = 3, /* Use tuner callback for AGC */ + +}; + +static const struct mb86a20s_config kworld_mb86a20s_config = { + .demod_address = 0x10, +}; + +static int kworld_sbtvd_gate_ctrl(struct dvb_frontend* fe, int enable) +{ + struct saa7134_dev *dev = fe->dvb->priv; + + unsigned char initmsg[] = {0x45, 0x97}; + unsigned char msg_enable[] = {0x45, 0xc1}; + unsigned char msg_disable[] = {0x45, 0x81}; + struct i2c_msg msg = {.addr = 0x4b, .flags = 0, .buf = initmsg, .len = 2}; + + if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) { + wprintk("could not access the I2C gate\n"); + return -EIO; + } + if (enable) + msg.buf = msg_enable; + else + msg.buf = msg_disable; + if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) { + wprintk("could not access the I2C gate\n"); + return -EIO; + } + msleep(20); + return 0; +} + +/* ================================================================== + * tda1004x based DVB-T cards, helper functions + */ + +static int philips_tda1004x_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct saa7134_dev *dev = fe->dvb->priv; + return request_firmware(fw, name, &dev->pci->dev); +} + +/* ------------------------------------------------------------------ + * these tuners are tu1216, td1316(a) + */ + +static int philips_tda6651_pll_set(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; + u8 tuner_buf[4]; + struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = tuner_buf,.len = + sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + /* determine charge pump */ + tuner_frequency = c->frequency + 36166000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) + cp = 3; + else if (tuner_frequency < 160000000) + cp = 5; + else if (tuner_frequency < 200000000) + cp = 6; + else if (tuner_frequency < 290000000) + cp = 3; + else if (tuner_frequency < 420000000) + cp = 5; + else if (tuner_frequency < 480000000) + cp = 6; + else if (tuner_frequency < 620000000) + cp = 3; + else if (tuner_frequency < 830000000) + cp = 5; + else if (tuner_frequency < 895000000) + cp = 7; + else + return -EINVAL; + + /* determine band */ + if (c->frequency < 49000000) + return -EINVAL; + else if (c->frequency < 161000000) + band = 1; + else if (c->frequency < 444000000) + band = 2; + else if (c->frequency < 861000000) + band = 4; + else + return -EINVAL; + + /* setup PLL filter */ + switch (c->bandwidth_hz) { + case 6000000: + filter = 0; + break; + + case 7000000: + filter = 0; + break; + + case 8000000: + filter = 1; + break; + + default: + return -EINVAL; + } + + /* calculate divisor + * ((36166000+((1000000/6)/2)) + Finput)/(1000000/6) + */ + tuner_frequency = (((c->frequency / 1000) * 6) + 217496) / 1000; + + /* setup tuner buffer */ + tuner_buf[0] = (tuner_frequency >> 8) & 0x7f; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xca; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &tuner_msg, 1) != 1) { + wprintk("could not write to tuner at addr: 0x%02x\n", + addr << 1); + return -EIO; + } + msleep(1); + return 0; +} + +static int philips_tu1216_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; + static u8 tu1216_init[] = { 0x0b, 0xf5, 0x85, 0xab }; + struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = tu1216_init,.len = sizeof(tu1216_init) }; + + /* setup PLL configuration */ + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + msleep(1); + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static struct tda1004x_config philips_tu1216_60_config = { + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config philips_tu1216_61_config = { + + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +/* ------------------------------------------------------------------ */ + +static int philips_td1316_tuner_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; + static u8 msg[] = { 0x0b, 0xf5, 0x86, 0xab }; + struct i2c_msg init_msg = {.addr = addr,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + /* setup PLL configuration */ + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &init_msg, 1) != 1) + return -EIO; + return 0; +} + +static int philips_td1316_tuner_set_params(struct dvb_frontend *fe) +{ + return philips_tda6651_pll_set(fe); +} + +static int philips_td1316_tuner_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; + static u8 msg[] = { 0x0b, 0xdc, 0x86, 0xa4 }; + struct i2c_msg analog_msg = {.addr = addr,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + /* switch the tuner to analog mode */ + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &analog_msg, 1) != 1) + return -EIO; + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int philips_europa_tuner_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + static u8 msg[] = { 0x00, 0x40}; + struct i2c_msg init_msg = {.addr = 0x43,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + + if (philips_td1316_tuner_init(fe)) + return -EIO; + msleep(1); + if (i2c_transfer(&dev->i2c_adap, &init_msg, 1) != 1) + return -EIO; + + return 0; +} + +static int philips_europa_tuner_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + + static u8 msg[] = { 0x00, 0x14 }; + struct i2c_msg analog_msg = {.addr = 0x43,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + if (philips_td1316_tuner_sleep(fe)) + return -EIO; + + /* switch the board to analog mode */ + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(&dev->i2c_adap, &analog_msg, 1); + return 0; +} + +static int philips_europa_demod_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + + if (dev->original_demod_sleep) + dev->original_demod_sleep(fe); + fe->ops.i2c_gate_ctrl(fe, 1); + return 0; +} + +static struct tda1004x_config philips_europa_config = { + + .demod_address = 0x8, + .invert = 0, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_IFO_AUTO_POS, + .if_freq = TDA10046_FREQ_052, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config medion_cardbus = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_IFO_AUTO_NEG, + .if_freq = TDA10046_FREQ_3613, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config technotrend_budget_t3000_config = { + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .tuner_address = 0x63, + .request_firmware = philips_tda1004x_request_firmware +}; + +/* ------------------------------------------------------------------ + * tda 1004x based cards with philips silicon tuner + */ + +static int tda8290_i2c_gate_ctrl( struct dvb_frontend* fe, int enable) +{ + struct tda1004x_state *state = fe->demodulator_priv; + + u8 addr = state->config->i2c_gate; + static u8 tda8290_close[] = { 0x21, 0xc0}; + static u8 tda8290_open[] = { 0x21, 0x80}; + struct i2c_msg tda8290_msg = {.addr = addr,.flags = 0, .len = 2}; + if (enable) { + tda8290_msg.buf = tda8290_close; + } else { + tda8290_msg.buf = tda8290_open; + } + if (i2c_transfer(state->i2c, &tda8290_msg, 1) != 1) { + struct saa7134_dev *dev = fe->dvb->priv; + wprintk("could not access tda8290 I2C gate\n"); + return -EIO; + } + msleep(20); + return 0; +} + +static int philips_tda827x_tuner_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + + switch (state->config->antenna_switch) { + case 0: break; + case 1: dprintk("setting GPIO21 to 0 (TV antenna?)\n"); + saa7134_set_gpio(dev, 21, 0); + break; + case 2: dprintk("setting GPIO21 to 1 (Radio antenna?)\n"); + saa7134_set_gpio(dev, 21, 1); + break; + } + return 0; +} + +static int philips_tda827x_tuner_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + + switch (state->config->antenna_switch) { + case 0: break; + case 1: dprintk("setting GPIO21 to 1 (Radio antenna?)\n"); + saa7134_set_gpio(dev, 21, 1); + break; + case 2: dprintk("setting GPIO21 to 0 (TV antenna?)\n"); + saa7134_set_gpio(dev, 21, 0); + break; + } + return 0; +} + +static int configure_tda827x_fe(struct saa7134_dev *dev, + struct tda1004x_config *cdec_conf, + struct tda827x_config *tuner_conf) +{ + struct videobuf_dvb_frontend *fe0; + + /* Get the first frontend */ + fe0 = videobuf_dvb_get_frontend(&dev->frontends, 1); + + fe0->dvb.frontend = dvb_attach(tda10046_attach, cdec_conf, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (cdec_conf->i2c_gate) + fe0->dvb.frontend->ops.i2c_gate_ctrl = tda8290_i2c_gate_ctrl; + if (dvb_attach(tda827x_attach, fe0->dvb.frontend, + cdec_conf->tuner_address, + &dev->i2c_adap, tuner_conf)) + return 0; + + wprintk("no tda827x tuner found at addr: %02x\n", + cdec_conf->tuner_address); + } + return -EINVAL; +} + +/* ------------------------------------------------------------------ */ + +static struct tda827x_config tda827x_cfg_0 = { + .init = philips_tda827x_tuner_init, + .sleep = philips_tda827x_tuner_sleep, + .config = 0, + .switch_addr = 0 +}; + +static struct tda827x_config tda827x_cfg_1 = { + .init = philips_tda827x_tuner_init, + .sleep = philips_tda827x_tuner_sleep, + .config = 1, + .switch_addr = 0x4b +}; + +static struct tda827x_config tda827x_cfg_2 = { + .init = philips_tda827x_tuner_init, + .sleep = philips_tda827x_tuner_sleep, + .config = 2, + .switch_addr = 0x4b +}; + +static struct tda827x_config tda827x_cfg_2_sw42 = { + .init = philips_tda827x_tuner_init, + .sleep = philips_tda827x_tuner_sleep, + .config = 2, + .switch_addr = 0x42 +}; + +/* ------------------------------------------------------------------ */ + +static struct tda1004x_config tda827x_lifeview_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config philips_tiger_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config cinergy_ht_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config cinergy_ht_pci_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config philips_tiger_s_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config pinnacle_pctv_310i_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config hauppauge_hvr_1110_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_p7131_dual_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 2, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config lifeview_trio_config = { + .demod_address = 0x09, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP00_I, + .if_freq = TDA10046_FREQ_045, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config tevion_dvbt220rf_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config md8800_dvbt_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_p7131_4871_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 2, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_p7131_hybrid_lna_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 2, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config kworld_dvb_t_210_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config avermedia_super_007_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x60, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config twinhan_dtv_dvb_3056_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x42, + .tuner_address = 0x61, + .antenna_switch = 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_tiger_3in1_config = { + .demod_address = 0x0b, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch = 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct tda1004x_config asus_ps3_100_config = { + .demod_address = 0x0b, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .antenna_switch = 1, + .request_firmware = philips_tda1004x_request_firmware +}; + +/* ------------------------------------------------------------------ + * special case: this card uses saa713x GPIO22 for the mode switch + */ + +static int ads_duo_tuner_init(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + philips_tda827x_tuner_init(fe); + /* route TDA8275a AGC input to the channel decoder */ + saa7134_set_gpio(dev, 22, 1); + return 0; +} + +static int ads_duo_tuner_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + /* route TDA8275a AGC input to the analog IF chip*/ + saa7134_set_gpio(dev, 22, 0); + philips_tda827x_tuner_sleep(fe); + return 0; +} + +static struct tda827x_config ads_duo_cfg = { + .init = ads_duo_tuner_init, + .sleep = ads_duo_tuner_sleep, + .config = 0 +}; + +static struct tda1004x_config ads_tech_duo_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP00_I, + .if_freq = TDA10046_FREQ_045, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware +}; + +static struct zl10353_config behold_h6_config = { + .demod_address = 0x1e>>1, + .no_tuner = 1, + .parallel_ts = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static struct xc5000_config behold_x7_tunerconfig = { + .i2c_address = 0xc2>>1, + .if_khz = 4560, + .radio_input = XC5000_RADIO_FM1, +}; + +static struct zl10353_config behold_x7_config = { + .demod_address = 0x1e>>1, + .if2 = 45600, + .no_tuner = 1, + .parallel_ts = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static struct zl10353_config videomate_t750_zl10353_config = { + .demod_address = 0x0f, + .no_tuner = 1, + .parallel_ts = 1, + .disable_i2c_gate_ctrl = 1, +}; + +static struct qt1010_config videomate_t750_qt1010_config = { + .i2c_address = 0x62 +}; + + +/* ================================================================== + * tda10086 based DVB-S cards, helper functions + */ + +static struct tda10086_config flydvbs = { + .demod_address = 0x0e, + .invert = 0, + .diseqc_tone = 0, + .xtal_freq = TDA10086_XTAL_16M, +}; + +static struct tda10086_config sd1878_4m = { + .demod_address = 0x0e, + .invert = 0, + .diseqc_tone = 0, + .xtal_freq = TDA10086_XTAL_4M, +}; + +/* ------------------------------------------------------------------ + * special case: lnb supply is connected to the gated i2c + */ + +static int md8800_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) +{ + int res = -EIO; + struct saa7134_dev *dev = fe->dvb->priv; + if (fe->ops.i2c_gate_ctrl) { + fe->ops.i2c_gate_ctrl(fe, 1); + if (dev->original_set_voltage) + res = dev->original_set_voltage(fe, voltage); + fe->ops.i2c_gate_ctrl(fe, 0); + } + return res; +}; + +static int md8800_set_high_voltage(struct dvb_frontend *fe, long arg) +{ + int res = -EIO; + struct saa7134_dev *dev = fe->dvb->priv; + if (fe->ops.i2c_gate_ctrl) { + fe->ops.i2c_gate_ctrl(fe, 1); + if (dev->original_set_high_voltage) + res = dev->original_set_high_voltage(fe, arg); + fe->ops.i2c_gate_ctrl(fe, 0); + } + return res; +}; + +static int md8800_set_voltage2(struct dvb_frontend *fe, fe_sec_voltage_t voltage) +{ + struct saa7134_dev *dev = fe->dvb->priv; + u8 wbuf[2] = { 0x1f, 00 }; + u8 rbuf; + struct i2c_msg msg[] = { { .addr = 0x08, .flags = 0, .buf = wbuf, .len = 1 }, + { .addr = 0x08, .flags = I2C_M_RD, .buf = &rbuf, .len = 1 } }; + + if (i2c_transfer(&dev->i2c_adap, msg, 2) != 2) + return -EIO; + /* NOTE: this assumes that gpo1 is used, it might be bit 5 (gpo2) */ + if (voltage == SEC_VOLTAGE_18) + wbuf[1] = rbuf | 0x10; + else + wbuf[1] = rbuf & 0xef; + msg[0].len = 2; + i2c_transfer(&dev->i2c_adap, msg, 1); + return 0; +} + +static int md8800_set_high_voltage2(struct dvb_frontend *fe, long arg) +{ + struct saa7134_dev *dev = fe->dvb->priv; + wprintk("%s: sorry can't set high LNB supply voltage from here\n", __func__); + return -EIO; +} + +/* ================================================================== + * nxt200x based ATSC cards, helper functions + */ + +static struct nxt200x_config avertvhda180 = { + .demod_address = 0x0a, +}; + +static struct nxt200x_config kworldatsc110 = { + .demod_address = 0x0a, +}; + +/* ------------------------------------------------------------------ */ + +static struct mt312_config avertv_a700_mt312 = { + .demod_address = 0x0e, + .voltage_inverted = 1, +}; + +static struct zl10036_config avertv_a700_tuner = { + .tuner_address = 0x60, +}; + +static struct mt312_config zl10313_compro_s350_config = { + .demod_address = 0x0e, +}; + +static struct lgdt3305_config hcw_lgdt3305_config = { + .i2c_addr = 0x0e, + .mpeg_mode = LGDT3305_MPEG_SERIAL, + .tpclk_edge = LGDT3305_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3305_TP_VALID_HIGH, + .deny_i2c_rptr = 1, + .spectral_inversion = 1, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, +}; + +static struct tda10048_config hcw_tda10048_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_SERIAL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3500, + .dtv8_if_freq_khz = TDA10048_IF_4000, + .clk_freq_khz = TDA10048_CLK_16000, + .disable_gate_access = 1, +}; + +static struct tda18271_std_map hauppauge_tda18271_std_map = { + .atsc_6 = { .if_freq = 3250, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x58, }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x58, }, +}; + +static struct tda18271_config hcw_tda18271_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .config = 3, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda829x_config tda829x_no_probe = { + .probe_tuner = TDA829X_DONT_PROBE, +}; + +static struct tda10048_config zolid_tda10048_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_PARALLEL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3500, + .dtv8_if_freq_khz = TDA10048_IF_4000, + .clk_freq_khz = TDA10048_CLK_16000, + .disable_gate_access = 1, +}; + +static struct tda18271_config zolid_tda18271_config = { + .gate = TDA18271_GATE_ANALOG, +}; + +static struct tda10048_config dtv1000s_tda10048_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_PARALLEL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_200, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3800, + .dtv8_if_freq_khz = TDA10048_IF_4300, + .clk_freq_khz = TDA10048_CLK_16000, + .disable_gate_access = 1, +}; + +static struct tda18271_std_map dtv1000s_tda18271_std_map = { + .dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_7 = { .if_freq = 3800, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_8 = { .if_freq = 4300, .agc_mode = 3, .std = 6, + .if_lvl = 1, .rfagc_top = 0x37, }, +}; + +static struct tda18271_config dtv1000s_tda18271_config = { + .std_map = &dtv1000s_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, +}; + +static struct lgs8gxx_config prohdtv_pro2_lgs8g75_config = { + .prod = LGS8GXX_PROD_LGS8G75, + .demod_address = 0x1d, + .serial_ts = 0, + .ts_clk_pol = 1, + .ts_clk_gated = 0, + .if_clk_freq = 30400, /* 30.4 MHz */ + .if_freq = 4000, /* 4.00 MHz */ + .if_neg_center = 0, + .ext_adc = 0, + .adc_signed = 1, + .adc_vpp = 3, /* 2.0 Vpp */ + .if_neg_edge = 1, +}; + +static struct tda18271_config prohdtv_pro2_tda18271_config = { + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static struct tda18271_std_map kworld_tda18271_std_map = { + .atsc_6 = { .if_freq = 3250, .agc_mode = 3, .std = 3, + .if_lvl = 6, .rfagc_top = 0x37 }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0, + .if_lvl = 6, .rfagc_top = 0x37 }, +}; + +static struct tda18271_config kworld_pc150u_tda18271_config = { + .std_map = &kworld_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, + .config = 3, /* Use tuner callback for AGC */ + .rf_cal_on_startup = 1 +}; + +static struct s5h1411_config kworld_s5h1411_config = { + .output_mode = S5H1411_PARALLEL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .qam_if = S5H1411_IF_4000, + .vsb_if = S5H1411_IF_3250, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = + S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + + +/* ================================================================== + * Core code + */ + +static int dvb_init(struct saa7134_dev *dev) +{ + int ret; + int attach_xc3028 = 0; + struct videobuf_dvb_frontend *fe0; + + /* FIXME: add support for multi-frontend */ + mutex_init(&dev->frontends.lock); + INIT_LIST_HEAD(&dev->frontends.felist); + + printk(KERN_INFO "%s() allocating 1 frontend\n", __func__); + fe0 = videobuf_dvb_alloc_frontend(&dev->frontends, 1); + if (!fe0) { + printk(KERN_ERR "%s() failed to alloc\n", __func__); + return -ENOMEM; + } + + /* init struct videobuf_dvb */ + dev->ts.nr_bufs = 32; + dev->ts.nr_packets = 32*4; + fe0->dvb.name = dev->name; + videobuf_queue_sg_init(&fe0->dvb.dvbq, &saa7134_ts_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_ALTERNATE, + sizeof(struct saa7134_buf), + dev, NULL); + + switch (dev->board) { + case SAA7134_BOARD_PINNACLE_300I_DVBT_PAL: + dprintk("pinnacle 300i dvb setup\n"); + fe0->dvb.frontend = dvb_attach(mt352_attach, &pinnacle_300i, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.set_params = mt352_pinnacle_tuner_set_params; + } + break; + case SAA7134_BOARD_AVERMEDIA_777: + case SAA7134_BOARD_AVERMEDIA_A16AR: + dprintk("avertv 777 dvb setup\n"); + fe0->dvb.frontend = dvb_attach(mt352_attach, &avermedia_777, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x61, + TUNER_PHILIPS_TD1316); + } + break; + case SAA7134_BOARD_AVERMEDIA_A16D: + dprintk("AverMedia A16D dvb setup\n"); + fe0->dvb.frontend = dvb_attach(mt352_attach, + &avermedia_xc3028_mt352_dev, + &dev->i2c_adap); + attach_xc3028 = 1; + break; + case SAA7134_BOARD_MD7134: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &medion_cardbus, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, medion_cardbus.tuner_address, + TUNER_PHILIPS_FMD1216ME_MK3); + } + break; + case SAA7134_BOARD_PHILIPS_TOUGH: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &philips_tu1216_60_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.init = philips_tu1216_init; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_tda6651_pll_set; + } + break; + case SAA7134_BOARD_FLYDVBTDUO: + case SAA7134_BOARD_FLYDVBT_DUO_CARDBUS: + if (configure_tda827x_fe(dev, &tda827x_lifeview_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_PHILIPS_EUROPA: + case SAA7134_BOARD_VIDEOMATE_DVBT_300: + case SAA7134_BOARD_ASUS_EUROPA_HYBRID: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &philips_europa_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dev->original_demod_sleep = fe0->dvb.frontend->ops.sleep; + fe0->dvb.frontend->ops.sleep = philips_europa_demod_sleep; + fe0->dvb.frontend->ops.tuner_ops.init = philips_europa_tuner_init; + fe0->dvb.frontend->ops.tuner_ops.sleep = philips_europa_tuner_sleep; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_td1316_tuner_set_params; + } + break; + case SAA7134_BOARD_TECHNOTREND_BUDGET_T3000: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &technotrend_budget_t3000_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dev->original_demod_sleep = fe0->dvb.frontend->ops.sleep; + fe0->dvb.frontend->ops.sleep = philips_europa_demod_sleep; + fe0->dvb.frontend->ops.tuner_ops.init = philips_europa_tuner_init; + fe0->dvb.frontend->ops.tuner_ops.sleep = philips_europa_tuner_sleep; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_td1316_tuner_set_params; + } + break; + case SAA7134_BOARD_VIDEOMATE_DVBT_200: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &philips_tu1216_61_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.init = philips_tu1216_init; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_tda6651_pll_set; + } + break; + case SAA7134_BOARD_KWORLD_DVBT_210: + if (configure_tda827x_fe(dev, &kworld_dvb_t_210_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1120: + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &hcw_tda10048_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &hcw_tda18271_config); + } + break; + case SAA7134_BOARD_PHILIPS_TIGER: + if (configure_tda827x_fe(dev, &philips_tiger_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_PINNACLE_PCTV_310i: + if (configure_tda827x_fe(dev, &pinnacle_pctv_310i_config, + &tda827x_cfg_1) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1110: + if (configure_tda827x_fe(dev, &hauppauge_hvr_1110_config, + &tda827x_cfg_1) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1150: + fe0->dvb.frontend = dvb_attach(lgdt3305_attach, + &hcw_lgdt3305_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &hcw_tda18271_config); + } + break; + case SAA7134_BOARD_ASUSTeK_P7131_DUAL: + if (configure_tda827x_fe(dev, &asus_p7131_dual_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_FLYDVBT_LR301: + if (configure_tda827x_fe(dev, &tda827x_lifeview_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_FLYDVB_TRIO: + if (!use_frontend) { /* terrestrial */ + if (configure_tda827x_fe(dev, &lifeview_trio_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + } else { /* satellite */ + fe0->dvb.frontend = dvb_attach(tda10086_attach, &flydvbs, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, fe0->dvb.frontend, 0x63, + &dev->i2c_adap, 0) == NULL) { + wprintk("%s: Lifeview Trio, No tda826x found!\n", __func__); + goto detach_frontend; + } + if (dvb_attach(isl6421_attach, fe0->dvb.frontend, &dev->i2c_adap, + 0x08, 0, 0) == NULL) { + wprintk("%s: Lifeview Trio, No ISL6421 found!\n", __func__); + goto detach_frontend; + } + } + } + break; + case SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331: + case SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &ads_tech_duo_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda827x_attach,fe0->dvb.frontend, + ads_tech_duo_config.tuner_address, &dev->i2c_adap, + &ads_duo_cfg) == NULL) { + wprintk("no tda827x tuner found at addr: %02x\n", + ads_tech_duo_config.tuner_address); + goto detach_frontend; + } + } else + wprintk("failed to attach tda10046\n"); + break; + case SAA7134_BOARD_TEVION_DVBT_220RF: + if (configure_tda827x_fe(dev, &tevion_dvbt220rf_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_MEDION_MD8800_QUADRO: + if (!use_frontend) { /* terrestrial */ + if (configure_tda827x_fe(dev, &md8800_dvbt_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + } else { /* satellite */ + fe0->dvb.frontend = dvb_attach(tda10086_attach, + &flydvbs, &dev->i2c_adap); + if (fe0->dvb.frontend) { + struct dvb_frontend *fe = fe0->dvb.frontend; + u8 dev_id = dev->eedata[2]; + u8 data = 0xc4; + struct i2c_msg msg = {.addr = 0x08, .flags = 0, .len = 1}; + + if (dvb_attach(tda826x_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, 0) == NULL) { + wprintk("%s: Medion Quadro, no tda826x " + "found !\n", __func__); + goto detach_frontend; + } + if (dev_id != 0x08) { + /* we need to open the i2c gate (we know it exists) */ + fe->ops.i2c_gate_ctrl(fe, 1); + if (dvb_attach(isl6405_attach, fe, + &dev->i2c_adap, 0x08, 0, 0) == NULL) { + wprintk("%s: Medion Quadro, no ISL6405 " + "found !\n", __func__); + goto detach_frontend; + } + if (dev_id == 0x07) { + /* fire up the 2nd section of the LNB supply since + we can't do this from the other section */ + msg.buf = &data; + i2c_transfer(&dev->i2c_adap, &msg, 1); + } + fe->ops.i2c_gate_ctrl(fe, 0); + dev->original_set_voltage = fe->ops.set_voltage; + fe->ops.set_voltage = md8800_set_voltage; + dev->original_set_high_voltage = fe->ops.enable_high_lnb_voltage; + fe->ops.enable_high_lnb_voltage = md8800_set_high_voltage; + } else { + fe->ops.set_voltage = md8800_set_voltage2; + fe->ops.enable_high_lnb_voltage = md8800_set_high_voltage2; + } + } + } + break; + case SAA7134_BOARD_AVERMEDIA_AVERTVHD_A180: + fe0->dvb.frontend = dvb_attach(nxt200x_attach, &avertvhda180, + &dev->i2c_adap); + if (fe0->dvb.frontend) + dvb_attach(dvb_pll_attach, fe0->dvb.frontend, 0x61, + NULL, DVB_PLL_TDHU2); + break; + case SAA7134_BOARD_ADS_INSTANT_HDTV_PCI: + case SAA7134_BOARD_KWORLD_ATSC110: + fe0->dvb.frontend = dvb_attach(nxt200x_attach, &kworldatsc110, + &dev->i2c_adap); + if (fe0->dvb.frontend) + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x61, + TUNER_PHILIPS_TUV1236D); + break; + case SAA7134_BOARD_KWORLD_PC150U: + saa7134_set_gpio(dev, 18, 1); /* Switch to digital mode */ + saa7134_tuner_callback(dev, 0, + TDA18271_CALLBACK_CMD_AGC_ENABLE, 1); + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &kworld_s5h1411_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &kworld_pc150u_tda18271_config); + } + break; + case SAA7134_BOARD_FLYDVBS_LR300: + fe0->dvb.frontend = dvb_attach(tda10086_attach, &flydvbs, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, fe0->dvb.frontend, 0x60, + &dev->i2c_adap, 0) == NULL) { + wprintk("%s: No tda826x found!\n", __func__); + goto detach_frontend; + } + if (dvb_attach(isl6421_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x08, 0, 0) == NULL) { + wprintk("%s: No ISL6421 found!\n", __func__); + goto detach_frontend; + } + } + break; + case SAA7134_BOARD_ASUS_EUROPA2_HYBRID: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &medion_cardbus, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dev->original_demod_sleep = fe0->dvb.frontend->ops.sleep; + fe0->dvb.frontend->ops.sleep = philips_europa_demod_sleep; + + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, medion_cardbus.tuner_address, + TUNER_PHILIPS_FMD1216ME_MK3); + } + break; + case SAA7134_BOARD_VIDEOMATE_DVBT_200A: + fe0->dvb.frontend = dvb_attach(tda10046_attach, + &philips_europa_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + fe0->dvb.frontend->ops.tuner_ops.init = philips_td1316_tuner_init; + fe0->dvb.frontend->ops.tuner_ops.set_params = philips_td1316_tuner_set_params; + } + break; + case SAA7134_BOARD_CINERGY_HT_PCMCIA: + if (configure_tda827x_fe(dev, &cinergy_ht_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_CINERGY_HT_PCI: + if (configure_tda827x_fe(dev, &cinergy_ht_pci_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_PHILIPS_TIGER_S: + if (configure_tda827x_fe(dev, &philips_tiger_s_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_ASUS_P7131_4871: + if (configure_tda827x_fe(dev, &asus_p7131_4871_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: + if (configure_tda827x_fe(dev, &asus_p7131_hybrid_lna_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_AVERMEDIA_SUPER_007: + if (configure_tda827x_fe(dev, &avermedia_super_007_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_TWINHAN_DTV_DVB_3056: + if (configure_tda827x_fe(dev, &twinhan_dtv_dvb_3056_config, + &tda827x_cfg_2_sw42) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_PHILIPS_SNAKE: + fe0->dvb.frontend = dvb_attach(tda10086_attach, &flydvbs, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, fe0->dvb.frontend, 0x60, + &dev->i2c_adap, 0) == NULL) { + wprintk("%s: No tda826x found!\n", __func__); + goto detach_frontend; + } + if (dvb_attach(lnbp21_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0, 0) == NULL) { + wprintk("%s: No lnbp21 found!\n", __func__); + goto detach_frontend; + } + } + break; + case SAA7134_BOARD_CREATIX_CTX953: + if (configure_tda827x_fe(dev, &md8800_dvbt_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_MSI_TVANYWHERE_AD11: + if (configure_tda827x_fe(dev, &philips_tiger_s_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_AVERMEDIA_CARDBUS_506: + dprintk("AverMedia E506R dvb setup\n"); + saa7134_set_gpio(dev, 25, 0); + msleep(10); + saa7134_set_gpio(dev, 25, 1); + fe0->dvb.frontend = dvb_attach(mt352_attach, + &avermedia_xc3028_mt352_dev, + &dev->i2c_adap); + attach_xc3028 = 1; + break; + case SAA7134_BOARD_MD7134_BRIDGE_2: + fe0->dvb.frontend = dvb_attach(tda10086_attach, + &sd1878_4m, &dev->i2c_adap); + if (fe0->dvb.frontend) { + struct dvb_frontend *fe; + if (dvb_attach(dvb_pll_attach, fe0->dvb.frontend, 0x60, + &dev->i2c_adap, DVB_PLL_PHILIPS_SD1878_TDA8261) == NULL) { + wprintk("%s: MD7134 DVB-S, no SD1878 " + "found !\n", __func__); + goto detach_frontend; + } + /* we need to open the i2c gate (we know it exists) */ + fe = fe0->dvb.frontend; + fe->ops.i2c_gate_ctrl(fe, 1); + if (dvb_attach(isl6405_attach, fe, + &dev->i2c_adap, 0x08, 0, 0) == NULL) { + wprintk("%s: MD7134 DVB-S, no ISL6405 " + "found !\n", __func__); + goto detach_frontend; + } + fe->ops.i2c_gate_ctrl(fe, 0); + dev->original_set_voltage = fe->ops.set_voltage; + fe->ops.set_voltage = md8800_set_voltage; + dev->original_set_high_voltage = fe->ops.enable_high_lnb_voltage; + fe->ops.enable_high_lnb_voltage = md8800_set_high_voltage; + } + break; + case SAA7134_BOARD_AVERMEDIA_M103: + saa7134_set_gpio(dev, 25, 0); + msleep(10); + saa7134_set_gpio(dev, 25, 1); + fe0->dvb.frontend = dvb_attach(mt352_attach, + &avermedia_xc3028_mt352_dev, + &dev->i2c_adap); + attach_xc3028 = 1; + break; + case SAA7134_BOARD_ASUSTeK_TIGER_3IN1: + if (!use_frontend) { /* terrestrial */ + if (configure_tda827x_fe(dev, &asus_tiger_3in1_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + } else { /* satellite */ + fe0->dvb.frontend = dvb_attach(tda10086_attach, + &flydvbs, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, + fe0->dvb.frontend, 0x60, + &dev->i2c_adap, 0) == NULL) { + wprintk("%s: Asus Tiger 3in1, no " + "tda826x found!\n", __func__); + goto detach_frontend; + } + if (dvb_attach(lnbp21_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0, 0) == NULL) { + wprintk("%s: Asus Tiger 3in1, no lnbp21" + " found!\n", __func__); + goto detach_frontend; + } + } + } + break; + case SAA7134_BOARD_ASUSTeK_PS3_100: + if (!use_frontend) { /* terrestrial */ + if (configure_tda827x_fe(dev, &asus_ps3_100_config, + &tda827x_cfg_2) < 0) + goto detach_frontend; + } else { /* satellite */ + fe0->dvb.frontend = dvb_attach(tda10086_attach, + &flydvbs, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(tda826x_attach, + fe0->dvb.frontend, 0x60, + &dev->i2c_adap, 0) == NULL) { + wprintk("%s: Asus My Cinema PS3-100, no " + "tda826x found!\n", __func__); + goto detach_frontend; + } + if (dvb_attach(lnbp21_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0, 0) == NULL) { + wprintk("%s: Asus My Cinema PS3-100, no lnbp21" + " found!\n", __func__); + goto detach_frontend; + } + } + } + break; + case SAA7134_BOARD_ASUSTeK_TIGER: + if (configure_tda827x_fe(dev, &philips_tiger_config, + &tda827x_cfg_0) < 0) + goto detach_frontend; + break; + case SAA7134_BOARD_BEHOLD_H6: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &behold_h6_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(simple_tuner_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x61, + TUNER_PHILIPS_FMD1216MEX_MK3); + } + break; + case SAA7134_BOARD_BEHOLD_X7: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &behold_x7_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &dev->i2c_adap, &behold_x7_tunerconfig); + } + break; + case SAA7134_BOARD_BEHOLD_H7: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &behold_x7_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(xc5000_attach, fe0->dvb.frontend, + &dev->i2c_adap, &behold_x7_tunerconfig); + } + break; + case SAA7134_BOARD_AVERMEDIA_A700_PRO: + case SAA7134_BOARD_AVERMEDIA_A700_HYBRID: + /* Zarlink ZL10313 */ + fe0->dvb.frontend = dvb_attach(mt312_attach, + &avertv_a700_mt312, &dev->i2c_adap); + if (fe0->dvb.frontend) { + if (dvb_attach(zl10036_attach, fe0->dvb.frontend, + &avertv_a700_tuner, &dev->i2c_adap) == NULL) { + wprintk("%s: No zl10036 found!\n", + __func__); + } + } + break; + case SAA7134_BOARD_VIDEOMATE_S350: + fe0->dvb.frontend = dvb_attach(mt312_attach, + &zl10313_compro_s350_config, &dev->i2c_adap); + if (fe0->dvb.frontend) + if (dvb_attach(zl10039_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap) == NULL) + wprintk("%s: No zl10039 found!\n", + __func__); + + break; + case SAA7134_BOARD_VIDEOMATE_T750: + fe0->dvb.frontend = dvb_attach(zl10353_attach, + &videomate_t750_zl10353_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + if (dvb_attach(qt1010_attach, + fe0->dvb.frontend, + &dev->i2c_adap, + &videomate_t750_qt1010_config) == NULL) + wprintk("error attaching QT1010\n"); + } + break; + case SAA7134_BOARD_ZOLID_HYBRID_PCI: + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &zolid_tda10048_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &zolid_tda18271_config); + } + break; + case SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S: + fe0->dvb.frontend = dvb_attach(tda10048_attach, + &dtv1000s_tda10048_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &dtv1000s_tda18271_config); + } + break; + case SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG: + /* Switch to digital mode */ + saa7134_tuner_callback(dev, 0, + TDA18271_CALLBACK_CMD_AGC_ENABLE, 1); + fe0->dvb.frontend = dvb_attach(mb86a20s_attach, + &kworld_mb86a20s_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + fe0->dvb.frontend->ops.i2c_gate_ctrl = kworld_sbtvd_gate_ctrl; + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &kworld_tda18271_config); + } + + /* mb86a20s need to use the I2C gateway */ + break; + case SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2: + fe0->dvb.frontend = dvb_attach(lgs8gxx_attach, + &prohdtv_pro2_lgs8g75_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &prohdtv_pro2_tda18271_config); + } + break; + default: + wprintk("Huh? unknown DVB card?\n"); + break; + } + + if (attach_xc3028) { + struct dvb_frontend *fe; + struct xc2028_config cfg = { + .i2c_adap = &dev->i2c_adap, + .i2c_addr = 0x61, + }; + + if (!fe0->dvb.frontend) + goto detach_frontend; + + fe = dvb_attach(xc2028_attach, fe0->dvb.frontend, &cfg); + if (!fe) { + printk(KERN_ERR "%s/2: xc3028 attach failed\n", + dev->name); + goto detach_frontend; + } + } + + if (NULL == fe0->dvb.frontend) { + printk(KERN_ERR "%s/dvb: frontend initialization failed\n", dev->name); + goto detach_frontend; + } + /* define general-purpose callback pointer */ + fe0->dvb.frontend->callback = saa7134_tuner_callback; + + /* register everything else */ + ret = videobuf_dvb_register_bus(&dev->frontends, THIS_MODULE, dev, + &dev->pci->dev, adapter_nr, 0); + + /* this sequence is necessary to make the tda1004x load its firmware + * and to enter analog mode of hybrid boards + */ + if (!ret) { + if (fe0->dvb.frontend->ops.init) + fe0->dvb.frontend->ops.init(fe0->dvb.frontend); + if (fe0->dvb.frontend->ops.sleep) + fe0->dvb.frontend->ops.sleep(fe0->dvb.frontend); + if (fe0->dvb.frontend->ops.tuner_ops.sleep) + fe0->dvb.frontend->ops.tuner_ops.sleep(fe0->dvb.frontend); + } + return ret; + +detach_frontend: + videobuf_dvb_dealloc_frontends(&dev->frontends); + return -EINVAL; +} + +static int dvb_fini(struct saa7134_dev *dev) +{ + struct videobuf_dvb_frontend *fe0; + + /* Get the first frontend */ + fe0 = videobuf_dvb_get_frontend(&dev->frontends, 1); + if (!fe0) + return -EINVAL; + + /* FIXME: I suspect that this code is bogus, since the entry for + Pinnacle 300I DVB-T PAL already defines the proper init to allow + the detection of mt2032 (TDA9887_PORT2_INACTIVE) + */ + if (dev->board == SAA7134_BOARD_PINNACLE_300I_DVBT_PAL) { + struct v4l2_priv_tun_config tda9887_cfg; + static int on = TDA9887_PRESENT | TDA9887_PORT2_INACTIVE; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &on; + + /* otherwise we don't detect the tuner on next insmod */ + saa_call_all(dev, tuner, s_config, &tda9887_cfg); + } else if (dev->board == SAA7134_BOARD_MEDION_MD8800_QUADRO) { + if ((dev->eedata[2] == 0x07) && use_frontend) { + /* turn off the 2nd lnb supply */ + u8 data = 0x80; + struct i2c_msg msg = {.addr = 0x08, .buf = &data, .flags = 0, .len = 1}; + struct dvb_frontend *fe; + fe = fe0->dvb.frontend; + if (fe->ops.i2c_gate_ctrl) { + fe->ops.i2c_gate_ctrl(fe, 1); + i2c_transfer(&dev->i2c_adap, &msg, 1); + fe->ops.i2c_gate_ctrl(fe, 0); + } + } + } + videobuf_dvb_unregister_bus(&dev->frontends); + return 0; +} + +static struct saa7134_mpeg_ops dvb_ops = { + .type = SAA7134_MPEG_DVB, + .init = dvb_init, + .fini = dvb_fini, +}; + +static int __init dvb_register(void) +{ + return saa7134_ts_register(&dvb_ops); +} + +static void __exit dvb_unregister(void) +{ + saa7134_ts_unregister(&dvb_ops); +} + +module_init(dvb_register); +module_exit(dvb_unregister); + +/* ------------------------------------------------------------------ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-empress.c b/drivers/media/pci/saa7134/saa7134-empress.c similarity index 100% rename from drivers/media/video/saa7134/saa7134-empress.c rename to drivers/media/pci/saa7134/saa7134-empress.c diff --git a/drivers/media/video/saa7134/saa7134-i2c.c b/drivers/media/pci/saa7134/saa7134-i2c.c similarity index 100% rename from drivers/media/video/saa7134/saa7134-i2c.c rename to drivers/media/pci/saa7134/saa7134-i2c.c diff --git a/drivers/media/pci/saa7134/saa7134-input.c b/drivers/media/pci/saa7134/saa7134-input.c new file mode 100644 index 000000000000..0f78f5e537e2 --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-input.c @@ -0,0 +1,1041 @@ +/* + * + * handle saa7134 IR remotes via linux kernel input layer. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include "saa7134-reg.h" +#include "saa7134.h" + +#define MODULE_NAME "saa7134" + +static unsigned int disable_ir; +module_param(disable_ir, int, 0444); +MODULE_PARM_DESC(disable_ir,"disable infrared remote support"); + +static unsigned int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug,"enable debug messages [IR]"); + +static int pinnacle_remote; +module_param(pinnacle_remote, int, 0644); /* Choose Pinnacle PCTV remote */ +MODULE_PARM_DESC(pinnacle_remote, "Specify Pinnacle PCTV remote: 0=coloured, 1=grey (defaults to 0)"); + +#define dprintk(fmt, arg...) if (ir_debug) \ + printk(KERN_DEBUG "%s/ir: " fmt, dev->name , ## arg) +#define i2cdprintk(fmt, arg...) if (ir_debug) \ + printk(KERN_DEBUG "%s/ir: " fmt, ir->name , ## arg) + +/* Helper function for raw decoding at GPIO16 or GPIO18 */ +static int saa7134_raw_decode_irq(struct saa7134_dev *dev); + +/* -------------------- GPIO generic keycode builder -------------------- */ + +static int build_key(struct saa7134_dev *dev) +{ + struct saa7134_card_ir *ir = dev->remote; + u32 gpio, data; + + /* here comes the additional handshake steps for some cards */ + switch (dev->board) { + case SAA7134_BOARD_GOTVIEW_7135: + saa_setb(SAA7134_GPIO_GPSTATUS1, 0x80); + saa_clearb(SAA7134_GPIO_GPSTATUS1, 0x80); + break; + } + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_clearb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + if (ir->polling) { + if (ir->last_gpio == gpio) + return 0; + ir->last_gpio = gpio; + } + + data = ir_extract_bits(gpio, ir->mask_keycode); + dprintk("build_key gpio=0x%x mask=0x%x data=%d\n", + gpio, ir->mask_keycode, data); + + switch (dev->board) { + case SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG: + if (data == ir->mask_keycode) + rc_keyup(ir->dev); + else + rc_keydown_notimeout(ir->dev, data, 0); + return 0; + } + + if (ir->polling) { + if ((ir->mask_keydown && (0 != (gpio & ir->mask_keydown))) || + (ir->mask_keyup && (0 == (gpio & ir->mask_keyup)))) { + rc_keydown_notimeout(ir->dev, data, 0); + } else { + rc_keyup(ir->dev); + } + } + else { /* IRQ driven mode - handle key press and release in one go */ + if ((ir->mask_keydown && (0 != (gpio & ir->mask_keydown))) || + (ir->mask_keyup && (0 == (gpio & ir->mask_keyup)))) { + rc_keydown_notimeout(ir->dev, data, 0); + rc_keyup(ir->dev); + } + } + + return 0; +} + +/* --------------------- Chip specific I2C key builders ----------------- */ + +static int get_key_flydvb_trio(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + int gpio; + int attempt = 0; + unsigned char b; + + /* We need this to access GPI Used by the saa_readl macro. */ + struct saa7134_dev *dev = ir->c->adapter->algo_data; + + if (dev == NULL) { + i2cdprintk("get_key_flydvb_trio: " + "ir->c->adapter->algo_data is NULL!\n"); + return -EIO; + } + + /* rising SAA7134_GPIGPRESCAN reads the status */ + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + if (0x40000 & ~gpio) + return 0; /* No button press */ + + /* No button press - only before first key pressed */ + if (b == 0xFF) + return 0; + + /* poll IR chip */ + /* weak up the IR chip */ + b = 0; + + while (1 != i2c_master_send(ir->c, &b, 1)) { + if ((attempt++) < 10) { + /* + * wait a bit for next attempt - + * I don't know how make it better + */ + msleep(10); + continue; + } + i2cdprintk("send wake up byte to pic16C505 (IR chip)" + "failed %dx\n", attempt); + return -EIO; + } + if (1 != i2c_master_recv(ir->c, &b, 1)) { + i2cdprintk("read error\n"); + return -EIO; + } + + *ir_key = b; + *ir_raw = b; + return 1; +} + +static int get_key_msi_tvanywhere_plus(struct IR_i2c *ir, u32 *ir_key, + u32 *ir_raw) +{ + unsigned char b; + int gpio; + + /* is needed to access GPIO. Used by the saa_readl macro. */ + struct saa7134_dev *dev = ir->c->adapter->algo_data; + if (dev == NULL) { + i2cdprintk("get_key_msi_tvanywhere_plus: " + "ir->c->adapter->algo_data is NULL!\n"); + return -EIO; + } + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + /* GPIO&0x40 is pulsed low when a button is pressed. Don't do + I2C receive if gpio&0x40 is not low. */ + + if (gpio & 0x40) + return 0; /* No button press */ + + /* GPIO says there is a button press. Get it. */ + + if (1 != i2c_master_recv(ir->c, &b, 1)) { + i2cdprintk("read error\n"); + return -EIO; + } + + /* No button press */ + + if (b == 0xff) + return 0; + + /* Button pressed */ + + dprintk("get_key_msi_tvanywhere_plus: Key = 0x%02X\n", b); + *ir_key = b; + *ir_raw = b; + return 1; +} + +/* copied and modified from get_key_msi_tvanywhere_plus() */ +static int get_key_kworld_pc150u(struct IR_i2c *ir, u32 *ir_key, + u32 *ir_raw) +{ + unsigned char b; + unsigned int gpio; + + /* is needed to access GPIO. Used by the saa_readl macro. */ + struct saa7134_dev *dev = ir->c->adapter->algo_data; + if (dev == NULL) { + i2cdprintk("get_key_kworld_pc150u: " + "ir->c->adapter->algo_data is NULL!\n"); + return -EIO; + } + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + /* GPIO&0x100 is pulsed low when a button is pressed. Don't do + I2C receive if gpio&0x100 is not low. */ + + if (gpio & 0x100) + return 0; /* No button press */ + + /* GPIO says there is a button press. Get it. */ + + if (1 != i2c_master_recv(ir->c, &b, 1)) { + i2cdprintk("read error\n"); + return -EIO; + } + + /* No button press */ + + if (b == 0xff) + return 0; + + /* Button pressed */ + + dprintk("get_key_kworld_pc150u: Key = 0x%02X\n", b); + *ir_key = b; + *ir_raw = b; + return 1; +} + +static int get_key_purpletv(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b; + + /* poll IR chip */ + if (1 != i2c_master_recv(ir->c, &b, 1)) { + i2cdprintk("read error\n"); + return -EIO; + } + + /* no button press */ + if (b==0) + return 0; + + /* repeating */ + if (b & 0x80) + return 1; + + *ir_key = b; + *ir_raw = b; + return 1; +} + +static int get_key_hvr1110(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char buf[5]; + + /* poll IR chip */ + if (5 != i2c_master_recv(ir->c, buf, 5)) + return -EIO; + + /* Check if some key were pressed */ + if (!(buf[0] & 0x80)) + return 0; + + /* + * buf[3] & 0x80 is always high. + * buf[3] & 0x40 is a parity bit. A repeat event is marked + * by preserving it into two separate readings + * buf[4] bits 0 and 1, and buf[1] and buf[2] are always + * zero. + */ + *ir_key = 0x1fff & ((buf[3] << 8) | (buf[4] >> 2)); + *ir_raw = *ir_key; + return 1; +} + + +static int get_key_beholdm6xx(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char data[12]; + u32 gpio; + + struct saa7134_dev *dev = ir->c->adapter->algo_data; + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + if (0x400000 & ~gpio) + return 0; /* No button press */ + + ir->c->addr = 0x5a >> 1; + + if (12 != i2c_master_recv(ir->c, data, 12)) { + i2cdprintk("read error\n"); + return -EIO; + } + + if (data[9] != (unsigned char)(~data[8])) + return 0; + + *ir_raw = ((data[10] << 16) | (data[11] << 8) | (data[9] << 0)); + *ir_key = *ir_raw; + + return 1; +} + +/* Common (grey or coloured) pinnacle PCTV remote handling + * + */ +static int get_key_pinnacle(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw, + int parity_offset, int marker, int code_modulo) +{ + unsigned char b[4]; + unsigned int start = 0,parity = 0,code = 0; + + /* poll IR chip */ + if (4 != i2c_master_recv(ir->c, b, 4)) { + i2cdprintk("read error\n"); + return -EIO; + } + + for (start = 0; start < ARRAY_SIZE(b); start++) { + if (b[start] == marker) { + code=b[(start+parity_offset + 1) % 4]; + parity=b[(start+parity_offset) % 4]; + } + } + + /* Empty Request */ + if (parity == 0) + return 0; + + /* Repeating... */ + if (ir->old == parity) + return 0; + + ir->old = parity; + + /* drop special codes when a key is held down a long time for the grey controller + In this case, the second bit of the code is asserted */ + if (marker == 0xfe && (code & 0x40)) + return 0; + + code %= code_modulo; + + *ir_raw = code; + *ir_key = code; + + i2cdprintk("Pinnacle PCTV key %02x\n", code); + + return 1; +} + +/* The grey pinnacle PCTV remote + * + * There are one issue with this remote: + * - I2c packet does not change when the same key is pressed quickly. The workaround + * is to hold down each key for about half a second, so that another code is generated + * in the i2c packet, and the function can distinguish key presses. + * + * Sylvain Pasche + */ +static int get_key_pinnacle_grey(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + + return get_key_pinnacle(ir, ir_key, ir_raw, 1, 0xfe, 0xff); +} + + +/* The new pinnacle PCTV remote (with the colored buttons) + * + * Ricardo Cerqueira + */ +static int get_key_pinnacle_color(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + /* code_modulo parameter (0x88) is used to reduce code value to fit inside IR_KEYTAB_SIZE + * + * this is the only value that results in 42 unique + * codes < 128 + */ + + return get_key_pinnacle(ir, ir_key, ir_raw, 2, 0x80, 0x88); +} + +void saa7134_input_irq(struct saa7134_dev *dev) +{ + struct saa7134_card_ir *ir; + + if (!dev || !dev->remote) + return; + + ir = dev->remote; + if (!ir->running) + return; + + if (!ir->polling && !ir->raw_decode) { + build_key(dev); + } else if (ir->raw_decode) { + saa7134_raw_decode_irq(dev); + } +} + +static void saa7134_input_timer(unsigned long data) +{ + struct saa7134_dev *dev = (struct saa7134_dev *)data; + struct saa7134_card_ir *ir = dev->remote; + + build_key(dev); + mod_timer(&ir->timer, jiffies + msecs_to_jiffies(ir->polling)); +} + +static void ir_raw_decode_timer_end(unsigned long data) +{ + struct saa7134_dev *dev = (struct saa7134_dev *)data; + + ir_raw_event_handle(dev->remote->dev); +} + +static int __saa7134_ir_start(void *priv) +{ + struct saa7134_dev *dev = priv; + struct saa7134_card_ir *ir; + + if (!dev || !dev->remote) + return -EINVAL; + + ir = dev->remote; + if (ir->running) + return 0; + + /* Moved here from saa7134_input_init1() because the latter + * is not called on device resume */ + switch (dev->board) { + case SAA7134_BOARD_MD2819: + case SAA7134_BOARD_KWORLD_VSTREAM_XPERT: + case SAA7134_BOARD_AVERMEDIA_305: + case SAA7134_BOARD_AVERMEDIA_307: + case SAA7134_BOARD_AVERMEDIA_STUDIO_305: + case SAA7134_BOARD_AVERMEDIA_STUDIO_505: + case SAA7134_BOARD_AVERMEDIA_STUDIO_307: + case SAA7134_BOARD_AVERMEDIA_STUDIO_507: + case SAA7134_BOARD_AVERMEDIA_STUDIO_507UA: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM: + case SAA7134_BOARD_AVERMEDIA_M102: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS: + /* Without this we won't receive key up events */ + saa_setb(SAA7134_GPIO_GPMODE0, 0x4); + saa_setb(SAA7134_GPIO_GPSTATUS0, 0x4); + break; + case SAA7134_BOARD_AVERMEDIA_777: + case SAA7134_BOARD_AVERMEDIA_A16AR: + /* Without this we won't receive key up events */ + saa_setb(SAA7134_GPIO_GPMODE1, 0x1); + saa_setb(SAA7134_GPIO_GPSTATUS1, 0x1); + break; + case SAA7134_BOARD_AVERMEDIA_A16D: + /* Without this we won't receive key up events */ + saa_setb(SAA7134_GPIO_GPMODE1, 0x1); + saa_setb(SAA7134_GPIO_GPSTATUS1, 0x1); + break; + case SAA7134_BOARD_GOTVIEW_7135: + saa_setb(SAA7134_GPIO_GPMODE1, 0x80); + break; + } + + ir->running = true; + + if (ir->polling) { + setup_timer(&ir->timer, saa7134_input_timer, + (unsigned long)dev); + ir->timer.expires = jiffies + HZ; + add_timer(&ir->timer); + } else if (ir->raw_decode) { + /* set timer_end for code completion */ + setup_timer(&ir->timer, ir_raw_decode_timer_end, + (unsigned long)dev); + } + + return 0; +} + +static void __saa7134_ir_stop(void *priv) +{ + struct saa7134_dev *dev = priv; + struct saa7134_card_ir *ir; + + if (!dev || !dev->remote) + return; + + ir = dev->remote; + if (!ir->running) + return; + + if (ir->polling || ir->raw_decode) + del_timer_sync(&ir->timer); + + ir->running = false; + + return; +} + +int saa7134_ir_start(struct saa7134_dev *dev) +{ + if (dev->remote->users) + return __saa7134_ir_start(dev); + + return 0; +} + +void saa7134_ir_stop(struct saa7134_dev *dev) +{ + if (dev->remote->users) + __saa7134_ir_stop(dev); +} + +static int saa7134_ir_open(struct rc_dev *rc) +{ + struct saa7134_dev *dev = rc->priv; + + dev->remote->users++; + return __saa7134_ir_start(dev); +} + +static void saa7134_ir_close(struct rc_dev *rc) +{ + struct saa7134_dev *dev = rc->priv; + + dev->remote->users--; + if (!dev->remote->users) + __saa7134_ir_stop(dev); +} + +int saa7134_input_init1(struct saa7134_dev *dev) +{ + struct saa7134_card_ir *ir; + struct rc_dev *rc; + char *ir_codes = NULL; + u32 mask_keycode = 0; + u32 mask_keydown = 0; + u32 mask_keyup = 0; + unsigned polling = 0; + bool raw_decode = false; + int err; + + if (dev->has_remote != SAA7134_REMOTE_GPIO) + return -ENODEV; + if (disable_ir) + return -ENODEV; + + /* detect & configure */ + switch (dev->board) { + case SAA7134_BOARD_FLYVIDEO2000: + case SAA7134_BOARD_FLYVIDEO3000: + case SAA7134_BOARD_FLYTVPLATINUM_FM: + case SAA7134_BOARD_FLYTVPLATINUM_MINI2: + case SAA7134_BOARD_ROVERMEDIA_LINK_PRO_FM: + ir_codes = RC_MAP_FLYVIDEO; + mask_keycode = 0xEC00000; + mask_keydown = 0x0040000; + break; + case SAA7134_BOARD_CINERGY400: + case SAA7134_BOARD_CINERGY600: + case SAA7134_BOARD_CINERGY600_MK3: + ir_codes = RC_MAP_CINERGY; + mask_keycode = 0x00003f; + mask_keyup = 0x040000; + break; + case SAA7134_BOARD_ECS_TVP3XP: + case SAA7134_BOARD_ECS_TVP3XP_4CB5: + ir_codes = RC_MAP_EZTV; + mask_keycode = 0x00017c; + mask_keyup = 0x000002; + polling = 50; // ms + break; + case SAA7134_BOARD_KWORLD_XPERT: + case SAA7134_BOARD_AVACSSMARTTV: + ir_codes = RC_MAP_PIXELVIEW; + mask_keycode = 0x00001F; + mask_keyup = 0x000020; + polling = 50; // ms + break; + case SAA7134_BOARD_MD2819: + case SAA7134_BOARD_KWORLD_VSTREAM_XPERT: + case SAA7134_BOARD_AVERMEDIA_305: + case SAA7134_BOARD_AVERMEDIA_307: + case SAA7134_BOARD_AVERMEDIA_STUDIO_305: + case SAA7134_BOARD_AVERMEDIA_STUDIO_505: + case SAA7134_BOARD_AVERMEDIA_STUDIO_307: + case SAA7134_BOARD_AVERMEDIA_STUDIO_507: + case SAA7134_BOARD_AVERMEDIA_STUDIO_507UA: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM: + case SAA7134_BOARD_AVERMEDIA_M102: + case SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS: + ir_codes = RC_MAP_AVERMEDIA; + mask_keycode = 0x0007C8; + mask_keydown = 0x000010; + polling = 50; // ms + /* GPIO stuff moved to __saa7134_ir_start() */ + break; + case SAA7134_BOARD_AVERMEDIA_M135A: + ir_codes = RC_MAP_AVERMEDIA_M135A; + mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_AVERMEDIA_M733A: + ir_codes = RC_MAP_AVERMEDIA_M733A_RM_K6; + mask_keydown = 0x0040000; + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_AVERMEDIA_777: + case SAA7134_BOARD_AVERMEDIA_A16AR: + ir_codes = RC_MAP_AVERMEDIA; + mask_keycode = 0x02F200; + mask_keydown = 0x000400; + polling = 50; // ms + /* GPIO stuff moved to __saa7134_ir_start() */ + break; + case SAA7134_BOARD_AVERMEDIA_A16D: + ir_codes = RC_MAP_AVERMEDIA_A16D; + mask_keycode = 0x02F200; + mask_keydown = 0x000400; + polling = 50; /* ms */ + /* GPIO stuff moved to __saa7134_ir_start() */ + break; + case SAA7134_BOARD_KWORLD_TERMINATOR: + ir_codes = RC_MAP_PIXELVIEW; + mask_keycode = 0x00001f; + mask_keyup = 0x000060; + polling = 50; // ms + break; + case SAA7134_BOARD_MANLI_MTV001: + case SAA7134_BOARD_MANLI_MTV002: + ir_codes = RC_MAP_MANLI; + mask_keycode = 0x001f00; + mask_keyup = 0x004000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_BEHOLD_409FM: + case SAA7134_BOARD_BEHOLD_401: + case SAA7134_BOARD_BEHOLD_403: + case SAA7134_BOARD_BEHOLD_403FM: + case SAA7134_BOARD_BEHOLD_405: + case SAA7134_BOARD_BEHOLD_405FM: + case SAA7134_BOARD_BEHOLD_407: + case SAA7134_BOARD_BEHOLD_407FM: + case SAA7134_BOARD_BEHOLD_409: + case SAA7134_BOARD_BEHOLD_505FM: + case SAA7134_BOARD_BEHOLD_505RDS_MK5: + case SAA7134_BOARD_BEHOLD_505RDS_MK3: + case SAA7134_BOARD_BEHOLD_507_9FM: + case SAA7134_BOARD_BEHOLD_507RDS_MK3: + case SAA7134_BOARD_BEHOLD_507RDS_MK5: + ir_codes = RC_MAP_MANLI; + mask_keycode = 0x003f00; + mask_keyup = 0x004000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_BEHOLD_COLUMBUS_TVFM: + ir_codes = RC_MAP_BEHOLD_COLUMBUS; + mask_keycode = 0x003f00; + mask_keyup = 0x004000; + polling = 50; // ms + break; + case SAA7134_BOARD_SEDNA_PC_TV_CARDBUS: + ir_codes = RC_MAP_PCTV_SEDNA; + mask_keycode = 0x001f00; + mask_keyup = 0x004000; + polling = 50; // ms + break; + case SAA7134_BOARD_GOTVIEW_7135: + ir_codes = RC_MAP_GOTVIEW7135; + mask_keycode = 0x0003CC; + mask_keydown = 0x000010; + polling = 5; /* ms */ + /* GPIO stuff moved to __saa7134_ir_start() */ + break; + case SAA7134_BOARD_VIDEOMATE_TV_PVR: + case SAA7134_BOARD_VIDEOMATE_GOLD_PLUS: + case SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUSII: + ir_codes = RC_MAP_VIDEOMATE_TV_PVR; + mask_keycode = 0x00003F; + mask_keyup = 0x400000; + polling = 50; // ms + break; + case SAA7134_BOARD_PROTEUS_2309: + ir_codes = RC_MAP_PROTEUS_2309; + mask_keycode = 0x00007F; + mask_keyup = 0x000080; + polling = 50; // ms + break; + case SAA7134_BOARD_VIDEOMATE_DVBT_300: + case SAA7134_BOARD_VIDEOMATE_DVBT_200: + ir_codes = RC_MAP_VIDEOMATE_TV_PVR; + mask_keycode = 0x003F00; + mask_keyup = 0x040000; + break; + case SAA7134_BOARD_FLYDVBS_LR300: + case SAA7134_BOARD_FLYDVBT_LR301: + case SAA7134_BOARD_FLYDVBTDUO: + ir_codes = RC_MAP_FLYDVB; + mask_keycode = 0x0001F00; + mask_keydown = 0x0040000; + break; + case SAA7134_BOARD_ASUSTeK_P7131_DUAL: + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: + case SAA7134_BOARD_ASUSTeK_P7131_ANALOG: + ir_codes = RC_MAP_ASUS_PC39; + mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_ASUSTeK_PS3_100: + ir_codes = RC_MAP_ASUS_PS3_100; + mask_keydown = 0x0040000; + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_ENCORE_ENLTV: + case SAA7134_BOARD_ENCORE_ENLTV_FM: + ir_codes = RC_MAP_ENCORE_ENLTV; + mask_keycode = 0x00007f; + mask_keyup = 0x040000; + polling = 50; // ms + break; + case SAA7134_BOARD_ENCORE_ENLTV_FM53: + case SAA7134_BOARD_ENCORE_ENLTV_FM3: + ir_codes = RC_MAP_ENCORE_ENLTV_FM53; + mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + case SAA7134_BOARD_10MOONSTVMASTER3: + ir_codes = RC_MAP_ENCORE_ENLTV; + mask_keycode = 0x5f80000; + mask_keyup = 0x8000000; + polling = 50; //ms + break; + case SAA7134_BOARD_GENIUS_TVGO_A11MCE: + ir_codes = RC_MAP_GENIUS_TVGO_A11MCE; + mask_keycode = 0xff; + mask_keydown = 0xf00000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_REAL_ANGEL_220: + ir_codes = RC_MAP_REAL_AUDIO_220_32_KEYS; + mask_keycode = 0x3f00; + mask_keyup = 0x4000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG: + ir_codes = RC_MAP_KWORLD_PLUS_TV_ANALOG; + mask_keycode = 0x7f; + polling = 40; /* ms */ + break; + case SAA7134_BOARD_VIDEOMATE_S350: + ir_codes = RC_MAP_VIDEOMATE_S350; + mask_keycode = 0x003f00; + mask_keydown = 0x040000; + break; + case SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S: + ir_codes = RC_MAP_WINFAST; + mask_keycode = 0x5f00; + mask_keyup = 0x020000; + polling = 50; /* ms */ + break; + case SAA7134_BOARD_VIDEOMATE_M1F: + ir_codes = RC_MAP_VIDEOMATE_K100; + mask_keycode = 0x0ff00; + mask_keyup = 0x040000; + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: + ir_codes = RC_MAP_HAUPPAUGE; + mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ + mask_keyup = 0x0040000; + mask_keycode = 0xffff; + raw_decode = true; + break; + } + if (NULL == ir_codes) { + printk("%s: Oops: IR config error [card=%d]\n", + dev->name, dev->board); + return -ENODEV; + } + + ir = kzalloc(sizeof(*ir), GFP_KERNEL); + rc = rc_allocate_device(); + if (!ir || !rc) { + err = -ENOMEM; + goto err_out_free; + } + + ir->dev = rc; + dev->remote = ir; + + /* init hardware-specific stuff */ + ir->mask_keycode = mask_keycode; + ir->mask_keydown = mask_keydown; + ir->mask_keyup = mask_keyup; + ir->polling = polling; + ir->raw_decode = raw_decode; + + /* init input device */ + snprintf(ir->name, sizeof(ir->name), "saa7134 IR (%s)", + saa7134_boards[dev->board].name); + snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", + pci_name(dev->pci)); + + rc->priv = dev; + rc->open = saa7134_ir_open; + rc->close = saa7134_ir_close; + if (raw_decode) + rc->driver_type = RC_DRIVER_IR_RAW; + + rc->input_name = ir->name; + rc->input_phys = ir->phys; + rc->input_id.bustype = BUS_PCI; + rc->input_id.version = 1; + if (dev->pci->subsystem_vendor) { + rc->input_id.vendor = dev->pci->subsystem_vendor; + rc->input_id.product = dev->pci->subsystem_device; + } else { + rc->input_id.vendor = dev->pci->vendor; + rc->input_id.product = dev->pci->device; + } + rc->dev.parent = &dev->pci->dev; + rc->map_name = ir_codes; + rc->driver_name = MODULE_NAME; + + err = rc_register_device(rc); + if (err) + goto err_out_free; + + return 0; + +err_out_free: + rc_free_device(rc); + dev->remote = NULL; + kfree(ir); + return err; +} + +void saa7134_input_fini(struct saa7134_dev *dev) +{ + if (NULL == dev->remote) + return; + + saa7134_ir_stop(dev); + rc_unregister_device(dev->remote->dev); + kfree(dev->remote); + dev->remote = NULL; +} + +void saa7134_probe_i2c_ir(struct saa7134_dev *dev) +{ + struct i2c_board_info info; + struct i2c_msg msg_msi = { + .addr = 0x50, + .flags = I2C_M_RD, + .len = 0, + .buf = NULL, + }; + int rc; + + if (disable_ir) { + dprintk("IR has been disabled, not probing for i2c remote\n"); + return; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + memset(&dev->init_data, 0, sizeof(dev->init_data)); + strlcpy(info.type, "ir_video", I2C_NAME_SIZE); + + switch (dev->board) { + case SAA7134_BOARD_PINNACLE_PCTV_110i: + case SAA7134_BOARD_PINNACLE_PCTV_310i: + dev->init_data.name = "Pinnacle PCTV"; + if (pinnacle_remote == 0) { + dev->init_data.get_key = get_key_pinnacle_color; + dev->init_data.ir_codes = RC_MAP_PINNACLE_COLOR; + info.addr = 0x47; + } else { + dev->init_data.get_key = get_key_pinnacle_grey; + dev->init_data.ir_codes = RC_MAP_PINNACLE_GREY; + info.addr = 0x47; + } + break; + case SAA7134_BOARD_UPMOST_PURPLE_TV: + dev->init_data.name = "Purple TV"; + dev->init_data.get_key = get_key_purpletv; + dev->init_data.ir_codes = RC_MAP_PURPLETV; + info.addr = 0x7a; + break; + case SAA7134_BOARD_MSI_TVATANYWHERE_PLUS: + dev->init_data.name = "MSI TV@nywhere Plus"; + dev->init_data.get_key = get_key_msi_tvanywhere_plus; + dev->init_data.ir_codes = RC_MAP_MSI_TVANYWHERE_PLUS; + /* + * MSI TV@nyware Plus requires more frequent polling + * otherwise it will miss some keypresses + */ + dev->init_data.polling_interval = 50; + info.addr = 0x30; + /* MSI TV@nywhere Plus controller doesn't seem to + respond to probes unless we read something from + an existing device. Weird... + REVISIT: might no longer be needed */ + rc = i2c_transfer(&dev->i2c_adap, &msg_msi, 1); + dprintk("probe 0x%02x @ %s: %s\n", + msg_msi.addr, dev->i2c_adap.name, + (1 == rc) ? "yes" : "no"); + break; + case SAA7134_BOARD_KWORLD_PC150U: + /* copied and modified from MSI TV@nywhere Plus */ + dev->init_data.name = "Kworld PC150-U"; + dev->init_data.get_key = get_key_kworld_pc150u; + dev->init_data.ir_codes = RC_MAP_KWORLD_PC150U; + info.addr = 0x30; + /* MSI TV@nywhere Plus controller doesn't seem to + respond to probes unless we read something from + an existing device. Weird... + REVISIT: might no longer be needed */ + rc = i2c_transfer(&dev->i2c_adap, &msg_msi, 1); + dprintk("probe 0x%02x @ %s: %s\n", + msg_msi.addr, dev->i2c_adap.name, + (1 == rc) ? "yes" : "no"); + break; + case SAA7134_BOARD_HAUPPAUGE_HVR1110: + dev->init_data.name = "HVR 1110"; + dev->init_data.get_key = get_key_hvr1110; + dev->init_data.ir_codes = RC_MAP_HAUPPAUGE; + info.addr = 0x71; + break; + case SAA7134_BOARD_BEHOLD_607FM_MK3: + case SAA7134_BOARD_BEHOLD_607FM_MK5: + case SAA7134_BOARD_BEHOLD_609FM_MK3: + case SAA7134_BOARD_BEHOLD_609FM_MK5: + case SAA7134_BOARD_BEHOLD_607RDS_MK3: + case SAA7134_BOARD_BEHOLD_607RDS_MK5: + case SAA7134_BOARD_BEHOLD_609RDS_MK3: + case SAA7134_BOARD_BEHOLD_609RDS_MK5: + case SAA7134_BOARD_BEHOLD_M6: + case SAA7134_BOARD_BEHOLD_M63: + case SAA7134_BOARD_BEHOLD_M6_EXTRA: + case SAA7134_BOARD_BEHOLD_H6: + case SAA7134_BOARD_BEHOLD_X7: + case SAA7134_BOARD_BEHOLD_H7: + case SAA7134_BOARD_BEHOLD_A7: + dev->init_data.name = "BeholdTV"; + dev->init_data.get_key = get_key_beholdm6xx; + dev->init_data.ir_codes = RC_MAP_BEHOLD; + dev->init_data.type = RC_TYPE_NEC; + info.addr = 0x2d; + break; + case SAA7134_BOARD_AVERMEDIA_CARDBUS_501: + case SAA7134_BOARD_AVERMEDIA_CARDBUS_506: + info.addr = 0x40; + break; + case SAA7134_BOARD_FLYDVB_TRIO: + dev->init_data.name = "FlyDVB Trio"; + dev->init_data.get_key = get_key_flydvb_trio; + dev->init_data.ir_codes = RC_MAP_FLYDVB; + info.addr = 0x0b; + break; + default: + dprintk("No I2C IR support for board %x\n", dev->board); + return; + } + + if (dev->init_data.name) + info.platform_data = &dev->init_data; + i2c_new_device(&dev->i2c_adap, &info); +} + +static int saa7134_raw_decode_irq(struct saa7134_dev *dev) +{ + struct saa7134_card_ir *ir = dev->remote; + unsigned long timeout; + int space; + + /* Generate initial event */ + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + space = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2) & ir->mask_keydown; + ir_raw_event_store_edge(dev->remote->dev, space ? IR_SPACE : IR_PULSE); + + /* + * Wait 15 ms from the start of the first IR event before processing + * the event. This time is enough for NEC protocol. May need adjustments + * to work with other protocols. + */ + smp_mb(); + + if (!timer_pending(&ir->timer)) { + timeout = jiffies + msecs_to_jiffies(15); + mod_timer(&ir->timer, timeout); + } + + return 1; +} diff --git a/drivers/media/video/saa7134/saa7134-reg.h b/drivers/media/pci/saa7134/saa7134-reg.h similarity index 100% rename from drivers/media/video/saa7134/saa7134-reg.h rename to drivers/media/pci/saa7134/saa7134-reg.h diff --git a/drivers/media/video/saa7134/saa7134-ts.c b/drivers/media/pci/saa7134/saa7134-ts.c similarity index 100% rename from drivers/media/video/saa7134/saa7134-ts.c rename to drivers/media/pci/saa7134/saa7134-ts.c diff --git a/drivers/media/video/saa7134/saa7134-tvaudio.c b/drivers/media/pci/saa7134/saa7134-tvaudio.c similarity index 100% rename from drivers/media/video/saa7134/saa7134-tvaudio.c rename to drivers/media/pci/saa7134/saa7134-tvaudio.c diff --git a/drivers/media/video/saa7134/saa7134-vbi.c b/drivers/media/pci/saa7134/saa7134-vbi.c similarity index 100% rename from drivers/media/video/saa7134/saa7134-vbi.c rename to drivers/media/pci/saa7134/saa7134-vbi.c diff --git a/drivers/media/pci/saa7134/saa7134-video.c b/drivers/media/pci/saa7134/saa7134-video.c new file mode 100644 index 000000000000..22f8758d047f --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134-video.c @@ -0,0 +1,2661 @@ +/* + * + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001-03 Gerd Knorr [SuSE Labs] + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "saa7134-reg.h" +#include "saa7134.h" +#include +#include + +/* ------------------------------------------------------------------ */ + +unsigned int video_debug; +static unsigned int gbuffers = 8; +static unsigned int noninterlaced; /* 0 */ +static unsigned int gbufsize = 720*576*4; +static unsigned int gbufsize_max = 720*576*4; +static char secam[] = "--"; +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug,"enable debug messages [video]"); +module_param(gbuffers, int, 0444); +MODULE_PARM_DESC(gbuffers,"number of capture buffers, range 2-32"); +module_param(noninterlaced, int, 0644); +MODULE_PARM_DESC(noninterlaced,"capture non interlaced video"); +module_param_string(secam, secam, sizeof(secam), 0644); +MODULE_PARM_DESC(secam, "force SECAM variant, either DK,L or Lc"); + + +#define dprintk(fmt, arg...) if (video_debug&0x04) \ + printk(KERN_DEBUG "%s/video: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ +/* Defines for Video Output Port Register at address 0x191 */ + +/* Bit 0: VIP code T bit polarity */ + +#define VP_T_CODE_P_NON_INVERTED 0x00 +#define VP_T_CODE_P_INVERTED 0x01 + +/* ------------------------------------------------------------------ */ +/* Defines for Video Output Port Register at address 0x195 */ + +/* Bit 2: Video output clock delay control */ + +#define VP_CLK_CTRL2_NOT_DELAYED 0x00 +#define VP_CLK_CTRL2_DELAYED 0x04 + +/* Bit 1: Video output clock invert control */ + +#define VP_CLK_CTRL1_NON_INVERTED 0x00 +#define VP_CLK_CTRL1_INVERTED 0x02 + +/* ------------------------------------------------------------------ */ +/* Defines for Video Output Port Register at address 0x196 */ + +/* Bits 2 to 0: VSYNC pin video vertical sync type */ + +#define VP_VS_TYPE_MASK 0x07 + +#define VP_VS_TYPE_OFF 0x00 +#define VP_VS_TYPE_V123 0x01 +#define VP_VS_TYPE_V_ITU 0x02 +#define VP_VS_TYPE_VGATE_L 0x03 +#define VP_VS_TYPE_RESERVED1 0x04 +#define VP_VS_TYPE_RESERVED2 0x05 +#define VP_VS_TYPE_F_ITU 0x06 +#define VP_VS_TYPE_SC_FID 0x07 + +/* ------------------------------------------------------------------ */ +/* data structs for video */ + +static int video_out[][9] = { + [CCIR656] = { 0x00, 0xb1, 0x00, 0xa1, 0x00, 0x04, 0x06, 0x00, 0x00 }, +}; + +static struct saa7134_format formats[] = { + { + .name = "8 bpp gray", + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .pm = 0x06, + },{ + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .pm = 0x13 | 0x80, + },{ + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .pm = 0x13 | 0x80, + .bswap = 1, + },{ + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .pm = 0x10 | 0x80, + },{ + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .pm = 0x10 | 0x80, + .bswap = 1, + },{ + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .pm = 0x11, + },{ + .name = "24 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .pm = 0x11, + .bswap = 1, + },{ + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .pm = 0x12, + },{ + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .pm = 0x12, + .bswap = 1, + .wswap = 1, + },{ + .name = "4:2:2 packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .pm = 0x00, + .bswap = 1, + .yuv = 1, + },{ + .name = "4:2:2 packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .pm = 0x00, + .yuv = 1, + },{ + .name = "4:2:2 planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .pm = 0x09, + .yuv = 1, + .planar = 1, + .hshift = 1, + .vshift = 0, + },{ + .name = "4:2:0 planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .pm = 0x0a, + .yuv = 1, + .planar = 1, + .hshift = 1, + .vshift = 1, + },{ + .name = "4:2:0 planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YVU420, + .depth = 12, + .pm = 0x0a, + .yuv = 1, + .planar = 1, + .uvswap = 1, + .hshift = 1, + .vshift = 1, + } +}; +#define FORMATS ARRAY_SIZE(formats) + +#define NORM_625_50 \ + .h_start = 0, \ + .h_stop = 719, \ + .video_v_start = 24, \ + .video_v_stop = 311, \ + .vbi_v_start_0 = 7, \ + .vbi_v_stop_0 = 22, \ + .vbi_v_start_1 = 319, \ + .src_timing = 4 + +#define NORM_525_60 \ + .h_start = 0, \ + .h_stop = 719, \ + .video_v_start = 23, \ + .video_v_stop = 262, \ + .vbi_v_start_0 = 10, \ + .vbi_v_stop_0 = 21, \ + .vbi_v_start_1 = 273, \ + .src_timing = 7 + +static struct saa7134_tvnorm tvnorms[] = { + { + .name = "PAL", /* autodetect */ + .id = V4L2_STD_PAL, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "NTSC", + .id = V4L2_STD_NTSC, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0x89, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + + },{ + .name = "SECAM", + .id = V4L2_STD_SECAM, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + },{ + .name = "SECAM-DK", + .id = V4L2_STD_SECAM_DK, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + },{ + .name = "SECAM-L", + .id = V4L2_STD_SECAM_L, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + },{ + .name = "SECAM-Lc", + .id = V4L2_STD_SECAM_LC, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0xb9, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + + },{ + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0xa1, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-60", + .id = V4L2_STD_PAL_60, + + .h_start = 0, + .h_stop = 719, + .video_v_start = 23, + .video_v_stop = 262, + .vbi_v_start_0 = 10, + .vbi_v_stop_0 = 21, + .vbi_v_start_1 = 273, + .src_timing = 7, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + } +}; +#define TVNORMS ARRAY_SIZE(tvnorms) + +#define V4L2_CID_PRIVATE_INVERT (V4L2_CID_PRIVATE_BASE + 0) +#define V4L2_CID_PRIVATE_Y_ODD (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_PRIVATE_Y_EVEN (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_PRIVATE_BASE + 3) +#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 4) + +static const struct v4l2_queryctrl no_ctrl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; +static const struct v4l2_queryctrl video_ctrls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 128, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 68, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 64, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_HFLIP, + .name = "Mirror", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, + /* --- audio --- */ + { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = -15, + .maximum = 15, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + /* --- private --- */ + { + .id = V4L2_CID_PRIVATE_INVERT, + .name = "Invert", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_Y_ODD, + .name = "y offset odd field", + .minimum = 0, + .maximum = 128, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_Y_EVEN, + .name = "y offset even field", + .minimum = 0, + .maximum = 128, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_AUTOMUTE, + .name = "automute", + .minimum = 0, + .maximum = 1, + .default_value = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + } +}; +static const unsigned int CTRLS = ARRAY_SIZE(video_ctrls); + +static const struct v4l2_queryctrl* ctrl_by_id(unsigned int id) +{ + unsigned int i; + + for (i = 0; i < CTRLS; i++) + if (video_ctrls[i].id == id) + return video_ctrls+i; + return NULL; +} + +static struct saa7134_format* format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < FORMATS; i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int bit) +{ + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + mutex_lock(&dev->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + mutex_unlock(&dev->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dprintk("res: get %d\n",bit); + mutex_unlock(&dev->lock); + return 1; +} + +static int res_check(struct saa7134_fh *fh, unsigned int bit) +{ + return (fh->resources & bit); +} + +static int res_locked(struct saa7134_dev *dev, unsigned int bit) +{ + return (dev->resources & bit); +} + +static +void res_free(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int bits) +{ + BUG_ON((fh->resources & bits) != bits); + + mutex_lock(&dev->lock); + fh->resources &= ~bits; + dev->resources &= ~bits; + dprintk("res: put %d\n",bits); + mutex_unlock(&dev->lock); +} + +/* ------------------------------------------------------------------ */ + +static void set_tvnorm(struct saa7134_dev *dev, struct saa7134_tvnorm *norm) +{ + dprintk("set tv norm = %s\n",norm->name); + dev->tvnorm = norm; + + /* setup cropping */ + dev->crop_bounds.left = norm->h_start; + dev->crop_defrect.left = norm->h_start; + dev->crop_bounds.width = norm->h_stop - norm->h_start +1; + dev->crop_defrect.width = norm->h_stop - norm->h_start +1; + + dev->crop_bounds.top = (norm->vbi_v_stop_0+1)*2; + dev->crop_defrect.top = norm->video_v_start*2; + dev->crop_bounds.height = ((norm->id & V4L2_STD_525_60) ? 524 : 624) + - dev->crop_bounds.top; + dev->crop_defrect.height = (norm->video_v_stop - norm->video_v_start +1)*2; + + dev->crop_current = dev->crop_defrect; + + saa7134_set_tvnorm_hw(dev); +} + +static void video_mux(struct saa7134_dev *dev, int input) +{ + dprintk("video input = %d [%s]\n", input, card_in(dev, input).name); + dev->ctl_input = input; + set_tvnorm(dev, dev->tvnorm); + saa7134_tvaudio_setinput(dev, &card_in(dev, input)); +} + + +static void saa7134_set_decoder(struct saa7134_dev *dev) +{ + int luma_control, sync_control, mux; + + struct saa7134_tvnorm *norm = dev->tvnorm; + mux = card_in(dev, dev->ctl_input).vmux; + + luma_control = norm->luma_control; + sync_control = norm->sync_control; + + if (mux > 5) + luma_control |= 0x80; /* svideo */ + if (noninterlaced || dev->nosignal) + sync_control |= 0x20; + + /* setup video decoder */ + saa_writeb(SAA7134_INCR_DELAY, 0x08); + saa_writeb(SAA7134_ANALOG_IN_CTRL1, 0xc0 | mux); + saa_writeb(SAA7134_ANALOG_IN_CTRL2, 0x00); + + saa_writeb(SAA7134_ANALOG_IN_CTRL3, 0x90); + saa_writeb(SAA7134_ANALOG_IN_CTRL4, 0x90); + saa_writeb(SAA7134_HSYNC_START, 0xeb); + saa_writeb(SAA7134_HSYNC_STOP, 0xe0); + saa_writeb(SAA7134_SOURCE_TIMING1, norm->src_timing); + + saa_writeb(SAA7134_SYNC_CTRL, sync_control); + saa_writeb(SAA7134_LUMA_CTRL, luma_control); + saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright); + + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + + saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue); + saa_writeb(SAA7134_CHROMA_CTRL1, norm->chroma_ctrl1); + saa_writeb(SAA7134_CHROMA_GAIN, norm->chroma_gain); + + saa_writeb(SAA7134_CHROMA_CTRL2, norm->chroma_ctrl2); + saa_writeb(SAA7134_MODE_DELAY_CTRL, 0x00); + + saa_writeb(SAA7134_ANALOG_ADC, 0x01); + saa_writeb(SAA7134_VGATE_START, 0x11); + saa_writeb(SAA7134_VGATE_STOP, 0xfe); + saa_writeb(SAA7134_MISC_VGATE_MSB, norm->vgate_misc); + saa_writeb(SAA7134_RAW_DATA_GAIN, 0x40); + saa_writeb(SAA7134_RAW_DATA_OFFSET, 0x80); +} + +void saa7134_set_tvnorm_hw(struct saa7134_dev *dev) +{ + saa7134_set_decoder(dev); + + if (card_in(dev, dev->ctl_input).tv) + saa_call_all(dev, core, s_std, dev->tvnorm->id); + /* Set the correct norm for the saa6752hs. This function + does nothing if there is no saa6752hs. */ + saa_call_empress(dev, core, s_std, dev->tvnorm->id); +} + +static void set_h_prescale(struct saa7134_dev *dev, int task, int prescale) +{ + static const struct { + int xpsc; + int xacl; + int xc2_1; + int xdcg; + int vpfy; + } vals[] = { + /* XPSC XACL XC2_1 XDCG VPFY */ + { 1, 0, 0, 0, 0 }, + { 2, 2, 1, 2, 2 }, + { 3, 4, 1, 3, 2 }, + { 4, 8, 1, 4, 2 }, + { 5, 8, 1, 4, 2 }, + { 6, 8, 1, 4, 3 }, + { 7, 8, 1, 4, 3 }, + { 8, 15, 0, 4, 3 }, + { 9, 15, 0, 4, 3 }, + { 10, 16, 1, 5, 3 }, + }; + static const int count = ARRAY_SIZE(vals); + int i; + + for (i = 0; i < count; i++) + if (vals[i].xpsc == prescale) + break; + if (i == count) + return; + + saa_writeb(SAA7134_H_PRESCALE(task), vals[i].xpsc); + saa_writeb(SAA7134_ACC_LENGTH(task), vals[i].xacl); + saa_writeb(SAA7134_LEVEL_CTRL(task), + (vals[i].xc2_1 << 3) | (vals[i].xdcg)); + saa_andorb(SAA7134_FIR_PREFILTER_CTRL(task), 0x0f, + (vals[i].vpfy << 2) | vals[i].vpfy); +} + +static void set_v_scale(struct saa7134_dev *dev, int task, int yscale) +{ + int val,mirror; + + saa_writeb(SAA7134_V_SCALE_RATIO1(task), yscale & 0xff); + saa_writeb(SAA7134_V_SCALE_RATIO2(task), yscale >> 8); + + mirror = (dev->ctl_mirror) ? 0x02 : 0x00; + if (yscale < 2048) { + /* LPI */ + dprintk("yscale LPI yscale=%d\n",yscale); + saa_writeb(SAA7134_V_FILTER(task), 0x00 | mirror); + saa_writeb(SAA7134_LUMA_CONTRAST(task), 0x40); + saa_writeb(SAA7134_CHROMA_SATURATION(task), 0x40); + } else { + /* ACM */ + val = 0x40 * 1024 / yscale; + dprintk("yscale ACM yscale=%d val=0x%x\n",yscale,val); + saa_writeb(SAA7134_V_FILTER(task), 0x01 | mirror); + saa_writeb(SAA7134_LUMA_CONTRAST(task), val); + saa_writeb(SAA7134_CHROMA_SATURATION(task), val); + } + saa_writeb(SAA7134_LUMA_BRIGHT(task), 0x80); +} + +static void set_size(struct saa7134_dev *dev, int task, + int width, int height, int interlace) +{ + int prescale,xscale,yscale,y_even,y_odd; + int h_start, h_stop, v_start, v_stop; + int div = interlace ? 2 : 1; + + /* setup video scaler */ + h_start = dev->crop_current.left; + v_start = dev->crop_current.top/2; + h_stop = (dev->crop_current.left + dev->crop_current.width -1); + v_stop = (dev->crop_current.top + dev->crop_current.height -1)/2; + + saa_writeb(SAA7134_VIDEO_H_START1(task), h_start & 0xff); + saa_writeb(SAA7134_VIDEO_H_START2(task), h_start >> 8); + saa_writeb(SAA7134_VIDEO_H_STOP1(task), h_stop & 0xff); + saa_writeb(SAA7134_VIDEO_H_STOP2(task), h_stop >> 8); + saa_writeb(SAA7134_VIDEO_V_START1(task), v_start & 0xff); + saa_writeb(SAA7134_VIDEO_V_START2(task), v_start >> 8); + saa_writeb(SAA7134_VIDEO_V_STOP1(task), v_stop & 0xff); + saa_writeb(SAA7134_VIDEO_V_STOP2(task), v_stop >> 8); + + prescale = dev->crop_current.width / width; + if (0 == prescale) + prescale = 1; + xscale = 1024 * dev->crop_current.width / prescale / width; + yscale = 512 * div * dev->crop_current.height / height; + dprintk("prescale=%d xscale=%d yscale=%d\n",prescale,xscale,yscale); + set_h_prescale(dev,task,prescale); + saa_writeb(SAA7134_H_SCALE_INC1(task), xscale & 0xff); + saa_writeb(SAA7134_H_SCALE_INC2(task), xscale >> 8); + set_v_scale(dev,task,yscale); + + saa_writeb(SAA7134_VIDEO_PIXELS1(task), width & 0xff); + saa_writeb(SAA7134_VIDEO_PIXELS2(task), width >> 8); + saa_writeb(SAA7134_VIDEO_LINES1(task), height/div & 0xff); + saa_writeb(SAA7134_VIDEO_LINES2(task), height/div >> 8); + + /* deinterlace y offsets */ + y_odd = dev->ctl_y_odd; + y_even = dev->ctl_y_even; + saa_writeb(SAA7134_V_PHASE_OFFSET0(task), y_odd); + saa_writeb(SAA7134_V_PHASE_OFFSET1(task), y_even); + saa_writeb(SAA7134_V_PHASE_OFFSET2(task), y_odd); + saa_writeb(SAA7134_V_PHASE_OFFSET3(task), y_even); +} + +/* ------------------------------------------------------------------ */ + +struct cliplist { + __u16 position; + __u8 enable; + __u8 disable; +}; + +static void set_cliplist(struct saa7134_dev *dev, int reg, + struct cliplist *cl, int entries, char *name) +{ + __u8 winbits = 0; + int i; + + for (i = 0; i < entries; i++) { + winbits |= cl[i].enable; + winbits &= ~cl[i].disable; + if (i < 15 && cl[i].position == cl[i+1].position) + continue; + saa_writeb(reg + 0, winbits); + saa_writeb(reg + 2, cl[i].position & 0xff); + saa_writeb(reg + 3, cl[i].position >> 8); + dprintk("clip: %s winbits=%02x pos=%d\n", + name,winbits,cl[i].position); + reg += 8; + } + for (; reg < 0x400; reg += 8) { + saa_writeb(reg+ 0, 0); + saa_writeb(reg + 1, 0); + saa_writeb(reg + 2, 0); + saa_writeb(reg + 3, 0); + } +} + +static int clip_range(int val) +{ + if (val < 0) + val = 0; + return val; +} + +/* Sort into smallest position first order */ +static int cliplist_cmp(const void *a, const void *b) +{ + const struct cliplist *cla = a; + const struct cliplist *clb = b; + if (cla->position < clb->position) + return -1; + if (cla->position > clb->position) + return 1; + return 0; +} + +static int setup_clipping(struct saa7134_dev *dev, struct v4l2_clip *clips, + int nclips, int interlace) +{ + struct cliplist col[16], row[16]; + int cols = 0, rows = 0, i; + int div = interlace ? 2 : 1; + + memset(col, 0, sizeof(col)); + memset(row, 0, sizeof(row)); + for (i = 0; i < nclips && i < 8; i++) { + col[cols].position = clip_range(clips[i].c.left); + col[cols].enable = (1 << i); + cols++; + col[cols].position = clip_range(clips[i].c.left+clips[i].c.width); + col[cols].disable = (1 << i); + cols++; + row[rows].position = clip_range(clips[i].c.top / div); + row[rows].enable = (1 << i); + rows++; + row[rows].position = clip_range((clips[i].c.top + clips[i].c.height) + / div); + row[rows].disable = (1 << i); + rows++; + } + sort(col, cols, sizeof col[0], cliplist_cmp, NULL); + sort(row, rows, sizeof row[0], cliplist_cmp, NULL); + set_cliplist(dev,0x380,col,cols,"cols"); + set_cliplist(dev,0x384,row,rows,"rows"); + return 0; +} + +static int verify_preview(struct saa7134_dev *dev, struct v4l2_window *win) +{ + enum v4l2_field field; + int maxw, maxh; + + if (NULL == dev->ovbuf.base) + return -EINVAL; + if (NULL == dev->ovfmt) + return -EINVAL; + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + if (win->clipcount > 2048) + return -EINVAL; + + field = win->field; + maxw = dev->crop_current.width; + maxh = dev->crop_current.height; + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + return 0; +} + +static int start_preview(struct saa7134_dev *dev, struct saa7134_fh *fh) +{ + unsigned long base,control,bpl; + int err; + + err = verify_preview(dev,&fh->win); + if (0 != err) + return err; + + dev->ovfield = fh->win.field; + dprintk("start_preview %dx%d+%d+%d %s field=%s\n", + fh->win.w.width,fh->win.w.height, + fh->win.w.left,fh->win.w.top, + dev->ovfmt->name,v4l2_field_names[dev->ovfield]); + + /* setup window + clipping */ + set_size(dev,TASK_B,fh->win.w.width,fh->win.w.height, + V4L2_FIELD_HAS_BOTH(dev->ovfield)); + setup_clipping(dev,fh->clips,fh->nclips, + V4L2_FIELD_HAS_BOTH(dev->ovfield)); + if (dev->ovfmt->yuv) + saa_andorb(SAA7134_DATA_PATH(TASK_B), 0x3f, 0x03); + else + saa_andorb(SAA7134_DATA_PATH(TASK_B), 0x3f, 0x01); + saa_writeb(SAA7134_OFMT_VIDEO_B, dev->ovfmt->pm | 0x20); + + /* dma: setup channel 1 (= Video Task B) */ + base = (unsigned long)dev->ovbuf.base; + base += dev->ovbuf.fmt.bytesperline * fh->win.w.top; + base += dev->ovfmt->depth/8 * fh->win.w.left; + bpl = dev->ovbuf.fmt.bytesperline; + control = SAA7134_RS_CONTROL_BURST_16; + if (dev->ovfmt->bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + if (dev->ovfmt->wswap) + control |= SAA7134_RS_CONTROL_WSWAP; + if (V4L2_FIELD_HAS_BOTH(dev->ovfield)) { + saa_writel(SAA7134_RS_BA1(1),base); + saa_writel(SAA7134_RS_BA2(1),base+bpl); + saa_writel(SAA7134_RS_PITCH(1),bpl*2); + saa_writel(SAA7134_RS_CONTROL(1),control); + } else { + saa_writel(SAA7134_RS_BA1(1),base); + saa_writel(SAA7134_RS_BA2(1),base); + saa_writel(SAA7134_RS_PITCH(1),bpl); + saa_writel(SAA7134_RS_CONTROL(1),control); + } + + /* start dma */ + dev->ovenable = 1; + saa7134_set_dmabits(dev); + + return 0; +} + +static int stop_preview(struct saa7134_dev *dev, struct saa7134_fh *fh) +{ + dev->ovenable = 0; + saa7134_set_dmabits(dev); + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + unsigned long base,control,bpl; + unsigned long bpl_uv,lines_uv,base2,base3,tmp; /* planar */ + + dprintk("buffer_activate buf=%p\n",buf); + buf->vb.state = VIDEOBUF_ACTIVE; + buf->top_seen = 0; + + set_size(dev,TASK_A,buf->vb.width,buf->vb.height, + V4L2_FIELD_HAS_BOTH(buf->vb.field)); + if (buf->fmt->yuv) + saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x03); + else + saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x01); + saa_writeb(SAA7134_OFMT_VIDEO_A, buf->fmt->pm); + + /* DMA: setup channel 0 (= Video Task A0) */ + base = saa7134_buffer_base(buf); + if (buf->fmt->planar) + bpl = buf->vb.width; + else + bpl = (buf->vb.width * buf->fmt->depth) / 8; + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (buf->pt->dma >> 12); + if (buf->fmt->bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + if (buf->fmt->wswap) + control |= SAA7134_RS_CONTROL_WSWAP; + if (V4L2_FIELD_HAS_BOTH(buf->vb.field)) { + /* interlaced */ + saa_writel(SAA7134_RS_BA1(0),base); + saa_writel(SAA7134_RS_BA2(0),base+bpl); + saa_writel(SAA7134_RS_PITCH(0),bpl*2); + } else { + /* non-interlaced */ + saa_writel(SAA7134_RS_BA1(0),base); + saa_writel(SAA7134_RS_BA2(0),base); + saa_writel(SAA7134_RS_PITCH(0),bpl); + } + saa_writel(SAA7134_RS_CONTROL(0),control); + + if (buf->fmt->planar) { + /* DMA: setup channel 4+5 (= planar task A) */ + bpl_uv = bpl >> buf->fmt->hshift; + lines_uv = buf->vb.height >> buf->fmt->vshift; + base2 = base + bpl * buf->vb.height; + base3 = base2 + bpl_uv * lines_uv; + if (buf->fmt->uvswap) + tmp = base2, base2 = base3, base3 = tmp; + dprintk("uv: bpl=%ld lines=%ld base2/3=%ld/%ld\n", + bpl_uv,lines_uv,base2,base3); + if (V4L2_FIELD_HAS_BOTH(buf->vb.field)) { + /* interlaced */ + saa_writel(SAA7134_RS_BA1(4),base2); + saa_writel(SAA7134_RS_BA2(4),base2+bpl_uv); + saa_writel(SAA7134_RS_PITCH(4),bpl_uv*2); + saa_writel(SAA7134_RS_BA1(5),base3); + saa_writel(SAA7134_RS_BA2(5),base3+bpl_uv); + saa_writel(SAA7134_RS_PITCH(5),bpl_uv*2); + } else { + /* non-interlaced */ + saa_writel(SAA7134_RS_BA1(4),base2); + saa_writel(SAA7134_RS_BA2(4),base2); + saa_writel(SAA7134_RS_PITCH(4),bpl_uv); + saa_writel(SAA7134_RS_BA1(5),base3); + saa_writel(SAA7134_RS_BA2(5),base3); + saa_writel(SAA7134_RS_PITCH(5),bpl_uv); + } + saa_writel(SAA7134_RS_CONTROL(4),control); + saa_writel(SAA7134_RS_CONTROL(5),control); + } + + /* start DMA */ + saa7134_set_dmabits(dev); + mod_timer(&dev->video_q.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct saa7134_fh *fh = q->priv_data; + struct saa7134_dev *dev = fh->dev; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + unsigned int size; + int err; + + /* sanity checks */ + if (NULL == fh->fmt) + return -EINVAL; + if (fh->width < 48 || + fh->height < 32 || + fh->width/4 > dev->crop_current.width || + fh->height/4 > dev->crop_current.height || + fh->width > dev->crop_bounds.width || + fh->height > dev->crop_bounds.height) + return -EINVAL; + size = (fh->width * fh->height * fh->fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + dprintk("buffer_prepare [%d,size=%dx%d,bytes=%d,fields=%s,%s]\n", + vb->i,fh->width,fh->height,size,v4l2_field_names[field], + fh->fmt->name); + if (buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.size != size || + buf->vb.field != field || + buf->fmt != fh->fmt) { + saa7134_dma_free(q,buf); + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.size = size; + buf->vb.field = field; + buf->fmt = fh->fmt; + buf->pt = &fh->pt_cap; + dev->video_q.curr = NULL; + + err = videobuf_iolock(q,&buf->vb,&dev->ovbuf); + if (err) + goto oops; + err = saa7134_pgtable_build(dev->pci,buf->pt, + dma->sglist, + dma->sglen, + saa7134_buffer_startpage(buf)); + if (err) + goto oops; + } + buf->vb.state = VIDEOBUF_PREPARED; + buf->activate = buffer_activate; + return 0; + + oops: + saa7134_dma_free(q,buf); + return err; +} + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct saa7134_fh *fh = q->priv_data; + + *size = fh->fmt->depth * fh->width * fh->height >> 3; + if (0 == *count) + *count = gbuffers; + *count = saa7134_buffer_count(*size,*count); + return 0; +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct saa7134_fh *fh = q->priv_data; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + + saa7134_buffer_queue(fh->dev,&fh->dev->video_q,buf); +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + + saa7134_dma_free(q,buf); +} + +static struct videobuf_queue_ops video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +int saa7134_g_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c) +{ + const struct v4l2_queryctrl* ctrl; + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + return -EINVAL; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = dev->ctl_bright; + break; + case V4L2_CID_HUE: + c->value = dev->ctl_hue; + break; + case V4L2_CID_CONTRAST: + c->value = dev->ctl_contrast; + break; + case V4L2_CID_SATURATION: + c->value = dev->ctl_saturation; + break; + case V4L2_CID_AUDIO_MUTE: + c->value = dev->ctl_mute; + break; + case V4L2_CID_AUDIO_VOLUME: + c->value = dev->ctl_volume; + break; + case V4L2_CID_PRIVATE_INVERT: + c->value = dev->ctl_invert; + break; + case V4L2_CID_HFLIP: + c->value = dev->ctl_mirror; + break; + case V4L2_CID_PRIVATE_Y_EVEN: + c->value = dev->ctl_y_even; + break; + case V4L2_CID_PRIVATE_Y_ODD: + c->value = dev->ctl_y_odd; + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + c->value = dev->ctl_automute; + break; + default: + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_g_ctrl_internal); + +static int saa7134_g_ctrl(struct file *file, void *priv, struct v4l2_control *c) +{ + struct saa7134_fh *fh = priv; + + return saa7134_g_ctrl_internal(fh->dev, fh, c); +} + +int saa7134_s_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c) +{ + const struct v4l2_queryctrl* ctrl; + unsigned long flags; + int restart_overlay = 0; + int err; + + /* When called from the empress code fh == NULL. + That needs to be fixed somehow, but for now this is + good enough. */ + if (fh) { + err = v4l2_prio_check(&dev->prio, fh->prio); + if (0 != err) + return err; + } + err = -EINVAL; + + mutex_lock(&dev->lock); + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + goto error; + + dprintk("set_control name=%s val=%d\n",ctrl->name,c->value); + switch (ctrl->type) { + case V4L2_CTRL_TYPE_BOOLEAN: + case V4L2_CTRL_TYPE_MENU: + case V4L2_CTRL_TYPE_INTEGER: + if (c->value < ctrl->minimum) + c->value = ctrl->minimum; + if (c->value > ctrl->maximum) + c->value = ctrl->maximum; + break; + default: + /* nothing */; + }; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + dev->ctl_bright = c->value; + saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright); + break; + case V4L2_CID_HUE: + dev->ctl_hue = c->value; + saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue); + break; + case V4L2_CID_CONTRAST: + dev->ctl_contrast = c->value; + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + break; + case V4L2_CID_SATURATION: + dev->ctl_saturation = c->value; + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + break; + case V4L2_CID_AUDIO_MUTE: + dev->ctl_mute = c->value; + saa7134_tvaudio_setmute(dev); + break; + case V4L2_CID_AUDIO_VOLUME: + dev->ctl_volume = c->value; + saa7134_tvaudio_setvolume(dev,dev->ctl_volume); + break; + case V4L2_CID_PRIVATE_INVERT: + dev->ctl_invert = c->value; + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + break; + case V4L2_CID_HFLIP: + dev->ctl_mirror = c->value; + restart_overlay = 1; + break; + case V4L2_CID_PRIVATE_Y_EVEN: + dev->ctl_y_even = c->value; + restart_overlay = 1; + break; + case V4L2_CID_PRIVATE_Y_ODD: + dev->ctl_y_odd = c->value; + restart_overlay = 1; + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + { + struct v4l2_priv_tun_config tda9887_cfg; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &dev->tda9887_conf; + + dev->ctl_automute = c->value; + if (dev->tda9887_conf) { + if (dev->ctl_automute) + dev->tda9887_conf |= TDA9887_AUTOMUTE; + else + dev->tda9887_conf &= ~TDA9887_AUTOMUTE; + + saa_call_all(dev, tuner, s_config, &tda9887_cfg); + } + break; + } + default: + goto error; + } + if (restart_overlay && fh && res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + start_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + err = 0; + +error: + mutex_unlock(&dev->lock); + return err; +} +EXPORT_SYMBOL_GPL(saa7134_s_ctrl_internal); + +static int saa7134_s_ctrl(struct file *file, void *f, struct v4l2_control *c) +{ + struct saa7134_fh *fh = f; + + return saa7134_s_ctrl_internal(fh->dev, fh, c); +} + +/* ------------------------------------------------------------------ */ + +static struct videobuf_queue* saa7134_queue(struct saa7134_fh *fh) +{ + struct videobuf_queue* q = NULL; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + q = &fh->cap; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + q = &fh->vbi; + break; + default: + BUG(); + } + return q; +} + +static int saa7134_resource(struct saa7134_fh *fh) +{ + if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return RESOURCE_VIDEO; + + if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return RESOURCE_VBI; + + BUG(); + return 0; +} + +static int video_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct saa7134_dev *dev = video_drvdata(file); + struct saa7134_fh *fh; + enum v4l2_buf_type type = 0; + int radio = 0; + + switch (vdev->vfl_type) { + case VFL_TYPE_GRABBER: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + case VFL_TYPE_VBI: + type = V4L2_BUF_TYPE_VBI_CAPTURE; + break; + case VFL_TYPE_RADIO: + radio = 1; + break; + } + + dprintk("open dev=%s radio=%d type=%s\n", video_device_node_name(vdev), + radio, v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + + file->private_data = fh; + fh->dev = dev; + fh->radio = radio; + fh->type = type; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + fh->width = 720; + fh->height = 576; + v4l2_prio_open(&dev->prio, &fh->prio); + + videobuf_queue_sg_init(&fh->cap, &video_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct saa7134_buf), + fh, NULL); + videobuf_queue_sg_init(&fh->vbi, &saa7134_vbi_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct saa7134_buf), + fh, NULL); + saa7134_pgtable_alloc(dev->pci,&fh->pt_cap); + saa7134_pgtable_alloc(dev->pci,&fh->pt_vbi); + + if (fh->radio) { + /* switch to radio mode */ + saa7134_tvaudio_setinput(dev,&card(dev).radio); + saa_call_all(dev, tuner, s_radio); + } else { + /* switch to video/vbi mode */ + video_mux(dev,dev->ctl_input); + } + return 0; +} + +static ssize_t +video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct saa7134_fh *fh = file->private_data; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (res_locked(fh->dev,RESOURCE_VIDEO)) + return -EBUSY; + return videobuf_read_one(saa7134_queue(fh), + data, count, ppos, + file->f_flags & O_NONBLOCK); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return -EBUSY; + return videobuf_read_stream(saa7134_queue(fh), + data, count, ppos, 1, + file->f_flags & O_NONBLOCK); + break; + default: + BUG(); + return 0; + } +} + +static unsigned int +video_poll(struct file *file, struct poll_table_struct *wait) +{ + struct saa7134_fh *fh = file->private_data; + struct videobuf_buffer *buf = NULL; + unsigned int rc = 0; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) + return videobuf_poll_stream(file, &fh->vbi, wait); + + if (res_check(fh,RESOURCE_VIDEO)) { + mutex_lock(&fh->cap.vb_lock); + if (!list_empty(&fh->cap.stream)) + buf = list_entry(fh->cap.stream.next, struct videobuf_buffer, stream); + } else { + mutex_lock(&fh->cap.vb_lock); + if (UNSET == fh->cap.read_off) { + /* need to capture a new frame */ + if (res_locked(fh->dev,RESOURCE_VIDEO)) + goto err; + if (0 != fh->cap.ops->buf_prepare(&fh->cap,fh->cap.read_buf,fh->cap.field)) + goto err; + fh->cap.ops->buf_queue(&fh->cap,fh->cap.read_buf); + fh->cap.read_off = 0; + } + buf = fh->cap.read_buf; + } + + if (!buf) + goto err; + + poll_wait(file, &buf->done, wait); + if (buf->state == VIDEOBUF_DONE || + buf->state == VIDEOBUF_ERROR) + rc = POLLIN|POLLRDNORM; + mutex_unlock(&fh->cap.vb_lock); + return rc; + +err: + mutex_unlock(&fh->cap.vb_lock); + return POLLERR; +} + +static int video_release(struct file *file) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + struct saa6588_command cmd; + unsigned long flags; + + saa7134_tvaudio_close(dev); + + /* turn off overlay */ + if (res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + res_free(dev,fh,RESOURCE_OVERLAY); + } + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + videobuf_streamoff(&fh->cap); + res_free(dev,fh,RESOURCE_VIDEO); + } + if (fh->cap.read_buf) { + buffer_release(&fh->cap,fh->cap.read_buf); + kfree(fh->cap.read_buf); + } + + /* stop vbi capture */ + if (res_check(fh, RESOURCE_VBI)) { + videobuf_stop(&fh->vbi); + res_free(dev,fh,RESOURCE_VBI); + } + + /* ts-capture will not work in planar mode, so turn it off Hac: 04.05*/ + saa_andorb(SAA7134_OFMT_VIDEO_A, 0x1f, 0); + saa_andorb(SAA7134_OFMT_VIDEO_B, 0x1f, 0); + saa_andorb(SAA7134_OFMT_DATA_A, 0x1f, 0); + saa_andorb(SAA7134_OFMT_DATA_B, 0x1f, 0); + + saa_call_all(dev, core, s_power, 0); + if (fh->radio) + saa_call_all(dev, core, ioctl, SAA6588_CMD_CLOSE, &cmd); + + /* free stuff */ + videobuf_mmap_free(&fh->cap); + videobuf_mmap_free(&fh->vbi); + saa7134_pgtable_free(dev->pci,&fh->pt_cap); + saa7134_pgtable_free(dev->pci,&fh->pt_vbi); + + v4l2_prio_close(&dev->prio, fh->prio); + file->private_data = NULL; + kfree(fh); + return 0; +} + +static int video_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct saa7134_fh *fh = file->private_data; + + return videobuf_mmap_mapper(saa7134_queue(fh), vma); +} + +static ssize_t radio_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + struct saa6588_command cmd; + + cmd.block_count = count/3; + cmd.buffer = data; + cmd.instance = file; + cmd.result = -ENODEV; + + saa_call_all(dev, core, ioctl, SAA6588_CMD_READ, &cmd); + + return cmd.result; +} + +static unsigned int radio_poll(struct file *file, poll_table *wait) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + struct saa6588_command cmd; + + cmd.instance = file; + cmd.event_list = wait; + cmd.result = -ENODEV; + saa_call_all(dev, core, ioctl, SAA6588_CMD_POLL, &cmd); + + return cmd.result; +} + +/* ------------------------------------------------------------------ */ + +static int saa7134_try_get_set_fmt_vbi_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + struct saa7134_tvnorm *norm = dev->tvnorm; + + f->fmt.vbi.sampling_rate = 6750000 * 4; + f->fmt.vbi.samples_per_line = 2048 /* VBI_LINE_LENGTH */; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 64 * 4; + f->fmt.vbi.start[0] = norm->vbi_v_start_0; + f->fmt.vbi.count[0] = norm->vbi_v_stop_0 - norm->vbi_v_start_0 +1; + f->fmt.vbi.start[1] = norm->vbi_v_start_1; + f->fmt.vbi.count[1] = f->fmt.vbi.count[0]; + f->fmt.vbi.flags = 0; /* VBI_UNSYNC VBI_INTERLACED */ + + return 0; +} + +static int saa7134_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_fh *fh = priv; + + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->cap.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; +} + +static int saa7134_g_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_fh *fh = priv; + + if (saa7134_no_overlay > 0) { + printk(KERN_ERR "V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n"); + return -EINVAL; + } + f->fmt.win = fh->win; + + return 0; +} + +static int saa7134_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + struct saa7134_format *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = min(dev->crop_current.width*4, dev->crop_bounds.width); + maxh = min(dev->crop_current.height*4, dev->crop_bounds.height); + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + f->fmt.pix.width &= ~0x03; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int saa7134_try_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + + if (saa7134_no_overlay > 0) { + printk(KERN_ERR "V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n"); + return -EINVAL; + } + + return verify_preview(dev, &f->fmt.win); +} + +static int saa7134_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_fh *fh = priv; + int err; + + err = saa7134_try_fmt_vid_cap(file, priv, f); + if (0 != err) + return err; + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->cap.field = f->fmt.pix.field; + return 0; +} + +static int saa7134_s_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + int err; + unsigned long flags; + + if (saa7134_no_overlay > 0) { + printk(KERN_ERR "V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n"); + return -EINVAL; + } + err = verify_preview(dev, &f->fmt.win); + if (0 != err) + return err; + + mutex_lock(&dev->lock); + + fh->win = f->fmt.win; + fh->nclips = f->fmt.win.clipcount; + + if (fh->nclips > 8) + fh->nclips = 8; + + if (copy_from_user(fh->clips, f->fmt.win.clips, + sizeof(struct v4l2_clip)*fh->nclips)) { + mutex_unlock(&dev->lock); + return -EFAULT; + } + + if (res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock, flags); + stop_preview(dev, fh); + start_preview(dev, fh); + spin_unlock_irqrestore(&dev->slock, flags); + } + + mutex_unlock(&dev->lock); + return 0; +} + +int saa7134_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *c) +{ + const struct v4l2_queryctrl *ctrl; + + if ((c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) && + (c->id < V4L2_CID_PRIVATE_BASE || + c->id >= V4L2_CID_PRIVATE_LASTP1)) + return -EINVAL; + ctrl = ctrl_by_id(c->id); + *c = (NULL != ctrl) ? *ctrl : no_ctrl; + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_queryctrl); + +static int saa7134_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + unsigned int n; + + n = i->index; + if (n >= SAA7134_INPUT_MAX) + return -EINVAL; + if (NULL == card_in(dev, i->index).name) + return -EINVAL; + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name, card_in(dev, n).name); + if (card_in(dev, n).tv) + i->type = V4L2_INPUT_TYPE_TUNER; + i->audioset = 1; + if (n == dev->ctl_input) { + int v1 = saa_readb(SAA7134_STATUS_VIDEO1); + int v2 = saa_readb(SAA7134_STATUS_VIDEO2); + + if (0 != (v1 & 0x40)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + if (0 != (v2 & 0x40)) + i->status |= V4L2_IN_ST_NO_SYNC; + if (0 != (v2 & 0x0e)) + i->status |= V4L2_IN_ST_MACROVISION; + } + i->std = SAA7134_NORMS; + return 0; +} + +static int saa7134_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + + *i = dev->ctl_input; + return 0; +} + +static int saa7134_s_input(struct file *file, void *priv, unsigned int i) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + int err; + + err = v4l2_prio_check(&dev->prio, fh->prio); + if (0 != err) + return err; + + if (i >= SAA7134_INPUT_MAX) + return -EINVAL; + if (NULL == card_in(dev, i).name) + return -EINVAL; + mutex_lock(&dev->lock); + video_mux(dev, i); + mutex_unlock(&dev->lock); + return 0; +} + +static int saa7134_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + + unsigned int tuner_type = dev->tuner_type; + + strcpy(cap->driver, "saa7134"); + strlcpy(cap->card, saa7134_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | + V4L2_CAP_TUNER; + if (dev->has_rds) + cap->capabilities |= V4L2_CAP_RDS_CAPTURE; + if (saa7134_no_overlay <= 0) + cap->capabilities |= V4L2_CAP_VIDEO_OVERLAY; + + if ((tuner_type == TUNER_ABSENT) || (tuner_type == UNSET)) + cap->capabilities &= ~V4L2_CAP_TUNER; + return 0; +} + +int saa7134_s_std_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, v4l2_std_id *id) +{ + unsigned long flags; + unsigned int i; + v4l2_std_id fixup; + int err; + + /* When called from the empress code fh == NULL. + That needs to be fixed somehow, but for now this is + good enough. */ + if (fh) { + err = v4l2_prio_check(&dev->prio, fh->prio); + if (0 != err) + return err; + } else if (res_locked(dev, RESOURCE_OVERLAY)) { + /* Don't change the std from the mpeg device + if overlay is active. */ + return -EBUSY; + } + + for (i = 0; i < TVNORMS; i++) + if (*id == tvnorms[i].id) + break; + + if (i == TVNORMS) + for (i = 0; i < TVNORMS; i++) + if (*id & tvnorms[i].id) + break; + if (i == TVNORMS) + return -EINVAL; + + if ((*id & V4L2_STD_SECAM) && (secam[0] != '-')) { + if (secam[0] == 'L' || secam[0] == 'l') { + if (secam[1] == 'C' || secam[1] == 'c') + fixup = V4L2_STD_SECAM_LC; + else + fixup = V4L2_STD_SECAM_L; + } else { + if (secam[0] == 'D' || secam[0] == 'd') + fixup = V4L2_STD_SECAM_DK; + else + fixup = V4L2_STD_SECAM; + } + for (i = 0; i < TVNORMS; i++) { + if (fixup == tvnorms[i].id) + break; + } + if (i == TVNORMS) + return -EINVAL; + } + + *id = tvnorms[i].id; + + mutex_lock(&dev->lock); + if (fh && res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock, flags); + stop_preview(dev, fh); + spin_unlock_irqrestore(&dev->slock, flags); + + set_tvnorm(dev, &tvnorms[i]); + + spin_lock_irqsave(&dev->slock, flags); + start_preview(dev, fh); + spin_unlock_irqrestore(&dev->slock, flags); + } else + set_tvnorm(dev, &tvnorms[i]); + + saa7134_tvaudio_do_scan(dev); + mutex_unlock(&dev->lock); + return 0; +} +EXPORT_SYMBOL_GPL(saa7134_s_std_internal); + +static int saa7134_s_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct saa7134_fh *fh = priv; + + return saa7134_s_std_internal(fh->dev, fh, id); +} + +static int saa7134_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + + *id = dev->tvnorm->id; + return 0; +} + +static int saa7134_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *cap) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + cap->bounds = dev->crop_bounds; + cap->defrect = dev->crop_defrect; + cap->pixelaspect.numerator = 1; + cap->pixelaspect.denominator = 1; + if (dev->tvnorm->id & V4L2_STD_525_60) { + cap->pixelaspect.numerator = 11; + cap->pixelaspect.denominator = 10; + } + if (dev->tvnorm->id & V4L2_STD_625_50) { + cap->pixelaspect.numerator = 54; + cap->pixelaspect.denominator = 59; + } + return 0; +} + +static int saa7134_g_crop(struct file *file, void *f, struct v4l2_crop *crop) +{ + struct saa7134_fh *fh = f; + struct saa7134_dev *dev = fh->dev; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + crop->c = dev->crop_current; + return 0; +} + +static int saa7134_s_crop(struct file *file, void *f, const struct v4l2_crop *crop) +{ + struct saa7134_fh *fh = f; + struct saa7134_dev *dev = fh->dev; + struct v4l2_rect *b = &dev->crop_bounds; + struct v4l2_rect *c = &dev->crop_current; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + if (crop->c.height < 0) + return -EINVAL; + if (crop->c.width < 0) + return -EINVAL; + + if (res_locked(fh->dev, RESOURCE_OVERLAY)) + return -EBUSY; + if (res_locked(fh->dev, RESOURCE_VIDEO)) + return -EBUSY; + + *c = crop->c; + if (c->top < b->top) + c->top = b->top; + if (c->top > b->top + b->height) + c->top = b->top + b->height; + if (c->height > b->top - c->top + b->height) + c->height = b->top - c->top + b->height; + + if (c->left < b->left) + c->left = b->left; + if (c->left > b->left + b->width) + c->left = b->left + b->width; + if (c->width > b->left - c->left + b->width) + c->width = b->left - c->left + b->width; + return 0; +} + +static int saa7134_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + int n; + + if (0 != t->index) + return -EINVAL; + memset(t, 0, sizeof(*t)); + for (n = 0; n < SAA7134_INPUT_MAX; n++) { + if (card_in(dev, n).tv) + break; + } + if (n == SAA7134_INPUT_MAX) + return -EINVAL; + if (NULL != card_in(dev, n).name) { + strcpy(t->name, "Television"); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM | + V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2; + t->rangehigh = 0xffffffffUL; + t->rxsubchans = saa7134_tvaudio_getstereo(dev); + t->audmode = saa7134_tvaudio_rx2mode(t->rxsubchans); + } + if (0 != (saa_readb(SAA7134_STATUS_VIDEO1) & 0x03)) + t->signal = 0xffff; + return 0; +} + +static int saa7134_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + int rx, mode, err; + + err = v4l2_prio_check(&dev->prio, fh->prio); + if (0 != err) + return err; + + mode = dev->thread.mode; + if (UNSET == mode) { + rx = saa7134_tvaudio_getstereo(dev); + mode = saa7134_tvaudio_rx2mode(rx); + } + if (mode != t->audmode) + dev->thread.mode = t->audmode; + + return 0; +} + +static int saa7134_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + + f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + f->frequency = dev->ctl_freq; + + return 0; +} + +static int saa7134_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + int err; + + err = v4l2_prio_check(&dev->prio, fh->prio); + if (0 != err) + return err; + + if (0 != f->tuner) + return -EINVAL; + if (0 == fh->radio && V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + if (1 == fh->radio && V4L2_TUNER_RADIO != f->type) + return -EINVAL; + mutex_lock(&dev->lock); + dev->ctl_freq = f->frequency; + + saa_call_all(dev, tuner, s_frequency, f); + + saa7134_tvaudio_do_scan(dev); + mutex_unlock(&dev->lock); + return 0; +} + +static int saa7134_g_audio(struct file *file, void *priv, struct v4l2_audio *a) +{ + strcpy(a->name, "audio"); + return 0; +} + +static int saa7134_s_audio(struct file *file, void *priv, const struct v4l2_audio *a) +{ + return 0; +} + +static int saa7134_g_priority(struct file *file, void *f, enum v4l2_priority *p) +{ + struct saa7134_fh *fh = f; + struct saa7134_dev *dev = fh->dev; + + *p = v4l2_prio_max(&dev->prio); + return 0; +} + +static int saa7134_s_priority(struct file *file, void *f, + enum v4l2_priority prio) +{ + struct saa7134_fh *fh = f; + struct saa7134_dev *dev = fh->dev; + + return v4l2_prio_change(&dev->prio, &fh->prio, prio); +} + +static int saa7134_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= FORMATS) + return -EINVAL; + + strlcpy(f->description, formats[f->index].name, + sizeof(f->description)); + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static int saa7134_enum_fmt_vid_overlay(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (saa7134_no_overlay > 0) { + printk(KERN_ERR "V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n"); + return -EINVAL; + } + + if ((f->index >= FORMATS) || formats[f->index].planar) + return -EINVAL; + + strlcpy(f->description, formats[f->index].name, + sizeof(f->description)); + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static int saa7134_g_fbuf(struct file *file, void *f, + struct v4l2_framebuffer *fb) +{ + struct saa7134_fh *fh = f; + struct saa7134_dev *dev = fh->dev; + + *fb = dev->ovbuf; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + + return 0; +} + +static int saa7134_s_fbuf(struct file *file, void *f, + const struct v4l2_framebuffer *fb) +{ + struct saa7134_fh *fh = f; + struct saa7134_dev *dev = fh->dev; + struct saa7134_format *fmt; + + if (!capable(CAP_SYS_ADMIN) && + !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = format_by_fourcc(fb->fmt.pixelformat); + if (NULL == fmt) + return -EINVAL; + + /* ok, accept it */ + dev->ovbuf = *fb; + dev->ovfmt = fmt; + if (0 == dev->ovbuf.fmt.bytesperline) + dev->ovbuf.fmt.bytesperline = + dev->ovbuf.fmt.width*fmt->depth/8; + return 0; +} + +static int saa7134_overlay(struct file *file, void *f, unsigned int on) +{ + struct saa7134_fh *fh = f; + struct saa7134_dev *dev = fh->dev; + unsigned long flags; + + if (on) { + if (saa7134_no_overlay > 0) { + dprintk("no_overlay\n"); + return -EINVAL; + } + + if (!res_get(dev, fh, RESOURCE_OVERLAY)) + return -EBUSY; + spin_lock_irqsave(&dev->slock, flags); + start_preview(dev, fh); + spin_unlock_irqrestore(&dev->slock, flags); + } + if (!on) { + if (!res_check(fh, RESOURCE_OVERLAY)) + return -EINVAL; + spin_lock_irqsave(&dev->slock, flags); + stop_preview(dev, fh); + spin_unlock_irqrestore(&dev->slock, flags); + res_free(dev, fh, RESOURCE_OVERLAY); + } + return 0; +} + +static int saa7134_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct saa7134_fh *fh = priv; + return videobuf_reqbufs(saa7134_queue(fh), p); +} + +static int saa7134_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct saa7134_fh *fh = priv; + return videobuf_querybuf(saa7134_queue(fh), b); +} + +static int saa7134_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct saa7134_fh *fh = priv; + return videobuf_qbuf(saa7134_queue(fh), b); +} + +static int saa7134_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct saa7134_fh *fh = priv; + return videobuf_dqbuf(saa7134_queue(fh), b, + file->f_flags & O_NONBLOCK); +} + +static int saa7134_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + int res = saa7134_resource(fh); + + if (!res_get(dev, fh, res)) + return -EBUSY; + + return videobuf_streamon(saa7134_queue(fh)); +} + +static int saa7134_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + int err; + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + int res = saa7134_resource(fh); + + err = videobuf_streamoff(saa7134_queue(fh)); + if (err < 0) + return err; + res_free(dev, fh, res); + return 0; +} + +static int saa7134_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *parm) +{ + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register (struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + reg->val = saa_readb(reg->reg); + reg->size = 1; + return 0; +} + +static int vidioc_s_register (struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct saa7134_fh *fh = priv; + struct saa7134_dev *dev = fh->dev; + + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; + saa_writeb(reg->reg&0xffffff, reg->val); + return 0; +} +#endif + +static int radio_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + + strcpy(cap->driver, "saa7134"); + strlcpy(cap->card, saa7134_boards[dev->board].name, sizeof(cap->card)); + sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); + cap->capabilities = V4L2_CAP_TUNER; + return 0; +} + +static int radio_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + + if (0 != t->index) + return -EINVAL; + + memset(t, 0, sizeof(*t)); + strcpy(t->name, "Radio"); + t->type = V4L2_TUNER_RADIO; + + saa_call_all(dev, tuner, g_tuner, t); + if (dev->input->amux == TV) { + t->signal = 0xf800 - ((saa_readb(0x581) & 0x1f) << 11); + t->rxsubchans = (saa_readb(0x529) & 0x08) ? + V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; + } + return 0; +} +static int radio_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + + if (0 != t->index) + return -EINVAL; + + saa_call_all(dev, tuner, s_tuner, t); + return 0; +} + +static int radio_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + + strcpy(i->name, "Radio"); + i->type = V4L2_INPUT_TYPE_TUNER; + + return 0; +} + +static int radio_g_input(struct file *filp, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int radio_g_audio(struct file *file, void *priv, + struct v4l2_audio *a) +{ + memset(a, 0, sizeof(*a)); + strcpy(a->name, "Radio"); + return 0; +} + +static int radio_s_audio(struct file *file, void *priv, + const struct v4l2_audio *a) +{ + return 0; +} + +static int radio_s_input(struct file *filp, void *priv, unsigned int i) +{ + return 0; +} + +static int radio_s_std(struct file *file, void *fh, v4l2_std_id *norm) +{ + return 0; +} + +static int radio_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *c) +{ + const struct v4l2_queryctrl *ctrl; + + if (c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) + return -EINVAL; + if (c->id == V4L2_CID_AUDIO_MUTE) { + ctrl = ctrl_by_id(c->id); + *c = *ctrl; + } else + *c = no_ctrl; + return 0; +} + +static const struct v4l2_file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = saa7134_querycap, + .vidioc_enum_fmt_vid_cap = saa7134_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = saa7134_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = saa7134_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = saa7134_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_overlay = saa7134_enum_fmt_vid_overlay, + .vidioc_g_fmt_vid_overlay = saa7134_g_fmt_vid_overlay, + .vidioc_try_fmt_vid_overlay = saa7134_try_fmt_vid_overlay, + .vidioc_s_fmt_vid_overlay = saa7134_s_fmt_vid_overlay, + .vidioc_g_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap, + .vidioc_try_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap, + .vidioc_s_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap, + .vidioc_g_audio = saa7134_g_audio, + .vidioc_s_audio = saa7134_s_audio, + .vidioc_cropcap = saa7134_cropcap, + .vidioc_reqbufs = saa7134_reqbufs, + .vidioc_querybuf = saa7134_querybuf, + .vidioc_qbuf = saa7134_qbuf, + .vidioc_dqbuf = saa7134_dqbuf, + .vidioc_s_std = saa7134_s_std, + .vidioc_g_std = saa7134_g_std, + .vidioc_enum_input = saa7134_enum_input, + .vidioc_g_input = saa7134_g_input, + .vidioc_s_input = saa7134_s_input, + .vidioc_queryctrl = saa7134_queryctrl, + .vidioc_g_ctrl = saa7134_g_ctrl, + .vidioc_s_ctrl = saa7134_s_ctrl, + .vidioc_streamon = saa7134_streamon, + .vidioc_streamoff = saa7134_streamoff, + .vidioc_g_tuner = saa7134_g_tuner, + .vidioc_s_tuner = saa7134_s_tuner, + .vidioc_g_crop = saa7134_g_crop, + .vidioc_s_crop = saa7134_s_crop, + .vidioc_g_fbuf = saa7134_g_fbuf, + .vidioc_s_fbuf = saa7134_s_fbuf, + .vidioc_overlay = saa7134_overlay, + .vidioc_g_priority = saa7134_g_priority, + .vidioc_s_priority = saa7134_s_priority, + .vidioc_g_parm = saa7134_g_parm, + .vidioc_g_frequency = saa7134_g_frequency, + .vidioc_s_frequency = saa7134_s_frequency, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +static const struct v4l2_file_operations radio_fops = { + .owner = THIS_MODULE, + .open = video_open, + .read = radio_read, + .release = video_release, + .ioctl = video_ioctl2, + .poll = radio_poll, +}; + +static const struct v4l2_ioctl_ops radio_ioctl_ops = { + .vidioc_querycap = radio_querycap, + .vidioc_g_tuner = radio_g_tuner, + .vidioc_enum_input = radio_enum_input, + .vidioc_g_audio = radio_g_audio, + .vidioc_s_tuner = radio_s_tuner, + .vidioc_s_audio = radio_s_audio, + .vidioc_s_input = radio_s_input, + .vidioc_s_std = radio_s_std, + .vidioc_queryctrl = radio_queryctrl, + .vidioc_g_input = radio_g_input, + .vidioc_g_ctrl = saa7134_g_ctrl, + .vidioc_s_ctrl = saa7134_s_ctrl, + .vidioc_g_frequency = saa7134_g_frequency, + .vidioc_s_frequency = saa7134_s_frequency, +}; + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +struct video_device saa7134_video_template = { + .name = "saa7134-video", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = SAA7134_NORMS, + .current_norm = V4L2_STD_PAL, +}; + +struct video_device saa7134_radio_template = { + .name = "saa7134-radio", + .fops = &radio_fops, + .ioctl_ops = &radio_ioctl_ops, +}; + +int saa7134_video_init1(struct saa7134_dev *dev) +{ + /* sanitycheck insmod options */ + if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) + gbuffers = 2; + if (gbufsize < 0 || gbufsize > gbufsize_max) + gbufsize = gbufsize_max; + gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; + + /* put some sensible defaults into the data structures ... */ + dev->ctl_bright = ctrl_by_id(V4L2_CID_BRIGHTNESS)->default_value; + dev->ctl_contrast = ctrl_by_id(V4L2_CID_CONTRAST)->default_value; + dev->ctl_hue = ctrl_by_id(V4L2_CID_HUE)->default_value; + dev->ctl_saturation = ctrl_by_id(V4L2_CID_SATURATION)->default_value; + dev->ctl_volume = ctrl_by_id(V4L2_CID_AUDIO_VOLUME)->default_value; + dev->ctl_mute = 1; // ctrl_by_id(V4L2_CID_AUDIO_MUTE)->default_value; + dev->ctl_invert = ctrl_by_id(V4L2_CID_PRIVATE_INVERT)->default_value; + dev->ctl_automute = ctrl_by_id(V4L2_CID_PRIVATE_AUTOMUTE)->default_value; + + if (dev->tda9887_conf && dev->ctl_automute) + dev->tda9887_conf |= TDA9887_AUTOMUTE; + dev->automute = 0; + + INIT_LIST_HEAD(&dev->video_q.queue); + init_timer(&dev->video_q.timeout); + dev->video_q.timeout.function = saa7134_buffer_timeout; + dev->video_q.timeout.data = (unsigned long)(&dev->video_q); + dev->video_q.dev = dev; + + if (saa7134_boards[dev->board].video_out) + saa7134_videoport_init(dev); + + return 0; +} + +int saa7134_videoport_init(struct saa7134_dev *dev) +{ + /* enable video output */ + int vo = saa7134_boards[dev->board].video_out; + int video_reg; + unsigned int vid_port_opts = saa7134_boards[dev->board].vid_port_opts; + + /* Configure videoport */ + saa_writeb(SAA7134_VIDEO_PORT_CTRL0, video_out[vo][0]); + video_reg = video_out[vo][1]; + if (vid_port_opts & SET_T_CODE_POLARITY_NON_INVERTED) + video_reg &= ~VP_T_CODE_P_INVERTED; + saa_writeb(SAA7134_VIDEO_PORT_CTRL1, video_reg); + saa_writeb(SAA7134_VIDEO_PORT_CTRL2, video_out[vo][2]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL4, video_out[vo][4]); + video_reg = video_out[vo][5]; + if (vid_port_opts & SET_CLOCK_NOT_DELAYED) + video_reg &= ~VP_CLK_CTRL2_DELAYED; + if (vid_port_opts & SET_CLOCK_INVERTED) + video_reg |= VP_CLK_CTRL1_INVERTED; + saa_writeb(SAA7134_VIDEO_PORT_CTRL5, video_reg); + video_reg = video_out[vo][6]; + if (vid_port_opts & SET_VSYNC_OFF) { + video_reg &= ~VP_VS_TYPE_MASK; + video_reg |= VP_VS_TYPE_OFF; + } + saa_writeb(SAA7134_VIDEO_PORT_CTRL6, video_reg); + saa_writeb(SAA7134_VIDEO_PORT_CTRL7, video_out[vo][7]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL8, video_out[vo][8]); + + /* Start videoport */ + saa_writeb(SAA7134_VIDEO_PORT_CTRL3, video_out[vo][3]); + + return 0; +} + +int saa7134_video_init2(struct saa7134_dev *dev) +{ + /* init video hw */ + set_tvnorm(dev,&tvnorms[0]); + video_mux(dev,0); + saa7134_tvaudio_setmute(dev); + saa7134_tvaudio_setvolume(dev,dev->ctl_volume); + return 0; +} + +void saa7134_irq_video_signalchange(struct saa7134_dev *dev) +{ + static const char *st[] = { + "(no signal)", "NTSC", "PAL", "SECAM" }; + u32 st1,st2; + + st1 = saa_readb(SAA7134_STATUS_VIDEO1); + st2 = saa_readb(SAA7134_STATUS_VIDEO2); + dprintk("DCSDT: pll: %s, sync: %s, norm: %s\n", + (st1 & 0x40) ? "not locked" : "locked", + (st2 & 0x40) ? "no" : "yes", + st[st1 & 0x03]); + dev->nosignal = (st1 & 0x40) || (st2 & 0x40) || !(st2 & 0x1); + + if (dev->nosignal) { + /* no video signal -> mute audio */ + if (dev->ctl_automute) + dev->automute = 1; + saa7134_tvaudio_setmute(dev); + } else { + /* wake up tvaudio audio carrier scan thread */ + saa7134_tvaudio_do_scan(dev); + } + + if ((st2 & 0x80) && !noninterlaced && !dev->nosignal) + saa_clearb(SAA7134_SYNC_CTRL, 0x20); + else + saa_setb(SAA7134_SYNC_CTRL, 0x20); + + if (dev->mops && dev->mops->signal_change) + dev->mops->signal_change(dev); +} + + +void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status) +{ + enum v4l2_field field; + + spin_lock(&dev->slock); + if (dev->video_q.curr) { + dev->video_fieldcount++; + field = dev->video_q.curr->vb.field; + if (V4L2_FIELD_HAS_BOTH(field)) { + /* make sure we have seen both fields */ + if ((status & 0x10) == 0x00) { + dev->video_q.curr->top_seen = 1; + goto done; + } + if (!dev->video_q.curr->top_seen) + goto done; + } else if (field == V4L2_FIELD_TOP) { + if ((status & 0x10) != 0x10) + goto done; + } else if (field == V4L2_FIELD_BOTTOM) { + if ((status & 0x10) != 0x00) + goto done; + } + dev->video_q.curr->vb.field_count = dev->video_fieldcount; + saa7134_buffer_finish(dev,&dev->video_q,VIDEOBUF_DONE); + } + saa7134_buffer_next(dev,&dev->video_q); + + done: + spin_unlock(&dev->slock); +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/pci/saa7134/saa7134.h b/drivers/media/pci/saa7134/saa7134.h new file mode 100644 index 000000000000..c24b6512bd8f --- /dev/null +++ b/drivers/media/pci/saa7134/saa7134.h @@ -0,0 +1,855 @@ +/* + * + * v4l2 device driver for philips saa7134 based TV cards + * + * (c) 2001,02 Gerd Knorr + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define SAA7134_VERSION "0, 2, 17" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_VIDEO_SAA7134_DVB) || defined(CONFIG_VIDEO_SAA7134_DVB_MODULE) +#include +#endif + +#define UNSET (-1U) + +/* ----------------------------------------------------------- */ +/* enums */ + +enum saa7134_tvaudio_mode { + TVAUDIO_FM_MONO = 1, + TVAUDIO_FM_BG_STEREO = 2, + TVAUDIO_FM_SAT_STEREO = 3, + TVAUDIO_FM_K_STEREO = 4, + TVAUDIO_NICAM_AM = 5, + TVAUDIO_NICAM_FM = 6, +}; + +enum saa7134_audio_in { + TV = 1, + LINE1 = 2, + LINE2 = 3, + LINE2_LEFT, +}; + +enum saa7134_video_out { + CCIR656 = 1, +}; + +/* ----------------------------------------------------------- */ +/* static data */ + +struct saa7134_tvnorm { + char *name; + v4l2_std_id id; + + /* video decoder */ + unsigned int sync_control; + unsigned int luma_control; + unsigned int chroma_ctrl1; + unsigned int chroma_gain; + unsigned int chroma_ctrl2; + unsigned int vgate_misc; + + /* video scaler */ + unsigned int h_start; + unsigned int h_stop; + unsigned int video_v_start; + unsigned int video_v_stop; + unsigned int vbi_v_start_0; + unsigned int vbi_v_stop_0; + unsigned int src_timing; + unsigned int vbi_v_start_1; +}; + +struct saa7134_tvaudio { + char *name; + v4l2_std_id std; + enum saa7134_tvaudio_mode mode; + int carr1; + int carr2; +}; + +struct saa7134_format { + char *name; + unsigned int fourcc; + unsigned int depth; + unsigned int pm; + unsigned int vshift; /* vertical downsampling (for planar yuv) */ + unsigned int hshift; /* horizontal downsampling (for planar yuv) */ + unsigned int bswap:1; + unsigned int wswap:1; + unsigned int yuv:1; + unsigned int planar:1; + unsigned int uvswap:1; +}; + +struct saa7134_card_ir { + struct rc_dev *dev; + + char name[32]; + char phys[32]; + unsigned users; + + u32 polling; + u32 last_gpio; + u32 mask_keycode, mask_keydown, mask_keyup; + + bool running; + + struct timer_list timer; + + /* IR core raw decoding */ + u32 raw_decode; +}; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define SAA7134_BOARD_NOAUTO UNSET +#define SAA7134_BOARD_UNKNOWN 0 +#define SAA7134_BOARD_PROTEUS_PRO 1 +#define SAA7134_BOARD_FLYVIDEO3000 2 +#define SAA7134_BOARD_FLYVIDEO2000 3 +#define SAA7134_BOARD_EMPRESS 4 +#define SAA7134_BOARD_MONSTERTV 5 +#define SAA7134_BOARD_MD9717 6 +#define SAA7134_BOARD_TVSTATION_RDS 7 +#define SAA7134_BOARD_CINERGY400 8 +#define SAA7134_BOARD_MD5044 9 +#define SAA7134_BOARD_KWORLD 10 +#define SAA7134_BOARD_CINERGY600 11 +#define SAA7134_BOARD_MD7134 12 +#define SAA7134_BOARD_TYPHOON_90031 13 +#define SAA7134_BOARD_ELSA 14 +#define SAA7134_BOARD_ELSA_500TV 15 +#define SAA7134_BOARD_ASUSTeK_TVFM7134 16 +#define SAA7134_BOARD_VA1000POWER 17 +#define SAA7134_BOARD_BMK_MPEX_NOTUNER 18 +#define SAA7134_BOARD_VIDEOMATE_TV 19 +#define SAA7134_BOARD_CRONOS_PLUS 20 +#define SAA7134_BOARD_10MOONSTVMASTER 21 +#define SAA7134_BOARD_MD2819 22 +#define SAA7134_BOARD_BMK_MPEX_TUNER 23 +#define SAA7134_BOARD_TVSTATION_DVR 24 +#define SAA7134_BOARD_ASUSTEK_TVFM7133 25 +#define SAA7134_BOARD_PINNACLE_PCTV_STEREO 26 +#define SAA7134_BOARD_MANLI_MTV002 27 +#define SAA7134_BOARD_MANLI_MTV001 28 +#define SAA7134_BOARD_TG3000TV 29 +#define SAA7134_BOARD_ECS_TVP3XP 30 +#define SAA7134_BOARD_ECS_TVP3XP_4CB5 31 +#define SAA7134_BOARD_AVACSSMARTTV 32 +#define SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER 33 +#define SAA7134_BOARD_NOVAC_PRIMETV7133 34 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_305 35 +#define SAA7134_BOARD_UPMOST_PURPLE_TV 36 +#define SAA7134_BOARD_ITEMS_MTV005 37 +#define SAA7134_BOARD_CINERGY200 38 +#define SAA7134_BOARD_FLYTVPLATINUM_MINI 39 +#define SAA7134_BOARD_VIDEOMATE_TV_PVR 40 +#define SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUS 41 +#define SAA7134_BOARD_SABRENT_SBTTVFM 42 +#define SAA7134_BOARD_ZOLID_XPERT_TV7134 43 +#define SAA7134_BOARD_EMPIRE_PCI_TV_RADIO_LE 44 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_307 45 +#define SAA7134_BOARD_AVERMEDIA_CARDBUS 46 +#define SAA7134_BOARD_CINERGY400_CARDBUS 47 +#define SAA7134_BOARD_CINERGY600_MK3 48 +#define SAA7134_BOARD_VIDEOMATE_GOLD_PLUS 49 +#define SAA7134_BOARD_PINNACLE_300I_DVBT_PAL 50 +#define SAA7134_BOARD_PROVIDEO_PV952 51 +#define SAA7134_BOARD_AVERMEDIA_305 52 +#define SAA7134_BOARD_ASUSTeK_TVFM7135 53 +#define SAA7134_BOARD_FLYTVPLATINUM_FM 54 +#define SAA7134_BOARD_FLYDVBTDUO 55 +#define SAA7134_BOARD_AVERMEDIA_307 56 +#define SAA7134_BOARD_AVERMEDIA_GO_007_FM 57 +#define SAA7134_BOARD_ADS_INSTANT_TV 58 +#define SAA7134_BOARD_KWORLD_VSTREAM_XPERT 59 +#define SAA7134_BOARD_FLYDVBT_DUO_CARDBUS 60 +#define SAA7134_BOARD_PHILIPS_TOUGH 61 +#define SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUSII 62 +#define SAA7134_BOARD_KWORLD_XPERT 63 +#define SAA7134_BOARD_FLYTV_DIGIMATRIX 64 +#define SAA7134_BOARD_KWORLD_TERMINATOR 65 +#define SAA7134_BOARD_YUAN_TUN900 66 +#define SAA7134_BOARD_BEHOLD_409FM 67 +#define SAA7134_BOARD_GOTVIEW_7135 68 +#define SAA7134_BOARD_PHILIPS_EUROPA 69 +#define SAA7134_BOARD_VIDEOMATE_DVBT_300 70 +#define SAA7134_BOARD_VIDEOMATE_DVBT_200 71 +#define SAA7134_BOARD_RTD_VFG7350 72 +#define SAA7134_BOARD_RTD_VFG7330 73 +#define SAA7134_BOARD_FLYTVPLATINUM_MINI2 74 +#define SAA7134_BOARD_AVERMEDIA_AVERTVHD_A180 75 +#define SAA7134_BOARD_MONSTERTV_MOBILE 76 +#define SAA7134_BOARD_PINNACLE_PCTV_110i 77 +#define SAA7134_BOARD_ASUSTeK_P7131_DUAL 78 +#define SAA7134_BOARD_SEDNA_PC_TV_CARDBUS 79 +#define SAA7134_BOARD_ASUSTEK_DIGIMATRIX_TV 80 +#define SAA7134_BOARD_PHILIPS_TIGER 81 +#define SAA7134_BOARD_MSI_TVATANYWHERE_PLUS 82 +#define SAA7134_BOARD_CINERGY250PCI 83 +#define SAA7134_BOARD_FLYDVB_TRIO 84 +#define SAA7134_BOARD_AVERMEDIA_777 85 +#define SAA7134_BOARD_FLYDVBT_LR301 86 +#define SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331 87 +#define SAA7134_BOARD_TEVION_DVBT_220RF 88 +#define SAA7134_BOARD_ELSA_700TV 89 +#define SAA7134_BOARD_KWORLD_ATSC110 90 +#define SAA7134_BOARD_AVERMEDIA_A169_B 91 +#define SAA7134_BOARD_AVERMEDIA_A169_B1 92 +#define SAA7134_BOARD_MD7134_BRIDGE_2 93 +#define SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS 94 +#define SAA7134_BOARD_FLYVIDEO3000_NTSC 95 +#define SAA7134_BOARD_MEDION_MD8800_QUADRO 96 +#define SAA7134_BOARD_FLYDVBS_LR300 97 +#define SAA7134_BOARD_PROTEUS_2309 98 +#define SAA7134_BOARD_AVERMEDIA_A16AR 99 +#define SAA7134_BOARD_ASUS_EUROPA2_HYBRID 100 +#define SAA7134_BOARD_PINNACLE_PCTV_310i 101 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_507 102 +#define SAA7134_BOARD_VIDEOMATE_DVBT_200A 103 +#define SAA7134_BOARD_HAUPPAUGE_HVR1110 104 +#define SAA7134_BOARD_CINERGY_HT_PCMCIA 105 +#define SAA7134_BOARD_ENCORE_ENLTV 106 +#define SAA7134_BOARD_ENCORE_ENLTV_FM 107 +#define SAA7134_BOARD_CINERGY_HT_PCI 108 +#define SAA7134_BOARD_PHILIPS_TIGER_S 109 +#define SAA7134_BOARD_AVERMEDIA_M102 110 +#define SAA7134_BOARD_ASUS_P7131_4871 111 +#define SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA 112 +#define SAA7134_BOARD_ECS_TVP3XP_4CB6 113 +#define SAA7134_BOARD_KWORLD_DVBT_210 114 +#define SAA7134_BOARD_SABRENT_TV_PCB05 115 +#define SAA7134_BOARD_10MOONSTVMASTER3 116 +#define SAA7134_BOARD_AVERMEDIA_SUPER_007 117 +#define SAA7134_BOARD_BEHOLD_401 118 +#define SAA7134_BOARD_BEHOLD_403 119 +#define SAA7134_BOARD_BEHOLD_403FM 120 +#define SAA7134_BOARD_BEHOLD_405 121 +#define SAA7134_BOARD_BEHOLD_405FM 122 +#define SAA7134_BOARD_BEHOLD_407 123 +#define SAA7134_BOARD_BEHOLD_407FM 124 +#define SAA7134_BOARD_BEHOLD_409 125 +#define SAA7134_BOARD_BEHOLD_505FM 126 +#define SAA7134_BOARD_BEHOLD_507_9FM 127 +#define SAA7134_BOARD_BEHOLD_COLUMBUS_TVFM 128 +#define SAA7134_BOARD_BEHOLD_607FM_MK3 129 +#define SAA7134_BOARD_BEHOLD_M6 130 +#define SAA7134_BOARD_TWINHAN_DTV_DVB_3056 131 +#define SAA7134_BOARD_GENIUS_TVGO_A11MCE 132 +#define SAA7134_BOARD_PHILIPS_SNAKE 133 +#define SAA7134_BOARD_CREATIX_CTX953 134 +#define SAA7134_BOARD_MSI_TVANYWHERE_AD11 135 +#define SAA7134_BOARD_AVERMEDIA_CARDBUS_506 136 +#define SAA7134_BOARD_AVERMEDIA_A16D 137 +#define SAA7134_BOARD_AVERMEDIA_M115 138 +#define SAA7134_BOARD_VIDEOMATE_T750 139 +#define SAA7134_BOARD_AVERMEDIA_A700_PRO 140 +#define SAA7134_BOARD_AVERMEDIA_A700_HYBRID 141 +#define SAA7134_BOARD_BEHOLD_H6 142 +#define SAA7134_BOARD_BEHOLD_M63 143 +#define SAA7134_BOARD_BEHOLD_M6_EXTRA 144 +#define SAA7134_BOARD_AVERMEDIA_M103 145 +#define SAA7134_BOARD_ASUSTeK_P7131_ANALOG 146 +#define SAA7134_BOARD_ASUSTeK_TIGER_3IN1 147 +#define SAA7134_BOARD_ENCORE_ENLTV_FM53 148 +#define SAA7134_BOARD_AVERMEDIA_M135A 149 +#define SAA7134_BOARD_REAL_ANGEL_220 150 +#define SAA7134_BOARD_ADS_INSTANT_HDTV_PCI 151 +#define SAA7134_BOARD_ASUSTeK_TIGER 152 +#define SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG 153 +#define SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS 154 +#define SAA7134_BOARD_HAUPPAUGE_HVR1150 155 +#define SAA7134_BOARD_HAUPPAUGE_HVR1120 156 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_507UA 157 +#define SAA7134_BOARD_AVERMEDIA_CARDBUS_501 158 +#define SAA7134_BOARD_BEHOLD_505RDS_MK5 159 +#define SAA7134_BOARD_BEHOLD_507RDS_MK3 160 +#define SAA7134_BOARD_BEHOLD_507RDS_MK5 161 +#define SAA7134_BOARD_BEHOLD_607FM_MK5 162 +#define SAA7134_BOARD_BEHOLD_609FM_MK3 163 +#define SAA7134_BOARD_BEHOLD_609FM_MK5 164 +#define SAA7134_BOARD_BEHOLD_607RDS_MK3 165 +#define SAA7134_BOARD_BEHOLD_607RDS_MK5 166 +#define SAA7134_BOARD_BEHOLD_609RDS_MK3 167 +#define SAA7134_BOARD_BEHOLD_609RDS_MK5 168 +#define SAA7134_BOARD_VIDEOMATE_S350 169 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_505 170 +#define SAA7134_BOARD_BEHOLD_X7 171 +#define SAA7134_BOARD_ROVERMEDIA_LINK_PRO_FM 172 +#define SAA7134_BOARD_ZOLID_HYBRID_PCI 173 +#define SAA7134_BOARD_ASUS_EUROPA_HYBRID 174 +#define SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S 175 +#define SAA7134_BOARD_BEHOLD_505RDS_MK3 176 +#define SAA7134_BOARD_HAWELL_HW_404M7 177 +#define SAA7134_BOARD_BEHOLD_H7 178 +#define SAA7134_BOARD_BEHOLD_A7 179 +#define SAA7134_BOARD_AVERMEDIA_M733A 180 +#define SAA7134_BOARD_TECHNOTREND_BUDGET_T3000 181 +#define SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG 182 +#define SAA7134_BOARD_VIDEOMATE_M1F 183 +#define SAA7134_BOARD_ENCORE_ENLTV_FM3 184 +#define SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2 185 +#define SAA7134_BOARD_BEHOLD_501 186 +#define SAA7134_BOARD_BEHOLD_503FM 187 +#define SAA7134_BOARD_SENSORAY811_911 188 +#define SAA7134_BOARD_KWORLD_PC150U 189 +#define SAA7134_BOARD_ASUSTeK_PS3_100 190 + +#define SAA7134_MAXBOARDS 32 +#define SAA7134_INPUT_MAX 8 + +/* ----------------------------------------------------------- */ +/* Since we support 2 remote types, lets tell them apart */ + +#define SAA7134_REMOTE_GPIO 1 +#define SAA7134_REMOTE_I2C 2 + +/* ----------------------------------------------------------- */ +/* Video Output Port Register Initialization Options */ + +#define SET_T_CODE_POLARITY_NON_INVERTED (1 << 0) +#define SET_CLOCK_NOT_DELAYED (1 << 1) +#define SET_CLOCK_INVERTED (1 << 2) +#define SET_VSYNC_OFF (1 << 3) + +struct saa7134_input { + char *name; + unsigned int vmux; + enum saa7134_audio_in amux; + unsigned int gpio; + unsigned int tv:1; +}; + +enum saa7134_mpeg_type { + SAA7134_MPEG_UNUSED, + SAA7134_MPEG_EMPRESS, + SAA7134_MPEG_DVB, +}; + +enum saa7134_mpeg_ts_type { + SAA7134_MPEG_TS_PARALLEL = 0, + SAA7134_MPEG_TS_SERIAL, +}; + +struct saa7134_board { + char *name; + unsigned int audio_clock; + + /* input switching */ + unsigned int gpiomask; + struct saa7134_input inputs[SAA7134_INPUT_MAX]; + struct saa7134_input radio; + struct saa7134_input mute; + + /* i2c chip info */ + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + unsigned char empress_addr; + unsigned char rds_addr; + + unsigned int tda9887_conf; + unsigned int tuner_config; + + /* peripheral I/O */ + enum saa7134_video_out video_out; + enum saa7134_mpeg_type mpeg; + enum saa7134_mpeg_ts_type ts_type; + unsigned int vid_port_opts; + unsigned int ts_force_val:1; +}; + +#define card_has_radio(dev) (NULL != saa7134_boards[dev->board].radio.name) +#define card_is_empress(dev) (SAA7134_MPEG_EMPRESS == saa7134_boards[dev->board].mpeg) +#define card_is_dvb(dev) (SAA7134_MPEG_DVB == saa7134_boards[dev->board].mpeg) +#define card_has_mpeg(dev) (SAA7134_MPEG_UNUSED != saa7134_boards[dev->board].mpeg) +#define card(dev) (saa7134_boards[dev->board]) +#define card_in(dev,n) (saa7134_boards[dev->board].inputs[n]) + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +#define INTERLACE_AUTO 0 +#define INTERLACE_ON 1 +#define INTERLACE_OFF 2 + +#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */ +#define TS_BUFFER_TIMEOUT msecs_to_jiffies(1000) /* 1 second */ + +struct saa7134_dev; +struct saa7134_dma; + +/* saa7134 page table */ +struct saa7134_pgtable { + unsigned int size; + __le32 *cpu; + dma_addr_t dma; +}; + +/* tvaudio thread status */ +struct saa7134_thread { + struct task_struct *thread; + unsigned int scan1; + unsigned int scan2; + unsigned int mode; + unsigned int stopped; +}; + +/* buffer for one video/vbi/ts frame */ +struct saa7134_buf { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* saa7134 specific */ + struct saa7134_format *fmt; + unsigned int top_seen; + int (*activate)(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next); + + /* page tables */ + struct saa7134_pgtable *pt; +}; + +struct saa7134_dmaqueue { + struct saa7134_dev *dev; + struct saa7134_buf *curr; + struct list_head queue; + struct timer_list timeout; + unsigned int need_two; +}; + +/* video filehandle status */ +struct saa7134_fh { + struct saa7134_dev *dev; + unsigned int radio; + enum v4l2_buf_type type; + unsigned int resources; + enum v4l2_priority prio; + + /* video overlay */ + struct v4l2_window win; + struct v4l2_clip clips[8]; + unsigned int nclips; + + /* video capture */ + struct saa7134_format *fmt; + unsigned int width,height; + struct videobuf_queue cap; + struct saa7134_pgtable pt_cap; + + /* vbi capture */ + struct videobuf_queue vbi; + struct saa7134_pgtable pt_vbi; +}; + +/* dmasound dsp status */ +struct saa7134_dmasound { + struct mutex lock; + int minor_mixer; + int minor_dsp; + unsigned int users_dsp; + + /* mixer */ + enum saa7134_audio_in input; + unsigned int count; + unsigned int line1; + unsigned int line2; + + /* dsp */ + unsigned int afmt; + unsigned int rate; + unsigned int channels; + unsigned int recording_on; + unsigned int dma_running; + unsigned int blocks; + unsigned int blksize; + unsigned int bufsize; + struct saa7134_pgtable pt; + struct videobuf_dmabuf dma; + unsigned int dma_blk; + unsigned int read_offset; + unsigned int read_count; + void * priv_data; + struct snd_pcm_substream *substream; +}; + +/* ts/mpeg status */ +struct saa7134_ts { + /* TS capture */ + struct saa7134_pgtable pt_ts; + int nr_packets; + int nr_bufs; +}; + +/* ts/mpeg ops */ +struct saa7134_mpeg_ops { + enum saa7134_mpeg_type type; + struct list_head next; + int (*init)(struct saa7134_dev *dev); + int (*fini)(struct saa7134_dev *dev); + void (*signal_change)(struct saa7134_dev *dev); +}; + +/* global device status */ +struct saa7134_dev { + struct list_head devlist; + struct mutex lock; + spinlock_t slock; + struct v4l2_prio_state prio; + struct v4l2_device v4l2_dev; + /* workstruct for loading modules */ + struct work_struct request_module_wk; + + /* insmod option/autodetected */ + int autodetected; + + /* various device info */ + unsigned int resources; + struct video_device *video_dev; + struct video_device *radio_dev; + struct video_device *vbi_dev; + struct saa7134_dmasound dmasound; + + /* infrared remote */ + int has_remote; + struct saa7134_card_ir *remote; + + /* pci i/o */ + char name[32]; + int nr; + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; + __u32 __iomem *lmmio; + __u8 __iomem *bmmio; + + /* config info */ + unsigned int board; + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + + unsigned int tda9887_conf; + unsigned int gpio_value; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + unsigned char eedata[256]; + int has_rds; + + /* video overlay */ + struct v4l2_framebuffer ovbuf; + struct saa7134_format *ovfmt; + unsigned int ovenable; + enum v4l2_field ovfield; + + /* video+ts+vbi capture */ + struct saa7134_dmaqueue video_q; + struct saa7134_dmaqueue vbi_q; + unsigned int video_fieldcount; + unsigned int vbi_fieldcount; + + /* various v4l controls */ + struct saa7134_tvnorm *tvnorm; /* video */ + struct saa7134_tvaudio *tvaudio; + unsigned int ctl_input; + int ctl_bright; + int ctl_contrast; + int ctl_hue; + int ctl_saturation; + int ctl_freq; + int ctl_mute; /* audio */ + int ctl_volume; + int ctl_invert; /* private */ + int ctl_mirror; + int ctl_y_odd; + int ctl_y_even; + int ctl_automute; + + /* crop */ + struct v4l2_rect crop_bounds; + struct v4l2_rect crop_defrect; + struct v4l2_rect crop_current; + + /* other global state info */ + unsigned int automute; + struct saa7134_thread thread; + struct saa7134_input *input; + struct saa7134_input *hw_input; + unsigned int hw_mute; + int last_carrier; + int nosignal; + unsigned int insuspend; + + /* I2C keyboard data */ + struct IR_i2c_init_data init_data; + + /* SAA7134_MPEG_* */ + struct saa7134_ts ts; + struct saa7134_dmaqueue ts_q; + int ts_started; + struct saa7134_mpeg_ops *mops; + + /* SAA7134_MPEG_EMPRESS only */ + struct video_device *empress_dev; + struct videobuf_queue empress_tsq; + atomic_t empress_users; + struct work_struct empress_workqueue; + int empress_started; + +#if defined(CONFIG_VIDEO_SAA7134_DVB) || defined(CONFIG_VIDEO_SAA7134_DVB_MODULE) + /* SAA7134_MPEG_DVB only */ + struct videobuf_dvb_frontends frontends; + int (*original_demod_sleep)(struct dvb_frontend *fe); + int (*original_set_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage); + int (*original_set_high_voltage)(struct dvb_frontend *fe, long arg); +#endif + void (*gate_ctrl)(struct saa7134_dev *dev, int open); +}; + +/* ----------------------------------------------------------- */ + +#define saa_readl(reg) readl(dev->lmmio + (reg)) +#define saa_writel(reg,value) writel((value), dev->lmmio + (reg)); +#define saa_andorl(reg,mask,value) \ + writel((readl(dev->lmmio+(reg)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+(reg)) +#define saa_setl(reg,bit) saa_andorl((reg),(bit),(bit)) +#define saa_clearl(reg,bit) saa_andorl((reg),(bit),0) + +#define saa_readb(reg) readb(dev->bmmio + (reg)) +#define saa_writeb(reg,value) writeb((value), dev->bmmio + (reg)); +#define saa_andorb(reg,mask,value) \ + writeb((readb(dev->bmmio+(reg)) & ~(mask)) |\ + ((value) & (mask)), dev->bmmio+(reg)) +#define saa_setb(reg,bit) saa_andorb((reg),(bit),(bit)) +#define saa_clearb(reg,bit) saa_andorb((reg),(bit),0) + +#define saa_wait(us) { udelay(us); } + +#define SAA7134_NORMS (\ + V4L2_STD_PAL | V4L2_STD_PAL_N | \ + V4L2_STD_PAL_Nc | V4L2_STD_SECAM | \ + V4L2_STD_NTSC | V4L2_STD_PAL_M | \ + V4L2_STD_PAL_60) + +#define GRP_EMPRESS (1) +#define saa_call_all(dev, o, f, args...) do { \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 1); \ + v4l2_device_call_all(&(dev)->v4l2_dev, 0, o, f , ##args); \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 0); \ +} while (0) + +#define saa_call_empress(dev, o, f, args...) ({ \ + long _rc; \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 1); \ + _rc = v4l2_device_call_until_err(&(dev)->v4l2_dev, \ + GRP_EMPRESS, o, f , ##args); \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 0); \ + _rc; \ +}) + +/* ----------------------------------------------------------- */ +/* saa7134-core.c */ + +extern struct list_head saa7134_devlist; +extern struct mutex saa7134_devlist_lock; +extern int saa7134_no_overlay; + +void saa7134_track_gpio(struct saa7134_dev *dev, char *msg); +void saa7134_set_gpio(struct saa7134_dev *dev, int bit_no, int value); + +#define SAA7134_PGTABLE_SIZE 4096 + +int saa7134_pgtable_alloc(struct pci_dev *pci, struct saa7134_pgtable *pt); +int saa7134_pgtable_build(struct pci_dev *pci, struct saa7134_pgtable *pt, + struct scatterlist *list, unsigned int length, + unsigned int startpage); +void saa7134_pgtable_free(struct pci_dev *pci, struct saa7134_pgtable *pt); + +int saa7134_buffer_count(unsigned int size, unsigned int count); +int saa7134_buffer_startpage(struct saa7134_buf *buf); +unsigned long saa7134_buffer_base(struct saa7134_buf *buf); + +int saa7134_buffer_queue(struct saa7134_dev *dev, struct saa7134_dmaqueue *q, + struct saa7134_buf *buf); +void saa7134_buffer_finish(struct saa7134_dev *dev, struct saa7134_dmaqueue *q, + unsigned int state); +void saa7134_buffer_next(struct saa7134_dev *dev, struct saa7134_dmaqueue *q); +void saa7134_buffer_timeout(unsigned long data); +void saa7134_dma_free(struct videobuf_queue *q,struct saa7134_buf *buf); + +int saa7134_set_dmabits(struct saa7134_dev *dev); + +extern int (*saa7134_dmasound_init)(struct saa7134_dev *dev); +extern int (*saa7134_dmasound_exit)(struct saa7134_dev *dev); + + +/* ----------------------------------------------------------- */ +/* saa7134-cards.c */ + +extern struct saa7134_board saa7134_boards[]; +extern const unsigned int saa7134_bcount; +extern struct pci_device_id __devinitdata saa7134_pci_tbl[]; + +extern int saa7134_board_init1(struct saa7134_dev *dev); +extern int saa7134_board_init2(struct saa7134_dev *dev); +int saa7134_tuner_callback(void *priv, int component, int command, int arg); + + +/* ----------------------------------------------------------- */ +/* saa7134-i2c.c */ + +int saa7134_i2c_register(struct saa7134_dev *dev); +int saa7134_i2c_unregister(struct saa7134_dev *dev); + + +/* ----------------------------------------------------------- */ +/* saa7134-video.c */ + +extern unsigned int video_debug; +extern struct video_device saa7134_video_template; +extern struct video_device saa7134_radio_template; + +int saa7134_s_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c); +int saa7134_g_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c); +int saa7134_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *c); +int saa7134_s_std_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, v4l2_std_id *id); + +int saa7134_videoport_init(struct saa7134_dev *dev); +void saa7134_set_tvnorm_hw(struct saa7134_dev *dev); + +int saa7134_video_init1(struct saa7134_dev *dev); +int saa7134_video_init2(struct saa7134_dev *dev); +void saa7134_irq_video_signalchange(struct saa7134_dev *dev); +void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status); + + +/* ----------------------------------------------------------- */ +/* saa7134-ts.c */ + +#define TS_PACKET_SIZE 188 /* TS packets 188 bytes */ + +extern struct videobuf_queue_ops saa7134_ts_qops; + +int saa7134_ts_init1(struct saa7134_dev *dev); +int saa7134_ts_fini(struct saa7134_dev *dev); +void saa7134_irq_ts_done(struct saa7134_dev *dev, unsigned long status); + +int saa7134_ts_register(struct saa7134_mpeg_ops *ops); +void saa7134_ts_unregister(struct saa7134_mpeg_ops *ops); + +int saa7134_ts_init_hw(struct saa7134_dev *dev); + +int saa7134_ts_start(struct saa7134_dev *dev); +int saa7134_ts_stop(struct saa7134_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7134-vbi.c */ + +extern struct videobuf_queue_ops saa7134_vbi_qops; +extern struct video_device saa7134_vbi_template; + +int saa7134_vbi_init1(struct saa7134_dev *dev); +int saa7134_vbi_fini(struct saa7134_dev *dev); +void saa7134_irq_vbi_done(struct saa7134_dev *dev, unsigned long status); + + +/* ----------------------------------------------------------- */ +/* saa7134-tvaudio.c */ + +int saa7134_tvaudio_rx2mode(u32 rx); + +void saa7134_tvaudio_setmute(struct saa7134_dev *dev); +void saa7134_tvaudio_setinput(struct saa7134_dev *dev, + struct saa7134_input *in); +void saa7134_tvaudio_setvolume(struct saa7134_dev *dev, int level); +int saa7134_tvaudio_getstereo(struct saa7134_dev *dev); + +void saa7134_tvaudio_init(struct saa7134_dev *dev); +int saa7134_tvaudio_init2(struct saa7134_dev *dev); +int saa7134_tvaudio_fini(struct saa7134_dev *dev); +int saa7134_tvaudio_do_scan(struct saa7134_dev *dev); +int saa7134_tvaudio_close(struct saa7134_dev *dev); + +int saa_dsp_writel(struct saa7134_dev *dev, int reg, u32 value); + +void saa7134_enable_i2s(struct saa7134_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7134-oss.c */ + +extern const struct file_operations saa7134_dsp_fops; +extern const struct file_operations saa7134_mixer_fops; + +int saa7134_oss_init1(struct saa7134_dev *dev); +int saa7134_oss_fini(struct saa7134_dev *dev); +void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status); + +/* ----------------------------------------------------------- */ +/* saa7134-input.c */ + +#if defined(CONFIG_VIDEO_SAA7134_RC) +int saa7134_input_init1(struct saa7134_dev *dev); +void saa7134_input_fini(struct saa7134_dev *dev); +void saa7134_input_irq(struct saa7134_dev *dev); +void saa7134_probe_i2c_ir(struct saa7134_dev *dev); +int saa7134_ir_start(struct saa7134_dev *dev); +void saa7134_ir_stop(struct saa7134_dev *dev); +#else +#define saa7134_input_init1(dev) ((void)0) +#define saa7134_input_fini(dev) ((void)0) +#define saa7134_input_irq(dev) ((void)0) +#define saa7134_probe_i2c_ir(dev) ((void)0) +#define saa7134_ir_start(dev) ((void)0) +#define saa7134_ir_stop(dev) ((void)0) +#endif diff --git a/drivers/media/pci/saa7146/Kconfig b/drivers/media/pci/saa7146/Kconfig new file mode 100644 index 000000000000..da88b77a916c --- /dev/null +++ b/drivers/media/pci/saa7146/Kconfig @@ -0,0 +1,38 @@ +config VIDEO_HEXIUM_GEMINI + tristate "Hexium Gemini frame grabber" + depends on PCI && VIDEO_V4L2 && I2C + select VIDEO_SAA7146_VV + ---help--- + This is a video4linux driver for the Hexium Gemini frame + grabber card by Hexium. Please note that the Gemini Dual + card is *not* fully supported. + + To compile this driver as a module, choose M here: the + module will be called hexium_gemini. + +config VIDEO_HEXIUM_ORION + tristate "Hexium HV-PCI6 and Orion frame grabber" + depends on PCI && VIDEO_V4L2 && I2C + select VIDEO_SAA7146_VV + ---help--- + This is a video4linux driver for the Hexium HV-PCI6 and + Orion frame grabber cards by Hexium. + + To compile this driver as a module, choose M here: the + module will be called hexium_orion. + +config VIDEO_MXB + tristate "Siemens-Nixdorf 'Multimedia eXtension Board'" + depends on PCI && VIDEO_V4L2 && I2C + select VIDEO_SAA7146_VV + select VIDEO_TUNER + select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TDA9840 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TEA6415C if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TEA6420 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + This is a video4linux driver for the 'Multimedia eXtension Board' + TV card by Siemens-Nixdorf. + + To compile this driver as a module, choose M here: the + module will be called mxb. diff --git a/drivers/media/pci/saa7146/Makefile b/drivers/media/pci/saa7146/Makefile new file mode 100644 index 000000000000..f3566a95e4aa --- /dev/null +++ b/drivers/media/pci/saa7146/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_VIDEO_MXB) += mxb.o +obj-$(CONFIG_VIDEO_HEXIUM_ORION) += hexium_orion.o +obj-$(CONFIG_VIDEO_HEXIUM_GEMINI) += hexium_gemini.o + +ccflags-y += -I$(srctree)/drivers/media/i2c diff --git a/drivers/media/video/hexium_gemini.c b/drivers/media/pci/saa7146/hexium_gemini.c similarity index 100% rename from drivers/media/video/hexium_gemini.c rename to drivers/media/pci/saa7146/hexium_gemini.c diff --git a/drivers/media/video/hexium_orion.c b/drivers/media/pci/saa7146/hexium_orion.c similarity index 100% rename from drivers/media/video/hexium_orion.c rename to drivers/media/pci/saa7146/hexium_orion.c diff --git a/drivers/media/pci/saa7146/mxb.c b/drivers/media/pci/saa7146/mxb.c new file mode 100644 index 000000000000..91369daad722 --- /dev/null +++ b/drivers/media/pci/saa7146/mxb.c @@ -0,0 +1,886 @@ +/* + mxb - v4l2 driver for the Multimedia eXtension Board + + Copyright (C) 1998-2006 Michael Hunold + + Visit http://www.themm.net/~mihu/linux/saa7146/mxb.html + for further details about this card. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define DEBUG_VARIABLE debug + +#include +#include +#include +#include +#include + +#include "tea6415c.h" +#include "tea6420.h" + +#define MXB_AUDIOS 6 + +#define I2C_SAA7111A 0x24 +#define I2C_TDA9840 0x42 +#define I2C_TEA6415C 0x43 +#define I2C_TEA6420_1 0x4c +#define I2C_TEA6420_2 0x4d +#define I2C_TUNER 0x60 + +#define MXB_BOARD_CAN_DO_VBI(dev) (dev->revision != 0) + +/* global variable */ +static int mxb_num; + +/* initial frequence the tuner will be tuned to. + in verden (lower saxony, germany) 4148 is a + channel called "phoenix" */ +static int freq = 4148; +module_param(freq, int, 0644); +MODULE_PARM_DESC(freq, "initial frequency the tuner will be tuned to while setup"); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); + +#define MXB_INPUTS 4 +enum { TUNER, AUX1, AUX3, AUX3_YC }; + +static struct v4l2_input mxb_inputs[MXB_INPUTS] = { + { TUNER, "Tuner", V4L2_INPUT_TYPE_TUNER, 0x3f, 0, + V4L2_STD_PAL_BG | V4L2_STD_PAL_I, 0, V4L2_IN_CAP_STD }, + { AUX1, "AUX1", V4L2_INPUT_TYPE_CAMERA, 0x3f, 0, + V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { AUX3, "AUX3 Composite", V4L2_INPUT_TYPE_CAMERA, 0x3f, 0, + V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, + { AUX3_YC, "AUX3 S-Video", V4L2_INPUT_TYPE_CAMERA, 0x3f, 0, + V4L2_STD_ALL, 0, V4L2_IN_CAP_STD }, +}; + +/* this array holds the information, which port of the saa7146 each + input actually uses. the mxb uses port 0 for every input */ +static struct { + int hps_source; + int hps_sync; +} input_port_selection[MXB_INPUTS] = { + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, +}; + +/* this array holds the information of the audio source (mxb_audios), + which has to be switched corresponding to the video source (mxb_channels) */ +static int video_audio_connect[MXB_INPUTS] = + { 0, 1, 3, 3 }; + +struct mxb_routing { + u32 input; + u32 output; +}; + +/* these are the available audio sources, which can switched + to the line- and cd-output individually */ +static struct v4l2_audio mxb_audios[MXB_AUDIOS] = { + { + .index = 0, + .name = "Tuner", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 1, + .name = "AUX1", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 2, + .name = "AUX2", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 3, + .name = "AUX3", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 4, + .name = "Radio (X9)", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 5, + .name = "CD-ROM (X10)", + .capability = V4L2_AUDCAP_STEREO, + } +}; + +/* These are the necessary input-output-pins for bringing one audio source + (see above) to the CD-output. Note that gain is set to 0 in this table. */ +static struct mxb_routing TEA6420_cd[MXB_AUDIOS + 1][2] = { + { { 1, 1 }, { 1, 1 } }, /* Tuner */ + { { 5, 1 }, { 6, 1 } }, /* AUX 1 */ + { { 4, 1 }, { 6, 1 } }, /* AUX 2 */ + { { 3, 1 }, { 6, 1 } }, /* AUX 3 */ + { { 1, 1 }, { 3, 1 } }, /* Radio */ + { { 1, 1 }, { 2, 1 } }, /* CD-Rom */ + { { 6, 1 }, { 6, 1 } } /* Mute */ +}; + +/* These are the necessary input-output-pins for bringing one audio source + (see above) to the line-output. Note that gain is set to 0 in this table. */ +static struct mxb_routing TEA6420_line[MXB_AUDIOS + 1][2] = { + { { 2, 3 }, { 1, 2 } }, + { { 5, 3 }, { 6, 2 } }, + { { 4, 3 }, { 6, 2 } }, + { { 3, 3 }, { 6, 2 } }, + { { 2, 3 }, { 3, 2 } }, + { { 2, 3 }, { 2, 2 } }, + { { 6, 3 }, { 6, 2 } } /* Mute */ +}; + +struct mxb +{ + struct video_device *video_dev; + struct video_device *vbi_dev; + + struct i2c_adapter i2c_adapter; + + struct v4l2_subdev *saa7111a; + struct v4l2_subdev *tda9840; + struct v4l2_subdev *tea6415c; + struct v4l2_subdev *tuner; + struct v4l2_subdev *tea6420_1; + struct v4l2_subdev *tea6420_2; + + int cur_mode; /* current audio mode (mono, stereo, ...) */ + int cur_input; /* current input */ + int cur_audinput; /* current audio input */ + int cur_mute; /* current mute status */ + struct v4l2_frequency cur_freq; /* current frequency the tuner is tuned to */ +}; + +#define saa7111a_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->saa7111a, o, f, ##args) +#define tda9840_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->tda9840, o, f, ##args) +#define tea6415c_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->tea6415c, o, f, ##args) +#define tuner_call(mxb, o, f, args...) \ + v4l2_subdev_call(mxb->tuner, o, f, ##args) +#define call_all(dev, o, f, args...) \ + v4l2_device_call_until_err(&dev->v4l2_dev, 0, o, f, ##args) + +static void mxb_update_audmode(struct mxb *mxb) +{ + struct v4l2_tuner t = { + .audmode = mxb->cur_mode, + }; + + tda9840_call(mxb, tuner, s_tuner, &t); +} + +static inline void tea6420_route(struct mxb *mxb, int idx) +{ + v4l2_subdev_call(mxb->tea6420_1, audio, s_routing, + TEA6420_cd[idx][0].input, TEA6420_cd[idx][0].output, 0); + v4l2_subdev_call(mxb->tea6420_2, audio, s_routing, + TEA6420_cd[idx][1].input, TEA6420_cd[idx][1].output, 0); + v4l2_subdev_call(mxb->tea6420_1, audio, s_routing, + TEA6420_line[idx][0].input, TEA6420_line[idx][0].output, 0); + v4l2_subdev_call(mxb->tea6420_2, audio, s_routing, + TEA6420_line[idx][1].input, TEA6420_line[idx][1].output, 0); +} + +static struct saa7146_extension extension; + +static int mxb_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct saa7146_dev *dev = container_of(ctrl->handler, + struct saa7146_dev, ctrl_handler); + struct mxb *mxb = dev->ext_priv; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + mxb->cur_mute = ctrl->val; + /* switch the audio-source */ + tea6420_route(mxb, ctrl->val ? 6 : + video_audio_connect[mxb->cur_input]); + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct v4l2_ctrl_ops mxb_ctrl_ops = { + .s_ctrl = mxb_s_ctrl, +}; + +static int mxb_probe(struct saa7146_dev *dev) +{ + struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; + struct mxb *mxb = NULL; + + v4l2_ctrl_new_std(hdl, &mxb_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + if (hdl->error) + return hdl->error; + mxb = kzalloc(sizeof(struct mxb), GFP_KERNEL); + if (mxb == NULL) { + DEB_D("not enough kernel memory\n"); + return -ENOMEM; + } + + + snprintf(mxb->i2c_adapter.name, sizeof(mxb->i2c_adapter.name), "mxb%d", mxb_num); + + saa7146_i2c_adapter_prepare(dev, &mxb->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&mxb->i2c_adapter) < 0) { + DEB_S("cannot register i2c-device. skipping.\n"); + kfree(mxb); + return -EFAULT; + } + + mxb->saa7111a = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "saa7111", I2C_SAA7111A, NULL); + mxb->tea6420_1 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tea6420", I2C_TEA6420_1, NULL); + mxb->tea6420_2 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tea6420", I2C_TEA6420_2, NULL); + mxb->tea6415c = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tea6415c", I2C_TEA6415C, NULL); + mxb->tda9840 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tda9840", I2C_TDA9840, NULL); + mxb->tuner = v4l2_i2c_new_subdev(&dev->v4l2_dev, &mxb->i2c_adapter, + "tuner", I2C_TUNER, NULL); + + /* check if all devices are present */ + if (!mxb->tea6420_1 || !mxb->tea6420_2 || !mxb->tea6415c || + !mxb->tda9840 || !mxb->saa7111a || !mxb->tuner) { + pr_err("did not find all i2c devices. aborting\n"); + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + return -ENODEV; + } + + /* all devices are present, probe was successful */ + + /* we store the pointer in our private data field */ + dev->ext_priv = mxb; + + v4l2_ctrl_handler_setup(hdl); + + return 0; +} + +/* some init data for the saa7740, the so-called 'sound arena module'. + there are no specs available, so we simply use some init values */ +static struct { + int length; + char data[9]; +} mxb_saa7740_init[] = { + { 3, { 0x80, 0x00, 0x00 } },{ 3, { 0x80, 0x89, 0x00 } }, + { 3, { 0x80, 0xb0, 0x0a } },{ 3, { 0x00, 0x00, 0x00 } }, + { 3, { 0x49, 0x00, 0x00 } },{ 3, { 0x4a, 0x00, 0x00 } }, + { 3, { 0x4b, 0x00, 0x00 } },{ 3, { 0x4c, 0x00, 0x00 } }, + { 3, { 0x4d, 0x00, 0x00 } },{ 3, { 0x4e, 0x00, 0x00 } }, + { 3, { 0x4f, 0x00, 0x00 } },{ 3, { 0x50, 0x00, 0x00 } }, + { 3, { 0x51, 0x00, 0x00 } },{ 3, { 0x52, 0x00, 0x00 } }, + { 3, { 0x53, 0x00, 0x00 } },{ 3, { 0x54, 0x00, 0x00 } }, + { 3, { 0x55, 0x00, 0x00 } },{ 3, { 0x56, 0x00, 0x00 } }, + { 3, { 0x57, 0x00, 0x00 } },{ 3, { 0x58, 0x00, 0x00 } }, + { 3, { 0x59, 0x00, 0x00 } },{ 3, { 0x5a, 0x00, 0x00 } }, + { 3, { 0x5b, 0x00, 0x00 } },{ 3, { 0x5c, 0x00, 0x00 } }, + { 3, { 0x5d, 0x00, 0x00 } },{ 3, { 0x5e, 0x00, 0x00 } }, + { 3, { 0x5f, 0x00, 0x00 } },{ 3, { 0x60, 0x00, 0x00 } }, + { 3, { 0x61, 0x00, 0x00 } },{ 3, { 0x62, 0x00, 0x00 } }, + { 3, { 0x63, 0x00, 0x00 } },{ 3, { 0x64, 0x00, 0x00 } }, + { 3, { 0x65, 0x00, 0x00 } },{ 3, { 0x66, 0x00, 0x00 } }, + { 3, { 0x67, 0x00, 0x00 } },{ 3, { 0x68, 0x00, 0x00 } }, + { 3, { 0x69, 0x00, 0x00 } },{ 3, { 0x6a, 0x00, 0x00 } }, + { 3, { 0x6b, 0x00, 0x00 } },{ 3, { 0x6c, 0x00, 0x00 } }, + { 3, { 0x6d, 0x00, 0x00 } },{ 3, { 0x6e, 0x00, 0x00 } }, + { 3, { 0x6f, 0x00, 0x00 } },{ 3, { 0x70, 0x00, 0x00 } }, + { 3, { 0x71, 0x00, 0x00 } },{ 3, { 0x72, 0x00, 0x00 } }, + { 3, { 0x73, 0x00, 0x00 } },{ 3, { 0x74, 0x00, 0x00 } }, + { 3, { 0x75, 0x00, 0x00 } },{ 3, { 0x76, 0x00, 0x00 } }, + { 3, { 0x77, 0x00, 0x00 } },{ 3, { 0x41, 0x00, 0x42 } }, + { 3, { 0x42, 0x10, 0x42 } },{ 3, { 0x43, 0x20, 0x42 } }, + { 3, { 0x44, 0x30, 0x42 } },{ 3, { 0x45, 0x00, 0x01 } }, + { 3, { 0x46, 0x00, 0x01 } },{ 3, { 0x47, 0x00, 0x01 } }, + { 3, { 0x48, 0x00, 0x01 } }, + { 9, { 0x01, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x21, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x09, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x29, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x11, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x31, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x19, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x39, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x05, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x25, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x0d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x2d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x15, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x35, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x1d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 9, { 0x3d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 3, { 0x80, 0xb3, 0x0a } }, + {-1, { 0 } } +}; + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int mxb_init_done(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + struct i2c_msg msg; + struct tuner_setup tun_setup; + v4l2_std_id std = V4L2_STD_PAL_BG; + + int i = 0, err = 0; + + /* mute audio on tea6420s */ + tea6420_route(mxb, 6); + + /* select video mode in saa7111a */ + saa7111a_call(mxb, core, s_std, std); + + /* select tuner-output on saa7111a */ + i = 0; + saa7111a_call(mxb, video, s_routing, SAA7115_COMPOSITE0, + SAA7111_FMT_CCIR, 0); + + /* select a tuner type */ + tun_setup.mode_mask = T_ANALOG_TV; + tun_setup.addr = ADDR_UNSET; + tun_setup.type = TUNER_PHILIPS_PAL; + tuner_call(mxb, tuner, s_type_addr, &tun_setup); + /* tune in some frequency on tuner */ + mxb->cur_freq.tuner = 0; + mxb->cur_freq.type = V4L2_TUNER_ANALOG_TV; + mxb->cur_freq.frequency = freq; + tuner_call(mxb, tuner, s_frequency, &mxb->cur_freq); + + /* set a default video standard */ + /* These two gpio calls set the GPIO pins that control the tda9820 */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + saa7111a_call(mxb, core, s_gpio, 1); + saa7111a_call(mxb, core, s_std, std); + tuner_call(mxb, core, s_std, std); + + /* switch to tuner-channel on tea6415c */ + tea6415c_call(mxb, video, s_routing, 3, 17, 0); + + /* select tuner-output on multicable on tea6415c */ + tea6415c_call(mxb, video, s_routing, 3, 13, 0); + + /* the rest for mxb */ + mxb->cur_input = 0; + mxb->cur_audinput = video_audio_connect[mxb->cur_input]; + mxb->cur_mute = 1; + + mxb->cur_mode = V4L2_TUNER_MODE_STEREO; + mxb_update_audmode(mxb); + + /* check if the saa7740 (aka 'sound arena module') is present + on the mxb. if so, we must initialize it. due to lack of + informations about the saa7740, the values were reverse + engineered. */ + msg.addr = 0x1b; + msg.flags = 0; + msg.len = mxb_saa7740_init[0].length; + msg.buf = &mxb_saa7740_init[0].data[0]; + + err = i2c_transfer(&mxb->i2c_adapter, &msg, 1); + if (err == 1) { + /* the sound arena module is a pos, that's probably the reason + philips refuses to hand out a datasheet for the saa7740... + it seems to screw up the i2c bus, so we disable fast irq + based i2c transactions here and rely on the slow and safe + polling method ... */ + extension.flags &= ~SAA7146_USE_I2C_IRQ; + for (i = 1; ; i++) { + if (-1 == mxb_saa7740_init[i].length) + break; + + msg.len = mxb_saa7740_init[i].length; + msg.buf = &mxb_saa7740_init[i].data[0]; + err = i2c_transfer(&mxb->i2c_adapter, &msg, 1); + if (err != 1) { + DEB_D("failed to initialize 'sound arena module'\n"); + goto err; + } + } + pr_info("'sound arena module' detected\n"); + } +err: + /* the rest for saa7146: you should definitely set some basic values + for the input-port handling of the saa7146. */ + + /* ext->saa has been filled by the core driver */ + + /* some stuff is done via variables */ + saa7146_set_hps_source_and_sync(dev, input_port_selection[mxb->cur_input].hps_source, + input_port_selection[mxb->cur_input].hps_sync); + + /* some stuff is done via direct write to the registers */ + + /* this is ugly, but because of the fact that this is completely + hardware dependend, it should be done directly... */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, DD1_INIT, 0x02000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + return 0; +} + +/* interrupt-handler. this gets called when irq_mask is != 0. + it must clear the interrupt-bits in irq_mask it has handled */ +/* +void mxb_irq_bh(struct saa7146_dev* dev, u32* irq_mask) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; +} +*/ + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + DEB_EE("VIDIOC_ENUMINPUT %d\n", i->index); + if (i->index >= MXB_INPUTS) + return -EINVAL; + memcpy(i, &mxb_inputs[i->index], sizeof(struct v4l2_input)); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + *i = mxb->cur_input; + + DEB_EE("VIDIOC_G_INPUT %d\n", *i); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + int err = 0; + int i = 0; + + DEB_EE("VIDIOC_S_INPUT %d\n", input); + + if (input >= MXB_INPUTS) + return -EINVAL; + + mxb->cur_input = input; + + saa7146_set_hps_source_and_sync(dev, input_port_selection[input].hps_source, + input_port_selection[input].hps_sync); + + /* prepare switching of tea6415c and saa7111a; + have a look at the 'background'-file for further informations */ + switch (input) { + case TUNER: + i = SAA7115_COMPOSITE0; + + err = tea6415c_call(mxb, video, s_routing, 3, 17, 0); + + /* connect tuner-output always to multicable */ + if (!err) + err = tea6415c_call(mxb, video, s_routing, 3, 13, 0); + break; + case AUX3_YC: + /* nothing to be done here. aux3_yc is + directly connected to the saa711a */ + i = SAA7115_SVIDEO1; + break; + case AUX3: + /* nothing to be done here. aux3 is + directly connected to the saa711a */ + i = SAA7115_COMPOSITE1; + break; + case AUX1: + i = SAA7115_COMPOSITE0; + err = tea6415c_call(mxb, video, s_routing, 1, 17, 0); + break; + } + + if (err) + return err; + + /* switch video in saa7111a */ + if (saa7111a_call(mxb, video, s_routing, i, SAA7111_FMT_CCIR, 0)) + pr_err("VIDIOC_S_INPUT: could not address saa7111a\n"); + + mxb->cur_audinput = video_audio_connect[input]; + /* switch the audio-source only if necessary */ + if (0 == mxb->cur_mute) + tea6420_route(mxb, mxb->cur_audinput); + if (mxb->cur_audinput == 0) + mxb_update_audmode(mxb); + + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *fh, struct v4l2_tuner *t) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (t->index) { + DEB_D("VIDIOC_G_TUNER: channel %d does not have a tuner attached\n", + t->index); + return -EINVAL; + } + + DEB_EE("VIDIOC_G_TUNER: %d\n", t->index); + + memset(t, 0, sizeof(*t)); + strlcpy(t->name, "TV Tuner", sizeof(t->name)); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + t->audmode = mxb->cur_mode; + return call_all(dev, tuner, g_tuner, t); +} + +static int vidioc_s_tuner(struct file *file, void *fh, struct v4l2_tuner *t) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (t->index) { + DEB_D("VIDIOC_S_TUNER: channel %d does not have a tuner attached\n", + t->index); + return -EINVAL; + } + + mxb->cur_mode = t->audmode; + return call_all(dev, tuner, s_tuner, t); +} + +static int vidioc_querystd(struct file *file, void *fh, v4l2_std_id *norm) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + + return call_all(dev, video, querystd, norm); +} + +static int vidioc_g_frequency(struct file *file, void *fh, struct v4l2_frequency *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (f->tuner) + return -EINVAL; + *f = mxb->cur_freq; + + DEB_EE("VIDIOC_G_FREQ: freq:0x%08x\n", mxb->cur_freq.frequency); + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *fh, struct v4l2_frequency *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + struct saa7146_vv *vv = dev->vv_data; + + if (f->tuner) + return -EINVAL; + + if (V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + + DEB_EE("VIDIOC_S_FREQUENCY: freq:0x%08x\n", mxb->cur_freq.frequency); + + /* tune in desired frequency */ + tuner_call(mxb, tuner, s_frequency, f); + /* let the tuner subdev clamp the frequency to the tuner range */ + tuner_call(mxb, tuner, g_frequency, f); + mxb->cur_freq = *f; + if (mxb->cur_audinput == 0) + mxb_update_audmode(mxb); + + if (mxb->cur_input) + return 0; + + /* hack: changing the frequency should invalidate the vbi-counter (=> alevt) */ + spin_lock(&dev->slock); + vv->vbi_fieldcount = 0; + spin_unlock(&dev->slock); + + return 0; +} + +static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a) +{ + if (a->index >= MXB_AUDIOS) + return -EINVAL; + *a = mxb_audios[a->index]; + return 0; +} + +static int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + DEB_EE("VIDIOC_G_AUDIO\n"); + *a = mxb_audios[mxb->cur_audinput]; + return 0; +} + +static int vidioc_s_audio(struct file *file, void *fh, const struct v4l2_audio *a) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + DEB_D("VIDIOC_S_AUDIO %d\n", a->index); + if (mxb_inputs[mxb->cur_input].audioset & (1 << a->index)) { + if (mxb->cur_audinput != a->index) { + mxb->cur_audinput = a->index; + tea6420_route(mxb, a->index); + if (mxb->cur_audinput == 0) + mxb_update_audmode(mxb); + } + return 0; + } + return -EINVAL; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register(struct file *file, void *fh, struct v4l2_dbg_register *reg) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (v4l2_chip_match_host(®->match)) { + reg->val = saa7146_read(dev, reg->reg); + reg->size = 4; + return 0; + } + call_all(dev, core, g_register, reg); + return 0; +} + +static int vidioc_s_register(struct file *file, void *fh, struct v4l2_dbg_register *reg) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (v4l2_chip_match_host(®->match)) { + saa7146_write(dev, reg->reg, reg->val); + reg->size = 4; + return 0; + } + return call_all(dev, core, s_register, reg); +} +#endif + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int mxb_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct mxb *mxb; + + DEB_EE("dev:%p\n", dev); + + saa7146_vv_init(dev, &vv_data); + if (mxb_probe(dev)) { + saa7146_vv_release(dev); + return -1; + } + mxb = (struct mxb *)dev->ext_priv; + + vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data.vid_ops.vidioc_g_input = vidioc_g_input; + vv_data.vid_ops.vidioc_s_input = vidioc_s_input; + vv_data.vid_ops.vidioc_querystd = vidioc_querystd; + vv_data.vid_ops.vidioc_g_tuner = vidioc_g_tuner; + vv_data.vid_ops.vidioc_s_tuner = vidioc_s_tuner; + vv_data.vid_ops.vidioc_g_frequency = vidioc_g_frequency; + vv_data.vid_ops.vidioc_s_frequency = vidioc_s_frequency; + vv_data.vid_ops.vidioc_enumaudio = vidioc_enumaudio; + vv_data.vid_ops.vidioc_g_audio = vidioc_g_audio; + vv_data.vid_ops.vidioc_s_audio = vidioc_s_audio; +#ifdef CONFIG_VIDEO_ADV_DEBUG + vv_data.vid_ops.vidioc_g_register = vidioc_g_register; + vv_data.vid_ops.vidioc_s_register = vidioc_s_register; +#endif + if (saa7146_register_device(&mxb->video_dev, dev, "mxb", VFL_TYPE_GRABBER)) { + ERR("cannot register capture v4l2 device. skipping.\n"); + saa7146_vv_release(dev); + return -1; + } + + /* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ + if (MXB_BOARD_CAN_DO_VBI(dev)) { + if (saa7146_register_device(&mxb->vbi_dev, dev, "mxb", VFL_TYPE_VBI)) { + ERR("cannot register vbi v4l2 device. skipping.\n"); + } + } + + pr_info("found Multimedia eXtension Board #%d\n", mxb_num); + + mxb_num++; + mxb_init_done(dev); + return 0; +} + +static int mxb_detach(struct saa7146_dev *dev) +{ + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + DEB_EE("dev:%p\n", dev); + + /* mute audio on tea6420s */ + tea6420_route(mxb, 6); + + saa7146_unregister_device(&mxb->video_dev,dev); + if (MXB_BOARD_CAN_DO_VBI(dev)) + saa7146_unregister_device(&mxb->vbi_dev, dev); + saa7146_vv_release(dev); + + mxb_num--; + + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *standard) +{ + struct mxb *mxb = (struct mxb *)dev->ext_priv; + + if (V4L2_STD_PAL_I == standard->id) { + v4l2_std_id std = V4L2_STD_PAL_I; + + DEB_D("VIDIOC_S_STD: setting mxb for PAL_I\n"); + /* These two gpio calls set the GPIO pins that control the tda9820 */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + saa7111a_call(mxb, core, s_gpio, 0); + saa7111a_call(mxb, core, s_std, std); + if (mxb->cur_input == 0) + tuner_call(mxb, core, s_std, std); + } else { + v4l2_std_id std = V4L2_STD_PAL_BG; + + if (mxb->cur_input) + std = standard->id; + DEB_D("VIDIOC_S_STD: setting mxb for PAL/NTSC/SECAM\n"); + /* These two gpio calls set the GPIO pins that control the tda9820 */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + saa7111a_call(mxb, core, s_gpio, 1); + saa7111a_call(mxb, core, s_std, std); + if (mxb->cur_input == 0) + tuner_call(mxb, core, s_std, std); + } + return 0; +} + +static struct saa7146_standard standard[] = { + { + .name = "PAL-BG", .id = V4L2_STD_PAL_BG, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "PAL-I", .id = V4L2_STD_PAL_I, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x16, .v_field = 240, + .h_offset = 0x06, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 0x14, .v_field = 288, + .h_offset = 0x14, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +static struct saa7146_pci_extension_data mxb = { + .ext_priv = "Multimedia eXtension Board", + .ext = &extension, +}; + +static struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x0000, + .subdevice = 0x0000, + .driver_data = (unsigned long)&mxb, + }, { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = MXB_INPUTS, + .capabilities = V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE | V4L2_CAP_AUDIO, + .stds = &standard[0], + .num_stds = sizeof(standard)/sizeof(struct saa7146_standard), + .std_callback = &std_callback, +}; + +static struct saa7146_extension extension = { + .name = "Multimedia eXtension Board", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .attach = mxb_attach, + .detach = mxb_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init mxb_init_module(void) +{ + if (saa7146_register_extension(&extension)) { + DEB_S("failed to register extension\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit mxb_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(mxb_init_module); +module_exit(mxb_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for the Siemens-Nixdorf 'Multimedia eXtension board'"); +MODULE_AUTHOR("Michael Hunold "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/saa7164/Kconfig b/drivers/media/pci/saa7164/Kconfig new file mode 100644 index 000000000000..a53db7d1c96e --- /dev/null +++ b/drivers/media/pci/saa7164/Kconfig @@ -0,0 +1,18 @@ +config VIDEO_SAA7164 + tristate "NXP SAA7164 support" + depends on DVB_CORE && VIDEO_DEV && PCI && I2C + select I2C_ALGOBIT + select FW_LOADER + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEOBUF_DVB + select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + This is a video4linux driver for NXP SAA7164 based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called saa7164 + diff --git a/drivers/media/pci/saa7164/Makefile b/drivers/media/pci/saa7164/Makefile new file mode 100644 index 000000000000..ba0e33a1ee24 --- /dev/null +++ b/drivers/media/pci/saa7164/Makefile @@ -0,0 +1,12 @@ +saa7164-objs := saa7164-cards.o saa7164-core.o saa7164-i2c.o saa7164-dvb.o \ + saa7164-fw.o saa7164-bus.o saa7164-cmd.o saa7164-api.o \ + saa7164-buffer.o saa7164-encoder.o saa7164-vbi.o + +obj-$(CONFIG_VIDEO_SAA7164) += saa7164.o + +ccflags-y += -I$(srctree)/drivers/media/i2c +ccflags-y += -I$(srctree)/drivers/media/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb-frontends + +ccflags-y += $(extra-cflags-y) $(extra-cflags-m) diff --git a/drivers/media/pci/saa7164/saa7164-api.c b/drivers/media/pci/saa7164/saa7164-api.c new file mode 100644 index 000000000000..eff7135cf0e8 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-api.c @@ -0,0 +1,1524 @@ +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010 Steven Toth + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include "saa7164.h" + +int saa7164_api_get_load_info(struct saa7164_dev *dev, struct tmFwInfoStruct *i) +{ + int ret; + + if (!(saa_debug & DBGLVL_CPU)) + return 0; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + i->deviceinst = 0; + i->devicespec = 0; + i->mode = 0; + i->status = 0; + + ret = saa7164_cmd_send(dev, 0, GET_CUR, + GET_FW_STATUS_CONTROL, sizeof(struct tmFwInfoStruct), i); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + printk(KERN_INFO "saa7164[%d]-CPU: %d percent", dev->nr, i->CPULoad); + + return ret; +} + +int saa7164_api_collect_debug(struct saa7164_dev *dev) +{ + struct tmComResDebugGetData d; + u8 more = 255; + int ret; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + while (more--) { + + memset(&d, 0, sizeof(d)); + + ret = saa7164_cmd_send(dev, 0, GET_CUR, + GET_DEBUG_DATA_CONTROL, sizeof(d), &d); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", + __func__, ret); + + if (d.dwResult != SAA_OK) + break; + + printk(KERN_INFO "saa7164[%d]-FWMSG: %s", dev->nr, + d.ucDebugData); + } + + return 0; +} + +int saa7164_api_set_debug(struct saa7164_dev *dev, u8 level) +{ + struct tmComResDebugSetLevel lvl; + int ret; + + dprintk(DBGLVL_API, "%s(level=%d)\n", __func__, level); + + /* Retrieve current state */ + ret = saa7164_cmd_send(dev, 0, GET_CUR, + SET_DEBUG_LEVEL_CONTROL, sizeof(lvl), &lvl); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_API, "%s() Was %d\n", __func__, lvl.dwDebugLevel); + + lvl.dwDebugLevel = level; + + /* set new state */ + ret = saa7164_cmd_send(dev, 0, SET_CUR, + SET_DEBUG_LEVEL_CONTROL, sizeof(lvl), &lvl); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_set_vbi_format(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResProbeCommit fmt, rsp; + int ret; + + dprintk(DBGLVL_API, "%s(nr=%d, unitid=0x%x)\n", __func__, + port->nr, port->hwcfg.unitid); + + fmt.bmHint = 0; + fmt.bFormatIndex = 1; + fmt.bFrameIndex = 1; + + /* Probe, see if it can support this format */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, + SET_CUR, SAA_PROBE_CONTROL, sizeof(fmt), &fmt); + if (ret != SAA_OK) + printk(KERN_ERR "%s() set error, ret = 0x%x\n", __func__, ret); + + /* See of the format change was successful */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, + GET_CUR, SAA_PROBE_CONTROL, sizeof(rsp), &rsp); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() get error, ret = 0x%x\n", __func__, ret); + } else { + /* Compare requested vs received, should be same */ + if (memcmp(&fmt, &rsp, sizeof(rsp)) == 0) { + dprintk(DBGLVL_API, "SET/PROBE Verified\n"); + + /* Ask the device to select the negotiated format */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, + SET_CUR, SAA_COMMIT_CONTROL, sizeof(fmt), &fmt); + if (ret != SAA_OK) + printk(KERN_ERR "%s() commit error, ret = 0x%x\n", + __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, + GET_CUR, SAA_COMMIT_CONTROL, sizeof(rsp), &rsp); + if (ret != SAA_OK) + printk(KERN_ERR "%s() GET commit error, ret = 0x%x\n", + __func__, ret); + + if (memcmp(&fmt, &rsp, sizeof(rsp)) != 0) { + printk(KERN_ERR "%s() memcmp error, ret = 0x%x\n", + __func__, ret); + } else + dprintk(DBGLVL_API, "SET/COMMIT Verified\n"); + + dprintk(DBGLVL_API, "rsp.bmHint = 0x%x\n", rsp.bmHint); + dprintk(DBGLVL_API, "rsp.bFormatIndex = 0x%x\n", + rsp.bFormatIndex); + dprintk(DBGLVL_API, "rsp.bFrameIndex = 0x%x\n", + rsp.bFrameIndex); + } else + printk(KERN_ERR "%s() compare failed\n", __func__); + } + + if (ret == SAA_OK) + dprintk(DBGLVL_API, "%s(nr=%d) Success\n", __func__, port->nr); + + return ret; +} + +int saa7164_api_set_gop_size(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResEncVideoGopStructure gs; + int ret; + + dprintk(DBGLVL_ENC, "%s()\n", __func__); + + gs.ucRefFrameDist = port->encoder_params.refdist; + gs.ucGOPSize = port->encoder_params.gop_size; + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_VIDEO_GOP_STRUCTURE_CONTROL, + sizeof(gs), &gs); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_set_encoder(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResEncVideoBitRate vb; + struct tmComResEncAudioBitRate ab; + int ret; + + dprintk(DBGLVL_ENC, "%s() unitid=0x%x\n", __func__, + port->hwcfg.sourceid); + + if (port->encoder_params.stream_type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS) + port->encoder_profile = EU_PROFILE_PS_DVD; + else + port->encoder_profile = EU_PROFILE_TS_HQ; + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_PROFILE_CONTROL, sizeof(u8), &port->encoder_profile); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Resolution */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_PROFILE_CONTROL, sizeof(u8), &port->encoder_profile); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Establish video bitrates */ + if (port->encoder_params.bitrate_mode == + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR) + vb.ucVideoBitRateMode = EU_VIDEO_BIT_RATE_MODE_CONSTANT; + else + vb.ucVideoBitRateMode = EU_VIDEO_BIT_RATE_MODE_VARIABLE_PEAK; + vb.dwVideoBitRate = port->encoder_params.bitrate; + vb.dwVideoBitRatePeak = port->encoder_params.bitrate_peak; + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_VIDEO_BIT_RATE_CONTROL, + sizeof(struct tmComResEncVideoBitRate), + &vb); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Establish audio bitrates */ + ab.ucAudioBitRateMode = 0; + ab.dwAudioBitRate = 384000; + ab.dwAudioBitRatePeak = ab.dwAudioBitRate; + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_AUDIO_BIT_RATE_CONTROL, + sizeof(struct tmComResEncAudioBitRate), + &ab); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, + ret); + + saa7164_api_set_aspect_ratio(port); + saa7164_api_set_gop_size(port); + + return ret; +} + +int saa7164_api_get_encoder(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResEncVideoBitRate v; + struct tmComResEncAudioBitRate a; + struct tmComResEncVideoInputAspectRatio ar; + int ret; + + dprintk(DBGLVL_ENC, "%s() unitid=0x%x\n", __func__, + port->hwcfg.sourceid); + + port->encoder_profile = 0; + port->video_format = 0; + port->video_resolution = 0; + port->audio_format = 0; + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_PROFILE_CONTROL, sizeof(u8), &port->encoder_profile); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_VIDEO_RESOLUTION_CONTROL, sizeof(u8), + &port->video_resolution); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_VIDEO_FORMAT_CONTROL, sizeof(u8), &port->video_format); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_VIDEO_BIT_RATE_CONTROL, sizeof(v), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_AUDIO_FORMAT_CONTROL, sizeof(u8), &port->audio_format); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_AUDIO_BIT_RATE_CONTROL, sizeof(a), &a); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Aspect Ratio */ + ar.width = 0; + ar.height = 0; + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, GET_CUR, + EU_VIDEO_INPUT_ASPECT_CONTROL, + sizeof(struct tmComResEncVideoInputAspectRatio), &ar); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_ENC, "encoder_profile = %d\n", port->encoder_profile); + dprintk(DBGLVL_ENC, "video_format = %d\n", port->video_format); + dprintk(DBGLVL_ENC, "audio_format = %d\n", port->audio_format); + dprintk(DBGLVL_ENC, "video_resolution= %d\n", port->video_resolution); + dprintk(DBGLVL_ENC, "v.ucVideoBitRateMode = %d\n", + v.ucVideoBitRateMode); + dprintk(DBGLVL_ENC, "v.dwVideoBitRate = %d\n", + v.dwVideoBitRate); + dprintk(DBGLVL_ENC, "v.dwVideoBitRatePeak = %d\n", + v.dwVideoBitRatePeak); + dprintk(DBGLVL_ENC, "a.ucVideoBitRateMode = %d\n", + a.ucAudioBitRateMode); + dprintk(DBGLVL_ENC, "a.dwVideoBitRate = %d\n", + a.dwAudioBitRate); + dprintk(DBGLVL_ENC, "a.dwVideoBitRatePeak = %d\n", + a.dwAudioBitRatePeak); + dprintk(DBGLVL_ENC, "aspect.width / height = %d:%d\n", + ar.width, ar.height); + + return ret; +} + +int saa7164_api_set_aspect_ratio(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResEncVideoInputAspectRatio ar; + int ret; + + dprintk(DBGLVL_ENC, "%s(%d)\n", __func__, + port->encoder_params.ctl_aspect); + + switch (port->encoder_params.ctl_aspect) { + case V4L2_MPEG_VIDEO_ASPECT_1x1: + ar.width = 1; + ar.height = 1; + break; + case V4L2_MPEG_VIDEO_ASPECT_4x3: + ar.width = 4; + ar.height = 3; + break; + case V4L2_MPEG_VIDEO_ASPECT_16x9: + ar.width = 16; + ar.height = 9; + break; + case V4L2_MPEG_VIDEO_ASPECT_221x100: + ar.width = 221; + ar.height = 100; + break; + default: + BUG(); + } + + dprintk(DBGLVL_ENC, "%s(%d) now %d:%d\n", __func__, + port->encoder_params.ctl_aspect, + ar.width, ar.height); + + /* Aspect Ratio */ + ret = saa7164_cmd_send(port->dev, port->hwcfg.sourceid, SET_CUR, + EU_VIDEO_INPUT_ASPECT_CONTROL, + sizeof(struct tmComResEncVideoInputAspectRatio), &ar); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_set_usercontrol(struct saa7164_port *port, u8 ctl) +{ + struct saa7164_dev *dev = port->dev; + int ret; + u16 val; + + if (ctl == PU_BRIGHTNESS_CONTROL) + val = port->ctl_brightness; + else + if (ctl == PU_CONTRAST_CONTROL) + val = port->ctl_contrast; + else + if (ctl == PU_HUE_CONTROL) + val = port->ctl_hue; + else + if (ctl == PU_SATURATION_CONTROL) + val = port->ctl_saturation; + else + if (ctl == PU_SHARPNESS_CONTROL) + val = port->ctl_sharpness; + else + return -EINVAL; + + dprintk(DBGLVL_ENC, "%s() unitid=0x%x ctl=%d, val=%d\n", + __func__, port->encunit.vsourceid, ctl, val); + + ret = saa7164_cmd_send(port->dev, port->encunit.vsourceid, SET_CUR, + ctl, sizeof(u16), &val); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_get_usercontrol(struct saa7164_port *port, u8 ctl) +{ + struct saa7164_dev *dev = port->dev; + int ret; + u16 val; + + ret = saa7164_cmd_send(port->dev, port->encunit.vsourceid, GET_CUR, + ctl, sizeof(u16), &val); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + return ret; + } + + dprintk(DBGLVL_ENC, "%s() ctl=%d, val=%d\n", + __func__, ctl, val); + + if (ctl == PU_BRIGHTNESS_CONTROL) + port->ctl_brightness = val; + else + if (ctl == PU_CONTRAST_CONTROL) + port->ctl_contrast = val; + else + if (ctl == PU_HUE_CONTROL) + port->ctl_hue = val; + else + if (ctl == PU_SATURATION_CONTROL) + port->ctl_saturation = val; + else + if (ctl == PU_SHARPNESS_CONTROL) + port->ctl_sharpness = val; + + return ret; +} + +int saa7164_api_set_videomux(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + u8 inputs[] = { 1, 2, 2, 2, 5, 5, 5 }; + int ret; + + dprintk(DBGLVL_ENC, "%s() v_mux=%d a_mux=%d\n", + __func__, port->mux_input, inputs[port->mux_input - 1]); + + /* Audio Mute */ + ret = saa7164_api_audio_mute(port, 1); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Video Mux */ + ret = saa7164_cmd_send(port->dev, port->vidproc.sourceid, SET_CUR, + SU_INPUT_SELECT_CONTROL, sizeof(u8), &port->mux_input); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Audio Mux */ + ret = saa7164_cmd_send(port->dev, port->audfeat.sourceid, SET_CUR, + SU_INPUT_SELECT_CONTROL, sizeof(u8), + &inputs[port->mux_input - 1]); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Audio UnMute */ + ret = saa7164_api_audio_mute(port, 0); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_audio_mute(struct saa7164_port *port, int mute) +{ + struct saa7164_dev *dev = port->dev; + u8 v = mute; + int ret; + + dprintk(DBGLVL_API, "%s(%d)\n", __func__, mute); + + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR, + MUTE_CONTROL, sizeof(u8), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +/* 0 = silence, 0xff = full */ +int saa7164_api_set_audio_volume(struct saa7164_port *port, s8 level) +{ + struct saa7164_dev *dev = port->dev; + s16 v, min, max; + int ret; + + dprintk(DBGLVL_API, "%s(%d)\n", __func__, level); + + /* Obtain the min/max ranges */ + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_MIN, + VOLUME_CONTROL, sizeof(u16), &min); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_MAX, + VOLUME_CONTROL, sizeof(u16), &max); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_CUR, + (0x01 << 8) | VOLUME_CONTROL, sizeof(u16), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_API, "%s(%d) min=%d max=%d cur=%d\n", __func__, + level, min, max, v); + + v = level; + if (v < min) + v = min; + if (v > max) + v = max; + + /* Left */ + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR, + (0x01 << 8) | VOLUME_CONTROL, sizeof(s16), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Right */ + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR, + (0x02 << 8) | VOLUME_CONTROL, sizeof(s16), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, GET_CUR, + (0x01 << 8) | VOLUME_CONTROL, sizeof(u16), &v); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_API, "%s(%d) min=%d max=%d cur=%d\n", __func__, + level, min, max, v); + + return ret; +} + +int saa7164_api_set_audio_std(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResAudioDefaults lvl; + struct tmComResTunerStandard tvaudio; + int ret; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + /* Establish default levels */ + lvl.ucDecoderLevel = TMHW_LEV_ADJ_DECLEV_DEFAULT; + lvl.ucDecoderFM_Level = TMHW_LEV_ADJ_DECLEV_DEFAULT; + lvl.ucMonoLevel = TMHW_LEV_ADJ_MONOLEV_DEFAULT; + lvl.ucNICAM_Level = TMHW_LEV_ADJ_NICLEV_DEFAULT; + lvl.ucSAP_Level = TMHW_LEV_ADJ_SAPLEV_DEFAULT; + lvl.ucADC_Level = TMHW_LEV_ADJ_ADCLEV_DEFAULT; + ret = saa7164_cmd_send(port->dev, port->audfeat.unitid, SET_CUR, + AUDIO_DEFAULT_CONTROL, sizeof(struct tmComResAudioDefaults), + &lvl); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + /* Manually select the appropriate TV audio standard */ + if (port->encodernorm.id & V4L2_STD_NTSC) { + tvaudio.std = TU_STANDARD_NTSC_M; + tvaudio.country = 1; + } else { + tvaudio.std = TU_STANDARD_PAL_I; + tvaudio.country = 44; + } + + ret = saa7164_cmd_send(port->dev, port->tunerunit.unitid, SET_CUR, + TU_STANDARD_CONTROL, sizeof(tvaudio), &tvaudio); + if (ret != SAA_OK) + printk(KERN_ERR "%s() TU_STANDARD_CONTROL error, ret = 0x%x\n", + __func__, ret); + return ret; +} + +int saa7164_api_set_audio_detection(struct saa7164_port *port, int autodetect) +{ + struct saa7164_dev *dev = port->dev; + struct tmComResTunerStandardAuto p; + int ret; + + dprintk(DBGLVL_API, "%s(%d)\n", __func__, autodetect); + + /* Disable TV Audio autodetect if not already set (buggy) */ + if (autodetect) + p.mode = TU_STANDARD_AUTO; + else + p.mode = TU_STANDARD_MANUAL; + ret = saa7164_cmd_send(port->dev, port->tunerunit.unitid, SET_CUR, + TU_STANDARD_AUTO_CONTROL, sizeof(p), &p); + if (ret != SAA_OK) + printk(KERN_ERR + "%s() TU_STANDARD_AUTO_CONTROL error, ret = 0x%x\n", + __func__, ret); + + return ret; +} + +int saa7164_api_get_videomux(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + int ret; + + ret = saa7164_cmd_send(port->dev, port->vidproc.sourceid, GET_CUR, + SU_INPUT_SELECT_CONTROL, sizeof(u8), &port->mux_input); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_ENC, "%s() v_mux=%d\n", + __func__, port->mux_input); + + return ret; +} + +int saa7164_api_set_dif(struct saa7164_port *port, u8 reg, u8 val) +{ + struct saa7164_dev *dev = port->dev; + + u16 len = 0; + u8 buf[256]; + int ret; + u8 mas; + + dprintk(DBGLVL_API, "%s(nr=%d type=%d val=%x)\n", __func__, + port->nr, port->type, val); + + if (port->nr == 0) + mas = 0xd0; + else + mas = 0xe0; + + memset(buf, 0, sizeof(buf)); + + buf[0x00] = 0x04; + buf[0x01] = 0x00; + buf[0x02] = 0x00; + buf[0x03] = 0x00; + + buf[0x04] = 0x04; + buf[0x05] = 0x00; + buf[0x06] = 0x00; + buf[0x07] = 0x00; + + buf[0x08] = reg; + buf[0x09] = 0x26; + buf[0x0a] = mas; + buf[0x0b] = 0xb0; + + buf[0x0c] = val; + buf[0x0d] = 0x00; + buf[0x0e] = 0x00; + buf[0x0f] = 0x00; + + ret = saa7164_cmd_send(dev, port->ifunit.unitid, GET_LEN, + EXU_REGISTER_ACCESS_CONTROL, sizeof(len), &len); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret(1) = 0x%x\n", __func__, ret); + return -EIO; + } + + ret = saa7164_cmd_send(dev, port->ifunit.unitid, SET_CUR, + EXU_REGISTER_ACCESS_CONTROL, len, &buf); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret(2) = 0x%x\n", __func__, ret); +#if 0 + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, buf, 16, + false); +#endif + return ret == SAA_OK ? 0 : -EIO; +} + +/* Disable the IF block AGC controls */ +int saa7164_api_configure_dif(struct saa7164_port *port, u32 std) +{ + struct saa7164_dev *dev = port->dev; + int ret = 0; + u8 agc_disable; + + dprintk(DBGLVL_API, "%s(nr=%d, 0x%x)\n", __func__, port->nr, std); + + if (std & V4L2_STD_NTSC) { + dprintk(DBGLVL_API, " NTSC\n"); + saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_I) { + dprintk(DBGLVL_API, " PAL-I\n"); + saa7164_api_set_dif(port, 0x00, 0x08); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_M) { + dprintk(DBGLVL_API, " PAL-M\n"); + saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_N) { + dprintk(DBGLVL_API, " PAL-N\n"); + saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_Nc) { + dprintk(DBGLVL_API, " PAL-Nc\n"); + saa7164_api_set_dif(port, 0x00, 0x01); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_B) { + dprintk(DBGLVL_API, " PAL-B\n"); + saa7164_api_set_dif(port, 0x00, 0x02); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_PAL_DK) { + dprintk(DBGLVL_API, " PAL-DK\n"); + saa7164_api_set_dif(port, 0x00, 0x10); /* Video Standard */ + agc_disable = 0; + } else if (std & V4L2_STD_SECAM_L) { + dprintk(DBGLVL_API, " SECAM-L\n"); + saa7164_api_set_dif(port, 0x00, 0x20); /* Video Standard */ + agc_disable = 0; + } else { + /* Unknown standard, assume DTV */ + dprintk(DBGLVL_API, " Unknown (assuming DTV)\n"); + /* Undefinded Video Standard */ + saa7164_api_set_dif(port, 0x00, 0x80); + agc_disable = 1; + } + + saa7164_api_set_dif(port, 0x48, 0xa0); /* AGC Functions 1 */ + saa7164_api_set_dif(port, 0xc0, agc_disable); /* AGC Output Disable */ + saa7164_api_set_dif(port, 0x7c, 0x04); /* CVBS EQ */ + saa7164_api_set_dif(port, 0x04, 0x01); /* Active */ + msleep(100); + saa7164_api_set_dif(port, 0x04, 0x00); /* Active (again) */ + msleep(100); + + return ret; +} + +/* Ensure the dif is in the correct state for the operating mode + * (analog / dtv). We only configure the diff through the analog encoder + * so when we're in digital mode we need to find the appropriate encoder + * and use it to configure the DIF. + */ +int saa7164_api_initialize_dif(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_port *p = NULL; + int ret = -EINVAL; + u32 std = 0; + + dprintk(DBGLVL_API, "%s(nr=%d type=%d)\n", __func__, + port->nr, port->type); + + if (port->type == SAA7164_MPEG_ENCODER) { + /* Pick any analog standard to init the diff. + * we'll come back during encoder_init' + * and set the correct standard if requried. + */ + std = V4L2_STD_NTSC; + } else + if (port->type == SAA7164_MPEG_DVB) { + if (port->nr == SAA7164_PORT_TS1) + p = &dev->ports[SAA7164_PORT_ENC1]; + else + p = &dev->ports[SAA7164_PORT_ENC2]; + } else + if (port->type == SAA7164_MPEG_VBI) { + std = V4L2_STD_NTSC; + if (port->nr == SAA7164_PORT_VBI1) + p = &dev->ports[SAA7164_PORT_ENC1]; + else + p = &dev->ports[SAA7164_PORT_ENC2]; + } else + BUG(); + + if (p) + ret = saa7164_api_configure_dif(p, std); + + return ret; +} + +int saa7164_api_transition_port(struct saa7164_port *port, u8 mode) +{ + struct saa7164_dev *dev = port->dev; + + int ret; + + dprintk(DBGLVL_API, "%s(nr=%d unitid=0x%x,%d)\n", + __func__, port->nr, port->hwcfg.unitid, mode); + + ret = saa7164_cmd_send(port->dev, port->hwcfg.unitid, SET_CUR, + SAA_STATE_CONTROL, sizeof(mode), &mode); + if (ret != SAA_OK) + printk(KERN_ERR "%s(portnr %d unitid 0x%x) error, ret = 0x%x\n", + __func__, port->nr, port->hwcfg.unitid, ret); + + return ret; +} + +int saa7164_api_get_fw_version(struct saa7164_dev *dev, u32 *version) +{ + int ret; + + ret = saa7164_cmd_send(dev, 0, GET_CUR, + GET_FW_VERSION_CONTROL, sizeof(u32), version); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + return ret; +} + +int saa7164_api_read_eeprom(struct saa7164_dev *dev, u8 *buf, int buflen) +{ + u8 reg[] = { 0x0f, 0x00 }; + + if (buflen < 128) + return -ENOMEM; + + /* Assumption: Hauppauge eeprom is at 0xa0 on on bus 0 */ + /* TODO: Pull the details from the boards struct */ + return saa7164_api_i2c_read(&dev->i2c_bus[0], 0xa0 >> 1, sizeof(reg), + ®[0], 128, buf); +} + +int saa7164_api_configure_port_vbi(struct saa7164_dev *dev, + struct saa7164_port *port) +{ + struct tmComResVBIFormatDescrHeader *fmt = &port->vbi_fmt_ntsc; + + dprintk(DBGLVL_API, " bFormatIndex = 0x%x\n", fmt->bFormatIndex); + dprintk(DBGLVL_API, " VideoStandard = 0x%x\n", fmt->VideoStandard); + dprintk(DBGLVL_API, " StartLine = %d\n", fmt->StartLine); + dprintk(DBGLVL_API, " EndLine = %d\n", fmt->EndLine); + dprintk(DBGLVL_API, " FieldRate = %d\n", fmt->FieldRate); + dprintk(DBGLVL_API, " bNumLines = %d\n", fmt->bNumLines); + + /* Cache the hardware configuration in the port */ + + port->bufcounter = port->hwcfg.BARLocation; + port->pitch = port->hwcfg.BARLocation + (2 * sizeof(u32)); + port->bufsize = port->hwcfg.BARLocation + (3 * sizeof(u32)); + port->bufoffset = port->hwcfg.BARLocation + (4 * sizeof(u32)); + port->bufptr32l = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount) + sizeof(u32); + port->bufptr32h = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + port->bufptr64 = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + dprintk(DBGLVL_API, " = port->hwcfg.BARLocation = 0x%x\n", + port->hwcfg.BARLocation); + + dprintk(DBGLVL_API, " = VS_FORMAT_VBI (becomes dev->en[%d])\n", + port->nr); + + return 0; +} + +int saa7164_api_configure_port_mpeg2ts(struct saa7164_dev *dev, + struct saa7164_port *port, + struct tmComResTSFormatDescrHeader *tsfmt) +{ + dprintk(DBGLVL_API, " bFormatIndex = 0x%x\n", tsfmt->bFormatIndex); + dprintk(DBGLVL_API, " bDataOffset = 0x%x\n", tsfmt->bDataOffset); + dprintk(DBGLVL_API, " bPacketLength= 0x%x\n", tsfmt->bPacketLength); + dprintk(DBGLVL_API, " bStrideLength= 0x%x\n", tsfmt->bStrideLength); + dprintk(DBGLVL_API, " bguid = (....)\n"); + + /* Cache the hardware configuration in the port */ + + port->bufcounter = port->hwcfg.BARLocation; + port->pitch = port->hwcfg.BARLocation + (2 * sizeof(u32)); + port->bufsize = port->hwcfg.BARLocation + (3 * sizeof(u32)); + port->bufoffset = port->hwcfg.BARLocation + (4 * sizeof(u32)); + port->bufptr32l = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount) + sizeof(u32); + port->bufptr32h = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + port->bufptr64 = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + dprintk(DBGLVL_API, " = port->hwcfg.BARLocation = 0x%x\n", + port->hwcfg.BARLocation); + + dprintk(DBGLVL_API, " = VS_FORMAT_MPEGTS (becomes dev->ts[%d])\n", + port->nr); + + return 0; +} + +int saa7164_api_configure_port_mpeg2ps(struct saa7164_dev *dev, + struct saa7164_port *port, + struct tmComResPSFormatDescrHeader *fmt) +{ + dprintk(DBGLVL_API, " bFormatIndex = 0x%x\n", fmt->bFormatIndex); + dprintk(DBGLVL_API, " wPacketLength= 0x%x\n", fmt->wPacketLength); + dprintk(DBGLVL_API, " wPackLength= 0x%x\n", fmt->wPackLength); + dprintk(DBGLVL_API, " bPackDataType= 0x%x\n", fmt->bPackDataType); + + /* Cache the hardware configuration in the port */ + /* TODO: CHECK THIS in the port config */ + port->bufcounter = port->hwcfg.BARLocation; + port->pitch = port->hwcfg.BARLocation + (2 * sizeof(u32)); + port->bufsize = port->hwcfg.BARLocation + (3 * sizeof(u32)); + port->bufoffset = port->hwcfg.BARLocation + (4 * sizeof(u32)); + port->bufptr32l = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount) + sizeof(u32); + port->bufptr32h = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + port->bufptr64 = port->hwcfg.BARLocation + + (4 * sizeof(u32)) + + (sizeof(u32) * port->hwcfg.buffercount); + dprintk(DBGLVL_API, " = port->hwcfg.BARLocation = 0x%x\n", + port->hwcfg.BARLocation); + + dprintk(DBGLVL_API, " = VS_FORMAT_MPEGPS (becomes dev->enc[%d])\n", + port->nr); + + return 0; +} + +int saa7164_api_dump_subdevs(struct saa7164_dev *dev, u8 *buf, int len) +{ + struct saa7164_port *tsport = NULL; + struct saa7164_port *encport = NULL; + struct saa7164_port *vbiport = NULL; + u32 idx, next_offset; + int i; + struct tmComResDescrHeader *hdr, *t; + struct tmComResExtDevDescrHeader *exthdr; + struct tmComResPathDescrHeader *pathhdr; + struct tmComResAntTermDescrHeader *anttermhdr; + struct tmComResTunerDescrHeader *tunerunithdr; + struct tmComResDMATermDescrHeader *vcoutputtermhdr; + struct tmComResTSFormatDescrHeader *tsfmt; + struct tmComResPSFormatDescrHeader *psfmt; + struct tmComResSelDescrHeader *psel; + struct tmComResProcDescrHeader *pdh; + struct tmComResAFeatureDescrHeader *afd; + struct tmComResEncoderDescrHeader *edh; + struct tmComResVBIFormatDescrHeader *vbifmt; + u32 currpath = 0; + + dprintk(DBGLVL_API, + "%s(?,?,%d) sizeof(struct tmComResDescrHeader) = %d bytes\n", + __func__, len, (u32)sizeof(struct tmComResDescrHeader)); + + for (idx = 0; idx < (len - sizeof(struct tmComResDescrHeader));) { + + hdr = (struct tmComResDescrHeader *)(buf + idx); + + if (hdr->type != CS_INTERFACE) + return SAA_ERR_NOT_SUPPORTED; + + dprintk(DBGLVL_API, "@ 0x%x =\n", idx); + switch (hdr->subtype) { + case GENERAL_REQUEST: + dprintk(DBGLVL_API, " GENERAL_REQUEST\n"); + break; + case VC_TUNER_PATH: + dprintk(DBGLVL_API, " VC_TUNER_PATH\n"); + pathhdr = (struct tmComResPathDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " pathid = 0x%x\n", + pathhdr->pathid); + currpath = pathhdr->pathid; + break; + case VC_INPUT_TERMINAL: + dprintk(DBGLVL_API, " VC_INPUT_TERMINAL\n"); + anttermhdr = + (struct tmComResAntTermDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " terminalid = 0x%x\n", + anttermhdr->terminalid); + dprintk(DBGLVL_API, " terminaltype = 0x%x\n", + anttermhdr->terminaltype); + switch (anttermhdr->terminaltype) { + case ITT_ANTENNA: + dprintk(DBGLVL_API, " = ITT_ANTENNA\n"); + break; + case LINE_CONNECTOR: + dprintk(DBGLVL_API, " = LINE_CONNECTOR\n"); + break; + case SPDIF_CONNECTOR: + dprintk(DBGLVL_API, " = SPDIF_CONNECTOR\n"); + break; + case COMPOSITE_CONNECTOR: + dprintk(DBGLVL_API, + " = COMPOSITE_CONNECTOR\n"); + break; + case SVIDEO_CONNECTOR: + dprintk(DBGLVL_API, " = SVIDEO_CONNECTOR\n"); + break; + case COMPONENT_CONNECTOR: + dprintk(DBGLVL_API, + " = COMPONENT_CONNECTOR\n"); + break; + case STANDARD_DMA: + dprintk(DBGLVL_API, " = STANDARD_DMA\n"); + break; + default: + dprintk(DBGLVL_API, " = undefined (0x%x)\n", + anttermhdr->terminaltype); + } + dprintk(DBGLVL_API, " assocterminal= 0x%x\n", + anttermhdr->assocterminal); + dprintk(DBGLVL_API, " iterminal = 0x%x\n", + anttermhdr->iterminal); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + anttermhdr->controlsize); + break; + case VC_OUTPUT_TERMINAL: + dprintk(DBGLVL_API, " VC_OUTPUT_TERMINAL\n"); + vcoutputtermhdr = + (struct tmComResDMATermDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + vcoutputtermhdr->unitid); + dprintk(DBGLVL_API, " terminaltype = 0x%x\n", + vcoutputtermhdr->terminaltype); + switch (vcoutputtermhdr->terminaltype) { + case ITT_ANTENNA: + dprintk(DBGLVL_API, " = ITT_ANTENNA\n"); + break; + case LINE_CONNECTOR: + dprintk(DBGLVL_API, " = LINE_CONNECTOR\n"); + break; + case SPDIF_CONNECTOR: + dprintk(DBGLVL_API, " = SPDIF_CONNECTOR\n"); + break; + case COMPOSITE_CONNECTOR: + dprintk(DBGLVL_API, + " = COMPOSITE_CONNECTOR\n"); + break; + case SVIDEO_CONNECTOR: + dprintk(DBGLVL_API, " = SVIDEO_CONNECTOR\n"); + break; + case COMPONENT_CONNECTOR: + dprintk(DBGLVL_API, + " = COMPONENT_CONNECTOR\n"); + break; + case STANDARD_DMA: + dprintk(DBGLVL_API, " = STANDARD_DMA\n"); + break; + default: + dprintk(DBGLVL_API, " = undefined (0x%x)\n", + vcoutputtermhdr->terminaltype); + } + dprintk(DBGLVL_API, " assocterminal= 0x%x\n", + vcoutputtermhdr->assocterminal); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + vcoutputtermhdr->sourceid); + dprintk(DBGLVL_API, " iterminal = 0x%x\n", + vcoutputtermhdr->iterminal); + dprintk(DBGLVL_API, " BARLocation = 0x%x\n", + vcoutputtermhdr->BARLocation); + dprintk(DBGLVL_API, " flags = 0x%x\n", + vcoutputtermhdr->flags); + dprintk(DBGLVL_API, " interruptid = 0x%x\n", + vcoutputtermhdr->interruptid); + dprintk(DBGLVL_API, " buffercount = 0x%x\n", + vcoutputtermhdr->buffercount); + dprintk(DBGLVL_API, " metadatasize = 0x%x\n", + vcoutputtermhdr->metadatasize); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + vcoutputtermhdr->controlsize); + dprintk(DBGLVL_API, " numformats = 0x%x\n", + vcoutputtermhdr->numformats); + + t = (struct tmComResDescrHeader *) + ((struct tmComResDMATermDescrHeader *)(buf + idx)); + next_offset = idx + (vcoutputtermhdr->len); + for (i = 0; i < vcoutputtermhdr->numformats; i++) { + t = (struct tmComResDescrHeader *) + (buf + next_offset); + switch (t->subtype) { + case VS_FORMAT_MPEG2TS: + tsfmt = + (struct tmComResTSFormatDescrHeader *)t; + if (currpath == 1) + tsport = &dev->ports[SAA7164_PORT_TS1]; + else + tsport = &dev->ports[SAA7164_PORT_TS2]; + memcpy(&tsport->hwcfg, vcoutputtermhdr, + sizeof(*vcoutputtermhdr)); + saa7164_api_configure_port_mpeg2ts(dev, + tsport, tsfmt); + break; + case VS_FORMAT_MPEG2PS: + psfmt = + (struct tmComResPSFormatDescrHeader *)t; + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->hwcfg, vcoutputtermhdr, + sizeof(*vcoutputtermhdr)); + saa7164_api_configure_port_mpeg2ps(dev, + encport, psfmt); + break; + case VS_FORMAT_VBI: + vbifmt = + (struct tmComResVBIFormatDescrHeader *)t; + if (currpath == 1) + vbiport = &dev->ports[SAA7164_PORT_VBI1]; + else + vbiport = &dev->ports[SAA7164_PORT_VBI2]; + memcpy(&vbiport->hwcfg, vcoutputtermhdr, + sizeof(*vcoutputtermhdr)); + memcpy(&vbiport->vbi_fmt_ntsc, vbifmt, + sizeof(*vbifmt)); + saa7164_api_configure_port_vbi(dev, + vbiport); + break; + case VS_FORMAT_RDS: + dprintk(DBGLVL_API, + " = VS_FORMAT_RDS\n"); + break; + case VS_FORMAT_UNCOMPRESSED: + dprintk(DBGLVL_API, + " = VS_FORMAT_UNCOMPRESSED\n"); + break; + case VS_FORMAT_TYPE: + dprintk(DBGLVL_API, + " = VS_FORMAT_TYPE\n"); + break; + default: + dprintk(DBGLVL_API, + " = undefined (0x%x)\n", + t->subtype); + } + next_offset += t->len; + } + + break; + case TUNER_UNIT: + dprintk(DBGLVL_API, " TUNER_UNIT\n"); + tunerunithdr = + (struct tmComResTunerDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + tunerunithdr->unitid); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + tunerunithdr->sourceid); + dprintk(DBGLVL_API, " iunit = 0x%x\n", + tunerunithdr->iunit); + dprintk(DBGLVL_API, " tuningstandards = 0x%x\n", + tunerunithdr->tuningstandards); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + tunerunithdr->controlsize); + dprintk(DBGLVL_API, " controls = 0x%x\n", + tunerunithdr->controls); + + if (tunerunithdr->unitid == tunerunithdr->iunit) { + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->tunerunit, tunerunithdr, + sizeof(struct tmComResTunerDescrHeader)); + dprintk(DBGLVL_API, + " (becomes dev->enc[%d] tuner)\n", + encport->nr); + } + break; + case VC_SELECTOR_UNIT: + psel = (struct tmComResSelDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " VC_SELECTOR_UNIT\n"); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + psel->unitid); + dprintk(DBGLVL_API, " nrinpins = 0x%x\n", + psel->nrinpins); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + psel->sourceid); + break; + case VC_PROCESSING_UNIT: + pdh = (struct tmComResProcDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " VC_PROCESSING_UNIT\n"); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + pdh->unitid); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + pdh->sourceid); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + pdh->controlsize); + if (pdh->controlsize == 0x04) { + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->vidproc, pdh, + sizeof(struct tmComResProcDescrHeader)); + dprintk(DBGLVL_API, " (becomes dev->enc[%d])\n", + encport->nr); + } + break; + case FEATURE_UNIT: + afd = (struct tmComResAFeatureDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " FEATURE_UNIT\n"); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + afd->unitid); + dprintk(DBGLVL_API, " sourceid = 0x%x\n", + afd->sourceid); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + afd->controlsize); + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->audfeat, afd, + sizeof(struct tmComResAFeatureDescrHeader)); + dprintk(DBGLVL_API, " (becomes dev->enc[%d])\n", + encport->nr); + break; + case ENCODER_UNIT: + edh = (struct tmComResEncoderDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " ENCODER_UNIT\n"); + dprintk(DBGLVL_API, " subtype = 0x%x\n", edh->subtype); + dprintk(DBGLVL_API, " unitid = 0x%x\n", edh->unitid); + dprintk(DBGLVL_API, " vsourceid = 0x%x\n", + edh->vsourceid); + dprintk(DBGLVL_API, " asourceid = 0x%x\n", + edh->asourceid); + dprintk(DBGLVL_API, " iunit = 0x%x\n", edh->iunit); + if (edh->iunit == edh->unitid) { + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->encunit, edh, + sizeof(struct tmComResEncoderDescrHeader)); + dprintk(DBGLVL_API, + " (becomes dev->enc[%d])\n", + encport->nr); + } + break; + case EXTENSION_UNIT: + dprintk(DBGLVL_API, " EXTENSION_UNIT\n"); + exthdr = (struct tmComResExtDevDescrHeader *)(buf + idx); + dprintk(DBGLVL_API, " unitid = 0x%x\n", + exthdr->unitid); + dprintk(DBGLVL_API, " deviceid = 0x%x\n", + exthdr->deviceid); + dprintk(DBGLVL_API, " devicetype = 0x%x\n", + exthdr->devicetype); + if (exthdr->devicetype & 0x1) + dprintk(DBGLVL_API, " = Decoder Device\n"); + if (exthdr->devicetype & 0x2) + dprintk(DBGLVL_API, " = GPIO Source\n"); + if (exthdr->devicetype & 0x4) + dprintk(DBGLVL_API, " = Video Decoder\n"); + if (exthdr->devicetype & 0x8) + dprintk(DBGLVL_API, " = Audio Decoder\n"); + if (exthdr->devicetype & 0x20) + dprintk(DBGLVL_API, " = Crossbar\n"); + if (exthdr->devicetype & 0x40) + dprintk(DBGLVL_API, " = Tuner\n"); + if (exthdr->devicetype & 0x80) + dprintk(DBGLVL_API, " = IF PLL\n"); + if (exthdr->devicetype & 0x100) + dprintk(DBGLVL_API, " = Demodulator\n"); + if (exthdr->devicetype & 0x200) + dprintk(DBGLVL_API, " = RDS Decoder\n"); + if (exthdr->devicetype & 0x400) + dprintk(DBGLVL_API, " = Encoder\n"); + if (exthdr->devicetype & 0x800) + dprintk(DBGLVL_API, " = IR Decoder\n"); + if (exthdr->devicetype & 0x1000) + dprintk(DBGLVL_API, " = EEPROM\n"); + if (exthdr->devicetype & 0x2000) + dprintk(DBGLVL_API, + " = VBI Decoder\n"); + if (exthdr->devicetype & 0x10000) + dprintk(DBGLVL_API, + " = Streaming Device\n"); + if (exthdr->devicetype & 0x20000) + dprintk(DBGLVL_API, + " = DRM Device\n"); + if (exthdr->devicetype & 0x40000000) + dprintk(DBGLVL_API, + " = Generic Device\n"); + if (exthdr->devicetype & 0x80000000) + dprintk(DBGLVL_API, + " = Config Space Device\n"); + dprintk(DBGLVL_API, " numgpiopins = 0x%x\n", + exthdr->numgpiopins); + dprintk(DBGLVL_API, " numgpiogroups = 0x%x\n", + exthdr->numgpiogroups); + dprintk(DBGLVL_API, " controlsize = 0x%x\n", + exthdr->controlsize); + if (exthdr->devicetype & 0x80) { + if (currpath == 1) + encport = &dev->ports[SAA7164_PORT_ENC1]; + else + encport = &dev->ports[SAA7164_PORT_ENC2]; + memcpy(&encport->ifunit, exthdr, + sizeof(struct tmComResExtDevDescrHeader)); + dprintk(DBGLVL_API, + " (becomes dev->enc[%d])\n", + encport->nr); + } + break; + case PVC_INFRARED_UNIT: + dprintk(DBGLVL_API, " PVC_INFRARED_UNIT\n"); + break; + case DRM_UNIT: + dprintk(DBGLVL_API, " DRM_UNIT\n"); + break; + default: + dprintk(DBGLVL_API, "default %d\n", hdr->subtype); + } + + dprintk(DBGLVL_API, " 1.%x\n", hdr->len); + dprintk(DBGLVL_API, " 2.%x\n", hdr->type); + dprintk(DBGLVL_API, " 3.%x\n", hdr->subtype); + dprintk(DBGLVL_API, " 4.%x\n", hdr->unitid); + + idx += hdr->len; + } + + return 0; +} + +int saa7164_api_enum_subdevs(struct saa7164_dev *dev) +{ + int ret; + u32 buflen = 0; + u8 *buf; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + /* Get the total descriptor length */ + ret = saa7164_cmd_send(dev, 0, GET_LEN, + GET_DESCRIPTORS_CONTROL, sizeof(buflen), &buflen); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + + dprintk(DBGLVL_API, "%s() total descriptor size = %d bytes.\n", + __func__, buflen); + + /* Allocate enough storage for all of the descs */ + buf = kzalloc(buflen, GFP_KERNEL); + if (!buf) + return SAA_ERR_NO_RESOURCES; + + /* Retrieve them */ + ret = saa7164_cmd_send(dev, 0, GET_CUR, + GET_DESCRIPTORS_CONTROL, buflen, buf); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret = 0x%x\n", __func__, ret); + goto out; + } + + if (saa_debug & DBGLVL_API) + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, buf, + buflen & ~15, false); + + saa7164_api_dump_subdevs(dev, buf, buflen); + +out: + kfree(buf); + return ret; +} + +int saa7164_api_i2c_read(struct saa7164_i2c *bus, u8 addr, u32 reglen, u8 *reg, + u32 datalen, u8 *data) +{ + struct saa7164_dev *dev = bus->dev; + u16 len = 0; + int unitid; + u8 buf[256]; + int ret; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + if (reglen > 4) + return -EIO; + + /* Prepare the send buffer */ + /* Bytes 00-03 source register length + * 04-07 source bytes to read + * 08... register address + */ + memset(buf, 0, sizeof(buf)); + memcpy((buf + 2 * sizeof(u32) + 0), reg, reglen); + *((u32 *)(buf + 0 * sizeof(u32))) = reglen; + *((u32 *)(buf + 1 * sizeof(u32))) = datalen; + + unitid = saa7164_i2caddr_to_unitid(bus, addr); + if (unitid < 0) { + printk(KERN_ERR + "%s() error, cannot translate regaddr 0x%x to unitid\n", + __func__, addr); + return -EIO; + } + + ret = saa7164_cmd_send(bus->dev, unitid, GET_LEN, + EXU_REGISTER_ACCESS_CONTROL, sizeof(len), &len); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret(1) = 0x%x\n", __func__, ret); + return -EIO; + } + + dprintk(DBGLVL_API, "%s() len = %d bytes\n", __func__, len); + + if (saa_debug & DBGLVL_I2C) + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, buf, + 32, false); + + ret = saa7164_cmd_send(bus->dev, unitid, GET_CUR, + EXU_REGISTER_ACCESS_CONTROL, len, &buf); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret(2) = 0x%x\n", __func__, ret); + else { + if (saa_debug & DBGLVL_I2C) + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, + buf, sizeof(buf), false); + memcpy(data, (buf + 2 * sizeof(u32) + reglen), datalen); + } + + return ret == SAA_OK ? 0 : -EIO; +} + +/* For a given 8 bit i2c address device, write the buffer */ +int saa7164_api_i2c_write(struct saa7164_i2c *bus, u8 addr, u32 datalen, + u8 *data) +{ + struct saa7164_dev *dev = bus->dev; + u16 len = 0; + int unitid; + int reglen; + u8 buf[256]; + int ret; + + dprintk(DBGLVL_API, "%s()\n", __func__); + + if ((datalen == 0) || (datalen > 232)) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + unitid = saa7164_i2caddr_to_unitid(bus, addr); + if (unitid < 0) { + printk(KERN_ERR + "%s() error, cannot translate regaddr 0x%x to unitid\n", + __func__, addr); + return -EIO; + } + + reglen = saa7164_i2caddr_to_reglen(bus, addr); + if (reglen < 0) { + printk(KERN_ERR + "%s() error, cannot translate regaddr to reglen\n", + __func__); + return -EIO; + } + + ret = saa7164_cmd_send(bus->dev, unitid, GET_LEN, + EXU_REGISTER_ACCESS_CONTROL, sizeof(len), &len); + if (ret != SAA_OK) { + printk(KERN_ERR "%s() error, ret(1) = 0x%x\n", __func__, ret); + return -EIO; + } + + dprintk(DBGLVL_API, "%s() len = %d bytes\n", __func__, len); + + /* Prepare the send buffer */ + /* Bytes 00-03 dest register length + * 04-07 dest bytes to write + * 08... register address + */ + *((u32 *)(buf + 0 * sizeof(u32))) = reglen; + *((u32 *)(buf + 1 * sizeof(u32))) = datalen - reglen; + memcpy((buf + 2 * sizeof(u32)), data, datalen); + + if (saa_debug & DBGLVL_I2C) + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, + buf, sizeof(buf), false); + + ret = saa7164_cmd_send(bus->dev, unitid, SET_CUR, + EXU_REGISTER_ACCESS_CONTROL, len, &buf); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret(2) = 0x%x\n", __func__, ret); + + return ret == SAA_OK ? 0 : -EIO; +} + +int saa7164_api_modify_gpio(struct saa7164_dev *dev, u8 unitid, + u8 pin, u8 state) +{ + int ret; + struct tmComResGPIO t; + + dprintk(DBGLVL_API, "%s(0x%x, %d, %d)\n", + __func__, unitid, pin, state); + + if ((pin > 7) || (state > 2)) + return SAA_ERR_BAD_PARAMETER; + + t.pin = pin; + t.state = state; + + ret = saa7164_cmd_send(dev, unitid, SET_CUR, + EXU_GPIO_CONTROL, sizeof(t), &t); + if (ret != SAA_OK) + printk(KERN_ERR "%s() error, ret = 0x%x\n", + __func__, ret); + + return ret; +} + +int saa7164_api_set_gpiobit(struct saa7164_dev *dev, u8 unitid, + u8 pin) +{ + return saa7164_api_modify_gpio(dev, unitid, pin, 1); +} + +int saa7164_api_clear_gpiobit(struct saa7164_dev *dev, u8 unitid, + u8 pin) +{ + return saa7164_api_modify_gpio(dev, unitid, pin, 0); +} + diff --git a/drivers/media/video/saa7164/saa7164-buffer.c b/drivers/media/pci/saa7164/saa7164-buffer.c similarity index 100% rename from drivers/media/video/saa7164/saa7164-buffer.c rename to drivers/media/pci/saa7164/saa7164-buffer.c diff --git a/drivers/media/video/saa7164/saa7164-bus.c b/drivers/media/pci/saa7164/saa7164-bus.c similarity index 100% rename from drivers/media/video/saa7164/saa7164-bus.c rename to drivers/media/pci/saa7164/saa7164-bus.c diff --git a/drivers/media/video/saa7164/saa7164-cards.c b/drivers/media/pci/saa7164/saa7164-cards.c similarity index 100% rename from drivers/media/video/saa7164/saa7164-cards.c rename to drivers/media/pci/saa7164/saa7164-cards.c diff --git a/drivers/media/video/saa7164/saa7164-cmd.c b/drivers/media/pci/saa7164/saa7164-cmd.c similarity index 100% rename from drivers/media/video/saa7164/saa7164-cmd.c rename to drivers/media/pci/saa7164/saa7164-cmd.c diff --git a/drivers/media/pci/saa7164/saa7164-core.c b/drivers/media/pci/saa7164/saa7164-core.c new file mode 100644 index 000000000000..2c9ad878bef3 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164-core.c @@ -0,0 +1,1488 @@ +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010 Steven Toth + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_PROC_FS +#include +#endif +#include "saa7164.h" + +MODULE_DESCRIPTION("Driver for NXP SAA7164 based TV cards"); +MODULE_AUTHOR("Steven Toth "); +MODULE_LICENSE("GPL"); + +/* + * 1 Basic + * 2 + * 4 i2c + * 8 api + * 16 cmd + * 32 bus + */ + +unsigned int saa_debug; +module_param_named(debug, saa_debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); + +unsigned int fw_debug; +module_param(fw_debug, int, 0644); +MODULE_PARM_DESC(fw_debug, "Firware debug level def:2"); + +unsigned int encoder_buffers = SAA7164_MAX_ENCODER_BUFFERS; +module_param(encoder_buffers, int, 0644); +MODULE_PARM_DESC(encoder_buffers, "Total buffers in read queue 16-512 def:64"); + +unsigned int vbi_buffers = SAA7164_MAX_VBI_BUFFERS; +module_param(vbi_buffers, int, 0644); +MODULE_PARM_DESC(vbi_buffers, "Total buffers in read queue 16-512 def:64"); + +unsigned int waitsecs = 10; +module_param(waitsecs, int, 0644); +MODULE_PARM_DESC(waitsecs, "timeout on firmware messages"); + +static unsigned int card[] = {[0 ... (SAA7164_MAXBOARDS - 1)] = UNSET }; +module_param_array(card, int, NULL, 0444); +MODULE_PARM_DESC(card, "card type"); + +unsigned int print_histogram = 64; +module_param(print_histogram, int, 0644); +MODULE_PARM_DESC(print_histogram, "print histogram values once"); + +unsigned int crc_checking = 1; +module_param(crc_checking, int, 0644); +MODULE_PARM_DESC(crc_checking, "enable crc sanity checking on buffers"); + +unsigned int guard_checking = 1; +module_param(guard_checking, int, 0644); +MODULE_PARM_DESC(guard_checking, + "enable dma sanity checking for buffer overruns"); + +static unsigned int saa7164_devcount; + +static DEFINE_MUTEX(devlist); +LIST_HEAD(saa7164_devlist); + +#define INT_SIZE 16 + +static void saa7164_pack_verifier(struct saa7164_buffer *buf) +{ + u8 *p = (u8 *)buf->cpu; + int i; + + for (i = 0; i < buf->actual_size; i += 2048) { + + if ((*(p + i + 0) != 0x00) || (*(p + i + 1) != 0x00) || + (*(p + i + 2) != 0x01) || (*(p + i + 3) != 0xBA)) { + printk(KERN_ERR "No pack at 0x%x\n", i); +#if 0 + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, + p + 1, 32, false); +#endif + } + } +} + +#define FIXED_VIDEO_PID 0xf1 +#define FIXED_AUDIO_PID 0xf2 + +static void saa7164_ts_verifier(struct saa7164_buffer *buf) +{ + struct saa7164_port *port = buf->port; + u32 i; + u8 cc, a; + u16 pid; + u8 __iomem *bufcpu = (u8 *)buf->cpu; + + port->sync_errors = 0; + port->v_cc_errors = 0; + port->a_cc_errors = 0; + + for (i = 0; i < buf->actual_size; i += 188) { + if (*(bufcpu + i) != 0x47) + port->sync_errors++; + + /* TODO: Query pid lower 8 bits, ignoring upper bits intensionally */ + pid = ((*(bufcpu + i + 1) & 0x1f) << 8) | *(bufcpu + i + 2); + cc = *(bufcpu + i + 3) & 0x0f; + + if (pid == FIXED_VIDEO_PID) { + a = ((port->last_v_cc + 1) & 0x0f); + if (a != cc) { + printk(KERN_ERR "video cc last = %x current = %x i = %d\n", + port->last_v_cc, cc, i); + port->v_cc_errors++; + } + + port->last_v_cc = cc; + } else + if (pid == FIXED_AUDIO_PID) { + a = ((port->last_a_cc + 1) & 0x0f); + if (a != cc) { + printk(KERN_ERR "audio cc last = %x current = %x i = %d\n", + port->last_a_cc, cc, i); + port->a_cc_errors++; + } + + port->last_a_cc = cc; + } + + } + + /* Only report errors if we've been through this function atleast + * once already and the cached cc values are primed. First time through + * always generates errors. + */ + if (port->v_cc_errors && (port->done_first_interrupt > 1)) + printk(KERN_ERR "video pid cc, %d errors\n", port->v_cc_errors); + + if (port->a_cc_errors && (port->done_first_interrupt > 1)) + printk(KERN_ERR "audio pid cc, %d errors\n", port->a_cc_errors); + + if (port->sync_errors && (port->done_first_interrupt > 1)) + printk(KERN_ERR "sync_errors = %d\n", port->sync_errors); + + if (port->done_first_interrupt == 1) + port->done_first_interrupt++; +} + +static void saa7164_histogram_reset(struct saa7164_histogram *hg, char *name) +{ + int i; + + memset(hg, 0, sizeof(struct saa7164_histogram)); + strcpy(hg->name, name); + + /* First 30ms x 1ms */ + for (i = 0; i < 30; i++) + hg->counter1[0 + i].val = i; + + /* 30 - 200ms x 10ms */ + for (i = 0; i < 18; i++) + hg->counter1[30 + i].val = 30 + (i * 10); + + /* 200 - 2000ms x 100ms */ + for (i = 0; i < 15; i++) + hg->counter1[48 + i].val = 200 + (i * 200); + + /* Catch all massive value (2secs) */ + hg->counter1[55].val = 2000; + + /* Catch all massive value (4secs) */ + hg->counter1[56].val = 4000; + + /* Catch all massive value (8secs) */ + hg->counter1[57].val = 8000; + + /* Catch all massive value (15secs) */ + hg->counter1[58].val = 15000; + + /* Catch all massive value (30secs) */ + hg->counter1[59].val = 30000; + + /* Catch all massive value (60secs) */ + hg->counter1[60].val = 60000; + + /* Catch all massive value (5mins) */ + hg->counter1[61].val = 300000; + + /* Catch all massive value (15mins) */ + hg->counter1[62].val = 900000; + + /* Catch all massive values (1hr) */ + hg->counter1[63].val = 3600000; +} + +void saa7164_histogram_update(struct saa7164_histogram *hg, u32 val) +{ + int i; + for (i = 0; i < 64; i++) { + if (val <= hg->counter1[i].val) { + hg->counter1[i].count++; + hg->counter1[i].update_time = jiffies; + break; + } + } +} + +static void saa7164_histogram_print(struct saa7164_port *port, + struct saa7164_histogram *hg) +{ + u32 entries = 0; + int i; + + printk(KERN_ERR "Histogram named %s (ms, count, last_update_jiffy)\n", hg->name); + for (i = 0; i < 64; i++) { + if (hg->counter1[i].count == 0) + continue; + + printk(KERN_ERR " %4d %12d %Ld\n", + hg->counter1[i].val, + hg->counter1[i].count, + hg->counter1[i].update_time); + + entries++; + } + printk(KERN_ERR "Total: %d\n", entries); +} + +static void saa7164_work_enchandler_helper(struct saa7164_port *port, int bufnr) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf = NULL; + struct saa7164_user_buffer *ubuf = NULL; + struct list_head *c, *n; + int i = 0; + u8 __iomem *p; + + mutex_lock(&port->dmaqueue_lock); + list_for_each_safe(c, n, &port->dmaqueue.list) { + + buf = list_entry(c, struct saa7164_buffer, list); + if (i++ > port->hwcfg.buffercount) { + printk(KERN_ERR "%s() illegal i count %d\n", + __func__, i); + break; + } + + if (buf->idx == bufnr) { + + /* Found the buffer, deal with it */ + dprintk(DBGLVL_IRQ, "%s() bufnr: %d\n", __func__, bufnr); + + if (crc_checking) { + /* Throw a new checksum on the dma buffer */ + buf->crc = crc32(0, buf->cpu, buf->actual_size); + } + + if (guard_checking) { + p = (u8 *)buf->cpu; + if ((*(p + buf->actual_size + 0) != 0xff) || + (*(p + buf->actual_size + 1) != 0xff) || + (*(p + buf->actual_size + 2) != 0xff) || + (*(p + buf->actual_size + 3) != 0xff) || + (*(p + buf->actual_size + 0x10) != 0xff) || + (*(p + buf->actual_size + 0x11) != 0xff) || + (*(p + buf->actual_size + 0x12) != 0xff) || + (*(p + buf->actual_size + 0x13) != 0xff)) { + printk(KERN_ERR "%s() buf %p guard buffer breach\n", + __func__, buf); +#if 0 + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, + p + buf->actual_size - 32, 64, false); +#endif + } + } + + if ((port->nr != SAA7164_PORT_VBI1) && (port->nr != SAA7164_PORT_VBI2)) { + /* Validate the incoming buffer content */ + if (port->encoder_params.stream_type == V4L2_MPEG_STREAM_TYPE_MPEG2_TS) + saa7164_ts_verifier(buf); + else if (port->encoder_params.stream_type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS) + saa7164_pack_verifier(buf); + } + + /* find a free user buffer and clone to it */ + if (!list_empty(&port->list_buf_free.list)) { + + /* Pull the first buffer from the used list */ + ubuf = list_first_entry(&port->list_buf_free.list, + struct saa7164_user_buffer, list); + + if (buf->actual_size <= ubuf->actual_size) { + + memcpy_fromio(ubuf->data, buf->cpu, + ubuf->actual_size); + + if (crc_checking) { + /* Throw a new checksum on the read buffer */ + ubuf->crc = crc32(0, ubuf->data, ubuf->actual_size); + } + + /* Requeue the buffer on the free list */ + ubuf->pos = 0; + + list_move_tail(&ubuf->list, + &port->list_buf_used.list); + + /* Flag any userland waiters */ + wake_up_interruptible(&port->wait_read); + + } else { + printk(KERN_ERR "buf %p bufsize fails match\n", buf); + } + + } else + printk(KERN_ERR "encirq no free buffers, increase param encoder_buffers\n"); + + /* Ensure offset into buffer remains 0, fill buffer + * with known bad data. We check for this data at a later point + * in time. */ + saa7164_buffer_zero_offsets(port, bufnr); + memset_io(buf->cpu, 0xff, buf->pci_size); + if (crc_checking) { + /* Throw yet aanother new checksum on the dma buffer */ + buf->crc = crc32(0, buf->cpu, buf->actual_size); + } + + break; + } + } + mutex_unlock(&port->dmaqueue_lock); +} + +static void saa7164_work_enchandler(struct work_struct *w) +{ + struct saa7164_port *port = + container_of(w, struct saa7164_port, workenc); + struct saa7164_dev *dev = port->dev; + + u32 wp, mcb, rp, cnt = 0; + + port->last_svc_msecs_diff = port->last_svc_msecs; + port->last_svc_msecs = jiffies_to_msecs(jiffies); + + port->last_svc_msecs_diff = port->last_svc_msecs - + port->last_svc_msecs_diff; + + saa7164_histogram_update(&port->svc_interval, + port->last_svc_msecs_diff); + + port->last_irq_svc_msecs_diff = port->last_svc_msecs - + port->last_irq_msecs; + + saa7164_histogram_update(&port->irq_svc_interval, + port->last_irq_svc_msecs_diff); + + dprintk(DBGLVL_IRQ, + "%s() %Ldms elapsed irq->deferred %Ldms wp: %d rp: %d\n", + __func__, + port->last_svc_msecs_diff, + port->last_irq_svc_msecs_diff, + port->last_svc_wp, + port->last_svc_rp + ); + + /* Current write position */ + wp = saa7164_readl(port->bufcounter); + if (wp > (port->hwcfg.buffercount - 1)) { + printk(KERN_ERR "%s() illegal buf count %d\n", __func__, wp); + return; + } + + /* Most current complete buffer */ + if (wp == 0) + mcb = (port->hwcfg.buffercount - 1); + else + mcb = wp - 1; + + while (1) { + if (port->done_first_interrupt == 0) { + port->done_first_interrupt++; + rp = mcb; + } else + rp = (port->last_svc_rp + 1) % 8; + + if ((rp < 0) || (rp > (port->hwcfg.buffercount - 1))) { + printk(KERN_ERR "%s() illegal rp count %d\n", __func__, rp); + break; + } + + saa7164_work_enchandler_helper(port, rp); + port->last_svc_rp = rp; + cnt++; + + if (rp == mcb) + break; + } + + /* TODO: Convert this into a /proc/saa7164 style readable file */ + if (print_histogram == port->nr) { + saa7164_histogram_print(port, &port->irq_interval); + saa7164_histogram_print(port, &port->svc_interval); + saa7164_histogram_print(port, &port->irq_svc_interval); + saa7164_histogram_print(port, &port->read_interval); + saa7164_histogram_print(port, &port->poll_interval); + /* TODO: fix this to preserve any previous state */ + print_histogram = 64 + port->nr; + } +} + +static void saa7164_work_vbihandler(struct work_struct *w) +{ + struct saa7164_port *port = + container_of(w, struct saa7164_port, workenc); + struct saa7164_dev *dev = port->dev; + + u32 wp, mcb, rp, cnt = 0; + + port->last_svc_msecs_diff = port->last_svc_msecs; + port->last_svc_msecs = jiffies_to_msecs(jiffies); + port->last_svc_msecs_diff = port->last_svc_msecs - + port->last_svc_msecs_diff; + + saa7164_histogram_update(&port->svc_interval, + port->last_svc_msecs_diff); + + port->last_irq_svc_msecs_diff = port->last_svc_msecs - + port->last_irq_msecs; + + saa7164_histogram_update(&port->irq_svc_interval, + port->last_irq_svc_msecs_diff); + + dprintk(DBGLVL_IRQ, + "%s() %Ldms elapsed irq->deferred %Ldms wp: %d rp: %d\n", + __func__, + port->last_svc_msecs_diff, + port->last_irq_svc_msecs_diff, + port->last_svc_wp, + port->last_svc_rp + ); + + /* Current write position */ + wp = saa7164_readl(port->bufcounter); + if (wp > (port->hwcfg.buffercount - 1)) { + printk(KERN_ERR "%s() illegal buf count %d\n", __func__, wp); + return; + } + + /* Most current complete buffer */ + if (wp == 0) + mcb = (port->hwcfg.buffercount - 1); + else + mcb = wp - 1; + + while (1) { + if (port->done_first_interrupt == 0) { + port->done_first_interrupt++; + rp = mcb; + } else + rp = (port->last_svc_rp + 1) % 8; + + if ((rp < 0) || (rp > (port->hwcfg.buffercount - 1))) { + printk(KERN_ERR "%s() illegal rp count %d\n", __func__, rp); + break; + } + + saa7164_work_enchandler_helper(port, rp); + port->last_svc_rp = rp; + cnt++; + + if (rp == mcb) + break; + } + + /* TODO: Convert this into a /proc/saa7164 style readable file */ + if (print_histogram == port->nr) { + saa7164_histogram_print(port, &port->irq_interval); + saa7164_histogram_print(port, &port->svc_interval); + saa7164_histogram_print(port, &port->irq_svc_interval); + saa7164_histogram_print(port, &port->read_interval); + saa7164_histogram_print(port, &port->poll_interval); + /* TODO: fix this to preserve any previous state */ + print_histogram = 64 + port->nr; + } +} + +static void saa7164_work_cmdhandler(struct work_struct *w) +{ + struct saa7164_dev *dev = container_of(w, struct saa7164_dev, workcmd); + + /* Wake up any complete commands */ + saa7164_irq_dequeue(dev); +} + +static void saa7164_buffer_deliver(struct saa7164_buffer *buf) +{ + struct saa7164_port *port = buf->port; + + /* Feed the transport payload into the kernel demux */ + dvb_dmx_swfilter_packets(&port->dvb.demux, (u8 *)buf->cpu, + SAA7164_TS_NUMBER_OF_LINES); + +} + +static irqreturn_t saa7164_irq_vbi(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + + /* Store old time */ + port->last_irq_msecs_diff = port->last_irq_msecs; + + /* Collect new stats */ + port->last_irq_msecs = jiffies_to_msecs(jiffies); + + /* Calculate stats */ + port->last_irq_msecs_diff = port->last_irq_msecs - + port->last_irq_msecs_diff; + + saa7164_histogram_update(&port->irq_interval, + port->last_irq_msecs_diff); + + dprintk(DBGLVL_IRQ, "%s() %Ldms elapsed\n", __func__, + port->last_irq_msecs_diff); + + /* Tis calls the vbi irq handler */ + schedule_work(&port->workenc); + return 0; +} + +static irqreturn_t saa7164_irq_encoder(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + + /* Store old time */ + port->last_irq_msecs_diff = port->last_irq_msecs; + + /* Collect new stats */ + port->last_irq_msecs = jiffies_to_msecs(jiffies); + + /* Calculate stats */ + port->last_irq_msecs_diff = port->last_irq_msecs - + port->last_irq_msecs_diff; + + saa7164_histogram_update(&port->irq_interval, + port->last_irq_msecs_diff); + + dprintk(DBGLVL_IRQ, "%s() %Ldms elapsed\n", __func__, + port->last_irq_msecs_diff); + + schedule_work(&port->workenc); + return 0; +} + +static irqreturn_t saa7164_irq_ts(struct saa7164_port *port) +{ + struct saa7164_dev *dev = port->dev; + struct saa7164_buffer *buf; + struct list_head *c, *n; + int wp, i = 0, rp; + + /* Find the current write point from the hardware */ + wp = saa7164_readl(port->bufcounter); + if (wp > (port->hwcfg.buffercount - 1)) + BUG(); + + /* Find the previous buffer to the current write point */ + if (wp == 0) + rp = (port->hwcfg.buffercount - 1); + else + rp = wp - 1; + + /* Lookup the WP in the buffer list */ + /* TODO: turn this into a worker thread */ + list_for_each_safe(c, n, &port->dmaqueue.list) { + buf = list_entry(c, struct saa7164_buffer, list); + if (i++ > port->hwcfg.buffercount) + BUG(); + + if (buf->idx == rp) { + /* Found the buffer, deal with it */ + dprintk(DBGLVL_IRQ, "%s() wp: %d processing: %d\n", + __func__, wp, rp); + saa7164_buffer_deliver(buf); + break; + } + + } + return 0; +} + +/* Primary IRQ handler and dispatch mechanism */ +static irqreturn_t saa7164_irq(int irq, void *dev_id) +{ + struct saa7164_dev *dev = dev_id; + struct saa7164_port *porta = &dev->ports[SAA7164_PORT_TS1]; + struct saa7164_port *portb = &dev->ports[SAA7164_PORT_TS2]; + struct saa7164_port *portc = &dev->ports[SAA7164_PORT_ENC1]; + struct saa7164_port *portd = &dev->ports[SAA7164_PORT_ENC2]; + struct saa7164_port *porte = &dev->ports[SAA7164_PORT_VBI1]; + struct saa7164_port *portf = &dev->ports[SAA7164_PORT_VBI2]; + + u32 intid, intstat[INT_SIZE/4]; + int i, handled = 0, bit; + + if (dev == NULL) { + printk(KERN_ERR "%s() No device specified\n", __func__); + handled = 0; + goto out; + } + + /* Check that the hardware is accessible. If the status bytes are + * 0xFF then the device is not accessible, the the IRQ belongs + * to another driver. + * 4 x u32 interrupt registers. + */ + for (i = 0; i < INT_SIZE/4; i++) { + + /* TODO: Convert into saa7164_readl() */ + /* Read the 4 hardware interrupt registers */ + intstat[i] = saa7164_readl(dev->int_status + (i * 4)); + + if (intstat[i]) + handled = 1; + } + if (handled == 0) + goto out; + + /* For each of the HW interrupt registers */ + for (i = 0; i < INT_SIZE/4; i++) { + + if (intstat[i]) { + /* Each function of the board has it's own interruptid. + * Find the function that triggered then call + * it's handler. + */ + for (bit = 0; bit < 32; bit++) { + + if (((intstat[i] >> bit) & 0x00000001) == 0) + continue; + + /* Calculate the interrupt id (0x00 to 0x7f) */ + + intid = (i * 32) + bit; + if (intid == dev->intfdesc.bInterruptId) { + /* A response to an cmd/api call */ + schedule_work(&dev->workcmd); + } else if (intid == porta->hwcfg.interruptid) { + + /* Transport path 1 */ + saa7164_irq_ts(porta); + + } else if (intid == portb->hwcfg.interruptid) { + + /* Transport path 2 */ + saa7164_irq_ts(portb); + + } else if (intid == portc->hwcfg.interruptid) { + + /* Encoder path 1 */ + saa7164_irq_encoder(portc); + + } else if (intid == portd->hwcfg.interruptid) { + + /* Encoder path 2 */ + saa7164_irq_encoder(portd); + + } else if (intid == porte->hwcfg.interruptid) { + + /* VBI path 1 */ + saa7164_irq_vbi(porte); + + } else if (intid == portf->hwcfg.interruptid) { + + /* VBI path 2 */ + saa7164_irq_vbi(portf); + + } else { + /* Find the function */ + dprintk(DBGLVL_IRQ, + "%s() unhandled interrupt " + "reg 0x%x bit 0x%x " + "intid = 0x%x\n", + __func__, i, bit, intid); + } + } + + /* Ack it */ + saa7164_writel(dev->int_ack + (i * 4), intstat[i]); + + } + } +out: + return IRQ_RETVAL(handled); +} + +void saa7164_getfirmwarestatus(struct saa7164_dev *dev) +{ + struct saa7164_fw_status *s = &dev->fw_status; + + dev->fw_status.status = saa7164_readl(SAA_DEVICE_SYSINIT_STATUS); + dev->fw_status.mode = saa7164_readl(SAA_DEVICE_SYSINIT_MODE); + dev->fw_status.spec = saa7164_readl(SAA_DEVICE_SYSINIT_SPEC); + dev->fw_status.inst = saa7164_readl(SAA_DEVICE_SYSINIT_INST); + dev->fw_status.cpuload = saa7164_readl(SAA_DEVICE_SYSINIT_CPULOAD); + dev->fw_status.remainheap = + saa7164_readl(SAA_DEVICE_SYSINIT_REMAINHEAP); + + dprintk(1, "Firmware status:\n"); + dprintk(1, " .status = 0x%08x\n", s->status); + dprintk(1, " .mode = 0x%08x\n", s->mode); + dprintk(1, " .spec = 0x%08x\n", s->spec); + dprintk(1, " .inst = 0x%08x\n", s->inst); + dprintk(1, " .cpuload = 0x%08x\n", s->cpuload); + dprintk(1, " .remainheap = 0x%08x\n", s->remainheap); +} + +u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev) +{ + u32 reg; + + reg = saa7164_readl(SAA_DEVICE_VERSION); + dprintk(1, "Device running firmware version %d.%d.%d.%d (0x%x)\n", + (reg & 0x0000fc00) >> 10, + (reg & 0x000003e0) >> 5, + (reg & 0x0000001f), + (reg & 0xffff0000) >> 16, + reg); + + return reg; +} + +/* TODO: Debugging func, remove */ +void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr) +{ + int i; + + dprintk(1, "--------------------> " + "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + + for (i = 0; i < 0x100; i += 16) + dprintk(1, "region0[0x%08x] = " + "%02x %02x %02x %02x %02x %02x %02x %02x" + " %02x %02x %02x %02x %02x %02x %02x %02x\n", i, + (u8)saa7164_readb(addr + i + 0), + (u8)saa7164_readb(addr + i + 1), + (u8)saa7164_readb(addr + i + 2), + (u8)saa7164_readb(addr + i + 3), + (u8)saa7164_readb(addr + i + 4), + (u8)saa7164_readb(addr + i + 5), + (u8)saa7164_readb(addr + i + 6), + (u8)saa7164_readb(addr + i + 7), + (u8)saa7164_readb(addr + i + 8), + (u8)saa7164_readb(addr + i + 9), + (u8)saa7164_readb(addr + i + 10), + (u8)saa7164_readb(addr + i + 11), + (u8)saa7164_readb(addr + i + 12), + (u8)saa7164_readb(addr + i + 13), + (u8)saa7164_readb(addr + i + 14), + (u8)saa7164_readb(addr + i + 15) + ); +} + +static void saa7164_dump_hwdesc(struct saa7164_dev *dev) +{ + dprintk(1, "@0x%p hwdesc sizeof(struct tmComResHWDescr) = %d bytes\n", + &dev->hwdesc, (u32)sizeof(struct tmComResHWDescr)); + + dprintk(1, " .bLength = 0x%x\n", dev->hwdesc.bLength); + dprintk(1, " .bDescriptorType = 0x%x\n", dev->hwdesc.bDescriptorType); + dprintk(1, " .bDescriptorSubtype = 0x%x\n", + dev->hwdesc.bDescriptorSubtype); + + dprintk(1, " .bcdSpecVersion = 0x%x\n", dev->hwdesc.bcdSpecVersion); + dprintk(1, " .dwClockFrequency = 0x%x\n", dev->hwdesc.dwClockFrequency); + dprintk(1, " .dwClockUpdateRes = 0x%x\n", dev->hwdesc.dwClockUpdateRes); + dprintk(1, " .bCapabilities = 0x%x\n", dev->hwdesc.bCapabilities); + dprintk(1, " .dwDeviceRegistersLocation = 0x%x\n", + dev->hwdesc.dwDeviceRegistersLocation); + + dprintk(1, " .dwHostMemoryRegion = 0x%x\n", + dev->hwdesc.dwHostMemoryRegion); + + dprintk(1, " .dwHostMemoryRegionSize = 0x%x\n", + dev->hwdesc.dwHostMemoryRegionSize); + + dprintk(1, " .dwHostHibernatMemRegion = 0x%x\n", + dev->hwdesc.dwHostHibernatMemRegion); + + dprintk(1, " .dwHostHibernatMemRegionSize = 0x%x\n", + dev->hwdesc.dwHostHibernatMemRegionSize); +} + +static void saa7164_dump_intfdesc(struct saa7164_dev *dev) +{ + dprintk(1, "@0x%p intfdesc " + "sizeof(struct tmComResInterfaceDescr) = %d bytes\n", + &dev->intfdesc, (u32)sizeof(struct tmComResInterfaceDescr)); + + dprintk(1, " .bLength = 0x%x\n", dev->intfdesc.bLength); + dprintk(1, " .bDescriptorType = 0x%x\n", dev->intfdesc.bDescriptorType); + dprintk(1, " .bDescriptorSubtype = 0x%x\n", + dev->intfdesc.bDescriptorSubtype); + + dprintk(1, " .bFlags = 0x%x\n", dev->intfdesc.bFlags); + dprintk(1, " .bInterfaceType = 0x%x\n", dev->intfdesc.bInterfaceType); + dprintk(1, " .bInterfaceId = 0x%x\n", dev->intfdesc.bInterfaceId); + dprintk(1, " .bBaseInterface = 0x%x\n", dev->intfdesc.bBaseInterface); + dprintk(1, " .bInterruptId = 0x%x\n", dev->intfdesc.bInterruptId); + dprintk(1, " .bDebugInterruptId = 0x%x\n", + dev->intfdesc.bDebugInterruptId); + + dprintk(1, " .BARLocation = 0x%x\n", dev->intfdesc.BARLocation); +} + +static void saa7164_dump_busdesc(struct saa7164_dev *dev) +{ + dprintk(1, "@0x%p busdesc sizeof(struct tmComResBusDescr) = %d bytes\n", + &dev->busdesc, (u32)sizeof(struct tmComResBusDescr)); + + dprintk(1, " .CommandRing = 0x%016Lx\n", dev->busdesc.CommandRing); + dprintk(1, " .ResponseRing = 0x%016Lx\n", dev->busdesc.ResponseRing); + dprintk(1, " .CommandWrite = 0x%x\n", dev->busdesc.CommandWrite); + dprintk(1, " .CommandRead = 0x%x\n", dev->busdesc.CommandRead); + dprintk(1, " .ResponseWrite = 0x%x\n", dev->busdesc.ResponseWrite); + dprintk(1, " .ResponseRead = 0x%x\n", dev->busdesc.ResponseRead); +} + +/* Much of the hardware configuration and PCI registers are configured + * dynamically depending on firmware. We have to cache some initial + * structures then use these to locate other important structures + * from PCI space. + */ +static void saa7164_get_descriptors(struct saa7164_dev *dev) +{ + memcpy_fromio(&dev->hwdesc, dev->bmmio, sizeof(struct tmComResHWDescr)); + memcpy_fromio(&dev->intfdesc, dev->bmmio + sizeof(struct tmComResHWDescr), + sizeof(struct tmComResInterfaceDescr)); + memcpy_fromio(&dev->busdesc, dev->bmmio + dev->intfdesc.BARLocation, + sizeof(struct tmComResBusDescr)); + + if (dev->hwdesc.bLength != sizeof(struct tmComResHWDescr)) { + printk(KERN_ERR "Structure struct tmComResHWDescr is mangled\n"); + printk(KERN_ERR "Need %x got %d\n", dev->hwdesc.bLength, + (u32)sizeof(struct tmComResHWDescr)); + } else + saa7164_dump_hwdesc(dev); + + if (dev->intfdesc.bLength != sizeof(struct tmComResInterfaceDescr)) { + printk(KERN_ERR "struct struct tmComResInterfaceDescr is mangled\n"); + printk(KERN_ERR "Need %x got %d\n", dev->intfdesc.bLength, + (u32)sizeof(struct tmComResInterfaceDescr)); + } else + saa7164_dump_intfdesc(dev); + + saa7164_dump_busdesc(dev); +} + +static int saa7164_pci_quirks(struct saa7164_dev *dev) +{ + return 0; +} + +static int get_resources(struct saa7164_dev *dev) +{ + if (request_mem_region(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0), dev->name)) { + + if (request_mem_region(pci_resource_start(dev->pci, 2), + pci_resource_len(dev->pci, 2), dev->name)) + return 0; + } + + printk(KERN_ERR "%s: can't get MMIO memory @ 0x%llx or 0x%llx\n", + dev->name, + (u64)pci_resource_start(dev->pci, 0), + (u64)pci_resource_start(dev->pci, 2)); + + return -EBUSY; +} + +static int saa7164_port_init(struct saa7164_dev *dev, int portnr) +{ + struct saa7164_port *port = NULL; + + if ((portnr < 0) || (portnr >= SAA7164_MAX_PORTS)) + BUG(); + + port = &dev->ports[portnr]; + + port->dev = dev; + port->nr = portnr; + + if ((portnr == SAA7164_PORT_TS1) || (portnr == SAA7164_PORT_TS2)) + port->type = SAA7164_MPEG_DVB; + else + if ((portnr == SAA7164_PORT_ENC1) || (portnr == SAA7164_PORT_ENC2)) { + port->type = SAA7164_MPEG_ENCODER; + + /* We need a deferred interrupt handler for cmd handling */ + INIT_WORK(&port->workenc, saa7164_work_enchandler); + } else if ((portnr == SAA7164_PORT_VBI1) || (portnr == SAA7164_PORT_VBI2)) { + port->type = SAA7164_MPEG_VBI; + + /* We need a deferred interrupt handler for cmd handling */ + INIT_WORK(&port->workenc, saa7164_work_vbihandler); + } else + BUG(); + + /* Init all the critical resources */ + mutex_init(&port->dvb.lock); + INIT_LIST_HEAD(&port->dmaqueue.list); + mutex_init(&port->dmaqueue_lock); + + INIT_LIST_HEAD(&port->list_buf_used.list); + INIT_LIST_HEAD(&port->list_buf_free.list); + init_waitqueue_head(&port->wait_read); + + + saa7164_histogram_reset(&port->irq_interval, "irq intervals"); + saa7164_histogram_reset(&port->svc_interval, "deferred intervals"); + saa7164_histogram_reset(&port->irq_svc_interval, + "irq to deferred intervals"); + saa7164_histogram_reset(&port->read_interval, + "encoder/vbi read() intervals"); + saa7164_histogram_reset(&port->poll_interval, + "encoder/vbi poll() intervals"); + + return 0; +} + +static int saa7164_dev_setup(struct saa7164_dev *dev) +{ + int i; + + mutex_init(&dev->lock); + atomic_inc(&dev->refcount); + dev->nr = saa7164_devcount++; + + snprintf(dev->name, sizeof(dev->name), "saa7164[%d]", dev->nr); + + mutex_lock(&devlist); + list_add_tail(&dev->devlist, &saa7164_devlist); + mutex_unlock(&devlist); + + /* board config */ + dev->board = UNSET; + if (card[dev->nr] < saa7164_bcount) + dev->board = card[dev->nr]; + + for (i = 0; UNSET == dev->board && i < saa7164_idcount; i++) + if (dev->pci->subsystem_vendor == saa7164_subids[i].subvendor && + dev->pci->subsystem_device == + saa7164_subids[i].subdevice) + dev->board = saa7164_subids[i].card; + + if (UNSET == dev->board) { + dev->board = SAA7164_BOARD_UNKNOWN; + saa7164_card_list(dev); + } + + dev->pci_bus = dev->pci->bus->number; + dev->pci_slot = PCI_SLOT(dev->pci->devfn); + + /* I2C Defaults / setup */ + dev->i2c_bus[0].dev = dev; + dev->i2c_bus[0].nr = 0; + dev->i2c_bus[1].dev = dev; + dev->i2c_bus[1].nr = 1; + dev->i2c_bus[2].dev = dev; + dev->i2c_bus[2].nr = 2; + + /* Transport + Encoder ports 1, 2, 3, 4 - Defaults / setup */ + saa7164_port_init(dev, SAA7164_PORT_TS1); + saa7164_port_init(dev, SAA7164_PORT_TS2); + saa7164_port_init(dev, SAA7164_PORT_ENC1); + saa7164_port_init(dev, SAA7164_PORT_ENC2); + saa7164_port_init(dev, SAA7164_PORT_VBI1); + saa7164_port_init(dev, SAA7164_PORT_VBI2); + + if (get_resources(dev) < 0) { + printk(KERN_ERR "CORE %s No more PCIe resources for " + "subsystem: %04x:%04x\n", + dev->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device); + + saa7164_devcount--; + return -ENODEV; + } + + /* PCI/e allocations */ + dev->lmmio = ioremap(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0)); + + dev->lmmio2 = ioremap(pci_resource_start(dev->pci, 2), + pci_resource_len(dev->pci, 2)); + + dev->bmmio = (u8 __iomem *)dev->lmmio; + dev->bmmio2 = (u8 __iomem *)dev->lmmio2; + + /* Inerrupt and ack register locations offset of bmmio */ + dev->int_status = 0x183000 + 0xf80; + dev->int_ack = 0x183000 + 0xf90; + + printk(KERN_INFO + "CORE %s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + dev->name, dev->pci->subsystem_vendor, + dev->pci->subsystem_device, saa7164_boards[dev->board].name, + dev->board, card[dev->nr] == dev->board ? + "insmod option" : "autodetected"); + + saa7164_pci_quirks(dev); + + return 0; +} + +static void saa7164_dev_unregister(struct saa7164_dev *dev) +{ + dprintk(1, "%s()\n", __func__); + + release_mem_region(pci_resource_start(dev->pci, 0), + pci_resource_len(dev->pci, 0)); + + release_mem_region(pci_resource_start(dev->pci, 2), + pci_resource_len(dev->pci, 2)); + + if (!atomic_dec_and_test(&dev->refcount)) + return; + + iounmap(dev->lmmio); + iounmap(dev->lmmio2); + + return; +} + +#ifdef CONFIG_PROC_FS +static int saa7164_proc_show(struct seq_file *m, void *v) +{ + struct saa7164_dev *dev; + struct tmComResBusInfo *b; + struct list_head *list; + int i, c; + + if (saa7164_devcount == 0) + return 0; + + list_for_each(list, &saa7164_devlist) { + dev = list_entry(list, struct saa7164_dev, devlist); + seq_printf(m, "%s = %p\n", dev->name, dev); + + /* Lock the bus from any other access */ + b = &dev->bus; + mutex_lock(&b->lock); + + seq_printf(m, " .m_pdwSetWritePos = 0x%x (0x%08x)\n", + b->m_dwSetReadPos, saa7164_readl(b->m_dwSetReadPos)); + + seq_printf(m, " .m_pdwSetReadPos = 0x%x (0x%08x)\n", + b->m_dwSetWritePos, saa7164_readl(b->m_dwSetWritePos)); + + seq_printf(m, " .m_pdwGetWritePos = 0x%x (0x%08x)\n", + b->m_dwGetReadPos, saa7164_readl(b->m_dwGetReadPos)); + + seq_printf(m, " .m_pdwGetReadPos = 0x%x (0x%08x)\n", + b->m_dwGetWritePos, saa7164_readl(b->m_dwGetWritePos)); + c = 0; + seq_printf(m, "\n Set Ring:\n"); + seq_printf(m, "\n addr 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + for (i = 0; i < b->m_dwSizeSetRing; i++) { + if (c == 0) + seq_printf(m, " %04x:", i); + + seq_printf(m, " %02x", *(b->m_pdwSetRing + i)); + + if (++c == 16) { + seq_printf(m, "\n"); + c = 0; + } + } + + c = 0; + seq_printf(m, "\n Get Ring:\n"); + seq_printf(m, "\n addr 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + for (i = 0; i < b->m_dwSizeGetRing; i++) { + if (c == 0) + seq_printf(m, " %04x:", i); + + seq_printf(m, " %02x", *(b->m_pdwGetRing + i)); + + if (++c == 16) { + seq_printf(m, "\n"); + c = 0; + } + } + + mutex_unlock(&b->lock); + + } + + return 0; +} + +static int saa7164_proc_open(struct inode *inode, struct file *filp) +{ + return single_open(filp, saa7164_proc_show, NULL); +} + +static const struct file_operations saa7164_proc_fops = { + .open = saa7164_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int saa7164_proc_create(void) +{ + struct proc_dir_entry *pe; + + pe = proc_create("saa7164", S_IRUGO, NULL, &saa7164_proc_fops); + if (!pe) + return -ENOMEM; + + return 0; +} +#endif + +static int saa7164_thread_function(void *data) +{ + struct saa7164_dev *dev = data; + struct tmFwInfoStruct fwinfo; + u64 last_poll_time = 0; + + dprintk(DBGLVL_THR, "thread started\n"); + + set_freezable(); + + while (1) { + msleep_interruptible(100); + if (kthread_should_stop()) + break; + try_to_freeze(); + + dprintk(DBGLVL_THR, "thread running\n"); + + /* Dump the firmware debug message to console */ + /* Polling this costs us 1-2% of the arm CPU */ + /* convert this into a respnde to interrupt 0x7a */ + saa7164_api_collect_debug(dev); + + /* Monitor CPU load every 1 second */ + if ((last_poll_time + 1000 /* ms */) < jiffies_to_msecs(jiffies)) { + saa7164_api_get_load_info(dev, &fwinfo); + last_poll_time = jiffies_to_msecs(jiffies); + } + + } + + dprintk(DBGLVL_THR, "thread exiting\n"); + return 0; +} + +static int __devinit saa7164_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct saa7164_dev *dev; + int err, i; + u32 version; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (NULL == dev) + return -ENOMEM; + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail_free; + } + + if (saa7164_dev_setup(dev) < 0) { + err = -EINVAL; + goto fail_free; + } + + /* print pci info */ + dev->pci_rev = pci_dev->revision; + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s/0: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%llx\n", dev->name, + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat, + (unsigned long long)pci_resource_start(pci_dev, 0)); + + pci_set_master(pci_dev); + /* TODO */ + if (!pci_dma_supported(pci_dev, 0xffffffff)) { + printk("%s/0: Oops: no 32bit PCI DMA ???\n", dev->name); + err = -EIO; + goto fail_irq; + } + + err = request_irq(pci_dev->irq, saa7164_irq, + IRQF_SHARED | IRQF_DISABLED, dev->name, dev); + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", dev->name, + pci_dev->irq); + err = -EIO; + goto fail_irq; + } + + pci_set_drvdata(pci_dev, dev); + + /* Init the internal command list */ + for (i = 0; i < SAA_CMD_MAX_MSG_UNITS; i++) { + dev->cmds[i].seqno = i; + dev->cmds[i].inuse = 0; + mutex_init(&dev->cmds[i].lock); + init_waitqueue_head(&dev->cmds[i].wait); + } + + /* We need a deferred interrupt handler for cmd handling */ + INIT_WORK(&dev->workcmd, saa7164_work_cmdhandler); + + /* Only load the firmware if we know the board */ + if (dev->board != SAA7164_BOARD_UNKNOWN) { + + err = saa7164_downloadfirmware(dev); + if (err < 0) { + printk(KERN_ERR + "Failed to boot firmware, no features " + "registered\n"); + goto fail_fw; + } + + saa7164_get_descriptors(dev); + saa7164_dumpregs(dev, 0); + saa7164_getcurrentfirmwareversion(dev); + saa7164_getfirmwarestatus(dev); + err = saa7164_bus_setup(dev); + if (err < 0) + printk(KERN_ERR + "Failed to setup the bus, will continue\n"); + saa7164_bus_dump(dev); + + /* Ping the running firmware via the command bus and get the + * firmware version, this checks the bus is running OK. + */ + version = 0; + if (saa7164_api_get_fw_version(dev, &version) == SAA_OK) + dprintk(1, "Bus is operating correctly using " + "version %d.%d.%d.%d (0x%x)\n", + (version & 0x0000fc00) >> 10, + (version & 0x000003e0) >> 5, + (version & 0x0000001f), + (version & 0xffff0000) >> 16, + version); + else + printk(KERN_ERR + "Failed to communicate with the firmware\n"); + + /* Bring up the I2C buses */ + saa7164_i2c_register(&dev->i2c_bus[0]); + saa7164_i2c_register(&dev->i2c_bus[1]); + saa7164_i2c_register(&dev->i2c_bus[2]); + saa7164_gpio_setup(dev); + saa7164_card_setup(dev); + + /* Parse the dynamic device configuration, find various + * media endpoints (MPEG, WMV, PS, TS) and cache their + * configuration details into the driver, so we can + * reference them later during simething_register() func, + * interrupt handlers, deferred work handlers etc. + */ + saa7164_api_enum_subdevs(dev); + + /* Begin to create the video sub-systems and register funcs */ + if (saa7164_boards[dev->board].porta == SAA7164_MPEG_DVB) { + if (saa7164_dvb_register(&dev->ports[SAA7164_PORT_TS1]) < 0) { + printk(KERN_ERR "%s() Failed to register " + "dvb adapters on porta\n", + __func__); + } + } + + if (saa7164_boards[dev->board].portb == SAA7164_MPEG_DVB) { + if (saa7164_dvb_register(&dev->ports[SAA7164_PORT_TS2]) < 0) { + printk(KERN_ERR"%s() Failed to register " + "dvb adapters on portb\n", + __func__); + } + } + + if (saa7164_boards[dev->board].portc == SAA7164_MPEG_ENCODER) { + if (saa7164_encoder_register(&dev->ports[SAA7164_PORT_ENC1]) < 0) { + printk(KERN_ERR"%s() Failed to register " + "mpeg encoder\n", __func__); + } + } + + if (saa7164_boards[dev->board].portd == SAA7164_MPEG_ENCODER) { + if (saa7164_encoder_register(&dev->ports[SAA7164_PORT_ENC2]) < 0) { + printk(KERN_ERR"%s() Failed to register " + "mpeg encoder\n", __func__); + } + } + + if (saa7164_boards[dev->board].porte == SAA7164_MPEG_VBI) { + if (saa7164_vbi_register(&dev->ports[SAA7164_PORT_VBI1]) < 0) { + printk(KERN_ERR"%s() Failed to register " + "vbi device\n", __func__); + } + } + + if (saa7164_boards[dev->board].portf == SAA7164_MPEG_VBI) { + if (saa7164_vbi_register(&dev->ports[SAA7164_PORT_VBI2]) < 0) { + printk(KERN_ERR"%s() Failed to register " + "vbi device\n", __func__); + } + } + saa7164_api_set_debug(dev, fw_debug); + + if (fw_debug) { + dev->kthread = kthread_run(saa7164_thread_function, dev, + "saa7164 debug"); + if (!dev->kthread) + printk(KERN_ERR "%s() Failed to create " + "debug kernel thread\n", __func__); + } + + } /* != BOARD_UNKNOWN */ + else + printk(KERN_ERR "%s() Unsupported board detected, " + "registering without firmware\n", __func__); + + dprintk(1, "%s() parameter debug = %d\n", __func__, saa_debug); + dprintk(1, "%s() parameter waitsecs = %d\n", __func__, waitsecs); + +fail_fw: + return 0; + +fail_irq: + saa7164_dev_unregister(dev); +fail_free: + kfree(dev); + return err; +} + +static void saa7164_shutdown(struct saa7164_dev *dev) +{ + dprintk(1, "%s()\n", __func__); +} + +static void __devexit saa7164_finidev(struct pci_dev *pci_dev) +{ + struct saa7164_dev *dev = pci_get_drvdata(pci_dev); + + if (dev->board != SAA7164_BOARD_UNKNOWN) { + if (fw_debug && dev->kthread) { + kthread_stop(dev->kthread); + dev->kthread = NULL; + } + if (dev->firmwareloaded) + saa7164_api_set_debug(dev, 0x00); + } + + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].irq_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].svc_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].irq_svc_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].read_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_ENC1], + &dev->ports[SAA7164_PORT_ENC1].poll_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_VBI1], + &dev->ports[SAA7164_PORT_VBI1].read_interval); + saa7164_histogram_print(&dev->ports[SAA7164_PORT_VBI2], + &dev->ports[SAA7164_PORT_VBI2].poll_interval); + + saa7164_shutdown(dev); + + if (saa7164_boards[dev->board].porta == SAA7164_MPEG_DVB) + saa7164_dvb_unregister(&dev->ports[SAA7164_PORT_TS1]); + + if (saa7164_boards[dev->board].portb == SAA7164_MPEG_DVB) + saa7164_dvb_unregister(&dev->ports[SAA7164_PORT_TS2]); + + if (saa7164_boards[dev->board].portc == SAA7164_MPEG_ENCODER) + saa7164_encoder_unregister(&dev->ports[SAA7164_PORT_ENC1]); + + if (saa7164_boards[dev->board].portd == SAA7164_MPEG_ENCODER) + saa7164_encoder_unregister(&dev->ports[SAA7164_PORT_ENC2]); + + if (saa7164_boards[dev->board].porte == SAA7164_MPEG_VBI) + saa7164_vbi_unregister(&dev->ports[SAA7164_PORT_VBI1]); + + if (saa7164_boards[dev->board].portf == SAA7164_MPEG_VBI) + saa7164_vbi_unregister(&dev->ports[SAA7164_PORT_VBI2]); + + saa7164_i2c_unregister(&dev->i2c_bus[0]); + saa7164_i2c_unregister(&dev->i2c_bus[1]); + saa7164_i2c_unregister(&dev->i2c_bus[2]); + + pci_disable_device(pci_dev); + + /* unregister stuff */ + free_irq(pci_dev->irq, dev); + pci_set_drvdata(pci_dev, NULL); + + mutex_lock(&devlist); + list_del(&dev->devlist); + mutex_unlock(&devlist); + + saa7164_dev_unregister(dev); + kfree(dev); +} + +static struct pci_device_id saa7164_pci_tbl[] = { + { + /* SAA7164 */ + .vendor = 0x1131, + .device = 0x7164, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, { + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, saa7164_pci_tbl); + +static struct pci_driver saa7164_pci_driver = { + .name = "saa7164", + .id_table = saa7164_pci_tbl, + .probe = saa7164_initdev, + .remove = __devexit_p(saa7164_finidev), + /* TODO */ + .suspend = NULL, + .resume = NULL, +}; + +static int __init saa7164_init(void) +{ + printk(KERN_INFO "saa7164 driver loaded\n"); + +#ifdef CONFIG_PROC_FS + saa7164_proc_create(); +#endif + return pci_register_driver(&saa7164_pci_driver); +} + +static void __exit saa7164_fini(void) +{ +#ifdef CONFIG_PROC_FS + remove_proc_entry("saa7164", NULL); +#endif + pci_unregister_driver(&saa7164_pci_driver); +} + +module_init(saa7164_init); +module_exit(saa7164_fini); + diff --git a/drivers/media/video/saa7164/saa7164-dvb.c b/drivers/media/pci/saa7164/saa7164-dvb.c similarity index 100% rename from drivers/media/video/saa7164/saa7164-dvb.c rename to drivers/media/pci/saa7164/saa7164-dvb.c diff --git a/drivers/media/video/saa7164/saa7164-encoder.c b/drivers/media/pci/saa7164/saa7164-encoder.c similarity index 100% rename from drivers/media/video/saa7164/saa7164-encoder.c rename to drivers/media/pci/saa7164/saa7164-encoder.c diff --git a/drivers/media/video/saa7164/saa7164-fw.c b/drivers/media/pci/saa7164/saa7164-fw.c similarity index 100% rename from drivers/media/video/saa7164/saa7164-fw.c rename to drivers/media/pci/saa7164/saa7164-fw.c diff --git a/drivers/media/video/saa7164/saa7164-i2c.c b/drivers/media/pci/saa7164/saa7164-i2c.c similarity index 100% rename from drivers/media/video/saa7164/saa7164-i2c.c rename to drivers/media/pci/saa7164/saa7164-i2c.c diff --git a/drivers/media/video/saa7164/saa7164-reg.h b/drivers/media/pci/saa7164/saa7164-reg.h similarity index 100% rename from drivers/media/video/saa7164/saa7164-reg.h rename to drivers/media/pci/saa7164/saa7164-reg.h diff --git a/drivers/media/video/saa7164/saa7164-types.h b/drivers/media/pci/saa7164/saa7164-types.h similarity index 100% rename from drivers/media/video/saa7164/saa7164-types.h rename to drivers/media/pci/saa7164/saa7164-types.h diff --git a/drivers/media/video/saa7164/saa7164-vbi.c b/drivers/media/pci/saa7164/saa7164-vbi.c similarity index 100% rename from drivers/media/video/saa7164/saa7164-vbi.c rename to drivers/media/pci/saa7164/saa7164-vbi.c diff --git a/drivers/media/pci/saa7164/saa7164.h b/drivers/media/pci/saa7164/saa7164.h new file mode 100644 index 000000000000..437284e747c9 --- /dev/null +++ b/drivers/media/pci/saa7164/saa7164.h @@ -0,0 +1,616 @@ +/* + * Driver for the NXP SAA7164 PCIe bridge + * + * Copyright (c) 2010 Steven Toth + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + Driver architecture + ******************* + + saa7164_core.c/buffer.c/cards.c/i2c.c/dvb.c + | : Standard Linux driver framework for creating + | : exposing and managing interfaces to the rest + | : of the kernel or userland. Also uses _fw.c to load + | : firmware direct into the PCIe bus, bypassing layers. + V + saa7164_api..() : Translate kernel specific functions/features + | : into command buffers. + V + saa7164_cmd..() : Manages the flow of command packets on/off, + | : the bus. Deal with bus errors, timeouts etc. + V + saa7164_bus..() : Manage a read/write memory ring buffer in the + | : PCIe Address space. + | + | saa7164_fw...() : Load any frimware + | | : direct into the device + V V + <- ----------------- PCIe address space -------------------- -> +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "saa7164-reg.h" +#include "saa7164-types.h" + +#define SAA7164_MAXBOARDS 8 + +#define UNSET (-1U) +#define SAA7164_BOARD_NOAUTO UNSET +#define SAA7164_BOARD_UNKNOWN 0 +#define SAA7164_BOARD_UNKNOWN_REV2 1 +#define SAA7164_BOARD_UNKNOWN_REV3 2 +#define SAA7164_BOARD_HAUPPAUGE_HVR2250 3 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200 4 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200_2 5 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200_3 6 +#define SAA7164_BOARD_HAUPPAUGE_HVR2250_2 7 +#define SAA7164_BOARD_HAUPPAUGE_HVR2250_3 8 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200_4 9 +#define SAA7164_BOARD_HAUPPAUGE_HVR2200_5 10 + +#define SAA7164_MAX_UNITS 8 +#define SAA7164_TS_NUMBER_OF_LINES 312 +#define SAA7164_PS_NUMBER_OF_LINES 256 +#define SAA7164_PT_ENTRIES 16 /* (312 * 188) / 4096 */ +#define SAA7164_MAX_ENCODER_BUFFERS 64 /* max 5secs of latency at 6Mbps */ +#define SAA7164_MAX_VBI_BUFFERS 64 + +/* Port related defines */ +#define SAA7164_PORT_TS1 (0) +#define SAA7164_PORT_TS2 (SAA7164_PORT_TS1 + 1) +#define SAA7164_PORT_ENC1 (SAA7164_PORT_TS2 + 1) +#define SAA7164_PORT_ENC2 (SAA7164_PORT_ENC1 + 1) +#define SAA7164_PORT_VBI1 (SAA7164_PORT_ENC2 + 1) +#define SAA7164_PORT_VBI2 (SAA7164_PORT_VBI1 + 1) +#define SAA7164_MAX_PORTS (SAA7164_PORT_VBI2 + 1) + +#define DBGLVL_FW 4 +#define DBGLVL_DVB 8 +#define DBGLVL_I2C 16 +#define DBGLVL_API 32 +#define DBGLVL_CMD 64 +#define DBGLVL_BUS 128 +#define DBGLVL_IRQ 256 +#define DBGLVL_BUF 512 +#define DBGLVL_ENC 1024 +#define DBGLVL_VBI 2048 +#define DBGLVL_THR 4096 +#define DBGLVL_CPU 8192 + +#define SAA7164_NORMS \ + (V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_443) + +enum port_t { + SAA7164_MPEG_UNDEFINED = 0, + SAA7164_MPEG_DVB, + SAA7164_MPEG_ENCODER, + SAA7164_MPEG_VBI, +}; + +enum saa7164_i2c_bus_nr { + SAA7164_I2C_BUS_0 = 0, + SAA7164_I2C_BUS_1, + SAA7164_I2C_BUS_2, +}; + +enum saa7164_buffer_flags { + SAA7164_BUFFER_UNDEFINED = 0, + SAA7164_BUFFER_FREE, + SAA7164_BUFFER_BUSY, + SAA7164_BUFFER_FULL +}; + +enum saa7164_unit_type { + SAA7164_UNIT_UNDEFINED = 0, + SAA7164_UNIT_DIGITAL_DEMODULATOR, + SAA7164_UNIT_ANALOG_DEMODULATOR, + SAA7164_UNIT_TUNER, + SAA7164_UNIT_EEPROM, + SAA7164_UNIT_ZILOG_IRBLASTER, + SAA7164_UNIT_ENCODER, +}; + +/* The PCIe bridge doesn't grant direct access to i2c. + * Instead, you address i2c devices using a uniqely + * allocated 'unitid' value via a messaging API. This + * is a problem. The kernel and existing demod/tuner + * drivers expect to talk 'i2c', so we have to maintain + * a translation layer, and a series of functions to + * convert i2c bus + device address into a unit id. + */ +struct saa7164_unit { + enum saa7164_unit_type type; + u8 id; + char *name; + enum saa7164_i2c_bus_nr i2c_bus_nr; + u8 i2c_bus_addr; + u8 i2c_reg_len; +}; + +struct saa7164_board { + char *name; + enum port_t porta, portb, portc, + portd, porte, portf; + enum { + SAA7164_CHIP_UNDEFINED = 0, + SAA7164_CHIP_REV2, + SAA7164_CHIP_REV3, + } chiprev; + struct saa7164_unit unit[SAA7164_MAX_UNITS]; +}; + +struct saa7164_subid { + u16 subvendor; + u16 subdevice; + u32 card; +}; + +struct saa7164_encoder_fh { + struct saa7164_port *port; + atomic_t v4l_reading; +}; + +struct saa7164_vbi_fh { + struct saa7164_port *port; + atomic_t v4l_reading; +}; + +struct saa7164_histogram_bucket { + u32 val; + u32 count; + u64 update_time; +}; + +struct saa7164_histogram { + char name[32]; + struct saa7164_histogram_bucket counter1[64]; +}; + +struct saa7164_user_buffer { + struct list_head list; + + /* Attributes */ + u8 *data; + u32 pos; + u32 actual_size; + + u32 crc; +}; + +struct saa7164_fw_status { + + /* RISC Core details */ + u32 status; + u32 mode; + u32 spec; + u32 inst; + u32 cpuload; + u32 remainheap; + + /* Firmware version */ + u32 version; + u32 major; + u32 sub; + u32 rel; + u32 buildnr; +}; + +struct saa7164_dvb { + struct mutex lock; + struct dvb_adapter adapter; + struct dvb_frontend *frontend; + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dmx_frontend fe_hw; + struct dmx_frontend fe_mem; + struct dvb_net net; + int feeding; +}; + +struct saa7164_i2c { + struct saa7164_dev *dev; + + enum saa7164_i2c_bus_nr nr; + + /* I2C I/O */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + u32 i2c_rc; +}; + +struct saa7164_ctrl { + struct v4l2_queryctrl v; +}; + +struct saa7164_tvnorm { + char *name; + v4l2_std_id id; +}; + +struct saa7164_encoder_params { + struct saa7164_tvnorm encodernorm; + u32 height; + u32 width; + u32 is_50hz; + u32 bitrate; /* bps */ + u32 bitrate_peak; /* bps */ + u32 bitrate_mode; + u32 stream_type; /* V4L2_MPEG_STREAM_TYPE_MPEG2_TS */ + + u32 audio_sampling_freq; + u32 ctl_mute; + u32 ctl_aspect; + u32 refdist; + u32 gop_size; +}; + +struct saa7164_vbi_params { + struct saa7164_tvnorm encodernorm; + u32 height; + u32 width; + u32 is_50hz; + u32 bitrate; /* bps */ + u32 bitrate_peak; /* bps */ + u32 bitrate_mode; + u32 stream_type; /* V4L2_MPEG_STREAM_TYPE_MPEG2_TS */ + + u32 audio_sampling_freq; + u32 ctl_mute; + u32 ctl_aspect; + u32 refdist; + u32 gop_size; +}; + +struct saa7164_port; + +struct saa7164_buffer { + struct list_head list; + + /* Note of which h/w buffer list index position we occupy */ + int idx; + + struct saa7164_port *port; + + /* Hardware Specific */ + /* PCI Memory allocations */ + enum saa7164_buffer_flags flags; /* Free, Busy, Full */ + + /* A block of page align PCI memory */ + u32 pci_size; /* PCI allocation size in bytes */ + u64 __iomem *cpu; /* Virtual address */ + dma_addr_t dma; /* Physical address */ + u32 crc; /* Checksum for the entire buffer data */ + + /* A page table that splits the block into a number of entries */ + u32 pt_size; /* PCI allocation size in bytes */ + u64 __iomem *pt_cpu; /* Virtual address */ + dma_addr_t pt_dma; /* Physical address */ + + /* Encoder fops */ + u32 pos; + u32 actual_size; +}; + +struct saa7164_port { + + struct saa7164_dev *dev; + enum port_t type; + int nr; + + /* --- Generic port attributes --- */ + + /* HW stream parameters */ + struct tmHWStreamParameters hw_streamingparams; + + /* DMA configuration values, is seeded during initialization */ + struct tmComResDMATermDescrHeader hwcfg; + + /* hardware specific registers */ + u32 bufcounter; + u32 pitch; + u32 bufsize; + u32 bufoffset; + u32 bufptr32l; + u32 bufptr32h; + u64 bufptr64; + + u32 numpte; /* Number of entries in array, only valid in head */ + + struct mutex dmaqueue_lock; + struct saa7164_buffer dmaqueue; + + u64 last_irq_msecs, last_svc_msecs; + u64 last_irq_msecs_diff, last_svc_msecs_diff; + u32 last_svc_wp; + u32 last_svc_rp; + u64 last_irq_svc_msecs_diff; + u64 last_read_msecs, last_read_msecs_diff; + u64 last_poll_msecs, last_poll_msecs_diff; + + struct saa7164_histogram irq_interval; + struct saa7164_histogram svc_interval; + struct saa7164_histogram irq_svc_interval; + struct saa7164_histogram read_interval; + struct saa7164_histogram poll_interval; + + /* --- DVB Transport Specific --- */ + struct saa7164_dvb dvb; + + /* --- Encoder/V4L related attributes --- */ + /* Encoder */ + /* Defaults established in saa7164-encoder.c */ + struct saa7164_tvnorm encodernorm; + u32 height; + u32 width; + u32 freq; + u32 ts_packet_size; + u32 ts_packet_count; + u8 mux_input; + u8 encoder_profile; + u8 video_format; + u8 audio_format; + u8 video_resolution; + u16 ctl_brightness; + u16 ctl_contrast; + u16 ctl_hue; + u16 ctl_saturation; + u16 ctl_sharpness; + s8 ctl_volume; + + struct tmComResAFeatureDescrHeader audfeat; + struct tmComResEncoderDescrHeader encunit; + struct tmComResProcDescrHeader vidproc; + struct tmComResExtDevDescrHeader ifunit; + struct tmComResTunerDescrHeader tunerunit; + + struct work_struct workenc; + + /* V4L Encoder Video */ + struct saa7164_encoder_params encoder_params; + struct video_device *v4l_device; + atomic_t v4l_reader_count; + + struct saa7164_buffer list_buf_used; + struct saa7164_buffer list_buf_free; + wait_queue_head_t wait_read; + + /* V4L VBI */ + struct tmComResVBIFormatDescrHeader vbi_fmt_ntsc; + struct saa7164_vbi_params vbi_params; + + /* Debug */ + u32 sync_errors; + u32 v_cc_errors; + u32 a_cc_errors; + u8 last_v_cc; + u8 last_a_cc; + u32 done_first_interrupt; +}; + +struct saa7164_dev { + struct list_head devlist; + atomic_t refcount; + + /* pci stuff */ + struct pci_dev *pci; + unsigned char pci_rev, pci_lat; + int pci_bus, pci_slot; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + u32 __iomem *lmmio2; + u8 __iomem *bmmio2; + int pci_irqmask; + + /* board details */ + int nr; + int hwrevision; + u32 board; + char name[16]; + + /* firmware status */ + struct saa7164_fw_status fw_status; + u32 firmwareloaded; + + struct tmComResHWDescr hwdesc; + struct tmComResInterfaceDescr intfdesc; + struct tmComResBusDescr busdesc; + + struct tmComResBusInfo bus; + + /* Interrupt status and ack registers */ + u32 int_status; + u32 int_ack; + + struct cmd cmds[SAA_CMD_MAX_MSG_UNITS]; + struct mutex lock; + + /* I2c related */ + struct saa7164_i2c i2c_bus[3]; + + /* Transport related */ + struct saa7164_port ports[SAA7164_MAX_PORTS]; + + /* Deferred command/api interrupts handling */ + struct work_struct workcmd; + + /* A kernel thread to monitor the firmware log, used + * only in debug mode. + */ + struct task_struct *kthread; + +}; + +extern struct list_head saa7164_devlist; +extern unsigned int waitsecs; +extern unsigned int encoder_buffers; +extern unsigned int vbi_buffers; + +/* ----------------------------------------------------------- */ +/* saa7164-core.c */ +void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr); +void saa7164_getfirmwarestatus(struct saa7164_dev *dev); +u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev); +void saa7164_histogram_update(struct saa7164_histogram *hg, u32 val); + +/* ----------------------------------------------------------- */ +/* saa7164-fw.c */ +int saa7164_downloadfirmware(struct saa7164_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7164-i2c.c */ +extern int saa7164_i2c_register(struct saa7164_i2c *bus); +extern int saa7164_i2c_unregister(struct saa7164_i2c *bus); +extern void saa7164_call_i2c_clients(struct saa7164_i2c *bus, + unsigned int cmd, void *arg); + +/* ----------------------------------------------------------- */ +/* saa7164-bus.c */ +int saa7164_bus_setup(struct saa7164_dev *dev); +void saa7164_bus_dump(struct saa7164_dev *dev); +int saa7164_bus_set(struct saa7164_dev *dev, struct tmComResInfo* msg, + void *buf); +int saa7164_bus_get(struct saa7164_dev *dev, struct tmComResInfo* msg, + void *buf, int peekonly); + +/* ----------------------------------------------------------- */ +/* saa7164-cmd.c */ +int saa7164_cmd_send(struct saa7164_dev *dev, + u8 id, enum tmComResCmd command, u16 controlselector, + u16 size, void *buf); +void saa7164_cmd_signal(struct saa7164_dev *dev, u8 seqno); +int saa7164_irq_dequeue(struct saa7164_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7164-api.c */ +int saa7164_api_get_fw_version(struct saa7164_dev *dev, u32 *version); +int saa7164_api_enum_subdevs(struct saa7164_dev *dev); +int saa7164_api_i2c_read(struct saa7164_i2c *bus, u8 addr, u32 reglen, u8 *reg, + u32 datalen, u8 *data); +int saa7164_api_i2c_write(struct saa7164_i2c *bus, u8 addr, + u32 datalen, u8 *data); +int saa7164_api_dif_write(struct saa7164_i2c *bus, u8 addr, + u32 datalen, u8 *data); +int saa7164_api_read_eeprom(struct saa7164_dev *dev, u8 *buf, int buflen); +int saa7164_api_set_gpiobit(struct saa7164_dev *dev, u8 unitid, u8 pin); +int saa7164_api_clear_gpiobit(struct saa7164_dev *dev, u8 unitid, u8 pin); +int saa7164_api_transition_port(struct saa7164_port *port, u8 mode); +int saa7164_api_initialize_dif(struct saa7164_port *port); +int saa7164_api_configure_dif(struct saa7164_port *port, u32 std); +int saa7164_api_set_encoder(struct saa7164_port *port); +int saa7164_api_get_encoder(struct saa7164_port *port); +int saa7164_api_set_aspect_ratio(struct saa7164_port *port); +int saa7164_api_set_usercontrol(struct saa7164_port *port, u8 ctl); +int saa7164_api_get_usercontrol(struct saa7164_port *port, u8 ctl); +int saa7164_api_set_videomux(struct saa7164_port *port); +int saa7164_api_audio_mute(struct saa7164_port *port, int mute); +int saa7164_api_set_audio_volume(struct saa7164_port *port, s8 level); +int saa7164_api_set_audio_std(struct saa7164_port *port); +int saa7164_api_set_audio_detection(struct saa7164_port *port, int autodetect); +int saa7164_api_get_videomux(struct saa7164_port *port); +int saa7164_api_set_vbi_format(struct saa7164_port *port); +int saa7164_api_set_debug(struct saa7164_dev *dev, u8 level); +int saa7164_api_collect_debug(struct saa7164_dev *dev); +int saa7164_api_get_load_info(struct saa7164_dev *dev, + struct tmFwInfoStruct *i); + +/* ----------------------------------------------------------- */ +/* saa7164-cards.c */ +extern struct saa7164_board saa7164_boards[]; +extern const unsigned int saa7164_bcount; + +extern struct saa7164_subid saa7164_subids[]; +extern const unsigned int saa7164_idcount; + +extern void saa7164_card_list(struct saa7164_dev *dev); +extern void saa7164_gpio_setup(struct saa7164_dev *dev); +extern void saa7164_card_setup(struct saa7164_dev *dev); + +extern int saa7164_i2caddr_to_reglen(struct saa7164_i2c *bus, int addr); +extern int saa7164_i2caddr_to_unitid(struct saa7164_i2c *bus, int addr); +extern char *saa7164_unitid_name(struct saa7164_dev *dev, u8 unitid); + +/* ----------------------------------------------------------- */ +/* saa7164-dvb.c */ +extern int saa7164_dvb_register(struct saa7164_port *port); +extern int saa7164_dvb_unregister(struct saa7164_port *port); + +/* ----------------------------------------------------------- */ +/* saa7164-buffer.c */ +extern struct saa7164_buffer *saa7164_buffer_alloc( + struct saa7164_port *port, u32 len); +extern int saa7164_buffer_dealloc(struct saa7164_buffer *buf); +extern void saa7164_buffer_display(struct saa7164_buffer *buf); +extern int saa7164_buffer_activate(struct saa7164_buffer *buf, int i); +extern int saa7164_buffer_cfg_port(struct saa7164_port *port); +extern struct saa7164_user_buffer *saa7164_buffer_alloc_user( + struct saa7164_dev *dev, u32 len); +extern void saa7164_buffer_dealloc_user(struct saa7164_user_buffer *buf); +extern int saa7164_buffer_zero_offsets(struct saa7164_port *port, int i); + +/* ----------------------------------------------------------- */ +/* saa7164-encoder.c */ +int saa7164_encoder_register(struct saa7164_port *port); +void saa7164_encoder_unregister(struct saa7164_port *port); + +/* ----------------------------------------------------------- */ +/* saa7164-vbi.c */ +int saa7164_vbi_register(struct saa7164_port *port); +void saa7164_vbi_unregister(struct saa7164_port *port); + +/* ----------------------------------------------------------- */ + +extern unsigned int crc_checking; + +extern unsigned int saa_debug; +#define dprintk(level, fmt, arg...)\ + do { if (saa_debug & level)\ + printk(KERN_DEBUG "%s: " fmt, dev->name, ## arg);\ + } while (0) + +#define log_warn(fmt, arg...)\ + do { \ + printk(KERN_WARNING "%s: " fmt, dev->name, ## arg);\ + } while (0) + +#define saa7164_readl(reg) readl(dev->lmmio + ((reg) >> 2)) +#define saa7164_writel(reg, value) writel((value), dev->lmmio + ((reg) >> 2)) + +#define saa7164_readb(reg) readl(dev->bmmio + (reg)) +#define saa7164_writeb(reg, value) writel((value), dev->bmmio + (reg)) + diff --git a/drivers/media/pci/sta2x11/Kconfig b/drivers/media/pci/sta2x11/Kconfig new file mode 100644 index 000000000000..6749f67cab8a --- /dev/null +++ b/drivers/media/pci/sta2x11/Kconfig @@ -0,0 +1,12 @@ +config STA2X11_VIP + tristate "STA2X11 VIP Video For Linux" + depends on STA2X11 + select VIDEO_ADV7180 if MEDIA_SUBDRV_AUTOSELECT + select VIDEOBUF_DMA_CONTIG + depends on PCI && VIDEO_V4L2 && VIRT_TO_BUS + help + Say Y for support for STA2X11 VIP (Video Input Port) capture + device. + + To compile this driver as a module, choose M here: the + module will be called sta2x11_vip. diff --git a/drivers/media/pci/sta2x11/Makefile b/drivers/media/pci/sta2x11/Makefile new file mode 100644 index 000000000000..d6c471d1d1b4 --- /dev/null +++ b/drivers/media/pci/sta2x11/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_STA2X11_VIP) += sta2x11_vip.o diff --git a/drivers/media/video/sta2x11_vip.c b/drivers/media/pci/sta2x11/sta2x11_vip.c similarity index 100% rename from drivers/media/video/sta2x11_vip.c rename to drivers/media/pci/sta2x11/sta2x11_vip.c diff --git a/drivers/media/video/sta2x11_vip.h b/drivers/media/pci/sta2x11/sta2x11_vip.h similarity index 100% rename from drivers/media/video/sta2x11_vip.h rename to drivers/media/pci/sta2x11/sta2x11_vip.h diff --git a/drivers/media/pci/ttpci/Kconfig b/drivers/media/pci/ttpci/Kconfig new file mode 100644 index 000000000000..314e417addae --- /dev/null +++ b/drivers/media/pci/ttpci/Kconfig @@ -0,0 +1,159 @@ +config TTPCI_EEPROM + tristate + depends on I2C + default n + +config DVB_AV7110 + tristate "AV7110 cards" + depends on DVB_CORE && PCI && I2C + select TTPCI_EEPROM + select VIDEO_SAA7146_VV + depends on VIDEO_DEV # dependencies of VIDEO_SAA7146_VV + select DVB_VES1820 if MEDIA_SUBDRV_AUTOSELECT + select DVB_VES1X93 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA8083 if MEDIA_SUBDRV_AUTOSELECT + select DVB_SP8870 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0297 if MEDIA_SUBDRV_AUTOSELECT + select DVB_L64781 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + help + Support for SAA7146 and AV7110 based DVB cards as produced + by Fujitsu-Siemens, Technotrend, Hauppauge and others. + + This driver only supports the fullfeatured cards with + onboard MPEG2 decoder. + + This driver needs an external firmware. Please use the script + "/Documentation/dvb/get_dvb_firmware av7110" to + download/extract it, and then copy it to /usr/lib/hotplug/firmware + or /lib/firmware (depending on configuration of firmware hotplug). + + Alternatively, you can download the file and use the kernel's + EXTRA_FIRMWARE configuration option to build it into your + kernel image by adding the filename to the EXTRA_FIRMWARE + configuration option string. + + Say Y if you own such a card and want to use it. + +config DVB_AV7110_OSD + bool "AV7110 OSD support" + depends on DVB_AV7110 + default y if DVB_AV7110=y || DVB_AV7110=m + help + The AV7110 firmware provides some code to generate an OnScreenDisplay + on the video output. This is kind of nonstandard and not guaranteed to + be maintained. + + Anyway, some popular DVB software like VDR uses this OSD to render + its menus, so say Y if you want to use this software. + + All other people say N. + +config DVB_BUDGET_CORE + tristate "SAA7146 DVB cards (aka Budget, Nova-PCI)" + depends on DVB_CORE && PCI && I2C + select VIDEO_SAA7146 + select TTPCI_EEPROM + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder. + +config DVB_BUDGET + tristate "Budget cards" + depends on DVB_BUDGET_CORE && I2C + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_VES1X93 if MEDIA_SUBDRV_AUTOSELECT + select DVB_VES1820 if MEDIA_SUBDRV_AUTOSELECT + select DVB_L64781 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA8083 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1420 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10086 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA826X if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_ISL6423 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT + help + Support for simple SAA7146 based DVB cards (so called Budget- + or Nova-PCI cards) without onboard MPEG2 decoder, and without + analog inputs or an onboard Common Interface connector. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget. + +config DVB_BUDGET_CI + tristate "Budget cards with onboard CI connector" + depends on DVB_BUDGET_CORE && I2C + select DVB_STV0297 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA827X if MEDIA_SUBDRV_AUTOSELECT + depends on RC_CORE + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder, but with onboard Common Interface connector. + + Note: The Common Interface is not yet supported by this driver + due to lack of information from the vendor. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-ci. + +config DVB_BUDGET_AV + tristate "Budget cards with analog video inputs" + depends on DVB_BUDGET_CORE && I2C + select VIDEO_SAA7146_VV + depends on VIDEO_DEV # dependencies of VIDEO_SAA7146_VV + select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10021 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA8261 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TUA6100 if MEDIA_SUBDRV_AUTOSELECT + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder, but with one or more analog video inputs. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-av. + +config DVB_BUDGET_PATCH + tristate "AV7110 cards with Budget Patch" + depends on DVB_BUDGET_CORE && I2C + depends on DVB_AV7110 + select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT + select DVB_VES1X93 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA8083 if MEDIA_SUBDRV_AUTOSELECT + help + Support for Budget Patch (full TS) modification on + SAA7146+AV7110 based cards (DVB-S cards). This + driver doesn't use onboard MPEG2 decoder. The + card is driven in Budget-only mode. Card is + required to have loaded firmware to tune properly. + Firmware can be loaded by insertion and removal of + standard AV7110 driver prior to loading this + driver. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-patch. diff --git a/drivers/media/pci/ttpci/Makefile b/drivers/media/pci/ttpci/Makefile new file mode 100644 index 000000000000..98905963ff08 --- /dev/null +++ b/drivers/media/pci/ttpci/Makefile @@ -0,0 +1,21 @@ +# +# Makefile for the kernel SAA7146 FULL TS DVB device driver +# and the AV7110 DVB device driver +# + +dvb-ttpci-objs := av7110_hw.o av7110_v4l.o av7110_av.o av7110_ca.o av7110.o av7110_ipack.o + +ifdef CONFIG_INPUT_EVDEV +dvb-ttpci-objs += av7110_ir.o +endif + +obj-$(CONFIG_TTPCI_EEPROM) += ttpci-eeprom.o +obj-$(CONFIG_DVB_BUDGET_CORE) += budget-core.o +obj-$(CONFIG_DVB_BUDGET) += budget.o +obj-$(CONFIG_DVB_BUDGET_AV) += budget-av.o +obj-$(CONFIG_DVB_BUDGET_CI) += budget-ci.o +obj-$(CONFIG_DVB_BUDGET_PATCH) += budget-patch.o +obj-$(CONFIG_DVB_AV7110) += dvb-ttpci.o + +ccflags-y += -Idrivers/media/dvb-core/ -Idrivers/media/dvb-frontends/ +ccflags-y += -Idrivers/media/tuners diff --git a/drivers/media/dvb/ttpci/av7110.c b/drivers/media/pci/ttpci/av7110.c similarity index 100% rename from drivers/media/dvb/ttpci/av7110.c rename to drivers/media/pci/ttpci/av7110.c diff --git a/drivers/media/dvb/ttpci/av7110.h b/drivers/media/pci/ttpci/av7110.h similarity index 100% rename from drivers/media/dvb/ttpci/av7110.h rename to drivers/media/pci/ttpci/av7110.h diff --git a/drivers/media/dvb/ttpci/av7110_av.c b/drivers/media/pci/ttpci/av7110_av.c similarity index 100% rename from drivers/media/dvb/ttpci/av7110_av.c rename to drivers/media/pci/ttpci/av7110_av.c diff --git a/drivers/media/dvb/ttpci/av7110_av.h b/drivers/media/pci/ttpci/av7110_av.h similarity index 100% rename from drivers/media/dvb/ttpci/av7110_av.h rename to drivers/media/pci/ttpci/av7110_av.h diff --git a/drivers/media/dvb/ttpci/av7110_ca.c b/drivers/media/pci/ttpci/av7110_ca.c similarity index 100% rename from drivers/media/dvb/ttpci/av7110_ca.c rename to drivers/media/pci/ttpci/av7110_ca.c diff --git a/drivers/media/dvb/ttpci/av7110_ca.h b/drivers/media/pci/ttpci/av7110_ca.h similarity index 100% rename from drivers/media/dvb/ttpci/av7110_ca.h rename to drivers/media/pci/ttpci/av7110_ca.h diff --git a/drivers/media/dvb/ttpci/av7110_hw.c b/drivers/media/pci/ttpci/av7110_hw.c similarity index 100% rename from drivers/media/dvb/ttpci/av7110_hw.c rename to drivers/media/pci/ttpci/av7110_hw.c diff --git a/drivers/media/dvb/ttpci/av7110_hw.h b/drivers/media/pci/ttpci/av7110_hw.h similarity index 100% rename from drivers/media/dvb/ttpci/av7110_hw.h rename to drivers/media/pci/ttpci/av7110_hw.h diff --git a/drivers/media/dvb/ttpci/av7110_ipack.c b/drivers/media/pci/ttpci/av7110_ipack.c similarity index 100% rename from drivers/media/dvb/ttpci/av7110_ipack.c rename to drivers/media/pci/ttpci/av7110_ipack.c diff --git a/drivers/media/dvb/ttpci/av7110_ipack.h b/drivers/media/pci/ttpci/av7110_ipack.h similarity index 100% rename from drivers/media/dvb/ttpci/av7110_ipack.h rename to drivers/media/pci/ttpci/av7110_ipack.h diff --git a/drivers/media/dvb/ttpci/av7110_ir.c b/drivers/media/pci/ttpci/av7110_ir.c similarity index 100% rename from drivers/media/dvb/ttpci/av7110_ir.c rename to drivers/media/pci/ttpci/av7110_ir.c diff --git a/drivers/media/pci/ttpci/av7110_v4l.c b/drivers/media/pci/ttpci/av7110_v4l.c new file mode 100644 index 000000000000..730e906ea912 --- /dev/null +++ b/drivers/media/pci/ttpci/av7110_v4l.c @@ -0,0 +1,966 @@ +/* + * av7110_v4l.c: av7110 video4linux interface for DVB and Siemens DVB-C analog module + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * originally based on code by: + * Copyright (C) 1998,1999 Christian Theiss + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * the project's page is at http://www.linuxtv.org/ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include "av7110.h" +#include "av7110_hw.h" +#include "av7110_av.h" + +int msp_writereg(struct av7110 *av7110, u8 dev, u16 reg, u16 val) +{ + u8 msg[5] = { dev, reg >> 8, reg & 0xff, val >> 8 , val & 0xff }; + struct i2c_msg msgs = { .flags = 0, .len = 5, .buf = msg }; + + switch (av7110->adac_type) { + case DVB_ADAC_MSP34x0: + msgs.addr = 0x40; + break; + case DVB_ADAC_MSP34x5: + msgs.addr = 0x42; + break; + default: + return 0; + } + + if (i2c_transfer(&av7110->i2c_adap, &msgs, 1) != 1) { + dprintk(1, "dvb-ttpci: failed @ card %d, %u = %u\n", + av7110->dvb_adapter.num, reg, val); + return -EIO; + } + return 0; +} + +static int msp_readreg(struct av7110 *av7110, u8 dev, u16 reg, u16 *val) +{ + u8 msg1[3] = { dev, reg >> 8, reg & 0xff }; + u8 msg2[2]; + struct i2c_msg msgs[2] = { + { .flags = 0 , .len = 3, .buf = msg1 }, + { .flags = I2C_M_RD, .len = 2, .buf = msg2 } + }; + + switch (av7110->adac_type) { + case DVB_ADAC_MSP34x0: + msgs[0].addr = 0x40; + msgs[1].addr = 0x40; + break; + case DVB_ADAC_MSP34x5: + msgs[0].addr = 0x42; + msgs[1].addr = 0x42; + break; + default: + return 0; + } + + if (i2c_transfer(&av7110->i2c_adap, &msgs[0], 2) != 2) { + dprintk(1, "dvb-ttpci: failed @ card %d, %u\n", + av7110->dvb_adapter.num, reg); + return -EIO; + } + *val = (msg2[0] << 8) | msg2[1]; + return 0; +} + +static struct v4l2_input inputs[4] = { + { + .index = 0, + .name = "DVB", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 1, + .tuner = 0, /* ignored */ + .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, + .status = 0, + .capabilities = V4L2_IN_CAP_STD, + }, { + .index = 1, + .name = "Television", + .type = V4L2_INPUT_TYPE_TUNER, + .audioset = 1, + .tuner = 0, + .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, + .status = 0, + .capabilities = V4L2_IN_CAP_STD, + }, { + .index = 2, + .name = "Video", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 0, + .tuner = 0, + .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, + .status = 0, + .capabilities = V4L2_IN_CAP_STD, + }, { + .index = 3, + .name = "Y/C", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 0, + .tuner = 0, + .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, + .status = 0, + .capabilities = V4L2_IN_CAP_STD, + } +}; + +static int ves1820_writereg(struct saa7146_dev *dev, u8 addr, u8 reg, u8 data) +{ + struct av7110 *av7110 = dev->ext_priv; + u8 buf[] = { 0x00, reg, data }; + struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = buf, .len = 3 }; + + dprintk(4, "dev: %p\n", dev); + + if (1 != i2c_transfer(&av7110->i2c_adap, &msg, 1)) + return -1; + return 0; +} + +static int tuner_write(struct saa7146_dev *dev, u8 addr, u8 data [4]) +{ + struct av7110 *av7110 = dev->ext_priv; + struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = data, .len = 4 }; + + dprintk(4, "dev: %p\n", dev); + + if (1 != i2c_transfer(&av7110->i2c_adap, &msg, 1)) + return -1; + return 0; +} + +static int ves1820_set_tv_freq(struct saa7146_dev *dev, u32 freq) +{ + u32 div; + u8 config; + u8 buf[4]; + + dprintk(4, "freq: 0x%08x\n", freq); + + /* magic number: 614. tuning with the frequency given by v4l2 + is always off by 614*62.5 = 38375 kHz...*/ + div = freq + 614; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x8e; + + if (freq < (u32) (16 * 168.25)) + config = 0xa0; + else if (freq < (u32) (16 * 447.25)) + config = 0x90; + else + config = 0x30; + config &= ~0x02; + + buf[3] = config; + + return tuner_write(dev, 0x61, buf); +} + +static int stv0297_set_tv_freq(struct saa7146_dev *dev, u32 freq) +{ + struct av7110 *av7110 = (struct av7110*)dev->ext_priv; + u32 div; + u8 data[4]; + + div = (freq + 38900000 + 31250) / 62500; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0xce; + + if (freq < 45000000) + return -EINVAL; + else if (freq < 137000000) + data[3] = 0x01; + else if (freq < 403000000) + data[3] = 0x02; + else if (freq < 860000000) + data[3] = 0x04; + else + return -EINVAL; + + if (av7110->fe->ops.i2c_gate_ctrl) + av7110->fe->ops.i2c_gate_ctrl(av7110->fe, 1); + return tuner_write(dev, 0x63, data); +} + + + +static struct saa7146_standard analog_standard[]; +static struct saa7146_standard dvb_standard[]; +static struct saa7146_standard standard[]; + +static struct v4l2_audio msp3400_v4l2_audio = { + .index = 0, + .name = "Television", + .capability = V4L2_AUDCAP_STEREO +}; + +static int av7110_dvb_c_switch(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct av7110 *av7110 = (struct av7110*)dev->ext_priv; + u16 adswitch; + int source, sync, err; + + dprintk(4, "%p\n", av7110); + + if ((vv->video_status & STATUS_OVERLAY) != 0) { + vv->ov_suspend = vv->video_fh; + err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ + if (err != 0) { + dprintk(2, "suspending video failed\n"); + vv->ov_suspend = NULL; + } + } + + if (0 != av7110->current_input) { + dprintk(1, "switching to analog TV:\n"); + adswitch = 1; + source = SAA7146_HPS_SOURCE_PORT_B; + sync = SAA7146_HPS_SYNC_PORT_B; + memcpy(standard, analog_standard, sizeof(struct saa7146_standard) * 2); + + switch (av7110->current_input) { + case 1: + dprintk(1, "switching SAA7113 to Analog Tuner Input\n"); + msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0000); // loudspeaker source + msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0000); // headphone source + msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0000); // SCART 1 source + msp_writereg(av7110, MSP_WR_DSP, 0x000e, 0x3000); // FM matrix, mono + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x4f00); // loudspeaker + headphone + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x4f00); // SCART 1 volume + + if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { + if (ves1820_writereg(dev, 0x09, 0x0f, 0x60)) + dprintk(1, "setting band in demodulator failed\n"); + } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); // TDA9819 pin9(STD) + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); // TDA9819 pin30(VIF) + } + if (i2c_writereg(av7110, 0x48, 0x02, 0xd0) != 1) + dprintk(1, "saa7113 write failed @ card %d", av7110->dvb_adapter.num); + break; + case 2: + dprintk(1, "switching SAA7113 to Video AV CVBS Input\n"); + if (i2c_writereg(av7110, 0x48, 0x02, 0xd2) != 1) + dprintk(1, "saa7113 write failed @ card %d", av7110->dvb_adapter.num); + break; + case 3: + dprintk(1, "switching SAA7113 to Video AV Y/C Input\n"); + if (i2c_writereg(av7110, 0x48, 0x02, 0xd9) != 1) + dprintk(1, "saa7113 write failed @ card %d", av7110->dvb_adapter.num); + break; + default: + dprintk(1, "switching SAA7113 to Input: AV7110: SAA7113: invalid input\n"); + } + } else { + adswitch = 0; + source = SAA7146_HPS_SOURCE_PORT_A; + sync = SAA7146_HPS_SYNC_PORT_A; + memcpy(standard, dvb_standard, sizeof(struct saa7146_standard) * 2); + dprintk(1, "switching DVB mode\n"); + msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0220); // loudspeaker source + msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0220); // headphone source + msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0220); // SCART 1 source + msp_writereg(av7110, MSP_WR_DSP, 0x000e, 0x3000); // FM matrix, mono + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x7f00); // loudspeaker + headphone + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x7f00); // SCART 1 volume + + if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { + if (ves1820_writereg(dev, 0x09, 0x0f, 0x20)) + dprintk(1, "setting band in demodulator failed\n"); + } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTLO); // TDA9819 pin9(STD) + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); // TDA9819 pin30(VIF) + } + } + + /* hmm, this does not do anything!? */ + if (av7110_fw_cmd(av7110, COMTYPE_AUDIODAC, ADSwitch, 1, adswitch)) + dprintk(1, "ADSwitch error\n"); + + saa7146_set_hps_source_and_sync(dev, source, sync); + + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *fh, struct v4l2_tuner *t) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + u16 stereo_det; + s8 stereo; + + dprintk(2, "VIDIOC_G_TUNER: %d\n", t->index); + + if (!av7110->analog_tuner_flags || t->index != 0) + return -EINVAL; + + memset(t, 0, sizeof(*t)); + strcpy((char *)t->name, "Television"); + + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + t->rangelow = 772; /* 48.25 MHZ / 62.5 kHz = 772, see fi1216mk2-specs, page 2 */ + t->rangehigh = 13684; /* 855.25 MHz / 62.5 kHz = 13684 */ + /* FIXME: add the real signal strength here */ + t->signal = 0xffff; + t->afc = 0; + + /* FIXME: standard / stereo detection is still broken */ + msp_readreg(av7110, MSP_RD_DEM, 0x007e, &stereo_det); + dprintk(1, "VIDIOC_G_TUNER: msp3400 TV standard detection: 0x%04x\n", stereo_det); + msp_readreg(av7110, MSP_RD_DSP, 0x0018, &stereo_det); + dprintk(1, "VIDIOC_G_TUNER: msp3400 stereo detection: 0x%04x\n", stereo_det); + stereo = (s8)(stereo_det >> 8); + if (stereo > 0x10) { + /* stereo */ + t->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; + t->audmode = V4L2_TUNER_MODE_STEREO; + } else if (stereo < -0x10) { + /* bilingual */ + t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + t->audmode = V4L2_TUNER_MODE_LANG1; + } else /* mono */ + t->rxsubchans = V4L2_TUNER_SUB_MONO; + + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *fh, struct v4l2_tuner *t) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + u16 fm_matrix, src; + dprintk(2, "VIDIOC_S_TUNER: %d\n", t->index); + + if (!av7110->analog_tuner_flags || av7110->current_input != 1) + return -EINVAL; + + switch (t->audmode) { + case V4L2_TUNER_MODE_STEREO: + dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_STEREO\n"); + fm_matrix = 0x3001; /* stereo */ + src = 0x0020; + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1_LANG2\n"); + fm_matrix = 0x3000; /* bilingual */ + src = 0x0020; + break; + case V4L2_TUNER_MODE_LANG1: + dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1\n"); + fm_matrix = 0x3000; /* mono */ + src = 0x0000; + break; + case V4L2_TUNER_MODE_LANG2: + dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG2\n"); + fm_matrix = 0x3000; /* mono */ + src = 0x0010; + break; + default: /* case V4L2_TUNER_MODE_MONO: */ + dprintk(2, "VIDIOC_S_TUNER: TDA9840_SET_MONO\n"); + fm_matrix = 0x3000; /* mono */ + src = 0x0030; + break; + } + msp_writereg(av7110, MSP_WR_DSP, 0x000e, fm_matrix); + msp_writereg(av7110, MSP_WR_DSP, 0x0008, src); + msp_writereg(av7110, MSP_WR_DSP, 0x0009, src); + msp_writereg(av7110, MSP_WR_DSP, 0x000a, src); + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *fh, struct v4l2_frequency *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + dprintk(2, "VIDIOC_G_FREQ: freq:0x%08x\n", f->frequency); + + if (!av7110->analog_tuner_flags || av7110->current_input != 1) + return -EINVAL; + + memset(f, 0, sizeof(*f)); + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = av7110->current_freq; + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *fh, struct v4l2_frequency *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + dprintk(2, "VIDIOC_S_FREQUENCY: freq:0x%08x\n", f->frequency); + + if (!av7110->analog_tuner_flags || av7110->current_input != 1) + return -EINVAL; + + if (V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0xffe0); /* fast mute */ + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0xffe0); + + /* tune in desired frequency */ + if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) + ves1820_set_tv_freq(dev, f->frequency); + else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) + stv0297_set_tv_freq(dev, f->frequency); + av7110->current_freq = f->frequency; + + msp_writereg(av7110, MSP_WR_DSP, 0x0015, 0x003f); /* start stereo detection */ + msp_writereg(av7110, MSP_WR_DSP, 0x0015, 0x0000); + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x4f00); /* loudspeaker + headphone */ + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x4f00); /* SCART 1 volume */ + return 0; +} + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + dprintk(2, "VIDIOC_ENUMINPUT: %d\n", i->index); + + if (av7110->analog_tuner_flags) { + if (i->index >= 4) + return -EINVAL; + } else { + if (i->index != 0) + return -EINVAL; + } + + memcpy(i, &inputs[i->index], sizeof(struct v4l2_input)); + + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *input) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + *input = av7110->current_input; + dprintk(2, "VIDIOC_G_INPUT: %d\n", *input); + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int input) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + dprintk(2, "VIDIOC_S_INPUT: %d\n", input); + + if (!av7110->analog_tuner_flags) + return input ? -EINVAL : 0; + + if (input >= 4) + return -EINVAL; + + av7110->current_input = input; + return av7110_dvb_c_switch(fh); +} + +static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a) +{ + dprintk(2, "VIDIOC_G_AUDIO: %d\n", a->index); + if (a->index != 0) + return -EINVAL; + *a = msp3400_v4l2_audio; + return 0; +} + +static int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + dprintk(2, "VIDIOC_G_AUDIO: %d\n", a->index); + if (a->index != 0) + return -EINVAL; + if (av7110->current_input >= 2) + return -EINVAL; + *a = msp3400_v4l2_audio; + return 0; +} + +static int vidioc_s_audio(struct file *file, void *fh, const struct v4l2_audio *a) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + dprintk(2, "VIDIOC_S_AUDIO: %d\n", a->index); + if (av7110->current_input >= 2) + return -EINVAL; + return a->index ? -EINVAL : 0; +} + +static int vidioc_g_sliced_vbi_cap(struct file *file, void *fh, + struct v4l2_sliced_vbi_cap *cap) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + dprintk(2, "VIDIOC_G_SLICED_VBI_CAP\n"); + if (cap->type != V4L2_BUF_TYPE_SLICED_VBI_OUTPUT) + return -EINVAL; + if (FW_VERSION(av7110->arm_app) >= 0x2623) { + cap->service_set = V4L2_SLICED_WSS_625; + cap->service_lines[0][23] = V4L2_SLICED_WSS_625; + } + return 0; +} + +static int vidioc_g_fmt_sliced_vbi_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + dprintk(2, "VIDIOC_G_FMT:\n"); + if (FW_VERSION(av7110->arm_app) < 0x2623) + return -EINVAL; + memset(&f->fmt.sliced, 0, sizeof f->fmt.sliced); + if (av7110->wssMode) { + f->fmt.sliced.service_set = V4L2_SLICED_WSS_625; + f->fmt.sliced.service_lines[0][23] = V4L2_SLICED_WSS_625; + f->fmt.sliced.io_size = sizeof(struct v4l2_sliced_vbi_data); + } + return 0; +} + +static int vidioc_s_fmt_sliced_vbi_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + struct av7110 *av7110 = (struct av7110 *)dev->ext_priv; + + dprintk(2, "VIDIOC_S_FMT\n"); + if (FW_VERSION(av7110->arm_app) < 0x2623) + return -EINVAL; + if (f->fmt.sliced.service_set != V4L2_SLICED_WSS_625 && + f->fmt.sliced.service_lines[0][23] != V4L2_SLICED_WSS_625) { + memset(&f->fmt.sliced, 0, sizeof(f->fmt.sliced)); + /* WSS controlled by firmware */ + av7110->wssMode = 0; + av7110->wssData = 0; + return av7110_fw_cmd(av7110, COMTYPE_ENCODER, + SetWSSConfig, 1, 0); + } else { + memset(&f->fmt.sliced, 0, sizeof(f->fmt.sliced)); + f->fmt.sliced.service_set = V4L2_SLICED_WSS_625; + f->fmt.sliced.service_lines[0][23] = V4L2_SLICED_WSS_625; + f->fmt.sliced.io_size = sizeof(struct v4l2_sliced_vbi_data); + /* WSS controlled by userspace */ + av7110->wssMode = 1; + av7110->wssData = 0; + } + return 0; +} + +static int av7110_vbi_reset(struct file *file) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct av7110 *av7110 = (struct av7110*) dev->ext_priv; + + dprintk(2, "%s\n", __func__); + av7110->wssMode = 0; + av7110->wssData = 0; + if (FW_VERSION(av7110->arm_app) < 0x2623) + return 0; + else + return av7110_fw_cmd(av7110, COMTYPE_ENCODER, SetWSSConfig, 1, 0); +} + +static ssize_t av7110_vbi_write(struct file *file, const char __user *data, size_t count, loff_t *ppos) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct av7110 *av7110 = (struct av7110*) dev->ext_priv; + struct v4l2_sliced_vbi_data d; + int rc; + + dprintk(2, "%s\n", __func__); + if (FW_VERSION(av7110->arm_app) < 0x2623 || !av7110->wssMode || count != sizeof d) + return -EINVAL; + if (copy_from_user(&d, data, count)) + return -EFAULT; + if ((d.id != 0 && d.id != V4L2_SLICED_WSS_625) || d.field != 0 || d.line != 23) + return -EINVAL; + if (d.id) + av7110->wssData = ((d.data[1] << 8) & 0x3f00) | d.data[0]; + else + av7110->wssData = 0x8000; + rc = av7110_fw_cmd(av7110, COMTYPE_ENCODER, SetWSSConfig, 2, 1, av7110->wssData); + return (rc < 0) ? rc : count; +} + +/**************************************************************************** + * INITIALIZATION + ****************************************************************************/ + +static u8 saa7113_init_regs[] = { + 0x02, 0xd0, + 0x03, 0x23, + 0x04, 0x00, + 0x05, 0x00, + 0x06, 0xe9, + 0x07, 0x0d, + 0x08, 0x98, + 0x09, 0x02, + 0x0a, 0x80, + 0x0b, 0x40, + 0x0c, 0x40, + 0x0d, 0x00, + 0x0e, 0x01, + 0x0f, 0x7c, + 0x10, 0x48, + 0x11, 0x0c, + 0x12, 0x8b, + 0x13, 0x1a, + 0x14, 0x00, + 0x15, 0x00, + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1b, 0x00, + 0x1c, 0x00, + 0x1d, 0x00, + 0x1e, 0x00, + + 0x41, 0x77, + 0x42, 0x77, + 0x43, 0x77, + 0x44, 0x77, + 0x45, 0x77, + 0x46, 0x77, + 0x47, 0x77, + 0x48, 0x77, + 0x49, 0x77, + 0x4a, 0x77, + 0x4b, 0x77, + 0x4c, 0x77, + 0x4d, 0x77, + 0x4e, 0x77, + 0x4f, 0x77, + 0x50, 0x77, + 0x51, 0x77, + 0x52, 0x77, + 0x53, 0x77, + 0x54, 0x77, + 0x55, 0x77, + 0x56, 0x77, + 0x57, 0xff, + + 0xff +}; + + +static struct saa7146_ext_vv av7110_vv_data_st; +static struct saa7146_ext_vv av7110_vv_data_c; + +int av7110_init_analog_module(struct av7110 *av7110) +{ + u16 version1, version2; + + if (i2c_writereg(av7110, 0x80, 0x0, 0x80) == 1 && + i2c_writereg(av7110, 0x80, 0x0, 0) == 1) { + pr_info("DVB-C analog module @ card %d detected, initializing MSP3400\n", + av7110->dvb_adapter.num); + av7110->adac_type = DVB_ADAC_MSP34x0; + } else if (i2c_writereg(av7110, 0x84, 0x0, 0x80) == 1 && + i2c_writereg(av7110, 0x84, 0x0, 0) == 1) { + pr_info("DVB-C analog module @ card %d detected, initializing MSP3415\n", + av7110->dvb_adapter.num); + av7110->adac_type = DVB_ADAC_MSP34x5; + } else + return -ENODEV; + + msleep(100); // the probing above resets the msp... + msp_readreg(av7110, MSP_RD_DSP, 0x001e, &version1); + msp_readreg(av7110, MSP_RD_DSP, 0x001f, &version2); + dprintk(1, "dvb-ttpci: @ card %d MSP34xx version 0x%04x 0x%04x\n", + av7110->dvb_adapter.num, version1, version2); + msp_writereg(av7110, MSP_WR_DSP, 0x0013, 0x0c00); + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x7f00); // loudspeaker + headphone + msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0220); // loudspeaker source + msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0220); // headphone source + msp_writereg(av7110, MSP_WR_DSP, 0x0004, 0x7f00); // loudspeaker volume + msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0220); // SCART 1 source + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x7f00); // SCART 1 volume + msp_writereg(av7110, MSP_WR_DSP, 0x000d, 0x1900); // prescale SCART + + if (i2c_writereg(av7110, 0x48, 0x01, 0x00)!=1) { + pr_info("saa7113 not accessible\n"); + } else { + u8 *i = saa7113_init_regs; + + if ((av7110->dev->pci->subsystem_vendor == 0x110a) && (av7110->dev->pci->subsystem_device == 0x0000)) { + /* Fujitsu/Siemens DVB-Cable */ + av7110->analog_tuner_flags |= ANALOG_TUNER_VES1820; + } else if ((av7110->dev->pci->subsystem_vendor == 0x13c2) && (av7110->dev->pci->subsystem_device == 0x0002)) { + /* Hauppauge/TT DVB-C premium */ + av7110->analog_tuner_flags |= ANALOG_TUNER_VES1820; + } else if ((av7110->dev->pci->subsystem_vendor == 0x13c2) && (av7110->dev->pci->subsystem_device == 0x000A)) { + /* Hauppauge/TT DVB-C premium */ + av7110->analog_tuner_flags |= ANALOG_TUNER_STV0297; + } + + /* setup for DVB by default */ + if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { + if (ves1820_writereg(av7110->dev, 0x09, 0x0f, 0x20)) + dprintk(1, "setting band in demodulator failed\n"); + } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { + saa7146_setgpio(av7110->dev, 1, SAA7146_GPIO_OUTLO); // TDA9819 pin9(STD) + saa7146_setgpio(av7110->dev, 3, SAA7146_GPIO_OUTLO); // TDA9819 pin30(VIF) + } + + /* init the saa7113 */ + while (*i != 0xff) { + if (i2c_writereg(av7110, 0x48, i[0], i[1]) != 1) { + dprintk(1, "saa7113 initialization failed @ card %d", av7110->dvb_adapter.num); + break; + } + i += 2; + } + /* setup msp for analog sound: B/G Dual-FM */ + msp_writereg(av7110, MSP_WR_DEM, 0x00bb, 0x02d0); // AD_CV + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 3); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 18); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 27); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 48); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 66); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 72); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 4); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 64); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 0); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 3); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 18); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 27); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 48); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 66); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 72); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0083, 0xa000); // MODE_REG + msp_writereg(av7110, MSP_WR_DEM, 0x0093, 0x00aa); // DCO1_LO 5.74MHz + msp_writereg(av7110, MSP_WR_DEM, 0x009b, 0x04fc); // DCO1_HI + msp_writereg(av7110, MSP_WR_DEM, 0x00a3, 0x038e); // DCO2_LO 5.5MHz + msp_writereg(av7110, MSP_WR_DEM, 0x00ab, 0x04c6); // DCO2_HI + msp_writereg(av7110, MSP_WR_DEM, 0x0056, 0); // LOAD_REG 1/2 + } + + memcpy(standard, dvb_standard, sizeof(struct saa7146_standard) * 2); + /* set dd1 stream a & b */ + saa7146_write(av7110->dev, DD1_STREAM_B, 0x00000000); + saa7146_write(av7110->dev, DD1_INIT, 0x03000700); + saa7146_write(av7110->dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + return 0; +} + +int av7110_init_v4l(struct av7110 *av7110) +{ + struct saa7146_dev* dev = av7110->dev; + struct saa7146_ext_vv *vv_data; + int ret; + + /* special case DVB-C: these cards have an analog tuner + plus need some special handling, so we have separate + saa7146_ext_vv data for these... */ + if (av7110->analog_tuner_flags) + vv_data = &av7110_vv_data_c; + else + vv_data = &av7110_vv_data_st; + ret = saa7146_vv_init(dev, vv_data); + + if (ret) { + ERR("cannot init capture device. skipping\n"); + return -ENODEV; + } + vv_data->vid_ops.vidioc_enum_input = vidioc_enum_input; + vv_data->vid_ops.vidioc_g_input = vidioc_g_input; + vv_data->vid_ops.vidioc_s_input = vidioc_s_input; + vv_data->vid_ops.vidioc_g_tuner = vidioc_g_tuner; + vv_data->vid_ops.vidioc_s_tuner = vidioc_s_tuner; + vv_data->vid_ops.vidioc_g_frequency = vidioc_g_frequency; + vv_data->vid_ops.vidioc_s_frequency = vidioc_s_frequency; + vv_data->vid_ops.vidioc_enumaudio = vidioc_enumaudio; + vv_data->vid_ops.vidioc_g_audio = vidioc_g_audio; + vv_data->vid_ops.vidioc_s_audio = vidioc_s_audio; + vv_data->vid_ops.vidioc_g_fmt_vbi_cap = NULL; + + vv_data->vbi_ops.vidioc_g_tuner = vidioc_g_tuner; + vv_data->vbi_ops.vidioc_s_tuner = vidioc_s_tuner; + vv_data->vbi_ops.vidioc_g_frequency = vidioc_g_frequency; + vv_data->vbi_ops.vidioc_s_frequency = vidioc_s_frequency; + vv_data->vbi_ops.vidioc_g_fmt_vbi_cap = NULL; + vv_data->vbi_ops.vidioc_g_sliced_vbi_cap = vidioc_g_sliced_vbi_cap; + vv_data->vbi_ops.vidioc_g_fmt_sliced_vbi_out = vidioc_g_fmt_sliced_vbi_out; + vv_data->vbi_ops.vidioc_s_fmt_sliced_vbi_out = vidioc_s_fmt_sliced_vbi_out; + + if (FW_VERSION(av7110->arm_app) < 0x2623) + vv_data->capabilities &= ~V4L2_CAP_SLICED_VBI_OUTPUT; + + if (saa7146_register_device(&av7110->v4l_dev, dev, "av7110", VFL_TYPE_GRABBER)) { + ERR("cannot register capture device. skipping\n"); + saa7146_vv_release(dev); + return -ENODEV; + } + if (FW_VERSION(av7110->arm_app) >= 0x2623) { + if (saa7146_register_device(&av7110->vbi_dev, dev, "av7110", VFL_TYPE_VBI)) + ERR("cannot register vbi v4l2 device. skipping\n"); + } + return 0; +} + +int av7110_exit_v4l(struct av7110 *av7110) +{ + struct saa7146_dev* dev = av7110->dev; + + saa7146_unregister_device(&av7110->v4l_dev, av7110->dev); + saa7146_unregister_device(&av7110->vbi_dev, av7110->dev); + + saa7146_vv_release(dev); + + return 0; +} + + + +/* FIXME: these values are experimental values that look better than the + values from the latest "official" driver -- at least for me... (MiHu) */ +static struct saa7146_standard standard[] = { + { + .name = "PAL", .id = V4L2_STD_PAL_BG, + .v_offset = 0x15, .v_field = 288, + .h_offset = 0x48, .h_pixels = 708, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x10, .v_field = 244, + .h_offset = 0x40, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + } +}; + +static struct saa7146_standard analog_standard[] = { + { + .name = "PAL", .id = V4L2_STD_PAL_BG, + .v_offset = 0x1b, .v_field = 288, + .h_offset = 0x08, .h_pixels = 708, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x10, .v_field = 244, + .h_offset = 0x40, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + } +}; + +static struct saa7146_standard dvb_standard[] = { + { + .name = "PAL", .id = V4L2_STD_PAL_BG, + .v_offset = 0x14, .v_field = 288, + .h_offset = 0x48, .h_pixels = 708, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x10, .v_field = 244, + .h_offset = 0x40, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + } +}; + +static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std) +{ + struct av7110 *av7110 = (struct av7110*) dev->ext_priv; + + if (std->id & V4L2_STD_PAL) { + av7110->vidmode = AV7110_VIDEO_MODE_PAL; + av7110_set_vidmode(av7110, av7110->vidmode); + } + else if (std->id & V4L2_STD_NTSC) { + av7110->vidmode = AV7110_VIDEO_MODE_NTSC; + av7110_set_vidmode(av7110, av7110->vidmode); + } + else + return -1; + + return 0; +} + + +static struct saa7146_ext_vv av7110_vv_data_st = { + .inputs = 1, + .audios = 1, + .capabilities = V4L2_CAP_SLICED_VBI_OUTPUT | V4L2_CAP_AUDIO, + .flags = 0, + + .stds = &standard[0], + .num_stds = ARRAY_SIZE(standard), + .std_callback = &std_callback, + + .vbi_fops.open = av7110_vbi_reset, + .vbi_fops.release = av7110_vbi_reset, + .vbi_fops.write = av7110_vbi_write, +}; + +static struct saa7146_ext_vv av7110_vv_data_c = { + .inputs = 1, + .audios = 1, + .capabilities = V4L2_CAP_TUNER | V4L2_CAP_SLICED_VBI_OUTPUT | V4L2_CAP_AUDIO, + .flags = SAA7146_USE_PORT_B_FOR_VBI, + + .stds = &standard[0], + .num_stds = ARRAY_SIZE(standard), + .std_callback = &std_callback, + + .vbi_fops.open = av7110_vbi_reset, + .vbi_fops.release = av7110_vbi_reset, + .vbi_fops.write = av7110_vbi_write, +}; + diff --git a/drivers/media/dvb/ttpci/budget-av.c b/drivers/media/pci/ttpci/budget-av.c similarity index 100% rename from drivers/media/dvb/ttpci/budget-av.c rename to drivers/media/pci/ttpci/budget-av.c diff --git a/drivers/media/dvb/ttpci/budget-ci.c b/drivers/media/pci/ttpci/budget-ci.c similarity index 100% rename from drivers/media/dvb/ttpci/budget-ci.c rename to drivers/media/pci/ttpci/budget-ci.c diff --git a/drivers/media/dvb/ttpci/budget-core.c b/drivers/media/pci/ttpci/budget-core.c similarity index 100% rename from drivers/media/dvb/ttpci/budget-core.c rename to drivers/media/pci/ttpci/budget-core.c diff --git a/drivers/media/dvb/ttpci/budget-patch.c b/drivers/media/pci/ttpci/budget-patch.c similarity index 100% rename from drivers/media/dvb/ttpci/budget-patch.c rename to drivers/media/pci/ttpci/budget-patch.c diff --git a/drivers/media/pci/ttpci/budget.c b/drivers/media/pci/ttpci/budget.c new file mode 100644 index 000000000000..7e6e43ae5c51 --- /dev/null +++ b/drivers/media/pci/ttpci/budget.c @@ -0,0 +1,871 @@ +/* + * budget.c: driver for the SAA7146 based Budget DVB cards + * + * Compiled from various sources by Michael Hunold + * + * Copyright (C) 2002 Ralph Metzler + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * 26feb2004 Support for FS Activy Card (Grundig tuner) by + * Michael Dreher , + * Oliver Endriss and + * Andreas 'randy' Weinberger + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * the project's page is at http://www.linuxtv.org/ + */ + +#include "budget.h" +#include "stv0299.h" +#include "ves1x93.h" +#include "ves1820.h" +#include "l64781.h" +#include "tda8083.h" +#include "s5h1420.h" +#include "tda10086.h" +#include "tda826x.h" +#include "lnbp21.h" +#include "bsru6.h" +#include "bsbe1.h" +#include "tdhd1.h" +#include "stv6110x.h" +#include "stv090x.h" +#include "isl6423.h" +#include "lnbh24.h" + + +static int diseqc_method; +module_param(diseqc_method, int, 0444); +MODULE_PARM_DESC(diseqc_method, "Select DiSEqC method for subsystem id 13c2:1003, 0: default, 1: more reliable (for newer revisions only)"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static void Set22K (struct budget *budget, int state) +{ + struct saa7146_dev *dev=budget->dev; + dprintk(2, "budget: %p\n", budget); + saa7146_setgpio(dev, 3, (state ? SAA7146_GPIO_OUTHI : SAA7146_GPIO_OUTLO)); +} + +/* Diseqc functions only for TT Budget card */ +/* taken from the Skyvision DVB driver by + Ralph Metzler */ + +static void DiseqcSendBit (struct budget *budget, int data) +{ + struct saa7146_dev *dev=budget->dev; + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + udelay(data ? 500 : 1000); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + udelay(data ? 1000 : 500); +} + +static void DiseqcSendByte (struct budget *budget, int data) +{ + int i, par=1, d; + + dprintk(2, "budget: %p\n", budget); + + for (i=7; i>=0; i--) { + d = (data>>i)&1; + par ^= d; + DiseqcSendBit(budget, d); + } + + DiseqcSendBit(budget, par); +} + +static int SendDiSEqCMsg (struct budget *budget, int len, u8 *msg, unsigned long burst) +{ + struct saa7146_dev *dev=budget->dev; + int i; + + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + mdelay(16); + + for (i=0; idev; + + dprintk(2, "budget: %p\n", budget); + + switch (voltage) { + case SEC_VOLTAGE_13: + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTLO); + break; + case SEC_VOLTAGE_18: + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + break; + case SEC_VOLTAGE_OFF: + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTLO); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int siemens_budget_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + return SetVoltage_Activy (budget, voltage); +} + +static int budget_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + switch (tone) { + case SEC_TONE_ON: + Set22K (budget, 1); + break; + + case SEC_TONE_OFF: + Set22K (budget, 0); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int budget_diseqc_send_master_cmd(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + SendDiSEqCMsg (budget, cmd->msg_len, cmd->msg, 0); + + return 0; +} + +static int budget_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + SendDiSEqCMsg (budget, 0, NULL, minicmd); + + return 0; +} + +static int alps_bsrv2_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget* budget = (struct budget*) fe->dvb->priv; + u8 pwr = 0; + u8 buf[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; + u32 div = (c->frequency + 479500) / 125; + + if (c->frequency > 2000000) + pwr = 3; + else if (c->frequency > 1800000) + pwr = 2; + else if (c->frequency > 1600000) + pwr = 1; + else if (c->frequency > 1200000) + pwr = 0; + else if (c->frequency >= 1100000) + pwr = 1; + else pwr = 2; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = ((div & 0x18000) >> 10) | 0x95; + buf[3] = (pwr << 6) | 0x30; + + // NOTE: since we're using a prescaler of 2, we set the + // divisor frequency to 62.5kHz and divide by 125 above + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct ves1x93_config alps_bsrv2_config = +{ + .demod_address = 0x08, + .xin = 90100000UL, + .invert_pwm = 0, +}; + +static int alps_tdbe2_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget* budget = (struct budget*) fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x62, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (c->frequency + 35937500 + 31250) / 62500; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x85 | ((div >> 10) & 0x60); + data[3] = (c->frequency < 174000000 ? 0x88 : c->frequency < 470000000 ? 0x84 : 0x81); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct ves1820_config alps_tdbe2_config = { + .demod_address = 0x09, + .xin = 57840000UL, + .invert = 1, + .selagc = VES1820_SELAGC_SIGNAMPERR, +}; + +static int grundig_29504_401_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget *budget = fe->dvb->priv; + u8 *tuner_addr = fe->tuner_priv; + u32 div; + u8 cfg, cpump, band_select; + u8 data[4]; + struct i2c_msg msg = { .flags = 0, .buf = data, .len = sizeof(data) }; + + if (tuner_addr) + msg.addr = *tuner_addr; + else + msg.addr = 0x61; + + div = (36125000 + c->frequency) / 166666; + + cfg = 0x88; + + if (c->frequency < 175000000) + cpump = 2; + else if (c->frequency < 390000000) + cpump = 1; + else if (c->frequency < 470000000) + cpump = 2; + else if (c->frequency < 750000000) + cpump = 1; + else + cpump = 3; + + if (c->frequency < 175000000) + band_select = 0x0e; + else if (c->frequency < 470000000) + band_select = 0x05; + else + band_select = 0x03; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = ((div >> 10) & 0x60) | cfg; + data[3] = (cpump << 6) | band_select; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct l64781_config grundig_29504_401_config = { + .demod_address = 0x55, +}; + +static struct l64781_config grundig_29504_401_config_activy = { + .demod_address = 0x54, +}; + +static u8 tuner_address_grundig_29504_401_activy = 0x60; + +static int grundig_29504_451_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget* budget = (struct budget*) fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = c->frequency / 125; + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x8e; + data[3] = 0x00; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct tda8083_config grundig_29504_451_config = { + .demod_address = 0x68, +}; + +static int s5h1420_tuner_set_params(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct budget* budget = (struct budget*) fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = c->frequency / 1000; + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0xc2; + + if (div < 1450) + data[3] = 0x00; + else if (div < 1850) + data[3] = 0x40; + else if (div < 2000) + data[3] = 0x80; + else + data[3] = 0xc0; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + + return 0; +} + +static struct s5h1420_config s5h1420_config = { + .demod_address = 0x53, + .invert = 1, + .cdclk_polarity = 1, +}; + +static struct tda10086_config tda10086_config = { + .demod_address = 0x0e, + .invert = 0, + .diseqc_tone = 1, + .xtal_freq = TDA10086_XTAL_16M, +}; + +static struct stv0299_config alps_bsru6_config_activy = { + .demod_address = 0x68, + .inittab = alps_bsru6_inittab, + .mclk = 88000000UL, + .invert = 1, + .op0_off = 1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsru6_set_symbol_rate, +}; + +static struct stv0299_config alps_bsbe1_config_activy = { + .demod_address = 0x68, + .inittab = alps_bsbe1_inittab, + .mclk = 88000000UL, + .invert = 1, + .op0_off = 1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsbe1_set_symbol_rate, +}; + +static int alps_tdhd1_204_request_firmware(struct dvb_frontend *fe, const struct firmware **fw, char *name) +{ + struct budget *budget = (struct budget *)fe->dvb->priv; + + return request_firmware(fw, name, &budget->dev->pci->dev); +} + + +static int i2c_readreg(struct i2c_adapter *i2c, u8 adr, u8 reg) +{ + u8 val; + struct i2c_msg msg[] = { + { .addr = adr, .flags = 0, .buf = ®, .len = 1 }, + { .addr = adr, .flags = I2C_M_RD, .buf = &val, .len = 1 } + }; + + return (i2c_transfer(i2c, msg, 2) != 2) ? -EIO : val; +} + +static u8 read_pwm(struct budget* budget) +{ + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { { .addr = 0x50,.flags = 0,.buf = &b,.len = 1 }, + { .addr = 0x50,.flags = I2C_M_RD,.buf = &pwm,.len = 1} }; + + if ((i2c_transfer(&budget->i2c_adap, msg, 2) != 2) || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + +static struct stv090x_config tt1600_stv090x_config = { + .device = STV0903, + .demod_mode = STV090x_SINGLE, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 13500000, + .address = 0x68, + + .ts1_mode = STV090x_TSMODE_DVBCI, + .ts2_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, + + .repeater_level = STV090x_RPTLEVEL_16, + + .tuner_init = NULL, + .tuner_sleep = NULL, + .tuner_set_mode = NULL, + .tuner_set_frequency = NULL, + .tuner_get_frequency = NULL, + .tuner_set_bandwidth = NULL, + .tuner_get_bandwidth = NULL, + .tuner_set_bbgain = NULL, + .tuner_get_bbgain = NULL, + .tuner_set_refclk = NULL, + .tuner_get_status = NULL, +}; + +static struct stv6110x_config tt1600_stv6110x_config = { + .addr = 0x60, + .refclk = 27000000, + .clk_div = 2, +}; + +static struct isl6423_config tt1600_isl6423_config = { + .current_max = SEC_CURRENT_515m, + .curlim = SEC_CURRENT_LIM_ON, + .mod_extern = 1, + .addr = 0x08, +}; + +static void frontend_init(struct budget *budget) +{ + (void)alps_bsbe1_config; /* avoid warning */ + + switch(budget->dev->pci->subsystem_device) { + case 0x1003: // Hauppauge/TT Nova budget (stv0299/ALPS BSRU6(tsa5059) OR ves1893/ALPS BSRV2(sp5659)) + case 0x1013: + // try the ALPS BSRV2 first of all + budget->dvb_frontend = dvb_attach(ves1x93_attach, &alps_bsrv2_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsrv2_tuner_set_params; + budget->dvb_frontend->ops.diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops.diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops.set_tone = budget_set_tone; + break; + } + + // try the ALPS BSRU6 now + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + if (budget->dev->pci->subsystem_device == 0x1003 && diseqc_method == 0) { + budget->dvb_frontend->ops.diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops.diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops.set_tone = budget_set_tone; + } + break; + } + break; + + case 0x1004: // Hauppauge/TT DVB-C budget (ves1820/ALPS TDBE2(sp5659)) + + budget->dvb_frontend = dvb_attach(ves1820_attach, &alps_tdbe2_config, &budget->i2c_adap, read_pwm(budget)); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_tdbe2_tuner_set_params; + break; + } + break; + + case 0x1005: // Hauppauge/TT Nova-T budget (L64781/Grundig 29504-401(tsa5060)) + + budget->dvb_frontend = dvb_attach(l64781_attach, &grundig_29504_401_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_401_tuner_set_params; + budget->dvb_frontend->tuner_priv = NULL; + break; + } + break; + + case 0x4f60: /* Fujitsu Siemens Activy Budget-S PCI rev AL (stv0299/tsa5059) */ + { + int subtype = i2c_readreg(&budget->i2c_adap, 0x50, 0x67); + + if (subtype < 0) + break; + /* fixme: find a better way to identify the card */ + if (subtype < 0x36) { + /* assume ALPS BSRU6 */ + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsru6_config_activy, &budget->i2c_adap); + if (budget->dvb_frontend) { + printk(KERN_INFO "budget: tuner ALPS BSRU6 detected\n"); + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; + budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + break; + } + } else { + /* assume ALPS BSBE1 */ + /* reset tuner */ + saa7146_setgpio(budget->dev, 3, SAA7146_GPIO_OUTLO); + msleep(50); + saa7146_setgpio(budget->dev, 3, SAA7146_GPIO_OUTHI); + msleep(250); + budget->dvb_frontend = dvb_attach(stv0299_attach, &alps_bsbe1_config_activy, &budget->i2c_adap); + if (budget->dvb_frontend) { + printk(KERN_INFO "budget: tuner ALPS BSBE1 detected\n"); + budget->dvb_frontend->ops.tuner_ops.set_params = alps_bsbe1_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; + budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + break; + } + } + break; + } + + case 0x4f61: // Fujitsu Siemens Activy Budget-S PCI rev GR (tda8083/Grundig 29504-451(tsa5522)) + budget->dvb_frontend = dvb_attach(tda8083_attach, &grundig_29504_451_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_451_tuner_set_params; + budget->dvb_frontend->ops.set_voltage = siemens_budget_set_voltage; + budget->dvb_frontend->ops.dishnetwork_send_legacy_command = NULL; + } + break; + + case 0x5f60: /* Fujitsu Siemens Activy Budget-T PCI rev AL (tda10046/ALPS TDHD1-204A) */ + budget->dvb_frontend = dvb_attach(tda10046_attach, &alps_tdhd1_204a_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = alps_tdhd1_204a_tuner_set_params; + budget->dvb_frontend->tuner_priv = &budget->i2c_adap; + } + break; + + case 0x5f61: /* Fujitsu Siemens Activy Budget-T PCI rev GR (L64781/Grundig 29504-401(tsa5060)) */ + budget->dvb_frontend = dvb_attach(l64781_attach, &grundig_29504_401_config_activy, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->tuner_priv = &tuner_address_grundig_29504_401_activy; + budget->dvb_frontend->ops.tuner_ops.set_params = grundig_29504_401_tuner_set_params; + } + break; + + case 0x1016: // Hauppauge/TT Nova-S SE (samsung s5h1420/????(tda8260)) + budget->dvb_frontend = dvb_attach(s5h1420_attach, &s5h1420_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops.tuner_ops.set_params = s5h1420_tuner_set_params; + if (dvb_attach(lnbp21_attach, budget->dvb_frontend, &budget->i2c_adap, 0, 0) == NULL) { + printk("%s: No LNBP21 found!\n", __func__); + goto error_out; + } + break; + } + + case 0x1018: // TT Budget-S-1401 (philips tda10086/philips tda8262) + // gpio2 is connected to CLB - reset it + leave it high + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); + msleep(1); + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); + msleep(1); + + budget->dvb_frontend = dvb_attach(tda10086_attach, &tda10086_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + if (dvb_attach(tda826x_attach, budget->dvb_frontend, 0x60, &budget->i2c_adap, 0) == NULL) + printk("%s: No tda826x found!\n", __func__); + if (dvb_attach(lnbp21_attach, budget->dvb_frontend, &budget->i2c_adap, 0, 0) == NULL) { + printk("%s: No LNBP21 found!\n", __func__); + goto error_out; + } + break; + } + + case 0x101c: { /* TT S2-1600 */ + struct stv6110x_devctl *ctl; + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); + msleep(50); + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); + msleep(250); + + budget->dvb_frontend = dvb_attach(stv090x_attach, + &tt1600_stv090x_config, + &budget->i2c_adap, + STV090x_DEMODULATOR_0); + + if (budget->dvb_frontend) { + + ctl = dvb_attach(stv6110x_attach, + budget->dvb_frontend, + &tt1600_stv6110x_config, + &budget->i2c_adap); + + if (ctl) { + tt1600_stv090x_config.tuner_init = ctl->tuner_init; + tt1600_stv090x_config.tuner_sleep = ctl->tuner_sleep; + tt1600_stv090x_config.tuner_set_mode = ctl->tuner_set_mode; + tt1600_stv090x_config.tuner_set_frequency = ctl->tuner_set_frequency; + tt1600_stv090x_config.tuner_get_frequency = ctl->tuner_get_frequency; + tt1600_stv090x_config.tuner_set_bandwidth = ctl->tuner_set_bandwidth; + tt1600_stv090x_config.tuner_get_bandwidth = ctl->tuner_get_bandwidth; + tt1600_stv090x_config.tuner_set_bbgain = ctl->tuner_set_bbgain; + tt1600_stv090x_config.tuner_get_bbgain = ctl->tuner_get_bbgain; + tt1600_stv090x_config.tuner_set_refclk = ctl->tuner_set_refclk; + tt1600_stv090x_config.tuner_get_status = ctl->tuner_get_status; + + /* call the init function once to initialize + tuner's clock output divider and demod's + master clock */ + if (budget->dvb_frontend->ops.init) + budget->dvb_frontend->ops.init(budget->dvb_frontend); + + if (dvb_attach(isl6423_attach, + budget->dvb_frontend, + &budget->i2c_adap, + &tt1600_isl6423_config) == NULL) { + printk(KERN_ERR "%s: No Intersil ISL6423 found!\n", __func__); + goto error_out; + } + } else { + printk(KERN_ERR "%s: No STV6110(A) Silicon Tuner found!\n", __func__); + goto error_out; + } + } + } + break; + + case 0x1020: { /* Omicom S2 */ + struct stv6110x_devctl *ctl; + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTLO); + msleep(50); + saa7146_setgpio(budget->dev, 2, SAA7146_GPIO_OUTHI); + msleep(250); + + budget->dvb_frontend = dvb_attach(stv090x_attach, + &tt1600_stv090x_config, + &budget->i2c_adap, + STV090x_DEMODULATOR_0); + + if (budget->dvb_frontend) { + printk(KERN_INFO "budget: Omicom S2 detected\n"); + + ctl = dvb_attach(stv6110x_attach, + budget->dvb_frontend, + &tt1600_stv6110x_config, + &budget->i2c_adap); + + if (ctl) { + tt1600_stv090x_config.tuner_init = ctl->tuner_init; + tt1600_stv090x_config.tuner_sleep = ctl->tuner_sleep; + tt1600_stv090x_config.tuner_set_mode = ctl->tuner_set_mode; + tt1600_stv090x_config.tuner_set_frequency = ctl->tuner_set_frequency; + tt1600_stv090x_config.tuner_get_frequency = ctl->tuner_get_frequency; + tt1600_stv090x_config.tuner_set_bandwidth = ctl->tuner_set_bandwidth; + tt1600_stv090x_config.tuner_get_bandwidth = ctl->tuner_get_bandwidth; + tt1600_stv090x_config.tuner_set_bbgain = ctl->tuner_set_bbgain; + tt1600_stv090x_config.tuner_get_bbgain = ctl->tuner_get_bbgain; + tt1600_stv090x_config.tuner_set_refclk = ctl->tuner_set_refclk; + tt1600_stv090x_config.tuner_get_status = ctl->tuner_get_status; + + /* call the init function once to initialize + tuner's clock output divider and demod's + master clock */ + if (budget->dvb_frontend->ops.init) + budget->dvb_frontend->ops.init(budget->dvb_frontend); + + if (dvb_attach(lnbh24_attach, + budget->dvb_frontend, + &budget->i2c_adap, + LNBH24_PCL | LNBH24_TTX, + LNBH24_TEN, 0x14>>1) == NULL) { + printk(KERN_ERR + "No LNBH24 found!\n"); + goto error_out; + } + } else { + printk(KERN_ERR "%s: No STV6110(A) Silicon Tuner found!\n", __func__); + goto error_out; + } + } + } + break; + } + + if (budget->dvb_frontend == NULL) { + printk("budget: A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n", + budget->dev->pci->vendor, + budget->dev->pci->device, + budget->dev->pci->subsystem_vendor, + budget->dev->pci->subsystem_device); + } else { + if (dvb_register_frontend(&budget->dvb_adapter, budget->dvb_frontend)) + goto error_out; + } + return; + +error_out: + printk("budget: Frontend registration failed!\n"); + dvb_frontend_detach(budget->dvb_frontend); + budget->dvb_frontend = NULL; + return; +} + +static int budget_attach (struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) +{ + struct budget *budget = NULL; + int err; + + budget = kmalloc(sizeof(struct budget), GFP_KERNEL); + if( NULL == budget ) { + return -ENOMEM; + } + + dprintk(2, "dev:%p, info:%p, budget:%p\n", dev, info, budget); + + dev->ext_priv = budget; + + err = ttpci_budget_init(budget, dev, info, THIS_MODULE, adapter_nr); + if (err) { + printk("==> failed\n"); + kfree (budget); + return err; + } + + budget->dvb_adapter.priv = budget; + frontend_init(budget); + + ttpci_budget_init_hooks(budget); + + return 0; +} + +static int budget_detach (struct saa7146_dev* dev) +{ + struct budget *budget = (struct budget*) dev->ext_priv; + int err; + + if (budget->dvb_frontend) { + dvb_unregister_frontend(budget->dvb_frontend); + dvb_frontend_detach(budget->dvb_frontend); + } + + err = ttpci_budget_deinit (budget); + + kfree (budget); + dev->ext_priv = NULL; + + return err; +} + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(ttbs, "TT-Budget/WinTV-NOVA-S PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbc, "TT-Budget/WinTV-NOVA-C PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbt, "TT-Budget/WinTV-NOVA-T PCI", BUDGET_TT); +MAKE_BUDGET_INFO(satel, "SATELCO Multimedia PCI", BUDGET_TT_HW_DISEQC); +MAKE_BUDGET_INFO(ttbs1401, "TT-Budget-S-1401 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(tt1600, "TT-Budget S2-1600 PCI", BUDGET_TT); +MAKE_BUDGET_INFO(fsacs0, "Fujitsu Siemens Activy Budget-S PCI (rev GR/grundig frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsacs1, "Fujitsu Siemens Activy Budget-S PCI (rev AL/alps frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsact, "Fujitsu Siemens Activy Budget-T PCI (rev GR/Grundig frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsact1, "Fujitsu Siemens Activy Budget-T PCI (rev AL/ALPS TDHD1-204A)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(omicom, "Omicom S2 PCI", BUDGET_TT); + +static struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(ttbs, 0x13c2, 0x1003), + MAKE_EXTENSION_PCI(ttbc, 0x13c2, 0x1004), + MAKE_EXTENSION_PCI(ttbt, 0x13c2, 0x1005), + MAKE_EXTENSION_PCI(satel, 0x13c2, 0x1013), + MAKE_EXTENSION_PCI(ttbs, 0x13c2, 0x1016), + MAKE_EXTENSION_PCI(ttbs1401, 0x13c2, 0x1018), + MAKE_EXTENSION_PCI(tt1600, 0x13c2, 0x101c), + MAKE_EXTENSION_PCI(fsacs1,0x1131, 0x4f60), + MAKE_EXTENSION_PCI(fsacs0,0x1131, 0x4f61), + MAKE_EXTENSION_PCI(fsact1, 0x1131, 0x5f60), + MAKE_EXTENSION_PCI(fsact, 0x1131, 0x5f61), + MAKE_EXTENSION_PCI(omicom, 0x14c4, 0x1020), + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget dvb", + .flags = SAA7146_USE_I2C_IRQ, + + .module = THIS_MODULE, + .pci_tbl = pci_tbl, + .attach = budget_attach, + .detach = budget_detach, + + .irq_mask = MASK_10, + .irq_func = ttpci_budget_irq10_handler, +}; + +static int __init budget_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_init); +module_exit(budget_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, Michael Hunold, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called " + "budget PCI DVB cards by Siemens, Technotrend, Hauppauge"); diff --git a/drivers/media/dvb/ttpci/budget.h b/drivers/media/pci/ttpci/budget.h similarity index 100% rename from drivers/media/dvb/ttpci/budget.h rename to drivers/media/pci/ttpci/budget.h diff --git a/drivers/media/dvb/ttpci/ttpci-eeprom.c b/drivers/media/pci/ttpci/ttpci-eeprom.c similarity index 100% rename from drivers/media/dvb/ttpci/ttpci-eeprom.c rename to drivers/media/pci/ttpci/ttpci-eeprom.c diff --git a/drivers/media/dvb/ttpci/ttpci-eeprom.h b/drivers/media/pci/ttpci/ttpci-eeprom.h similarity index 100% rename from drivers/media/dvb/ttpci/ttpci-eeprom.h rename to drivers/media/pci/ttpci/ttpci-eeprom.h diff --git a/drivers/media/pci/zoran/Kconfig b/drivers/media/pci/zoran/Kconfig new file mode 100644 index 000000000000..26ca8702e33f --- /dev/null +++ b/drivers/media/pci/zoran/Kconfig @@ -0,0 +1,74 @@ +config VIDEO_ZORAN + tristate "Zoran ZR36057/36067 Video For Linux" + depends on PCI && I2C_ALGOBIT && VIDEO_V4L2 && VIRT_TO_BUS + help + Say Y for support for MJPEG capture cards based on the Zoran + 36057/36067 PCI controller chipset. This includes the Iomega + Buz, Pinnacle DC10+ and the Linux Media Labs LML33. There is + a driver homepage at . For + more information, check . + + To compile this driver as a module, choose M here: the + module will be called zr36067. + +config VIDEO_ZORAN_DC30 + tristate "Pinnacle/Miro DC30(+) support" + depends on VIDEO_ZORAN + select VIDEO_ADV7175 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_VPX3220 if MEDIA_SUBDRV_AUTOSELECT + help + Support for the Pinnacle/Miro DC30(+) MJPEG capture/playback + card. This also supports really old DC10 cards based on the + zr36050 MJPEG codec and zr36016 VFE. + +config VIDEO_ZORAN_ZR36060 + tristate "Zoran ZR36060" + depends on VIDEO_ZORAN + help + Say Y to support Zoran boards based on 36060 chips. + This includes Iomega Buz, Pinnacle DC10, Linux media Labs 33 + and 33 R10 and AverMedia 6 boards. + +config VIDEO_ZORAN_BUZ + tristate "Iomega Buz support" + depends on VIDEO_ZORAN_ZR36060 + select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_SAA7185 if MEDIA_SUBDRV_AUTOSELECT + help + Support for the Iomega Buz MJPEG capture/playback card. + +config VIDEO_ZORAN_DC10 + tristate "Pinnacle/Miro DC10(+) support" + depends on VIDEO_ZORAN_ZR36060 + select VIDEO_SAA7110 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_ADV7175 if MEDIA_SUBDRV_AUTOSELECT + help + Support for the Pinnacle/Miro DC10(+) MJPEG capture/playback + card. + +config VIDEO_ZORAN_LML33 + tristate "Linux Media Labs LML33 support" + depends on VIDEO_ZORAN_ZR36060 + select VIDEO_BT819 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_BT856 if MEDIA_SUBDRV_AUTOSELECT + help + Support for the Linux Media Labs LML33 MJPEG capture/playback + card. + +config VIDEO_ZORAN_LML33R10 + tristate "Linux Media Labs LML33R10 support" + depends on VIDEO_ZORAN_ZR36060 + select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_ADV7170 if MEDIA_SUBDRV_AUTOSELECT + help + support for the Linux Media Labs LML33R10 MJPEG capture/playback + card. + +config VIDEO_ZORAN_AVS6EYES + tristate "AverMedia 6 Eyes support" + depends on VIDEO_ZORAN_ZR36060 + select VIDEO_BT856 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_BT866 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_KS0127 if MEDIA_SUBDRV_AUTOSELECT + help + Support for the AverMedia 6 Eyes video surveillance card. diff --git a/drivers/media/video/zoran/Makefile b/drivers/media/pci/zoran/Makefile similarity index 100% rename from drivers/media/video/zoran/Makefile rename to drivers/media/pci/zoran/Makefile diff --git a/drivers/media/video/zoran/videocodec.c b/drivers/media/pci/zoran/videocodec.c similarity index 100% rename from drivers/media/video/zoran/videocodec.c rename to drivers/media/pci/zoran/videocodec.c diff --git a/drivers/media/video/zoran/videocodec.h b/drivers/media/pci/zoran/videocodec.h similarity index 100% rename from drivers/media/video/zoran/videocodec.h rename to drivers/media/pci/zoran/videocodec.h diff --git a/drivers/media/video/zoran/zoran.h b/drivers/media/pci/zoran/zoran.h similarity index 100% rename from drivers/media/video/zoran/zoran.h rename to drivers/media/pci/zoran/zoran.h diff --git a/drivers/media/pci/zoran/zoran_card.c b/drivers/media/pci/zoran/zoran_card.c new file mode 100644 index 000000000000..fffc54b452c8 --- /dev/null +++ b/drivers/media/pci/zoran/zoran_card.c @@ -0,0 +1,1528 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov + * + * Currently maintained by: + * Ronald Bultje + * Laurent Pinchart + * Mailinglist + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "videocodec.h" +#include "zoran.h" +#include "zoran_card.h" +#include "zoran_device.h" +#include "zoran_procfs.h" + +extern const struct zoran_format zoran_formats[]; + +static int card[BUZ_MAX] = { [0 ... (BUZ_MAX-1)] = -1 }; +module_param_array(card, int, NULL, 0444); +MODULE_PARM_DESC(card, "Card type"); + +/* + The video mem address of the video card. + The driver has a little database for some videocards + to determine it from there. If your video card is not in there + you have either to give it to the driver as a parameter + or set in in a VIDIOCSFBUF ioctl + */ + +static unsigned long vidmem; /* default = 0 - Video memory base address */ +module_param(vidmem, ulong, 0444); +MODULE_PARM_DESC(vidmem, "Default video memory base address"); + +/* + Default input and video norm at startup of the driver. +*/ + +static unsigned int default_input; /* default 0 = Composite, 1 = S-Video */ +module_param(default_input, uint, 0444); +MODULE_PARM_DESC(default_input, + "Default input (0=Composite, 1=S-Video, 2=Internal)"); + +static int default_mux = 1; /* 6 Eyes input selection */ +module_param(default_mux, int, 0644); +MODULE_PARM_DESC(default_mux, + "Default 6 Eyes mux setting (Input selection)"); + +static int default_norm; /* default 0 = PAL, 1 = NTSC 2 = SECAM */ +module_param(default_norm, int, 0444); +MODULE_PARM_DESC(default_norm, "Default norm (0=PAL, 1=NTSC, 2=SECAM)"); + +/* /dev/videoN, -1 for autodetect */ +static int video_nr[BUZ_MAX] = { [0 ... (BUZ_MAX-1)] = -1 }; +module_param_array(video_nr, int, NULL, 0444); +MODULE_PARM_DESC(video_nr, "Video device number (-1=Auto)"); + +int v4l_nbufs = 4; +int v4l_bufsize = 864; /* Everybody should be able to work with this setting */ +module_param(v4l_nbufs, int, 0644); +MODULE_PARM_DESC(v4l_nbufs, "Maximum number of V4L buffers to use"); +module_param(v4l_bufsize, int, 0644); +MODULE_PARM_DESC(v4l_bufsize, "Maximum size per V4L buffer (in kB)"); + +int jpg_nbufs = 32; +int jpg_bufsize = 512; /* max size for 100% quality full-PAL frame */ +module_param(jpg_nbufs, int, 0644); +MODULE_PARM_DESC(jpg_nbufs, "Maximum number of JPG buffers to use"); +module_param(jpg_bufsize, int, 0644); +MODULE_PARM_DESC(jpg_bufsize, "Maximum size per JPG buffer (in kB)"); + +int pass_through = 0; /* 1=Pass through TV signal when device is not used */ + /* 0=Show color bar when device is not used (LML33: only if lml33dpath=1) */ +module_param(pass_through, int, 0644); +MODULE_PARM_DESC(pass_through, + "Pass TV signal through to TV-out when idling"); + +int zr36067_debug = 1; +module_param_named(debug, zr36067_debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-5)"); + +#define ZORAN_VERSION "0.10.1" + +MODULE_DESCRIPTION("Zoran-36057/36067 JPEG codec driver"); +MODULE_AUTHOR("Serguei Miridonov"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ZORAN_VERSION); + +#define ZR_DEVICE(subven, subdev, data) { \ + .vendor = PCI_VENDOR_ID_ZORAN, .device = PCI_DEVICE_ID_ZORAN_36057, \ + .subvendor = (subven), .subdevice = (subdev), .driver_data = (data) } + +static struct pci_device_id zr36067_pci_tbl[] = { + ZR_DEVICE(PCI_VENDOR_ID_MIRO, PCI_DEVICE_ID_MIRO_DC10PLUS, DC10plus), + ZR_DEVICE(PCI_VENDOR_ID_MIRO, PCI_DEVICE_ID_MIRO_DC30PLUS, DC30plus), + ZR_DEVICE(PCI_VENDOR_ID_ELECTRONICDESIGNGMBH, PCI_DEVICE_ID_LML_33R10, LML33R10), + ZR_DEVICE(PCI_VENDOR_ID_IOMEGA, PCI_DEVICE_ID_IOMEGA_BUZ, BUZ), + ZR_DEVICE(PCI_ANY_ID, PCI_ANY_ID, NUM_CARDS), + {0} +}; +MODULE_DEVICE_TABLE(pci, zr36067_pci_tbl); + +static unsigned int zoran_num; /* number of cards found */ + +/* videocodec bus functions ZR36060 */ +static u32 +zr36060_read (struct videocodec *codec, + u16 reg) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + __u32 data; + + if (post_office_wait(zr) + || post_office_write(zr, 0, 1, reg >> 8) + || post_office_write(zr, 0, 2, reg & 0xff)) { + return -1; + } + + data = post_office_read(zr, 0, 3) & 0xff; + return data; +} + +static void +zr36060_write (struct videocodec *codec, + u16 reg, + u32 val) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + + if (post_office_wait(zr) + || post_office_write(zr, 0, 1, reg >> 8) + || post_office_write(zr, 0, 2, reg & 0xff)) { + return; + } + + post_office_write(zr, 0, 3, val & 0xff); +} + +/* videocodec bus functions ZR36050 */ +static u32 +zr36050_read (struct videocodec *codec, + u16 reg) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + __u32 data; + + if (post_office_wait(zr) + || post_office_write(zr, 1, 0, reg >> 2)) { // reg. HIGHBYTES + return -1; + } + + data = post_office_read(zr, 0, reg & 0x03) & 0xff; // reg. LOWBYTES + read + return data; +} + +static void +zr36050_write (struct videocodec *codec, + u16 reg, + u32 val) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + + if (post_office_wait(zr) + || post_office_write(zr, 1, 0, reg >> 2)) { // reg. HIGHBYTES + return; + } + + post_office_write(zr, 0, reg & 0x03, val & 0xff); // reg. LOWBYTES + wr. data +} + +/* videocodec bus functions ZR36016 */ +static u32 +zr36016_read (struct videocodec *codec, + u16 reg) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + __u32 data; + + if (post_office_wait(zr)) { + return -1; + } + + data = post_office_read(zr, 2, reg & 0x03) & 0xff; // read + return data; +} + +/* hack for in zoran_device.c */ +void +zr36016_write (struct videocodec *codec, + u16 reg, + u32 val) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + + if (post_office_wait(zr)) { + return; + } + + post_office_write(zr, 2, reg & 0x03, val & 0x0ff); // wr. data +} + +/* + * Board specific information + */ + +static void +dc10_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: %s\n", ZR_DEVNAME(zr), __func__); + + /* Pixel clock selection */ + GPIO(zr, 4, 0); + GPIO(zr, 5, 1); + /* Enable the video bus sync signals */ + GPIO(zr, 7, 0); +} + +static void +dc10plus_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: %s\n", ZR_DEVNAME(zr), __func__); +} + +static void +buz_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: %s\n", ZR_DEVNAME(zr), __func__); + + /* some stuff from Iomega */ + pci_write_config_dword(zr->pci_dev, 0xfc, 0x90680f15); + pci_write_config_dword(zr->pci_dev, 0x0c, 0x00012020); + pci_write_config_dword(zr->pci_dev, 0xe8, 0xc0200000); +} + +static void +lml33_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: %s\n", ZR_DEVNAME(zr), __func__); + + GPIO(zr, 2, 1); // Set Composite input/output +} + +static void +avs6eyes_init (struct zoran *zr) +{ + // AverMedia 6-Eyes original driver by Christer Weinigel + + // Lifted straight from Christer's old driver and + // modified slightly by Martin Samuelsson. + + int mux = default_mux; /* 1 = BT866, 7 = VID1 */ + + GPIO(zr, 4, 1); /* Bt866 SLEEP on */ + udelay(2); + + GPIO(zr, 0, 1); /* ZR36060 /RESET on */ + GPIO(zr, 1, 0); /* ZR36060 /SLEEP on */ + GPIO(zr, 2, mux & 1); /* MUX S0 */ + GPIO(zr, 3, 0); /* /FRAME on */ + GPIO(zr, 4, 0); /* Bt866 SLEEP off */ + GPIO(zr, 5, mux & 2); /* MUX S1 */ + GPIO(zr, 6, 0); /* ? */ + GPIO(zr, 7, mux & 4); /* MUX S2 */ + +} + +static char * +codecid_to_modulename (u16 codecid) +{ + char *name = NULL; + + switch (codecid) { + case CODEC_TYPE_ZR36060: + name = "zr36060"; + break; + case CODEC_TYPE_ZR36050: + name = "zr36050"; + break; + case CODEC_TYPE_ZR36016: + name = "zr36016"; + break; + } + + return name; +} + +// struct tvnorm { +// u16 Wt, Wa, HStart, HSyncStart, Ht, Ha, VStart; +// }; + +static struct tvnorm f50sqpixel = { 944, 768, 83, 880, 625, 576, 16 }; +static struct tvnorm f60sqpixel = { 780, 640, 51, 716, 525, 480, 12 }; +static struct tvnorm f50ccir601 = { 864, 720, 75, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601 = { 858, 720, 57, 788, 525, 480, 16 }; + +static struct tvnorm f50ccir601_lml33 = { 864, 720, 75+34, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601_lml33 = { 858, 720, 57+34, 788, 525, 480, 16 }; + +/* The DC10 (57/16/50) uses VActive as HSync, so HStart must be 0 */ +static struct tvnorm f50sqpixel_dc10 = { 944, 768, 0, 880, 625, 576, 0 }; +static struct tvnorm f60sqpixel_dc10 = { 780, 640, 0, 716, 525, 480, 12 }; + +/* FIXME: I cannot swap U and V in saa7114, so i do one + * pixel left shift in zoran (75 -> 74) + * (Maxim Yevtyushkin ) */ +static struct tvnorm f50ccir601_lm33r10 = { 864, 720, 74+54, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601_lm33r10 = { 858, 720, 56+54, 788, 525, 480, 16 }; + +/* FIXME: The ks0127 seem incapable of swapping U and V, too, which is why I + * copy Maxim's left shift hack for the 6 Eyes. + * + * Christer's driver used the unshifted norms, though... + * /Sam */ +static struct tvnorm f50ccir601_avs6eyes = { 864, 720, 74, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601_avs6eyes = { 858, 720, 56, 788, 525, 480, 16 }; + +static const unsigned short vpx3220_addrs[] = { 0x43, 0x47, I2C_CLIENT_END }; +static const unsigned short saa7110_addrs[] = { 0x4e, 0x4f, I2C_CLIENT_END }; +static const unsigned short saa7111_addrs[] = { 0x25, 0x24, I2C_CLIENT_END }; +static const unsigned short saa7114_addrs[] = { 0x21, 0x20, I2C_CLIENT_END }; +static const unsigned short adv717x_addrs[] = { 0x6a, 0x6b, 0x2a, 0x2b, I2C_CLIENT_END }; +static const unsigned short ks0127_addrs[] = { 0x6c, 0x6d, I2C_CLIENT_END }; +static const unsigned short saa7185_addrs[] = { 0x44, I2C_CLIENT_END }; +static const unsigned short bt819_addrs[] = { 0x45, I2C_CLIENT_END }; +static const unsigned short bt856_addrs[] = { 0x44, I2C_CLIENT_END }; +static const unsigned short bt866_addrs[] = { 0x44, I2C_CLIENT_END }; + +static struct card_info zoran_cards[NUM_CARDS] __devinitdata = { + { + .type = DC10_old, + .name = "DC10(old)", + .i2c_decoder = "vpx3220a", + .addrs_decoder = vpx3220_addrs, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC|V4L2_STD_PAL|V4L2_STD_SECAM, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10_init, + }, { + .type = DC10_new, + .name = "DC10(new)", + .i2c_decoder = "saa7110", + .addrs_decoder = saa7110_addrs, + .i2c_encoder = "adv7175", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 3, + .input = { + { 0, "Composite" }, + { 7, "S-Video" }, + { 5, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC|V4L2_STD_PAL|V4L2_STD_SECAM, + .tvn = { + &f50sqpixel, + &f60sqpixel, + &f50sqpixel}, + .jpeg_int = ZR36057_ISR_GIRQ0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 3, 0, 6, 1, 2, -1, 4, 5 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { -1, 1}, + .vfe_pol = { 1, 1, 1, 1, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10plus_init, + }, { + .type = DC10plus, + .name = "DC10plus", + .i2c_decoder = "saa7110", + .addrs_decoder = saa7110_addrs, + .i2c_encoder = "adv7175", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 3, + .input = { + { 0, "Composite" }, + { 7, "S-Video" }, + { 5, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC|V4L2_STD_PAL|V4L2_STD_SECAM, + .tvn = { + &f50sqpixel, + &f60sqpixel, + &f50sqpixel + }, + .jpeg_int = ZR36057_ISR_GIRQ0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 3, 0, 6, 1, 2, -1, 4, 5 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { -1, 1 }, + .vfe_pol = { 1, 1, 1, 1, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10plus_init, + }, { + .type = DC30, + .name = "DC30", + .i2c_decoder = "vpx3220a", + .addrs_decoder = vpx3220_addrs, + .i2c_encoder = "adv7175", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC|V4L2_STD_PAL|V4L2_STD_SECAM, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10_init, + }, { + .type = DC30plus, + .name = "DC30plus", + .i2c_decoder = "vpx3220a", + .addrs_decoder = vpx3220_addrs, + .i2c_encoder = "adv7175", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = V4L2_STD_NTSC|V4L2_STD_PAL|V4L2_STD_SECAM, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .input_mux = 0, + .init = &dc10_init, + }, { + .type = LML33, + .name = "LML33", + .i2c_decoder = "bt819a", + .addrs_decoder = bt819_addrs, + .i2c_encoder = "bt856", + .addrs_encoder = bt856_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 0, "Composite" }, + { 7, "S-Video" } + }, + .norms = V4L2_STD_NTSC|V4L2_STD_PAL, + .tvn = { + &f50ccir601_lml33, + &f60ccir601_lml33, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, 5, 7, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 1, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .input_mux = 0, + .init = &lml33_init, + }, { + .type = LML33R10, + .name = "LML33R10", + .i2c_decoder = "saa7114", + .addrs_decoder = saa7114_addrs, + .i2c_encoder = "adv7170", + .addrs_encoder = adv717x_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 0, "Composite" }, + { 7, "S-Video" } + }, + .norms = V4L2_STD_NTSC|V4L2_STD_PAL, + .tvn = { + &f50ccir601_lm33r10, + &f60ccir601_lm33r10, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, 5, 7, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 1, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .input_mux = 0, + .init = &lml33_init, + }, { + .type = BUZ, + .name = "Buz", + .i2c_decoder = "saa7111", + .addrs_decoder = saa7111_addrs, + .i2c_encoder = "saa7185", + .addrs_encoder = saa7185_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 3, "Composite" }, + { 7, "S-Video" } + }, + .norms = V4L2_STD_NTSC|V4L2_STD_PAL|V4L2_STD_SECAM, + .tvn = { + &f50ccir601, + &f60ccir601, + &f50ccir601 + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, -1, -1, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .input_mux = 0, + .init = &buz_init, + }, { + .type = AVS6EYES, + .name = "6-Eyes", + /* AverMedia chose not to brand the 6-Eyes. Thus it + can't be autodetected, and requires card=x. */ + .i2c_decoder = "ks0127", + .addrs_decoder = ks0127_addrs, + .i2c_encoder = "bt866", + .addrs_encoder = bt866_addrs, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 10, + .input = { + { 0, "Composite 1" }, + { 1, "Composite 2" }, + { 2, "Composite 3" }, + { 4, "Composite 4" }, + { 5, "Composite 5" }, + { 6, "Composite 6" }, + { 8, "S-Video 1" }, + { 9, "S-Video 2" }, + {10, "S-Video 3" }, + {15, "YCbCr" } + }, + .norms = V4L2_STD_NTSC|V4L2_STD_PAL, + .tvn = { + &f50ccir601_avs6eyes, + &f60ccir601_avs6eyes, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, 0, 3, -1, -1, -1, -1, -1 },// Validity unknown /Sam + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, // Validity unknown /Sam + .gpcs = { 3, 1 }, // Validity unknown /Sam + .vfe_pol = { 1, 0, 0, 0, 0, 1, 0, 0 }, // Validity unknown /Sam + .gws_not_connected = 1, + .input_mux = 1, + .init = &avs6eyes_init, + } + +}; + +/* + * I2C functions + */ +/* software I2C functions */ +static int +zoran_i2c_getsda (void *data) +{ + struct zoran *zr = (struct zoran *) data; + + return (btread(ZR36057_I2CBR) >> 1) & 1; +} + +static int +zoran_i2c_getscl (void *data) +{ + struct zoran *zr = (struct zoran *) data; + + return btread(ZR36057_I2CBR) & 1; +} + +static void +zoran_i2c_setsda (void *data, + int state) +{ + struct zoran *zr = (struct zoran *) data; + + if (state) + zr->i2cbr |= 2; + else + zr->i2cbr &= ~2; + btwrite(zr->i2cbr, ZR36057_I2CBR); +} + +static void +zoran_i2c_setscl (void *data, + int state) +{ + struct zoran *zr = (struct zoran *) data; + + if (state) + zr->i2cbr |= 1; + else + zr->i2cbr &= ~1; + btwrite(zr->i2cbr, ZR36057_I2CBR); +} + +static const struct i2c_algo_bit_data zoran_i2c_bit_data_template = { + .setsda = zoran_i2c_setsda, + .setscl = zoran_i2c_setscl, + .getsda = zoran_i2c_getsda, + .getscl = zoran_i2c_getscl, + .udelay = 10, + .timeout = 100, +}; + +static int +zoran_register_i2c (struct zoran *zr) +{ + memcpy(&zr->i2c_algo, &zoran_i2c_bit_data_template, + sizeof(struct i2c_algo_bit_data)); + zr->i2c_algo.data = zr; + strlcpy(zr->i2c_adapter.name, ZR_DEVNAME(zr), + sizeof(zr->i2c_adapter.name)); + i2c_set_adapdata(&zr->i2c_adapter, &zr->v4l2_dev); + zr->i2c_adapter.algo_data = &zr->i2c_algo; + zr->i2c_adapter.dev.parent = &zr->pci_dev->dev; + return i2c_bit_add_bus(&zr->i2c_adapter); +} + +static void +zoran_unregister_i2c (struct zoran *zr) +{ + i2c_del_adapter(&zr->i2c_adapter); +} + +/* Check a zoran_params struct for correctness, insert default params */ + +int +zoran_check_jpg_settings (struct zoran *zr, + struct zoran_jpg_settings *settings, + int try) +{ + int err = 0, err0 = 0; + + dprintk(4, + KERN_DEBUG + "%s: %s - dec: %d, Hdcm: %d, Vdcm: %d, Tdcm: %d\n", + ZR_DEVNAME(zr), __func__, settings->decimation, settings->HorDcm, + settings->VerDcm, settings->TmpDcm); + dprintk(4, + KERN_DEBUG + "%s: %s - x: %d, y: %d, w: %d, y: %d\n", + ZR_DEVNAME(zr), __func__, settings->img_x, settings->img_y, + settings->img_width, settings->img_height); + /* Check decimation, set default values for decimation = 1, 2, 4 */ + switch (settings->decimation) { + case 1: + + settings->HorDcm = 1; + settings->VerDcm = 1; + settings->TmpDcm = 1; + settings->field_per_buff = 2; + settings->img_x = 0; + settings->img_y = 0; + settings->img_width = BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 2: + + settings->HorDcm = 2; + settings->VerDcm = 1; + settings->TmpDcm = 2; + settings->field_per_buff = 1; + settings->img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings->img_y = 0; + settings->img_width = + (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 4: + + if (zr->card.type == DC10_new) { + dprintk(1, + KERN_DEBUG + "%s: %s - HDec by 4 is not supported on the DC10\n", + ZR_DEVNAME(zr), __func__); + err0++; + break; + } + + settings->HorDcm = 4; + settings->VerDcm = 2; + settings->TmpDcm = 2; + settings->field_per_buff = 1; + settings->img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings->img_y = 0; + settings->img_width = + (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 0: + + /* We have to check the data the user has set */ + + if (settings->HorDcm != 1 && settings->HorDcm != 2 && + (zr->card.type == DC10_new || settings->HorDcm != 4)) { + settings->HorDcm = clamp(settings->HorDcm, 1, 2); + err0++; + } + if (settings->VerDcm != 1 && settings->VerDcm != 2) { + settings->VerDcm = clamp(settings->VerDcm, 1, 2); + err0++; + } + if (settings->TmpDcm != 1 && settings->TmpDcm != 2) { + settings->TmpDcm = clamp(settings->TmpDcm, 1, 2); + err0++; + } + if (settings->field_per_buff != 1 && + settings->field_per_buff != 2) { + settings->field_per_buff = clamp(settings->field_per_buff, 1, 2); + err0++; + } + if (settings->img_x < 0) { + settings->img_x = 0; + err0++; + } + if (settings->img_y < 0) { + settings->img_y = 0; + err0++; + } + if (settings->img_width < 0 || settings->img_width > BUZ_MAX_WIDTH) { + settings->img_width = clamp(settings->img_width, 0, (int)BUZ_MAX_WIDTH); + err0++; + } + if (settings->img_height < 0 || settings->img_height > BUZ_MAX_HEIGHT / 2) { + settings->img_height = clamp(settings->img_height, 0, BUZ_MAX_HEIGHT / 2); + err0++; + } + if (settings->img_x + settings->img_width > BUZ_MAX_WIDTH) { + settings->img_x = BUZ_MAX_WIDTH - settings->img_width; + err0++; + } + if (settings->img_y + settings->img_height > BUZ_MAX_HEIGHT / 2) { + settings->img_y = BUZ_MAX_HEIGHT / 2 - settings->img_height; + err0++; + } + if (settings->img_width % (16 * settings->HorDcm) != 0) { + settings->img_width -= settings->img_width % (16 * settings->HorDcm); + if (settings->img_width == 0) + settings->img_width = 16 * settings->HorDcm; + err0++; + } + if (settings->img_height % (8 * settings->VerDcm) != 0) { + settings->img_height -= settings->img_height % (8 * settings->VerDcm); + if (settings->img_height == 0) + settings->img_height = 8 * settings->VerDcm; + err0++; + } + + if (!try && err0) { + dprintk(1, + KERN_ERR + "%s: %s - error in params for decimation = 0\n", + ZR_DEVNAME(zr), __func__); + err++; + } + break; + default: + dprintk(1, + KERN_ERR + "%s: %s - decimation = %d, must be 0, 1, 2 or 4\n", + ZR_DEVNAME(zr), __func__, settings->decimation); + err++; + break; + } + + if (settings->jpg_comp.quality > 100) + settings->jpg_comp.quality = 100; + if (settings->jpg_comp.quality < 5) + settings->jpg_comp.quality = 5; + if (settings->jpg_comp.APPn < 0) + settings->jpg_comp.APPn = 0; + if (settings->jpg_comp.APPn > 15) + settings->jpg_comp.APPn = 15; + if (settings->jpg_comp.APP_len < 0) + settings->jpg_comp.APP_len = 0; + if (settings->jpg_comp.APP_len > 60) + settings->jpg_comp.APP_len = 60; + if (settings->jpg_comp.COM_len < 0) + settings->jpg_comp.COM_len = 0; + if (settings->jpg_comp.COM_len > 60) + settings->jpg_comp.COM_len = 60; + if (err) + return -EINVAL; + return 0; +} + +void +zoran_open_init_params (struct zoran *zr) +{ + int i; + + /* User must explicitly set a window */ + zr->overlay_settings.is_set = 0; + zr->overlay_mask = NULL; + zr->overlay_active = ZORAN_FREE; + + zr->v4l_memgrab_active = 0; + zr->v4l_overlay_active = 0; + zr->v4l_grab_frame = NO_GRAB_ACTIVE; + zr->v4l_grab_seq = 0; + zr->v4l_settings.width = 192; + zr->v4l_settings.height = 144; + zr->v4l_settings.format = &zoran_formats[7]; /* YUY2 - YUV-4:2:2 packed */ + zr->v4l_settings.bytesperline = + zr->v4l_settings.width * + ((zr->v4l_settings.format->depth + 7) / 8); + + /* DMA ring stuff for V4L */ + zr->v4l_pend_tail = 0; + zr->v4l_pend_head = 0; + zr->v4l_sync_tail = 0; + zr->v4l_buffers.active = ZORAN_FREE; + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + zr->v4l_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + } + zr->v4l_buffers.allocated = 0; + + for (i = 0; i < BUZ_MAX_FRAME; i++) { + zr->jpg_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + } + zr->jpg_buffers.active = ZORAN_FREE; + zr->jpg_buffers.allocated = 0; + /* Set necessary params and call zoran_check_jpg_settings to set the defaults */ + zr->jpg_settings.decimation = 1; + zr->jpg_settings.jpg_comp.quality = 50; /* default compression factor 8 */ + if (zr->card.type != BUZ) + zr->jpg_settings.odd_even = 1; + else + zr->jpg_settings.odd_even = 0; + zr->jpg_settings.jpg_comp.APPn = 0; + zr->jpg_settings.jpg_comp.APP_len = 0; /* No APPn marker */ + memset(zr->jpg_settings.jpg_comp.APP_data, 0, + sizeof(zr->jpg_settings.jpg_comp.APP_data)); + zr->jpg_settings.jpg_comp.COM_len = 0; /* No COM marker */ + memset(zr->jpg_settings.jpg_comp.COM_data, 0, + sizeof(zr->jpg_settings.jpg_comp.COM_data)); + zr->jpg_settings.jpg_comp.jpeg_markers = + V4L2_JPEG_MARKER_DHT | V4L2_JPEG_MARKER_DQT; + i = zoran_check_jpg_settings(zr, &zr->jpg_settings, 0); + if (i) + dprintk(1, KERN_ERR "%s: %s internal error\n", + ZR_DEVNAME(zr), __func__); + + clear_interrupt_counters(zr); + zr->testing = 0; +} + +static void __devinit +test_interrupts (struct zoran *zr) +{ + DEFINE_WAIT(wait); + int timeout, icr; + + clear_interrupt_counters(zr); + + zr->testing = 1; + icr = btread(ZR36057_ICR); + btwrite(0x78000000 | ZR36057_ICR_IntPinEn, ZR36057_ICR); + prepare_to_wait(&zr->test_q, &wait, TASK_INTERRUPTIBLE); + timeout = schedule_timeout(HZ); + finish_wait(&zr->test_q, &wait); + btwrite(0, ZR36057_ICR); + btwrite(0x78000000, ZR36057_ISR); + zr->testing = 0; + dprintk(5, KERN_INFO "%s: Testing interrupts...\n", ZR_DEVNAME(zr)); + if (timeout) { + dprintk(1, ": time spent: %d\n", 1 * HZ - timeout); + } + if (zr36067_debug > 1) + print_interrupts(zr); + btwrite(icr, ZR36057_ICR); +} + +static int __devinit +zr36057_init (struct zoran *zr) +{ + int j, err; + + dprintk(1, + KERN_INFO + "%s: %s - initializing card[%d], zr=%p\n", + ZR_DEVNAME(zr), __func__, zr->id, zr); + + /* default setup of all parameters which will persist between opens */ + zr->user = 0; + + init_waitqueue_head(&zr->v4l_capq); + init_waitqueue_head(&zr->jpg_capq); + init_waitqueue_head(&zr->test_q); + zr->jpg_buffers.allocated = 0; + zr->v4l_buffers.allocated = 0; + + zr->vbuf_base = (void *) vidmem; + zr->vbuf_width = 0; + zr->vbuf_height = 0; + zr->vbuf_depth = 0; + zr->vbuf_bytesperline = 0; + + /* Avoid nonsense settings from user for default input/norm */ + if (default_norm < 0 || default_norm > 2) + default_norm = 0; + if (default_norm == 0) { + zr->norm = V4L2_STD_PAL; + zr->timing = zr->card.tvn[0]; + } else if (default_norm == 1) { + zr->norm = V4L2_STD_NTSC; + zr->timing = zr->card.tvn[1]; + } else { + zr->norm = V4L2_STD_SECAM; + zr->timing = zr->card.tvn[2]; + } + if (zr->timing == NULL) { + dprintk(1, + KERN_WARNING + "%s: %s - default TV standard not supported by hardware. PAL will be used.\n", + ZR_DEVNAME(zr), __func__); + zr->norm = V4L2_STD_PAL; + zr->timing = zr->card.tvn[0]; + } + + if (default_input > zr->card.inputs-1) { + dprintk(1, + KERN_WARNING + "%s: default_input value %d out of range (0-%d)\n", + ZR_DEVNAME(zr), default_input, zr->card.inputs-1); + default_input = 0; + } + zr->input = default_input; + + /* default setup (will be repeated at every open) */ + zoran_open_init_params(zr); + + /* allocate memory *before* doing anything to the hardware + * in case allocation fails */ + zr->stat_com = kzalloc(BUZ_NUM_STAT_COM * 4, GFP_KERNEL); + zr->video_dev = video_device_alloc(); + if (!zr->stat_com || !zr->video_dev) { + dprintk(1, + KERN_ERR + "%s: %s - kmalloc (STAT_COM) failed\n", + ZR_DEVNAME(zr), __func__); + err = -ENOMEM; + goto exit_free; + } + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + zr->stat_com[j] = cpu_to_le32(1); /* mark as unavailable to zr36057 */ + } + + /* + * Now add the template and register the device unit. + */ + memcpy(zr->video_dev, &zoran_template, sizeof(zoran_template)); + zr->video_dev->parent = &zr->pci_dev->dev; + strcpy(zr->video_dev->name, ZR_DEVNAME(zr)); + /* It's not a mem2mem device, but you can both capture and output from + one and the same device. This should really be split up into two + device nodes, but that's a job for another day. */ + zr->video_dev->vfl_dir = VFL_DIR_M2M; + err = video_register_device(zr->video_dev, VFL_TYPE_GRABBER, video_nr[zr->id]); + if (err < 0) + goto exit_free; + video_set_drvdata(zr->video_dev, zr); + + zoran_init_hardware(zr); + if (zr36067_debug > 2) + detect_guest_activity(zr); + test_interrupts(zr); + if (!pass_through) { + decoder_call(zr, video, s_stream, 0); + encoder_call(zr, video, s_routing, 2, 0, 0); + } + + zr->zoran_proc = NULL; + zr->initialized = 1; + return 0; + +exit_free: + kfree(zr->stat_com); + kfree(zr->video_dev); + return err; +} + +static void __devexit zoran_remove(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); + struct zoran *zr = to_zoran(v4l2_dev); + + if (!zr->initialized) + goto exit_free; + + /* unregister videocodec bus */ + if (zr->codec) { + struct videocodec_master *master = zr->codec->master_data; + + videocodec_detach(zr->codec); + kfree(master); + } + if (zr->vfe) { + struct videocodec_master *master = zr->vfe->master_data; + + videocodec_detach(zr->vfe); + kfree(master); + } + + /* unregister i2c bus */ + zoran_unregister_i2c(zr); + /* disable PCI bus-mastering */ + zoran_set_pci_master(zr, 0); + /* put chip into reset */ + btwrite(0, ZR36057_SPGPPCR); + free_irq(zr->pci_dev->irq, zr); + /* unmap and free memory */ + kfree(zr->stat_com); + zoran_proc_cleanup(zr); + iounmap(zr->zr36057_mem); + pci_disable_device(zr->pci_dev); + video_unregister_device(zr->video_dev); +exit_free: + v4l2_device_unregister(&zr->v4l2_dev); + kfree(zr); +} + +void +zoran_vdev_release (struct video_device *vdev) +{ + kfree(vdev); +} + +static struct videocodec_master * __devinit +zoran_setup_videocodec (struct zoran *zr, + int type) +{ + struct videocodec_master *m = NULL; + + m = kmalloc(sizeof(struct videocodec_master), GFP_KERNEL); + if (!m) { + dprintk(1, KERN_ERR "%s: %s - no memory\n", + ZR_DEVNAME(zr), __func__); + return m; + } + + /* magic and type are unused for master struct. Makes sense only at + codec structs. + In the past, .type were initialized to the old V4L1 .hardware + value, as VID_HARDWARE_ZR36067 + */ + m->magic = 0L; + m->type = 0; + + m->flags = CODEC_FLAG_ENCODER | CODEC_FLAG_DECODER; + strlcpy(m->name, ZR_DEVNAME(zr), sizeof(m->name)); + m->data = zr; + + switch (type) + { + case CODEC_TYPE_ZR36060: + m->readreg = zr36060_read; + m->writereg = zr36060_write; + m->flags |= CODEC_FLAG_JPEG | CODEC_FLAG_VFE; + break; + case CODEC_TYPE_ZR36050: + m->readreg = zr36050_read; + m->writereg = zr36050_write; + m->flags |= CODEC_FLAG_JPEG; + break; + case CODEC_TYPE_ZR36016: + m->readreg = zr36016_read; + m->writereg = zr36016_write; + m->flags |= CODEC_FLAG_VFE; + break; + } + + return m; +} + +static void zoran_subdev_notify(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + struct zoran *zr = to_zoran(sd->v4l2_dev); + + /* Bt819 needs to reset its FIFO buffer using #FRST pin and + LML33 card uses GPIO(7) for that. */ + if (cmd == BT819_FIFO_RESET_LOW) + GPIO(zr, 7, 0); + else if (cmd == BT819_FIFO_RESET_HIGH) + GPIO(zr, 7, 1); +} + +/* + * Scan for a Buz card (actually for the PCI controller ZR36057), + * request the irq and map the io memory + */ +static int __devinit zoran_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + unsigned char latency, need_latency; + struct zoran *zr; + int result; + struct videocodec_master *master_vfe = NULL; + struct videocodec_master *master_codec = NULL; + int card_num; + char *codec_name, *vfe_name; + unsigned int nr; + + + nr = zoran_num++; + if (nr >= BUZ_MAX) { + dprintk(1, KERN_ERR "%s: driver limited to %d card(s) maximum\n", + ZORAN_NAME, BUZ_MAX); + return -ENOENT; + } + + zr = kzalloc(sizeof(struct zoran), GFP_KERNEL); + if (!zr) { + dprintk(1, KERN_ERR "%s: %s - kzalloc failed\n", + ZORAN_NAME, __func__); + return -ENOMEM; + } + zr->v4l2_dev.notify = zoran_subdev_notify; + if (v4l2_device_register(&pdev->dev, &zr->v4l2_dev)) + goto zr_free_mem; + zr->pci_dev = pdev; + zr->id = nr; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), "MJPEG[%u]", zr->id); + spin_lock_init(&zr->spinlock); + mutex_init(&zr->resource_lock); + mutex_init(&zr->other_lock); + if (pci_enable_device(pdev)) + goto zr_unreg; + zr->revision = zr->pci_dev->revision; + + dprintk(1, + KERN_INFO + "%s: Zoran ZR360%c7 (rev %d), irq: %d, memory: 0x%08llx\n", + ZR_DEVNAME(zr), zr->revision < 2 ? '5' : '6', zr->revision, + zr->pci_dev->irq, (uint64_t)pci_resource_start(zr->pci_dev, 0)); + if (zr->revision >= 2) { + dprintk(1, + KERN_INFO + "%s: Subsystem vendor=0x%04x id=0x%04x\n", + ZR_DEVNAME(zr), zr->pci_dev->subsystem_vendor, + zr->pci_dev->subsystem_device); + } + + /* Use auto-detected card type? */ + if (card[nr] == -1) { + if (zr->revision < 2) { + dprintk(1, + KERN_ERR + "%s: No card type specified, please use the card=X module parameter\n", + ZR_DEVNAME(zr)); + dprintk(1, + KERN_ERR + "%s: It is not possible to auto-detect ZR36057 based cards\n", + ZR_DEVNAME(zr)); + goto zr_unreg; + } + + card_num = ent->driver_data; + if (card_num >= NUM_CARDS) { + dprintk(1, + KERN_ERR + "%s: Unknown card, try specifying card=X module parameter\n", + ZR_DEVNAME(zr)); + goto zr_unreg; + } + dprintk(3, + KERN_DEBUG + "%s: %s() - card %s detected\n", + ZR_DEVNAME(zr), __func__, zoran_cards[card_num].name); + } else { + card_num = card[nr]; + if (card_num >= NUM_CARDS || card_num < 0) { + dprintk(1, + KERN_ERR + "%s: User specified card type %d out of range (0 .. %d)\n", + ZR_DEVNAME(zr), card_num, NUM_CARDS - 1); + goto zr_unreg; + } + } + + /* even though we make this a non pointer and thus + * theoretically allow for making changes to this struct + * on a per-individual card basis at runtime, this is + * strongly discouraged. This structure is intended to + * keep general card information, no settings or anything */ + zr->card = zoran_cards[card_num]; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), + "%s[%u]", zr->card.name, zr->id); + + zr->zr36057_mem = pci_ioremap_bar(zr->pci_dev, 0); + if (!zr->zr36057_mem) { + dprintk(1, KERN_ERR "%s: %s() - ioremap failed\n", + ZR_DEVNAME(zr), __func__); + goto zr_unreg; + } + + result = request_irq(zr->pci_dev->irq, zoran_irq, + IRQF_SHARED | IRQF_DISABLED, ZR_DEVNAME(zr), zr); + if (result < 0) { + if (result == -EINVAL) { + dprintk(1, + KERN_ERR + "%s: %s - bad irq number or handler\n", + ZR_DEVNAME(zr), __func__); + } else if (result == -EBUSY) { + dprintk(1, + KERN_ERR + "%s: %s - IRQ %d busy, change your PnP config in BIOS\n", + ZR_DEVNAME(zr), __func__, zr->pci_dev->irq); + } else { + dprintk(1, + KERN_ERR + "%s: %s - can't assign irq, error code %d\n", + ZR_DEVNAME(zr), __func__, result); + } + goto zr_unmap; + } + + /* set PCI latency timer */ + pci_read_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, + &latency); + need_latency = zr->revision > 1 ? 32 : 48; + if (latency != need_latency) { + dprintk(2, KERN_INFO "%s: Changing PCI latency from %d to %d\n", + ZR_DEVNAME(zr), latency, need_latency); + pci_write_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, + need_latency); + } + + zr36057_restart(zr); + /* i2c */ + dprintk(2, KERN_INFO "%s: Initializing i2c bus...\n", + ZR_DEVNAME(zr)); + + if (zoran_register_i2c(zr) < 0) { + dprintk(1, KERN_ERR "%s: %s - can't initialize i2c bus\n", + ZR_DEVNAME(zr), __func__); + goto zr_free_irq; + } + + zr->decoder = v4l2_i2c_new_subdev(&zr->v4l2_dev, + &zr->i2c_adapter, zr->card.i2c_decoder, + 0, zr->card.addrs_decoder); + + if (zr->card.i2c_encoder) + zr->encoder = v4l2_i2c_new_subdev(&zr->v4l2_dev, + &zr->i2c_adapter, zr->card.i2c_encoder, + 0, zr->card.addrs_encoder); + + dprintk(2, + KERN_INFO "%s: Initializing videocodec bus...\n", + ZR_DEVNAME(zr)); + + if (zr->card.video_codec) { + codec_name = codecid_to_modulename(zr->card.video_codec); + if (codec_name) { + result = request_module(codec_name); + if (result) { + dprintk(1, + KERN_ERR + "%s: failed to load modules %s: %d\n", + ZR_DEVNAME(zr), codec_name, result); + } + } + } + if (zr->card.video_vfe) { + vfe_name = codecid_to_modulename(zr->card.video_vfe); + if (vfe_name) { + result = request_module(vfe_name); + if (result < 0) { + dprintk(1, + KERN_ERR + "%s: failed to load modules %s: %d\n", + ZR_DEVNAME(zr), vfe_name, result); + } + } + } + + /* reset JPEG codec */ + jpeg_codec_sleep(zr, 1); + jpeg_codec_reset(zr); + /* video bus enabled */ + /* display codec revision */ + if (zr->card.video_codec != 0) { + master_codec = zoran_setup_videocodec(zr, zr->card.video_codec); + if (!master_codec) + goto zr_unreg_i2c; + zr->codec = videocodec_attach(master_codec); + if (!zr->codec) { + dprintk(1, KERN_ERR "%s: %s - no codec found\n", + ZR_DEVNAME(zr), __func__); + goto zr_free_codec; + } + if (zr->codec->type != zr->card.video_codec) { + dprintk(1, KERN_ERR "%s: %s - wrong codec\n", + ZR_DEVNAME(zr), __func__); + goto zr_detach_codec; + } + } + if (zr->card.video_vfe != 0) { + master_vfe = zoran_setup_videocodec(zr, zr->card.video_vfe); + if (!master_vfe) + goto zr_detach_codec; + zr->vfe = videocodec_attach(master_vfe); + if (!zr->vfe) { + dprintk(1, KERN_ERR "%s: %s - no VFE found\n", + ZR_DEVNAME(zr), __func__); + goto zr_free_vfe; + } + if (zr->vfe->type != zr->card.video_vfe) { + dprintk(1, KERN_ERR "%s: %s = wrong VFE\n", + ZR_DEVNAME(zr), __func__); + goto zr_detach_vfe; + } + } + + /* take care of Natoma chipset and a revision 1 zr36057 */ + if ((pci_pci_problems & PCIPCI_NATOMA) && zr->revision <= 1) { + zr->jpg_buffers.need_contiguous = 1; + dprintk(1, KERN_INFO + "%s: ZR36057/Natoma bug, max. buffer size is 128K\n", + ZR_DEVNAME(zr)); + } + + if (zr36057_init(zr) < 0) + goto zr_detach_vfe; + + zoran_proc_init(zr); + + return 0; + +zr_detach_vfe: + videocodec_detach(zr->vfe); +zr_free_vfe: + kfree(master_vfe); +zr_detach_codec: + videocodec_detach(zr->codec); +zr_free_codec: + kfree(master_codec); +zr_unreg_i2c: + zoran_unregister_i2c(zr); +zr_free_irq: + btwrite(0, ZR36057_SPGPPCR); + free_irq(zr->pci_dev->irq, zr); +zr_unmap: + iounmap(zr->zr36057_mem); +zr_unreg: + v4l2_device_unregister(&zr->v4l2_dev); +zr_free_mem: + kfree(zr); + + return -ENODEV; +} + +static struct pci_driver zoran_driver = { + .name = "zr36067", + .id_table = zr36067_pci_tbl, + .probe = zoran_probe, + .remove = __devexit_p(zoran_remove), +}; + +static int __init zoran_init(void) +{ + int res; + + printk(KERN_INFO "Zoran MJPEG board driver version %s\n", + ZORAN_VERSION); + + /* check the parameters we have been given, adjust if necessary */ + if (v4l_nbufs < 2) + v4l_nbufs = 2; + if (v4l_nbufs > VIDEO_MAX_FRAME) + v4l_nbufs = VIDEO_MAX_FRAME; + /* The user specfies the in KB, we want them in byte + * (and page aligned) */ + v4l_bufsize = PAGE_ALIGN(v4l_bufsize * 1024); + if (v4l_bufsize < 32768) + v4l_bufsize = 32768; + /* 2 MB is arbitrary but sufficient for the maximum possible images */ + if (v4l_bufsize > 2048 * 1024) + v4l_bufsize = 2048 * 1024; + if (jpg_nbufs < 4) + jpg_nbufs = 4; + if (jpg_nbufs > BUZ_MAX_FRAME) + jpg_nbufs = BUZ_MAX_FRAME; + jpg_bufsize = PAGE_ALIGN(jpg_bufsize * 1024); + if (jpg_bufsize < 8192) + jpg_bufsize = 8192; + if (jpg_bufsize > (512 * 1024)) + jpg_bufsize = 512 * 1024; + /* Use parameter for vidmem or try to find a video card */ + if (vidmem) { + dprintk(1, + KERN_INFO + "%s: Using supplied video memory base address @ 0x%lx\n", + ZORAN_NAME, vidmem); + } + + /* some mainboards might not do PCI-PCI data transfer well */ + if (pci_pci_problems & (PCIPCI_FAIL|PCIAGP_FAIL|PCIPCI_ALIMAGIK)) { + dprintk(1, + KERN_WARNING + "%s: chipset does not support reliable PCI-PCI DMA\n", + ZORAN_NAME); + } + + res = pci_register_driver(&zoran_driver); + if (res) { + dprintk(1, + KERN_ERR + "%s: Unable to register ZR36057 driver\n", + ZORAN_NAME); + return res; + } + + return 0; +} + +static void __exit zoran_exit(void) +{ + pci_unregister_driver(&zoran_driver); +} + +module_init(zoran_init); +module_exit(zoran_exit); diff --git a/drivers/media/video/zoran/zoran_card.h b/drivers/media/pci/zoran/zoran_card.h similarity index 100% rename from drivers/media/video/zoran/zoran_card.h rename to drivers/media/pci/zoran/zoran_card.h diff --git a/drivers/media/video/zoran/zoran_device.c b/drivers/media/pci/zoran/zoran_device.c similarity index 100% rename from drivers/media/video/zoran/zoran_device.c rename to drivers/media/pci/zoran/zoran_device.c diff --git a/drivers/media/video/zoran/zoran_device.h b/drivers/media/pci/zoran/zoran_device.h similarity index 100% rename from drivers/media/video/zoran/zoran_device.h rename to drivers/media/pci/zoran/zoran_device.h diff --git a/drivers/media/pci/zoran/zoran_driver.c b/drivers/media/pci/zoran/zoran_driver.c new file mode 100644 index 000000000000..53f12c7466b0 --- /dev/null +++ b/drivers/media/pci/zoran/zoran_driver.c @@ -0,0 +1,3090 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * Copyright (C) 2000 Serguei Miridonov + * + * Changes for BUZ by Wolfgang Scherr + * + * Changes for DC10/DC30 by Laurent Pinchart + * + * Changes for LML33R10 by Maxim Yevtyushkin + * + * Changes for videodev2/v4l2 by Ronald Bultje + * + * Based on + * + * Miro DC10 driver + * Copyright (C) 1999 Wolfgang Scherr + * + * Iomega Buz driver version 1.0 + * Copyright (C) 1999 Rainer Johanni + * + * buz.0.0.3 + * Copyright (C) 1998 Dave Perks + * + * bttv - Bt848 frame grabber driver + * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + * & Marcus Metzler (mocm@thp.uni-koeln.de) + * + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include "videocodec.h" + +#include +#include +#include +#include + +#include +#include "zoran.h" +#include "zoran_device.h" +#include "zoran_card.h" + + +const struct zoran_format zoran_formats[] = { + { + .name = "15-bit RGB LE", + .fourcc = V4L2_PIX_FMT_RGB555, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 15, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + .vfespfr = ZR36057_VFESPFR_RGB555|ZR36057_VFESPFR_ErrDif| + ZR36057_VFESPFR_LittleEndian, + }, { + .name = "15-bit RGB BE", + .fourcc = V4L2_PIX_FMT_RGB555X, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 15, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + .vfespfr = ZR36057_VFESPFR_RGB555|ZR36057_VFESPFR_ErrDif, + }, { + .name = "16-bit RGB LE", + .fourcc = V4L2_PIX_FMT_RGB565, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + .vfespfr = ZR36057_VFESPFR_RGB565|ZR36057_VFESPFR_ErrDif| + ZR36057_VFESPFR_LittleEndian, + }, { + .name = "16-bit RGB BE", + .fourcc = V4L2_PIX_FMT_RGB565X, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + .vfespfr = ZR36057_VFESPFR_RGB565|ZR36057_VFESPFR_ErrDif, + }, { + .name = "24-bit RGB", + .fourcc = V4L2_PIX_FMT_BGR24, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 24, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + .vfespfr = ZR36057_VFESPFR_RGB888|ZR36057_VFESPFR_Pack24, + }, { + .name = "32-bit RGB LE", + .fourcc = V4L2_PIX_FMT_BGR32, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 32, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + .vfespfr = ZR36057_VFESPFR_RGB888|ZR36057_VFESPFR_LittleEndian, + }, { + .name = "32-bit RGB BE", + .fourcc = V4L2_PIX_FMT_RGB32, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 32, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + .vfespfr = ZR36057_VFESPFR_RGB888, + }, { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + .vfespfr = ZR36057_VFESPFR_YUV422, + }, { + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + .vfespfr = ZR36057_VFESPFR_YUV422|ZR36057_VFESPFR_LittleEndian, + }, { + .name = "Hardware-encoded Motion-JPEG", + .fourcc = V4L2_PIX_FMT_MJPEG, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + .depth = 0, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_PLAYBACK | + ZORAN_FORMAT_COMPRESSED, + } +}; +#define NUM_FORMATS ARRAY_SIZE(zoran_formats) + + /* small helper function for calculating buffersizes for v4l2 + * we calculate the nearest higher power-of-two, which + * will be the recommended buffersize */ +static __u32 +zoran_v4l2_calc_bufsize (struct zoran_jpg_settings *settings) +{ + __u8 div = settings->VerDcm * settings->HorDcm * settings->TmpDcm; + __u32 num = (1024 * 512) / (div); + __u32 result = 2; + + num--; + while (num) { + num >>= 1; + result <<= 1; + } + + if (result > jpg_bufsize) + return jpg_bufsize; + if (result < 8192) + return 8192; + return result; +} + +/* forward references */ +static void v4l_fbuffer_free(struct zoran_fh *fh); +static void jpg_fbuffer_free(struct zoran_fh *fh); + +/* Set mapping mode */ +static void map_mode_raw(struct zoran_fh *fh) +{ + fh->map_mode = ZORAN_MAP_MODE_RAW; + fh->buffers.buffer_size = v4l_bufsize; + fh->buffers.num_buffers = v4l_nbufs; +} +static void map_mode_jpg(struct zoran_fh *fh, int play) +{ + fh->map_mode = play ? ZORAN_MAP_MODE_JPG_PLAY : ZORAN_MAP_MODE_JPG_REC; + fh->buffers.buffer_size = jpg_bufsize; + fh->buffers.num_buffers = jpg_nbufs; +} +static inline const char *mode_name(enum zoran_map_mode mode) +{ + return mode == ZORAN_MAP_MODE_RAW ? "V4L" : "JPG"; +} + +/* + * Allocate the V4L grab buffers + * + * These have to be pysically contiguous. + */ + +static int v4l_fbuffer_alloc(struct zoran_fh *fh) +{ + struct zoran *zr = fh->zr; + int i, off; + unsigned char *mem; + + for (i = 0; i < fh->buffers.num_buffers; i++) { + if (fh->buffers.buffer[i].v4l.fbuffer) + dprintk(2, + KERN_WARNING + "%s: %s - buffer %d already allocated!?\n", + ZR_DEVNAME(zr), __func__, i); + + //udelay(20); + mem = kmalloc(fh->buffers.buffer_size, + GFP_KERNEL | __GFP_NOWARN); + if (!mem) { + dprintk(1, + KERN_ERR + "%s: %s - kmalloc for V4L buf %d failed\n", + ZR_DEVNAME(zr), __func__, i); + v4l_fbuffer_free(fh); + return -ENOBUFS; + } + fh->buffers.buffer[i].v4l.fbuffer = mem; + fh->buffers.buffer[i].v4l.fbuffer_phys = virt_to_phys(mem); + fh->buffers.buffer[i].v4l.fbuffer_bus = virt_to_bus(mem); + for (off = 0; off < fh->buffers.buffer_size; + off += PAGE_SIZE) + SetPageReserved(virt_to_page(mem + off)); + dprintk(4, + KERN_INFO + "%s: %s - V4L frame %d mem 0x%lx (bus: 0x%llx)\n", + ZR_DEVNAME(zr), __func__, i, (unsigned long) mem, + (unsigned long long)virt_to_bus(mem)); + } + + fh->buffers.allocated = 1; + + return 0; +} + +/* free the V4L grab buffers */ +static void v4l_fbuffer_free(struct zoran_fh *fh) +{ + struct zoran *zr = fh->zr; + int i, off; + unsigned char *mem; + + dprintk(4, KERN_INFO "%s: %s\n", ZR_DEVNAME(zr), __func__); + + for (i = 0; i < fh->buffers.num_buffers; i++) { + if (!fh->buffers.buffer[i].v4l.fbuffer) + continue; + + mem = fh->buffers.buffer[i].v4l.fbuffer; + for (off = 0; off < fh->buffers.buffer_size; + off += PAGE_SIZE) + ClearPageReserved(virt_to_page(mem + off)); + kfree(fh->buffers.buffer[i].v4l.fbuffer); + fh->buffers.buffer[i].v4l.fbuffer = NULL; + } + + fh->buffers.allocated = 0; +} + +/* + * Allocate the MJPEG grab buffers. + * + * If a Natoma chipset is present and this is a revision 1 zr36057, + * each MJPEG buffer needs to be physically contiguous. + * (RJ: This statement is from Dave Perks' original driver, + * I could never check it because I have a zr36067) + * + * RJ: The contents grab buffers needs never be accessed in the driver. + * Therefore there is no need to allocate them with vmalloc in order + * to get a contiguous virtual memory space. + * I don't understand why many other drivers first allocate them with + * vmalloc (which uses internally also get_zeroed_page, but delivers you + * virtual addresses) and then again have to make a lot of efforts + * to get the physical address. + * + * Ben Capper: + * On big-endian architectures (such as ppc) some extra steps + * are needed. When reading and writing to the stat_com array + * and fragment buffers, the device expects to see little- + * endian values. The use of cpu_to_le32() and le32_to_cpu() + * in this function (and one or two others in zoran_device.c) + * ensure that these values are always stored in little-endian + * form, regardless of architecture. The zr36057 does Very Bad + * Things on big endian architectures if the stat_com array + * and fragment buffers are not little-endian. + */ + +static int jpg_fbuffer_alloc(struct zoran_fh *fh) +{ + struct zoran *zr = fh->zr; + int i, j, off; + u8 *mem; + + for (i = 0; i < fh->buffers.num_buffers; i++) { + if (fh->buffers.buffer[i].jpg.frag_tab) + dprintk(2, + KERN_WARNING + "%s: %s - buffer %d already allocated!?\n", + ZR_DEVNAME(zr), __func__, i); + + /* Allocate fragment table for this buffer */ + + mem = (void *)get_zeroed_page(GFP_KERNEL); + if (!mem) { + dprintk(1, + KERN_ERR + "%s: %s - get_zeroed_page (frag_tab) failed for buffer %d\n", + ZR_DEVNAME(zr), __func__, i); + jpg_fbuffer_free(fh); + return -ENOBUFS; + } + fh->buffers.buffer[i].jpg.frag_tab = (__le32 *)mem; + fh->buffers.buffer[i].jpg.frag_tab_bus = virt_to_bus(mem); + + if (fh->buffers.need_contiguous) { + mem = kmalloc(fh->buffers.buffer_size, GFP_KERNEL); + if (mem == NULL) { + dprintk(1, + KERN_ERR + "%s: %s - kmalloc failed for buffer %d\n", + ZR_DEVNAME(zr), __func__, i); + jpg_fbuffer_free(fh); + return -ENOBUFS; + } + fh->buffers.buffer[i].jpg.frag_tab[0] = + cpu_to_le32(virt_to_bus(mem)); + fh->buffers.buffer[i].jpg.frag_tab[1] = + cpu_to_le32((fh->buffers.buffer_size >> 1) | 1); + for (off = 0; off < fh->buffers.buffer_size; off += PAGE_SIZE) + SetPageReserved(virt_to_page(mem + off)); + } else { + /* jpg_bufsize is already page aligned */ + for (j = 0; j < fh->buffers.buffer_size / PAGE_SIZE; j++) { + mem = (void *)get_zeroed_page(GFP_KERNEL); + if (mem == NULL) { + dprintk(1, + KERN_ERR + "%s: %s - get_zeroed_page failed for buffer %d\n", + ZR_DEVNAME(zr), __func__, i); + jpg_fbuffer_free(fh); + return -ENOBUFS; + } + + fh->buffers.buffer[i].jpg.frag_tab[2 * j] = + cpu_to_le32(virt_to_bus(mem)); + fh->buffers.buffer[i].jpg.frag_tab[2 * j + 1] = + cpu_to_le32((PAGE_SIZE >> 2) << 1); + SetPageReserved(virt_to_page(mem)); + } + + fh->buffers.buffer[i].jpg.frag_tab[2 * j - 1] |= cpu_to_le32(1); + } + } + + dprintk(4, + KERN_DEBUG "%s: %s - %d KB allocated\n", + ZR_DEVNAME(zr), __func__, + (fh->buffers.num_buffers * fh->buffers.buffer_size) >> 10); + + fh->buffers.allocated = 1; + + return 0; +} + +/* free the MJPEG grab buffers */ +static void jpg_fbuffer_free(struct zoran_fh *fh) +{ + struct zoran *zr = fh->zr; + int i, j, off; + unsigned char *mem; + __le32 frag_tab; + struct zoran_buffer *buffer; + + dprintk(4, KERN_DEBUG "%s: %s\n", ZR_DEVNAME(zr), __func__); + + for (i = 0, buffer = &fh->buffers.buffer[0]; + i < fh->buffers.num_buffers; i++, buffer++) { + if (!buffer->jpg.frag_tab) + continue; + + if (fh->buffers.need_contiguous) { + frag_tab = buffer->jpg.frag_tab[0]; + + if (frag_tab) { + mem = bus_to_virt(le32_to_cpu(frag_tab)); + for (off = 0; off < fh->buffers.buffer_size; off += PAGE_SIZE) + ClearPageReserved(virt_to_page(mem + off)); + kfree(mem); + buffer->jpg.frag_tab[0] = 0; + buffer->jpg.frag_tab[1] = 0; + } + } else { + for (j = 0; j < fh->buffers.buffer_size / PAGE_SIZE; j++) { + frag_tab = buffer->jpg.frag_tab[2 * j]; + + if (!frag_tab) + break; + ClearPageReserved(virt_to_page(bus_to_virt(le32_to_cpu(frag_tab)))); + free_page((unsigned long)bus_to_virt(le32_to_cpu(frag_tab))); + buffer->jpg.frag_tab[2 * j] = 0; + buffer->jpg.frag_tab[2 * j + 1] = 0; + } + } + + free_page((unsigned long)buffer->jpg.frag_tab); + buffer->jpg.frag_tab = NULL; + } + + fh->buffers.allocated = 0; +} + +/* + * V4L Buffer grabbing + */ + +static int +zoran_v4l_set_format (struct zoran_fh *fh, + int width, + int height, + const struct zoran_format *format) +{ + struct zoran *zr = fh->zr; + int bpp; + + /* Check size and format of the grab wanted */ + + if (height < BUZ_MIN_HEIGHT || width < BUZ_MIN_WIDTH || + height > BUZ_MAX_HEIGHT || width > BUZ_MAX_WIDTH) { + dprintk(1, + KERN_ERR + "%s: %s - wrong frame size (%dx%d)\n", + ZR_DEVNAME(zr), __func__, width, height); + return -EINVAL; + } + + bpp = (format->depth + 7) / 8; + + /* Check against available buffer size */ + if (height * width * bpp > fh->buffers.buffer_size) { + dprintk(1, + KERN_ERR + "%s: %s - video buffer size (%d kB) is too small\n", + ZR_DEVNAME(zr), __func__, fh->buffers.buffer_size >> 10); + return -EINVAL; + } + + /* The video front end needs 4-byte alinged line sizes */ + + if ((bpp == 2 && (width & 1)) || (bpp == 3 && (width & 3))) { + dprintk(1, + KERN_ERR + "%s: %s - wrong frame alignment\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + + fh->v4l_settings.width = width; + fh->v4l_settings.height = height; + fh->v4l_settings.format = format; + fh->v4l_settings.bytesperline = bpp * fh->v4l_settings.width; + + return 0; +} + +static int zoran_v4l_queue_frame(struct zoran_fh *fh, int num) +{ + struct zoran *zr = fh->zr; + unsigned long flags; + int res = 0; + + if (!fh->buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: %s - buffers not yet allocated\n", + ZR_DEVNAME(zr), __func__); + res = -ENOMEM; + } + + /* No grabbing outside the buffer range! */ + if (num >= fh->buffers.num_buffers || num < 0) { + dprintk(1, + KERN_ERR + "%s: %s - buffer %d is out of range\n", + ZR_DEVNAME(zr), __func__, num); + res = -EINVAL; + } + + spin_lock_irqsave(&zr->spinlock, flags); + + if (fh->buffers.active == ZORAN_FREE) { + if (zr->v4l_buffers.active == ZORAN_FREE) { + zr->v4l_buffers = fh->buffers; + fh->buffers.active = ZORAN_ACTIVE; + } else { + dprintk(1, + KERN_ERR + "%s: %s - another session is already capturing\n", + ZR_DEVNAME(zr), __func__); + res = -EBUSY; + } + } + + /* make sure a grab isn't going on currently with this buffer */ + if (!res) { + switch (zr->v4l_buffers.buffer[num].state) { + default: + case BUZ_STATE_PEND: + if (zr->v4l_buffers.active == ZORAN_FREE) { + fh->buffers.active = ZORAN_FREE; + zr->v4l_buffers.allocated = 0; + } + res = -EBUSY; /* what are you doing? */ + break; + case BUZ_STATE_DONE: + dprintk(2, + KERN_WARNING + "%s: %s - queueing buffer %d in state DONE!?\n", + ZR_DEVNAME(zr), __func__, num); + case BUZ_STATE_USER: + /* since there is at least one unused buffer there's room for at least + * one more pend[] entry */ + zr->v4l_pend[zr->v4l_pend_head++ & V4L_MASK_FRAME] = num; + zr->v4l_buffers.buffer[num].state = BUZ_STATE_PEND; + zr->v4l_buffers.buffer[num].bs.length = + fh->v4l_settings.bytesperline * + zr->v4l_settings.height; + fh->buffers.buffer[num] = zr->v4l_buffers.buffer[num]; + break; + } + } + + spin_unlock_irqrestore(&zr->spinlock, flags); + + if (!res && zr->v4l_buffers.active == ZORAN_FREE) + zr->v4l_buffers.active = fh->buffers.active; + + return res; +} + +/* + * Sync on a V4L buffer + */ + +static int v4l_sync(struct zoran_fh *fh, int frame) +{ + struct zoran *zr = fh->zr; + unsigned long flags; + + if (fh->buffers.active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: %s - no grab active for this session\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + + /* check passed-in frame number */ + if (frame >= fh->buffers.num_buffers || frame < 0) { + dprintk(1, + KERN_ERR "%s: %s - frame %d is invalid\n", + ZR_DEVNAME(zr), __func__, frame); + return -EINVAL; + } + + /* Check if is buffer was queued at all */ + if (zr->v4l_buffers.buffer[frame].state == BUZ_STATE_USER) { + dprintk(1, + KERN_ERR + "%s: %s - attempt to sync on a buffer which was not queued?\n", + ZR_DEVNAME(zr), __func__); + return -EPROTO; + } + + /* wait on this buffer to get ready */ + if (!wait_event_interruptible_timeout(zr->v4l_capq, + (zr->v4l_buffers.buffer[frame].state != BUZ_STATE_PEND), 10*HZ)) + return -ETIME; + if (signal_pending(current)) + return -ERESTARTSYS; + + /* buffer should now be in BUZ_STATE_DONE */ + if (zr->v4l_buffers.buffer[frame].state != BUZ_STATE_DONE) + dprintk(2, + KERN_ERR "%s: %s - internal state error\n", + ZR_DEVNAME(zr), __func__); + + zr->v4l_buffers.buffer[frame].state = BUZ_STATE_USER; + fh->buffers.buffer[frame] = zr->v4l_buffers.buffer[frame]; + + spin_lock_irqsave(&zr->spinlock, flags); + + /* Check if streaming capture has finished */ + if (zr->v4l_pend_tail == zr->v4l_pend_head) { + zr36057_set_memgrab(zr, 0); + if (zr->v4l_buffers.active == ZORAN_ACTIVE) { + fh->buffers.active = zr->v4l_buffers.active = ZORAN_FREE; + zr->v4l_buffers.allocated = 0; + } + } + + spin_unlock_irqrestore(&zr->spinlock, flags); + + return 0; +} + +/* + * Queue a MJPEG buffer for capture/playback + */ + +static int zoran_jpg_queue_frame(struct zoran_fh *fh, int num, + enum zoran_codec_mode mode) +{ + struct zoran *zr = fh->zr; + unsigned long flags; + int res = 0; + + /* Check if buffers are allocated */ + if (!fh->buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: %s - buffers not yet allocated\n", + ZR_DEVNAME(zr), __func__); + return -ENOMEM; + } + + /* No grabbing outside the buffer range! */ + if (num >= fh->buffers.num_buffers || num < 0) { + dprintk(1, + KERN_ERR + "%s: %s - buffer %d out of range\n", + ZR_DEVNAME(zr), __func__, num); + return -EINVAL; + } + + /* what is the codec mode right now? */ + if (zr->codec_mode == BUZ_MODE_IDLE) { + zr->jpg_settings = fh->jpg_settings; + } else if (zr->codec_mode != mode) { + /* wrong codec mode active - invalid */ + dprintk(1, + KERN_ERR + "%s: %s - codec in wrong mode\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + + if (fh->buffers.active == ZORAN_FREE) { + if (zr->jpg_buffers.active == ZORAN_FREE) { + zr->jpg_buffers = fh->buffers; + fh->buffers.active = ZORAN_ACTIVE; + } else { + dprintk(1, + KERN_ERR + "%s: %s - another session is already capturing\n", + ZR_DEVNAME(zr), __func__); + res = -EBUSY; + } + } + + if (!res && zr->codec_mode == BUZ_MODE_IDLE) { + /* Ok load up the jpeg codec */ + zr36057_enable_jpg(zr, mode); + } + + spin_lock_irqsave(&zr->spinlock, flags); + + if (!res) { + switch (zr->jpg_buffers.buffer[num].state) { + case BUZ_STATE_DONE: + dprintk(2, + KERN_WARNING + "%s: %s - queing frame in BUZ_STATE_DONE state!?\n", + ZR_DEVNAME(zr), __func__); + case BUZ_STATE_USER: + /* since there is at least one unused buffer there's room for at + *least one more pend[] entry */ + zr->jpg_pend[zr->jpg_que_head++ & BUZ_MASK_FRAME] = num; + zr->jpg_buffers.buffer[num].state = BUZ_STATE_PEND; + fh->buffers.buffer[num] = zr->jpg_buffers.buffer[num]; + zoran_feed_stat_com(zr); + break; + default: + case BUZ_STATE_DMA: + case BUZ_STATE_PEND: + if (zr->jpg_buffers.active == ZORAN_FREE) { + fh->buffers.active = ZORAN_FREE; + zr->jpg_buffers.allocated = 0; + } + res = -EBUSY; /* what are you doing? */ + break; + } + } + + spin_unlock_irqrestore(&zr->spinlock, flags); + + if (!res && zr->jpg_buffers.active == ZORAN_FREE) + zr->jpg_buffers.active = fh->buffers.active; + + return res; +} + +static int jpg_qbuf(struct zoran_fh *fh, int frame, enum zoran_codec_mode mode) +{ + struct zoran *zr = fh->zr; + int res = 0; + + /* Does the user want to stop streaming? */ + if (frame < 0) { + if (zr->codec_mode == mode) { + if (fh->buffers.active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: %s(-1) - session not active\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + fh->buffers.active = zr->jpg_buffers.active = ZORAN_FREE; + zr->jpg_buffers.allocated = 0; + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + return 0; + } else { + dprintk(1, + KERN_ERR + "%s: %s - stop streaming but not in streaming mode\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + } + + if ((res = zoran_jpg_queue_frame(fh, frame, mode))) + return res; + + /* Start the jpeg codec when the first frame is queued */ + if (!res && zr->jpg_que_head == 1) + jpeg_start(zr); + + return res; +} + +/* + * Sync on a MJPEG buffer + */ + +static int jpg_sync(struct zoran_fh *fh, struct zoran_sync *bs) +{ + struct zoran *zr = fh->zr; + unsigned long flags; + int frame; + + if (fh->buffers.active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: %s - capture is not currently active\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + if (zr->codec_mode != BUZ_MODE_MOTION_DECOMPRESS && + zr->codec_mode != BUZ_MODE_MOTION_COMPRESS) { + dprintk(1, + KERN_ERR + "%s: %s - codec not in streaming mode\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + if (!wait_event_interruptible_timeout(zr->jpg_capq, + (zr->jpg_que_tail != zr->jpg_dma_tail || + zr->jpg_dma_tail == zr->jpg_dma_head), + 10*HZ)) { + int isr; + + btand(~ZR36057_JMC_Go_en, ZR36057_JMC); + udelay(1); + zr->codec->control(zr->codec, CODEC_G_STATUS, + sizeof(isr), &isr); + dprintk(1, + KERN_ERR + "%s: %s - timeout: codec isr=0x%02x\n", + ZR_DEVNAME(zr), __func__, isr); + + return -ETIME; + + } + if (signal_pending(current)) + return -ERESTARTSYS; + + spin_lock_irqsave(&zr->spinlock, flags); + + if (zr->jpg_dma_tail != zr->jpg_dma_head) + frame = zr->jpg_pend[zr->jpg_que_tail++ & BUZ_MASK_FRAME]; + else + frame = zr->jpg_pend[zr->jpg_que_tail & BUZ_MASK_FRAME]; + + /* buffer should now be in BUZ_STATE_DONE */ + if (zr->jpg_buffers.buffer[frame].state != BUZ_STATE_DONE) + dprintk(2, + KERN_ERR "%s: %s - internal state error\n", + ZR_DEVNAME(zr), __func__); + + *bs = zr->jpg_buffers.buffer[frame].bs; + bs->frame = frame; + zr->jpg_buffers.buffer[frame].state = BUZ_STATE_USER; + fh->buffers.buffer[frame] = zr->jpg_buffers.buffer[frame]; + + spin_unlock_irqrestore(&zr->spinlock, flags); + + return 0; +} + +static void zoran_open_init_session(struct zoran_fh *fh) +{ + int i; + struct zoran *zr = fh->zr; + + /* Per default, map the V4L Buffers */ + map_mode_raw(fh); + + /* take over the card's current settings */ + fh->overlay_settings = zr->overlay_settings; + fh->overlay_settings.is_set = 0; + fh->overlay_settings.format = zr->overlay_settings.format; + fh->overlay_active = ZORAN_FREE; + + /* v4l settings */ + fh->v4l_settings = zr->v4l_settings; + /* jpg settings */ + fh->jpg_settings = zr->jpg_settings; + + /* buffers */ + memset(&fh->buffers, 0, sizeof(fh->buffers)); + for (i = 0; i < MAX_FRAME; i++) { + fh->buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + fh->buffers.buffer[i].bs.frame = i; + } + fh->buffers.allocated = 0; + fh->buffers.active = ZORAN_FREE; +} + +static void zoran_close_end_session(struct zoran_fh *fh) +{ + struct zoran *zr = fh->zr; + + /* overlay */ + if (fh->overlay_active != ZORAN_FREE) { + fh->overlay_active = zr->overlay_active = ZORAN_FREE; + zr->v4l_overlay_active = 0; + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 0); + zr->overlay_mask = NULL; + } + + if (fh->map_mode == ZORAN_MAP_MODE_RAW) { + /* v4l capture */ + if (fh->buffers.active != ZORAN_FREE) { + unsigned long flags; + + spin_lock_irqsave(&zr->spinlock, flags); + zr36057_set_memgrab(zr, 0); + zr->v4l_buffers.allocated = 0; + zr->v4l_buffers.active = fh->buffers.active = ZORAN_FREE; + spin_unlock_irqrestore(&zr->spinlock, flags); + } + + /* v4l buffers */ + if (fh->buffers.allocated) + v4l_fbuffer_free(fh); + } else { + /* jpg capture */ + if (fh->buffers.active != ZORAN_FREE) { + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + zr->jpg_buffers.allocated = 0; + zr->jpg_buffers.active = fh->buffers.active = ZORAN_FREE; + } + + /* jpg buffers */ + if (fh->buffers.allocated) + jpg_fbuffer_free(fh); + } +} + +/* + * Open a zoran card. Right now the flags stuff is just playing + */ + +static int zoran_open(struct file *file) +{ + struct zoran *zr = video_drvdata(file); + struct zoran_fh *fh; + int res, first_open = 0; + + dprintk(2, KERN_INFO "%s: %s(%s, pid=[%d]), users(-)=%d\n", + ZR_DEVNAME(zr), __func__, current->comm, task_pid_nr(current), zr->user + 1); + + mutex_lock(&zr->other_lock); + + if (zr->user >= 2048) { + dprintk(1, KERN_ERR "%s: too many users (%d) on device\n", + ZR_DEVNAME(zr), zr->user); + res = -EBUSY; + goto fail_unlock; + } + + /* now, create the open()-specific file_ops struct */ + fh = kzalloc(sizeof(struct zoran_fh), GFP_KERNEL); + if (!fh) { + dprintk(1, + KERN_ERR + "%s: %s - allocation of zoran_fh failed\n", + ZR_DEVNAME(zr), __func__); + res = -ENOMEM; + goto fail_unlock; + } + /* used to be BUZ_MAX_WIDTH/HEIGHT, but that gives overflows + * on norm-change! */ + fh->overlay_mask = + kmalloc(((768 + 31) / 32) * 576 * 4, GFP_KERNEL); + if (!fh->overlay_mask) { + dprintk(1, + KERN_ERR + "%s: %s - allocation of overlay_mask failed\n", + ZR_DEVNAME(zr), __func__); + res = -ENOMEM; + goto fail_fh; + } + + if (zr->user++ == 0) + first_open = 1; + + /*mutex_unlock(&zr->resource_lock);*/ + + /* default setup - TODO: look at flags */ + if (first_open) { /* First device open */ + zr36057_restart(zr); + zoran_open_init_params(zr); + zoran_init_hardware(zr); + + btor(ZR36057_ICR_IntPinEn, ZR36057_ICR); + } + + /* set file_ops stuff */ + file->private_data = fh; + fh->zr = zr; + zoran_open_init_session(fh); + mutex_unlock(&zr->other_lock); + + return 0; + +fail_fh: + kfree(fh); +fail_unlock: + mutex_unlock(&zr->other_lock); + + dprintk(2, KERN_INFO "%s: open failed (%d), users(-)=%d\n", + ZR_DEVNAME(zr), res, zr->user); + + return res; +} + +static int +zoran_close(struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + dprintk(2, KERN_INFO "%s: %s(%s, pid=[%d]), users(+)=%d\n", + ZR_DEVNAME(zr), __func__, current->comm, task_pid_nr(current), zr->user - 1); + + /* kernel locks (fs/device.c), so don't do that ourselves + * (prevents deadlocks) */ + mutex_lock(&zr->other_lock); + + zoran_close_end_session(fh); + + if (zr->user-- == 1) { /* Last process */ + /* Clean up JPEG process */ + wake_up_interruptible(&zr->jpg_capq); + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + zr->jpg_buffers.allocated = 0; + zr->jpg_buffers.active = ZORAN_FREE; + + /* disable interrupts */ + btand(~ZR36057_ICR_IntPinEn, ZR36057_ICR); + + if (zr36067_debug > 1) + print_interrupts(zr); + + /* Overlay off */ + zr->v4l_overlay_active = 0; + zr36057_overlay(zr, 0); + zr->overlay_mask = NULL; + + /* capture off */ + wake_up_interruptible(&zr->v4l_capq); + zr36057_set_memgrab(zr, 0); + zr->v4l_buffers.allocated = 0; + zr->v4l_buffers.active = ZORAN_FREE; + zoran_set_pci_master(zr, 0); + + if (!pass_through) { /* Switch to color bar */ + decoder_call(zr, video, s_stream, 0); + encoder_call(zr, video, s_routing, 2, 0, 0); + } + } + mutex_unlock(&zr->other_lock); + + file->private_data = NULL; + kfree(fh->overlay_mask); + kfree(fh); + + dprintk(4, KERN_INFO "%s: %s done\n", ZR_DEVNAME(zr), __func__); + + return 0; +} + + +static ssize_t +zoran_read (struct file *file, + char __user *data, + size_t count, + loff_t *ppos) +{ + /* we simply don't support read() (yet)... */ + + return -EINVAL; +} + +static ssize_t +zoran_write (struct file *file, + const char __user *data, + size_t count, + loff_t *ppos) +{ + /* ...and the same goes for write() */ + + return -EINVAL; +} + +static int setup_fbuffer(struct zoran_fh *fh, + void *base, + const struct zoran_format *fmt, + int width, + int height, + int bytesperline) +{ + struct zoran *zr = fh->zr; + + /* (Ronald) v4l/v4l2 guidelines */ + if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* Don't allow frame buffer overlay if PCI or AGP is buggy, or on + ALi Magik (that needs very low latency while the card needs a + higher value always) */ + + if (pci_pci_problems & (PCIPCI_FAIL | PCIAGP_FAIL | PCIPCI_ALIMAGIK)) + return -ENXIO; + + /* we need a bytesperline value, even if not given */ + if (!bytesperline) + bytesperline = width * ((fmt->depth + 7) & ~7) / 8; + +#if 0 + if (zr->overlay_active) { + /* dzjee... stupid users... don't even bother to turn off + * overlay before changing the memory location... + * normally, we would return errors here. However, one of + * the tools that does this is... xawtv! and since xawtv + * is used by +/- 99% of the users, we'd rather be user- + * friendly and silently do as if nothing went wrong */ + dprintk(3, + KERN_ERR + "%s: %s - forced overlay turnoff because framebuffer changed\n", + ZR_DEVNAME(zr), __func__); + zr36057_overlay(zr, 0); + } +#endif + + if (!(fmt->flags & ZORAN_FORMAT_OVERLAY)) { + dprintk(1, + KERN_ERR + "%s: %s - no valid overlay format given\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + if (height <= 0 || width <= 0 || bytesperline <= 0) { + dprintk(1, + KERN_ERR + "%s: %s - invalid height/width/bpl value (%d|%d|%d)\n", + ZR_DEVNAME(zr), __func__, width, height, bytesperline); + return -EINVAL; + } + if (bytesperline & 3) { + dprintk(1, + KERN_ERR + "%s: %s - bytesperline (%d) must be 4-byte aligned\n", + ZR_DEVNAME(zr), __func__, bytesperline); + return -EINVAL; + } + + zr->vbuf_base = (void *) ((unsigned long) base & ~3); + zr->vbuf_height = height; + zr->vbuf_width = width; + zr->vbuf_depth = fmt->depth; + zr->overlay_settings.format = fmt; + zr->vbuf_bytesperline = bytesperline; + + /* The user should set new window parameters */ + zr->overlay_settings.is_set = 0; + + return 0; +} + + +static int setup_window(struct zoran_fh *fh, + int x, + int y, + int width, + int height, + struct v4l2_clip __user *clips, + unsigned int clipcount, + void __user *bitmap) +{ + struct zoran *zr = fh->zr; + struct v4l2_clip *vcp = NULL; + int on, end; + + + if (!zr->vbuf_base) { + dprintk(1, + KERN_ERR + "%s: %s - frame buffer has to be set first\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + + if (!fh->overlay_settings.format) { + dprintk(1, + KERN_ERR + "%s: %s - no overlay format set\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + + if (clipcount > 2048) { + dprintk(1, + KERN_ERR + "%s: %s - invalid clipcount\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + + /* + * The video front end needs 4-byte alinged line sizes, we correct that + * silently here if necessary + */ + if (zr->vbuf_depth == 15 || zr->vbuf_depth == 16) { + end = (x + width) & ~1; /* round down */ + x = (x + 1) & ~1; /* round up */ + width = end - x; + } + + if (zr->vbuf_depth == 24) { + end = (x + width) & ~3; /* round down */ + x = (x + 3) & ~3; /* round up */ + width = end - x; + } + + if (width > BUZ_MAX_WIDTH) + width = BUZ_MAX_WIDTH; + if (height > BUZ_MAX_HEIGHT) + height = BUZ_MAX_HEIGHT; + + /* Check for invalid parameters */ + if (width < BUZ_MIN_WIDTH || height < BUZ_MIN_HEIGHT || + width > BUZ_MAX_WIDTH || height > BUZ_MAX_HEIGHT) { + dprintk(1, + KERN_ERR + "%s: %s - width = %d or height = %d invalid\n", + ZR_DEVNAME(zr), __func__, width, height); + return -EINVAL; + } + + fh->overlay_settings.x = x; + fh->overlay_settings.y = y; + fh->overlay_settings.width = width; + fh->overlay_settings.height = height; + fh->overlay_settings.clipcount = clipcount; + + /* + * If an overlay is running, we have to switch it off + * and switch it on again in order to get the new settings in effect. + * + * We also want to avoid that the overlay mask is written + * when an overlay is running. + */ + + on = zr->v4l_overlay_active && !zr->v4l_memgrab_active && + zr->overlay_active != ZORAN_FREE && + fh->overlay_active != ZORAN_FREE; + if (on) + zr36057_overlay(zr, 0); + + /* + * Write the overlay mask if clips are wanted. + * We prefer a bitmap. + */ + if (bitmap) { + /* fake value - it just means we want clips */ + fh->overlay_settings.clipcount = 1; + + if (copy_from_user(fh->overlay_mask, bitmap, + (width * height + 7) / 8)) { + return -EFAULT; + } + } else if (clipcount) { + /* write our own bitmap from the clips */ + vcp = vmalloc(sizeof(struct v4l2_clip) * (clipcount + 4)); + if (vcp == NULL) { + dprintk(1, + KERN_ERR + "%s: %s - Alloc of clip mask failed\n", + ZR_DEVNAME(zr), __func__); + return -ENOMEM; + } + if (copy_from_user + (vcp, clips, sizeof(struct v4l2_clip) * clipcount)) { + vfree(vcp); + return -EFAULT; + } + write_overlay_mask(fh, vcp, clipcount); + vfree(vcp); + } + + fh->overlay_settings.is_set = 1; + if (fh->overlay_active != ZORAN_FREE && + zr->overlay_active != ZORAN_FREE) + zr->overlay_settings = fh->overlay_settings; + + if (on) + zr36057_overlay(zr, 1); + + /* Make sure the changes come into effect */ + return wait_grab_pending(zr); +} + +static int setup_overlay(struct zoran_fh *fh, int on) +{ + struct zoran *zr = fh->zr; + + /* If there is nothing to do, return immediately */ + if ((on && fh->overlay_active != ZORAN_FREE) || + (!on && fh->overlay_active == ZORAN_FREE)) + return 0; + + /* check whether we're touching someone else's overlay */ + if (on && zr->overlay_active != ZORAN_FREE && + fh->overlay_active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: %s - overlay is already active for another session\n", + ZR_DEVNAME(zr), __func__); + return -EBUSY; + } + if (!on && zr->overlay_active != ZORAN_FREE && + fh->overlay_active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: %s - you cannot cancel someone else's session\n", + ZR_DEVNAME(zr), __func__); + return -EPERM; + } + + if (on == 0) { + zr->overlay_active = fh->overlay_active = ZORAN_FREE; + zr->v4l_overlay_active = 0; + /* When a grab is running, the video simply + * won't be switched on any more */ + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 0); + zr->overlay_mask = NULL; + } else { + if (!zr->vbuf_base || !fh->overlay_settings.is_set) { + dprintk(1, + KERN_ERR + "%s: %s - buffer or window not set\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + if (!fh->overlay_settings.format) { + dprintk(1, + KERN_ERR + "%s: %s - no overlay format set\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + zr->overlay_active = fh->overlay_active = ZORAN_LOCKED; + zr->v4l_overlay_active = 1; + zr->overlay_mask = fh->overlay_mask; + zr->overlay_settings = fh->overlay_settings; + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 1); + /* When a grab is running, the video will be + * switched on when grab is finished */ + } + + /* Make sure the changes come into effect */ + return wait_grab_pending(zr); +} + +/* get the status of a buffer in the clients buffer queue */ +static int zoran_v4l2_buffer_status(struct zoran_fh *fh, + struct v4l2_buffer *buf, int num) +{ + struct zoran *zr = fh->zr; + unsigned long flags; + + buf->flags = V4L2_BUF_FLAG_MAPPED; + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + /* check range */ + if (num < 0 || num >= fh->buffers.num_buffers || + !fh->buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: %s - wrong number or buffers not allocated\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + + spin_lock_irqsave(&zr->spinlock, flags); + dprintk(3, + KERN_DEBUG + "%s: %s() - raw active=%c, buffer %d: state=%c, map=%c\n", + ZR_DEVNAME(zr), __func__, + "FAL"[fh->buffers.active], num, + "UPMD"[zr->v4l_buffers.buffer[num].state], + fh->buffers.buffer[num].map ? 'Y' : 'N'); + spin_unlock_irqrestore(&zr->spinlock, flags); + + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf->length = fh->buffers.buffer_size; + + /* get buffer */ + buf->bytesused = fh->buffers.buffer[num].bs.length; + if (fh->buffers.buffer[num].state == BUZ_STATE_DONE || + fh->buffers.buffer[num].state == BUZ_STATE_USER) { + buf->sequence = fh->buffers.buffer[num].bs.seq; + buf->flags |= V4L2_BUF_FLAG_DONE; + buf->timestamp = fh->buffers.buffer[num].bs.timestamp; + } else { + buf->flags |= V4L2_BUF_FLAG_QUEUED; + } + + if (fh->v4l_settings.height <= BUZ_MAX_HEIGHT / 2) + buf->field = V4L2_FIELD_TOP; + else + buf->field = V4L2_FIELD_INTERLACED; + + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + + /* check range */ + if (num < 0 || num >= fh->buffers.num_buffers || + !fh->buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: %s - wrong number or buffers not allocated\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + + buf->type = (fh->map_mode == ZORAN_MAP_MODE_JPG_REC) ? + V4L2_BUF_TYPE_VIDEO_CAPTURE : + V4L2_BUF_TYPE_VIDEO_OUTPUT; + buf->length = fh->buffers.buffer_size; + + /* these variables are only written after frame has been captured */ + if (fh->buffers.buffer[num].state == BUZ_STATE_DONE || + fh->buffers.buffer[num].state == BUZ_STATE_USER) { + buf->sequence = fh->buffers.buffer[num].bs.seq; + buf->timestamp = fh->buffers.buffer[num].bs.timestamp; + buf->bytesused = fh->buffers.buffer[num].bs.length; + buf->flags |= V4L2_BUF_FLAG_DONE; + } else { + buf->flags |= V4L2_BUF_FLAG_QUEUED; + } + + /* which fields are these? */ + if (fh->jpg_settings.TmpDcm != 1) + buf->field = fh->jpg_settings.odd_even ? + V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM; + else + buf->field = fh->jpg_settings.odd_even ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_SEQ_BT; + + break; + + default: + + dprintk(5, + KERN_ERR + "%s: %s - invalid buffer type|map_mode (%d|%d)\n", + ZR_DEVNAME(zr), __func__, buf->type, fh->map_mode); + return -EINVAL; + } + + buf->memory = V4L2_MEMORY_MMAP; + buf->index = num; + buf->m.offset = buf->length * num; + + return 0; +} + +static int +zoran_set_norm (struct zoran *zr, + v4l2_std_id norm) +{ + int on; + + if (zr->v4l_buffers.active != ZORAN_FREE || + zr->jpg_buffers.active != ZORAN_FREE) { + dprintk(1, + KERN_WARNING + "%s: %s called while in playback/capture mode\n", + ZR_DEVNAME(zr), __func__); + return -EBUSY; + } + + if (!(norm & zr->card.norms)) { + dprintk(1, + KERN_ERR "%s: %s - unsupported norm %llx\n", + ZR_DEVNAME(zr), __func__, norm); + return -EINVAL; + } + + if (norm == V4L2_STD_ALL) { + unsigned int status = 0; + v4l2_std_id std = 0; + + decoder_call(zr, video, querystd, &std); + decoder_call(zr, core, s_std, std); + + /* let changes come into effect */ + ssleep(2); + + decoder_call(zr, video, g_input_status, &status); + if (status & V4L2_IN_ST_NO_SIGNAL) { + dprintk(1, + KERN_ERR + "%s: %s - no norm detected\n", + ZR_DEVNAME(zr), __func__); + /* reset norm */ + decoder_call(zr, core, s_std, zr->norm); + return -EIO; + } + + norm = std; + } + if (norm & V4L2_STD_SECAM) + zr->timing = zr->card.tvn[2]; + else if (norm & V4L2_STD_NTSC) + zr->timing = zr->card.tvn[1]; + else + zr->timing = zr->card.tvn[0]; + + /* We switch overlay off and on since a change in the + * norm needs different VFE settings */ + on = zr->overlay_active && !zr->v4l_memgrab_active; + if (on) + zr36057_overlay(zr, 0); + + decoder_call(zr, core, s_std, norm); + encoder_call(zr, video, s_std_output, norm); + + if (on) + zr36057_overlay(zr, 1); + + /* Make sure the changes come into effect */ + zr->norm = norm; + + return 0; +} + +static int +zoran_set_input (struct zoran *zr, + int input) +{ + if (input == zr->input) { + return 0; + } + + if (zr->v4l_buffers.active != ZORAN_FREE || + zr->jpg_buffers.active != ZORAN_FREE) { + dprintk(1, + KERN_WARNING + "%s: %s called while in playback/capture mode\n", + ZR_DEVNAME(zr), __func__); + return -EBUSY; + } + + if (input < 0 || input >= zr->card.inputs) { + dprintk(1, + KERN_ERR + "%s: %s - unnsupported input %d\n", + ZR_DEVNAME(zr), __func__, input); + return -EINVAL; + } + + zr->input = input; + + decoder_call(zr, video, s_routing, + zr->card.input[input].muxsel, 0, 0); + + return 0; +} + +/* + * ioctl routine + */ + +static int zoran_querycap(struct file *file, void *__fh, struct v4l2_capability *cap) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + memset(cap, 0, sizeof(*cap)); + strncpy(cap->card, ZR_DEVNAME(zr), sizeof(cap->card)-1); + strncpy(cap->driver, "zoran", sizeof(cap->driver)-1); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", + pci_name(zr->pci_dev)); + cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OVERLAY; + return 0; +} + +static int zoran_enum_fmt(struct zoran *zr, struct v4l2_fmtdesc *fmt, int flag) +{ + unsigned int num, i; + + for (num = i = 0; i < NUM_FORMATS; i++) { + if (zoran_formats[i].flags & flag && num++ == fmt->index) { + strncpy(fmt->description, zoran_formats[i].name, + sizeof(fmt->description) - 1); + /* fmt struct pre-zeroed, so adding '\0' not needed */ + fmt->pixelformat = zoran_formats[i].fourcc; + if (zoran_formats[i].flags & ZORAN_FORMAT_COMPRESSED) + fmt->flags |= V4L2_FMT_FLAG_COMPRESSED; + return 0; + } + } + return -EINVAL; +} + +static int zoran_enum_fmt_vid_cap(struct file *file, void *__fh, + struct v4l2_fmtdesc *f) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + return zoran_enum_fmt(zr, f, ZORAN_FORMAT_CAPTURE); +} + +static int zoran_enum_fmt_vid_out(struct file *file, void *__fh, + struct v4l2_fmtdesc *f) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + return zoran_enum_fmt(zr, f, ZORAN_FORMAT_PLAYBACK); +} + +static int zoran_enum_fmt_vid_overlay(struct file *file, void *__fh, + struct v4l2_fmtdesc *f) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + return zoran_enum_fmt(zr, f, ZORAN_FORMAT_OVERLAY); +} + +static int zoran_g_fmt_vid_out(struct file *file, void *__fh, + struct v4l2_format *fmt) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + mutex_lock(&zr->resource_lock); + + fmt->fmt.pix.width = fh->jpg_settings.img_width / fh->jpg_settings.HorDcm; + fmt->fmt.pix.height = fh->jpg_settings.img_height * 2 / + (fh->jpg_settings.VerDcm * fh->jpg_settings.TmpDcm); + fmt->fmt.pix.sizeimage = zoran_v4l2_calc_bufsize(&fh->jpg_settings); + fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + if (fh->jpg_settings.TmpDcm == 1) + fmt->fmt.pix.field = (fh->jpg_settings.odd_even ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = (fh->jpg_settings.odd_even ? + V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM); + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + mutex_unlock(&zr->resource_lock); + return 0; +} + +static int zoran_g_fmt_vid_cap(struct file *file, void *__fh, + struct v4l2_format *fmt) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + if (fh->map_mode != ZORAN_MAP_MODE_RAW) + return zoran_g_fmt_vid_out(file, fh, fmt); + + mutex_lock(&zr->resource_lock); + fmt->fmt.pix.width = fh->v4l_settings.width; + fmt->fmt.pix.height = fh->v4l_settings.height; + fmt->fmt.pix.sizeimage = fh->v4l_settings.bytesperline * + fh->v4l_settings.height; + fmt->fmt.pix.pixelformat = fh->v4l_settings.format->fourcc; + fmt->fmt.pix.colorspace = fh->v4l_settings.format->colorspace; + fmt->fmt.pix.bytesperline = fh->v4l_settings.bytesperline; + if (BUZ_MAX_HEIGHT < (fh->v4l_settings.height * 2)) + fmt->fmt.pix.field = V4L2_FIELD_INTERLACED; + else + fmt->fmt.pix.field = V4L2_FIELD_TOP; + mutex_unlock(&zr->resource_lock); + return 0; +} + +static int zoran_g_fmt_vid_overlay(struct file *file, void *__fh, + struct v4l2_format *fmt) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + mutex_lock(&zr->resource_lock); + + fmt->fmt.win.w.left = fh->overlay_settings.x; + fmt->fmt.win.w.top = fh->overlay_settings.y; + fmt->fmt.win.w.width = fh->overlay_settings.width; + fmt->fmt.win.w.height = fh->overlay_settings.height; + if (fh->overlay_settings.width * 2 > BUZ_MAX_HEIGHT) + fmt->fmt.win.field = V4L2_FIELD_INTERLACED; + else + fmt->fmt.win.field = V4L2_FIELD_TOP; + + mutex_unlock(&zr->resource_lock); + return 0; +} + +static int zoran_try_fmt_vid_overlay(struct file *file, void *__fh, + struct v4l2_format *fmt) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + mutex_lock(&zr->resource_lock); + + if (fmt->fmt.win.w.width > BUZ_MAX_WIDTH) + fmt->fmt.win.w.width = BUZ_MAX_WIDTH; + if (fmt->fmt.win.w.width < BUZ_MIN_WIDTH) + fmt->fmt.win.w.width = BUZ_MIN_WIDTH; + if (fmt->fmt.win.w.height > BUZ_MAX_HEIGHT) + fmt->fmt.win.w.height = BUZ_MAX_HEIGHT; + if (fmt->fmt.win.w.height < BUZ_MIN_HEIGHT) + fmt->fmt.win.w.height = BUZ_MIN_HEIGHT; + + mutex_unlock(&zr->resource_lock); + return 0; +} + +static int zoran_try_fmt_vid_out(struct file *file, void *__fh, + struct v4l2_format *fmt) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + struct zoran_jpg_settings settings; + int res = 0; + + if (fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + + mutex_lock(&zr->resource_lock); + settings = fh->jpg_settings; + + /* we actually need to set 'real' parameters now */ + if ((fmt->fmt.pix.height * 2) > BUZ_MAX_HEIGHT) + settings.TmpDcm = 1; + else + settings.TmpDcm = 2; + settings.decimation = 0; + if (fmt->fmt.pix.height <= fh->jpg_settings.img_height / 2) + settings.VerDcm = 2; + else + settings.VerDcm = 1; + if (fmt->fmt.pix.width <= fh->jpg_settings.img_width / 4) + settings.HorDcm = 4; + else if (fmt->fmt.pix.width <= fh->jpg_settings.img_width / 2) + settings.HorDcm = 2; + else + settings.HorDcm = 1; + if (settings.TmpDcm == 1) + settings.field_per_buff = 2; + else + settings.field_per_buff = 1; + + if (settings.HorDcm > 1) { + settings.img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings.img_width = (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + } else { + settings.img_x = 0; + settings.img_width = BUZ_MAX_WIDTH; + } + + /* check */ + res = zoran_check_jpg_settings(zr, &settings, 1); + if (res) + goto tryfmt_unlock_and_return; + + /* tell the user what we actually did */ + fmt->fmt.pix.width = settings.img_width / settings.HorDcm; + fmt->fmt.pix.height = settings.img_height * 2 / + (settings.TmpDcm * settings.VerDcm); + if (settings.TmpDcm == 1) + fmt->fmt.pix.field = (fh->jpg_settings.odd_even ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = (fh->jpg_settings.odd_even ? + V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM); + + fmt->fmt.pix.sizeimage = zoran_v4l2_calc_bufsize(&settings); + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; +tryfmt_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; +} + +static int zoran_try_fmt_vid_cap(struct file *file, void *__fh, + struct v4l2_format *fmt) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int bpp; + int i; + + if (fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) + return zoran_try_fmt_vid_out(file, fh, fmt); + + mutex_lock(&zr->resource_lock); + + for (i = 0; i < NUM_FORMATS; i++) + if (zoran_formats[i].fourcc == fmt->fmt.pix.pixelformat) + break; + + if (i == NUM_FORMATS) { + mutex_unlock(&zr->resource_lock); + return -EINVAL; + } + + bpp = DIV_ROUND_UP(zoran_formats[i].depth, 8); + v4l_bound_align_image( + &fmt->fmt.pix.width, BUZ_MIN_WIDTH, BUZ_MAX_WIDTH, bpp == 2 ? 1 : 2, + &fmt->fmt.pix.height, BUZ_MIN_HEIGHT, BUZ_MAX_HEIGHT, 0, 0); + mutex_unlock(&zr->resource_lock); + + return 0; +} + +static int zoran_s_fmt_vid_overlay(struct file *file, void *__fh, + struct v4l2_format *fmt) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res; + + dprintk(3, "x=%d, y=%d, w=%d, h=%d, cnt=%d, map=0x%p\n", + fmt->fmt.win.w.left, fmt->fmt.win.w.top, + fmt->fmt.win.w.width, + fmt->fmt.win.w.height, + fmt->fmt.win.clipcount, + fmt->fmt.win.bitmap); + mutex_lock(&zr->resource_lock); + res = setup_window(fh, fmt->fmt.win.w.left, fmt->fmt.win.w.top, + fmt->fmt.win.w.width, fmt->fmt.win.w.height, + (struct v4l2_clip __user *)fmt->fmt.win.clips, + fmt->fmt.win.clipcount, fmt->fmt.win.bitmap); + mutex_unlock(&zr->resource_lock); + return res; +} + +static int zoran_s_fmt_vid_out(struct file *file, void *__fh, + struct v4l2_format *fmt) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + __le32 printformat = __cpu_to_le32(fmt->fmt.pix.pixelformat); + struct zoran_jpg_settings settings; + int res = 0; + + dprintk(3, "size=%dx%d, fmt=0x%x (%4.4s)\n", + fmt->fmt.pix.width, fmt->fmt.pix.height, + fmt->fmt.pix.pixelformat, + (char *) &printformat); + if (fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + + mutex_lock(&zr->resource_lock); + + if (fh->buffers.allocated) { + dprintk(1, KERN_ERR "%s: VIDIOC_S_FMT - cannot change capture mode\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto sfmtjpg_unlock_and_return; + } + + settings = fh->jpg_settings; + + /* we actually need to set 'real' parameters now */ + if (fmt->fmt.pix.height * 2 > BUZ_MAX_HEIGHT) + settings.TmpDcm = 1; + else + settings.TmpDcm = 2; + settings.decimation = 0; + if (fmt->fmt.pix.height <= fh->jpg_settings.img_height / 2) + settings.VerDcm = 2; + else + settings.VerDcm = 1; + if (fmt->fmt.pix.width <= fh->jpg_settings.img_width / 4) + settings.HorDcm = 4; + else if (fmt->fmt.pix.width <= fh->jpg_settings.img_width / 2) + settings.HorDcm = 2; + else + settings.HorDcm = 1; + if (settings.TmpDcm == 1) + settings.field_per_buff = 2; + else + settings.field_per_buff = 1; + + if (settings.HorDcm > 1) { + settings.img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings.img_width = (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + } else { + settings.img_x = 0; + settings.img_width = BUZ_MAX_WIDTH; + } + + /* check */ + res = zoran_check_jpg_settings(zr, &settings, 0); + if (res) + goto sfmtjpg_unlock_and_return; + + /* it's ok, so set them */ + fh->jpg_settings = settings; + + map_mode_jpg(fh, fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT); + fh->buffers.buffer_size = zoran_v4l2_calc_bufsize(&fh->jpg_settings); + + /* tell the user what we actually did */ + fmt->fmt.pix.width = settings.img_width / settings.HorDcm; + fmt->fmt.pix.height = settings.img_height * 2 / + (settings.TmpDcm * settings.VerDcm); + if (settings.TmpDcm == 1) + fmt->fmt.pix.field = (fh->jpg_settings.odd_even ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = (fh->jpg_settings.odd_even ? + V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM); + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.sizeimage = fh->buffers.buffer_size; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + +sfmtjpg_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; +} + +static int zoran_s_fmt_vid_cap(struct file *file, void *__fh, + struct v4l2_format *fmt) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int i; + int res = 0; + + if (fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) + return zoran_s_fmt_vid_out(file, fh, fmt); + + for (i = 0; i < NUM_FORMATS; i++) + if (fmt->fmt.pix.pixelformat == zoran_formats[i].fourcc) + break; + if (i == NUM_FORMATS) { + dprintk(1, KERN_ERR "%s: VIDIOC_S_FMT - unknown/unsupported format 0x%x\n", + ZR_DEVNAME(zr), fmt->fmt.pix.pixelformat); + return -EINVAL; + } + + mutex_lock(&zr->resource_lock); + + if ((fh->map_mode != ZORAN_MAP_MODE_RAW && fh->buffers.allocated) || + fh->buffers.active != ZORAN_FREE) { + dprintk(1, KERN_ERR "%s: VIDIOC_S_FMT - cannot change capture mode\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto sfmtv4l_unlock_and_return; + } + if (fmt->fmt.pix.height > BUZ_MAX_HEIGHT) + fmt->fmt.pix.height = BUZ_MAX_HEIGHT; + if (fmt->fmt.pix.width > BUZ_MAX_WIDTH) + fmt->fmt.pix.width = BUZ_MAX_WIDTH; + + map_mode_raw(fh); + + res = zoran_v4l_set_format(fh, fmt->fmt.pix.width, fmt->fmt.pix.height, + &zoran_formats[i]); + if (res) + goto sfmtv4l_unlock_and_return; + + /* tell the user the results/missing stuff */ + fmt->fmt.pix.bytesperline = fh->v4l_settings.bytesperline; + fmt->fmt.pix.sizeimage = fh->v4l_settings.height * fh->v4l_settings.bytesperline; + fmt->fmt.pix.colorspace = fh->v4l_settings.format->colorspace; + if (BUZ_MAX_HEIGHT < (fh->v4l_settings.height * 2)) + fmt->fmt.pix.field = V4L2_FIELD_INTERLACED; + else + fmt->fmt.pix.field = V4L2_FIELD_TOP; + +sfmtv4l_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; +} + +static int zoran_g_fbuf(struct file *file, void *__fh, + struct v4l2_framebuffer *fb) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + memset(fb, 0, sizeof(*fb)); + mutex_lock(&zr->resource_lock); + fb->base = zr->vbuf_base; + fb->fmt.width = zr->vbuf_width; + fb->fmt.height = zr->vbuf_height; + if (zr->overlay_settings.format) + fb->fmt.pixelformat = fh->overlay_settings.format->fourcc; + fb->fmt.bytesperline = zr->vbuf_bytesperline; + mutex_unlock(&zr->resource_lock); + fb->fmt.colorspace = V4L2_COLORSPACE_SRGB; + fb->fmt.field = V4L2_FIELD_INTERLACED; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + + return 0; +} + +static int zoran_s_fbuf(struct file *file, void *__fh, + const struct v4l2_framebuffer *fb) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int i, res = 0; + __le32 printformat = __cpu_to_le32(fb->fmt.pixelformat); + + for (i = 0; i < NUM_FORMATS; i++) + if (zoran_formats[i].fourcc == fb->fmt.pixelformat) + break; + if (i == NUM_FORMATS) { + dprintk(1, KERN_ERR "%s: VIDIOC_S_FBUF - format=0x%x (%4.4s) not allowed\n", + ZR_DEVNAME(zr), fb->fmt.pixelformat, + (char *)&printformat); + return -EINVAL; + } + + mutex_lock(&zr->resource_lock); + res = setup_fbuffer(fh, fb->base, &zoran_formats[i], fb->fmt.width, + fb->fmt.height, fb->fmt.bytesperline); + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int zoran_overlay(struct file *file, void *__fh, unsigned int on) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res; + + mutex_lock(&zr->resource_lock); + res = setup_overlay(fh, on); + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int zoran_streamoff(struct file *file, void *__fh, enum v4l2_buf_type type); + +static int zoran_reqbufs(struct file *file, void *__fh, struct v4l2_requestbuffers *req) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res = 0; + + if (req->memory != V4L2_MEMORY_MMAP) { + dprintk(2, + KERN_ERR + "%s: only MEMORY_MMAP capture is supported, not %d\n", + ZR_DEVNAME(zr), req->memory); + return -EINVAL; + } + + if (req->count == 0) + return zoran_streamoff(file, fh, req->type); + + mutex_lock(&zr->resource_lock); + if (fh->buffers.allocated) { + dprintk(2, + KERN_ERR + "%s: VIDIOC_REQBUFS - buffers already allocated\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto v4l2reqbuf_unlock_and_return; + } + + if (fh->map_mode == ZORAN_MAP_MODE_RAW && + req->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + /* control user input */ + if (req->count < 2) + req->count = 2; + if (req->count > v4l_nbufs) + req->count = v4l_nbufs; + + /* The next mmap will map the V4L buffers */ + map_mode_raw(fh); + fh->buffers.num_buffers = req->count; + + if (v4l_fbuffer_alloc(fh)) { + res = -ENOMEM; + goto v4l2reqbuf_unlock_and_return; + } + } else if (fh->map_mode == ZORAN_MAP_MODE_JPG_REC || + fh->map_mode == ZORAN_MAP_MODE_JPG_PLAY) { + /* we need to calculate size ourselves now */ + if (req->count < 4) + req->count = 4; + if (req->count > jpg_nbufs) + req->count = jpg_nbufs; + + /* The next mmap will map the MJPEG buffers */ + map_mode_jpg(fh, req->type == V4L2_BUF_TYPE_VIDEO_OUTPUT); + fh->buffers.num_buffers = req->count; + fh->buffers.buffer_size = zoran_v4l2_calc_bufsize(&fh->jpg_settings); + + if (jpg_fbuffer_alloc(fh)) { + res = -ENOMEM; + goto v4l2reqbuf_unlock_and_return; + } + } else { + dprintk(1, + KERN_ERR + "%s: VIDIOC_REQBUFS - unknown type %d\n", + ZR_DEVNAME(zr), req->type); + res = -EINVAL; + goto v4l2reqbuf_unlock_and_return; + } +v4l2reqbuf_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int zoran_querybuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res; + + mutex_lock(&zr->resource_lock); + res = zoran_v4l2_buffer_status(fh, buf, buf->index); + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int zoran_qbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res = 0, codec_mode, buf_type; + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + dprintk(1, KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto qbuf_unlock_and_return; + } + + res = zoran_v4l_queue_frame(fh, buf->index); + if (res) + goto qbuf_unlock_and_return; + if (!zr->v4l_memgrab_active && fh->buffers.active == ZORAN_LOCKED) + zr36057_set_memgrab(zr, 1); + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + if (fh->map_mode == ZORAN_MAP_MODE_JPG_PLAY) { + buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + codec_mode = BUZ_MODE_MOTION_DECOMPRESS; + } else { + buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + codec_mode = BUZ_MODE_MOTION_COMPRESS; + } + + if (buf->type != buf_type) { + dprintk(1, KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto qbuf_unlock_and_return; + } + + res = zoran_jpg_queue_frame(fh, buf->index, codec_mode); + if (res != 0) + goto qbuf_unlock_and_return; + if (zr->codec_mode == BUZ_MODE_IDLE && + fh->buffers.active == ZORAN_LOCKED) + zr36057_enable_jpg(zr, codec_mode); + + break; + + default: + dprintk(1, KERN_ERR + "%s: VIDIOC_QBUF - unsupported type %d\n", + ZR_DEVNAME(zr), buf->type); + res = -EINVAL; + break; + } +qbuf_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int zoran_dqbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res = 0, buf_type, num = -1; /* compiler borks here (?) */ + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + dprintk(1, KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto dqbuf_unlock_and_return; + } + + num = zr->v4l_pend[zr->v4l_sync_tail & V4L_MASK_FRAME]; + if (file->f_flags & O_NONBLOCK && + zr->v4l_buffers.buffer[num].state != BUZ_STATE_DONE) { + res = -EAGAIN; + goto dqbuf_unlock_and_return; + } + res = v4l_sync(fh, num); + if (res) + goto dqbuf_unlock_and_return; + zr->v4l_sync_tail++; + res = zoran_v4l2_buffer_status(fh, buf, num); + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + { + struct zoran_sync bs; + + if (fh->map_mode == ZORAN_MAP_MODE_JPG_PLAY) + buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + else + buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (buf->type != buf_type) { + dprintk(1, KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto dqbuf_unlock_and_return; + } + + num = zr->jpg_pend[zr->jpg_que_tail & BUZ_MASK_FRAME]; + + if (file->f_flags & O_NONBLOCK && + zr->jpg_buffers.buffer[num].state != BUZ_STATE_DONE) { + res = -EAGAIN; + goto dqbuf_unlock_and_return; + } + bs.frame = 0; /* suppress compiler warning */ + res = jpg_sync(fh, &bs); + if (res) + goto dqbuf_unlock_and_return; + res = zoran_v4l2_buffer_status(fh, buf, bs.frame); + break; + } + + default: + dprintk(1, KERN_ERR + "%s: VIDIOC_DQBUF - unsupported type %d\n", + ZR_DEVNAME(zr), buf->type); + res = -EINVAL; + break; + } +dqbuf_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int zoran_streamon(struct file *file, void *__fh, enum v4l2_buf_type type) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res = 0; + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: /* raw capture */ + if (zr->v4l_buffers.active != ZORAN_ACTIVE || + fh->buffers.active != ZORAN_ACTIVE) { + res = -EBUSY; + goto strmon_unlock_and_return; + } + + zr->v4l_buffers.active = fh->buffers.active = ZORAN_LOCKED; + zr->v4l_settings = fh->v4l_settings; + + zr->v4l_sync_tail = zr->v4l_pend_tail; + if (!zr->v4l_memgrab_active && + zr->v4l_pend_head != zr->v4l_pend_tail) { + zr36057_set_memgrab(zr, 1); + } + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + /* what is the codec mode right now? */ + if (zr->jpg_buffers.active != ZORAN_ACTIVE || + fh->buffers.active != ZORAN_ACTIVE) { + res = -EBUSY; + goto strmon_unlock_and_return; + } + + zr->jpg_buffers.active = fh->buffers.active = ZORAN_LOCKED; + + if (zr->jpg_que_head != zr->jpg_que_tail) { + /* Start the jpeg codec when the first frame is queued */ + jpeg_start(zr); + } + break; + + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_STREAMON - invalid map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + res = -EINVAL; + break; + } +strmon_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int zoran_streamoff(struct file *file, void *__fh, enum v4l2_buf_type type) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int i, res = 0; + unsigned long flags; + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: /* raw capture */ + if (fh->buffers.active == ZORAN_FREE && + zr->v4l_buffers.active != ZORAN_FREE) { + res = -EPERM; /* stay off other's settings! */ + goto strmoff_unlock_and_return; + } + if (zr->v4l_buffers.active == ZORAN_FREE) + goto strmoff_unlock_and_return; + + spin_lock_irqsave(&zr->spinlock, flags); + /* unload capture */ + if (zr->v4l_memgrab_active) { + + zr36057_set_memgrab(zr, 0); + } + + for (i = 0; i < fh->buffers.num_buffers; i++) + zr->v4l_buffers.buffer[i].state = BUZ_STATE_USER; + fh->buffers = zr->v4l_buffers; + + zr->v4l_buffers.active = fh->buffers.active = ZORAN_FREE; + + zr->v4l_grab_seq = 0; + zr->v4l_pend_head = zr->v4l_pend_tail = 0; + zr->v4l_sync_tail = 0; + + spin_unlock_irqrestore(&zr->spinlock, flags); + + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + if (fh->buffers.active == ZORAN_FREE && + zr->jpg_buffers.active != ZORAN_FREE) { + res = -EPERM; /* stay off other's settings! */ + goto strmoff_unlock_and_return; + } + if (zr->jpg_buffers.active == ZORAN_FREE) + goto strmoff_unlock_and_return; + + res = jpg_qbuf(fh, -1, + (fh->map_mode == ZORAN_MAP_MODE_JPG_REC) ? + BUZ_MODE_MOTION_COMPRESS : + BUZ_MODE_MOTION_DECOMPRESS); + if (res) + goto strmoff_unlock_and_return; + break; + default: + dprintk(1, KERN_ERR + "%s: VIDIOC_STREAMOFF - invalid map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + res = -EINVAL; + break; + } +strmoff_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int zoran_queryctrl(struct file *file, void *__fh, + struct v4l2_queryctrl *ctrl) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + /* we only support hue/saturation/contrast/brightness */ + if (ctrl->id < V4L2_CID_BRIGHTNESS || + ctrl->id > V4L2_CID_HUE) + return -EINVAL; + + decoder_call(zr, core, queryctrl, ctrl); + + return 0; +} + +static int zoran_g_ctrl(struct file *file, void *__fh, struct v4l2_control *ctrl) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + /* we only support hue/saturation/contrast/brightness */ + if (ctrl->id < V4L2_CID_BRIGHTNESS || + ctrl->id > V4L2_CID_HUE) + return -EINVAL; + + mutex_lock(&zr->resource_lock); + decoder_call(zr, core, g_ctrl, ctrl); + mutex_unlock(&zr->resource_lock); + + return 0; +} + +static int zoran_s_ctrl(struct file *file, void *__fh, struct v4l2_control *ctrl) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + /* we only support hue/saturation/contrast/brightness */ + if (ctrl->id < V4L2_CID_BRIGHTNESS || + ctrl->id > V4L2_CID_HUE) + return -EINVAL; + + mutex_lock(&zr->resource_lock); + decoder_call(zr, core, s_ctrl, ctrl); + mutex_unlock(&zr->resource_lock); + + return 0; +} + +static int zoran_g_std(struct file *file, void *__fh, v4l2_std_id *std) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + mutex_lock(&zr->resource_lock); + *std = zr->norm; + mutex_unlock(&zr->resource_lock); + return 0; +} + +static int zoran_s_std(struct file *file, void *__fh, v4l2_std_id *std) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res = 0; + + mutex_lock(&zr->resource_lock); + res = zoran_set_norm(zr, *std); + if (res) + goto sstd_unlock_and_return; + + res = wait_grab_pending(zr); +sstd_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; +} + +static int zoran_enum_input(struct file *file, void *__fh, + struct v4l2_input *inp) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + if (inp->index >= zr->card.inputs) + return -EINVAL; + + strncpy(inp->name, zr->card.input[inp->index].name, + sizeof(inp->name) - 1); + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = V4L2_STD_ALL; + + /* Get status of video decoder */ + mutex_lock(&zr->resource_lock); + decoder_call(zr, video, g_input_status, &inp->status); + mutex_unlock(&zr->resource_lock); + return 0; +} + +static int zoran_g_input(struct file *file, void *__fh, unsigned int *input) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + + mutex_lock(&zr->resource_lock); + *input = zr->input; + mutex_unlock(&zr->resource_lock); + + return 0; +} + +static int zoran_s_input(struct file *file, void *__fh, unsigned int input) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res; + + mutex_lock(&zr->resource_lock); + res = zoran_set_input(zr, input); + if (res) + goto sinput_unlock_and_return; + + /* Make sure the changes come into effect */ + res = wait_grab_pending(zr); +sinput_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; +} + +static int zoran_enum_output(struct file *file, void *__fh, + struct v4l2_output *outp) +{ + if (outp->index != 0) + return -EINVAL; + + outp->index = 0; + outp->type = V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY; + strncpy(outp->name, "Autodetect", sizeof(outp->name)-1); + + return 0; +} + +static int zoran_g_output(struct file *file, void *__fh, unsigned int *output) +{ + *output = 0; + + return 0; +} + +static int zoran_s_output(struct file *file, void *__fh, unsigned int output) +{ + if (output != 0) + return -EINVAL; + + return 0; +} + +/* cropping (sub-frame capture) */ +static int zoran_cropcap(struct file *file, void *__fh, + struct v4l2_cropcap *cropcap) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int type = cropcap->type, res = 0; + + memset(cropcap, 0, sizeof(*cropcap)); + cropcap->type = type; + + mutex_lock(&zr->resource_lock); + + if (cropcap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + (cropcap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fh->map_mode == ZORAN_MAP_MODE_RAW)) { + dprintk(1, KERN_ERR + "%s: VIDIOC_CROPCAP - subcapture only supported for compressed capture\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto cropcap_unlock_and_return; + } + + cropcap->bounds.top = cropcap->bounds.left = 0; + cropcap->bounds.width = BUZ_MAX_WIDTH; + cropcap->bounds.height = BUZ_MAX_HEIGHT; + cropcap->defrect.top = cropcap->defrect.left = 0; + cropcap->defrect.width = BUZ_MIN_WIDTH; + cropcap->defrect.height = BUZ_MIN_HEIGHT; +cropcap_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; +} + +static int zoran_g_crop(struct file *file, void *__fh, struct v4l2_crop *crop) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int type = crop->type, res = 0; + + memset(crop, 0, sizeof(*crop)); + crop->type = type; + + mutex_lock(&zr->resource_lock); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fh->map_mode == ZORAN_MAP_MODE_RAW)) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_G_CROP - subcapture only supported for compressed capture\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto gcrop_unlock_and_return; + } + + crop->c.top = fh->jpg_settings.img_y; + crop->c.left = fh->jpg_settings.img_x; + crop->c.width = fh->jpg_settings.img_width; + crop->c.height = fh->jpg_settings.img_height; + +gcrop_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int zoran_s_crop(struct file *file, void *__fh, const struct v4l2_crop *crop) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res = 0; + struct zoran_jpg_settings settings; + + settings = fh->jpg_settings; + + mutex_lock(&zr->resource_lock); + + if (fh->buffers.allocated) { + dprintk(1, KERN_ERR + "%s: VIDIOC_S_CROP - cannot change settings while active\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto scrop_unlock_and_return; + } + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fh->map_mode == ZORAN_MAP_MODE_RAW)) { + dprintk(1, KERN_ERR + "%s: VIDIOC_G_CROP - subcapture only supported for compressed capture\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto scrop_unlock_and_return; + } + + /* move into a form that we understand */ + settings.img_x = crop->c.left; + settings.img_y = crop->c.top; + settings.img_width = crop->c.width; + settings.img_height = crop->c.height; + + /* check validity */ + res = zoran_check_jpg_settings(zr, &settings, 0); + if (res) + goto scrop_unlock_and_return; + + /* accept */ + fh->jpg_settings = settings; + +scrop_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; +} + +static int zoran_g_jpegcomp(struct file *file, void *__fh, + struct v4l2_jpegcompression *params) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + memset(params, 0, sizeof(*params)); + + mutex_lock(&zr->resource_lock); + + params->quality = fh->jpg_settings.jpg_comp.quality; + params->APPn = fh->jpg_settings.jpg_comp.APPn; + memcpy(params->APP_data, + fh->jpg_settings.jpg_comp.APP_data, + fh->jpg_settings.jpg_comp.APP_len); + params->APP_len = fh->jpg_settings.jpg_comp.APP_len; + memcpy(params->COM_data, + fh->jpg_settings.jpg_comp.COM_data, + fh->jpg_settings.jpg_comp.COM_len); + params->COM_len = fh->jpg_settings.jpg_comp.COM_len; + params->jpeg_markers = + fh->jpg_settings.jpg_comp.jpeg_markers; + + mutex_unlock(&zr->resource_lock); + + return 0; +} + +static int zoran_s_jpegcomp(struct file *file, void *__fh, + const struct v4l2_jpegcompression *params) +{ + struct zoran_fh *fh = __fh; + struct zoran *zr = fh->zr; + int res = 0; + struct zoran_jpg_settings settings; + + settings = fh->jpg_settings; + + settings.jpg_comp = *params; + + mutex_lock(&zr->resource_lock); + + if (fh->buffers.active != ZORAN_FREE) { + dprintk(1, KERN_WARNING + "%s: VIDIOC_S_JPEGCOMP called while in playback/capture mode\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto sjpegc_unlock_and_return; + } + + res = zoran_check_jpg_settings(zr, &settings, 0); + if (res) + goto sjpegc_unlock_and_return; + if (!fh->buffers.allocated) + fh->buffers.buffer_size = + zoran_v4l2_calc_bufsize(&fh->jpg_settings); + fh->jpg_settings.jpg_comp = settings.jpg_comp; +sjpegc_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + +static unsigned int +zoran_poll (struct file *file, + poll_table *wait) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int res = 0, frame; + unsigned long flags; + + /* we should check whether buffers are ready to be synced on + * (w/o waits - O_NONBLOCK) here + * if ready for read (sync), return POLLIN|POLLRDNORM, + * if ready for write (sync), return POLLOUT|POLLWRNORM, + * if error, return POLLERR, + * if no buffers queued or so, return POLLNVAL + */ + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + poll_wait(file, &zr->v4l_capq, wait); + frame = zr->v4l_pend[zr->v4l_sync_tail & V4L_MASK_FRAME]; + + spin_lock_irqsave(&zr->spinlock, flags); + dprintk(3, + KERN_DEBUG + "%s: %s() raw - active=%c, sync_tail=%lu/%c, pend_tail=%lu, pend_head=%lu\n", + ZR_DEVNAME(zr), __func__, + "FAL"[fh->buffers.active], zr->v4l_sync_tail, + "UPMD"[zr->v4l_buffers.buffer[frame].state], + zr->v4l_pend_tail, zr->v4l_pend_head); + /* Process is the one capturing? */ + if (fh->buffers.active != ZORAN_FREE && + /* Buffer ready to DQBUF? */ + zr->v4l_buffers.buffer[frame].state == BUZ_STATE_DONE) + res = POLLIN | POLLRDNORM; + spin_unlock_irqrestore(&zr->spinlock, flags); + + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + poll_wait(file, &zr->jpg_capq, wait); + frame = zr->jpg_pend[zr->jpg_que_tail & BUZ_MASK_FRAME]; + + spin_lock_irqsave(&zr->spinlock, flags); + dprintk(3, + KERN_DEBUG + "%s: %s() jpg - active=%c, que_tail=%lu/%c, que_head=%lu, dma=%lu/%lu\n", + ZR_DEVNAME(zr), __func__, + "FAL"[fh->buffers.active], zr->jpg_que_tail, + "UPMD"[zr->jpg_buffers.buffer[frame].state], + zr->jpg_que_head, zr->jpg_dma_tail, zr->jpg_dma_head); + if (fh->buffers.active != ZORAN_FREE && + zr->jpg_buffers.buffer[frame].state == BUZ_STATE_DONE) { + if (fh->map_mode == ZORAN_MAP_MODE_JPG_REC) + res = POLLIN | POLLRDNORM; + else + res = POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&zr->spinlock, flags); + + break; + + default: + dprintk(1, + KERN_ERR + "%s: %s - internal error, unknown map_mode=%d\n", + ZR_DEVNAME(zr), __func__, fh->map_mode); + res = POLLNVAL; + } + + mutex_unlock(&zr->resource_lock); + + return res; +} + + +/* + * This maps the buffers to user space. + * + * Depending on the state of fh->map_mode + * the V4L or the MJPEG buffers are mapped + * per buffer or all together + * + * Note that we need to connect to some + * unmap signal event to unmap the de-allocate + * the buffer accordingly (zoran_vm_close()) + */ + +static void +zoran_vm_open (struct vm_area_struct *vma) +{ + struct zoran_mapping *map = vma->vm_private_data; + + map->count++; +} + +static void +zoran_vm_close (struct vm_area_struct *vma) +{ + struct zoran_mapping *map = vma->vm_private_data; + struct zoran_fh *fh = map->fh; + struct zoran *zr = fh->zr; + int i; + + if (--map->count > 0) + return; + + dprintk(3, KERN_INFO "%s: %s - munmap(%s)\n", ZR_DEVNAME(zr), + __func__, mode_name(fh->map_mode)); + + for (i = 0; i < fh->buffers.num_buffers; i++) { + if (fh->buffers.buffer[i].map == map) + fh->buffers.buffer[i].map = NULL; + } + kfree(map); + + /* Any buffers still mapped? */ + for (i = 0; i < fh->buffers.num_buffers; i++) + if (fh->buffers.buffer[i].map) + return; + + dprintk(3, KERN_INFO "%s: %s - free %s buffers\n", ZR_DEVNAME(zr), + __func__, mode_name(fh->map_mode)); + + mutex_lock(&zr->resource_lock); + + if (fh->map_mode == ZORAN_MAP_MODE_RAW) { + if (fh->buffers.active != ZORAN_FREE) { + unsigned long flags; + + spin_lock_irqsave(&zr->spinlock, flags); + zr36057_set_memgrab(zr, 0); + zr->v4l_buffers.allocated = 0; + zr->v4l_buffers.active = fh->buffers.active = ZORAN_FREE; + spin_unlock_irqrestore(&zr->spinlock, flags); + } + v4l_fbuffer_free(fh); + } else { + if (fh->buffers.active != ZORAN_FREE) { + jpg_qbuf(fh, -1, zr->codec_mode); + zr->jpg_buffers.allocated = 0; + zr->jpg_buffers.active = fh->buffers.active = ZORAN_FREE; + } + jpg_fbuffer_free(fh); + } + + mutex_unlock(&zr->resource_lock); +} + +static const struct vm_operations_struct zoran_vm_ops = { + .open = zoran_vm_open, + .close = zoran_vm_close, +}; + +static int +zoran_mmap (struct file *file, + struct vm_area_struct *vma) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long size = (vma->vm_end - vma->vm_start); + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + int i, j; + unsigned long page, start = vma->vm_start, todo, pos, fraglen; + int first, last; + struct zoran_mapping *map; + int res = 0; + + dprintk(3, + KERN_INFO "%s: %s(%s) of 0x%08lx-0x%08lx (size=%lu)\n", + ZR_DEVNAME(zr), __func__, + mode_name(fh->map_mode), vma->vm_start, vma->vm_end, size); + + if (!(vma->vm_flags & VM_SHARED) || !(vma->vm_flags & VM_READ) || + !(vma->vm_flags & VM_WRITE)) { + dprintk(1, + KERN_ERR + "%s: %s - no MAP_SHARED/PROT_{READ,WRITE} given\n", + ZR_DEVNAME(zr), __func__); + return -EINVAL; + } + + mutex_lock(&zr->resource_lock); + + if (!fh->buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: %s(%s) - buffers not yet allocated\n", + ZR_DEVNAME(zr), __func__, mode_name(fh->map_mode)); + res = -ENOMEM; + goto mmap_unlock_and_return; + } + + first = offset / fh->buffers.buffer_size; + last = first - 1 + size / fh->buffers.buffer_size; + if (offset % fh->buffers.buffer_size != 0 || + size % fh->buffers.buffer_size != 0 || first < 0 || + last < 0 || first >= fh->buffers.num_buffers || + last >= fh->buffers.buffer_size) { + dprintk(1, + KERN_ERR + "%s: %s(%s) - offset=%lu or size=%lu invalid for bufsize=%d and numbufs=%d\n", + ZR_DEVNAME(zr), __func__, mode_name(fh->map_mode), offset, size, + fh->buffers.buffer_size, + fh->buffers.num_buffers); + res = -EINVAL; + goto mmap_unlock_and_return; + } + + /* Check if any buffers are already mapped */ + for (i = first; i <= last; i++) { + if (fh->buffers.buffer[i].map) { + dprintk(1, + KERN_ERR + "%s: %s(%s) - buffer %d already mapped\n", + ZR_DEVNAME(zr), __func__, mode_name(fh->map_mode), i); + res = -EBUSY; + goto mmap_unlock_and_return; + } + } + + /* map these buffers */ + map = kmalloc(sizeof(struct zoran_mapping), GFP_KERNEL); + if (!map) { + res = -ENOMEM; + goto mmap_unlock_and_return; + } + map->fh = fh; + map->count = 1; + + vma->vm_ops = &zoran_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = map; + + if (fh->map_mode == ZORAN_MAP_MODE_RAW) { + for (i = first; i <= last; i++) { + todo = size; + if (todo > fh->buffers.buffer_size) + todo = fh->buffers.buffer_size; + page = fh->buffers.buffer[i].v4l.fbuffer_phys; + if (remap_pfn_range(vma, start, page >> PAGE_SHIFT, + todo, PAGE_SHARED)) { + dprintk(1, + KERN_ERR + "%s: %s(V4L) - remap_pfn_range failed\n", + ZR_DEVNAME(zr), __func__); + res = -EAGAIN; + goto mmap_unlock_and_return; + } + size -= todo; + start += todo; + fh->buffers.buffer[i].map = map; + if (size == 0) + break; + } + } else { + for (i = first; i <= last; i++) { + for (j = 0; + j < fh->buffers.buffer_size / PAGE_SIZE; + j++) { + fraglen = + (le32_to_cpu(fh->buffers.buffer[i].jpg. + frag_tab[2 * j + 1]) & ~1) << 1; + todo = size; + if (todo > fraglen) + todo = fraglen; + pos = + le32_to_cpu(fh->buffers. + buffer[i].jpg.frag_tab[2 * j]); + /* should just be pos on i386 */ + page = virt_to_phys(bus_to_virt(pos)) + >> PAGE_SHIFT; + if (remap_pfn_range(vma, start, page, + todo, PAGE_SHARED)) { + dprintk(1, + KERN_ERR + "%s: %s(V4L) - remap_pfn_range failed\n", + ZR_DEVNAME(zr), __func__); + res = -EAGAIN; + goto mmap_unlock_and_return; + } + size -= todo; + start += todo; + if (size == 0) + break; + if (le32_to_cpu(fh->buffers.buffer[i].jpg. + frag_tab[2 * j + 1]) & 1) + break; /* was last fragment */ + } + fh->buffers.buffer[i].map = map; + if (size == 0) + break; + + } + } + +mmap_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + +static const struct v4l2_ioctl_ops zoran_ioctl_ops = { + .vidioc_querycap = zoran_querycap, + .vidioc_cropcap = zoran_cropcap, + .vidioc_s_crop = zoran_s_crop, + .vidioc_g_crop = zoran_g_crop, + .vidioc_enum_input = zoran_enum_input, + .vidioc_g_input = zoran_g_input, + .vidioc_s_input = zoran_s_input, + .vidioc_enum_output = zoran_enum_output, + .vidioc_g_output = zoran_g_output, + .vidioc_s_output = zoran_s_output, + .vidioc_g_fbuf = zoran_g_fbuf, + .vidioc_s_fbuf = zoran_s_fbuf, + .vidioc_g_std = zoran_g_std, + .vidioc_s_std = zoran_s_std, + .vidioc_g_jpegcomp = zoran_g_jpegcomp, + .vidioc_s_jpegcomp = zoran_s_jpegcomp, + .vidioc_overlay = zoran_overlay, + .vidioc_reqbufs = zoran_reqbufs, + .vidioc_querybuf = zoran_querybuf, + .vidioc_qbuf = zoran_qbuf, + .vidioc_dqbuf = zoran_dqbuf, + .vidioc_streamon = zoran_streamon, + .vidioc_streamoff = zoran_streamoff, + .vidioc_enum_fmt_vid_cap = zoran_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = zoran_enum_fmt_vid_out, + .vidioc_enum_fmt_vid_overlay = zoran_enum_fmt_vid_overlay, + .vidioc_g_fmt_vid_cap = zoran_g_fmt_vid_cap, + .vidioc_g_fmt_vid_out = zoran_g_fmt_vid_out, + .vidioc_g_fmt_vid_overlay = zoran_g_fmt_vid_overlay, + .vidioc_s_fmt_vid_cap = zoran_s_fmt_vid_cap, + .vidioc_s_fmt_vid_out = zoran_s_fmt_vid_out, + .vidioc_s_fmt_vid_overlay = zoran_s_fmt_vid_overlay, + .vidioc_try_fmt_vid_cap = zoran_try_fmt_vid_cap, + .vidioc_try_fmt_vid_out = zoran_try_fmt_vid_out, + .vidioc_try_fmt_vid_overlay = zoran_try_fmt_vid_overlay, + .vidioc_queryctrl = zoran_queryctrl, + .vidioc_s_ctrl = zoran_s_ctrl, + .vidioc_g_ctrl = zoran_g_ctrl, +}; + +/* please use zr->resource_lock consistently and kill this wrapper */ +static long zoran_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int ret; + + mutex_lock(&zr->other_lock); + ret = video_ioctl2(file, cmd, arg); + mutex_unlock(&zr->other_lock); + + return ret; +} + +static const struct v4l2_file_operations zoran_fops = { + .owner = THIS_MODULE, + .open = zoran_open, + .release = zoran_close, + .unlocked_ioctl = zoran_ioctl, + .read = zoran_read, + .write = zoran_write, + .mmap = zoran_mmap, + .poll = zoran_poll, +}; + +struct video_device zoran_template __devinitdata = { + .name = ZORAN_NAME, + .fops = &zoran_fops, + .ioctl_ops = &zoran_ioctl_ops, + .release = &zoran_vdev_release, + .tvnorms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, +}; + diff --git a/drivers/media/video/zoran/zoran_procfs.c b/drivers/media/pci/zoran/zoran_procfs.c similarity index 100% rename from drivers/media/video/zoran/zoran_procfs.c rename to drivers/media/pci/zoran/zoran_procfs.c diff --git a/drivers/media/video/zoran/zoran_procfs.h b/drivers/media/pci/zoran/zoran_procfs.h similarity index 100% rename from drivers/media/video/zoran/zoran_procfs.h rename to drivers/media/pci/zoran/zoran_procfs.h diff --git a/drivers/media/video/zoran/zr36016.c b/drivers/media/pci/zoran/zr36016.c similarity index 100% rename from drivers/media/video/zoran/zr36016.c rename to drivers/media/pci/zoran/zr36016.c diff --git a/drivers/media/video/zoran/zr36016.h b/drivers/media/pci/zoran/zr36016.h similarity index 100% rename from drivers/media/video/zoran/zr36016.h rename to drivers/media/pci/zoran/zr36016.h diff --git a/drivers/media/video/zoran/zr36050.c b/drivers/media/pci/zoran/zr36050.c similarity index 100% rename from drivers/media/video/zoran/zr36050.c rename to drivers/media/pci/zoran/zr36050.c diff --git a/drivers/media/video/zoran/zr36050.h b/drivers/media/pci/zoran/zr36050.h similarity index 100% rename from drivers/media/video/zoran/zr36050.h rename to drivers/media/pci/zoran/zr36050.h diff --git a/drivers/media/video/zoran/zr36057.h b/drivers/media/pci/zoran/zr36057.h similarity index 100% rename from drivers/media/video/zoran/zr36057.h rename to drivers/media/pci/zoran/zr36057.h diff --git a/drivers/media/video/zoran/zr36060.c b/drivers/media/pci/zoran/zr36060.c similarity index 100% rename from drivers/media/video/zoran/zr36060.c rename to drivers/media/pci/zoran/zr36060.c diff --git a/drivers/media/video/zoran/zr36060.h b/drivers/media/pci/zoran/zr36060.h similarity index 100% rename from drivers/media/video/zoran/zr36060.h rename to drivers/media/pci/zoran/zr36060.h diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig new file mode 100644 index 000000000000..f588d6296c76 --- /dev/null +++ b/drivers/media/platform/Kconfig @@ -0,0 +1,223 @@ +# +# Platform drivers +# All drivers here are currently for webcam support + +menuconfig V4L_PLATFORM_DRIVERS + bool "V4L platform devices" + depends on MEDIA_CAMERA_SUPPORT + default n + ---help--- + Say Y here to enable support for platform-specific V4L drivers. + +if V4L_PLATFORM_DRIVERS + +source "drivers/media/platform/marvell-ccic/Kconfig" + +config VIDEO_VIA_CAMERA + tristate "VIAFB camera controller support" + depends on FB_VIA + select VIDEOBUF_DMA_SG + select VIDEO_OV7670 + help + Driver support for the integrated camera controller in VIA + Chrome9 chipsets. Currently only tested on OLPC xo-1.5 systems + with ov7670 sensors. + +# +# Platform multimedia device configuration +# + +source "drivers/media/platform/davinci/Kconfig" + +source "drivers/media/platform/omap/Kconfig" + +source "drivers/media/platform/blackfin/Kconfig" + +config VIDEO_SH_VOU + tristate "SuperH VOU video output driver" + depends on MEDIA_CAMERA_SUPPORT + depends on VIDEO_DEV && ARCH_SHMOBILE + select VIDEOBUF_DMA_CONTIG + help + Support for the Video Output Unit (VOU) on SuperH SoCs. + +config VIDEO_VIU + tristate "Freescale VIU Video Driver" + depends on VIDEO_V4L2 && PPC_MPC512x + select VIDEOBUF_DMA_CONTIG + default y + ---help--- + Support for Freescale VIU video driver. This device captures + video data, or overlays video on DIU frame buffer. + + Say Y here if you want to enable VIU device on MPC5121e Rev2+. + In doubt, say N. + +config VIDEO_TIMBERDALE + tristate "Support for timberdale Video In/LogiWIN" + depends on VIDEO_V4L2 && I2C && DMADEVICES + select DMA_ENGINE + select TIMB_DMA + select VIDEO_ADV7180 + select VIDEOBUF_DMA_CONTIG + ---help--- + Add support for the Video In peripherial of the timberdale FPGA. + +config VIDEO_VINO + tristate "SGI Vino Video For Linux" + depends on I2C && SGI_IP22 && VIDEO_V4L2 + select VIDEO_SAA7191 if MEDIA_SUBDRV_AUTOSELECT + help + Say Y here to build in support for the Vino video input system found + on SGI Indy machines. + +config VIDEO_M32R_AR + tristate "AR devices" + depends on M32R && VIDEO_V4L2 + ---help--- + This is a video4linux driver for the Renesas AR (Artificial Retina) + camera module. + +config VIDEO_M32R_AR_M64278 + tristate "AR device with color module M64278(VGA)" + depends on PLAT_M32700UT + select VIDEO_M32R_AR + ---help--- + This is a video4linux driver for the Renesas AR (Artificial + Retina) with M64278E-800 camera module. + This module supports VGA(640x480 pixels) resolutions. + + To compile this driver as a module, choose M here: the + module will be called arv. + +config VIDEO_OMAP2 + tristate "OMAP2 Camera Capture Interface driver" + depends on VIDEO_DEV && ARCH_OMAP2 + select VIDEOBUF_DMA_SG + ---help--- + This is a v4l2 driver for the TI OMAP2 camera capture interface + +config VIDEO_OMAP3 + tristate "OMAP 3 Camera support (EXPERIMENTAL)" + depends on OMAP_IOVMM && VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && ARCH_OMAP3 && EXPERIMENTAL + ---help--- + Driver for an OMAP 3 camera controller. + +config VIDEO_OMAP3_DEBUG + bool "OMAP 3 Camera debug messages" + depends on VIDEO_OMAP3 + ---help--- + Enable debug messages on OMAP 3 camera controller driver. + +source "drivers/media/platform/soc_camera/Kconfig" +source "drivers/media/platform/s5p-fimc/Kconfig" +source "drivers/media/platform/s5p-tv/Kconfig" + +endif # V4L_PLATFORM_DRIVERS + +menuconfig V4L_MEM2MEM_DRIVERS + bool "Memory-to-memory multimedia devices" + depends on VIDEO_V4L2 + depends on MEDIA_CAMERA_SUPPORT + default n + ---help--- + Say Y here to enable selecting drivers for V4L devices that + use system memory for both source and destination buffers, as opposed + to capture and output drivers, which use memory buffers for just + one of those. + +if V4L_MEM2MEM_DRIVERS + +config VIDEO_CODA + tristate "Chips&Media Coda multi-standard codec IP" + depends on VIDEO_DEV && VIDEO_V4L2 && ARCH_MXC + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select IRAM_ALLOC if SOC_IMX53 + ---help--- + Coda is a range of video codec IPs that supports + H.264, MPEG-4, and other video formats. + +config VIDEO_MEM2MEM_DEINTERLACE + tristate "Deinterlace support" + depends on VIDEO_DEV && VIDEO_V4L2 && DMA_ENGINE + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + Generic deinterlacing V4L2 driver. + +config VIDEO_SAMSUNG_S5P_G2D + tristate "Samsung S5P and EXYNOS4 G2D 2d graphics accelerator driver" + depends on VIDEO_DEV && VIDEO_V4L2 && PLAT_S5P + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + default n + ---help--- + This is a v4l2 driver for Samsung S5P and EXYNOS4 G2D + 2d graphics accelerator. + +config VIDEO_SAMSUNG_S5P_JPEG + tristate "Samsung S5P/Exynos4 JPEG codec driver (EXPERIMENTAL)" + depends on VIDEO_DEV && VIDEO_V4L2 && PLAT_S5P && EXPERIMENTAL + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + ---help--- + This is a v4l2 driver for Samsung S5P and EXYNOS4 JPEG codec + +config VIDEO_SAMSUNG_S5P_MFC + tristate "Samsung S5P MFC 5.1 Video Codec" + depends on VIDEO_DEV && VIDEO_V4L2 && PLAT_S5P + select VIDEOBUF2_DMA_CONTIG + default n + help + MFC 5.1 driver for V4L2. + +config VIDEO_MX2_EMMAPRP + tristate "MX2 eMMa-PrP support" + depends on VIDEO_DEV && VIDEO_V4L2 && SOC_IMX27 + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + MX2X chips have a PrP that can be used to process buffers from + memory to memory. Operations include resizing and format + conversion. + +config VIDEO_SAMSUNG_EXYNOS_GSC + tristate "Samsung Exynos G-Scaler driver" + depends on VIDEO_DEV && VIDEO_V4L2 && ARCH_EXYNOS5 + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This is a v4l2 driver for Samsung EXYNOS5 SoC G-Scaler. + +endif # V4L_MEM2MEM_DRIVERS + +menuconfig V4L_TEST_DRIVERS + bool "Media test drivers" + depends on MEDIA_CAMERA_SUPPORT + +if V4L_TEST_DRIVERS +config VIDEO_VIVI + tristate "Virtual Video Driver" + depends on VIDEO_DEV && VIDEO_V4L2 && !SPARC32 && !SPARC64 + depends on FRAMEBUFFER_CONSOLE || STI_CONSOLE + select FONT_8x16 + select VIDEOBUF2_VMALLOC + default n + ---help--- + Enables a virtual video driver. This device shows a color bar + and a timestamp, as a real device would generate by using V4L2 + api. + Say Y here if you want to test video apps or debug V4L devices. + In doubt, say N. + +config VIDEO_MEM2MEM_TESTDEV + tristate "Virtual test device for mem2mem framework" + depends on VIDEO_DEV && VIDEO_V4L2 + select VIDEOBUF2_VMALLOC + select V4L2_MEM2MEM_DEV + default n + ---help--- + This is a virtual test device for the memory-to-memory driver + framework. +endif #V4L_TEST_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile new file mode 100644 index 000000000000..baaa55026c8e --- /dev/null +++ b/drivers/media/platform/Makefile @@ -0,0 +1,50 @@ +# +# Makefile for the video capture/playback device drivers. +# + +omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o + +obj-$(CONFIG_VIDEO_VINO) += indycam.o +obj-$(CONFIG_VIDEO_VINO) += vino.o + +obj-$(CONFIG_VIDEO_TIMBERDALE) += timblogiw.o +obj-$(CONFIG_VIDEO_M32R_AR_M64278) += arv.o + +obj-$(CONFIG_VIDEO_VIA_CAMERA) += via-camera.o +obj-$(CONFIG_VIDEO_CAFE_CCIC) += marvell-ccic/ +obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/ + +obj-$(CONFIG_VIDEO_OMAP2) += omap2cam.o +obj-$(CONFIG_VIDEO_OMAP3) += omap3isp/ + +obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o +obj-$(CONFIG_VIDEO_VIVI) += vivi.o + +obj-$(CONFIG_VIDEO_MEM2MEM_TESTDEV) += mem2mem_testdev.o + +obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o +obj-$(CONFIG_VIDEO_CODA) += coda.o + +obj-$(CONFIG_VIDEO_MEM2MEM_DEINTERLACE) += m2m-deinterlace.o + +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_FIMC) += s5p-fimc/ +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_JPEG) += s5p-jpeg/ +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_MFC) += s5p-mfc/ +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_TV) += s5p-tv/ + +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_G2D) += s5p-g2d/ +obj-$(CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC) += exynos-gsc/ + +obj-$(CONFIG_BLACKFIN) += blackfin/ + +obj-$(CONFIG_ARCH_DAVINCI) += davinci/ + +obj-$(CONFIG_VIDEO_SH_VOU) += sh_vou.o + +obj-$(CONFIG_SOC_CAMERA) += soc_camera/ + +obj-y += davinci/ + +obj-$(CONFIG_ARCH_OMAP) += omap/ + +ccflags-y += -I$(srctree)/drivers/media/i2c diff --git a/drivers/media/video/arv.c b/drivers/media/platform/arv.c similarity index 100% rename from drivers/media/video/arv.c rename to drivers/media/platform/arv.c diff --git a/drivers/media/video/blackfin/Kconfig b/drivers/media/platform/blackfin/Kconfig similarity index 100% rename from drivers/media/video/blackfin/Kconfig rename to drivers/media/platform/blackfin/Kconfig diff --git a/drivers/media/video/blackfin/Makefile b/drivers/media/platform/blackfin/Makefile similarity index 100% rename from drivers/media/video/blackfin/Makefile rename to drivers/media/platform/blackfin/Makefile diff --git a/drivers/media/platform/blackfin/bfin_capture.c b/drivers/media/platform/blackfin/bfin_capture.c new file mode 100644 index 000000000000..cb2eb26850b1 --- /dev/null +++ b/drivers/media/platform/blackfin/bfin_capture.c @@ -0,0 +1,1069 @@ +/* + * Analog Devices video capture driver + * + * Copyright (c) 2011 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define CAPTURE_DRV_NAME "bfin_capture" +#define BCAP_MIN_NUM_BUF 2 + +struct bcap_format { + char *desc; + u32 pixelformat; + enum v4l2_mbus_pixelcode mbus_code; + int bpp; /* bits per pixel */ +}; + +struct bcap_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +struct bcap_device { + /* capture device instance */ + struct v4l2_device v4l2_dev; + /* v4l2 control handler */ + struct v4l2_ctrl_handler ctrl_handler; + /* device node data */ + struct video_device *video_dev; + /* sub device instance */ + struct v4l2_subdev *sd; + /* capture config */ + struct bfin_capture_config *cfg; + /* ppi interface */ + struct ppi_if *ppi; + /* current input */ + unsigned int cur_input; + /* current selected standard */ + v4l2_std_id std; + /* used to store pixel format */ + struct v4l2_pix_format fmt; + /* bits per pixel*/ + int bpp; + /* used to store sensor supported format */ + struct bcap_format *sensor_formats; + /* number of sensor formats array */ + int num_sensor_formats; + /* pointing to current video buffer */ + struct bcap_buffer *cur_frm; + /* pointing to next video buffer */ + struct bcap_buffer *next_frm; + /* buffer queue used in videobuf2 */ + struct vb2_queue buffer_queue; + /* allocator-specific contexts for each plane */ + struct vb2_alloc_ctx *alloc_ctx; + /* queue of filled frames */ + struct list_head dma_queue; + /* used in videobuf2 callback */ + spinlock_t lock; + /* used to access capture device */ + struct mutex mutex; + /* used to wait ppi to complete one transfer */ + struct completion comp; + /* prepare to stop */ + bool stop; +}; + +struct bcap_fh { + struct v4l2_fh fh; + /* indicates whether this file handle is doing IO */ + bool io_allowed; +}; + +static const struct bcap_format bcap_formats[] = { + { + .desc = "YCbCr 4:2:2 Interleaved UYVY", + .pixelformat = V4L2_PIX_FMT_UYVY, + .mbus_code = V4L2_MBUS_FMT_UYVY8_2X8, + .bpp = 16, + }, + { + .desc = "YCbCr 4:2:2 Interleaved YUYV", + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, + .bpp = 16, + }, + { + .desc = "RGB 565", + .pixelformat = V4L2_PIX_FMT_RGB565, + .mbus_code = V4L2_MBUS_FMT_RGB565_2X8_LE, + .bpp = 16, + }, + { + .desc = "RGB 444", + .pixelformat = V4L2_PIX_FMT_RGB444, + .mbus_code = V4L2_MBUS_FMT_RGB444_2X8_PADHI_LE, + .bpp = 16, + }, + +}; +#define BCAP_MAX_FMTS ARRAY_SIZE(bcap_formats) + +static irqreturn_t bcap_isr(int irq, void *dev_id); + +static struct bcap_buffer *to_bcap_vb(struct vb2_buffer *vb) +{ + return container_of(vb, struct bcap_buffer, vb); +} + +static int bcap_init_sensor_formats(struct bcap_device *bcap_dev) +{ + enum v4l2_mbus_pixelcode code; + struct bcap_format *sf; + unsigned int num_formats = 0; + int i, j; + + while (!v4l2_subdev_call(bcap_dev->sd, video, + enum_mbus_fmt, num_formats, &code)) + num_formats++; + if (!num_formats) + return -ENXIO; + + sf = kzalloc(num_formats * sizeof(*sf), GFP_KERNEL); + if (!sf) + return -ENOMEM; + + for (i = 0; i < num_formats; i++) { + v4l2_subdev_call(bcap_dev->sd, video, + enum_mbus_fmt, i, &code); + for (j = 0; j < BCAP_MAX_FMTS; j++) + if (code == bcap_formats[j].mbus_code) + break; + if (j == BCAP_MAX_FMTS) { + /* we don't allow this sensor working with our bridge */ + kfree(sf); + return -EINVAL; + } + sf[i] = bcap_formats[j]; + } + bcap_dev->sensor_formats = sf; + bcap_dev->num_sensor_formats = num_formats; + return 0; +} + +static void bcap_free_sensor_formats(struct bcap_device *bcap_dev) +{ + bcap_dev->num_sensor_formats = 0; + kfree(bcap_dev->sensor_formats); + bcap_dev->sensor_formats = NULL; +} + +static int bcap_open(struct file *file) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct video_device *vfd = bcap_dev->video_dev; + struct bcap_fh *bcap_fh; + + if (!bcap_dev->sd) { + v4l2_err(&bcap_dev->v4l2_dev, "No sub device registered\n"); + return -ENODEV; + } + + bcap_fh = kzalloc(sizeof(*bcap_fh), GFP_KERNEL); + if (!bcap_fh) { + v4l2_err(&bcap_dev->v4l2_dev, + "unable to allocate memory for file handle object\n"); + return -ENOMEM; + } + + v4l2_fh_init(&bcap_fh->fh, vfd); + + /* store pointer to v4l2_fh in private_data member of file */ + file->private_data = &bcap_fh->fh; + v4l2_fh_add(&bcap_fh->fh); + bcap_fh->io_allowed = false; + return 0; +} + +static int bcap_release(struct file *file) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_fh *fh = file->private_data; + struct bcap_fh *bcap_fh = container_of(fh, struct bcap_fh, fh); + + /* if this instance is doing IO */ + if (bcap_fh->io_allowed) + vb2_queue_release(&bcap_dev->buffer_queue); + + file->private_data = NULL; + v4l2_fh_del(&bcap_fh->fh); + v4l2_fh_exit(&bcap_fh->fh); + kfree(bcap_fh); + return 0; +} + +static int bcap_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + int ret; + + if (mutex_lock_interruptible(&bcap_dev->mutex)) + return -ERESTARTSYS; + ret = vb2_mmap(&bcap_dev->buffer_queue, vma); + mutex_unlock(&bcap_dev->mutex); + return ret; +} + +#ifndef CONFIG_MMU +static unsigned long bcap_get_unmapped_area(struct file *file, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return vb2_get_unmapped_area(&bcap_dev->buffer_queue, + addr, + len, + pgoff, + flags); +} +#endif + +static unsigned int bcap_poll(struct file *file, poll_table *wait) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + unsigned int res; + + mutex_lock(&bcap_dev->mutex); + res = vb2_poll(&bcap_dev->buffer_queue, file, wait); + mutex_unlock(&bcap_dev->mutex); + return res; +} + +static int bcap_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + + if (*nbuffers < BCAP_MIN_NUM_BUF) + *nbuffers = BCAP_MIN_NUM_BUF; + + *nplanes = 1; + sizes[0] = bcap_dev->fmt.sizeimage; + alloc_ctxs[0] = bcap_dev->alloc_ctx; + + return 0; +} + +static int bcap_buffer_init(struct vb2_buffer *vb) +{ + struct bcap_buffer *buf = to_bcap_vb(vb); + + INIT_LIST_HEAD(&buf->list); + return 0; +} + +static int bcap_buffer_prepare(struct vb2_buffer *vb) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vb->vb2_queue); + struct bcap_buffer *buf = to_bcap_vb(vb); + unsigned long size; + + size = bcap_dev->fmt.sizeimage; + if (vb2_plane_size(vb, 0) < size) { + v4l2_err(&bcap_dev->v4l2_dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + vb2_set_plane_payload(&buf->vb, 0, size); + + return 0; +} + +static void bcap_buffer_queue(struct vb2_buffer *vb) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vb->vb2_queue); + struct bcap_buffer *buf = to_bcap_vb(vb); + unsigned long flags; + + spin_lock_irqsave(&bcap_dev->lock, flags); + list_add_tail(&buf->list, &bcap_dev->dma_queue); + spin_unlock_irqrestore(&bcap_dev->lock, flags); +} + +static void bcap_buffer_cleanup(struct vb2_buffer *vb) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vb->vb2_queue); + struct bcap_buffer *buf = to_bcap_vb(vb); + unsigned long flags; + + spin_lock_irqsave(&bcap_dev->lock, flags); + list_del_init(&buf->list); + spin_unlock_irqrestore(&bcap_dev->lock, flags); +} + +static void bcap_lock(struct vb2_queue *vq) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + mutex_lock(&bcap_dev->mutex); +} + +static void bcap_unlock(struct vb2_queue *vq) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + mutex_unlock(&bcap_dev->mutex); +} + +static int bcap_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + struct ppi_if *ppi = bcap_dev->ppi; + struct ppi_params params; + int ret; + + /* enable streamon on the sub device */ + ret = v4l2_subdev_call(bcap_dev->sd, video, s_stream, 1); + if (ret && (ret != -ENOIOCTLCMD)) { + v4l2_err(&bcap_dev->v4l2_dev, "stream on failed in subdev\n"); + return ret; + } + + /* set ppi params */ + params.width = bcap_dev->fmt.width; + params.height = bcap_dev->fmt.height; + params.bpp = bcap_dev->bpp; + params.ppi_control = bcap_dev->cfg->ppi_control; + params.int_mask = bcap_dev->cfg->int_mask; + params.blank_clocks = bcap_dev->cfg->blank_clocks; + ret = ppi->ops->set_params(ppi, ¶ms); + if (ret < 0) { + v4l2_err(&bcap_dev->v4l2_dev, + "Error in setting ppi params\n"); + return ret; + } + + /* attach ppi DMA irq handler */ + ret = ppi->ops->attach_irq(ppi, bcap_isr); + if (ret < 0) { + v4l2_err(&bcap_dev->v4l2_dev, + "Error in attaching interrupt handler\n"); + return ret; + } + + INIT_COMPLETION(bcap_dev->comp); + bcap_dev->stop = false; + return 0; +} + +static int bcap_stop_streaming(struct vb2_queue *vq) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + struct ppi_if *ppi = bcap_dev->ppi; + int ret; + + if (!vb2_is_streaming(vq)) + return 0; + + bcap_dev->stop = true; + wait_for_completion(&bcap_dev->comp); + ppi->ops->stop(ppi); + ppi->ops->detach_irq(ppi); + ret = v4l2_subdev_call(bcap_dev->sd, video, s_stream, 0); + if (ret && (ret != -ENOIOCTLCMD)) + v4l2_err(&bcap_dev->v4l2_dev, + "stream off failed in subdev\n"); + + /* release all active buffers */ + while (!list_empty(&bcap_dev->dma_queue)) { + bcap_dev->next_frm = list_entry(bcap_dev->dma_queue.next, + struct bcap_buffer, list); + list_del(&bcap_dev->next_frm->list); + vb2_buffer_done(&bcap_dev->next_frm->vb, VB2_BUF_STATE_ERROR); + } + return 0; +} + +static struct vb2_ops bcap_video_qops = { + .queue_setup = bcap_queue_setup, + .buf_init = bcap_buffer_init, + .buf_prepare = bcap_buffer_prepare, + .buf_cleanup = bcap_buffer_cleanup, + .buf_queue = bcap_buffer_queue, + .wait_prepare = bcap_unlock, + .wait_finish = bcap_lock, + .start_streaming = bcap_start_streaming, + .stop_streaming = bcap_stop_streaming, +}; + +static int bcap_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req_buf) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct vb2_queue *vq = &bcap_dev->buffer_queue; + struct v4l2_fh *fh = file->private_data; + struct bcap_fh *bcap_fh = container_of(fh, struct bcap_fh, fh); + + if (vb2_is_busy(vq)) + return -EBUSY; + + bcap_fh->io_allowed = true; + + return vb2_reqbufs(vq, req_buf); +} + +static int bcap_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return vb2_querybuf(&bcap_dev->buffer_queue, buf); +} + +static int bcap_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_fh *fh = file->private_data; + struct bcap_fh *bcap_fh = container_of(fh, struct bcap_fh, fh); + + if (!bcap_fh->io_allowed) + return -EBUSY; + + return vb2_qbuf(&bcap_dev->buffer_queue, buf); +} + +static int bcap_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_fh *fh = file->private_data; + struct bcap_fh *bcap_fh = container_of(fh, struct bcap_fh, fh); + + if (!bcap_fh->io_allowed) + return -EBUSY; + + return vb2_dqbuf(&bcap_dev->buffer_queue, + buf, file->f_flags & O_NONBLOCK); +} + +static irqreturn_t bcap_isr(int irq, void *dev_id) +{ + struct ppi_if *ppi = dev_id; + struct bcap_device *bcap_dev = ppi->priv; + struct timeval timevalue; + struct vb2_buffer *vb = &bcap_dev->cur_frm->vb; + dma_addr_t addr; + + spin_lock(&bcap_dev->lock); + + if (bcap_dev->cur_frm != bcap_dev->next_frm) { + do_gettimeofday(&timevalue); + vb->v4l2_buf.timestamp = timevalue; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + bcap_dev->cur_frm = bcap_dev->next_frm; + } + + ppi->ops->stop(ppi); + + if (bcap_dev->stop) { + complete(&bcap_dev->comp); + } else { + if (!list_empty(&bcap_dev->dma_queue)) { + bcap_dev->next_frm = list_entry(bcap_dev->dma_queue.next, + struct bcap_buffer, list); + list_del(&bcap_dev->next_frm->list); + addr = vb2_dma_contig_plane_dma_addr(&bcap_dev->next_frm->vb, 0); + ppi->ops->update_addr(ppi, (unsigned long)addr); + } + ppi->ops->start(ppi); + } + + spin_unlock(&bcap_dev->lock); + + return IRQ_HANDLED; +} + +static int bcap_streamon(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bcap_fh *fh = file->private_data; + struct ppi_if *ppi = bcap_dev->ppi; + dma_addr_t addr; + int ret; + + if (!fh->io_allowed) + return -EBUSY; + + /* call streamon to start streaming in videobuf */ + ret = vb2_streamon(&bcap_dev->buffer_queue, buf_type); + if (ret) + return ret; + + /* if dma queue is empty, return error */ + if (list_empty(&bcap_dev->dma_queue)) { + v4l2_err(&bcap_dev->v4l2_dev, "dma queue is empty\n"); + ret = -EINVAL; + goto err; + } + + /* get the next frame from the dma queue */ + bcap_dev->next_frm = list_entry(bcap_dev->dma_queue.next, + struct bcap_buffer, list); + bcap_dev->cur_frm = bcap_dev->next_frm; + /* remove buffer from the dma queue */ + list_del(&bcap_dev->cur_frm->list); + addr = vb2_dma_contig_plane_dma_addr(&bcap_dev->cur_frm->vb, 0); + /* update DMA address */ + ppi->ops->update_addr(ppi, (unsigned long)addr); + /* enable ppi */ + ppi->ops->start(ppi); + + return 0; +err: + vb2_streamoff(&bcap_dev->buffer_queue, buf_type); + return ret; +} + +static int bcap_streamoff(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bcap_fh *fh = file->private_data; + + if (!fh->io_allowed) + return -EBUSY; + + return vb2_streamoff(&bcap_dev->buffer_queue, buf_type); +} + +static int bcap_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return v4l2_subdev_call(bcap_dev->sd, video, querystd, std); +} + +static int bcap_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + *std = bcap_dev->std; + return 0; +} + +static int bcap_s_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + int ret; + + if (vb2_is_busy(&bcap_dev->buffer_queue)) + return -EBUSY; + + ret = v4l2_subdev_call(bcap_dev->sd, core, s_std, *std); + if (ret < 0) + return ret; + + bcap_dev->std = *std; + return 0; +} + +static int bcap_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bfin_capture_config *config = bcap_dev->cfg; + int ret; + u32 status; + + if (input->index >= config->num_inputs) + return -EINVAL; + + *input = config->inputs[input->index]; + /* get input status */ + ret = v4l2_subdev_call(bcap_dev->sd, video, g_input_status, &status); + if (!ret) + input->status = status; + return 0; +} + +static int bcap_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + *index = bcap_dev->cur_input; + return 0; +} + +static int bcap_s_input(struct file *file, void *priv, unsigned int index) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bfin_capture_config *config = bcap_dev->cfg; + struct bcap_route *route; + int ret; + + if (vb2_is_busy(&bcap_dev->buffer_queue)) + return -EBUSY; + + if (index >= config->num_inputs) + return -EINVAL; + + route = &config->routes[index]; + ret = v4l2_subdev_call(bcap_dev->sd, video, s_routing, + route->input, route->output, 0); + if ((ret < 0) && (ret != -ENOIOCTLCMD)) { + v4l2_err(&bcap_dev->v4l2_dev, "Failed to set input\n"); + return ret; + } + bcap_dev->cur_input = index; + return 0; +} + +static int bcap_try_format(struct bcap_device *bcap, + struct v4l2_pix_format *pixfmt, + enum v4l2_mbus_pixelcode *mbus_code, + int *bpp) +{ + struct bcap_format *sf = bcap->sensor_formats; + struct bcap_format *fmt = NULL; + struct v4l2_mbus_framefmt mbus_fmt; + int ret, i; + + for (i = 0; i < bcap->num_sensor_formats; i++) { + fmt = &sf[i]; + if (pixfmt->pixelformat == fmt->pixelformat) + break; + } + if (i == bcap->num_sensor_formats) + fmt = &sf[0]; + + if (mbus_code) + *mbus_code = fmt->mbus_code; + if (bpp) + *bpp = fmt->bpp; + v4l2_fill_mbus_format(&mbus_fmt, pixfmt, fmt->mbus_code); + ret = v4l2_subdev_call(bcap->sd, video, + try_mbus_fmt, &mbus_fmt); + if (ret < 0) + return ret; + v4l2_fill_pix_format(pixfmt, &mbus_fmt); + pixfmt->bytesperline = pixfmt->width * fmt->bpp / 8; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + return 0; +} + +static int bcap_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bcap_format *sf = bcap_dev->sensor_formats; + + if (fmt->index >= bcap_dev->num_sensor_formats) + return -EINVAL; + + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + strlcpy(fmt->description, + sf[fmt->index].desc, + sizeof(fmt->description)); + fmt->pixelformat = sf[fmt->index].pixelformat; + return 0; +} + +static int bcap_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + return bcap_try_format(bcap_dev, pixfmt, NULL, NULL); +} + +static int bcap_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + fmt->fmt.pix = bcap_dev->fmt; + return 0; +} + +static int bcap_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_mbus_framefmt mbus_fmt; + enum v4l2_mbus_pixelcode mbus_code; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + int ret, bpp; + + if (vb2_is_busy(&bcap_dev->buffer_queue)) + return -EBUSY; + + /* see if format works */ + ret = bcap_try_format(bcap_dev, pixfmt, &mbus_code, &bpp); + if (ret < 0) + return ret; + + v4l2_fill_mbus_format(&mbus_fmt, pixfmt, mbus_code); + ret = v4l2_subdev_call(bcap_dev->sd, video, s_mbus_fmt, &mbus_fmt); + if (ret < 0) + return ret; + bcap_dev->fmt = *pixfmt; + bcap_dev->bpp = bpp; + return 0; +} + +static int bcap_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + strlcpy(cap->driver, CAPTURE_DRV_NAME, sizeof(cap->driver)); + strlcpy(cap->bus_info, "Blackfin Platform", sizeof(cap->bus_info)); + strlcpy(cap->card, bcap_dev->cfg->card_name, sizeof(cap->card)); + return 0; +} + +static int bcap_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + return v4l2_subdev_call(bcap_dev->sd, video, g_parm, a); +} + +static int bcap_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + return v4l2_subdev_call(bcap_dev->sd, video, s_parm, a); +} + +static int bcap_g_chip_ident(struct file *file, void *priv, + struct v4l2_dbg_chip_ident *chip) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + chip->ident = V4L2_IDENT_NONE; + chip->revision = 0; + if (chip->match.type != V4L2_CHIP_MATCH_I2C_DRIVER && + chip->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + return v4l2_subdev_call(bcap_dev->sd, core, + g_chip_ident, chip); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int bcap_dbg_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return v4l2_subdev_call(bcap_dev->sd, core, + g_register, reg); +} + +static int bcap_dbg_s_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return v4l2_subdev_call(bcap_dev->sd, core, + s_register, reg); +} +#endif + +static int bcap_log_status(struct file *file, void *priv) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + /* status for sub devices */ + v4l2_device_call_all(&bcap_dev->v4l2_dev, 0, core, log_status); + return 0; +} + +static const struct v4l2_ioctl_ops bcap_ioctl_ops = { + .vidioc_querycap = bcap_querycap, + .vidioc_g_fmt_vid_cap = bcap_g_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = bcap_enum_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = bcap_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = bcap_try_fmt_vid_cap, + .vidioc_enum_input = bcap_enum_input, + .vidioc_g_input = bcap_g_input, + .vidioc_s_input = bcap_s_input, + .vidioc_querystd = bcap_querystd, + .vidioc_s_std = bcap_s_std, + .vidioc_g_std = bcap_g_std, + .vidioc_reqbufs = bcap_reqbufs, + .vidioc_querybuf = bcap_querybuf, + .vidioc_qbuf = bcap_qbuf, + .vidioc_dqbuf = bcap_dqbuf, + .vidioc_streamon = bcap_streamon, + .vidioc_streamoff = bcap_streamoff, + .vidioc_g_parm = bcap_g_parm, + .vidioc_s_parm = bcap_s_parm, + .vidioc_g_chip_ident = bcap_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = bcap_dbg_g_register, + .vidioc_s_register = bcap_dbg_s_register, +#endif + .vidioc_log_status = bcap_log_status, +}; + +static struct v4l2_file_operations bcap_fops = { + .owner = THIS_MODULE, + .open = bcap_open, + .release = bcap_release, + .unlocked_ioctl = video_ioctl2, + .mmap = bcap_mmap, +#ifndef CONFIG_MMU + .get_unmapped_area = bcap_get_unmapped_area, +#endif + .poll = bcap_poll +}; + +static int __devinit bcap_probe(struct platform_device *pdev) +{ + struct bcap_device *bcap_dev; + struct video_device *vfd; + struct i2c_adapter *i2c_adap; + struct bfin_capture_config *config; + struct vb2_queue *q; + int ret; + + config = pdev->dev.platform_data; + if (!config) { + v4l2_err(pdev->dev.driver, "Unable to get board config\n"); + return -ENODEV; + } + + bcap_dev = kzalloc(sizeof(*bcap_dev), GFP_KERNEL); + if (!bcap_dev) { + v4l2_err(pdev->dev.driver, "Unable to alloc bcap_dev\n"); + return -ENOMEM; + } + + bcap_dev->cfg = config; + + bcap_dev->ppi = ppi_create_instance(config->ppi_info); + if (!bcap_dev->ppi) { + v4l2_err(pdev->dev.driver, "Unable to create ppi\n"); + ret = -ENODEV; + goto err_free_dev; + } + bcap_dev->ppi->priv = bcap_dev; + + bcap_dev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(bcap_dev->alloc_ctx)) { + ret = PTR_ERR(bcap_dev->alloc_ctx); + goto err_free_ppi; + } + + vfd = video_device_alloc(); + if (!vfd) { + ret = -ENOMEM; + v4l2_err(pdev->dev.driver, "Unable to alloc video device\n"); + goto err_cleanup_ctx; + } + + /* initialize field of video device */ + vfd->release = video_device_release; + vfd->fops = &bcap_fops; + vfd->ioctl_ops = &bcap_ioctl_ops; + vfd->tvnorms = 0; + vfd->v4l2_dev = &bcap_dev->v4l2_dev; + set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); + strncpy(vfd->name, CAPTURE_DRV_NAME, sizeof(vfd->name)); + bcap_dev->video_dev = vfd; + + ret = v4l2_device_register(&pdev->dev, &bcap_dev->v4l2_dev); + if (ret) { + v4l2_err(pdev->dev.driver, + "Unable to register v4l2 device\n"); + goto err_release_vdev; + } + v4l2_info(&bcap_dev->v4l2_dev, "v4l2 device registered\n"); + + bcap_dev->v4l2_dev.ctrl_handler = &bcap_dev->ctrl_handler; + ret = v4l2_ctrl_handler_init(&bcap_dev->ctrl_handler, 0); + if (ret) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to init control handler\n"); + goto err_unreg_v4l2; + } + + spin_lock_init(&bcap_dev->lock); + /* initialize queue */ + q = &bcap_dev->buffer_queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP; + q->drv_priv = bcap_dev; + q->buf_struct_size = sizeof(struct bcap_buffer); + q->ops = &bcap_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + + vb2_queue_init(q); + + mutex_init(&bcap_dev->mutex); + init_completion(&bcap_dev->comp); + + /* init video dma queues */ + INIT_LIST_HEAD(&bcap_dev->dma_queue); + + vfd->lock = &bcap_dev->mutex; + + /* register video device */ + ret = video_register_device(bcap_dev->video_dev, VFL_TYPE_GRABBER, -1); + if (ret) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to register video device\n"); + goto err_free_handler; + } + video_set_drvdata(bcap_dev->video_dev, bcap_dev); + v4l2_info(&bcap_dev->v4l2_dev, "video device registered as: %s\n", + video_device_node_name(vfd)); + + /* load up the subdevice */ + i2c_adap = i2c_get_adapter(config->i2c_adapter_id); + if (!i2c_adap) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to find i2c adapter\n"); + ret = -ENODEV; + goto err_unreg_vdev; + + } + bcap_dev->sd = v4l2_i2c_new_subdev_board(&bcap_dev->v4l2_dev, + i2c_adap, + &config->board_info, + NULL); + if (bcap_dev->sd) { + int i; + /* update tvnorms from the sub devices */ + for (i = 0; i < config->num_inputs; i++) + vfd->tvnorms |= config->inputs[i].std; + } else { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to register sub device\n"); + goto err_unreg_vdev; + } + + v4l2_info(&bcap_dev->v4l2_dev, "v4l2 sub device registered\n"); + + /* now we can probe the default state */ + if (vfd->tvnorms) { + v4l2_std_id std; + ret = v4l2_subdev_call(bcap_dev->sd, core, g_std, &std); + if (ret) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to get std\n"); + goto err_unreg_vdev; + } + bcap_dev->std = std; + } + ret = bcap_init_sensor_formats(bcap_dev); + if (ret) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to create sensor formats table\n"); + goto err_unreg_vdev; + } + return 0; +err_unreg_vdev: + video_unregister_device(bcap_dev->video_dev); + bcap_dev->video_dev = NULL; +err_free_handler: + v4l2_ctrl_handler_free(&bcap_dev->ctrl_handler); +err_unreg_v4l2: + v4l2_device_unregister(&bcap_dev->v4l2_dev); +err_release_vdev: + if (bcap_dev->video_dev) + video_device_release(bcap_dev->video_dev); +err_cleanup_ctx: + vb2_dma_contig_cleanup_ctx(bcap_dev->alloc_ctx); +err_free_ppi: + ppi_delete_instance(bcap_dev->ppi); +err_free_dev: + kfree(bcap_dev); + return ret; +} + +static int __devexit bcap_remove(struct platform_device *pdev) +{ + struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); + struct bcap_device *bcap_dev = container_of(v4l2_dev, + struct bcap_device, v4l2_dev); + + bcap_free_sensor_formats(bcap_dev); + video_unregister_device(bcap_dev->video_dev); + v4l2_ctrl_handler_free(&bcap_dev->ctrl_handler); + v4l2_device_unregister(v4l2_dev); + vb2_dma_contig_cleanup_ctx(bcap_dev->alloc_ctx); + ppi_delete_instance(bcap_dev->ppi); + kfree(bcap_dev); + return 0; +} + +static struct platform_driver bcap_driver = { + .driver = { + .name = CAPTURE_DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = bcap_probe, + .remove = __devexit_p(bcap_remove), +}; + +static __init int bcap_init(void) +{ + return platform_driver_register(&bcap_driver); +} + +static __exit void bcap_exit(void) +{ + platform_driver_unregister(&bcap_driver); +} + +module_init(bcap_init); +module_exit(bcap_exit); + +MODULE_DESCRIPTION("Analog Devices blackfin video capture driver"); +MODULE_AUTHOR("Scott Jiang "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/blackfin/ppi.c b/drivers/media/platform/blackfin/ppi.c similarity index 100% rename from drivers/media/video/blackfin/ppi.c rename to drivers/media/platform/blackfin/ppi.c diff --git a/drivers/media/platform/coda.c b/drivers/media/platform/coda.c new file mode 100644 index 000000000000..cd04ae252c30 --- /dev/null +++ b/drivers/media/platform/coda.c @@ -0,0 +1,2049 @@ +/* + * Coda multi-standard codec IP + * + * Copyright (C) 2012 Vista Silicon S.L. + * Javier Martin, + * Xavier Duret + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "coda.h" + +#define CODA_NAME "coda" + +#define CODA_MAX_INSTANCES 4 + +#define CODA_FMO_BUF_SIZE 32 +#define CODADX6_WORK_BUF_SIZE (288 * 1024 + CODA_FMO_BUF_SIZE * 8 * 1024) +#define CODA7_WORK_BUF_SIZE (512 * 1024 + CODA_FMO_BUF_SIZE * 8 * 1024) +#define CODA_PARA_BUF_SIZE (10 * 1024) +#define CODA_ISRAM_SIZE (2048 * 2) +#define CODA7_IRAM_SIZE 0x14000 /* 81920 bytes */ + +#define CODA_MAX_FRAMEBUFFERS 2 + +#define MAX_W 720 +#define MAX_H 576 +#define CODA_MAX_FRAME_SIZE 0x90000 +#define FMO_SLICE_SAVE_BUF_SIZE (32) +#define CODA_DEFAULT_GAMMA 4096 + +#define MIN_W 176 +#define MIN_H 144 +#define MAX_W 720 +#define MAX_H 576 + +#define S_ALIGN 1 /* multiple of 2 */ +#define W_ALIGN 1 /* multiple of 2 */ +#define H_ALIGN 1 /* multiple of 2 */ + +#define fh_to_ctx(__fh) container_of(__fh, struct coda_ctx, fh) + +static int coda_debug; +module_param(coda_debug, int, 0); +MODULE_PARM_DESC(coda_debug, "Debug level (0-1)"); + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +enum coda_fmt_type { + CODA_FMT_ENC, + CODA_FMT_RAW, +}; + +enum coda_inst_type { + CODA_INST_ENCODER, + CODA_INST_DECODER, +}; + +enum coda_product { + CODA_DX6 = 0xf001, + CODA_7541 = 0xf012, +}; + +struct coda_fmt { + char *name; + u32 fourcc; + enum coda_fmt_type type; +}; + +struct coda_devtype { + char *firmware; + enum coda_product product; + struct coda_fmt *formats; + unsigned int num_formats; + size_t workbuf_size; +}; + +/* Per-queue, driver-specific private data */ +struct coda_q_data { + unsigned int width; + unsigned int height; + unsigned int sizeimage; + struct coda_fmt *fmt; +}; + +struct coda_aux_buf { + void *vaddr; + dma_addr_t paddr; + u32 size; +}; + +struct coda_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; + struct platform_device *plat_dev; + const struct coda_devtype *devtype; + + void __iomem *regs_base; + struct clk *clk_per; + struct clk *clk_ahb; + + struct coda_aux_buf codebuf; + struct coda_aux_buf workbuf; + long unsigned int iram_paddr; + + spinlock_t irqlock; + struct mutex dev_mutex; + struct v4l2_m2m_dev *m2m_dev; + struct vb2_alloc_ctx *alloc_ctx; + struct list_head instances; + unsigned long instance_mask; + struct delayed_work timeout; + struct completion done; +}; + +struct coda_params { + u8 rot_mode; + u8 h264_intra_qp; + u8 h264_inter_qp; + u8 mpeg4_intra_qp; + u8 mpeg4_inter_qp; + u8 gop_size; + int codec_mode; + enum v4l2_mpeg_video_multi_slice_mode slice_mode; + u32 framerate; + u16 bitrate; + u32 slice_max_bits; + u32 slice_max_mb; +}; + +struct coda_ctx { + struct coda_dev *dev; + struct list_head list; + int aborting; + int rawstreamon; + int compstreamon; + u32 isequence; + struct coda_q_data q_data[2]; + enum coda_inst_type inst_type; + enum v4l2_colorspace colorspace; + struct coda_params params; + struct v4l2_m2m_ctx *m2m_ctx; + struct v4l2_ctrl_handler ctrls; + struct v4l2_fh fh; + int gopcounter; + char vpu_header[3][64]; + int vpu_header_size[3]; + struct coda_aux_buf parabuf; + struct coda_aux_buf internal_frames[CODA_MAX_FRAMEBUFFERS]; + int num_internal_frames; + int idx; +}; + +static inline void coda_write(struct coda_dev *dev, u32 data, u32 reg) +{ + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "%s: data=0x%x, reg=0x%x\n", __func__, data, reg); + writel(data, dev->regs_base + reg); +} + +static inline unsigned int coda_read(struct coda_dev *dev, u32 reg) +{ + u32 data; + data = readl(dev->regs_base + reg); + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "%s: data=0x%x, reg=0x%x\n", __func__, data, reg); + return data; +} + +static inline unsigned long coda_isbusy(struct coda_dev *dev) +{ + return coda_read(dev, CODA_REG_BIT_BUSY); +} + +static inline int coda_is_initialized(struct coda_dev *dev) +{ + return (coda_read(dev, CODA_REG_BIT_CUR_PC) != 0); +} + +static int coda_wait_timeout(struct coda_dev *dev) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + + while (coda_isbusy(dev)) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + } + return 0; +} + +static void coda_command_async(struct coda_ctx *ctx, int cmd) +{ + struct coda_dev *dev = ctx->dev; + coda_write(dev, CODA_REG_BIT_BUSY_FLAG, CODA_REG_BIT_BUSY); + + coda_write(dev, ctx->idx, CODA_REG_BIT_RUN_INDEX); + coda_write(dev, ctx->params.codec_mode, CODA_REG_BIT_RUN_COD_STD); + coda_write(dev, cmd, CODA_REG_BIT_RUN_COMMAND); +} + +static int coda_command_sync(struct coda_ctx *ctx, int cmd) +{ + struct coda_dev *dev = ctx->dev; + + coda_command_async(ctx, cmd); + return coda_wait_timeout(dev); +} + +static struct coda_q_data *get_q_data(struct coda_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &(ctx->q_data[V4L2_M2M_SRC]); + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &(ctx->q_data[V4L2_M2M_DST]); + default: + BUG(); + } + return NULL; +} + +/* + * Add one array of supported formats for each version of Coda: + * i.MX27 -> codadx6 + * i.MX51 -> coda7 + * i.MX6 -> coda960 + */ +static struct coda_fmt codadx6_formats[] = { + { + .name = "YUV 4:2:0 Planar", + .fourcc = V4L2_PIX_FMT_YUV420, + .type = CODA_FMT_RAW, + }, + { + .name = "H264 Encoded Stream", + .fourcc = V4L2_PIX_FMT_H264, + .type = CODA_FMT_ENC, + }, + { + .name = "MPEG4 Encoded Stream", + .fourcc = V4L2_PIX_FMT_MPEG4, + .type = CODA_FMT_ENC, + }, +}; + +static struct coda_fmt coda7_formats[] = { + { + .name = "YUV 4:2:0 Planar", + .fourcc = V4L2_PIX_FMT_YUV420, + .type = CODA_FMT_RAW, + }, + { + .name = "H264 Encoded Stream", + .fourcc = V4L2_PIX_FMT_H264, + .type = CODA_FMT_ENC, + }, + { + .name = "MPEG4 Encoded Stream", + .fourcc = V4L2_PIX_FMT_MPEG4, + .type = CODA_FMT_ENC, + }, +}; + +static struct coda_fmt *find_format(struct coda_dev *dev, struct v4l2_format *f) +{ + struct coda_fmt *formats = dev->devtype->formats; + int num_formats = dev->devtype->num_formats; + unsigned int k; + + for (k = 0; k < num_formats; k++) { + if (formats[k].fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == num_formats) + return NULL; + + return &formats[k]; +} + +/* + * V4L2 ioctl() operations. + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strlcpy(cap->driver, CODA_NAME, sizeof(cap->driver)); + strlcpy(cap->card, CODA_NAME, sizeof(cap->card)); + strlcpy(cap->bus_info, CODA_NAME, sizeof(cap->bus_info)); + /* + * This is only a mem-to-mem video device. The capture and output + * device capability flags are left only for backward compatibility + * and are scheduled for removal. + */ + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int enum_fmt(void *priv, struct v4l2_fmtdesc *f, + enum coda_fmt_type type) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + struct coda_dev *dev = ctx->dev; + struct coda_fmt *formats = dev->devtype->formats; + struct coda_fmt *fmt; + int num_formats = dev->devtype->num_formats; + int i, num = 0; + + for (i = 0; i < num_formats; i++) { + if (formats[i].type == type) { + if (num == f->index) + break; + ++num; + } + } + + if (i < num_formats) { + fmt = &formats[i]; + strlcpy(f->description, fmt->name, sizeof(f->description)); + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(priv, f, CODA_FMT_ENC); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(priv, f, CODA_FMT_RAW); +} + +static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct coda_q_data *q_data; + struct coda_ctx *ctx = fh_to_ctx(priv); + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 2); + else /* encoded formats h.264/mpeg4 */ + f->fmt.pix.bytesperline = 0; + + f->fmt.pix.sizeimage = q_data->sizeimage; + f->fmt.pix.colorspace = ctx->colorspace; + + return 0; +} + +static int vidioc_try_fmt(struct coda_dev *dev, struct v4l2_format *f) +{ + enum v4l2_field field; + + field = f->fmt.pix.field; + if (field == V4L2_FIELD_ANY) + field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != field) + return -EINVAL; + + /* V4L2 specification suggests the driver corrects the format struct + * if any of the dimensions is unsupported */ + f->fmt.pix.field = field; + + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 2); + f->fmt.pix.sizeimage = f->fmt.pix.width * + f->fmt.pix.height * 3 / 2; + } else { /*encoded formats h.264/mpeg4 */ + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = CODA_MAX_FRAME_SIZE; + } + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + struct coda_fmt *fmt; + struct coda_ctx *ctx = fh_to_ctx(priv); + + fmt = find_format(ctx->dev, f); + /* + * Since decoding support is not implemented yet do not allow + * CODA_FMT_RAW formats in the capture interface. + */ + if (!fmt || !(fmt->type == CODA_FMT_ENC)) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_H264; + + f->fmt.pix.colorspace = ctx->colorspace; + + ret = vidioc_try_fmt(ctx->dev, f); + if (ret < 0) + return ret; + + return 0; +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + struct coda_fmt *fmt; + int ret; + + fmt = find_format(ctx->dev, f); + /* + * Since decoding support is not implemented yet do not allow + * CODA_FMT formats in the capture interface. + */ + if (!fmt || !(fmt->type == CODA_FMT_RAW)) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + + if (!f->fmt.pix.colorspace) + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + + ret = vidioc_try_fmt(ctx->dev, f); + if (ret < 0) + return ret; + + return 0; +} + +static int vidioc_s_fmt(struct coda_ctx *ctx, struct v4l2_format *f) +{ + struct coda_q_data *q_data; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = vidioc_try_fmt(ctx->dev, f); + if (ret) + return ret; + + q_data->fmt = find_format(ctx->dev, f); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + q_data->sizeimage = f->fmt.pix.sizeimage; + + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(fh_to_ctx(priv), f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = vidioc_s_fmt(ctx, f); + if (ret) + ctx->colorspace = f->fmt.pix.colorspace; + + return ret; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + + return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + + return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + + return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + + return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); +} + +static const struct v4l2_ioctl_ops coda_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +}; + +/* + * Mem-to-mem operations. + */ +static void coda_device_run(void *m2m_priv) +{ + struct coda_ctx *ctx = m2m_priv; + struct coda_q_data *q_data_src, *q_data_dst; + struct vb2_buffer *src_buf, *dst_buf; + struct coda_dev *dev = ctx->dev; + int force_ipicture; + int quant_param = 0; + u32 picture_y, picture_cb, picture_cr; + u32 pic_stream_buffer_addr, pic_stream_buffer_size; + u32 dst_fourcc; + + src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + dst_fourcc = q_data_dst->fmt->fourcc; + + src_buf->v4l2_buf.sequence = ctx->isequence; + dst_buf->v4l2_buf.sequence = ctx->isequence; + ctx->isequence++; + + /* + * Workaround coda firmware BUG that only marks the first + * frame as IDR. This is a problem for some decoders that can't + * recover when a frame is lost. + */ + if (src_buf->v4l2_buf.sequence % ctx->params.gop_size) { + src_buf->v4l2_buf.flags |= V4L2_BUF_FLAG_PFRAME; + src_buf->v4l2_buf.flags &= ~V4L2_BUF_FLAG_KEYFRAME; + } else { + src_buf->v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME; + src_buf->v4l2_buf.flags &= ~V4L2_BUF_FLAG_PFRAME; + } + + /* + * Copy headers at the beginning of the first frame for H.264 only. + * In MPEG4 they are already copied by the coda. + */ + if (src_buf->v4l2_buf.sequence == 0) { + pic_stream_buffer_addr = + vb2_dma_contig_plane_dma_addr(dst_buf, 0) + + ctx->vpu_header_size[0] + + ctx->vpu_header_size[1] + + ctx->vpu_header_size[2]; + pic_stream_buffer_size = CODA_MAX_FRAME_SIZE - + ctx->vpu_header_size[0] - + ctx->vpu_header_size[1] - + ctx->vpu_header_size[2]; + memcpy(vb2_plane_vaddr(dst_buf, 0), + &ctx->vpu_header[0][0], ctx->vpu_header_size[0]); + memcpy(vb2_plane_vaddr(dst_buf, 0) + ctx->vpu_header_size[0], + &ctx->vpu_header[1][0], ctx->vpu_header_size[1]); + memcpy(vb2_plane_vaddr(dst_buf, 0) + ctx->vpu_header_size[0] + + ctx->vpu_header_size[1], &ctx->vpu_header[2][0], + ctx->vpu_header_size[2]); + } else { + pic_stream_buffer_addr = + vb2_dma_contig_plane_dma_addr(dst_buf, 0); + pic_stream_buffer_size = CODA_MAX_FRAME_SIZE; + } + + if (src_buf->v4l2_buf.flags & V4L2_BUF_FLAG_KEYFRAME) { + force_ipicture = 1; + switch (dst_fourcc) { + case V4L2_PIX_FMT_H264: + quant_param = ctx->params.h264_intra_qp; + break; + case V4L2_PIX_FMT_MPEG4: + quant_param = ctx->params.mpeg4_intra_qp; + break; + default: + v4l2_warn(&ctx->dev->v4l2_dev, + "cannot set intra qp, fmt not supported\n"); + break; + } + } else { + force_ipicture = 0; + switch (dst_fourcc) { + case V4L2_PIX_FMT_H264: + quant_param = ctx->params.h264_inter_qp; + break; + case V4L2_PIX_FMT_MPEG4: + quant_param = ctx->params.mpeg4_inter_qp; + break; + default: + v4l2_warn(&ctx->dev->v4l2_dev, + "cannot set inter qp, fmt not supported\n"); + break; + } + } + + /* submit */ + coda_write(dev, CODA_ROT_MIR_ENABLE | ctx->params.rot_mode, CODA_CMD_ENC_PIC_ROT_MODE); + coda_write(dev, quant_param, CODA_CMD_ENC_PIC_QS); + + + picture_y = vb2_dma_contig_plane_dma_addr(src_buf, 0); + picture_cb = picture_y + q_data_src->width * q_data_src->height; + picture_cr = picture_cb + q_data_src->width / 2 * + q_data_src->height / 2; + + coda_write(dev, picture_y, CODA_CMD_ENC_PIC_SRC_ADDR_Y); + coda_write(dev, picture_cb, CODA_CMD_ENC_PIC_SRC_ADDR_CB); + coda_write(dev, picture_cr, CODA_CMD_ENC_PIC_SRC_ADDR_CR); + coda_write(dev, force_ipicture << 1 & 0x2, + CODA_CMD_ENC_PIC_OPTION); + + coda_write(dev, pic_stream_buffer_addr, CODA_CMD_ENC_PIC_BB_START); + coda_write(dev, pic_stream_buffer_size / 1024, + CODA_CMD_ENC_PIC_BB_SIZE); + + if (dev->devtype->product == CODA_7541) { + coda_write(dev, CODA7_USE_BIT_ENABLE | CODA7_USE_HOST_BIT_ENABLE | + CODA7_USE_ME_ENABLE | CODA7_USE_HOST_ME_ENABLE, + CODA7_REG_BIT_AXI_SRAM_USE); + } + + /* 1 second timeout in case CODA locks up */ + schedule_delayed_work(&dev->timeout, HZ); + + INIT_COMPLETION(dev->done); + coda_command_async(ctx, CODA_COMMAND_PIC_RUN); +} + +static int coda_job_ready(void *m2m_priv) +{ + struct coda_ctx *ctx = m2m_priv; + + /* + * For both 'P' and 'key' frame cases 1 picture + * and 1 frame are needed. + */ + if (!v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) || + !v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx)) { + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "not ready: not enough video buffers.\n"); + return 0; + } + + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "job ready\n"); + return 1; +} + +static void coda_job_abort(void *priv) +{ + struct coda_ctx *ctx = priv; + struct coda_dev *dev = ctx->dev; + + ctx->aborting = 1; + + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "Aborting task\n"); + + v4l2_m2m_job_finish(dev->m2m_dev, ctx->m2m_ctx); +} + +static void coda_lock(void *m2m_priv) +{ + struct coda_ctx *ctx = m2m_priv; + struct coda_dev *pcdev = ctx->dev; + mutex_lock(&pcdev->dev_mutex); +} + +static void coda_unlock(void *m2m_priv) +{ + struct coda_ctx *ctx = m2m_priv; + struct coda_dev *pcdev = ctx->dev; + mutex_unlock(&pcdev->dev_mutex); +} + +static struct v4l2_m2m_ops coda_m2m_ops = { + .device_run = coda_device_run, + .job_ready = coda_job_ready, + .job_abort = coda_job_abort, + .lock = coda_lock, + .unlock = coda_unlock, +}; + +static void set_default_params(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + + ctx->params.codec_mode = CODA_MODE_INVALID; + ctx->colorspace = V4L2_COLORSPACE_REC709; + ctx->params.framerate = 30; + ctx->aborting = 0; + + /* Default formats for output and input queues */ + ctx->q_data[V4L2_M2M_SRC].fmt = &dev->devtype->formats[0]; + ctx->q_data[V4L2_M2M_DST].fmt = &dev->devtype->formats[1]; + ctx->q_data[V4L2_M2M_SRC].width = MAX_W; + ctx->q_data[V4L2_M2M_SRC].height = MAX_H; + ctx->q_data[V4L2_M2M_SRC].sizeimage = (MAX_W * MAX_H * 3) / 2; + ctx->q_data[V4L2_M2M_DST].width = MAX_W; + ctx->q_data[V4L2_M2M_DST].height = MAX_H; + ctx->q_data[V4L2_M2M_DST].sizeimage = CODA_MAX_FRAME_SIZE; +} + +/* + * Queue operations + */ +static int coda_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(vq); + struct coda_q_data *q_data; + unsigned int size; + + q_data = get_q_data(ctx, vq->type); + size = q_data->sizeimage; + + *nplanes = 1; + sizes[0] = size; + + alloc_ctxs[0] = ctx->dev->alloc_ctx; + + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "get %d buffer(s) of size %d each.\n", *nbuffers, size); + + return 0; +} + +static int coda_buf_prepare(struct vb2_buffer *vb) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct coda_q_data *q_data; + + q_data = get_q_data(ctx, vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + v4l2_warn(&ctx->dev->v4l2_dev, + "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void coda_buf_queue(struct vb2_buffer *vb) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); +} + +static void coda_wait_prepare(struct vb2_queue *q) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(q); + coda_unlock(ctx); +} + +static void coda_wait_finish(struct vb2_queue *q) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(q); + coda_lock(ctx); +} + +static void coda_free_framebuffers(struct coda_ctx *ctx) +{ + int i; + + for (i = 0; i < CODA_MAX_FRAMEBUFFERS; i++) { + if (ctx->internal_frames[i].vaddr) { + dma_free_coherent(&ctx->dev->plat_dev->dev, + ctx->internal_frames[i].size, + ctx->internal_frames[i].vaddr, + ctx->internal_frames[i].paddr); + ctx->internal_frames[i].vaddr = NULL; + } + } +} + +static int coda_alloc_framebuffers(struct coda_ctx *ctx, struct coda_q_data *q_data, u32 fourcc) +{ + struct coda_dev *dev = ctx->dev; + + int height = q_data->height; + int width = q_data->width; + u32 *p; + int i; + + /* Allocate frame buffers */ + ctx->num_internal_frames = CODA_MAX_FRAMEBUFFERS; + for (i = 0; i < ctx->num_internal_frames; i++) { + ctx->internal_frames[i].size = q_data->sizeimage; + if (fourcc == V4L2_PIX_FMT_H264 && dev->devtype->product != CODA_DX6) + ctx->internal_frames[i].size += width / 2 * height / 2; + ctx->internal_frames[i].vaddr = dma_alloc_coherent( + &dev->plat_dev->dev, ctx->internal_frames[i].size, + &ctx->internal_frames[i].paddr, GFP_KERNEL); + if (!ctx->internal_frames[i].vaddr) { + coda_free_framebuffers(ctx); + return -ENOMEM; + } + } + + /* Register frame buffers in the parameter buffer */ + p = ctx->parabuf.vaddr; + + if (dev->devtype->product == CODA_DX6) { + for (i = 0; i < ctx->num_internal_frames; i++) { + p[i * 3] = ctx->internal_frames[i].paddr; /* Y */ + p[i * 3 + 1] = p[i * 3] + width * height; /* Cb */ + p[i * 3 + 2] = p[i * 3 + 1] + width / 2 * height / 2; /* Cr */ + } + } else { + for (i = 0; i < ctx->num_internal_frames; i += 2) { + p[i * 3 + 1] = ctx->internal_frames[i].paddr; /* Y */ + p[i * 3] = p[i * 3 + 1] + width * height; /* Cb */ + p[i * 3 + 3] = p[i * 3] + (width / 2) * (height / 2); /* Cr */ + + if (fourcc == V4L2_PIX_FMT_H264) + p[96 + i + 1] = p[i * 3 + 3] + (width / 2) * (height / 2); + + if (i + 1 < ctx->num_internal_frames) { + p[i * 3 + 2] = ctx->internal_frames[i+1].paddr; /* Y */ + p[i * 3 + 5] = p[i * 3 + 2] + width * height ; /* Cb */ + p[i * 3 + 4] = p[i * 3 + 5] + (width / 2) * (height / 2); /* Cr */ + + if (fourcc == V4L2_PIX_FMT_H264) + p[96 + i] = p[i * 3 + 4] + (width / 2) * (height / 2); + } + } + } + + return 0; +} + +static int coda_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(q); + struct v4l2_device *v4l2_dev = &ctx->dev->v4l2_dev; + u32 bitstream_buf, bitstream_size; + struct coda_dev *dev = ctx->dev; + struct coda_q_data *q_data_src, *q_data_dst; + struct vb2_buffer *buf; + u32 dst_fourcc; + u32 value; + int ret; + + if (count < 1) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + ctx->rawstreamon = 1; + else + ctx->compstreamon = 1; + + /* Don't start the coda unless both queues are on */ + if (!(ctx->rawstreamon & ctx->compstreamon)) + return 0; + + if (coda_isbusy(dev)) + if (wait_for_completion_interruptible_timeout(&dev->done, HZ) <= 0) + return -EBUSY; + + ctx->gopcounter = ctx->params.gop_size - 1; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + bitstream_buf = vb2_dma_contig_plane_dma_addr(buf, 0); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + bitstream_size = q_data_dst->sizeimage; + dst_fourcc = q_data_dst->fmt->fourcc; + + /* Find out whether coda must encode or decode */ + if (q_data_src->fmt->type == CODA_FMT_RAW && + q_data_dst->fmt->type == CODA_FMT_ENC) { + ctx->inst_type = CODA_INST_ENCODER; + } else if (q_data_src->fmt->type == CODA_FMT_ENC && + q_data_dst->fmt->type == CODA_FMT_RAW) { + ctx->inst_type = CODA_INST_DECODER; + v4l2_err(v4l2_dev, "decoding not supported.\n"); + return -EINVAL; + } else { + v4l2_err(v4l2_dev, "couldn't tell instance type.\n"); + return -EINVAL; + } + + if (!coda_is_initialized(dev)) { + v4l2_err(v4l2_dev, "coda is not initialized.\n"); + return -EFAULT; + } + coda_write(dev, ctx->parabuf.paddr, CODA_REG_BIT_PARA_BUF_ADDR); + coda_write(dev, bitstream_buf, CODA_REG_BIT_RD_PTR(ctx->idx)); + coda_write(dev, bitstream_buf, CODA_REG_BIT_WR_PTR(ctx->idx)); + switch (dev->devtype->product) { + case CODA_DX6: + coda_write(dev, CODADX6_STREAM_BUF_DYNALLOC_EN | + CODADX6_STREAM_BUF_PIC_RESET, CODA_REG_BIT_STREAM_CTRL); + break; + default: + coda_write(dev, CODA7_STREAM_BUF_DYNALLOC_EN | + CODA7_STREAM_BUF_PIC_RESET, CODA_REG_BIT_STREAM_CTRL); + } + + if (dev->devtype->product == CODA_DX6) { + /* Configure the coda */ + coda_write(dev, dev->iram_paddr, CODADX6_REG_BIT_SEARCH_RAM_BASE_ADDR); + } + + /* Could set rotation here if needed */ + switch (dev->devtype->product) { + case CODA_DX6: + value = (q_data_src->width & CODADX6_PICWIDTH_MASK) << CODADX6_PICWIDTH_OFFSET; + break; + default: + value = (q_data_src->width & CODA7_PICWIDTH_MASK) << CODA7_PICWIDTH_OFFSET; + } + value |= (q_data_src->height & CODA_PICHEIGHT_MASK) << CODA_PICHEIGHT_OFFSET; + coda_write(dev, value, CODA_CMD_ENC_SEQ_SRC_SIZE); + coda_write(dev, ctx->params.framerate, + CODA_CMD_ENC_SEQ_SRC_F_RATE); + + switch (dst_fourcc) { + case V4L2_PIX_FMT_MPEG4: + if (dev->devtype->product == CODA_DX6) + ctx->params.codec_mode = CODADX6_MODE_ENCODE_MP4; + else + ctx->params.codec_mode = CODA7_MODE_ENCODE_MP4; + + coda_write(dev, CODA_STD_MPEG4, CODA_CMD_ENC_SEQ_COD_STD); + coda_write(dev, 0, CODA_CMD_ENC_SEQ_MP4_PARA); + break; + case V4L2_PIX_FMT_H264: + if (dev->devtype->product == CODA_DX6) + ctx->params.codec_mode = CODADX6_MODE_ENCODE_H264; + else + ctx->params.codec_mode = CODA7_MODE_ENCODE_H264; + + coda_write(dev, CODA_STD_H264, CODA_CMD_ENC_SEQ_COD_STD); + coda_write(dev, 0, CODA_CMD_ENC_SEQ_264_PARA); + break; + default: + v4l2_err(v4l2_dev, + "dst format (0x%08x) invalid.\n", dst_fourcc); + return -EINVAL; + } + + switch (ctx->params.slice_mode) { + case V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE: + value = 0; + break; + case V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_MB: + value = (ctx->params.slice_max_mb & CODA_SLICING_SIZE_MASK) << CODA_SLICING_SIZE_OFFSET; + value |= (1 & CODA_SLICING_UNIT_MASK) << CODA_SLICING_UNIT_OFFSET; + value |= 1 & CODA_SLICING_MODE_MASK; + break; + case V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_BYTES: + value = (ctx->params.slice_max_bits & CODA_SLICING_SIZE_MASK) << CODA_SLICING_SIZE_OFFSET; + value |= (0 & CODA_SLICING_UNIT_MASK) << CODA_SLICING_UNIT_OFFSET; + value |= 1 & CODA_SLICING_MODE_MASK; + break; + } + coda_write(dev, value, CODA_CMD_ENC_SEQ_SLICE_MODE); + value = ctx->params.gop_size & CODA_GOP_SIZE_MASK; + coda_write(dev, value, CODA_CMD_ENC_SEQ_GOP_SIZE); + + if (ctx->params.bitrate) { + /* Rate control enabled */ + value = (ctx->params.bitrate & CODA_RATECONTROL_BITRATE_MASK) << CODA_RATECONTROL_BITRATE_OFFSET; + value |= 1 & CODA_RATECONTROL_ENABLE_MASK; + } else { + value = 0; + } + coda_write(dev, value, CODA_CMD_ENC_SEQ_RC_PARA); + + coda_write(dev, 0, CODA_CMD_ENC_SEQ_RC_BUF_SIZE); + coda_write(dev, 0, CODA_CMD_ENC_SEQ_INTRA_REFRESH); + + coda_write(dev, bitstream_buf, CODA_CMD_ENC_SEQ_BB_START); + coda_write(dev, bitstream_size / 1024, CODA_CMD_ENC_SEQ_BB_SIZE); + + /* set default gamma */ + value = (CODA_DEFAULT_GAMMA & CODA_GAMMA_MASK) << CODA_GAMMA_OFFSET; + coda_write(dev, value, CODA_CMD_ENC_SEQ_RC_GAMMA); + + value = (CODA_DEFAULT_GAMMA > 0) << CODA_OPTION_GAMMA_OFFSET; + value |= (0 & CODA_OPTION_SLICEREPORT_MASK) << CODA_OPTION_SLICEREPORT_OFFSET; + coda_write(dev, value, CODA_CMD_ENC_SEQ_OPTION); + + if (dst_fourcc == V4L2_PIX_FMT_H264) { + value = (FMO_SLICE_SAVE_BUF_SIZE << 7); + value |= (0 & CODA_FMOPARAM_TYPE_MASK) << CODA_FMOPARAM_TYPE_OFFSET; + value |= 0 & CODA_FMOPARAM_SLICENUM_MASK; + if (dev->devtype->product == CODA_DX6) { + coda_write(dev, value, CODADX6_CMD_ENC_SEQ_FMO); + } else { + coda_write(dev, dev->iram_paddr, CODA7_CMD_ENC_SEQ_SEARCH_BASE); + coda_write(dev, 48 * 1024, CODA7_CMD_ENC_SEQ_SEARCH_SIZE); + } + } + + if (coda_command_sync(ctx, CODA_COMMAND_SEQ_INIT)) { + v4l2_err(v4l2_dev, "CODA_COMMAND_SEQ_INIT timeout\n"); + return -ETIMEDOUT; + } + + if (coda_read(dev, CODA_RET_ENC_SEQ_SUCCESS) == 0) + return -EFAULT; + + ret = coda_alloc_framebuffers(ctx, q_data_src, dst_fourcc); + if (ret < 0) + return ret; + + coda_write(dev, ctx->num_internal_frames, CODA_CMD_SET_FRAME_BUF_NUM); + coda_write(dev, round_up(q_data_src->width, 8), CODA_CMD_SET_FRAME_BUF_STRIDE); + if (dev->devtype->product != CODA_DX6) { + coda_write(dev, round_up(q_data_src->width, 8), CODA7_CMD_SET_FRAME_SOURCE_BUF_STRIDE); + coda_write(dev, dev->iram_paddr + 48 * 1024, CODA7_CMD_SET_FRAME_AXI_DBKY_ADDR); + coda_write(dev, dev->iram_paddr + 53 * 1024, CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR); + coda_write(dev, dev->iram_paddr + 58 * 1024, CODA7_CMD_SET_FRAME_AXI_BIT_ADDR); + coda_write(dev, dev->iram_paddr + 68 * 1024, CODA7_CMD_SET_FRAME_AXI_IPACDC_ADDR); + coda_write(dev, 0x0, CODA7_CMD_SET_FRAME_AXI_OVL_ADDR); + } + if (coda_command_sync(ctx, CODA_COMMAND_SET_FRAME_BUF)) { + v4l2_err(v4l2_dev, "CODA_COMMAND_SET_FRAME_BUF timeout\n"); + return -ETIMEDOUT; + } + + /* Save stream headers */ + buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + switch (dst_fourcc) { + case V4L2_PIX_FMT_H264: + /* + * Get SPS in the first frame and copy it to an + * intermediate buffer. + */ + coda_write(dev, vb2_dma_contig_plane_dma_addr(buf, 0), CODA_CMD_ENC_HEADER_BB_START); + coda_write(dev, bitstream_size, CODA_CMD_ENC_HEADER_BB_SIZE); + coda_write(dev, CODA_HEADER_H264_SPS, CODA_CMD_ENC_HEADER_CODE); + if (coda_command_sync(ctx, CODA_COMMAND_ENCODE_HEADER)) { + v4l2_err(v4l2_dev, "CODA_COMMAND_ENCODE_HEADER timeout\n"); + return -ETIMEDOUT; + } + ctx->vpu_header_size[0] = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->idx)) - + coda_read(dev, CODA_CMD_ENC_HEADER_BB_START); + memcpy(&ctx->vpu_header[0][0], vb2_plane_vaddr(buf, 0), + ctx->vpu_header_size[0]); + + /* + * Get PPS in the first frame and copy it to an + * intermediate buffer. + */ + coda_write(dev, vb2_dma_contig_plane_dma_addr(buf, 0), CODA_CMD_ENC_HEADER_BB_START); + coda_write(dev, bitstream_size, CODA_CMD_ENC_HEADER_BB_SIZE); + coda_write(dev, CODA_HEADER_H264_PPS, CODA_CMD_ENC_HEADER_CODE); + if (coda_command_sync(ctx, CODA_COMMAND_ENCODE_HEADER)) { + v4l2_err(v4l2_dev, "CODA_COMMAND_ENCODE_HEADER timeout\n"); + return -ETIMEDOUT; + } + ctx->vpu_header_size[1] = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->idx)) - + coda_read(dev, CODA_CMD_ENC_HEADER_BB_START); + memcpy(&ctx->vpu_header[1][0], vb2_plane_vaddr(buf, 0), + ctx->vpu_header_size[1]); + ctx->vpu_header_size[2] = 0; + break; + case V4L2_PIX_FMT_MPEG4: + /* + * Get VOS in the first frame and copy it to an + * intermediate buffer + */ + coda_write(dev, vb2_dma_contig_plane_dma_addr(buf, 0), CODA_CMD_ENC_HEADER_BB_START); + coda_write(dev, bitstream_size, CODA_CMD_ENC_HEADER_BB_SIZE); + coda_write(dev, CODA_HEADER_MP4V_VOS, CODA_CMD_ENC_HEADER_CODE); + if (coda_command_sync(ctx, CODA_COMMAND_ENCODE_HEADER)) { + v4l2_err(v4l2_dev, "CODA_COMMAND_ENCODE_HEADER timeout\n"); + return -ETIMEDOUT; + } + ctx->vpu_header_size[0] = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->idx)) - + coda_read(dev, CODA_CMD_ENC_HEADER_BB_START); + memcpy(&ctx->vpu_header[0][0], vb2_plane_vaddr(buf, 0), + ctx->vpu_header_size[0]); + + coda_write(dev, vb2_dma_contig_plane_dma_addr(buf, 0), CODA_CMD_ENC_HEADER_BB_START); + coda_write(dev, bitstream_size, CODA_CMD_ENC_HEADER_BB_SIZE); + coda_write(dev, CODA_HEADER_MP4V_VIS, CODA_CMD_ENC_HEADER_CODE); + if (coda_command_sync(ctx, CODA_COMMAND_ENCODE_HEADER)) { + v4l2_err(v4l2_dev, "CODA_COMMAND_ENCODE_HEADER failed\n"); + return -ETIMEDOUT; + } + ctx->vpu_header_size[1] = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->idx)) - + coda_read(dev, CODA_CMD_ENC_HEADER_BB_START); + memcpy(&ctx->vpu_header[1][0], vb2_plane_vaddr(buf, 0), + ctx->vpu_header_size[1]); + + coda_write(dev, vb2_dma_contig_plane_dma_addr(buf, 0), CODA_CMD_ENC_HEADER_BB_START); + coda_write(dev, bitstream_size, CODA_CMD_ENC_HEADER_BB_SIZE); + coda_write(dev, CODA_HEADER_MP4V_VOL, CODA_CMD_ENC_HEADER_CODE); + if (coda_command_sync(ctx, CODA_COMMAND_ENCODE_HEADER)) { + v4l2_err(v4l2_dev, "CODA_COMMAND_ENCODE_HEADER failed\n"); + return -ETIMEDOUT; + } + ctx->vpu_header_size[2] = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->idx)) - + coda_read(dev, CODA_CMD_ENC_HEADER_BB_START); + memcpy(&ctx->vpu_header[2][0], vb2_plane_vaddr(buf, 0), + ctx->vpu_header_size[2]); + break; + default: + /* No more formats need to save headers at the moment */ + break; + } + + return 0; +} + +static int coda_stop_streaming(struct vb2_queue *q) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(q); + struct coda_dev *dev = ctx->dev; + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "%s: output\n", __func__); + ctx->rawstreamon = 0; + } else { + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "%s: capture\n", __func__); + ctx->compstreamon = 0; + } + + /* Don't stop the coda unless both queues are off */ + if (ctx->rawstreamon || ctx->compstreamon) + return 0; + + if (coda_isbusy(dev)) { + if (wait_for_completion_interruptible_timeout(&dev->done, HZ) <= 0) { + v4l2_warn(&dev->v4l2_dev, + "%s: timeout, sending SEQ_END anyway\n", __func__); + } + } + + cancel_delayed_work(&dev->timeout); + + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "%s: sent command 'SEQ_END' to coda\n", __func__); + if (coda_command_sync(ctx, CODA_COMMAND_SEQ_END)) { + v4l2_err(&dev->v4l2_dev, + "CODA_COMMAND_SEQ_END failed\n"); + return -ETIMEDOUT; + } + + coda_free_framebuffers(ctx); + + return 0; +} + +static struct vb2_ops coda_qops = { + .queue_setup = coda_queue_setup, + .buf_prepare = coda_buf_prepare, + .buf_queue = coda_buf_queue, + .wait_prepare = coda_wait_prepare, + .wait_finish = coda_wait_finish, + .start_streaming = coda_start_streaming, + .stop_streaming = coda_stop_streaming, +}; + +static int coda_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct coda_ctx *ctx = + container_of(ctrl->handler, struct coda_ctx, ctrls); + + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "s_ctrl: id = %d, val = %d\n", ctrl->id, ctrl->val); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + if (ctrl->val) + ctx->params.rot_mode |= CODA_MIR_HOR; + else + ctx->params.rot_mode &= ~CODA_MIR_HOR; + break; + case V4L2_CID_VFLIP: + if (ctrl->val) + ctx->params.rot_mode |= CODA_MIR_VER; + else + ctx->params.rot_mode &= ~CODA_MIR_VER; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + ctx->params.bitrate = ctrl->val / 1000; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + ctx->params.gop_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + ctx->params.h264_intra_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + ctx->params.h264_inter_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP: + ctx->params.mpeg4_intra_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP: + ctx->params.mpeg4_inter_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + ctx->params.slice_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + ctx->params.slice_max_mb = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + ctx->params.slice_max_bits = ctrl->val * 8; + break; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + break; + default: + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "Invalid control, id=%d, val=%d\n", + ctrl->id, ctrl->val); + return -EINVAL; + } + + return 0; +} + +static struct v4l2_ctrl_ops coda_ctrl_ops = { + .s_ctrl = coda_s_ctrl, +}; + +static int coda_ctrls_setup(struct coda_ctx *ctx) +{ + v4l2_ctrl_handler_init(&ctx->ctrls, 9); + + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, 0, 32767000, 1, 0); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, 1, 60, 1, 16); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, 1, 51, 1, 25); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, 1, 51, 1, 25); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP, 1, 31, 1, 2); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP, 1, 31, 1, 2); + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE, + V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_BYTES, 0x0, + V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB, 1, 0x3fffffff, 1, 1); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES, 1, 0x3fffffff, 1, 500); + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEADER_MODE, + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + (1 << V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE), + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME); + + if (ctx->ctrls.error) { + v4l2_err(&ctx->dev->v4l2_dev, "control initialization error (%d)", + ctx->ctrls.error); + return -EINVAL; + } + + return v4l2_ctrl_handler_setup(&ctx->ctrls); +} + +static int coda_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct coda_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &coda_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &coda_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + + return vb2_queue_init(dst_vq); +} + +static int coda_next_free_instance(struct coda_dev *dev) +{ + return ffz(dev->instance_mask); +} + +static int coda_open(struct file *file) +{ + struct coda_dev *dev = video_drvdata(file); + struct coda_ctx *ctx = NULL; + int ret = 0; + int idx; + + idx = coda_next_free_instance(dev); + if (idx >= CODA_MAX_INSTANCES) + return -EBUSY; + set_bit(idx, &dev->instance_mask); + + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + ctx->dev = dev; + ctx->idx = idx; + + set_default_params(ctx); + ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, + &coda_queue_init); + if (IS_ERR(ctx->m2m_ctx)) { + int ret = PTR_ERR(ctx->m2m_ctx); + + v4l2_err(&dev->v4l2_dev, "%s return error (%d)\n", + __func__, ret); + goto err; + } + ret = coda_ctrls_setup(ctx); + if (ret) { + v4l2_err(&dev->v4l2_dev, "failed to setup coda controls\n"); + goto err; + } + + ctx->fh.ctrl_handler = &ctx->ctrls; + + ctx->parabuf.vaddr = dma_alloc_coherent(&dev->plat_dev->dev, + CODA_PARA_BUF_SIZE, &ctx->parabuf.paddr, GFP_KERNEL); + if (!ctx->parabuf.vaddr) { + v4l2_err(&dev->v4l2_dev, "failed to allocate parabuf"); + ret = -ENOMEM; + goto err; + } + + coda_lock(ctx); + list_add(&ctx->list, &dev->instances); + coda_unlock(ctx); + + clk_prepare_enable(dev->clk_per); + clk_prepare_enable(dev->clk_ahb); + + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, "Created instance %d (%p)\n", + ctx->idx, ctx); + + return 0; + +err: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + return ret; +} + +static int coda_release(struct file *file) +{ + struct coda_dev *dev = video_drvdata(file); + struct coda_ctx *ctx = fh_to_ctx(file->private_data); + + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, "Releasing instance %p\n", + ctx); + + coda_lock(ctx); + list_del(&ctx->list); + coda_unlock(ctx); + + dma_free_coherent(&dev->plat_dev->dev, CODA_PARA_BUF_SIZE, + ctx->parabuf.vaddr, ctx->parabuf.paddr); + v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_ctrl_handler_free(&ctx->ctrls); + clk_disable_unprepare(dev->clk_per); + clk_disable_unprepare(dev->clk_ahb); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + clear_bit(ctx->idx, &dev->instance_mask); + kfree(ctx); + + return 0; +} + +static unsigned int coda_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct coda_ctx *ctx = fh_to_ctx(file->private_data); + int ret; + + coda_lock(ctx); + ret = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); + coda_unlock(ctx); + return ret; +} + +static int coda_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct coda_ctx *ctx = fh_to_ctx(file->private_data); + + return v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); +} + +static const struct v4l2_file_operations coda_fops = { + .owner = THIS_MODULE, + .open = coda_open, + .release = coda_release, + .poll = coda_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = coda_mmap, +}; + +static irqreturn_t coda_irq_handler(int irq, void *data) +{ + struct vb2_buffer *src_buf, *dst_buf; + struct coda_dev *dev = data; + u32 wr_ptr, start_ptr; + struct coda_ctx *ctx; + + __cancel_delayed_work(&dev->timeout); + + /* read status register to attend the IRQ */ + coda_read(dev, CODA_REG_BIT_INT_STATUS); + coda_write(dev, CODA_REG_BIT_INT_CLEAR_SET, + CODA_REG_BIT_INT_CLEAR); + + ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); + if (ctx == NULL) { + v4l2_err(&dev->v4l2_dev, "Instance released before the end of transaction\n"); + return IRQ_HANDLED; + } + + if (ctx->aborting) { + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "task has been aborted\n"); + return IRQ_HANDLED; + } + + if (coda_isbusy(ctx->dev)) { + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, + "coda is still busy!!!!\n"); + return IRQ_NONE; + } + + complete(&dev->done); + + src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + + /* Get results from the coda */ + coda_read(dev, CODA_RET_ENC_PIC_TYPE); + start_ptr = coda_read(dev, CODA_CMD_ENC_PIC_BB_START); + wr_ptr = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->idx)); + /* Calculate bytesused field */ + if (dst_buf->v4l2_buf.sequence == 0) { + dst_buf->v4l2_planes[0].bytesused = (wr_ptr - start_ptr) + + ctx->vpu_header_size[0] + + ctx->vpu_header_size[1] + + ctx->vpu_header_size[2]; + } else { + dst_buf->v4l2_planes[0].bytesused = (wr_ptr - start_ptr); + } + + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "frame size = %u\n", + wr_ptr - start_ptr); + + coda_read(dev, CODA_RET_ENC_PIC_SLICE_NUM); + coda_read(dev, CODA_RET_ENC_PIC_FLAG); + + if (src_buf->v4l2_buf.flags & V4L2_BUF_FLAG_KEYFRAME) { + dst_buf->v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME; + dst_buf->v4l2_buf.flags &= ~V4L2_BUF_FLAG_PFRAME; + } else { + dst_buf->v4l2_buf.flags |= V4L2_BUF_FLAG_PFRAME; + dst_buf->v4l2_buf.flags &= ~V4L2_BUF_FLAG_KEYFRAME; + } + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + + ctx->gopcounter--; + if (ctx->gopcounter < 0) + ctx->gopcounter = ctx->params.gop_size - 1; + + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, + "job finished: encoding frame (%d) (%s)\n", + dst_buf->v4l2_buf.sequence, + (dst_buf->v4l2_buf.flags & V4L2_BUF_FLAG_KEYFRAME) ? + "KEYFRAME" : "PFRAME"); + + v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->m2m_ctx); + + return IRQ_HANDLED; +} + +static void coda_timeout(struct work_struct *work) +{ + struct coda_ctx *ctx; + struct coda_dev *dev = container_of(to_delayed_work(work), + struct coda_dev, timeout); + + if (completion_done(&dev->done)) + return; + + complete(&dev->done); + + v4l2_err(&dev->v4l2_dev, "CODA PIC_RUN timeout, stopping all streams\n"); + + mutex_lock(&dev->dev_mutex); + list_for_each_entry(ctx, &dev->instances, list) { + v4l2_m2m_streamoff(NULL, ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + v4l2_m2m_streamoff(NULL, ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + } + mutex_unlock(&dev->dev_mutex); +} + +static u32 coda_supported_firmwares[] = { + CODA_FIRMWARE_VERNUM(CODA_DX6, 2, 2, 5), + CODA_FIRMWARE_VERNUM(CODA_7541, 13, 4, 29), +}; + +static bool coda_firmware_supported(u32 vernum) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coda_supported_firmwares); i++) + if (vernum == coda_supported_firmwares[i]) + return true; + return false; +} + +static char *coda_product_name(int product) +{ + static char buf[9]; + + switch (product) { + case CODA_DX6: + return "CodaDx6"; + case CODA_7541: + return "CODA7541"; + default: + snprintf(buf, sizeof(buf), "(0x%04x)", product); + return buf; + } +} + +static int coda_hw_init(struct coda_dev *dev) +{ + u16 product, major, minor, release; + u32 data; + u16 *p; + int i; + + clk_prepare_enable(dev->clk_per); + clk_prepare_enable(dev->clk_ahb); + + /* + * Copy the first CODA_ISRAM_SIZE in the internal SRAM. + * The 16-bit chars in the code buffer are in memory access + * order, re-sort them to CODA order for register download. + * Data in this SRAM survives a reboot. + */ + p = (u16 *)dev->codebuf.vaddr; + if (dev->devtype->product == CODA_DX6) { + for (i = 0; i < (CODA_ISRAM_SIZE / 2); i++) { + data = CODA_DOWN_ADDRESS_SET(i) | + CODA_DOWN_DATA_SET(p[i ^ 1]); + coda_write(dev, data, CODA_REG_BIT_CODE_DOWN); + } + } else { + for (i = 0; i < (CODA_ISRAM_SIZE / 2); i++) { + data = CODA_DOWN_ADDRESS_SET(i) | + CODA_DOWN_DATA_SET(p[round_down(i, 4) + + 3 - (i % 4)]); + coda_write(dev, data, CODA_REG_BIT_CODE_DOWN); + } + } + + /* Tell the BIT where to find everything it needs */ + coda_write(dev, dev->workbuf.paddr, + CODA_REG_BIT_WORK_BUF_ADDR); + coda_write(dev, dev->codebuf.paddr, + CODA_REG_BIT_CODE_BUF_ADDR); + coda_write(dev, 0, CODA_REG_BIT_CODE_RUN); + + /* Set default values */ + switch (dev->devtype->product) { + case CODA_DX6: + coda_write(dev, CODADX6_STREAM_BUF_PIC_FLUSH, CODA_REG_BIT_STREAM_CTRL); + break; + default: + coda_write(dev, CODA7_STREAM_BUF_PIC_FLUSH, CODA_REG_BIT_STREAM_CTRL); + } + coda_write(dev, 0, CODA_REG_BIT_FRAME_MEM_CTRL); + + if (dev->devtype->product != CODA_DX6) + coda_write(dev, 0, CODA7_REG_BIT_AXI_SRAM_USE); + + coda_write(dev, CODA_INT_INTERRUPT_ENABLE, + CODA_REG_BIT_INT_ENABLE); + + /* Reset VPU and start processor */ + data = coda_read(dev, CODA_REG_BIT_CODE_RESET); + data |= CODA_REG_RESET_ENABLE; + coda_write(dev, data, CODA_REG_BIT_CODE_RESET); + udelay(10); + data &= ~CODA_REG_RESET_ENABLE; + coda_write(dev, data, CODA_REG_BIT_CODE_RESET); + coda_write(dev, CODA_REG_RUN_ENABLE, CODA_REG_BIT_CODE_RUN); + + /* Load firmware */ + coda_write(dev, 0, CODA_CMD_FIRMWARE_VERNUM); + coda_write(dev, CODA_REG_BIT_BUSY_FLAG, CODA_REG_BIT_BUSY); + coda_write(dev, 0, CODA_REG_BIT_RUN_INDEX); + coda_write(dev, 0, CODA_REG_BIT_RUN_COD_STD); + coda_write(dev, CODA_COMMAND_FIRMWARE_GET, CODA_REG_BIT_RUN_COMMAND); + if (coda_wait_timeout(dev)) { + clk_disable_unprepare(dev->clk_per); + clk_disable_unprepare(dev->clk_ahb); + v4l2_err(&dev->v4l2_dev, "firmware get command error\n"); + return -EIO; + } + + /* Check we are compatible with the loaded firmware */ + data = coda_read(dev, CODA_CMD_FIRMWARE_VERNUM); + product = CODA_FIRMWARE_PRODUCT(data); + major = CODA_FIRMWARE_MAJOR(data); + minor = CODA_FIRMWARE_MINOR(data); + release = CODA_FIRMWARE_RELEASE(data); + + clk_disable_unprepare(dev->clk_per); + clk_disable_unprepare(dev->clk_ahb); + + if (product != dev->devtype->product) { + v4l2_err(&dev->v4l2_dev, "Wrong firmware. Hw: %s, Fw: %s," + " Version: %u.%u.%u\n", + coda_product_name(dev->devtype->product), + coda_product_name(product), major, minor, release); + return -EINVAL; + } + + v4l2_info(&dev->v4l2_dev, "Initialized %s.\n", + coda_product_name(product)); + + if (coda_firmware_supported(data)) { + v4l2_info(&dev->v4l2_dev, "Firmware version: %u.%u.%u\n", + major, minor, release); + } else { + v4l2_warn(&dev->v4l2_dev, "Unsupported firmware version: " + "%u.%u.%u\n", major, minor, release); + } + + return 0; +} + +static void coda_fw_callback(const struct firmware *fw, void *context) +{ + struct coda_dev *dev = context; + struct platform_device *pdev = dev->plat_dev; + int ret; + + if (!fw) { + v4l2_err(&dev->v4l2_dev, "firmware request failed\n"); + return; + } + + /* allocate auxiliary per-device code buffer for the BIT processor */ + dev->codebuf.size = fw->size; + dev->codebuf.vaddr = dma_alloc_coherent(&pdev->dev, fw->size, + &dev->codebuf.paddr, + GFP_KERNEL); + if (!dev->codebuf.vaddr) { + dev_err(&pdev->dev, "failed to allocate code buffer\n"); + return; + } + + /* Copy the whole firmware image to the code buffer */ + memcpy(dev->codebuf.vaddr, fw->data, fw->size); + release_firmware(fw); + + ret = coda_hw_init(dev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "HW initialization failed\n"); + return; + } + + dev->vfd.fops = &coda_fops, + dev->vfd.ioctl_ops = &coda_ioctl_ops; + dev->vfd.release = video_device_release_empty, + dev->vfd.lock = &dev->dev_mutex; + dev->vfd.v4l2_dev = &dev->v4l2_dev; + dev->vfd.vfl_dir = VFL_DIR_M2M; + snprintf(dev->vfd.name, sizeof(dev->vfd.name), "%s", CODA_NAME); + video_set_drvdata(&dev->vfd, dev); + + dev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(dev->alloc_ctx)) { + v4l2_err(&dev->v4l2_dev, "Failed to alloc vb2 context\n"); + return; + } + + dev->m2m_dev = v4l2_m2m_init(&coda_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + goto rel_ctx; + } + + ret = video_register_device(&dev->vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto rel_m2m; + } + v4l2_info(&dev->v4l2_dev, "codec registered as /dev/video%d\n", + dev->vfd.num); + + return; + +rel_m2m: + v4l2_m2m_release(dev->m2m_dev); +rel_ctx: + vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); +} + +static int coda_firmware_request(struct coda_dev *dev) +{ + char *fw = dev->devtype->firmware; + + dev_dbg(&dev->plat_dev->dev, "requesting firmware '%s' for %s\n", fw, + coda_product_name(dev->devtype->product)); + + return request_firmware_nowait(THIS_MODULE, true, + fw, &dev->plat_dev->dev, GFP_KERNEL, dev, coda_fw_callback); +} + +enum coda_platform { + CODA_IMX27, + CODA_IMX53, +}; + +static const struct coda_devtype coda_devdata[] = { + [CODA_IMX27] = { + .firmware = "v4l-codadx6-imx27.bin", + .product = CODA_DX6, + .formats = codadx6_formats, + .num_formats = ARRAY_SIZE(codadx6_formats), + }, + [CODA_IMX53] = { + .firmware = "v4l-coda7541-imx53.bin", + .product = CODA_7541, + .formats = coda7_formats, + .num_formats = ARRAY_SIZE(coda7_formats), + }, +}; + +static struct platform_device_id coda_platform_ids[] = { + { .name = "coda-imx27", .driver_data = CODA_IMX27 }, + { .name = "coda-imx53", .driver_data = CODA_7541 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, coda_platform_ids); + +#ifdef CONFIG_OF +static const struct of_device_id coda_dt_ids[] = { + { .compatible = "fsl,imx27-vpu", .data = &coda_platform_ids[CODA_IMX27] }, + { .compatible = "fsl,imx53-vpu", .data = &coda_devdata[CODA_IMX53] }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, coda_dt_ids); +#endif + +static int __devinit coda_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(of_match_ptr(coda_dt_ids), &pdev->dev); + const struct platform_device_id *pdev_id; + struct coda_dev *dev; + struct resource *res; + int ret, irq; + + dev = devm_kzalloc(&pdev->dev, sizeof *dev, GFP_KERNEL); + if (!dev) { + dev_err(&pdev->dev, "Not enough memory for %s\n", + CODA_NAME); + return -ENOMEM; + } + + spin_lock_init(&dev->irqlock); + INIT_LIST_HEAD(&dev->instances); + INIT_DELAYED_WORK(&dev->timeout, coda_timeout); + init_completion(&dev->done); + complete(&dev->done); + + dev->plat_dev = pdev; + dev->clk_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(dev->clk_per)) { + dev_err(&pdev->dev, "Could not get per clock\n"); + return PTR_ERR(dev->clk_per); + } + + dev->clk_ahb = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(dev->clk_ahb)) { + dev_err(&pdev->dev, "Could not get ahb clock\n"); + return PTR_ERR(dev->clk_ahb); + } + + /* Get memory for physical registers */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "failed to get memory region resource\n"); + return -ENOENT; + } + + if (devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), CODA_NAME) == NULL) { + dev_err(&pdev->dev, "failed to request memory region\n"); + return -ENOENT; + } + dev->regs_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!dev->regs_base) { + dev_err(&pdev->dev, "failed to ioremap address region\n"); + return -ENOENT; + } + + /* IRQ */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get irq resource\n"); + return -ENOENT; + } + + if (devm_request_irq(&pdev->dev, irq, coda_irq_handler, + 0, CODA_NAME, dev) < 0) { + dev_err(&pdev->dev, "failed to request irq\n"); + return -ENOENT; + } + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + return ret; + + mutex_init(&dev->dev_mutex); + + pdev_id = of_id ? of_id->data : platform_get_device_id(pdev); + + if (of_id) { + dev->devtype = of_id->data; + } else if (pdev_id) { + dev->devtype = &coda_devdata[pdev_id->driver_data]; + } else { + v4l2_device_unregister(&dev->v4l2_dev); + return -EINVAL; + } + + /* allocate auxiliary per-device buffers for the BIT processor */ + switch (dev->devtype->product) { + case CODA_DX6: + dev->workbuf.size = CODADX6_WORK_BUF_SIZE; + break; + default: + dev->workbuf.size = CODA7_WORK_BUF_SIZE; + } + dev->workbuf.vaddr = dma_alloc_coherent(&pdev->dev, dev->workbuf.size, + &dev->workbuf.paddr, + GFP_KERNEL); + if (!dev->workbuf.vaddr) { + dev_err(&pdev->dev, "failed to allocate work buffer\n"); + v4l2_device_unregister(&dev->v4l2_dev); + return -ENOMEM; + } + + if (dev->devtype->product == CODA_DX6) { + dev->iram_paddr = 0xffff4c00; + } else { + void __iomem *iram_vaddr; + + iram_vaddr = iram_alloc(CODA7_IRAM_SIZE, + &dev->iram_paddr); + if (!iram_vaddr) { + dev_err(&pdev->dev, "unable to alloc iram\n"); + return -ENOMEM; + } + } + + platform_set_drvdata(pdev, dev); + + return coda_firmware_request(dev); +} + +static int coda_remove(struct platform_device *pdev) +{ + struct coda_dev *dev = platform_get_drvdata(pdev); + + video_unregister_device(&dev->vfd); + if (dev->m2m_dev) + v4l2_m2m_release(dev->m2m_dev); + if (dev->alloc_ctx) + vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); + v4l2_device_unregister(&dev->v4l2_dev); + if (dev->iram_paddr) + iram_free(dev->iram_paddr, CODA7_IRAM_SIZE); + if (dev->codebuf.vaddr) + dma_free_coherent(&pdev->dev, dev->codebuf.size, + &dev->codebuf.vaddr, dev->codebuf.paddr); + if (dev->workbuf.vaddr) + dma_free_coherent(&pdev->dev, dev->workbuf.size, &dev->workbuf.vaddr, + dev->workbuf.paddr); + return 0; +} + +static struct platform_driver coda_driver = { + .probe = coda_probe, + .remove = __devexit_p(coda_remove), + .driver = { + .name = CODA_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(coda_dt_ids), + }, + .id_table = coda_platform_ids, +}; + +module_platform_driver(coda_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Javier Martin "); +MODULE_DESCRIPTION("Coda multi-standard codec V4L2 driver"); diff --git a/drivers/media/platform/coda.h b/drivers/media/platform/coda.h new file mode 100644 index 000000000000..f3f5e43c1ac2 --- /dev/null +++ b/drivers/media/platform/coda.h @@ -0,0 +1,238 @@ +/* + * linux/drivers/media/platform/coda/coda_regs.h + * + * Copyright (C) 2012 Vista Silicon SL + * Javier Martin + * Xavier Duret + * + * 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. + */ + +#ifndef _REGS_CODA_H_ +#define _REGS_CODA_H_ + +/* HW registers */ +#define CODA_REG_BIT_CODE_RUN 0x000 +#define CODA_REG_RUN_ENABLE (1 << 0) +#define CODA_REG_BIT_CODE_DOWN 0x004 +#define CODA_DOWN_ADDRESS_SET(x) (((x) & 0xffff) << 16) +#define CODA_DOWN_DATA_SET(x) ((x) & 0xffff) +#define CODA_REG_BIT_HOST_IN_REQ 0x008 +#define CODA_REG_BIT_INT_CLEAR 0x00c +#define CODA_REG_BIT_INT_CLEAR_SET 0x1 +#define CODA_REG_BIT_INT_STATUS 0x010 +#define CODA_REG_BIT_CODE_RESET 0x014 +#define CODA_REG_RESET_ENABLE (1 << 0) +#define CODA_REG_BIT_CUR_PC 0x018 + +/* Static SW registers */ +#define CODA_REG_BIT_CODE_BUF_ADDR 0x100 +#define CODA_REG_BIT_WORK_BUF_ADDR 0x104 +#define CODA_REG_BIT_PARA_BUF_ADDR 0x108 +#define CODA_REG_BIT_STREAM_CTRL 0x10c +#define CODA7_STREAM_BUF_PIC_RESET (1 << 4) +#define CODADX6_STREAM_BUF_PIC_RESET (1 << 3) +#define CODA7_STREAM_BUF_PIC_FLUSH (1 << 3) +#define CODADX6_STREAM_BUF_PIC_FLUSH (1 << 2) +#define CODA7_STREAM_BUF_DYNALLOC_EN (1 << 5) +#define CODADX6_STREAM_BUF_DYNALLOC_EN (1 << 4) +#define CODA_STREAM_CHKDIS_OFFSET (1 << 1) +#define CODA_STREAM_ENDIAN_SELECT (1 << 0) +#define CODA_REG_BIT_FRAME_MEM_CTRL 0x110 +#define CODA_IMAGE_ENDIAN_SELECT (1 << 0) +#define CODA_REG_BIT_RD_PTR(x) (0x120 + 8 * (x)) +#define CODA_REG_BIT_WR_PTR(x) (0x124 + 8 * (x)) +#define CODADX6_REG_BIT_SEARCH_RAM_BASE_ADDR 0x140 +#define CODA7_REG_BIT_AXI_SRAM_USE 0x140 +#define CODA7_USE_BIT_ENABLE (1 << 0) +#define CODA7_USE_HOST_BIT_ENABLE (1 << 7) +#define CODA7_USE_ME_ENABLE (1 << 4) +#define CODA7_USE_HOST_ME_ENABLE (1 << 11) +#define CODA_REG_BIT_BUSY 0x160 +#define CODA_REG_BIT_BUSY_FLAG 1 +#define CODA_REG_BIT_RUN_COMMAND 0x164 +#define CODA_COMMAND_SEQ_INIT 1 +#define CODA_COMMAND_SEQ_END 2 +#define CODA_COMMAND_PIC_RUN 3 +#define CODA_COMMAND_SET_FRAME_BUF 4 +#define CODA_COMMAND_ENCODE_HEADER 5 +#define CODA_COMMAND_ENC_PARA_SET 6 +#define CODA_COMMAND_DEC_PARA_SET 7 +#define CODA_COMMAND_DEC_BUF_FLUSH 8 +#define CODA_COMMAND_RC_CHANGE_PARAMETER 9 +#define CODA_COMMAND_FIRMWARE_GET 0xf +#define CODA_REG_BIT_RUN_INDEX 0x168 +#define CODA_INDEX_SET(x) ((x) & 0x3) +#define CODA_REG_BIT_RUN_COD_STD 0x16c +#define CODADX6_MODE_DECODE_MP4 0 +#define CODADX6_MODE_ENCODE_MP4 1 +#define CODADX6_MODE_DECODE_H264 2 +#define CODADX6_MODE_ENCODE_H264 3 +#define CODA7_MODE_DECODE_H264 0 +#define CODA7_MODE_DECODE_VC1 1 +#define CODA7_MODE_DECODE_MP2 2 +#define CODA7_MODE_DECODE_MP4 3 +#define CODA7_MODE_DECODE_DV3 3 +#define CODA7_MODE_DECODE_RV 4 +#define CODA7_MODE_DECODE_MJPG 5 +#define CODA7_MODE_ENCODE_H264 8 +#define CODA7_MODE_ENCODE_MP4 11 +#define CODA7_MODE_ENCODE_MJPG 13 +#define CODA_MODE_INVALID 0xffff +#define CODA_REG_BIT_INT_ENABLE 0x170 +#define CODA_INT_INTERRUPT_ENABLE (1 << 3) + +/* + * Commands' mailbox: + * registers with offsets in the range 0x180-0x1d0 + * have different meaning depending on the command being + * issued. + */ + +/* Encoder Sequence Initialization */ +#define CODA_CMD_ENC_SEQ_BB_START 0x180 +#define CODA_CMD_ENC_SEQ_BB_SIZE 0x184 +#define CODA_CMD_ENC_SEQ_OPTION 0x188 +#define CODA_OPTION_GAMMA_OFFSET 7 +#define CODA_OPTION_GAMMA_MASK 0x01 +#define CODA_OPTION_LIMITQP_OFFSET 6 +#define CODA_OPTION_LIMITQP_MASK 0x01 +#define CODA_OPTION_RCINTRAQP_OFFSET 5 +#define CODA_OPTION_RCINTRAQP_MASK 0x01 +#define CODA_OPTION_FMO_OFFSET 4 +#define CODA_OPTION_FMO_MASK 0x01 +#define CODA_OPTION_SLICEREPORT_OFFSET 1 +#define CODA_OPTION_SLICEREPORT_MASK 0x01 +#define CODA_CMD_ENC_SEQ_COD_STD 0x18c +#define CODA_STD_MPEG4 0 +#define CODA_STD_H263 1 +#define CODA_STD_H264 2 +#define CODA_STD_MJPG 3 +#define CODA_CMD_ENC_SEQ_SRC_SIZE 0x190 +#define CODA7_PICWIDTH_OFFSET 16 +#define CODA7_PICWIDTH_MASK 0xffff +#define CODADX6_PICWIDTH_OFFSET 10 +#define CODADX6_PICWIDTH_MASK 0x3ff +#define CODA_PICHEIGHT_OFFSET 0 +#define CODA_PICHEIGHT_MASK 0x3ff +#define CODA_CMD_ENC_SEQ_SRC_F_RATE 0x194 +#define CODA_CMD_ENC_SEQ_MP4_PARA 0x198 +#define CODA_MP4PARAM_VERID_OFFSET 6 +#define CODA_MP4PARAM_VERID_MASK 0x01 +#define CODA_MP4PARAM_INTRADCVLCTHR_OFFSET 2 +#define CODA_MP4PARAM_INTRADCVLCTHR_MASK 0x07 +#define CODA_MP4PARAM_REVERSIBLEVLCENABLE_OFFSET 1 +#define CODA_MP4PARAM_REVERSIBLEVLCENABLE_MASK 0x01 +#define CODA_MP4PARAM_DATAPARTITIONENABLE_OFFSET 0 +#define CODA_MP4PARAM_DATAPARTITIONENABLE_MASK 0x01 +#define CODA_CMD_ENC_SEQ_263_PARA 0x19c +#define CODA_263PARAM_ANNEXJENABLE_OFFSET 2 +#define CODA_263PARAM_ANNEXJENABLE_MASK 0x01 +#define CODA_263PARAM_ANNEXKENABLE_OFFSET 1 +#define CODA_263PARAM_ANNEXKENABLE_MASK 0x01 +#define CODA_263PARAM_ANNEXTENABLE_OFFSET 0 +#define CODA_263PARAM_ANNEXTENABLE_MASK 0x01 +#define CODA_CMD_ENC_SEQ_264_PARA 0x1a0 +#define CODA_264PARAM_DEBLKFILTEROFFSETBETA_OFFSET 12 +#define CODA_264PARAM_DEBLKFILTEROFFSETBETA_MASK 0x0f +#define CODA_264PARAM_DEBLKFILTEROFFSETALPHA_OFFSET 8 +#define CODA_264PARAM_DEBLKFILTEROFFSETALPHA_MASK 0x0f +#define CODA_264PARAM_DISABLEDEBLK_OFFSET 6 +#define CODA_264PARAM_DISABLEDEBLK_MASK 0x01 +#define CODA_264PARAM_CONSTRAINEDINTRAPREDFLAG_OFFSET 5 +#define CODA_264PARAM_CONSTRAINEDINTRAPREDFLAG_MASK 0x01 +#define CODA_264PARAM_CHROMAQPOFFSET_OFFSET 0 +#define CODA_264PARAM_CHROMAQPOFFSET_MASK 0x1f +#define CODA_CMD_ENC_SEQ_SLICE_MODE 0x1a4 +#define CODA_SLICING_SIZE_OFFSET 2 +#define CODA_SLICING_SIZE_MASK 0x3fffffff +#define CODA_SLICING_UNIT_OFFSET 1 +#define CODA_SLICING_UNIT_MASK 0x01 +#define CODA_SLICING_MODE_OFFSET 0 +#define CODA_SLICING_MODE_MASK 0x01 +#define CODA_CMD_ENC_SEQ_GOP_SIZE 0x1a8 +#define CODA_GOP_SIZE_OFFSET 0 +#define CODA_GOP_SIZE_MASK 0x3f +#define CODA_CMD_ENC_SEQ_RC_PARA 0x1ac +#define CODA_RATECONTROL_AUTOSKIP_OFFSET 31 +#define CODA_RATECONTROL_AUTOSKIP_MASK 0x01 +#define CODA_RATECONTROL_INITIALDELAY_OFFSET 16 +#define CODA_RATECONTROL_INITIALDELAY_MASK 0x7f +#define CODA_RATECONTROL_BITRATE_OFFSET 1 +#define CODA_RATECONTROL_BITRATE_MASK 0x7f +#define CODA_RATECONTROL_ENABLE_OFFSET 0 +#define CODA_RATECONTROL_ENABLE_MASK 0x01 +#define CODA_CMD_ENC_SEQ_RC_BUF_SIZE 0x1b0 +#define CODA_CMD_ENC_SEQ_INTRA_REFRESH 0x1b4 +#define CODADX6_CMD_ENC_SEQ_FMO 0x1b8 +#define CODA_FMOPARAM_TYPE_OFFSET 4 +#define CODA_FMOPARAM_TYPE_MASK 1 +#define CODA_FMOPARAM_SLICENUM_OFFSET 0 +#define CODA_FMOPARAM_SLICENUM_MASK 0x0f +#define CODA7_CMD_ENC_SEQ_SEARCH_BASE 0x1b8 +#define CODA7_CMD_ENC_SEQ_SEARCH_SIZE 0x1bc +#define CODA_CMD_ENC_SEQ_RC_QP_MAX 0x1c8 +#define CODA_QPMAX_OFFSET 0 +#define CODA_QPMAX_MASK 0x3f +#define CODA_CMD_ENC_SEQ_RC_GAMMA 0x1cc +#define CODA_GAMMA_OFFSET 0 +#define CODA_GAMMA_MASK 0xffff +#define CODA_RET_ENC_SEQ_SUCCESS 0x1c0 + +/* Encoder Picture Run */ +#define CODA_CMD_ENC_PIC_SRC_ADDR_Y 0x180 +#define CODA_CMD_ENC_PIC_SRC_ADDR_CB 0x184 +#define CODA_CMD_ENC_PIC_SRC_ADDR_CR 0x188 +#define CODA_CMD_ENC_PIC_QS 0x18c +#define CODA_CMD_ENC_PIC_ROT_MODE 0x190 +#define CODA_ROT_MIR_ENABLE (1 << 4) +#define CODA_ROT_0 (0x0 << 0) +#define CODA_ROT_90 (0x1 << 0) +#define CODA_ROT_180 (0x2 << 0) +#define CODA_ROT_270 (0x3 << 0) +#define CODA_MIR_NONE (0x0 << 2) +#define CODA_MIR_VER (0x1 << 2) +#define CODA_MIR_HOR (0x2 << 2) +#define CODA_MIR_VER_HOR (0x3 << 2) +#define CODA_CMD_ENC_PIC_OPTION 0x194 +#define CODA_CMD_ENC_PIC_BB_START 0x198 +#define CODA_CMD_ENC_PIC_BB_SIZE 0x19c +#define CODA_RET_ENC_PIC_TYPE 0x1c4 +#define CODA_RET_ENC_PIC_SLICE_NUM 0x1cc +#define CODA_RET_ENC_PIC_FLAG 0x1d0 + +/* Set Frame Buffer */ +#define CODA_CMD_SET_FRAME_BUF_NUM 0x180 +#define CODA_CMD_SET_FRAME_BUF_STRIDE 0x184 +#define CODA7_CMD_SET_FRAME_AXI_BIT_ADDR 0x190 +#define CODA7_CMD_SET_FRAME_AXI_IPACDC_ADDR 0x194 +#define CODA7_CMD_SET_FRAME_AXI_DBKY_ADDR 0x198 +#define CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR 0x19c +#define CODA7_CMD_SET_FRAME_AXI_OVL_ADDR 0x1a0 +#define CODA7_CMD_SET_FRAME_SOURCE_BUF_STRIDE 0x1a8 + +/* Encoder Header */ +#define CODA_CMD_ENC_HEADER_CODE 0x180 +#define CODA_GAMMA_OFFSET 0 +#define CODA_HEADER_H264_SPS 0 +#define CODA_HEADER_H264_PPS 1 +#define CODA_HEADER_MP4V_VOL 0 +#define CODA_HEADER_MP4V_VOS 1 +#define CODA_HEADER_MP4V_VIS 2 +#define CODA_CMD_ENC_HEADER_BB_START 0x184 +#define CODA_CMD_ENC_HEADER_BB_SIZE 0x188 + +/* Get Version */ +#define CODA_CMD_FIRMWARE_VERNUM 0x1c0 +#define CODA_FIRMWARE_PRODUCT(x) (((x) >> 16) & 0xffff) +#define CODA_FIRMWARE_MAJOR(x) (((x) >> 12) & 0x0f) +#define CODA_FIRMWARE_MINOR(x) (((x) >> 8) & 0x0f) +#define CODA_FIRMWARE_RELEASE(x) ((x) & 0xff) +#define CODA_FIRMWARE_VERNUM(product, major, minor, release) \ + ((product) << 16 | ((major) << 12) | \ + ((minor) << 8) | (release)) + +#endif diff --git a/drivers/media/platform/davinci/Kconfig b/drivers/media/platform/davinci/Kconfig new file mode 100644 index 000000000000..78e26d24f637 --- /dev/null +++ b/drivers/media/platform/davinci/Kconfig @@ -0,0 +1,121 @@ +config VIDEO_DAVINCI_VPIF_DISPLAY + tristate "DM646x/DA850/OMAPL138 EVM Video Display" + depends on VIDEO_DEV && (MACH_DAVINCI_DM6467_EVM || MACH_DAVINCI_DA850_EVM) + select VIDEOBUF2_DMA_CONTIG + select VIDEO_DAVINCI_VPIF + select VIDEO_ADV7343 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_THS7303 if MEDIA_SUBDRV_AUTOSELECT + help + Enables Davinci VPIF module used for display devices. + This module is common for following DM6467/DA850/OMAPL138 + based display devices. + + To compile this driver as a module, choose M here: the + module will be called vpif_display. + +config VIDEO_DAVINCI_VPIF_CAPTURE + tristate "DM646x/DA850/OMAPL138 EVM Video Capture" + depends on VIDEO_DEV && (MACH_DAVINCI_DM6467_EVM || MACH_DAVINCI_DA850_EVM) + select VIDEOBUF2_DMA_CONTIG + select VIDEO_DAVINCI_VPIF + help + Enables Davinci VPIF module used for captur devices. + This module is common for following DM6467/DA850/OMAPL138 + based capture devices. + + To compile this driver as a module, choose M here: the + module will be called vpif_capture. + +config VIDEO_DAVINCI_VPIF + tristate "DaVinci VPIF Driver" + depends on VIDEO_DAVINCI_VPIF_DISPLAY || VIDEO_DAVINCI_VPIF_CAPTURE + help + Support for DaVinci VPIF Driver. + + To compile this driver as a module, choose M here: the + module will be called vpif. + +config VIDEO_VPSS_SYSTEM + tristate "VPSS System module driver" + depends on ARCH_DAVINCI + help + Support for vpss system module for video driver + +config VIDEO_VPFE_CAPTURE + tristate "VPFE Video Capture Driver" + depends on VIDEO_V4L2 && (ARCH_DAVINCI || ARCH_OMAP3) + depends on I2C + select VIDEOBUF_DMA_CONTIG + help + Support for DMx/AMx VPFE based frame grabber. This is the + common V4L2 module for following DMx/AMx SoCs from Texas + Instruments:- DM6446, DM365, DM355 & AM3517/05. + + To compile this driver as a module, choose M here: the + module will be called vpfe-capture. + +config VIDEO_DM6446_CCDC + tristate "DM6446 CCDC HW module" + depends on VIDEO_VPFE_CAPTURE + select VIDEO_VPSS_SYSTEM + default y + help + Enables DaVinci CCD hw module. DaVinci CCDC hw interfaces + with decoder modules such as TVP5146 over BT656 or + sensor module such as MT9T001 over a raw interface. This + module configures the interface and CCDC/ISIF to do + video frame capture from slave decoders. + + To compile this driver as a module, choose M here: the + module will be called vpfe. + +config VIDEO_DM355_CCDC + tristate "DM355 CCDC HW module" + depends on ARCH_DAVINCI_DM355 && VIDEO_VPFE_CAPTURE + select VIDEO_VPSS_SYSTEM + default y + help + Enables DM355 CCD hw module. DM355 CCDC hw interfaces + with decoder modules such as TVP5146 over BT656 or + sensor module such as MT9T001 over a raw interface. This + module configures the interface and CCDC/ISIF to do + video frame capture from a slave decoders + + To compile this driver as a module, choose M here: the + module will be called vpfe. + +config VIDEO_ISIF + tristate "ISIF HW module" + depends on ARCH_DAVINCI_DM365 && VIDEO_VPFE_CAPTURE + select VIDEO_VPSS_SYSTEM + default y + help + Enables ISIF hw module. This is the hardware module for + configuring ISIF in VPFE to capture Raw Bayer RGB data from + a image sensor or YUV data from a YUV source. + + To compile this driver as a module, choose M here: the + module will be called vpfe. + +config VIDEO_DM644X_VPBE + tristate "DM644X VPBE HW module" + depends on ARCH_DAVINCI_DM644x + select VIDEO_VPSS_SYSTEM + select VIDEOBUF_DMA_CONTIG + help + Enables VPBE modules used for display on a DM644x + SoC. + + To compile this driver as a module, choose M here: the + module will be called vpbe. + + +config VIDEO_VPBE_DISPLAY + tristate "VPBE V4L2 Display driver" + depends on ARCH_DAVINCI_DM644x + select VIDEO_DM644X_VPBE + help + Enables VPBE V4L2 Display driver on a DM644x device + + To compile this driver as a module, choose M here: the + module will be called vpbe_display. diff --git a/drivers/media/video/davinci/Makefile b/drivers/media/platform/davinci/Makefile similarity index 100% rename from drivers/media/video/davinci/Makefile rename to drivers/media/platform/davinci/Makefile diff --git a/drivers/media/video/davinci/ccdc_hw_device.h b/drivers/media/platform/davinci/ccdc_hw_device.h similarity index 100% rename from drivers/media/video/davinci/ccdc_hw_device.h rename to drivers/media/platform/davinci/ccdc_hw_device.h diff --git a/drivers/media/platform/davinci/dm355_ccdc.c b/drivers/media/platform/davinci/dm355_ccdc.c new file mode 100644 index 000000000000..ce0e4131c067 --- /dev/null +++ b/drivers/media/platform/davinci/dm355_ccdc.c @@ -0,0 +1,1072 @@ +/* + * Copyright (C) 2005-2009 Texas Instruments Inc + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * CCDC hardware module for DM355 + * ------------------------------ + * + * This module is for configuring DM355 CCD controller of VPFE to capture + * Raw yuv or Bayer RGB data from a decoder. CCDC has several modules + * such as Defect Pixel Correction, Color Space Conversion etc to + * pre-process the Bayer RGB data, before writing it to SDRAM. This + * module also allows application to configure individual + * module parameters through VPFE_CMD_S_CCDC_RAW_PARAMS IOCTL. + * To do so, application include dm355_ccdc.h and vpfe_capture.h header + * files. The setparams() API is called by vpfe_capture driver + * to configure module parameters + * + * TODO: 1) Raw bayer parameter settings and bayer capture + * 2) Split module parameter structure to module specific ioctl structs + * 3) add support for lense shading correction + * 4) investigate if enum used for user space type definition + * to be replaced by #defines or integer + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dm355_ccdc_regs.h" +#include "ccdc_hw_device.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CCDC Driver for DM355"); +MODULE_AUTHOR("Texas Instruments"); + +static struct ccdc_oper_config { + struct device *dev; + /* CCDC interface type */ + enum vpfe_hw_if_type if_type; + /* Raw Bayer configuration */ + struct ccdc_params_raw bayer; + /* YCbCr configuration */ + struct ccdc_params_ycbcr ycbcr; + /* Master clock */ + struct clk *mclk; + /* slave clock */ + struct clk *sclk; + /* ccdc base address */ + void __iomem *base_addr; +} ccdc_cfg = { + /* Raw configurations */ + .bayer = { + .pix_fmt = CCDC_PIXFMT_RAW, + .frm_fmt = CCDC_FRMFMT_PROGRESSIVE, + .win = CCDC_WIN_VGA, + .fid_pol = VPFE_PINPOL_POSITIVE, + .vd_pol = VPFE_PINPOL_POSITIVE, + .hd_pol = VPFE_PINPOL_POSITIVE, + .gain = { + .r_ye = 256, + .gb_g = 256, + .gr_cy = 256, + .b_mg = 256 + }, + .config_params = { + .datasft = 2, + .mfilt1 = CCDC_NO_MEDIAN_FILTER1, + .mfilt2 = CCDC_NO_MEDIAN_FILTER2, + .alaw = { + .gama_wd = 2, + }, + .blk_clamp = { + .sample_pixel = 1, + .dc_sub = 25 + }, + .col_pat_field0 = { + .olop = CCDC_GREEN_BLUE, + .olep = CCDC_BLUE, + .elop = CCDC_RED, + .elep = CCDC_GREEN_RED + }, + .col_pat_field1 = { + .olop = CCDC_GREEN_BLUE, + .olep = CCDC_BLUE, + .elop = CCDC_RED, + .elep = CCDC_GREEN_RED + }, + }, + }, + /* YCbCr configuration */ + .ycbcr = { + .win = CCDC_WIN_PAL, + .pix_fmt = CCDC_PIXFMT_YCBCR_8BIT, + .frm_fmt = CCDC_FRMFMT_INTERLACED, + .fid_pol = VPFE_PINPOL_POSITIVE, + .vd_pol = VPFE_PINPOL_POSITIVE, + .hd_pol = VPFE_PINPOL_POSITIVE, + .bt656_enable = 1, + .pix_order = CCDC_PIXORDER_CBYCRY, + .buf_type = CCDC_BUFTYPE_FLD_INTERLEAVED + }, +}; + + +/* Raw Bayer formats */ +static u32 ccdc_raw_bayer_pix_formats[] = + {V4L2_PIX_FMT_SBGGR8, V4L2_PIX_FMT_SBGGR16}; + +/* Raw YUV formats */ +static u32 ccdc_raw_yuv_pix_formats[] = + {V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_YUYV}; + +/* register access routines */ +static inline u32 regr(u32 offset) +{ + return __raw_readl(ccdc_cfg.base_addr + offset); +} + +static inline void regw(u32 val, u32 offset) +{ + __raw_writel(val, ccdc_cfg.base_addr + offset); +} + +static void ccdc_enable(int en) +{ + unsigned int temp; + temp = regr(SYNCEN); + temp &= (~CCDC_SYNCEN_VDHDEN_MASK); + temp |= (en & CCDC_SYNCEN_VDHDEN_MASK); + regw(temp, SYNCEN); +} + +static void ccdc_enable_output_to_sdram(int en) +{ + unsigned int temp; + temp = regr(SYNCEN); + temp &= (~(CCDC_SYNCEN_WEN_MASK)); + temp |= ((en << CCDC_SYNCEN_WEN_SHIFT) & CCDC_SYNCEN_WEN_MASK); + regw(temp, SYNCEN); +} + +static void ccdc_config_gain_offset(void) +{ + /* configure gain */ + regw(ccdc_cfg.bayer.gain.r_ye, RYEGAIN); + regw(ccdc_cfg.bayer.gain.gr_cy, GRCYGAIN); + regw(ccdc_cfg.bayer.gain.gb_g, GBGGAIN); + regw(ccdc_cfg.bayer.gain.b_mg, BMGGAIN); + /* configure offset */ + regw(ccdc_cfg.bayer.ccdc_offset, OFFSET); +} + +/* + * ccdc_restore_defaults() + * This function restore power on defaults in the ccdc registers + */ +static int ccdc_restore_defaults(void) +{ + int i; + + dev_dbg(ccdc_cfg.dev, "\nstarting ccdc_restore_defaults..."); + /* set all registers to zero */ + for (i = 0; i <= CCDC_REG_LAST; i += 4) + regw(0, i); + + /* now override the values with power on defaults in registers */ + regw(MODESET_DEFAULT, MODESET); + /* no culling support */ + regw(CULH_DEFAULT, CULH); + regw(CULV_DEFAULT, CULV); + /* Set default Gain and Offset */ + ccdc_cfg.bayer.gain.r_ye = GAIN_DEFAULT; + ccdc_cfg.bayer.gain.gb_g = GAIN_DEFAULT; + ccdc_cfg.bayer.gain.gr_cy = GAIN_DEFAULT; + ccdc_cfg.bayer.gain.b_mg = GAIN_DEFAULT; + ccdc_config_gain_offset(); + regw(OUTCLIP_DEFAULT, OUTCLIP); + regw(LSCCFG2_DEFAULT, LSCCFG2); + /* select ccdc input */ + if (vpss_select_ccdc_source(VPSS_CCDCIN)) { + dev_dbg(ccdc_cfg.dev, "\ncouldn't select ccdc input source"); + return -EFAULT; + } + /* select ccdc clock */ + if (vpss_enable_clock(VPSS_CCDC_CLOCK, 1) < 0) { + dev_dbg(ccdc_cfg.dev, "\ncouldn't enable ccdc clock"); + return -EFAULT; + } + dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_restore_defaults..."); + return 0; +} + +static int ccdc_open(struct device *device) +{ + return ccdc_restore_defaults(); +} + +static int ccdc_close(struct device *device) +{ + /* disable clock */ + vpss_enable_clock(VPSS_CCDC_CLOCK, 0); + /* do nothing for now */ + return 0; +} +/* + * ccdc_setwin() + * This function will configure the window size to + * be capture in CCDC reg. + */ +static void ccdc_setwin(struct v4l2_rect *image_win, + enum ccdc_frmfmt frm_fmt, int ppc) +{ + int horz_start, horz_nr_pixels; + int vert_start, vert_nr_lines; + int mid_img = 0; + + dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_setwin..."); + + /* + * ppc - per pixel count. indicates how many pixels per cell + * output to SDRAM. example, for ycbcr, it is one y and one c, so 2. + * raw capture this is 1 + */ + horz_start = image_win->left << (ppc - 1); + horz_nr_pixels = ((image_win->width) << (ppc - 1)) - 1; + + /* Writing the horizontal info into the registers */ + regw(horz_start, SPH); + regw(horz_nr_pixels, NPH); + vert_start = image_win->top; + + if (frm_fmt == CCDC_FRMFMT_INTERLACED) { + vert_nr_lines = (image_win->height >> 1) - 1; + vert_start >>= 1; + /* Since first line doesn't have any data */ + vert_start += 1; + /* configure VDINT0 and VDINT1 */ + regw(vert_start, VDINT0); + } else { + /* Since first line doesn't have any data */ + vert_start += 1; + vert_nr_lines = image_win->height - 1; + /* configure VDINT0 and VDINT1 */ + mid_img = vert_start + (image_win->height / 2); + regw(vert_start, VDINT0); + regw(mid_img, VDINT1); + } + regw(vert_start & CCDC_START_VER_ONE_MASK, SLV0); + regw(vert_start & CCDC_START_VER_TWO_MASK, SLV1); + regw(vert_nr_lines & CCDC_NUM_LINES_VER, NLV); + dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_setwin..."); +} + +static int validate_ccdc_param(struct ccdc_config_params_raw *ccdcparam) +{ + if (ccdcparam->datasft < CCDC_DATA_NO_SHIFT || + ccdcparam->datasft > CCDC_DATA_SHIFT_6BIT) { + dev_dbg(ccdc_cfg.dev, "Invalid value of data shift\n"); + return -EINVAL; + } + + if (ccdcparam->mfilt1 < CCDC_NO_MEDIAN_FILTER1 || + ccdcparam->mfilt1 > CCDC_MEDIAN_FILTER1) { + dev_dbg(ccdc_cfg.dev, "Invalid value of median filter1\n"); + return -EINVAL; + } + + if (ccdcparam->mfilt2 < CCDC_NO_MEDIAN_FILTER2 || + ccdcparam->mfilt2 > CCDC_MEDIAN_FILTER2) { + dev_dbg(ccdc_cfg.dev, "Invalid value of median filter2\n"); + return -EINVAL; + } + + if ((ccdcparam->med_filt_thres < 0) || + (ccdcparam->med_filt_thres > CCDC_MED_FILT_THRESH)) { + dev_dbg(ccdc_cfg.dev, + "Invalid value of median filter threshold\n"); + return -EINVAL; + } + + if (ccdcparam->data_sz < CCDC_DATA_16BITS || + ccdcparam->data_sz > CCDC_DATA_8BITS) { + dev_dbg(ccdc_cfg.dev, "Invalid value of data size\n"); + return -EINVAL; + } + + if (ccdcparam->alaw.enable) { + if (ccdcparam->alaw.gama_wd < CCDC_GAMMA_BITS_13_4 || + ccdcparam->alaw.gama_wd > CCDC_GAMMA_BITS_09_0) { + dev_dbg(ccdc_cfg.dev, "Invalid value of ALAW\n"); + return -EINVAL; + } + } + + if (ccdcparam->blk_clamp.b_clamp_enable) { + if (ccdcparam->blk_clamp.sample_pixel < CCDC_SAMPLE_1PIXELS || + ccdcparam->blk_clamp.sample_pixel > CCDC_SAMPLE_16PIXELS) { + dev_dbg(ccdc_cfg.dev, + "Invalid value of sample pixel\n"); + return -EINVAL; + } + if (ccdcparam->blk_clamp.sample_ln < CCDC_SAMPLE_1LINES || + ccdcparam->blk_clamp.sample_ln > CCDC_SAMPLE_16LINES) { + dev_dbg(ccdc_cfg.dev, + "Invalid value of sample lines\n"); + return -EINVAL; + } + } + return 0; +} + +/* Parameter operations */ +static int ccdc_set_params(void __user *params) +{ + struct ccdc_config_params_raw ccdc_raw_params; + int x; + + /* only raw module parameters can be set through the IOCTL */ + if (ccdc_cfg.if_type != VPFE_RAW_BAYER) + return -EINVAL; + + x = copy_from_user(&ccdc_raw_params, params, sizeof(ccdc_raw_params)); + if (x) { + dev_dbg(ccdc_cfg.dev, "ccdc_set_params: error in copying ccdc" + "params, %d\n", x); + return -EFAULT; + } + + if (!validate_ccdc_param(&ccdc_raw_params)) { + memcpy(&ccdc_cfg.bayer.config_params, + &ccdc_raw_params, + sizeof(ccdc_raw_params)); + return 0; + } + return -EINVAL; +} + +/* This function will configure CCDC for YCbCr video capture */ +static void ccdc_config_ycbcr(void) +{ + struct ccdc_params_ycbcr *params = &ccdc_cfg.ycbcr; + u32 temp; + + /* first set the CCDC power on defaults values in all registers */ + dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_config_ycbcr..."); + ccdc_restore_defaults(); + + /* configure pixel format & video frame format */ + temp = (((params->pix_fmt & CCDC_INPUT_MODE_MASK) << + CCDC_INPUT_MODE_SHIFT) | + ((params->frm_fmt & CCDC_FRM_FMT_MASK) << + CCDC_FRM_FMT_SHIFT)); + + /* setup BT.656 sync mode */ + if (params->bt656_enable) { + regw(CCDC_REC656IF_BT656_EN, REC656IF); + /* + * configure the FID, VD, HD pin polarity fld,hd pol positive, + * vd negative, 8-bit pack mode + */ + temp |= CCDC_VD_POL_NEGATIVE; + } else { /* y/c external sync mode */ + temp |= (((params->fid_pol & CCDC_FID_POL_MASK) << + CCDC_FID_POL_SHIFT) | + ((params->hd_pol & CCDC_HD_POL_MASK) << + CCDC_HD_POL_SHIFT) | + ((params->vd_pol & CCDC_VD_POL_MASK) << + CCDC_VD_POL_SHIFT)); + } + + /* pack the data to 8-bit */ + temp |= CCDC_DATA_PACK_ENABLE; + + regw(temp, MODESET); + + /* configure video window */ + ccdc_setwin(¶ms->win, params->frm_fmt, 2); + + /* configure the order of y cb cr in SD-RAM */ + temp = (params->pix_order << CCDC_Y8POS_SHIFT); + temp |= CCDC_LATCH_ON_VSYNC_DISABLE | CCDC_CCDCFG_FIDMD_NO_LATCH_VSYNC; + regw(temp, CCDCFG); + + /* + * configure the horizontal line offset. This is done by rounding up + * width to a multiple of 16 pixels and multiply by two to account for + * y:cb:cr 4:2:2 data + */ + regw(((params->win.width * 2 + 31) >> 5), HSIZE); + + /* configure the memory line offset */ + if (params->buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) { + /* two fields are interleaved in memory */ + regw(CCDC_SDOFST_FIELD_INTERLEAVED, SDOFST); + } + + dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_config_ycbcr...\n"); +} + +/* + * ccdc_config_black_clamp() + * configure parameters for Optical Black Clamp + */ +static void ccdc_config_black_clamp(struct ccdc_black_clamp *bclamp) +{ + u32 val; + + if (!bclamp->b_clamp_enable) { + /* configure DCSub */ + regw(bclamp->dc_sub & CCDC_BLK_DC_SUB_MASK, DCSUB); + regw(0x0000, CLAMP); + return; + } + /* Enable the Black clamping, set sample lines and pixels */ + val = (bclamp->start_pixel & CCDC_BLK_ST_PXL_MASK) | + ((bclamp->sample_pixel & CCDC_BLK_SAMPLE_LN_MASK) << + CCDC_BLK_SAMPLE_LN_SHIFT) | CCDC_BLK_CLAMP_ENABLE; + regw(val, CLAMP); + + /* If Black clamping is enable then make dcsub 0 */ + val = (bclamp->sample_ln & CCDC_NUM_LINE_CALC_MASK) + << CCDC_NUM_LINE_CALC_SHIFT; + regw(val, DCSUB); +} + +/* + * ccdc_config_black_compense() + * configure parameters for Black Compensation + */ +static void ccdc_config_black_compense(struct ccdc_black_compensation *bcomp) +{ + u32 val; + + val = (bcomp->b & CCDC_BLK_COMP_MASK) | + ((bcomp->gb & CCDC_BLK_COMP_MASK) << + CCDC_BLK_COMP_GB_COMP_SHIFT); + regw(val, BLKCMP1); + + val = ((bcomp->gr & CCDC_BLK_COMP_MASK) << + CCDC_BLK_COMP_GR_COMP_SHIFT) | + ((bcomp->r & CCDC_BLK_COMP_MASK) << + CCDC_BLK_COMP_R_COMP_SHIFT); + regw(val, BLKCMP0); +} + +/* + * ccdc_write_dfc_entry() + * write an entry in the dfc table. + */ +int ccdc_write_dfc_entry(int index, struct ccdc_vertical_dft *dfc) +{ +/* TODO This is to be re-visited and adjusted */ +#define DFC_WRITE_WAIT_COUNT 1000 + u32 val, count = DFC_WRITE_WAIT_COUNT; + + regw(dfc->dft_corr_vert[index], DFCMEM0); + regw(dfc->dft_corr_horz[index], DFCMEM1); + regw(dfc->dft_corr_sub1[index], DFCMEM2); + regw(dfc->dft_corr_sub2[index], DFCMEM3); + regw(dfc->dft_corr_sub3[index], DFCMEM4); + /* set WR bit to write */ + val = regr(DFCMEMCTL) | CCDC_DFCMEMCTL_DFCMWR_MASK; + regw(val, DFCMEMCTL); + + /* + * Assume, it is very short. If we get an error, we need to + * adjust this value + */ + while (regr(DFCMEMCTL) & CCDC_DFCMEMCTL_DFCMWR_MASK) + count--; + /* + * TODO We expect the count to be non-zero to be successful. Adjust + * the count if write requires more time + */ + + if (count) { + dev_err(ccdc_cfg.dev, "defect table write timeout !!!\n"); + return -1; + } + return 0; +} + +/* + * ccdc_config_vdfc() + * configure parameters for Vertical Defect Correction + */ +static int ccdc_config_vdfc(struct ccdc_vertical_dft *dfc) +{ + u32 val; + int i; + + /* Configure General Defect Correction. The table used is from IPIPE */ + val = dfc->gen_dft_en & CCDC_DFCCTL_GDFCEN_MASK; + + /* Configure Vertical Defect Correction if needed */ + if (!dfc->ver_dft_en) { + /* Enable only General Defect Correction */ + regw(val, DFCCTL); + return 0; + } + + if (dfc->table_size > CCDC_DFT_TABLE_SIZE) + return -EINVAL; + + val |= CCDC_DFCCTL_VDFC_DISABLE; + val |= (dfc->dft_corr_ctl.vdfcsl & CCDC_DFCCTL_VDFCSL_MASK) << + CCDC_DFCCTL_VDFCSL_SHIFT; + val |= (dfc->dft_corr_ctl.vdfcuda & CCDC_DFCCTL_VDFCUDA_MASK) << + CCDC_DFCCTL_VDFCUDA_SHIFT; + val |= (dfc->dft_corr_ctl.vdflsft & CCDC_DFCCTL_VDFLSFT_MASK) << + CCDC_DFCCTL_VDFLSFT_SHIFT; + regw(val , DFCCTL); + + /* clear address ptr to offset 0 */ + val = CCDC_DFCMEMCTL_DFCMARST_MASK << CCDC_DFCMEMCTL_DFCMARST_SHIFT; + + /* write defect table entries */ + for (i = 0; i < dfc->table_size; i++) { + /* increment address for non zero index */ + if (i != 0) + val = CCDC_DFCMEMCTL_INC_ADDR; + regw(val, DFCMEMCTL); + if (ccdc_write_dfc_entry(i, dfc) < 0) + return -EFAULT; + } + + /* update saturation level and enable dfc */ + regw(dfc->saturation_ctl & CCDC_VDC_DFCVSAT_MASK, DFCVSAT); + val = regr(DFCCTL) | (CCDC_DFCCTL_VDFCEN_MASK << + CCDC_DFCCTL_VDFCEN_SHIFT); + regw(val, DFCCTL); + return 0; +} + +/* + * ccdc_config_csc() + * configure parameters for color space conversion + * Each register CSCM0-7 has two values in S8Q5 format. + */ +static void ccdc_config_csc(struct ccdc_csc *csc) +{ + u32 val1, val2; + int i; + + if (!csc->enable) + return; + + /* Enable the CSC sub-module */ + regw(CCDC_CSC_ENABLE, CSCCTL); + + /* Converting the co-eff as per the format of the register */ + for (i = 0; i < CCDC_CSC_COEFF_TABLE_SIZE; i++) { + if ((i % 2) == 0) { + /* CSCM - LSB */ + val1 = (csc->coeff[i].integer & + CCDC_CSC_COEF_INTEG_MASK) + << CCDC_CSC_COEF_INTEG_SHIFT; + /* + * convert decimal part to binary. Use 2 decimal + * precision, user values range from .00 - 0.99 + */ + val1 |= (((csc->coeff[i].decimal & + CCDC_CSC_COEF_DECIMAL_MASK) * + CCDC_CSC_DEC_MAX) / 100); + } else { + + /* CSCM - MSB */ + val2 = (csc->coeff[i].integer & + CCDC_CSC_COEF_INTEG_MASK) + << CCDC_CSC_COEF_INTEG_SHIFT; + val2 |= (((csc->coeff[i].decimal & + CCDC_CSC_COEF_DECIMAL_MASK) * + CCDC_CSC_DEC_MAX) / 100); + val2 <<= CCDC_CSCM_MSB_SHIFT; + val2 |= val1; + regw(val2, (CSCM0 + ((i - 1) << 1))); + } + } +} + +/* + * ccdc_config_color_patterns() + * configure parameters for color patterns + */ +static void ccdc_config_color_patterns(struct ccdc_col_pat *pat0, + struct ccdc_col_pat *pat1) +{ + u32 val; + + val = (pat0->olop | (pat0->olep << 2) | (pat0->elop << 4) | + (pat0->elep << 6) | (pat1->olop << 8) | (pat1->olep << 10) | + (pat1->elop << 12) | (pat1->elep << 14)); + regw(val, COLPTN); +} + +/* This function will configure CCDC for Raw mode image capture */ +static int ccdc_config_raw(void) +{ + struct ccdc_params_raw *params = &ccdc_cfg.bayer; + struct ccdc_config_params_raw *config_params = + &ccdc_cfg.bayer.config_params; + unsigned int val; + + dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_config_raw..."); + + /* restore power on defaults to register */ + ccdc_restore_defaults(); + + /* CCDCFG register: + * set CCD Not to swap input since input is RAW data + * set FID detection function to Latch at V-Sync + * set WENLOG - ccdc valid area to AND + * set TRGSEL to WENBIT + * set EXTRG to DISABLE + * disable latching function on VSYNC - shadowed registers + */ + regw(CCDC_YCINSWP_RAW | CCDC_CCDCFG_FIDMD_LATCH_VSYNC | + CCDC_CCDCFG_WENLOG_AND | CCDC_CCDCFG_TRGSEL_WEN | + CCDC_CCDCFG_EXTRG_DISABLE | CCDC_LATCH_ON_VSYNC_DISABLE, CCDCFG); + + /* + * Set VDHD direction to input, input type to raw input + * normal data polarity, do not use external WEN + */ + val = (CCDC_VDHDOUT_INPUT | CCDC_RAW_IP_MODE | CCDC_DATAPOL_NORMAL | + CCDC_EXWEN_DISABLE); + + /* + * Configure the vertical sync polarity (MODESET.VDPOL), horizontal + * sync polarity (MODESET.HDPOL), field id polarity (MODESET.FLDPOL), + * frame format(progressive or interlace), & pixel format (Input mode) + */ + val |= (((params->vd_pol & CCDC_VD_POL_MASK) << CCDC_VD_POL_SHIFT) | + ((params->hd_pol & CCDC_HD_POL_MASK) << CCDC_HD_POL_SHIFT) | + ((params->fid_pol & CCDC_FID_POL_MASK) << CCDC_FID_POL_SHIFT) | + ((params->frm_fmt & CCDC_FRM_FMT_MASK) << CCDC_FRM_FMT_SHIFT) | + ((params->pix_fmt & CCDC_PIX_FMT_MASK) << CCDC_PIX_FMT_SHIFT)); + + /* set pack for alaw compression */ + if ((config_params->data_sz == CCDC_DATA_8BITS) || + config_params->alaw.enable) + val |= CCDC_DATA_PACK_ENABLE; + + /* Configure for LPF */ + if (config_params->lpf_enable) + val |= (config_params->lpf_enable & CCDC_LPF_MASK) << + CCDC_LPF_SHIFT; + + /* Configure the data shift */ + val |= (config_params->datasft & CCDC_DATASFT_MASK) << + CCDC_DATASFT_SHIFT; + regw(val , MODESET); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to MODESET...\n", val); + + /* Configure the Median Filter threshold */ + regw((config_params->med_filt_thres) & CCDC_MED_FILT_THRESH, MEDFILT); + + /* Configure GAMMAWD register. defaur 11-2, and Mosaic cfa pattern */ + val = CCDC_GAMMA_BITS_11_2 << CCDC_GAMMAWD_INPUT_SHIFT | + CCDC_CFA_MOSAIC; + + /* Enable and configure aLaw register if needed */ + if (config_params->alaw.enable) { + val |= (CCDC_ALAW_ENABLE | + ((config_params->alaw.gama_wd & + CCDC_ALAW_GAMA_WD_MASK) << + CCDC_GAMMAWD_INPUT_SHIFT)); + } + + /* Configure Median filter1 & filter2 */ + val |= ((config_params->mfilt1 << CCDC_MFILT1_SHIFT) | + (config_params->mfilt2 << CCDC_MFILT2_SHIFT)); + + regw(val, GAMMAWD); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to GAMMAWD...\n", val); + + /* configure video window */ + ccdc_setwin(¶ms->win, params->frm_fmt, 1); + + /* Optical Clamp Averaging */ + ccdc_config_black_clamp(&config_params->blk_clamp); + + /* Black level compensation */ + ccdc_config_black_compense(&config_params->blk_comp); + + /* Vertical Defect Correction if needed */ + if (ccdc_config_vdfc(&config_params->vertical_dft) < 0) + return -EFAULT; + + /* color space conversion */ + ccdc_config_csc(&config_params->csc); + + /* color pattern */ + ccdc_config_color_patterns(&config_params->col_pat_field0, + &config_params->col_pat_field1); + + /* Configure the Gain & offset control */ + ccdc_config_gain_offset(); + + dev_dbg(ccdc_cfg.dev, "\nWriting %x to COLPTN...\n", val); + + /* Configure DATAOFST register */ + val = (config_params->data_offset.horz_offset & CCDC_DATAOFST_MASK) << + CCDC_DATAOFST_H_SHIFT; + val |= (config_params->data_offset.vert_offset & CCDC_DATAOFST_MASK) << + CCDC_DATAOFST_V_SHIFT; + regw(val, DATAOFST); + + /* configuring HSIZE register */ + val = (params->horz_flip_enable & CCDC_HSIZE_FLIP_MASK) << + CCDC_HSIZE_FLIP_SHIFT; + + /* If pack 8 is enable then 1 pixel will take 1 byte */ + if ((config_params->data_sz == CCDC_DATA_8BITS) || + config_params->alaw.enable) { + val |= (((params->win.width) + 31) >> 5) & + CCDC_HSIZE_VAL_MASK; + + /* adjust to multiple of 32 */ + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to HSIZE...\n", + (((params->win.width) + 31) >> 5) & + CCDC_HSIZE_VAL_MASK); + } else { + /* else one pixel will take 2 byte */ + val |= (((params->win.width * 2) + 31) >> 5) & + CCDC_HSIZE_VAL_MASK; + + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to HSIZE...\n", + (((params->win.width * 2) + 31) >> 5) & + CCDC_HSIZE_VAL_MASK); + } + regw(val, HSIZE); + + /* Configure SDOFST register */ + if (params->frm_fmt == CCDC_FRMFMT_INTERLACED) { + if (params->image_invert_enable) { + /* For interlace inverse mode */ + regw(CCDC_SDOFST_INTERLACE_INVERSE, SDOFST); + dev_dbg(ccdc_cfg.dev, "\nWriting %x to SDOFST...\n", + CCDC_SDOFST_INTERLACE_INVERSE); + } else { + /* For interlace non inverse mode */ + regw(CCDC_SDOFST_INTERLACE_NORMAL, SDOFST); + dev_dbg(ccdc_cfg.dev, "\nWriting %x to SDOFST...\n", + CCDC_SDOFST_INTERLACE_NORMAL); + } + } else if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { + if (params->image_invert_enable) { + /* For progessive inverse mode */ + regw(CCDC_SDOFST_PROGRESSIVE_INVERSE, SDOFST); + dev_dbg(ccdc_cfg.dev, "\nWriting %x to SDOFST...\n", + CCDC_SDOFST_PROGRESSIVE_INVERSE); + } else { + /* For progessive non inverse mode */ + regw(CCDC_SDOFST_PROGRESSIVE_NORMAL, SDOFST); + dev_dbg(ccdc_cfg.dev, "\nWriting %x to SDOFST...\n", + CCDC_SDOFST_PROGRESSIVE_NORMAL); + } + } + dev_dbg(ccdc_cfg.dev, "\nend of ccdc_config_raw..."); + return 0; +} + +static int ccdc_configure(void) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc_config_raw(); + else + ccdc_config_ycbcr(); + return 0; +} + +static int ccdc_set_buftype(enum ccdc_buftype buf_type) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc_cfg.bayer.buf_type = buf_type; + else + ccdc_cfg.ycbcr.buf_type = buf_type; + return 0; +} +static enum ccdc_buftype ccdc_get_buftype(void) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc_cfg.bayer.buf_type; + return ccdc_cfg.ycbcr.buf_type; +} + +static int ccdc_enum_pix(u32 *pix, int i) +{ + int ret = -EINVAL; + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { + if (i < ARRAY_SIZE(ccdc_raw_bayer_pix_formats)) { + *pix = ccdc_raw_bayer_pix_formats[i]; + ret = 0; + } + } else { + if (i < ARRAY_SIZE(ccdc_raw_yuv_pix_formats)) { + *pix = ccdc_raw_yuv_pix_formats[i]; + ret = 0; + } + } + return ret; +} + +static int ccdc_set_pixel_format(u32 pixfmt) +{ + struct ccdc_a_law *alaw = &ccdc_cfg.bayer.config_params.alaw; + + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { + ccdc_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; + if (pixfmt == V4L2_PIX_FMT_SBGGR8) + alaw->enable = 1; + else if (pixfmt != V4L2_PIX_FMT_SBGGR16) + return -EINVAL; + } else { + if (pixfmt == V4L2_PIX_FMT_YUYV) + ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_YCBYCR; + else if (pixfmt == V4L2_PIX_FMT_UYVY) + ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; + else + return -EINVAL; + } + return 0; +} +static u32 ccdc_get_pixel_format(void) +{ + struct ccdc_a_law *alaw = &ccdc_cfg.bayer.config_params.alaw; + u32 pixfmt; + + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + if (alaw->enable) + pixfmt = V4L2_PIX_FMT_SBGGR8; + else + pixfmt = V4L2_PIX_FMT_SBGGR16; + else { + if (ccdc_cfg.ycbcr.pix_order == CCDC_PIXORDER_YCBYCR) + pixfmt = V4L2_PIX_FMT_YUYV; + else + pixfmt = V4L2_PIX_FMT_UYVY; + } + return pixfmt; +} +static int ccdc_set_image_window(struct v4l2_rect *win) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc_cfg.bayer.win = *win; + else + ccdc_cfg.ycbcr.win = *win; + return 0; +} + +static void ccdc_get_image_window(struct v4l2_rect *win) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + *win = ccdc_cfg.bayer.win; + else + *win = ccdc_cfg.ycbcr.win; +} + +static unsigned int ccdc_get_line_length(void) +{ + struct ccdc_config_params_raw *config_params = + &ccdc_cfg.bayer.config_params; + unsigned int len; + + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { + if ((config_params->alaw.enable) || + (config_params->data_sz == CCDC_DATA_8BITS)) + len = ccdc_cfg.bayer.win.width; + else + len = ccdc_cfg.bayer.win.width * 2; + } else + len = ccdc_cfg.ycbcr.win.width * 2; + return ALIGN(len, 32); +} + +static int ccdc_set_frame_format(enum ccdc_frmfmt frm_fmt) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc_cfg.bayer.frm_fmt = frm_fmt; + else + ccdc_cfg.ycbcr.frm_fmt = frm_fmt; + return 0; +} + +static enum ccdc_frmfmt ccdc_get_frame_format(void) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc_cfg.bayer.frm_fmt; + else + return ccdc_cfg.ycbcr.frm_fmt; +} + +static int ccdc_getfid(void) +{ + return (regr(MODESET) >> 15) & 1; +} + +/* misc operations */ +static inline void ccdc_setfbaddr(unsigned long addr) +{ + regw((addr >> 21) & 0x007f, STADRH); + regw((addr >> 5) & 0x0ffff, STADRL); +} + +static int ccdc_set_hw_if_params(struct vpfe_hw_if_param *params) +{ + ccdc_cfg.if_type = params->if_type; + + switch (params->if_type) { + case VPFE_BT656: + case VPFE_YCBCR_SYNC_16: + case VPFE_YCBCR_SYNC_8: + ccdc_cfg.ycbcr.vd_pol = params->vdpol; + ccdc_cfg.ycbcr.hd_pol = params->hdpol; + break; + default: + /* TODO add support for raw bayer here */ + return -EINVAL; + } + return 0; +} + +static struct ccdc_hw_device ccdc_hw_dev = { + .name = "DM355 CCDC", + .owner = THIS_MODULE, + .hw_ops = { + .open = ccdc_open, + .close = ccdc_close, + .enable = ccdc_enable, + .enable_out_to_sdram = ccdc_enable_output_to_sdram, + .set_hw_if_params = ccdc_set_hw_if_params, + .set_params = ccdc_set_params, + .configure = ccdc_configure, + .set_buftype = ccdc_set_buftype, + .get_buftype = ccdc_get_buftype, + .enum_pix = ccdc_enum_pix, + .set_pixel_format = ccdc_set_pixel_format, + .get_pixel_format = ccdc_get_pixel_format, + .set_frame_format = ccdc_set_frame_format, + .get_frame_format = ccdc_get_frame_format, + .set_image_window = ccdc_set_image_window, + .get_image_window = ccdc_get_image_window, + .get_line_length = ccdc_get_line_length, + .setfbaddr = ccdc_setfbaddr, + .getfid = ccdc_getfid, + }, +}; + +static int __devinit dm355_ccdc_probe(struct platform_device *pdev) +{ + void (*setup_pinmux)(void); + struct resource *res; + int status = 0; + + /* + * first try to register with vpfe. If not correct platform, then we + * don't have to iomap + */ + status = vpfe_register_ccdc_device(&ccdc_hw_dev); + if (status < 0) + return status; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + status = -ENODEV; + goto fail_nores; + } + + res = request_mem_region(res->start, resource_size(res), res->name); + if (!res) { + status = -EBUSY; + goto fail_nores; + } + + ccdc_cfg.base_addr = ioremap_nocache(res->start, resource_size(res)); + if (!ccdc_cfg.base_addr) { + status = -ENOMEM; + goto fail_nomem; + } + + /* Get and enable Master clock */ + ccdc_cfg.mclk = clk_get(&pdev->dev, "master"); + if (IS_ERR(ccdc_cfg.mclk)) { + status = PTR_ERR(ccdc_cfg.mclk); + goto fail_nomap; + } + if (clk_enable(ccdc_cfg.mclk)) { + status = -ENODEV; + goto fail_mclk; + } + + /* Get and enable Slave clock */ + ccdc_cfg.sclk = clk_get(&pdev->dev, "slave"); + if (IS_ERR(ccdc_cfg.sclk)) { + status = PTR_ERR(ccdc_cfg.sclk); + goto fail_mclk; + } + if (clk_enable(ccdc_cfg.sclk)) { + status = -ENODEV; + goto fail_sclk; + } + + /* Platform data holds setup_pinmux function ptr */ + if (NULL == pdev->dev.platform_data) { + status = -ENODEV; + goto fail_sclk; + } + setup_pinmux = pdev->dev.platform_data; + /* + * setup Mux configuration for ccdc which may be different for + * different SoCs using this CCDC + */ + setup_pinmux(); + ccdc_cfg.dev = &pdev->dev; + printk(KERN_NOTICE "%s is registered with vpfe.\n", ccdc_hw_dev.name); + return 0; +fail_sclk: + clk_put(ccdc_cfg.sclk); +fail_mclk: + clk_put(ccdc_cfg.mclk); +fail_nomap: + iounmap(ccdc_cfg.base_addr); +fail_nomem: + release_mem_region(res->start, resource_size(res)); +fail_nores: + vpfe_unregister_ccdc_device(&ccdc_hw_dev); + return status; +} + +static int dm355_ccdc_remove(struct platform_device *pdev) +{ + struct resource *res; + + clk_put(ccdc_cfg.mclk); + clk_put(ccdc_cfg.sclk); + iounmap(ccdc_cfg.base_addr); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + vpfe_unregister_ccdc_device(&ccdc_hw_dev); + return 0; +} + +static struct platform_driver dm355_ccdc_driver = { + .driver = { + .name = "dm355_ccdc", + .owner = THIS_MODULE, + }, + .remove = __devexit_p(dm355_ccdc_remove), + .probe = dm355_ccdc_probe, +}; + +module_platform_driver(dm355_ccdc_driver); diff --git a/drivers/media/video/davinci/dm355_ccdc_regs.h b/drivers/media/platform/davinci/dm355_ccdc_regs.h similarity index 100% rename from drivers/media/video/davinci/dm355_ccdc_regs.h rename to drivers/media/platform/davinci/dm355_ccdc_regs.h diff --git a/drivers/media/platform/davinci/dm644x_ccdc.c b/drivers/media/platform/davinci/dm644x_ccdc.c new file mode 100644 index 000000000000..ee7942b1996e --- /dev/null +++ b/drivers/media/platform/davinci/dm644x_ccdc.c @@ -0,0 +1,1081 @@ +/* + * Copyright (C) 2006-2009 Texas Instruments Inc + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * CCDC hardware module for DM6446 + * ------------------------------ + * + * This module is for configuring CCD controller of DM6446 VPFE to capture + * Raw yuv or Bayer RGB data from a decoder. CCDC has several modules + * such as Defect Pixel Correction, Color Space Conversion etc to + * pre-process the Raw Bayer RGB data, before writing it to SDRAM. This + * module also allows application to configure individual + * module parameters through VPFE_CMD_S_CCDC_RAW_PARAMS IOCTL. + * To do so, application includes dm644x_ccdc.h and vpfe_capture.h header + * files. The setparams() API is called by vpfe_capture driver + * to configure module parameters. This file is named DM644x so that other + * variants such DM6443 may be supported using the same module. + * + * TODO: Test Raw bayer parameter settings and bayer capture + * Split module parameter structure to module specific ioctl structs + * investigate if enum used for user space type definition + * to be replaced by #defines or integer + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dm644x_ccdc_regs.h" +#include "ccdc_hw_device.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CCDC Driver for DM6446"); +MODULE_AUTHOR("Texas Instruments"); + +static struct ccdc_oper_config { + struct device *dev; + /* CCDC interface type */ + enum vpfe_hw_if_type if_type; + /* Raw Bayer configuration */ + struct ccdc_params_raw bayer; + /* YCbCr configuration */ + struct ccdc_params_ycbcr ycbcr; + /* Master clock */ + struct clk *mclk; + /* slave clock */ + struct clk *sclk; + /* ccdc base address */ + void __iomem *base_addr; +} ccdc_cfg = { + /* Raw configurations */ + .bayer = { + .pix_fmt = CCDC_PIXFMT_RAW, + .frm_fmt = CCDC_FRMFMT_PROGRESSIVE, + .win = CCDC_WIN_VGA, + .fid_pol = VPFE_PINPOL_POSITIVE, + .vd_pol = VPFE_PINPOL_POSITIVE, + .hd_pol = VPFE_PINPOL_POSITIVE, + .config_params = { + .data_sz = CCDC_DATA_10BITS, + }, + }, + .ycbcr = { + .pix_fmt = CCDC_PIXFMT_YCBCR_8BIT, + .frm_fmt = CCDC_FRMFMT_INTERLACED, + .win = CCDC_WIN_PAL, + .fid_pol = VPFE_PINPOL_POSITIVE, + .vd_pol = VPFE_PINPOL_POSITIVE, + .hd_pol = VPFE_PINPOL_POSITIVE, + .bt656_enable = 1, + .pix_order = CCDC_PIXORDER_CBYCRY, + .buf_type = CCDC_BUFTYPE_FLD_INTERLEAVED + }, +}; + +#define CCDC_MAX_RAW_YUV_FORMATS 2 + +/* Raw Bayer formats */ +static u32 ccdc_raw_bayer_pix_formats[] = + {V4L2_PIX_FMT_SBGGR8, V4L2_PIX_FMT_SBGGR16}; + +/* Raw YUV formats */ +static u32 ccdc_raw_yuv_pix_formats[] = + {V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_YUYV}; + +/* CCDC Save/Restore context */ +static u32 ccdc_ctx[CCDC_REG_END / sizeof(u32)]; + +/* register access routines */ +static inline u32 regr(u32 offset) +{ + return __raw_readl(ccdc_cfg.base_addr + offset); +} + +static inline void regw(u32 val, u32 offset) +{ + __raw_writel(val, ccdc_cfg.base_addr + offset); +} + +static void ccdc_enable(int flag) +{ + regw(flag, CCDC_PCR); +} + +static void ccdc_enable_vport(int flag) +{ + if (flag) + /* enable video port */ + regw(CCDC_ENABLE_VIDEO_PORT, CCDC_FMTCFG); + else + regw(CCDC_DISABLE_VIDEO_PORT, CCDC_FMTCFG); +} + +/* + * ccdc_setwin() + * This function will configure the window size + * to be capture in CCDC reg + */ +void ccdc_setwin(struct v4l2_rect *image_win, + enum ccdc_frmfmt frm_fmt, + int ppc) +{ + int horz_start, horz_nr_pixels; + int vert_start, vert_nr_lines; + int val = 0, mid_img = 0; + + dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_setwin..."); + /* + * ppc - per pixel count. indicates how many pixels per cell + * output to SDRAM. example, for ycbcr, it is one y and one c, so 2. + * raw capture this is 1 + */ + horz_start = image_win->left << (ppc - 1); + horz_nr_pixels = (image_win->width << (ppc - 1)) - 1; + regw((horz_start << CCDC_HORZ_INFO_SPH_SHIFT) | horz_nr_pixels, + CCDC_HORZ_INFO); + + vert_start = image_win->top; + + if (frm_fmt == CCDC_FRMFMT_INTERLACED) { + vert_nr_lines = (image_win->height >> 1) - 1; + vert_start >>= 1; + /* Since first line doesn't have any data */ + vert_start += 1; + /* configure VDINT0 */ + val = (vert_start << CCDC_VDINT_VDINT0_SHIFT); + regw(val, CCDC_VDINT); + + } else { + /* Since first line doesn't have any data */ + vert_start += 1; + vert_nr_lines = image_win->height - 1; + /* + * configure VDINT0 and VDINT1. VDINT1 will be at half + * of image height + */ + mid_img = vert_start + (image_win->height / 2); + val = (vert_start << CCDC_VDINT_VDINT0_SHIFT) | + (mid_img & CCDC_VDINT_VDINT1_MASK); + regw(val, CCDC_VDINT); + + } + regw((vert_start << CCDC_VERT_START_SLV0_SHIFT) | vert_start, + CCDC_VERT_START); + regw(vert_nr_lines, CCDC_VERT_LINES); + dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_setwin..."); +} + +static void ccdc_readregs(void) +{ + unsigned int val = 0; + + val = regr(CCDC_ALAW); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to ALAW...\n", val); + val = regr(CCDC_CLAMP); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to CLAMP...\n", val); + val = regr(CCDC_DCSUB); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to DCSUB...\n", val); + val = regr(CCDC_BLKCMP); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to BLKCMP...\n", val); + val = regr(CCDC_FPC_ADDR); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FPC_ADDR...\n", val); + val = regr(CCDC_FPC); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FPC...\n", val); + val = regr(CCDC_FMTCFG); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FMTCFG...\n", val); + val = regr(CCDC_COLPTN); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to COLPTN...\n", val); + val = regr(CCDC_FMT_HORZ); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FMT_HORZ...\n", val); + val = regr(CCDC_FMT_VERT); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FMT_VERT...\n", val); + val = regr(CCDC_HSIZE_OFF); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to HSIZE_OFF...\n", val); + val = regr(CCDC_SDOFST); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to SDOFST...\n", val); + val = regr(CCDC_VP_OUT); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to VP_OUT...\n", val); + val = regr(CCDC_SYN_MODE); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to SYN_MODE...\n", val); + val = regr(CCDC_HORZ_INFO); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to HORZ_INFO...\n", val); + val = regr(CCDC_VERT_START); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to VERT_START...\n", val); + val = regr(CCDC_VERT_LINES); + dev_notice(ccdc_cfg.dev, "\nReading 0x%x to VERT_LINES...\n", val); +} + +static int validate_ccdc_param(struct ccdc_config_params_raw *ccdcparam) +{ + if (ccdcparam->alaw.enable) { + if ((ccdcparam->alaw.gama_wd > CCDC_GAMMA_BITS_09_0) || + (ccdcparam->alaw.gama_wd < CCDC_GAMMA_BITS_15_6) || + (ccdcparam->alaw.gama_wd < ccdcparam->data_sz)) { + dev_dbg(ccdc_cfg.dev, "\nInvalid data line select"); + return -1; + } + } + return 0; +} + +static int ccdc_update_raw_params(struct ccdc_config_params_raw *raw_params) +{ + struct ccdc_config_params_raw *config_params = + &ccdc_cfg.bayer.config_params; + unsigned int *fpc_virtaddr = NULL; + unsigned int *fpc_physaddr = NULL; + + memcpy(config_params, raw_params, sizeof(*raw_params)); + /* + * allocate memory for fault pixel table and copy the user + * values to the table + */ + if (!config_params->fault_pxl.enable) + return 0; + + fpc_physaddr = (unsigned int *)config_params->fault_pxl.fpc_table_addr; + fpc_virtaddr = (unsigned int *)phys_to_virt( + (unsigned long)fpc_physaddr); + /* + * Allocate memory for FPC table if current + * FPC table buffer is not big enough to + * accommodate FPC Number requested + */ + if (raw_params->fault_pxl.fp_num != config_params->fault_pxl.fp_num) { + if (fpc_physaddr != NULL) { + free_pages((unsigned long)fpc_physaddr, + get_order + (config_params->fault_pxl.fp_num * + FP_NUM_BYTES)); + } + + /* Allocate memory for FPC table */ + fpc_virtaddr = + (unsigned int *)__get_free_pages(GFP_KERNEL | GFP_DMA, + get_order(raw_params-> + fault_pxl.fp_num * + FP_NUM_BYTES)); + + if (fpc_virtaddr == NULL) { + dev_dbg(ccdc_cfg.dev, + "\nUnable to allocate memory for FPC"); + return -EFAULT; + } + fpc_physaddr = + (unsigned int *)virt_to_phys((void *)fpc_virtaddr); + } + + /* Copy number of fault pixels and FPC table */ + config_params->fault_pxl.fp_num = raw_params->fault_pxl.fp_num; + if (copy_from_user(fpc_virtaddr, + (void __user *)raw_params->fault_pxl.fpc_table_addr, + config_params->fault_pxl.fp_num * FP_NUM_BYTES)) { + dev_dbg(ccdc_cfg.dev, "\n copy_from_user failed"); + return -EFAULT; + } + config_params->fault_pxl.fpc_table_addr = (unsigned int)fpc_physaddr; + return 0; +} + +static int ccdc_close(struct device *dev) +{ + struct ccdc_config_params_raw *config_params = + &ccdc_cfg.bayer.config_params; + unsigned int *fpc_physaddr = NULL, *fpc_virtaddr = NULL; + + fpc_physaddr = (unsigned int *)config_params->fault_pxl.fpc_table_addr; + + if (fpc_physaddr != NULL) { + fpc_virtaddr = (unsigned int *) + phys_to_virt((unsigned long)fpc_physaddr); + free_pages((unsigned long)fpc_virtaddr, + get_order(config_params->fault_pxl.fp_num * + FP_NUM_BYTES)); + } + return 0; +} + +/* + * ccdc_restore_defaults() + * This function will write defaults to all CCDC registers + */ +static void ccdc_restore_defaults(void) +{ + int i; + + /* disable CCDC */ + ccdc_enable(0); + /* set all registers to default value */ + for (i = 4; i <= 0x94; i += 4) + regw(0, i); + regw(CCDC_NO_CULLING, CCDC_CULLING); + regw(CCDC_GAMMA_BITS_11_2, CCDC_ALAW); +} + +static int ccdc_open(struct device *device) +{ + ccdc_restore_defaults(); + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc_enable_vport(1); + return 0; +} + +static void ccdc_sbl_reset(void) +{ + vpss_clear_wbl_overflow(VPSS_PCR_CCDC_WBL_O); +} + +/* Parameter operations */ +static int ccdc_set_params(void __user *params) +{ + struct ccdc_config_params_raw ccdc_raw_params; + int x; + + if (ccdc_cfg.if_type != VPFE_RAW_BAYER) + return -EINVAL; + + x = copy_from_user(&ccdc_raw_params, params, sizeof(ccdc_raw_params)); + if (x) { + dev_dbg(ccdc_cfg.dev, "ccdc_set_params: error in copying" + "ccdc params, %d\n", x); + return -EFAULT; + } + + if (!validate_ccdc_param(&ccdc_raw_params)) { + if (!ccdc_update_raw_params(&ccdc_raw_params)) + return 0; + } + return -EINVAL; +} + +/* + * ccdc_config_ycbcr() + * This function will configure CCDC for YCbCr video capture + */ +void ccdc_config_ycbcr(void) +{ + struct ccdc_params_ycbcr *params = &ccdc_cfg.ycbcr; + u32 syn_mode; + + dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_config_ycbcr..."); + /* + * first restore the CCDC registers to default values + * This is important since we assume default values to be set in + * a lot of registers that we didn't touch + */ + ccdc_restore_defaults(); + + /* + * configure pixel format, frame format, configure video frame + * format, enable output to SDRAM, enable internal timing generator + * and 8bit pack mode + */ + syn_mode = (((params->pix_fmt & CCDC_SYN_MODE_INPMOD_MASK) << + CCDC_SYN_MODE_INPMOD_SHIFT) | + ((params->frm_fmt & CCDC_SYN_FLDMODE_MASK) << + CCDC_SYN_FLDMODE_SHIFT) | CCDC_VDHDEN_ENABLE | + CCDC_WEN_ENABLE | CCDC_DATA_PACK_ENABLE); + + /* setup BT.656 sync mode */ + if (params->bt656_enable) { + regw(CCDC_REC656IF_BT656_EN, CCDC_REC656IF); + + /* + * configure the FID, VD, HD pin polarity, + * fld,hd pol positive, vd negative, 8-bit data + */ + syn_mode |= CCDC_SYN_MODE_VD_POL_NEGATIVE; + if (ccdc_cfg.if_type == VPFE_BT656_10BIT) + syn_mode |= CCDC_SYN_MODE_10BITS; + else + syn_mode |= CCDC_SYN_MODE_8BITS; + } else { + /* y/c external sync mode */ + syn_mode |= (((params->fid_pol & CCDC_FID_POL_MASK) << + CCDC_FID_POL_SHIFT) | + ((params->hd_pol & CCDC_HD_POL_MASK) << + CCDC_HD_POL_SHIFT) | + ((params->vd_pol & CCDC_VD_POL_MASK) << + CCDC_VD_POL_SHIFT)); + } + regw(syn_mode, CCDC_SYN_MODE); + + /* configure video window */ + ccdc_setwin(¶ms->win, params->frm_fmt, 2); + + /* + * configure the order of y cb cr in SDRAM, and disable latch + * internal register on vsync + */ + if (ccdc_cfg.if_type == VPFE_BT656_10BIT) + regw((params->pix_order << CCDC_CCDCFG_Y8POS_SHIFT) | + CCDC_LATCH_ON_VSYNC_DISABLE | CCDC_CCDCFG_BW656_10BIT, + CCDC_CCDCFG); + else + regw((params->pix_order << CCDC_CCDCFG_Y8POS_SHIFT) | + CCDC_LATCH_ON_VSYNC_DISABLE, CCDC_CCDCFG); + + /* + * configure the horizontal line offset. This should be a + * on 32 byte boundary. So clear LSB 5 bits + */ + regw(((params->win.width * 2 + 31) & ~0x1f), CCDC_HSIZE_OFF); + + /* configure the memory line offset */ + if (params->buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) + /* two fields are interleaved in memory */ + regw(CCDC_SDOFST_FIELD_INTERLEAVED, CCDC_SDOFST); + + ccdc_sbl_reset(); + dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_config_ycbcr...\n"); +} + +static void ccdc_config_black_clamp(struct ccdc_black_clamp *bclamp) +{ + u32 val; + + if (!bclamp->enable) { + /* configure DCSub */ + val = (bclamp->dc_sub) & CCDC_BLK_DC_SUB_MASK; + regw(val, CCDC_DCSUB); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to DCSUB...\n", val); + regw(CCDC_CLAMP_DEFAULT_VAL, CCDC_CLAMP); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x0000 to CLAMP...\n"); + return; + } + /* + * Configure gain, Start pixel, No of line to be avg, + * No of pixel/line to be avg, & Enable the Black clamping + */ + val = ((bclamp->sgain & CCDC_BLK_SGAIN_MASK) | + ((bclamp->start_pixel & CCDC_BLK_ST_PXL_MASK) << + CCDC_BLK_ST_PXL_SHIFT) | + ((bclamp->sample_ln & CCDC_BLK_SAMPLE_LINE_MASK) << + CCDC_BLK_SAMPLE_LINE_SHIFT) | + ((bclamp->sample_pixel & CCDC_BLK_SAMPLE_LN_MASK) << + CCDC_BLK_SAMPLE_LN_SHIFT) | CCDC_BLK_CLAMP_ENABLE); + regw(val, CCDC_CLAMP); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to CLAMP...\n", val); + /* If Black clamping is enable then make dcsub 0 */ + regw(CCDC_DCSUB_DEFAULT_VAL, CCDC_DCSUB); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x00000000 to DCSUB...\n"); +} + +static void ccdc_config_black_compense(struct ccdc_black_compensation *bcomp) +{ + u32 val; + + val = ((bcomp->b & CCDC_BLK_COMP_MASK) | + ((bcomp->gb & CCDC_BLK_COMP_MASK) << + CCDC_BLK_COMP_GB_COMP_SHIFT) | + ((bcomp->gr & CCDC_BLK_COMP_MASK) << + CCDC_BLK_COMP_GR_COMP_SHIFT) | + ((bcomp->r & CCDC_BLK_COMP_MASK) << + CCDC_BLK_COMP_R_COMP_SHIFT)); + regw(val, CCDC_BLKCMP); +} + +static void ccdc_config_fpc(struct ccdc_fault_pixel *fpc) +{ + u32 val; + + /* Initially disable FPC */ + val = CCDC_FPC_DISABLE; + regw(val, CCDC_FPC); + + if (!fpc->enable) + return; + + /* Configure Fault pixel if needed */ + regw(fpc->fpc_table_addr, CCDC_FPC_ADDR); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FPC_ADDR...\n", + (fpc->fpc_table_addr)); + /* Write the FPC params with FPC disable */ + val = fpc->fp_num & CCDC_FPC_FPC_NUM_MASK; + regw(val, CCDC_FPC); + + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FPC...\n", val); + /* read the FPC register */ + val = regr(CCDC_FPC) | CCDC_FPC_ENABLE; + regw(val, CCDC_FPC); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FPC...\n", val); +} + +/* + * ccdc_config_raw() + * This function will configure CCDC for Raw capture mode + */ +void ccdc_config_raw(void) +{ + struct ccdc_params_raw *params = &ccdc_cfg.bayer; + struct ccdc_config_params_raw *config_params = + &ccdc_cfg.bayer.config_params; + unsigned int syn_mode = 0; + unsigned int val; + + dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_config_raw..."); + + /* Reset CCDC */ + ccdc_restore_defaults(); + + /* Disable latching function registers on VSYNC */ + regw(CCDC_LATCH_ON_VSYNC_DISABLE, CCDC_CCDCFG); + + /* + * Configure the vertical sync polarity(SYN_MODE.VDPOL), + * horizontal sync polarity (SYN_MODE.HDPOL), frame id polarity + * (SYN_MODE.FLDPOL), frame format(progressive or interlace), + * data size(SYNMODE.DATSIZ), &pixel format (Input mode), output + * SDRAM, enable internal timing generator + */ + syn_mode = + (((params->vd_pol & CCDC_VD_POL_MASK) << CCDC_VD_POL_SHIFT) | + ((params->hd_pol & CCDC_HD_POL_MASK) << CCDC_HD_POL_SHIFT) | + ((params->fid_pol & CCDC_FID_POL_MASK) << CCDC_FID_POL_SHIFT) | + ((params->frm_fmt & CCDC_FRM_FMT_MASK) << CCDC_FRM_FMT_SHIFT) | + ((config_params->data_sz & CCDC_DATA_SZ_MASK) << + CCDC_DATA_SZ_SHIFT) | + ((params->pix_fmt & CCDC_PIX_FMT_MASK) << CCDC_PIX_FMT_SHIFT) | + CCDC_WEN_ENABLE | CCDC_VDHDEN_ENABLE); + + /* Enable and configure aLaw register if needed */ + if (config_params->alaw.enable) { + val = ((config_params->alaw.gama_wd & + CCDC_ALAW_GAMA_WD_MASK) | CCDC_ALAW_ENABLE); + regw(val, CCDC_ALAW); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to ALAW...\n", val); + } + + /* Configure video window */ + ccdc_setwin(¶ms->win, params->frm_fmt, CCDC_PPC_RAW); + + /* Configure Black Clamp */ + ccdc_config_black_clamp(&config_params->blk_clamp); + + /* Configure Black level compensation */ + ccdc_config_black_compense(&config_params->blk_comp); + + /* Configure Fault Pixel Correction */ + ccdc_config_fpc(&config_params->fault_pxl); + + /* If data size is 8 bit then pack the data */ + if ((config_params->data_sz == CCDC_DATA_8BITS) || + config_params->alaw.enable) + syn_mode |= CCDC_DATA_PACK_ENABLE; + +#ifdef CONFIG_DM644X_VIDEO_PORT_ENABLE + /* enable video port */ + val = CCDC_ENABLE_VIDEO_PORT; +#else + /* disable video port */ + val = CCDC_DISABLE_VIDEO_PORT; +#endif + + if (config_params->data_sz == CCDC_DATA_8BITS) + val |= (CCDC_DATA_10BITS & CCDC_FMTCFG_VPIN_MASK) + << CCDC_FMTCFG_VPIN_SHIFT; + else + val |= (config_params->data_sz & CCDC_FMTCFG_VPIN_MASK) + << CCDC_FMTCFG_VPIN_SHIFT; + /* Write value in FMTCFG */ + regw(val, CCDC_FMTCFG); + + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FMTCFG...\n", val); + /* Configure the color pattern according to mt9t001 sensor */ + regw(CCDC_COLPTN_VAL, CCDC_COLPTN); + + dev_dbg(ccdc_cfg.dev, "\nWriting 0xBB11BB11 to COLPTN...\n"); + /* + * Configure Data formatter(Video port) pixel selection + * (FMT_HORZ, FMT_VERT) + */ + val = ((params->win.left & CCDC_FMT_HORZ_FMTSPH_MASK) << + CCDC_FMT_HORZ_FMTSPH_SHIFT) | + (params->win.width & CCDC_FMT_HORZ_FMTLNH_MASK); + regw(val, CCDC_FMT_HORZ); + + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FMT_HORZ...\n", val); + val = (params->win.top & CCDC_FMT_VERT_FMTSLV_MASK) + << CCDC_FMT_VERT_FMTSLV_SHIFT; + if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) + val |= (params->win.height) & CCDC_FMT_VERT_FMTLNV_MASK; + else + val |= (params->win.height >> 1) & CCDC_FMT_VERT_FMTLNV_MASK; + + dev_dbg(ccdc_cfg.dev, "\nparams->win.height 0x%x ...\n", + params->win.height); + regw(val, CCDC_FMT_VERT); + + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FMT_VERT...\n", val); + + dev_dbg(ccdc_cfg.dev, "\nbelow regw(val, FMT_VERT)..."); + + /* + * Configure Horizontal offset register. If pack 8 is enabled then + * 1 pixel will take 1 byte + */ + if ((config_params->data_sz == CCDC_DATA_8BITS) || + config_params->alaw.enable) + regw((params->win.width + CCDC_32BYTE_ALIGN_VAL) & + CCDC_HSIZE_OFF_MASK, CCDC_HSIZE_OFF); + else + /* else one pixel will take 2 byte */ + regw(((params->win.width * CCDC_TWO_BYTES_PER_PIXEL) + + CCDC_32BYTE_ALIGN_VAL) & CCDC_HSIZE_OFF_MASK, + CCDC_HSIZE_OFF); + + /* Set value for SDOFST */ + if (params->frm_fmt == CCDC_FRMFMT_INTERLACED) { + if (params->image_invert_enable) { + /* For intelace inverse mode */ + regw(CCDC_INTERLACED_IMAGE_INVERT, CCDC_SDOFST); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x4B6D to SDOFST..\n"); + } + + else { + /* For intelace non inverse mode */ + regw(CCDC_INTERLACED_NO_IMAGE_INVERT, CCDC_SDOFST); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x0249 to SDOFST..\n"); + } + } else if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { + regw(CCDC_PROGRESSIVE_NO_IMAGE_INVERT, CCDC_SDOFST); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x0000 to SDOFST...\n"); + } + + /* + * Configure video port pixel selection (VPOUT) + * Here -1 is to make the height value less than FMT_VERT.FMTLNV + */ + if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) + val = (((params->win.height - 1) & CCDC_VP_OUT_VERT_NUM_MASK)) + << CCDC_VP_OUT_VERT_NUM_SHIFT; + else + val = + ((((params->win.height >> CCDC_INTERLACED_HEIGHT_SHIFT) - + 1) & CCDC_VP_OUT_VERT_NUM_MASK)) << + CCDC_VP_OUT_VERT_NUM_SHIFT; + + val |= ((((params->win.width))) & CCDC_VP_OUT_HORZ_NUM_MASK) + << CCDC_VP_OUT_HORZ_NUM_SHIFT; + val |= (params->win.left) & CCDC_VP_OUT_HORZ_ST_MASK; + regw(val, CCDC_VP_OUT); + + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to VP_OUT...\n", val); + regw(syn_mode, CCDC_SYN_MODE); + dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to SYN_MODE...\n", syn_mode); + + ccdc_sbl_reset(); + dev_dbg(ccdc_cfg.dev, "\nend of ccdc_config_raw..."); + ccdc_readregs(); +} + +static int ccdc_configure(void) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc_config_raw(); + else + ccdc_config_ycbcr(); + return 0; +} + +static int ccdc_set_buftype(enum ccdc_buftype buf_type) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc_cfg.bayer.buf_type = buf_type; + else + ccdc_cfg.ycbcr.buf_type = buf_type; + return 0; +} + +static enum ccdc_buftype ccdc_get_buftype(void) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc_cfg.bayer.buf_type; + return ccdc_cfg.ycbcr.buf_type; +} + +static int ccdc_enum_pix(u32 *pix, int i) +{ + int ret = -EINVAL; + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { + if (i < ARRAY_SIZE(ccdc_raw_bayer_pix_formats)) { + *pix = ccdc_raw_bayer_pix_formats[i]; + ret = 0; + } + } else { + if (i < ARRAY_SIZE(ccdc_raw_yuv_pix_formats)) { + *pix = ccdc_raw_yuv_pix_formats[i]; + ret = 0; + } + } + return ret; +} + +static int ccdc_set_pixel_format(u32 pixfmt) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { + ccdc_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; + if (pixfmt == V4L2_PIX_FMT_SBGGR8) + ccdc_cfg.bayer.config_params.alaw.enable = 1; + else if (pixfmt != V4L2_PIX_FMT_SBGGR16) + return -EINVAL; + } else { + if (pixfmt == V4L2_PIX_FMT_YUYV) + ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_YCBYCR; + else if (pixfmt == V4L2_PIX_FMT_UYVY) + ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; + else + return -EINVAL; + } + return 0; +} + +static u32 ccdc_get_pixel_format(void) +{ + struct ccdc_a_law *alaw = &ccdc_cfg.bayer.config_params.alaw; + u32 pixfmt; + + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + if (alaw->enable) + pixfmt = V4L2_PIX_FMT_SBGGR8; + else + pixfmt = V4L2_PIX_FMT_SBGGR16; + else { + if (ccdc_cfg.ycbcr.pix_order == CCDC_PIXORDER_YCBYCR) + pixfmt = V4L2_PIX_FMT_YUYV; + else + pixfmt = V4L2_PIX_FMT_UYVY; + } + return pixfmt; +} + +static int ccdc_set_image_window(struct v4l2_rect *win) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc_cfg.bayer.win = *win; + else + ccdc_cfg.ycbcr.win = *win; + return 0; +} + +static void ccdc_get_image_window(struct v4l2_rect *win) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + *win = ccdc_cfg.bayer.win; + else + *win = ccdc_cfg.ycbcr.win; +} + +static unsigned int ccdc_get_line_length(void) +{ + struct ccdc_config_params_raw *config_params = + &ccdc_cfg.bayer.config_params; + unsigned int len; + + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { + if ((config_params->alaw.enable) || + (config_params->data_sz == CCDC_DATA_8BITS)) + len = ccdc_cfg.bayer.win.width; + else + len = ccdc_cfg.bayer.win.width * 2; + } else + len = ccdc_cfg.ycbcr.win.width * 2; + return ALIGN(len, 32); +} + +static int ccdc_set_frame_format(enum ccdc_frmfmt frm_fmt) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc_cfg.bayer.frm_fmt = frm_fmt; + else + ccdc_cfg.ycbcr.frm_fmt = frm_fmt; + return 0; +} + +static enum ccdc_frmfmt ccdc_get_frame_format(void) +{ + if (ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc_cfg.bayer.frm_fmt; + else + return ccdc_cfg.ycbcr.frm_fmt; +} + +static int ccdc_getfid(void) +{ + return (regr(CCDC_SYN_MODE) >> 15) & 1; +} + +/* misc operations */ +static inline void ccdc_setfbaddr(unsigned long addr) +{ + regw(addr & 0xffffffe0, CCDC_SDR_ADDR); +} + +static int ccdc_set_hw_if_params(struct vpfe_hw_if_param *params) +{ + ccdc_cfg.if_type = params->if_type; + + switch (params->if_type) { + case VPFE_BT656: + case VPFE_YCBCR_SYNC_16: + case VPFE_YCBCR_SYNC_8: + case VPFE_BT656_10BIT: + ccdc_cfg.ycbcr.vd_pol = params->vdpol; + ccdc_cfg.ycbcr.hd_pol = params->hdpol; + break; + default: + /* TODO add support for raw bayer here */ + return -EINVAL; + } + return 0; +} + +static void ccdc_save_context(void) +{ + ccdc_ctx[CCDC_PCR >> 2] = regr(CCDC_PCR); + ccdc_ctx[CCDC_SYN_MODE >> 2] = regr(CCDC_SYN_MODE); + ccdc_ctx[CCDC_HD_VD_WID >> 2] = regr(CCDC_HD_VD_WID); + ccdc_ctx[CCDC_PIX_LINES >> 2] = regr(CCDC_PIX_LINES); + ccdc_ctx[CCDC_HORZ_INFO >> 2] = regr(CCDC_HORZ_INFO); + ccdc_ctx[CCDC_VERT_START >> 2] = regr(CCDC_VERT_START); + ccdc_ctx[CCDC_VERT_LINES >> 2] = regr(CCDC_VERT_LINES); + ccdc_ctx[CCDC_CULLING >> 2] = regr(CCDC_CULLING); + ccdc_ctx[CCDC_HSIZE_OFF >> 2] = regr(CCDC_HSIZE_OFF); + ccdc_ctx[CCDC_SDOFST >> 2] = regr(CCDC_SDOFST); + ccdc_ctx[CCDC_SDR_ADDR >> 2] = regr(CCDC_SDR_ADDR); + ccdc_ctx[CCDC_CLAMP >> 2] = regr(CCDC_CLAMP); + ccdc_ctx[CCDC_DCSUB >> 2] = regr(CCDC_DCSUB); + ccdc_ctx[CCDC_COLPTN >> 2] = regr(CCDC_COLPTN); + ccdc_ctx[CCDC_BLKCMP >> 2] = regr(CCDC_BLKCMP); + ccdc_ctx[CCDC_FPC >> 2] = regr(CCDC_FPC); + ccdc_ctx[CCDC_FPC_ADDR >> 2] = regr(CCDC_FPC_ADDR); + ccdc_ctx[CCDC_VDINT >> 2] = regr(CCDC_VDINT); + ccdc_ctx[CCDC_ALAW >> 2] = regr(CCDC_ALAW); + ccdc_ctx[CCDC_REC656IF >> 2] = regr(CCDC_REC656IF); + ccdc_ctx[CCDC_CCDCFG >> 2] = regr(CCDC_CCDCFG); + ccdc_ctx[CCDC_FMTCFG >> 2] = regr(CCDC_FMTCFG); + ccdc_ctx[CCDC_FMT_HORZ >> 2] = regr(CCDC_FMT_HORZ); + ccdc_ctx[CCDC_FMT_VERT >> 2] = regr(CCDC_FMT_VERT); + ccdc_ctx[CCDC_FMT_ADDR0 >> 2] = regr(CCDC_FMT_ADDR0); + ccdc_ctx[CCDC_FMT_ADDR1 >> 2] = regr(CCDC_FMT_ADDR1); + ccdc_ctx[CCDC_FMT_ADDR2 >> 2] = regr(CCDC_FMT_ADDR2); + ccdc_ctx[CCDC_FMT_ADDR3 >> 2] = regr(CCDC_FMT_ADDR3); + ccdc_ctx[CCDC_FMT_ADDR4 >> 2] = regr(CCDC_FMT_ADDR4); + ccdc_ctx[CCDC_FMT_ADDR5 >> 2] = regr(CCDC_FMT_ADDR5); + ccdc_ctx[CCDC_FMT_ADDR6 >> 2] = regr(CCDC_FMT_ADDR6); + ccdc_ctx[CCDC_FMT_ADDR7 >> 2] = regr(CCDC_FMT_ADDR7); + ccdc_ctx[CCDC_PRGEVEN_0 >> 2] = regr(CCDC_PRGEVEN_0); + ccdc_ctx[CCDC_PRGEVEN_1 >> 2] = regr(CCDC_PRGEVEN_1); + ccdc_ctx[CCDC_PRGODD_0 >> 2] = regr(CCDC_PRGODD_0); + ccdc_ctx[CCDC_PRGODD_1 >> 2] = regr(CCDC_PRGODD_1); + ccdc_ctx[CCDC_VP_OUT >> 2] = regr(CCDC_VP_OUT); +} + +static void ccdc_restore_context(void) +{ + regw(ccdc_ctx[CCDC_SYN_MODE >> 2], CCDC_SYN_MODE); + regw(ccdc_ctx[CCDC_HD_VD_WID >> 2], CCDC_HD_VD_WID); + regw(ccdc_ctx[CCDC_PIX_LINES >> 2], CCDC_PIX_LINES); + regw(ccdc_ctx[CCDC_HORZ_INFO >> 2], CCDC_HORZ_INFO); + regw(ccdc_ctx[CCDC_VERT_START >> 2], CCDC_VERT_START); + regw(ccdc_ctx[CCDC_VERT_LINES >> 2], CCDC_VERT_LINES); + regw(ccdc_ctx[CCDC_CULLING >> 2], CCDC_CULLING); + regw(ccdc_ctx[CCDC_HSIZE_OFF >> 2], CCDC_HSIZE_OFF); + regw(ccdc_ctx[CCDC_SDOFST >> 2], CCDC_SDOFST); + regw(ccdc_ctx[CCDC_SDR_ADDR >> 2], CCDC_SDR_ADDR); + regw(ccdc_ctx[CCDC_CLAMP >> 2], CCDC_CLAMP); + regw(ccdc_ctx[CCDC_DCSUB >> 2], CCDC_DCSUB); + regw(ccdc_ctx[CCDC_COLPTN >> 2], CCDC_COLPTN); + regw(ccdc_ctx[CCDC_BLKCMP >> 2], CCDC_BLKCMP); + regw(ccdc_ctx[CCDC_FPC >> 2], CCDC_FPC); + regw(ccdc_ctx[CCDC_FPC_ADDR >> 2], CCDC_FPC_ADDR); + regw(ccdc_ctx[CCDC_VDINT >> 2], CCDC_VDINT); + regw(ccdc_ctx[CCDC_ALAW >> 2], CCDC_ALAW); + regw(ccdc_ctx[CCDC_REC656IF >> 2], CCDC_REC656IF); + regw(ccdc_ctx[CCDC_CCDCFG >> 2], CCDC_CCDCFG); + regw(ccdc_ctx[CCDC_FMTCFG >> 2], CCDC_FMTCFG); + regw(ccdc_ctx[CCDC_FMT_HORZ >> 2], CCDC_FMT_HORZ); + regw(ccdc_ctx[CCDC_FMT_VERT >> 2], CCDC_FMT_VERT); + regw(ccdc_ctx[CCDC_FMT_ADDR0 >> 2], CCDC_FMT_ADDR0); + regw(ccdc_ctx[CCDC_FMT_ADDR1 >> 2], CCDC_FMT_ADDR1); + regw(ccdc_ctx[CCDC_FMT_ADDR2 >> 2], CCDC_FMT_ADDR2); + regw(ccdc_ctx[CCDC_FMT_ADDR3 >> 2], CCDC_FMT_ADDR3); + regw(ccdc_ctx[CCDC_FMT_ADDR4 >> 2], CCDC_FMT_ADDR4); + regw(ccdc_ctx[CCDC_FMT_ADDR5 >> 2], CCDC_FMT_ADDR5); + regw(ccdc_ctx[CCDC_FMT_ADDR6 >> 2], CCDC_FMT_ADDR6); + regw(ccdc_ctx[CCDC_FMT_ADDR7 >> 2], CCDC_FMT_ADDR7); + regw(ccdc_ctx[CCDC_PRGEVEN_0 >> 2], CCDC_PRGEVEN_0); + regw(ccdc_ctx[CCDC_PRGEVEN_1 >> 2], CCDC_PRGEVEN_1); + regw(ccdc_ctx[CCDC_PRGODD_0 >> 2], CCDC_PRGODD_0); + regw(ccdc_ctx[CCDC_PRGODD_1 >> 2], CCDC_PRGODD_1); + regw(ccdc_ctx[CCDC_VP_OUT >> 2], CCDC_VP_OUT); + regw(ccdc_ctx[CCDC_PCR >> 2], CCDC_PCR); +} +static struct ccdc_hw_device ccdc_hw_dev = { + .name = "DM6446 CCDC", + .owner = THIS_MODULE, + .hw_ops = { + .open = ccdc_open, + .close = ccdc_close, + .reset = ccdc_sbl_reset, + .enable = ccdc_enable, + .set_hw_if_params = ccdc_set_hw_if_params, + .set_params = ccdc_set_params, + .configure = ccdc_configure, + .set_buftype = ccdc_set_buftype, + .get_buftype = ccdc_get_buftype, + .enum_pix = ccdc_enum_pix, + .set_pixel_format = ccdc_set_pixel_format, + .get_pixel_format = ccdc_get_pixel_format, + .set_frame_format = ccdc_set_frame_format, + .get_frame_format = ccdc_get_frame_format, + .set_image_window = ccdc_set_image_window, + .get_image_window = ccdc_get_image_window, + .get_line_length = ccdc_get_line_length, + .setfbaddr = ccdc_setfbaddr, + .getfid = ccdc_getfid, + }, +}; + +static int __devinit dm644x_ccdc_probe(struct platform_device *pdev) +{ + struct resource *res; + int status = 0; + + /* + * first try to register with vpfe. If not correct platform, then we + * don't have to iomap + */ + status = vpfe_register_ccdc_device(&ccdc_hw_dev); + if (status < 0) + return status; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + status = -ENODEV; + goto fail_nores; + } + + res = request_mem_region(res->start, resource_size(res), res->name); + if (!res) { + status = -EBUSY; + goto fail_nores; + } + + ccdc_cfg.base_addr = ioremap_nocache(res->start, resource_size(res)); + if (!ccdc_cfg.base_addr) { + status = -ENOMEM; + goto fail_nomem; + } + + /* Get and enable Master clock */ + ccdc_cfg.mclk = clk_get(&pdev->dev, "master"); + if (IS_ERR(ccdc_cfg.mclk)) { + status = PTR_ERR(ccdc_cfg.mclk); + goto fail_nomap; + } + if (clk_enable(ccdc_cfg.mclk)) { + status = -ENODEV; + goto fail_mclk; + } + + /* Get and enable Slave clock */ + ccdc_cfg.sclk = clk_get(&pdev->dev, "slave"); + if (IS_ERR(ccdc_cfg.sclk)) { + status = PTR_ERR(ccdc_cfg.sclk); + goto fail_mclk; + } + if (clk_enable(ccdc_cfg.sclk)) { + status = -ENODEV; + goto fail_sclk; + } + ccdc_cfg.dev = &pdev->dev; + printk(KERN_NOTICE "%s is registered with vpfe.\n", ccdc_hw_dev.name); + return 0; +fail_sclk: + clk_put(ccdc_cfg.sclk); +fail_mclk: + clk_put(ccdc_cfg.mclk); +fail_nomap: + iounmap(ccdc_cfg.base_addr); +fail_nomem: + release_mem_region(res->start, resource_size(res)); +fail_nores: + vpfe_unregister_ccdc_device(&ccdc_hw_dev); + return status; +} + +static int dm644x_ccdc_remove(struct platform_device *pdev) +{ + struct resource *res; + + clk_put(ccdc_cfg.mclk); + clk_put(ccdc_cfg.sclk); + iounmap(ccdc_cfg.base_addr); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + vpfe_unregister_ccdc_device(&ccdc_hw_dev); + return 0; +} + +static int dm644x_ccdc_suspend(struct device *dev) +{ + /* Save CCDC context */ + ccdc_save_context(); + /* Disable CCDC */ + ccdc_enable(0); + /* Disable both master and slave clock */ + clk_disable(ccdc_cfg.mclk); + clk_disable(ccdc_cfg.sclk); + + return 0; +} + +static int dm644x_ccdc_resume(struct device *dev) +{ + /* Enable both master and slave clock */ + clk_enable(ccdc_cfg.mclk); + clk_enable(ccdc_cfg.sclk); + /* Restore CCDC context */ + ccdc_restore_context(); + + return 0; +} + +static const struct dev_pm_ops dm644x_ccdc_pm_ops = { + .suspend = dm644x_ccdc_suspend, + .resume = dm644x_ccdc_resume, +}; + +static struct platform_driver dm644x_ccdc_driver = { + .driver = { + .name = "dm644x_ccdc", + .owner = THIS_MODULE, + .pm = &dm644x_ccdc_pm_ops, + }, + .remove = __devexit_p(dm644x_ccdc_remove), + .probe = dm644x_ccdc_probe, +}; + +module_platform_driver(dm644x_ccdc_driver); diff --git a/drivers/media/video/davinci/dm644x_ccdc_regs.h b/drivers/media/platform/davinci/dm644x_ccdc_regs.h similarity index 100% rename from drivers/media/video/davinci/dm644x_ccdc_regs.h rename to drivers/media/platform/davinci/dm644x_ccdc_regs.h diff --git a/drivers/media/platform/davinci/isif.c b/drivers/media/platform/davinci/isif.c new file mode 100644 index 000000000000..b99d5423e3a8 --- /dev/null +++ b/drivers/media/platform/davinci/isif.c @@ -0,0 +1,1162 @@ +/* + * Copyright (C) 2008-2009 Texas Instruments Inc + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Image Sensor Interface (ISIF) driver + * + * This driver is for configuring the ISIF IP available on DM365 or any other + * TI SoCs. This is used for capturing yuv or bayer video or image data + * from a decoder or sensor. This IP is similar to the CCDC IP on DM355 + * and DM6446, but with enhanced or additional ip blocks. The driver + * configures the ISIF upon commands from the vpfe bridge driver through + * ccdc_hw_device interface. + * + * TODO: 1) Raw bayer parameter settings and bayer capture + * 2) Add support for control ioctl + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "isif_regs.h" +#include "ccdc_hw_device.h" + +/* Defaults for module configuration parameters */ +static struct isif_config_params_raw isif_config_defaults = { + .linearize = { + .en = 0, + .corr_shft = ISIF_NO_SHIFT, + .scale_fact = {1, 0}, + }, + .df_csc = { + .df_or_csc = 0, + .csc = { + .en = 0, + }, + }, + .dfc = { + .en = 0, + }, + .bclamp = { + .en = 0, + }, + .gain_offset = { + .gain = { + .r_ye = {1, 0}, + .gr_cy = {1, 0}, + .gb_g = {1, 0}, + .b_mg = {1, 0}, + }, + }, + .culling = { + .hcpat_odd = 0xff, + .hcpat_even = 0xff, + .vcpat = 0xff, + }, + .compress = { + .alg = ISIF_ALAW, + }, +}; + +/* ISIF operation configuration */ +static struct isif_oper_config { + struct device *dev; + enum vpfe_hw_if_type if_type; + struct isif_ycbcr_config ycbcr; + struct isif_params_raw bayer; + enum isif_data_pack data_pack; + /* Master clock */ + struct clk *mclk; + /* ISIF base address */ + void __iomem *base_addr; + /* ISIF Linear Table 0 */ + void __iomem *linear_tbl0_addr; + /* ISIF Linear Table 1 */ + void __iomem *linear_tbl1_addr; +} isif_cfg = { + .ycbcr = { + .pix_fmt = CCDC_PIXFMT_YCBCR_8BIT, + .frm_fmt = CCDC_FRMFMT_INTERLACED, + .win = ISIF_WIN_NTSC, + .fid_pol = VPFE_PINPOL_POSITIVE, + .vd_pol = VPFE_PINPOL_POSITIVE, + .hd_pol = VPFE_PINPOL_POSITIVE, + .pix_order = CCDC_PIXORDER_CBYCRY, + .buf_type = CCDC_BUFTYPE_FLD_INTERLEAVED, + }, + .bayer = { + .pix_fmt = CCDC_PIXFMT_RAW, + .frm_fmt = CCDC_FRMFMT_PROGRESSIVE, + .win = ISIF_WIN_VGA, + .fid_pol = VPFE_PINPOL_POSITIVE, + .vd_pol = VPFE_PINPOL_POSITIVE, + .hd_pol = VPFE_PINPOL_POSITIVE, + .gain = { + .r_ye = {1, 0}, + .gr_cy = {1, 0}, + .gb_g = {1, 0}, + .b_mg = {1, 0}, + }, + .cfa_pat = ISIF_CFA_PAT_MOSAIC, + .data_msb = ISIF_BIT_MSB_11, + .config_params = { + .data_shift = ISIF_NO_SHIFT, + .col_pat_field0 = { + .olop = ISIF_GREEN_BLUE, + .olep = ISIF_BLUE, + .elop = ISIF_RED, + .elep = ISIF_GREEN_RED, + }, + .col_pat_field1 = { + .olop = ISIF_GREEN_BLUE, + .olep = ISIF_BLUE, + .elop = ISIF_RED, + .elep = ISIF_GREEN_RED, + }, + .test_pat_gen = 0, + }, + }, + .data_pack = ISIF_DATA_PACK8, +}; + +/* Raw Bayer formats */ +static const u32 isif_raw_bayer_pix_formats[] = { + V4L2_PIX_FMT_SBGGR8, V4L2_PIX_FMT_SBGGR16}; + +/* Raw YUV formats */ +static const u32 isif_raw_yuv_pix_formats[] = { + V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_YUYV}; + +/* register access routines */ +static inline u32 regr(u32 offset) +{ + return __raw_readl(isif_cfg.base_addr + offset); +} + +static inline void regw(u32 val, u32 offset) +{ + __raw_writel(val, isif_cfg.base_addr + offset); +} + +/* reg_modify() - read, modify and write register */ +static inline u32 reg_modify(u32 mask, u32 val, u32 offset) +{ + u32 new_val = (regr(offset) & ~mask) | (val & mask); + + regw(new_val, offset); + return new_val; +} + +static inline void regw_lin_tbl(u32 val, u32 offset, int i) +{ + if (!i) + __raw_writel(val, isif_cfg.linear_tbl0_addr + offset); + else + __raw_writel(val, isif_cfg.linear_tbl1_addr + offset); +} + +static void isif_disable_all_modules(void) +{ + /* disable BC */ + regw(0, CLAMPCFG); + /* disable vdfc */ + regw(0, DFCCTL); + /* disable CSC */ + regw(0, CSCCTL); + /* disable linearization */ + regw(0, LINCFG0); + /* disable other modules here as they are supported */ +} + +static void isif_enable(int en) +{ + if (!en) { + /* Before disable isif, disable all ISIF modules */ + isif_disable_all_modules(); + /* + * wait for next VD. Assume lowest scan rate is 12 Hz. So + * 100 msec delay is good enough + */ + msleep(100); + } + reg_modify(ISIF_SYNCEN_VDHDEN_MASK, en, SYNCEN); +} + +static void isif_enable_output_to_sdram(int en) +{ + reg_modify(ISIF_SYNCEN_WEN_MASK, en << ISIF_SYNCEN_WEN_SHIFT, SYNCEN); +} + +static void isif_config_culling(struct isif_cul *cul) +{ + u32 val; + + /* Horizontal pattern */ + val = (cul->hcpat_even << CULL_PAT_EVEN_LINE_SHIFT) | cul->hcpat_odd; + regw(val, CULH); + + /* vertical pattern */ + regw(cul->vcpat, CULV); + + /* LPF */ + reg_modify(ISIF_LPF_MASK << ISIF_LPF_SHIFT, + cul->en_lpf << ISIF_LPF_SHIFT, MODESET); +} + +static void isif_config_gain_offset(void) +{ + struct isif_gain_offsets_adj *gain_off_p = + &isif_cfg.bayer.config_params.gain_offset; + u32 val; + + val = (!!gain_off_p->gain_sdram_en << GAIN_SDRAM_EN_SHIFT) | + (!!gain_off_p->gain_ipipe_en << GAIN_IPIPE_EN_SHIFT) | + (!!gain_off_p->gain_h3a_en << GAIN_H3A_EN_SHIFT) | + (!!gain_off_p->offset_sdram_en << OFST_SDRAM_EN_SHIFT) | + (!!gain_off_p->offset_ipipe_en << OFST_IPIPE_EN_SHIFT) | + (!!gain_off_p->offset_h3a_en << OFST_H3A_EN_SHIFT); + + reg_modify(GAIN_OFFSET_EN_MASK, val, CGAMMAWD); + + val = (gain_off_p->gain.r_ye.integer << GAIN_INTEGER_SHIFT) | + gain_off_p->gain.r_ye.decimal; + regw(val, CRGAIN); + + val = (gain_off_p->gain.gr_cy.integer << GAIN_INTEGER_SHIFT) | + gain_off_p->gain.gr_cy.decimal; + regw(val, CGRGAIN); + + val = (gain_off_p->gain.gb_g.integer << GAIN_INTEGER_SHIFT) | + gain_off_p->gain.gb_g.decimal; + regw(val, CGBGAIN); + + val = (gain_off_p->gain.b_mg.integer << GAIN_INTEGER_SHIFT) | + gain_off_p->gain.b_mg.decimal; + regw(val, CBGAIN); + + regw(gain_off_p->offset, COFSTA); +} + +static void isif_restore_defaults(void) +{ + enum vpss_ccdc_source_sel source = VPSS_CCDCIN; + + dev_dbg(isif_cfg.dev, "\nstarting isif_restore_defaults..."); + isif_cfg.bayer.config_params = isif_config_defaults; + /* Enable clock to ISIF, IPIPEIF and BL */ + vpss_enable_clock(VPSS_CCDC_CLOCK, 1); + vpss_enable_clock(VPSS_IPIPEIF_CLOCK, 1); + vpss_enable_clock(VPSS_BL_CLOCK, 1); + /* Set default offset and gain */ + isif_config_gain_offset(); + vpss_select_ccdc_source(source); + dev_dbg(isif_cfg.dev, "\nEnd of isif_restore_defaults..."); +} + +static int isif_open(struct device *device) +{ + isif_restore_defaults(); + return 0; +} + +/* This function will configure the window size to be capture in ISIF reg */ +static void isif_setwin(struct v4l2_rect *image_win, + enum ccdc_frmfmt frm_fmt, int ppc) +{ + int horz_start, horz_nr_pixels; + int vert_start, vert_nr_lines; + int mid_img = 0; + + dev_dbg(isif_cfg.dev, "\nStarting isif_setwin..."); + /* + * ppc - per pixel count. indicates how many pixels per cell + * output to SDRAM. example, for ycbcr, it is one y and one c, so 2. + * raw capture this is 1 + */ + horz_start = image_win->left << (ppc - 1); + horz_nr_pixels = ((image_win->width) << (ppc - 1)) - 1; + + /* Writing the horizontal info into the registers */ + regw(horz_start & START_PX_HOR_MASK, SPH); + regw(horz_nr_pixels & NUM_PX_HOR_MASK, LNH); + vert_start = image_win->top; + + if (frm_fmt == CCDC_FRMFMT_INTERLACED) { + vert_nr_lines = (image_win->height >> 1) - 1; + vert_start >>= 1; + /* To account for VD since line 0 doesn't have any data */ + vert_start += 1; + } else { + /* To account for VD since line 0 doesn't have any data */ + vert_start += 1; + vert_nr_lines = image_win->height - 1; + /* configure VDINT0 and VDINT1 */ + mid_img = vert_start + (image_win->height / 2); + regw(mid_img, VDINT1); + } + + regw(0, VDINT0); + regw(vert_start & START_VER_ONE_MASK, SLV0); + regw(vert_start & START_VER_TWO_MASK, SLV1); + regw(vert_nr_lines & NUM_LINES_VER, LNV); +} + +static void isif_config_bclamp(struct isif_black_clamp *bc) +{ + u32 val; + + /* + * DC Offset is always added to image data irrespective of bc enable + * status + */ + regw(bc->dc_offset, CLDCOFST); + + if (bc->en) { + val = bc->bc_mode_color << ISIF_BC_MODE_COLOR_SHIFT; + + /* Enable BC and horizontal clamp caculation paramaters */ + val = val | 1 | (bc->horz.mode << ISIF_HORZ_BC_MODE_SHIFT); + + regw(val, CLAMPCFG); + + if (bc->horz.mode != ISIF_HORZ_BC_DISABLE) { + /* + * Window count for calculation + * Base window selection + * pixel limit + * Horizontal size of window + * vertical size of the window + * Horizontal start position of the window + * Vertical start position of the window + */ + val = bc->horz.win_count_calc | + ((!!bc->horz.base_win_sel_calc) << + ISIF_HORZ_BC_WIN_SEL_SHIFT) | + ((!!bc->horz.clamp_pix_limit) << + ISIF_HORZ_BC_PIX_LIMIT_SHIFT) | + (bc->horz.win_h_sz_calc << + ISIF_HORZ_BC_WIN_H_SIZE_SHIFT) | + (bc->horz.win_v_sz_calc << + ISIF_HORZ_BC_WIN_V_SIZE_SHIFT); + regw(val, CLHWIN0); + + regw(bc->horz.win_start_h_calc, CLHWIN1); + regw(bc->horz.win_start_v_calc, CLHWIN2); + } + + /* vertical clamp caculation paramaters */ + + /* Reset clamp value sel for previous line */ + val |= + (bc->vert.reset_val_sel << ISIF_VERT_BC_RST_VAL_SEL_SHIFT) | + (bc->vert.line_ave_coef << ISIF_VERT_BC_LINE_AVE_COEF_SHIFT); + regw(val, CLVWIN0); + + /* Optical Black horizontal start position */ + regw(bc->vert.ob_start_h, CLVWIN1); + /* Optical Black vertical start position */ + regw(bc->vert.ob_start_v, CLVWIN2); + /* Optical Black vertical size for calculation */ + regw(bc->vert.ob_v_sz_calc, CLVWIN3); + /* Vertical start position for BC subtraction */ + regw(bc->vert_start_sub, CLSV); + } +} + +static void isif_config_linearization(struct isif_linearize *linearize) +{ + u32 val, i; + + if (!linearize->en) { + regw(0, LINCFG0); + return; + } + + /* shift value for correction & enable linearization (set lsb) */ + val = (linearize->corr_shft << ISIF_LIN_CORRSFT_SHIFT) | 1; + regw(val, LINCFG0); + + /* Scale factor */ + val = ((!!linearize->scale_fact.integer) << + ISIF_LIN_SCALE_FACT_INTEG_SHIFT) | + linearize->scale_fact.decimal; + regw(val, LINCFG1); + + for (i = 0; i < ISIF_LINEAR_TAB_SIZE; i++) { + if (i % 2) + regw_lin_tbl(linearize->table[i], ((i >> 1) << 2), 1); + else + regw_lin_tbl(linearize->table[i], ((i >> 1) << 2), 0); + } +} + +static int isif_config_dfc(struct isif_dfc *vdfc) +{ + /* initialize retries to loop for max ~ 250 usec */ + u32 val, count, retries = loops_per_jiffy / (4000/HZ); + int i; + + if (!vdfc->en) + return 0; + + /* Correction mode */ + val = (vdfc->corr_mode << ISIF_VDFC_CORR_MOD_SHIFT); + + /* Correct whole line or partial */ + if (vdfc->corr_whole_line) + val |= 1 << ISIF_VDFC_CORR_WHOLE_LN_SHIFT; + + /* level shift value */ + val |= vdfc->def_level_shift << ISIF_VDFC_LEVEL_SHFT_SHIFT; + + regw(val, DFCCTL); + + /* Defect saturation level */ + regw(vdfc->def_sat_level, VDFSATLV); + + regw(vdfc->table[0].pos_vert, DFCMEM0); + regw(vdfc->table[0].pos_horz, DFCMEM1); + if (vdfc->corr_mode == ISIF_VDFC_NORMAL || + vdfc->corr_mode == ISIF_VDFC_HORZ_INTERPOL_IF_SAT) { + regw(vdfc->table[0].level_at_pos, DFCMEM2); + regw(vdfc->table[0].level_up_pixels, DFCMEM3); + regw(vdfc->table[0].level_low_pixels, DFCMEM4); + } + + /* set DFCMARST and set DFCMWR */ + val = regr(DFCMEMCTL) | (1 << ISIF_DFCMEMCTL_DFCMARST_SHIFT) | 1; + regw(val, DFCMEMCTL); + + count = retries; + while (count && (regr(DFCMEMCTL) & 0x1)) + count--; + + if (!count) { + dev_dbg(isif_cfg.dev, "defect table write timeout !!!\n"); + return -1; + } + + for (i = 1; i < vdfc->num_vdefects; i++) { + regw(vdfc->table[i].pos_vert, DFCMEM0); + regw(vdfc->table[i].pos_horz, DFCMEM1); + if (vdfc->corr_mode == ISIF_VDFC_NORMAL || + vdfc->corr_mode == ISIF_VDFC_HORZ_INTERPOL_IF_SAT) { + regw(vdfc->table[i].level_at_pos, DFCMEM2); + regw(vdfc->table[i].level_up_pixels, DFCMEM3); + regw(vdfc->table[i].level_low_pixels, DFCMEM4); + } + val = regr(DFCMEMCTL); + /* clear DFCMARST and set DFCMWR */ + val &= ~BIT(ISIF_DFCMEMCTL_DFCMARST_SHIFT); + val |= 1; + regw(val, DFCMEMCTL); + + count = retries; + while (count && (regr(DFCMEMCTL) & 0x1)) + count--; + + if (!count) { + dev_err(isif_cfg.dev, + "defect table write timeout !!!\n"); + return -1; + } + } + if (vdfc->num_vdefects < ISIF_VDFC_TABLE_SIZE) { + /* Extra cycle needed */ + regw(0, DFCMEM0); + regw(0x1FFF, DFCMEM1); + regw(1, DFCMEMCTL); + } + + /* enable VDFC */ + reg_modify((1 << ISIF_VDFC_EN_SHIFT), (1 << ISIF_VDFC_EN_SHIFT), + DFCCTL); + return 0; +} + +static void isif_config_csc(struct isif_df_csc *df_csc) +{ + u32 val1 = 0, val2 = 0, i; + + if (!df_csc->csc.en) { + regw(0, CSCCTL); + return; + } + for (i = 0; i < ISIF_CSC_NUM_COEFF; i++) { + if ((i % 2) == 0) { + /* CSCM - LSB */ + val1 = (df_csc->csc.coeff[i].integer << + ISIF_CSC_COEF_INTEG_SHIFT) | + df_csc->csc.coeff[i].decimal; + } else { + + /* CSCM - MSB */ + val2 = (df_csc->csc.coeff[i].integer << + ISIF_CSC_COEF_INTEG_SHIFT) | + df_csc->csc.coeff[i].decimal; + val2 <<= ISIF_CSCM_MSB_SHIFT; + val2 |= val1; + regw(val2, (CSCM0 + ((i - 1) << 1))); + } + } + + /* program the active area */ + regw(df_csc->start_pix, FMTSPH); + /* + * one extra pixel as required for CSC. Actually number of + * pixel - 1 should be configured in this register. So we + * need to subtract 1 before writing to FMTSPH, but we will + * not do this since csc requires one extra pixel + */ + regw(df_csc->num_pixels, FMTLNH); + regw(df_csc->start_line, FMTSLV); + /* + * one extra line as required for CSC. See reason documented for + * num_pixels + */ + regw(df_csc->num_lines, FMTLNV); + + /* Enable CSC */ + regw(1, CSCCTL); +} + +static int isif_config_raw(void) +{ + struct isif_params_raw *params = &isif_cfg.bayer; + struct isif_config_params_raw *module_params = + &isif_cfg.bayer.config_params; + struct vpss_pg_frame_size frame_size; + struct vpss_sync_pol sync; + u32 val; + + dev_dbg(isif_cfg.dev, "\nStarting isif_config_raw..\n"); + + /* + * Configure CCDCFG register:- + * Set CCD Not to swap input since input is RAW data + * Set FID detection function to Latch at V-Sync + * Set WENLOG - isif valid area + * Set TRGSEL + * Set EXTRG + * Packed to 8 or 16 bits + */ + + val = ISIF_YCINSWP_RAW | ISIF_CCDCFG_FIDMD_LATCH_VSYNC | + ISIF_CCDCFG_WENLOG_AND | ISIF_CCDCFG_TRGSEL_WEN | + ISIF_CCDCFG_EXTRG_DISABLE | isif_cfg.data_pack; + + dev_dbg(isif_cfg.dev, "Writing 0x%x to ...CCDCFG \n", val); + regw(val, CCDCFG); + + /* + * Configure the vertical sync polarity(MODESET.VDPOL) + * Configure the horizontal sync polarity (MODESET.HDPOL) + * Configure frame id polarity (MODESET.FLDPOL) + * Configure data polarity + * Configure External WEN Selection + * Configure frame format(progressive or interlace) + * Configure pixel format (Input mode) + * Configure the data shift + */ + + val = ISIF_VDHDOUT_INPUT | (params->vd_pol << ISIF_VD_POL_SHIFT) | + (params->hd_pol << ISIF_HD_POL_SHIFT) | + (params->fid_pol << ISIF_FID_POL_SHIFT) | + (ISIF_DATAPOL_NORMAL << ISIF_DATAPOL_SHIFT) | + (ISIF_EXWEN_DISABLE << ISIF_EXWEN_SHIFT) | + (params->frm_fmt << ISIF_FRM_FMT_SHIFT) | + (params->pix_fmt << ISIF_INPUT_SHIFT) | + (params->config_params.data_shift << ISIF_DATASFT_SHIFT); + + regw(val, MODESET); + dev_dbg(isif_cfg.dev, "Writing 0x%x to MODESET...\n", val); + + /* + * Configure GAMMAWD register + * CFA pattern setting + */ + val = params->cfa_pat << ISIF_GAMMAWD_CFA_SHIFT; + + /* Gamma msb */ + if (module_params->compress.alg == ISIF_ALAW) + val |= ISIF_ALAW_ENABLE; + + val |= (params->data_msb << ISIF_ALAW_GAMA_WD_SHIFT); + regw(val, CGAMMAWD); + + /* Configure DPCM compression settings */ + if (module_params->compress.alg == ISIF_DPCM) { + val = BIT(ISIF_DPCM_EN_SHIFT) | + (module_params->compress.pred << + ISIF_DPCM_PREDICTOR_SHIFT); + } + + regw(val, MISC); + + /* Configure Gain & Offset */ + isif_config_gain_offset(); + + /* Configure Color pattern */ + val = (params->config_params.col_pat_field0.olop) | + (params->config_params.col_pat_field0.olep << 2) | + (params->config_params.col_pat_field0.elop << 4) | + (params->config_params.col_pat_field0.elep << 6) | + (params->config_params.col_pat_field1.olop << 8) | + (params->config_params.col_pat_field1.olep << 10) | + (params->config_params.col_pat_field1.elop << 12) | + (params->config_params.col_pat_field1.elep << 14); + regw(val, CCOLP); + dev_dbg(isif_cfg.dev, "Writing %x to CCOLP ...\n", val); + + /* Configure HSIZE register */ + val = (!!params->horz_flip_en) << ISIF_HSIZE_FLIP_SHIFT; + + /* calculate line offset in 32 bytes based on pack value */ + if (isif_cfg.data_pack == ISIF_PACK_8BIT) + val |= ((params->win.width + 31) >> 5); + else if (isif_cfg.data_pack == ISIF_PACK_12BIT) + val |= (((params->win.width + + (params->win.width >> 2)) + 31) >> 5); + else + val |= (((params->win.width * 2) + 31) >> 5); + regw(val, HSIZE); + + /* Configure SDOFST register */ + if (params->frm_fmt == CCDC_FRMFMT_INTERLACED) { + if (params->image_invert_en) { + /* For interlace inverse mode */ + regw(0x4B6D, SDOFST); + dev_dbg(isif_cfg.dev, "Writing 0x4B6D to SDOFST...\n"); + } else { + /* For interlace non inverse mode */ + regw(0x0B6D, SDOFST); + dev_dbg(isif_cfg.dev, "Writing 0x0B6D to SDOFST...\n"); + } + } else if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { + if (params->image_invert_en) { + /* For progressive inverse mode */ + regw(0x4000, SDOFST); + dev_dbg(isif_cfg.dev, "Writing 0x4000 to SDOFST...\n"); + } else { + /* For progressive non inverse mode */ + regw(0x0000, SDOFST); + dev_dbg(isif_cfg.dev, "Writing 0x0000 to SDOFST...\n"); + } + } + + /* Configure video window */ + isif_setwin(¶ms->win, params->frm_fmt, 1); + + /* Configure Black Clamp */ + isif_config_bclamp(&module_params->bclamp); + + /* Configure Vertical Defection Pixel Correction */ + if (isif_config_dfc(&module_params->dfc) < 0) + return -EFAULT; + + if (!module_params->df_csc.df_or_csc) + /* Configure Color Space Conversion */ + isif_config_csc(&module_params->df_csc); + + isif_config_linearization(&module_params->linearize); + + /* Configure Culling */ + isif_config_culling(&module_params->culling); + + /* Configure horizontal and vertical offsets(DFC,LSC,Gain) */ + regw(module_params->horz_offset, DATAHOFST); + regw(module_params->vert_offset, DATAVOFST); + + /* Setup test pattern if enabled */ + if (params->config_params.test_pat_gen) { + /* Use the HD/VD pol settings from user */ + sync.ccdpg_hdpol = params->hd_pol; + sync.ccdpg_vdpol = params->vd_pol; + dm365_vpss_set_sync_pol(sync); + frame_size.hlpfr = isif_cfg.bayer.win.width; + frame_size.pplen = isif_cfg.bayer.win.height; + dm365_vpss_set_pg_frame_size(frame_size); + vpss_select_ccdc_source(VPSS_PGLPBK); + } + + dev_dbg(isif_cfg.dev, "\nEnd of isif_config_ycbcr...\n"); + return 0; +} + +static int isif_set_buftype(enum ccdc_buftype buf_type) +{ + if (isif_cfg.if_type == VPFE_RAW_BAYER) + isif_cfg.bayer.buf_type = buf_type; + else + isif_cfg.ycbcr.buf_type = buf_type; + + return 0; + +} +static enum ccdc_buftype isif_get_buftype(void) +{ + if (isif_cfg.if_type == VPFE_RAW_BAYER) + return isif_cfg.bayer.buf_type; + + return isif_cfg.ycbcr.buf_type; +} + +static int isif_enum_pix(u32 *pix, int i) +{ + int ret = -EINVAL; + + if (isif_cfg.if_type == VPFE_RAW_BAYER) { + if (i < ARRAY_SIZE(isif_raw_bayer_pix_formats)) { + *pix = isif_raw_bayer_pix_formats[i]; + ret = 0; + } + } else { + if (i < ARRAY_SIZE(isif_raw_yuv_pix_formats)) { + *pix = isif_raw_yuv_pix_formats[i]; + ret = 0; + } + } + + return ret; +} + +static int isif_set_pixel_format(unsigned int pixfmt) +{ + if (isif_cfg.if_type == VPFE_RAW_BAYER) { + if (pixfmt == V4L2_PIX_FMT_SBGGR8) { + if ((isif_cfg.bayer.config_params.compress.alg != + ISIF_ALAW) && + (isif_cfg.bayer.config_params.compress.alg != + ISIF_DPCM)) { + dev_dbg(isif_cfg.dev, + "Either configure A-Law or DPCM\n"); + return -EINVAL; + } + isif_cfg.data_pack = ISIF_PACK_8BIT; + } else if (pixfmt == V4L2_PIX_FMT_SBGGR16) { + isif_cfg.bayer.config_params.compress.alg = + ISIF_NO_COMPRESSION; + isif_cfg.data_pack = ISIF_PACK_16BIT; + } else + return -EINVAL; + isif_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; + } else { + if (pixfmt == V4L2_PIX_FMT_YUYV) + isif_cfg.ycbcr.pix_order = CCDC_PIXORDER_YCBYCR; + else if (pixfmt == V4L2_PIX_FMT_UYVY) + isif_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; + else + return -EINVAL; + isif_cfg.data_pack = ISIF_PACK_8BIT; + } + return 0; +} + +static u32 isif_get_pixel_format(void) +{ + u32 pixfmt; + + if (isif_cfg.if_type == VPFE_RAW_BAYER) + if (isif_cfg.bayer.config_params.compress.alg == ISIF_ALAW || + isif_cfg.bayer.config_params.compress.alg == ISIF_DPCM) + pixfmt = V4L2_PIX_FMT_SBGGR8; + else + pixfmt = V4L2_PIX_FMT_SBGGR16; + else { + if (isif_cfg.ycbcr.pix_order == CCDC_PIXORDER_YCBYCR) + pixfmt = V4L2_PIX_FMT_YUYV; + else + pixfmt = V4L2_PIX_FMT_UYVY; + } + return pixfmt; +} + +static int isif_set_image_window(struct v4l2_rect *win) +{ + if (isif_cfg.if_type == VPFE_RAW_BAYER) { + isif_cfg.bayer.win.top = win->top; + isif_cfg.bayer.win.left = win->left; + isif_cfg.bayer.win.width = win->width; + isif_cfg.bayer.win.height = win->height; + } else { + isif_cfg.ycbcr.win.top = win->top; + isif_cfg.ycbcr.win.left = win->left; + isif_cfg.ycbcr.win.width = win->width; + isif_cfg.ycbcr.win.height = win->height; + } + return 0; +} + +static void isif_get_image_window(struct v4l2_rect *win) +{ + if (isif_cfg.if_type == VPFE_RAW_BAYER) + *win = isif_cfg.bayer.win; + else + *win = isif_cfg.ycbcr.win; +} + +static unsigned int isif_get_line_length(void) +{ + unsigned int len; + + if (isif_cfg.if_type == VPFE_RAW_BAYER) { + if (isif_cfg.data_pack == ISIF_PACK_8BIT) + len = ((isif_cfg.bayer.win.width)); + else if (isif_cfg.data_pack == ISIF_PACK_12BIT) + len = (((isif_cfg.bayer.win.width * 2) + + (isif_cfg.bayer.win.width >> 2))); + else + len = (((isif_cfg.bayer.win.width * 2))); + } else + len = (((isif_cfg.ycbcr.win.width * 2))); + return ALIGN(len, 32); +} + +static int isif_set_frame_format(enum ccdc_frmfmt frm_fmt) +{ + if (isif_cfg.if_type == VPFE_RAW_BAYER) + isif_cfg.bayer.frm_fmt = frm_fmt; + else + isif_cfg.ycbcr.frm_fmt = frm_fmt; + return 0; +} +static enum ccdc_frmfmt isif_get_frame_format(void) +{ + if (isif_cfg.if_type == VPFE_RAW_BAYER) + return isif_cfg.bayer.frm_fmt; + return isif_cfg.ycbcr.frm_fmt; +} + +static int isif_getfid(void) +{ + return (regr(MODESET) >> 15) & 0x1; +} + +/* misc operations */ +static void isif_setfbaddr(unsigned long addr) +{ + regw((addr >> 21) & 0x07ff, CADU); + regw((addr >> 5) & 0x0ffff, CADL); +} + +static int isif_set_hw_if_params(struct vpfe_hw_if_param *params) +{ + isif_cfg.if_type = params->if_type; + + switch (params->if_type) { + case VPFE_BT656: + case VPFE_BT656_10BIT: + case VPFE_YCBCR_SYNC_8: + isif_cfg.ycbcr.pix_fmt = CCDC_PIXFMT_YCBCR_8BIT; + isif_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; + break; + case VPFE_BT1120: + case VPFE_YCBCR_SYNC_16: + isif_cfg.ycbcr.pix_fmt = CCDC_PIXFMT_YCBCR_16BIT; + isif_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; + break; + case VPFE_RAW_BAYER: + isif_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; + break; + default: + dev_dbg(isif_cfg.dev, "Invalid interface type\n"); + return -EINVAL; + } + + return 0; +} + +/* This function will configure ISIF for YCbCr parameters. */ +static int isif_config_ycbcr(void) +{ + struct isif_ycbcr_config *params = &isif_cfg.ycbcr; + struct vpss_pg_frame_size frame_size; + u32 modeset = 0, ccdcfg = 0; + struct vpss_sync_pol sync; + + dev_dbg(isif_cfg.dev, "\nStarting isif_config_ycbcr..."); + + /* configure pixel format or input mode */ + modeset = modeset | (params->pix_fmt << ISIF_INPUT_SHIFT) | + (params->frm_fmt << ISIF_FRM_FMT_SHIFT) | + (params->fid_pol << ISIF_FID_POL_SHIFT) | + (params->hd_pol << ISIF_HD_POL_SHIFT) | + (params->vd_pol << ISIF_VD_POL_SHIFT); + + /* pack the data to 8-bit ISIFCFG */ + switch (isif_cfg.if_type) { + case VPFE_BT656: + if (params->pix_fmt != CCDC_PIXFMT_YCBCR_8BIT) { + dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + modeset |= (VPFE_PINPOL_NEGATIVE << ISIF_VD_POL_SHIFT); + regw(3, REC656IF); + ccdcfg = ccdcfg | ISIF_DATA_PACK8 | ISIF_YCINSWP_YCBCR; + break; + case VPFE_BT656_10BIT: + if (params->pix_fmt != CCDC_PIXFMT_YCBCR_8BIT) { + dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + /* setup BT.656, embedded sync */ + regw(3, REC656IF); + /* enable 10 bit mode in ccdcfg */ + ccdcfg = ccdcfg | ISIF_DATA_PACK8 | ISIF_YCINSWP_YCBCR | + ISIF_BW656_ENABLE; + break; + case VPFE_BT1120: + if (params->pix_fmt != CCDC_PIXFMT_YCBCR_16BIT) { + dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + regw(3, REC656IF); + break; + + case VPFE_YCBCR_SYNC_8: + ccdcfg |= ISIF_DATA_PACK8; + ccdcfg |= ISIF_YCINSWP_YCBCR; + if (params->pix_fmt != CCDC_PIXFMT_YCBCR_8BIT) { + dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + break; + case VPFE_YCBCR_SYNC_16: + if (params->pix_fmt != CCDC_PIXFMT_YCBCR_16BIT) { + dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + break; + default: + /* should never come here */ + dev_dbg(isif_cfg.dev, "Invalid interface type\n"); + return -EINVAL; + } + + regw(modeset, MODESET); + + /* Set up pix order */ + ccdcfg |= params->pix_order << ISIF_PIX_ORDER_SHIFT; + + regw(ccdcfg, CCDCFG); + + /* configure video window */ + if ((isif_cfg.if_type == VPFE_BT1120) || + (isif_cfg.if_type == VPFE_YCBCR_SYNC_16)) + isif_setwin(¶ms->win, params->frm_fmt, 1); + else + isif_setwin(¶ms->win, params->frm_fmt, 2); + + /* + * configure the horizontal line offset + * this is done by rounding up width to a multiple of 16 pixels + * and multiply by two to account for y:cb:cr 4:2:2 data + */ + regw(((((params->win.width * 2) + 31) & 0xffffffe0) >> 5), HSIZE); + + /* configure the memory line offset */ + if ((params->frm_fmt == CCDC_FRMFMT_INTERLACED) && + (params->buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED)) + /* two fields are interleaved in memory */ + regw(0x00000249, SDOFST); + + /* Setup test pattern if enabled */ + if (isif_cfg.bayer.config_params.test_pat_gen) { + sync.ccdpg_hdpol = params->hd_pol; + sync.ccdpg_vdpol = params->vd_pol; + dm365_vpss_set_sync_pol(sync); + dm365_vpss_set_pg_frame_size(frame_size); + } + return 0; +} + +static int isif_configure(void) +{ + if (isif_cfg.if_type == VPFE_RAW_BAYER) + return isif_config_raw(); + return isif_config_ycbcr(); +} + +static int isif_close(struct device *device) +{ + /* copy defaults to module params */ + isif_cfg.bayer.config_params = isif_config_defaults; + return 0; +} + +static struct ccdc_hw_device isif_hw_dev = { + .name = "ISIF", + .owner = THIS_MODULE, + .hw_ops = { + .open = isif_open, + .close = isif_close, + .enable = isif_enable, + .enable_out_to_sdram = isif_enable_output_to_sdram, + .set_hw_if_params = isif_set_hw_if_params, + .configure = isif_configure, + .set_buftype = isif_set_buftype, + .get_buftype = isif_get_buftype, + .enum_pix = isif_enum_pix, + .set_pixel_format = isif_set_pixel_format, + .get_pixel_format = isif_get_pixel_format, + .set_frame_format = isif_set_frame_format, + .get_frame_format = isif_get_frame_format, + .set_image_window = isif_set_image_window, + .get_image_window = isif_get_image_window, + .get_line_length = isif_get_line_length, + .setfbaddr = isif_setfbaddr, + .getfid = isif_getfid, + }, +}; + +static int __devinit isif_probe(struct platform_device *pdev) +{ + void (*setup_pinmux)(void); + struct resource *res; + void *__iomem addr; + int status = 0, i; + + /* + * first try to register with vpfe. If not correct platform, then we + * don't have to iomap + */ + status = vpfe_register_ccdc_device(&isif_hw_dev); + if (status < 0) + return status; + + /* Get and enable Master clock */ + isif_cfg.mclk = clk_get(&pdev->dev, "master"); + if (IS_ERR(isif_cfg.mclk)) { + status = PTR_ERR(isif_cfg.mclk); + goto fail_mclk; + } + if (clk_enable(isif_cfg.mclk)) { + status = -ENODEV; + goto fail_mclk; + } + + /* Platform data holds setup_pinmux function ptr */ + if (NULL == pdev->dev.platform_data) { + status = -ENODEV; + goto fail_mclk; + } + setup_pinmux = pdev->dev.platform_data; + /* + * setup Mux configuration for ccdc which may be different for + * different SoCs using this CCDC + */ + setup_pinmux(); + + i = 0; + /* Get the ISIF base address, linearization table0 and table1 addr. */ + while (i < 3) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) { + status = -ENODEV; + goto fail_nobase_res; + } + res = request_mem_region(res->start, resource_size(res), + res->name); + if (!res) { + status = -EBUSY; + goto fail_nobase_res; + } + addr = ioremap_nocache(res->start, resource_size(res)); + if (!addr) { + status = -ENOMEM; + goto fail_base_iomap; + } + switch (i) { + case 0: + /* ISIF base address */ + isif_cfg.base_addr = addr; + break; + case 1: + /* ISIF linear tbl0 address */ + isif_cfg.linear_tbl0_addr = addr; + break; + default: + /* ISIF linear tbl0 address */ + isif_cfg.linear_tbl1_addr = addr; + break; + } + i++; + } + isif_cfg.dev = &pdev->dev; + + printk(KERN_NOTICE "%s is registered with vpfe.\n", + isif_hw_dev.name); + return 0; +fail_base_iomap: + release_mem_region(res->start, resource_size(res)); + i--; +fail_nobase_res: + if (isif_cfg.base_addr) + iounmap(isif_cfg.base_addr); + if (isif_cfg.linear_tbl0_addr) + iounmap(isif_cfg.linear_tbl0_addr); + + while (i >= 0) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + release_mem_region(res->start, resource_size(res)); + i--; + } +fail_mclk: + clk_put(isif_cfg.mclk); + vpfe_unregister_ccdc_device(&isif_hw_dev); + return status; +} + +static int isif_remove(struct platform_device *pdev) +{ + struct resource *res; + int i = 0; + + iounmap(isif_cfg.base_addr); + iounmap(isif_cfg.linear_tbl0_addr); + iounmap(isif_cfg.linear_tbl1_addr); + while (i < 3) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (res) + release_mem_region(res->start, resource_size(res)); + i++; + } + vpfe_unregister_ccdc_device(&isif_hw_dev); + return 0; +} + +static struct platform_driver isif_driver = { + .driver = { + .name = "isif", + .owner = THIS_MODULE, + }, + .remove = __devexit_p(isif_remove), + .probe = isif_probe, +}; + +module_platform_driver(isif_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/davinci/isif_regs.h b/drivers/media/platform/davinci/isif_regs.h similarity index 100% rename from drivers/media/video/davinci/isif_regs.h rename to drivers/media/platform/davinci/isif_regs.h diff --git a/drivers/media/video/davinci/vpbe.c b/drivers/media/platform/davinci/vpbe.c similarity index 100% rename from drivers/media/video/davinci/vpbe.c rename to drivers/media/platform/davinci/vpbe.c diff --git a/drivers/media/platform/davinci/vpbe_display.c b/drivers/media/platform/davinci/vpbe_display.c new file mode 100644 index 000000000000..239f37bfa313 --- /dev/null +++ b/drivers/media/platform/davinci/vpbe_display.c @@ -0,0 +1,1839 @@ +/* + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpbe_venc_regs.h" + +#define VPBE_DISPLAY_DRIVER "vpbe-v4l2" + +static int debug; + +#define VPBE_DEFAULT_NUM_BUFS 3 + +module_param(debug, int, 0644); + +static int venc_is_second_field(struct vpbe_display *disp_dev) +{ + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + int ret; + int val; + + ret = v4l2_subdev_call(vpbe_dev->venc, + core, + ioctl, + VENC_GET_FLD, + &val); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in getting Field ID 0\n"); + } + return val; +} + +static void vpbe_isr_even_field(struct vpbe_display *disp_obj, + struct vpbe_layer *layer) +{ + struct timespec timevalue; + + if (layer->cur_frm == layer->next_frm) + return; + ktime_get_ts(&timevalue); + layer->cur_frm->ts.tv_sec = timevalue.tv_sec; + layer->cur_frm->ts.tv_usec = timevalue.tv_nsec / NSEC_PER_USEC; + layer->cur_frm->state = VIDEOBUF_DONE; + wake_up_interruptible(&layer->cur_frm->done); + /* Make cur_frm pointing to next_frm */ + layer->cur_frm = layer->next_frm; +} + +static void vpbe_isr_odd_field(struct vpbe_display *disp_obj, + struct vpbe_layer *layer) +{ + struct osd_state *osd_device = disp_obj->osd_device; + unsigned long addr; + + spin_lock(&disp_obj->dma_queue_lock); + if (list_empty(&layer->dma_queue) || + (layer->cur_frm != layer->next_frm)) { + spin_unlock(&disp_obj->dma_queue_lock); + return; + } + /* + * one field is displayed configure + * the next frame if it is available + * otherwise hold on current frame + * Get next from the buffer queue + */ + layer->next_frm = list_entry( + layer->dma_queue.next, + struct videobuf_buffer, + queue); + /* Remove that from the buffer queue */ + list_del(&layer->next_frm->queue); + spin_unlock(&disp_obj->dma_queue_lock); + /* Mark state of the frame to active */ + layer->next_frm->state = VIDEOBUF_ACTIVE; + addr = videobuf_to_dma_contig(layer->next_frm); + osd_device->ops.start_layer(osd_device, + layer->layer_info.id, + addr, + disp_obj->cbcr_ofst); +} + +/* interrupt service routine */ +static irqreturn_t venc_isr(int irq, void *arg) +{ + struct vpbe_display *disp_dev = (struct vpbe_display *)arg; + struct vpbe_layer *layer; + static unsigned last_event; + unsigned event = 0; + int fid; + int i; + + if ((NULL == arg) || (NULL == disp_dev->dev[0])) + return IRQ_HANDLED; + + if (venc_is_second_field(disp_dev)) + event |= VENC_SECOND_FIELD; + else + event |= VENC_FIRST_FIELD; + + if (event == (last_event & ~VENC_END_OF_FRAME)) { + /* + * If the display is non-interlaced, then we need to flag the + * end-of-frame event at every interrupt regardless of the + * value of the FIDST bit. We can conclude that the display is + * non-interlaced if the value of the FIDST bit is unchanged + * from the previous interrupt. + */ + event |= VENC_END_OF_FRAME; + } else if (event == VENC_SECOND_FIELD) { + /* end-of-frame for interlaced display */ + event |= VENC_END_OF_FRAME; + } + last_event = event; + + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + layer = disp_dev->dev[i]; + /* If streaming is started in this layer */ + if (!layer->started) + continue; + + if (layer->layer_first_int) { + layer->layer_first_int = 0; + continue; + } + /* Check the field format */ + if ((V4L2_FIELD_NONE == layer->pix_fmt.field) && + (event & VENC_END_OF_FRAME)) { + /* Progressive mode */ + + vpbe_isr_even_field(disp_dev, layer); + vpbe_isr_odd_field(disp_dev, layer); + } else { + /* Interlaced mode */ + + layer->field_id ^= 1; + if (event & VENC_FIRST_FIELD) + fid = 0; + else + fid = 1; + + /* + * If field id does not match with store + * field id + */ + if (fid != layer->field_id) { + /* Make them in sync */ + layer->field_id = fid; + continue; + } + /* + * device field id and local field id are + * in sync. If this is even field + */ + if (0 == fid) + vpbe_isr_even_field(disp_dev, layer); + else /* odd field */ + vpbe_isr_odd_field(disp_dev, layer); + } + } + + return IRQ_HANDLED; +} + +/* + * vpbe_buffer_prepare() + * This is the callback function called from videobuf_qbuf() function + * the buffer is prepared and user space virtual address is converted into + * physical address + */ +static int vpbe_buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct vpbe_fh *fh = q->priv_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + unsigned long addr; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "vpbe_buffer_prepare\n"); + + /* If buffer is not initialized, initialize it */ + if (VIDEOBUF_NEEDS_INIT == vb->state) { + vb->width = layer->pix_fmt.width; + vb->height = layer->pix_fmt.height; + vb->size = layer->pix_fmt.sizeimage; + vb->field = field; + + ret = videobuf_iolock(q, vb, NULL); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, "Failed to map \ + user address\n"); + return -EINVAL; + } + + addr = videobuf_to_dma_contig(vb); + + if (q->streaming) { + if (!IS_ALIGNED(addr, 8)) { + v4l2_err(&vpbe_dev->v4l2_dev, + "buffer_prepare:offset is \ + not aligned to 32 bytes\n"); + return -EINVAL; + } + } + vb->state = VIDEOBUF_PREPARED; + } + return 0; +} + +/* + * vpbe_buffer_setup() + * This function allocates memory for the buffers + */ +static int vpbe_buffer_setup(struct videobuf_queue *q, + unsigned int *count, + unsigned int *size) +{ + /* Get the file handle object and layer object */ + struct vpbe_fh *fh = q->priv_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_buffer_setup\n"); + + *size = layer->pix_fmt.sizeimage; + + /* Store number of buffers allocated in numbuffer member */ + if (*count < VPBE_DEFAULT_NUM_BUFS) + *count = layer->numbuffers = VPBE_DEFAULT_NUM_BUFS; + + return 0; +} + +/* + * vpbe_buffer_queue() + * This function adds the buffer to DMA queue + */ +static void vpbe_buffer_queue(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + /* Get the file handle object and layer object */ + struct vpbe_fh *fh = q->priv_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_display *disp = fh->disp_dev; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + unsigned long flags; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "vpbe_buffer_queue\n"); + + /* add the buffer to the DMA queue */ + spin_lock_irqsave(&disp->dma_queue_lock, flags); + list_add_tail(&vb->queue, &layer->dma_queue); + spin_unlock_irqrestore(&disp->dma_queue_lock, flags); + /* Change state of the buffer */ + vb->state = VIDEOBUF_QUEUED; +} + +/* + * vpbe_buffer_release() + * This function is called from the videobuf layer to free memory allocated to + * the buffers + */ +static void vpbe_buffer_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + /* Get the file handle object and layer object */ + struct vpbe_fh *fh = q->priv_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "vpbe_buffer_release\n"); + + if (V4L2_MEMORY_USERPTR != layer->memory) + videobuf_dma_contig_free(q, vb); + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static struct videobuf_queue_ops video_qops = { + .buf_setup = vpbe_buffer_setup, + .buf_prepare = vpbe_buffer_prepare, + .buf_queue = vpbe_buffer_queue, + .buf_release = vpbe_buffer_release, +}; + +static +struct vpbe_layer* +_vpbe_display_get_other_win_layer(struct vpbe_display *disp_dev, + struct vpbe_layer *layer) +{ + enum vpbe_display_device_id thiswin, otherwin; + thiswin = layer->device_id; + + otherwin = (thiswin == VPBE_DISPLAY_DEVICE_0) ? + VPBE_DISPLAY_DEVICE_1 : VPBE_DISPLAY_DEVICE_0; + return disp_dev->dev[otherwin]; +} + +static int vpbe_set_osd_display_params(struct vpbe_display *disp_dev, + struct vpbe_layer *layer) +{ + struct osd_layer_config *cfg = &layer->layer_info.config; + struct osd_state *osd_device = disp_dev->osd_device; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + unsigned long addr; + int ret; + + addr = videobuf_to_dma_contig(layer->cur_frm); + /* Set address in the display registers */ + osd_device->ops.start_layer(osd_device, + layer->layer_info.id, + addr, + disp_dev->cbcr_ofst); + + ret = osd_device->ops.enable_layer(osd_device, + layer->layer_info.id, 0); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in enabling osd window layer 0\n"); + return -1; + } + + /* Enable the window */ + layer->layer_info.enable = 1; + if (cfg->pixfmt == PIXFMT_NV12) { + struct vpbe_layer *otherlayer = + _vpbe_display_get_other_win_layer(disp_dev, layer); + + ret = osd_device->ops.enable_layer(osd_device, + otherlayer->layer_info.id, 1); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in enabling osd window layer 1\n"); + return -1; + } + otherlayer->layer_info.enable = 1; + } + return 0; +} + +static void +vpbe_disp_calculate_scale_factor(struct vpbe_display *disp_dev, + struct vpbe_layer *layer, + int expected_xsize, int expected_ysize) +{ + struct display_layer_info *layer_info = &layer->layer_info; + struct v4l2_pix_format *pixfmt = &layer->pix_fmt; + struct osd_layer_config *cfg = &layer->layer_info.config; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + int calculated_xsize; + int h_exp = 0; + int v_exp = 0; + int h_scale; + int v_scale; + + v4l2_std_id standard_id = vpbe_dev->current_timings.timings.std_id; + + /* + * Application initially set the image format. Current display + * size is obtained from the vpbe display controller. expected_xsize + * and expected_ysize are set through S_CROP ioctl. Based on this, + * driver will calculate the scale factors for vertical and + * horizontal direction so that the image is displayed scaled + * and expanded. Application uses expansion to display the image + * in a square pixel. Otherwise it is displayed using displays + * pixel aspect ratio.It is expected that application chooses + * the crop coordinates for cropped or scaled display. if crop + * size is less than the image size, it is displayed cropped or + * it is displayed scaled and/or expanded. + * + * to begin with, set the crop window same as expected. Later we + * will override with scaled window size + */ + + cfg->xsize = pixfmt->width; + cfg->ysize = pixfmt->height; + layer_info->h_zoom = ZOOM_X1; /* no horizontal zoom */ + layer_info->v_zoom = ZOOM_X1; /* no horizontal zoom */ + layer_info->h_exp = H_EXP_OFF; /* no horizontal zoom */ + layer_info->v_exp = V_EXP_OFF; /* no horizontal zoom */ + + if (pixfmt->width < expected_xsize) { + h_scale = vpbe_dev->current_timings.xres / pixfmt->width; + if (h_scale < 2) + h_scale = 1; + else if (h_scale >= 4) + h_scale = 4; + else + h_scale = 2; + cfg->xsize *= h_scale; + if (cfg->xsize < expected_xsize) { + if ((standard_id & V4L2_STD_525_60) || + (standard_id & V4L2_STD_625_50)) { + calculated_xsize = (cfg->xsize * + VPBE_DISPLAY_H_EXP_RATIO_N) / + VPBE_DISPLAY_H_EXP_RATIO_D; + if (calculated_xsize <= expected_xsize) { + h_exp = 1; + cfg->xsize = calculated_xsize; + } + } + } + if (h_scale == 2) + layer_info->h_zoom = ZOOM_X2; + else if (h_scale == 4) + layer_info->h_zoom = ZOOM_X4; + if (h_exp) + layer_info->h_exp = H_EXP_9_OVER_8; + } else { + /* no scaling, only cropping. Set display area to crop area */ + cfg->xsize = expected_xsize; + } + + if (pixfmt->height < expected_ysize) { + v_scale = expected_ysize / pixfmt->height; + if (v_scale < 2) + v_scale = 1; + else if (v_scale >= 4) + v_scale = 4; + else + v_scale = 2; + cfg->ysize *= v_scale; + if (cfg->ysize < expected_ysize) { + if ((standard_id & V4L2_STD_625_50)) { + calculated_xsize = (cfg->ysize * + VPBE_DISPLAY_V_EXP_RATIO_N) / + VPBE_DISPLAY_V_EXP_RATIO_D; + if (calculated_xsize <= expected_ysize) { + v_exp = 1; + cfg->ysize = calculated_xsize; + } + } + } + if (v_scale == 2) + layer_info->v_zoom = ZOOM_X2; + else if (v_scale == 4) + layer_info->v_zoom = ZOOM_X4; + if (v_exp) + layer_info->h_exp = V_EXP_6_OVER_5; + } else { + /* no scaling, only cropping. Set display area to crop area */ + cfg->ysize = expected_ysize; + } + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "crop display xsize = %d, ysize = %d\n", + cfg->xsize, cfg->ysize); +} + +static void vpbe_disp_adj_position(struct vpbe_display *disp_dev, + struct vpbe_layer *layer, + int top, int left) +{ + struct osd_layer_config *cfg = &layer->layer_info.config; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + + cfg->xpos = min((unsigned int)left, + vpbe_dev->current_timings.xres - cfg->xsize); + cfg->ypos = min((unsigned int)top, + vpbe_dev->current_timings.yres - cfg->ysize); + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "new xpos = %d, ypos = %d\n", + cfg->xpos, cfg->ypos); +} + +static void vpbe_disp_check_window_params(struct vpbe_display *disp_dev, + struct v4l2_rect *c) +{ + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + + if ((c->width == 0) || + ((c->width + c->left) > vpbe_dev->current_timings.xres)) + c->width = vpbe_dev->current_timings.xres - c->left; + + if ((c->height == 0) || ((c->height + c->top) > + vpbe_dev->current_timings.yres)) + c->height = vpbe_dev->current_timings.yres - c->top; + + /* window height must be even for interlaced display */ + if (vpbe_dev->current_timings.interlaced) + c->height &= (~0x01); + +} + +/** + * vpbe_try_format() + * If user application provides width and height, and have bytesperline set + * to zero, driver calculates bytesperline and sizeimage based on hardware + * limits. + */ +static int vpbe_try_format(struct vpbe_display *disp_dev, + struct v4l2_pix_format *pixfmt, int check) +{ + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + int min_height = 1; + int min_width = 32; + int max_height; + int max_width; + int bpp; + + if ((pixfmt->pixelformat != V4L2_PIX_FMT_UYVY) && + (pixfmt->pixelformat != V4L2_PIX_FMT_NV12)) + /* choose default as V4L2_PIX_FMT_UYVY */ + pixfmt->pixelformat = V4L2_PIX_FMT_UYVY; + + /* Check the field format */ + if ((pixfmt->field != V4L2_FIELD_INTERLACED) && + (pixfmt->field != V4L2_FIELD_NONE)) { + if (vpbe_dev->current_timings.interlaced) + pixfmt->field = V4L2_FIELD_INTERLACED; + else + pixfmt->field = V4L2_FIELD_NONE; + } + + if (pixfmt->field == V4L2_FIELD_INTERLACED) + min_height = 2; + + if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) + bpp = 1; + else + bpp = 2; + + max_width = vpbe_dev->current_timings.xres; + max_height = vpbe_dev->current_timings.yres; + + min_width /= bpp; + + if (!pixfmt->width || (pixfmt->width < min_width) || + (pixfmt->width > max_width)) { + pixfmt->width = vpbe_dev->current_timings.xres; + } + + if (!pixfmt->height || (pixfmt->height < min_height) || + (pixfmt->height > max_height)) { + pixfmt->height = vpbe_dev->current_timings.yres; + } + + if (pixfmt->bytesperline < (pixfmt->width * bpp)) + pixfmt->bytesperline = pixfmt->width * bpp; + + /* Make the bytesperline 32 byte aligned */ + pixfmt->bytesperline = ((pixfmt->width * bpp + 31) & ~31); + + if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height + + (pixfmt->bytesperline * pixfmt->height >> 1); + else + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + + return 0; +} + +static int vpbe_display_g_priority(struct file *file, void *priv, + enum v4l2_priority *p) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + + *p = v4l2_prio_max(&layer->prio); + + return 0; +} + +static int vpbe_display_s_priority(struct file *file, void *priv, + enum v4l2_priority p) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + int ret; + + ret = v4l2_prio_change(&layer->prio, &fh->prio, p); + + return ret; +} + +static int vpbe_display_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + cap->version = VPBE_DISPLAY_VERSION_CODE; + cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + strlcpy(cap->driver, VPBE_DISPLAY_DRIVER, sizeof(cap->driver)); + strlcpy(cap->bus_info, "platform", sizeof(cap->bus_info)); + strlcpy(cap->card, vpbe_dev->cfg->module_name, sizeof(cap->card)); + + return 0; +} + +static int vpbe_display_s_crop(struct file *file, void *priv, + const struct v4l2_crop *crop) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_display *disp_dev = fh->disp_dev; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + struct osd_layer_config *cfg = &layer->layer_info.config; + struct osd_state *osd_device = disp_dev->osd_device; + struct v4l2_rect *rect = &crop->c; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_S_CROP, layer id = %d\n", layer->device_id); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + + if (rect->top < 0) + rect->top = 0; + if (rect->left < 0) + rect->left = 0; + + vpbe_disp_check_window_params(disp_dev, rect); + + osd_device->ops.get_layer_config(osd_device, + layer->layer_info.id, cfg); + + vpbe_disp_calculate_scale_factor(disp_dev, layer, + rect->width, + rect->height); + vpbe_disp_adj_position(disp_dev, layer, rect->top, + rect->left); + ret = osd_device->ops.set_layer_config(osd_device, + layer->layer_info.id, cfg); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in set layer config:\n"); + return -EINVAL; + } + + /* apply zooming and h or v expansion */ + osd_device->ops.set_zoom(osd_device, + layer->layer_info.id, + layer->layer_info.h_zoom, + layer->layer_info.v_zoom); + ret = osd_device->ops.set_vid_expansion(osd_device, + layer->layer_info.h_exp, + layer->layer_info.v_exp); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in set vid expansion:\n"); + return -EINVAL; + } + + if ((layer->layer_info.h_zoom != ZOOM_X1) || + (layer->layer_info.v_zoom != ZOOM_X1) || + (layer->layer_info.h_exp != H_EXP_OFF) || + (layer->layer_info.v_exp != V_EXP_OFF)) + /* Enable expansion filter */ + osd_device->ops.set_interpolation_filter(osd_device, 1); + else + osd_device->ops.set_interpolation_filter(osd_device, 0); + + return 0; +} + +static int vpbe_display_g_crop(struct file *file, void *priv, + struct v4l2_crop *crop) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct osd_layer_config *cfg = &layer->layer_info.config; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + struct osd_state *osd_device = fh->disp_dev->osd_device; + struct v4l2_rect *rect = &crop->c; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_G_CROP, layer id = %d\n", + layer->device_id); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buf type\n"); + ret = -EINVAL; + } + osd_device->ops.get_layer_config(osd_device, + layer->layer_info.id, cfg); + rect->top = cfg->ypos; + rect->left = cfg->xpos; + rect->width = cfg->xsize; + rect->height = cfg->ysize; + + return 0; +} + +static int vpbe_display_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *cropcap) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_CROPCAP ioctl\n"); + + cropcap->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + cropcap->bounds.left = 0; + cropcap->bounds.top = 0; + cropcap->bounds.width = vpbe_dev->current_timings.xres; + cropcap->bounds.height = vpbe_dev->current_timings.yres; + cropcap->pixelaspect = vpbe_dev->current_timings.aspect; + cropcap->defrect = cropcap->bounds; + return 0; +} + +static int vpbe_display_g_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_G_FMT, layer id = %d\n", + layer->device_id); + + /* If buffer type is video output */ + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) { + v4l2_err(&vpbe_dev->v4l2_dev, "invalid type\n"); + return -EINVAL; + } + /* Fill in the information about format */ + fmt->fmt.pix = layer->pix_fmt; + + return 0; +} + +static int vpbe_display_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + unsigned int index = 0; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_ENUM_FMT, layer id = %d\n", + layer->device_id); + if (fmt->index > 1) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid format index\n"); + return -EINVAL; + } + + /* Fill in the information about format */ + index = fmt->index; + memset(fmt, 0, sizeof(*fmt)); + fmt->index = index; + fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + if (index == 0) { + strcpy(fmt->description, "YUV 4:2:2 - UYVY"); + fmt->pixelformat = V4L2_PIX_FMT_UYVY; + } else { + strcpy(fmt->description, "Y/CbCr 4:2:0"); + fmt->pixelformat = V4L2_PIX_FMT_NV12; + } + + return 0; +} + +static int vpbe_display_s_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_display *disp_dev = fh->disp_dev; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + struct osd_layer_config *cfg = &layer->layer_info.config; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + struct osd_state *osd_device = disp_dev->osd_device; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_S_FMT, layer id = %d\n", + layer->device_id); + + /* If streaming is started, return error */ + if (layer->started) { + v4l2_err(&vpbe_dev->v4l2_dev, "Streaming is started\n"); + return -EBUSY; + } + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) { + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "invalid type\n"); + return -EINVAL; + } + /* Check for valid pixel format */ + ret = vpbe_try_format(disp_dev, pixfmt, 1); + if (ret) + return ret; + + /* YUV420 is requested, check availability of the + other video window */ + + layer->pix_fmt = *pixfmt; + + /* Get osd layer config */ + osd_device->ops.get_layer_config(osd_device, + layer->layer_info.id, cfg); + /* Store the pixel format in the layer object */ + cfg->xsize = pixfmt->width; + cfg->ysize = pixfmt->height; + cfg->line_length = pixfmt->bytesperline; + cfg->ypos = 0; + cfg->xpos = 0; + cfg->interlaced = vpbe_dev->current_timings.interlaced; + + if (V4L2_PIX_FMT_UYVY == pixfmt->pixelformat) + cfg->pixfmt = PIXFMT_YCbCrI; + + /* Change of the default pixel format for both video windows */ + if (V4L2_PIX_FMT_NV12 == pixfmt->pixelformat) { + struct vpbe_layer *otherlayer; + cfg->pixfmt = PIXFMT_NV12; + otherlayer = _vpbe_display_get_other_win_layer(disp_dev, + layer); + otherlayer->layer_info.config.pixfmt = PIXFMT_NV12; + } + + /* Set the layer config in the osd window */ + ret = osd_device->ops.set_layer_config(osd_device, + layer->layer_info.id, cfg); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in S_FMT params:\n"); + return -EINVAL; + } + + /* Readback and fill the local copy of current pix format */ + osd_device->ops.get_layer_config(osd_device, + layer->layer_info.id, cfg); + + return 0; +} + +static int vpbe_display_try_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_display *disp_dev = fh->disp_dev; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_TRY_FMT\n"); + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) { + v4l2_err(&vpbe_dev->v4l2_dev, "invalid type\n"); + return -EINVAL; + } + + /* Check for valid field format */ + return vpbe_try_format(disp_dev, pixfmt, 0); + +} + +/** + * vpbe_display_s_std - Set the given standard in the encoder + * + * Sets the standard if supported by the current encoder. Return the status. + * 0 - success & -EINVAL on error + */ +static int vpbe_display_s_std(struct file *file, void *priv, + v4l2_std_id *std_id) +{ + struct vpbe_fh *fh = priv; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_STD\n"); + + /* If streaming is started, return error */ + if (layer->started) { + v4l2_err(&vpbe_dev->v4l2_dev, "Streaming is started\n"); + return -EBUSY; + } + if (NULL != vpbe_dev->ops.s_std) { + ret = vpbe_dev->ops.s_std(vpbe_dev, std_id); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Failed to set standard for sub devices\n"); + return -EINVAL; + } + } else { + return -EINVAL; + } + + return 0; +} + +/** + * vpbe_display_g_std - Get the standard in the current encoder + * + * Get the standard in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int vpbe_display_g_std(struct file *file, void *priv, + v4l2_std_id *std_id) +{ + struct vpbe_fh *fh = priv; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_STD\n"); + + /* Get the standard from the current encoder */ + if (vpbe_dev->current_timings.timings_type & VPBE_ENC_STD) { + *std_id = vpbe_dev->current_timings.timings.std_id; + return 0; + } + + return -EINVAL; +} + +/** + * vpbe_display_enum_output - enumerate outputs + * + * Enumerates the outputs available at the vpbe display + * returns the status, -EINVAL if end of output list + */ +static int vpbe_display_enum_output(struct file *file, void *priv, + struct v4l2_output *output) +{ + struct vpbe_fh *fh = priv; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_ENUM_OUTPUT\n"); + + /* Enumerate outputs */ + + if (NULL == vpbe_dev->ops.enum_outputs) + return -EINVAL; + + ret = vpbe_dev->ops.enum_outputs(vpbe_dev, output); + if (ret) { + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "Failed to enumerate outputs\n"); + return -EINVAL; + } + + return 0; +} + +/** + * vpbe_display_s_output - Set output to + * the output specified by the index + */ +static int vpbe_display_s_output(struct file *file, void *priv, + unsigned int i) +{ + struct vpbe_fh *fh = priv; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_OUTPUT\n"); + /* If streaming is started, return error */ + if (layer->started) { + v4l2_err(&vpbe_dev->v4l2_dev, "Streaming is started\n"); + return -EBUSY; + } + if (NULL == vpbe_dev->ops.set_output) + return -EINVAL; + + ret = vpbe_dev->ops.set_output(vpbe_dev, i); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Failed to set output for sub devices\n"); + return -EINVAL; + } + + return 0; +} + +/** + * vpbe_display_g_output - Get output from subdevice + * for a given by the index + */ +static int vpbe_display_g_output(struct file *file, void *priv, + unsigned int *i) +{ + struct vpbe_fh *fh = priv; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_OUTPUT\n"); + /* Get the standard from the current encoder */ + *i = vpbe_dev->current_out_index; + + return 0; +} + +/** + * vpbe_display_enum_dv_presets - Enumerate the dv presets + * + * enum the preset in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int +vpbe_display_enum_dv_presets(struct file *file, void *priv, + struct v4l2_dv_enum_preset *preset) +{ + struct vpbe_fh *fh = priv; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_ENUM_DV_PRESETS\n"); + + /* Enumerate outputs */ + if (NULL == vpbe_dev->ops.enum_dv_presets) + return -EINVAL; + + ret = vpbe_dev->ops.enum_dv_presets(vpbe_dev, preset); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Failed to enumerate dv presets info\n"); + return -EINVAL; + } + + return 0; +} + +/** + * vpbe_display_s_dv_preset - Set the dv presets + * + * Set the preset in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int +vpbe_display_s_dv_preset(struct file *file, void *priv, + struct v4l2_dv_preset *preset) +{ + struct vpbe_fh *fh = priv; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_DV_PRESETS\n"); + + + /* If streaming is started, return error */ + if (layer->started) { + v4l2_err(&vpbe_dev->v4l2_dev, "Streaming is started\n"); + return -EBUSY; + } + + /* Set the given standard in the encoder */ + if (!vpbe_dev->ops.s_dv_preset) + return -EINVAL; + + ret = vpbe_dev->ops.s_dv_preset(vpbe_dev, preset); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Failed to set the dv presets info\n"); + return -EINVAL; + } + /* set the current norm to zero to be consistent. If STD is used + * v4l2 layer will set the norm properly on successful s_std call + */ + layer->video_dev.current_norm = 0; + + return 0; +} + +/** + * vpbe_display_g_dv_preset - Set the dv presets + * + * Get the preset in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int +vpbe_display_g_dv_preset(struct file *file, void *priv, + struct v4l2_dv_preset *dv_preset) +{ + struct vpbe_fh *fh = priv; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_DV_PRESETS\n"); + + /* Get the given standard in the encoder */ + + if (vpbe_dev->current_timings.timings_type & + VPBE_ENC_DV_PRESET) { + dv_preset->preset = + vpbe_dev->current_timings.timings.dv_preset; + } else { + return -EINVAL; + } + + return 0; +} + +static int vpbe_display_streamoff(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + struct osd_state *osd_device = fh->disp_dev->osd_device; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_STREAMOFF,layer id = %d\n", + layer->device_id); + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != buf_type) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); + return -EINVAL; + } + + /* If io is allowed for this file handle, return error */ + if (!fh->io_allowed) { + v4l2_err(&vpbe_dev->v4l2_dev, "No io_allowed\n"); + return -EACCES; + } + + /* If streaming is not started, return error */ + if (!layer->started) { + v4l2_err(&vpbe_dev->v4l2_dev, "streaming not started in layer" + " id = %d\n", layer->device_id); + return -EINVAL; + } + + osd_device->ops.disable_layer(osd_device, + layer->layer_info.id); + layer->started = 0; + ret = videobuf_streamoff(&layer->buffer_queue); + + return ret; +} + +static int vpbe_display_streamon(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_display *disp_dev = fh->disp_dev; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + struct osd_state *osd_device = disp_dev->osd_device; + int ret; + + osd_device->ops.disable_layer(osd_device, + layer->layer_info.id); + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_STREAMON, layerid=%d\n", + layer->device_id); + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != buf_type) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); + return -EINVAL; + } + + /* If file handle is not allowed IO, return error */ + if (!fh->io_allowed) { + v4l2_err(&vpbe_dev->v4l2_dev, "No io_allowed\n"); + return -EACCES; + } + /* If Streaming is already started, return error */ + if (layer->started) { + v4l2_err(&vpbe_dev->v4l2_dev, "layer is already streaming\n"); + return -EBUSY; + } + + /* + * Call videobuf_streamon to start streaming + * in videobuf + */ + ret = videobuf_streamon(&layer->buffer_queue); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, + "error in videobuf_streamon\n"); + return ret; + } + /* If buffer queue is empty, return error */ + if (list_empty(&layer->dma_queue)) { + v4l2_err(&vpbe_dev->v4l2_dev, "buffer queue is empty\n"); + goto streamoff; + } + /* Get the next frame from the buffer queue */ + layer->next_frm = layer->cur_frm = list_entry(layer->dma_queue.next, + struct videobuf_buffer, queue); + /* Remove buffer from the buffer queue */ + list_del(&layer->cur_frm->queue); + /* Mark state of the current frame to active */ + layer->cur_frm->state = VIDEOBUF_ACTIVE; + /* Initialize field_id and started member */ + layer->field_id = 0; + + /* Set parameters in OSD and VENC */ + ret = vpbe_set_osd_display_params(disp_dev, layer); + if (ret < 0) + goto streamoff; + + /* + * if request format is yuv420 semiplanar, need to + * enable both video windows + */ + layer->started = 1; + + layer->layer_first_int = 1; + + return ret; +streamoff: + ret = videobuf_streamoff(&layer->buffer_queue); + return ret; +} + +static int vpbe_display_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_DQBUF, layer id = %d\n", + layer->device_id); + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); + return -EINVAL; + } + /* If this file handle is not allowed to do IO, return error */ + if (!fh->io_allowed) { + v4l2_err(&vpbe_dev->v4l2_dev, "No io_allowed\n"); + return -EACCES; + } + if (file->f_flags & O_NONBLOCK) + /* Call videobuf_dqbuf for non blocking mode */ + ret = videobuf_dqbuf(&layer->buffer_queue, buf, 1); + else + /* Call videobuf_dqbuf for blocking mode */ + ret = videobuf_dqbuf(&layer->buffer_queue, buf, 0); + + return ret; +} + +static int vpbe_display_qbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_QBUF, layer id = %d\n", + layer->device_id); + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != p->type) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); + return -EINVAL; + } + + /* If this file handle is not allowed to do IO, return error */ + if (!fh->io_allowed) { + v4l2_err(&vpbe_dev->v4l2_dev, "No io_allowed\n"); + return -EACCES; + } + + return videobuf_qbuf(&layer->buffer_queue, p); +} + +static int vpbe_display_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_QUERYBUF, layer id = %d\n", + layer->device_id); + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); + return -EINVAL; + } + + /* Call videobuf_querybuf to get information */ + ret = videobuf_querybuf(&layer->buffer_queue, buf); + + return ret; +} + +static int vpbe_display_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req_buf) +{ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_reqbufs\n"); + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != req_buf->type) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); + return -EINVAL; + } + + /* If io users of the layer is not zero, return error */ + if (0 != layer->io_usrs) { + v4l2_err(&vpbe_dev->v4l2_dev, "not IO user\n"); + return -EBUSY; + } + /* Initialize videobuf queue as per the buffer type */ + videobuf_queue_dma_contig_init(&layer->buffer_queue, + &video_qops, + vpbe_dev->pdev, + &layer->irqlock, + V4L2_BUF_TYPE_VIDEO_OUTPUT, + layer->pix_fmt.field, + sizeof(struct videobuf_buffer), + fh, NULL); + + /* Set io allowed member of file handle to TRUE */ + fh->io_allowed = 1; + /* Increment io usrs member of layer object to 1 */ + layer->io_usrs = 1; + /* Store type of memory requested in layer object */ + layer->memory = req_buf->memory; + /* Initialize buffer queue */ + INIT_LIST_HEAD(&layer->dma_queue); + /* Allocate buffers */ + ret = videobuf_reqbufs(&layer->buffer_queue, req_buf); + + return ret; +} + +/* + * vpbe_display_mmap() + * It is used to map kernel space buffers into user spaces + */ +static int vpbe_display_mmap(struct file *filep, struct vm_area_struct *vma) +{ + /* Get the layer object and file handle object */ + struct vpbe_fh *fh = filep->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_mmap\n"); + + if (mutex_lock_interruptible(&layer->opslock)) + return -ERESTARTSYS; + ret = videobuf_mmap_mapper(&layer->buffer_queue, vma); + mutex_unlock(&layer->opslock); + return ret; +} + +/* vpbe_display_poll(): It is used for select/poll system call + */ +static unsigned int vpbe_display_poll(struct file *filep, poll_table *wait) +{ + struct vpbe_fh *fh = filep->private_data; + struct vpbe_layer *layer = fh->layer; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + unsigned int err = 0; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_poll\n"); + if (layer->started) { + mutex_lock(&layer->opslock); + err = videobuf_poll_stream(filep, &layer->buffer_queue, wait); + mutex_unlock(&layer->opslock); + } + return err; +} + +/* + * vpbe_display_open() + * It creates object of file handle structure and stores it in private_data + * member of filepointer + */ +static int vpbe_display_open(struct file *file) +{ + struct vpbe_fh *fh = NULL; + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_display *disp_dev = layer->disp_dev; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + struct osd_state *osd_device = disp_dev->osd_device; + int err; + + /* Allocate memory for the file handle object */ + fh = kmalloc(sizeof(struct vpbe_fh), GFP_KERNEL); + if (fh == NULL) { + v4l2_err(&vpbe_dev->v4l2_dev, + "unable to allocate memory for file handle object\n"); + return -ENOMEM; + } + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "vpbe display open plane = %d\n", + layer->device_id); + + /* store pointer to fh in private_data member of filep */ + file->private_data = fh; + fh->layer = layer; + fh->disp_dev = disp_dev; + + if (!layer->usrs) { + if (mutex_lock_interruptible(&layer->opslock)) + return -ERESTARTSYS; + /* First claim the layer for this device */ + err = osd_device->ops.request_layer(osd_device, + layer->layer_info.id); + mutex_unlock(&layer->opslock); + if (err < 0) { + /* Couldn't get layer */ + v4l2_err(&vpbe_dev->v4l2_dev, + "Display Manager failed to allocate layer\n"); + kfree(fh); + return -EINVAL; + } + } + /* Increment layer usrs counter */ + layer->usrs++; + /* Set io_allowed member to false */ + fh->io_allowed = 0; + /* Initialize priority of this instance to default priority */ + fh->prio = V4L2_PRIORITY_UNSET; + v4l2_prio_open(&layer->prio, &fh->prio); + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "vpbe display device opened successfully\n"); + return 0; +} + +/* + * vpbe_display_release() + * This function deletes buffer queue, frees the buffers and the davinci + * display file * handle + */ +static int vpbe_display_release(struct file *file) +{ + /* Get the layer object and file handle object */ + struct vpbe_fh *fh = file->private_data; + struct vpbe_layer *layer = fh->layer; + struct osd_layer_config *cfg = &layer->layer_info.config; + struct vpbe_display *disp_dev = fh->disp_dev; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + struct osd_state *osd_device = disp_dev->osd_device; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_release\n"); + + mutex_lock(&layer->opslock); + /* if this instance is doing IO */ + if (fh->io_allowed) { + /* Reset io_usrs member of layer object */ + layer->io_usrs = 0; + + osd_device->ops.disable_layer(osd_device, + layer->layer_info.id); + layer->started = 0; + /* Free buffers allocated */ + videobuf_queue_cancel(&layer->buffer_queue); + videobuf_mmap_free(&layer->buffer_queue); + } + + /* Decrement layer usrs counter */ + layer->usrs--; + /* If this file handle has initialize encoder device, reset it */ + if (!layer->usrs) { + if (cfg->pixfmt == PIXFMT_NV12) { + struct vpbe_layer *otherlayer; + otherlayer = + _vpbe_display_get_other_win_layer(disp_dev, layer); + osd_device->ops.disable_layer(osd_device, + otherlayer->layer_info.id); + osd_device->ops.release_layer(osd_device, + otherlayer->layer_info.id); + } + osd_device->ops.disable_layer(osd_device, + layer->layer_info.id); + osd_device->ops.release_layer(osd_device, + layer->layer_info.id); + } + /* Close the priority */ + v4l2_prio_close(&layer->prio, fh->prio); + file->private_data = NULL; + mutex_unlock(&layer->opslock); + + /* Free memory allocated to file handle object */ + kfree(fh); + + disp_dev->cbcr_ofst = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vpbe_display_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct v4l2_dbg_match *match = ®->match; + struct vpbe_fh *fh = file->private_data; + struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + + if (match->type >= 2) { + v4l2_subdev_call(vpbe_dev->venc, + core, + g_register, + reg); + } + + return 0; +} + +static int vpbe_display_s_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + return 0; +} +#endif + +/* vpbe capture ioctl operations */ +static const struct v4l2_ioctl_ops vpbe_ioctl_ops = { + .vidioc_querycap = vpbe_display_querycap, + .vidioc_g_fmt_vid_out = vpbe_display_g_fmt, + .vidioc_enum_fmt_vid_out = vpbe_display_enum_fmt, + .vidioc_s_fmt_vid_out = vpbe_display_s_fmt, + .vidioc_try_fmt_vid_out = vpbe_display_try_fmt, + .vidioc_reqbufs = vpbe_display_reqbufs, + .vidioc_querybuf = vpbe_display_querybuf, + .vidioc_qbuf = vpbe_display_qbuf, + .vidioc_dqbuf = vpbe_display_dqbuf, + .vidioc_streamon = vpbe_display_streamon, + .vidioc_streamoff = vpbe_display_streamoff, + .vidioc_cropcap = vpbe_display_cropcap, + .vidioc_g_crop = vpbe_display_g_crop, + .vidioc_s_crop = vpbe_display_s_crop, + .vidioc_g_priority = vpbe_display_g_priority, + .vidioc_s_priority = vpbe_display_s_priority, + .vidioc_s_std = vpbe_display_s_std, + .vidioc_g_std = vpbe_display_g_std, + .vidioc_enum_output = vpbe_display_enum_output, + .vidioc_s_output = vpbe_display_s_output, + .vidioc_g_output = vpbe_display_g_output, + .vidioc_s_dv_preset = vpbe_display_s_dv_preset, + .vidioc_g_dv_preset = vpbe_display_g_dv_preset, + .vidioc_enum_dv_presets = vpbe_display_enum_dv_presets, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vpbe_display_g_register, + .vidioc_s_register = vpbe_display_s_register, +#endif +}; + +static struct v4l2_file_operations vpbe_fops = { + .owner = THIS_MODULE, + .open = vpbe_display_open, + .release = vpbe_display_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vpbe_display_mmap, + .poll = vpbe_display_poll +}; + +static int vpbe_device_get(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vpbe_display *vpbe_disp = data; + + if (strcmp("vpbe_controller", pdev->name) == 0) + vpbe_disp->vpbe_dev = platform_get_drvdata(pdev); + + if (strcmp("vpbe-osd", pdev->name) == 0) + vpbe_disp->osd_device = platform_get_drvdata(pdev); + + return 0; +} + +static __devinit int init_vpbe_layer(int i, struct vpbe_display *disp_dev, + struct platform_device *pdev) +{ + struct vpbe_layer *vpbe_display_layer = NULL; + struct video_device *vbd = NULL; + + /* Allocate memory for four plane display objects */ + + disp_dev->dev[i] = + kzalloc(sizeof(struct vpbe_layer), GFP_KERNEL); + + /* If memory allocation fails, return error */ + if (!disp_dev->dev[i]) { + printk(KERN_ERR "ran out of memory\n"); + return -ENOMEM; + } + spin_lock_init(&disp_dev->dev[i]->irqlock); + mutex_init(&disp_dev->dev[i]->opslock); + + /* Get the pointer to the layer object */ + vpbe_display_layer = disp_dev->dev[i]; + vbd = &vpbe_display_layer->video_dev; + /* Initialize field of video device */ + vbd->release = video_device_release_empty; + vbd->fops = &vpbe_fops; + vbd->ioctl_ops = &vpbe_ioctl_ops; + vbd->minor = -1; + vbd->v4l2_dev = &disp_dev->vpbe_dev->v4l2_dev; + vbd->lock = &vpbe_display_layer->opslock; + vbd->vfl_dir = VFL_DIR_TX; + + if (disp_dev->vpbe_dev->current_timings.timings_type & + VPBE_ENC_STD) { + vbd->tvnorms = (V4L2_STD_525_60 | V4L2_STD_625_50); + vbd->current_norm = + disp_dev->vpbe_dev-> + current_timings.timings.std_id; + } else + vbd->current_norm = 0; + + snprintf(vbd->name, sizeof(vbd->name), + "DaVinci_VPBE Display_DRIVER_V%d.%d.%d", + (VPBE_DISPLAY_VERSION_CODE >> 16) & 0xff, + (VPBE_DISPLAY_VERSION_CODE >> 8) & 0xff, + (VPBE_DISPLAY_VERSION_CODE) & 0xff); + + vpbe_display_layer->device_id = i; + + vpbe_display_layer->layer_info.id = + ((i == VPBE_DISPLAY_DEVICE_0) ? WIN_VID0 : WIN_VID1); + + /* Initialize prio member of layer object */ + v4l2_prio_init(&vpbe_display_layer->prio); + + return 0; +} + +static __devinit int register_device(struct vpbe_layer *vpbe_display_layer, + struct vpbe_display *disp_dev, + struct platform_device *pdev) { + int err; + + v4l2_info(&disp_dev->vpbe_dev->v4l2_dev, + "Trying to register VPBE display device.\n"); + v4l2_info(&disp_dev->vpbe_dev->v4l2_dev, + "layer=%x,layer->video_dev=%x\n", + (int)vpbe_display_layer, + (int)&vpbe_display_layer->video_dev); + + err = video_register_device(&vpbe_display_layer->video_dev, + VFL_TYPE_GRABBER, + -1); + if (err) + return -ENODEV; + + vpbe_display_layer->disp_dev = disp_dev; + /* set the driver data in platform device */ + platform_set_drvdata(pdev, disp_dev); + video_set_drvdata(&vpbe_display_layer->video_dev, + vpbe_display_layer); + + return 0; +} + + + +/* + * vpbe_display_probe() + * This function creates device entries by register itself to the V4L2 driver + * and initializes fields of each layer objects + */ +static __devinit int vpbe_display_probe(struct platform_device *pdev) +{ + struct vpbe_layer *vpbe_display_layer; + struct vpbe_display *disp_dev; + struct resource *res = NULL; + int k; + int i; + int err; + int irq; + + printk(KERN_DEBUG "vpbe_display_probe\n"); + /* Allocate memory for vpbe_display */ + disp_dev = kzalloc(sizeof(struct vpbe_display), GFP_KERNEL); + if (!disp_dev) { + printk(KERN_ERR "ran out of memory\n"); + return -ENOMEM; + } + + spin_lock_init(&disp_dev->dma_queue_lock); + /* + * Scan all the platform devices to find the vpbe + * controller device and get the vpbe_dev object + */ + err = bus_for_each_dev(&platform_bus_type, NULL, disp_dev, + vpbe_device_get); + if (err < 0) + return err; + /* Initialize the vpbe display controller */ + if (NULL != disp_dev->vpbe_dev->ops.initialize) { + err = disp_dev->vpbe_dev->ops.initialize(&pdev->dev, + disp_dev->vpbe_dev); + if (err) { + v4l2_err(&disp_dev->vpbe_dev->v4l2_dev, + "Error initing vpbe\n"); + err = -ENOMEM; + goto probe_out; + } + } + + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + if (init_vpbe_layer(i, disp_dev, pdev)) { + err = -ENODEV; + goto probe_out; + } + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + v4l2_err(&disp_dev->vpbe_dev->v4l2_dev, + "Unable to get VENC interrupt resource\n"); + err = -ENODEV; + goto probe_out; + } + + irq = res->start; + if (request_irq(irq, venc_isr, IRQF_DISABLED, VPBE_DISPLAY_DRIVER, + disp_dev)) { + v4l2_err(&disp_dev->vpbe_dev->v4l2_dev, + "Unable to request interrupt\n"); + err = -ENODEV; + goto probe_out; + } + + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + if (register_device(disp_dev->dev[i], disp_dev, pdev)) { + err = -ENODEV; + goto probe_out_irq; + } + } + + printk(KERN_DEBUG "Successfully completed the probing of vpbe v4l2 device\n"); + return 0; + +probe_out_irq: + free_irq(res->start, disp_dev); +probe_out: + for (k = 0; k < VPBE_DISPLAY_MAX_DEVICES; k++) { + /* Get the pointer to the layer object */ + vpbe_display_layer = disp_dev->dev[k]; + /* Unregister video device */ + if (vpbe_display_layer) { + video_unregister_device( + &vpbe_display_layer->video_dev); + kfree(disp_dev->dev[k]); + } + } + kfree(disp_dev); + return err; +} + +/* + * vpbe_display_remove() + * It un-register hardware layer from V4L2 driver + */ +static int vpbe_display_remove(struct platform_device *pdev) +{ + struct vpbe_layer *vpbe_display_layer; + struct vpbe_display *disp_dev = platform_get_drvdata(pdev); + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + struct resource *res; + int i; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_remove\n"); + + /* unregister irq */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + free_irq(res->start, disp_dev); + + /* deinitialize the vpbe display controller */ + if (NULL != vpbe_dev->ops.deinitialize) + vpbe_dev->ops.deinitialize(&pdev->dev, vpbe_dev); + /* un-register device */ + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + /* Get the pointer to the layer object */ + vpbe_display_layer = disp_dev->dev[i]; + /* Unregister video device */ + video_unregister_device(&vpbe_display_layer->video_dev); + + } + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + kfree(disp_dev->dev[i]); + disp_dev->dev[i] = NULL; + } + + return 0; +} + +static struct platform_driver vpbe_display_driver = { + .driver = { + .name = VPBE_DISPLAY_DRIVER, + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = vpbe_display_probe, + .remove = __devexit_p(vpbe_display_remove), +}; + +module_platform_driver(vpbe_display_driver); + +MODULE_DESCRIPTION("TI DM644x/DM355/DM365 VPBE Display controller"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); diff --git a/drivers/media/video/davinci/vpbe_osd.c b/drivers/media/platform/davinci/vpbe_osd.c similarity index 100% rename from drivers/media/video/davinci/vpbe_osd.c rename to drivers/media/platform/davinci/vpbe_osd.c diff --git a/drivers/media/video/davinci/vpbe_osd_regs.h b/drivers/media/platform/davinci/vpbe_osd_regs.h similarity index 100% rename from drivers/media/video/davinci/vpbe_osd_regs.h rename to drivers/media/platform/davinci/vpbe_osd_regs.h diff --git a/drivers/media/video/davinci/vpbe_venc.c b/drivers/media/platform/davinci/vpbe_venc.c similarity index 100% rename from drivers/media/video/davinci/vpbe_venc.c rename to drivers/media/platform/davinci/vpbe_venc.c diff --git a/drivers/media/video/davinci/vpbe_venc_regs.h b/drivers/media/platform/davinci/vpbe_venc_regs.h similarity index 100% rename from drivers/media/video/davinci/vpbe_venc_regs.h rename to drivers/media/platform/davinci/vpbe_venc_regs.h diff --git a/drivers/media/platform/davinci/vpfe_capture.c b/drivers/media/platform/davinci/vpfe_capture.c new file mode 100644 index 000000000000..48052cbffc2b --- /dev/null +++ b/drivers/media/platform/davinci/vpfe_capture.c @@ -0,0 +1,2080 @@ +/* + * Copyright (C) 2008-2009 Texas Instruments Inc + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Driver name : VPFE Capture driver + * VPFE Capture driver allows applications to capture and stream video + * frames on DaVinci SoCs (DM6446, DM355 etc) from a YUV source such as + * TVP5146 or Raw Bayer RGB image data from an image sensor + * such as Microns' MT9T001, MT9T031 etc. + * + * These SoCs have, in common, a Video Processing Subsystem (VPSS) that + * consists of a Video Processing Front End (VPFE) for capturing + * video/raw image data and Video Processing Back End (VPBE) for displaying + * YUV data through an in-built analog encoder or Digital LCD port. This + * driver is for capture through VPFE. A typical EVM using these SoCs have + * following high level configuration. + * + * + * decoder(TVP5146/ YUV/ + * MT9T001) --> Raw Bayer RGB ---> MUX -> VPFE (CCDC/ISIF) + * data input | | + * V | + * SDRAM | + * V + * Image Processor + * | + * V + * SDRAM + * The data flow happens from a decoder connected to the VPFE over a + * YUV embedded (BT.656/BT.1120) or separate sync or raw bayer rgb interface + * and to the input of VPFE through an optional MUX (if more inputs are + * to be interfaced on the EVM). The input data is first passed through + * CCDC (CCD Controller, a.k.a Image Sensor Interface, ISIF). The CCDC + * does very little or no processing on YUV data and does pre-process Raw + * Bayer RGB data through modules such as Defect Pixel Correction (DFC) + * Color Space Conversion (CSC), data gain/offset etc. After this, data + * can be written to SDRAM or can be connected to the image processing + * block such as IPIPE (on DM355 only). + * + * Features supported + * - MMAP IO + * - Capture using TVP5146 over BT.656 + * - support for interfacing decoders using sub device model + * - Work with DM355 or DM6446 CCDC to do Raw Bayer RGB/YUV + * data capture to SDRAM. + * TODO list + * - Support multiple REQBUF after open + * - Support for de-allocating buffers through REQBUF + * - Support for Raw Bayer RGB capture + * - Support for chaining Image Processor + * - Support for static allocation of buffers + * - Support for USERPTR IO + * - Support for STREAMON before QBUF + * - Support for control ioctls + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ccdc_hw_device.h" + +static int debug; +static u32 numbuffers = 3; +static u32 bufsize = (720 * 576 * 2); + +module_param(numbuffers, uint, S_IRUGO); +module_param(bufsize, uint, S_IRUGO); +module_param(debug, int, 0644); + +MODULE_PARM_DESC(numbuffers, "buffer count (default:3)"); +MODULE_PARM_DESC(bufsize, "buffer size in bytes (default:720 x 576 x 2)"); +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +MODULE_DESCRIPTION("VPFE Video for Linux Capture Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); + +/* standard information */ +struct vpfe_standard { + v4l2_std_id std_id; + unsigned int width; + unsigned int height; + struct v4l2_fract pixelaspect; + /* 0 - progressive, 1 - interlaced */ + int frame_format; +}; + +/* ccdc configuration */ +struct ccdc_config { + /* This make sure vpfe is probed and ready to go */ + int vpfe_probed; + /* name of ccdc device */ + char name[32]; +}; + +/* data structures */ +static struct vpfe_config_params config_params = { + .min_numbuffers = 3, + .numbuffers = 3, + .min_bufsize = 720 * 480 * 2, + .device_bufsize = 720 * 576 * 2, +}; + +/* ccdc device registered */ +static struct ccdc_hw_device *ccdc_dev; +/* lock for accessing ccdc information */ +static DEFINE_MUTEX(ccdc_lock); +/* ccdc configuration */ +static struct ccdc_config *ccdc_cfg; + +const struct vpfe_standard vpfe_standards[] = { + {V4L2_STD_525_60, 720, 480, {11, 10}, 1}, + {V4L2_STD_625_50, 720, 576, {54, 59}, 1}, +}; + +/* Used when raw Bayer image from ccdc is directly captured to SDRAM */ +static const struct vpfe_pixel_format vpfe_pix_fmts[] = { + { + .fmtdesc = { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "Bayer GrRBGb 8bit A-Law compr.", + .pixelformat = V4L2_PIX_FMT_SBGGR8, + }, + .bpp = 1, + }, + { + .fmtdesc = { + .index = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "Bayer GrRBGb - 16bit", + .pixelformat = V4L2_PIX_FMT_SBGGR16, + }, + .bpp = 2, + }, + { + .fmtdesc = { + .index = 2, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "Bayer GrRBGb 8bit DPCM compr.", + .pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8, + }, + .bpp = 1, + }, + { + .fmtdesc = { + .index = 3, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "YCbCr 4:2:2 Interleaved UYVY", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, + .bpp = 2, + }, + { + .fmtdesc = { + .index = 4, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "YCbCr 4:2:2 Interleaved YUYV", + .pixelformat = V4L2_PIX_FMT_YUYV, + }, + .bpp = 2, + }, + { + .fmtdesc = { + .index = 5, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .description = "Y/CbCr 4:2:0 - Semi planar", + .pixelformat = V4L2_PIX_FMT_NV12, + }, + .bpp = 1, + }, +}; + +/* + * vpfe_lookup_pix_format() + * lookup an entry in the vpfe pix format table based on pix_format + */ +static const struct vpfe_pixel_format *vpfe_lookup_pix_format(u32 pix_format) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vpfe_pix_fmts); i++) { + if (pix_format == vpfe_pix_fmts[i].fmtdesc.pixelformat) + return &vpfe_pix_fmts[i]; + } + return NULL; +} + +/* + * vpfe_register_ccdc_device. CCDC module calls this to + * register with vpfe capture + */ +int vpfe_register_ccdc_device(struct ccdc_hw_device *dev) +{ + int ret = 0; + printk(KERN_NOTICE "vpfe_register_ccdc_device: %s\n", dev->name); + + BUG_ON(!dev->hw_ops.open); + BUG_ON(!dev->hw_ops.enable); + BUG_ON(!dev->hw_ops.set_hw_if_params); + BUG_ON(!dev->hw_ops.configure); + BUG_ON(!dev->hw_ops.set_buftype); + BUG_ON(!dev->hw_ops.get_buftype); + BUG_ON(!dev->hw_ops.enum_pix); + BUG_ON(!dev->hw_ops.set_frame_format); + BUG_ON(!dev->hw_ops.get_frame_format); + BUG_ON(!dev->hw_ops.get_pixel_format); + BUG_ON(!dev->hw_ops.set_pixel_format); + BUG_ON(!dev->hw_ops.set_image_window); + BUG_ON(!dev->hw_ops.get_image_window); + BUG_ON(!dev->hw_ops.get_line_length); + BUG_ON(!dev->hw_ops.getfid); + + mutex_lock(&ccdc_lock); + if (NULL == ccdc_cfg) { + /* + * TODO. Will this ever happen? if so, we need to fix it. + * Proabably we need to add the request to a linked list and + * walk through it during vpfe probe + */ + printk(KERN_ERR "vpfe capture not initialized\n"); + ret = -EFAULT; + goto unlock; + } + + if (strcmp(dev->name, ccdc_cfg->name)) { + /* ignore this ccdc */ + ret = -EINVAL; + goto unlock; + } + + if (ccdc_dev) { + printk(KERN_ERR "ccdc already registered\n"); + ret = -EINVAL; + goto unlock; + } + + ccdc_dev = dev; +unlock: + mutex_unlock(&ccdc_lock); + return ret; +} +EXPORT_SYMBOL(vpfe_register_ccdc_device); + +/* + * vpfe_unregister_ccdc_device. CCDC module calls this to + * unregister with vpfe capture + */ +void vpfe_unregister_ccdc_device(struct ccdc_hw_device *dev) +{ + if (NULL == dev) { + printk(KERN_ERR "invalid ccdc device ptr\n"); + return; + } + + printk(KERN_NOTICE "vpfe_unregister_ccdc_device, dev->name = %s\n", + dev->name); + + if (strcmp(dev->name, ccdc_cfg->name)) { + /* ignore this ccdc */ + return; + } + + mutex_lock(&ccdc_lock); + ccdc_dev = NULL; + mutex_unlock(&ccdc_lock); + return; +} +EXPORT_SYMBOL(vpfe_unregister_ccdc_device); + +/* + * vpfe_get_ccdc_image_format - Get image parameters based on CCDC settings + */ +static int vpfe_get_ccdc_image_format(struct vpfe_device *vpfe_dev, + struct v4l2_format *f) +{ + struct v4l2_rect image_win; + enum ccdc_buftype buf_type; + enum ccdc_frmfmt frm_fmt; + + memset(f, 0, sizeof(*f)); + f->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + ccdc_dev->hw_ops.get_image_window(&image_win); + f->fmt.pix.width = image_win.width; + f->fmt.pix.height = image_win.height; + f->fmt.pix.bytesperline = ccdc_dev->hw_ops.get_line_length(); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height; + buf_type = ccdc_dev->hw_ops.get_buftype(); + f->fmt.pix.pixelformat = ccdc_dev->hw_ops.get_pixel_format(); + frm_fmt = ccdc_dev->hw_ops.get_frame_format(); + if (frm_fmt == CCDC_FRMFMT_PROGRESSIVE) + f->fmt.pix.field = V4L2_FIELD_NONE; + else if (frm_fmt == CCDC_FRMFMT_INTERLACED) { + if (buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + else if (buf_type == CCDC_BUFTYPE_FLD_SEPARATED) + f->fmt.pix.field = V4L2_FIELD_SEQ_TB; + else { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf_type\n"); + return -EINVAL; + } + } else { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid frm_fmt\n"); + return -EINVAL; + } + return 0; +} + +/* + * vpfe_config_ccdc_image_format() + * For a pix format, configure ccdc to setup the capture + */ +static int vpfe_config_ccdc_image_format(struct vpfe_device *vpfe_dev) +{ + enum ccdc_frmfmt frm_fmt = CCDC_FRMFMT_INTERLACED; + int ret = 0; + + if (ccdc_dev->hw_ops.set_pixel_format( + vpfe_dev->fmt.fmt.pix.pixelformat) < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, + "couldn't set pix format in ccdc\n"); + return -EINVAL; + } + /* configure the image window */ + ccdc_dev->hw_ops.set_image_window(&vpfe_dev->crop); + + switch (vpfe_dev->fmt.fmt.pix.field) { + case V4L2_FIELD_INTERLACED: + /* do nothing, since it is default */ + ret = ccdc_dev->hw_ops.set_buftype( + CCDC_BUFTYPE_FLD_INTERLEAVED); + break; + case V4L2_FIELD_NONE: + frm_fmt = CCDC_FRMFMT_PROGRESSIVE; + /* buffer type only applicable for interlaced scan */ + break; + case V4L2_FIELD_SEQ_TB: + ret = ccdc_dev->hw_ops.set_buftype( + CCDC_BUFTYPE_FLD_SEPARATED); + break; + default: + return -EINVAL; + } + + /* set the frame format */ + if (!ret) + ret = ccdc_dev->hw_ops.set_frame_format(frm_fmt); + return ret; +} +/* + * vpfe_config_image_format() + * For a given standard, this functions sets up the default + * pix format & crop values in the vpfe device and ccdc. It first + * starts with defaults based values from the standard table. + * It then checks if sub device support g_mbus_fmt and then override the + * values based on that.Sets crop values to match with scan resolution + * starting at 0,0. It calls vpfe_config_ccdc_image_format() set the + * values in ccdc + */ +static int vpfe_config_image_format(struct vpfe_device *vpfe_dev, + const v4l2_std_id *std_id) +{ + struct vpfe_subdev_info *sdinfo = vpfe_dev->current_subdev; + struct v4l2_mbus_framefmt mbus_fmt; + struct v4l2_pix_format *pix = &vpfe_dev->fmt.fmt.pix; + int i, ret = 0; + + for (i = 0; i < ARRAY_SIZE(vpfe_standards); i++) { + if (vpfe_standards[i].std_id & *std_id) { + vpfe_dev->std_info.active_pixels = + vpfe_standards[i].width; + vpfe_dev->std_info.active_lines = + vpfe_standards[i].height; + vpfe_dev->std_info.frame_format = + vpfe_standards[i].frame_format; + vpfe_dev->std_index = i; + break; + } + } + + if (i == ARRAY_SIZE(vpfe_standards)) { + v4l2_err(&vpfe_dev->v4l2_dev, "standard not supported\n"); + return -EINVAL; + } + + vpfe_dev->crop.top = 0; + vpfe_dev->crop.left = 0; + vpfe_dev->crop.width = vpfe_dev->std_info.active_pixels; + vpfe_dev->crop.height = vpfe_dev->std_info.active_lines; + pix->width = vpfe_dev->crop.width; + pix->height = vpfe_dev->crop.height; + + /* first field and frame format based on standard frame format */ + if (vpfe_dev->std_info.frame_format) { + pix->field = V4L2_FIELD_INTERLACED; + /* assume V4L2_PIX_FMT_UYVY as default */ + pix->pixelformat = V4L2_PIX_FMT_UYVY; + v4l2_fill_mbus_format(&mbus_fmt, pix, + V4L2_MBUS_FMT_YUYV10_2X10); + } else { + pix->field = V4L2_FIELD_NONE; + /* assume V4L2_PIX_FMT_SBGGR8 */ + pix->pixelformat = V4L2_PIX_FMT_SBGGR8; + v4l2_fill_mbus_format(&mbus_fmt, pix, + V4L2_MBUS_FMT_SBGGR8_1X8); + } + + /* if sub device supports g_mbus_fmt, override the defaults */ + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, + sdinfo->grp_id, video, g_mbus_fmt, &mbus_fmt); + + if (ret && ret != -ENOIOCTLCMD) { + v4l2_err(&vpfe_dev->v4l2_dev, + "error in getting g_mbus_fmt from sub device\n"); + return ret; + } + v4l2_fill_pix_format(pix, &mbus_fmt); + pix->bytesperline = pix->width * 2; + pix->sizeimage = pix->bytesperline * pix->height; + + /* Sets the values in CCDC */ + ret = vpfe_config_ccdc_image_format(vpfe_dev); + if (ret) + return ret; + + /* Update the values of sizeimage and bytesperline */ + if (!ret) { + pix->bytesperline = ccdc_dev->hw_ops.get_line_length(); + pix->sizeimage = pix->bytesperline * pix->height; + } + return ret; +} + +static int vpfe_initialize_device(struct vpfe_device *vpfe_dev) +{ + int ret = 0; + + /* set first input of current subdevice as the current input */ + vpfe_dev->current_input = 0; + + /* set default standard */ + vpfe_dev->std_index = 0; + + /* Configure the default format information */ + ret = vpfe_config_image_format(vpfe_dev, + &vpfe_standards[vpfe_dev->std_index].std_id); + if (ret) + return ret; + + /* now open the ccdc device to initialize it */ + mutex_lock(&ccdc_lock); + if (NULL == ccdc_dev) { + v4l2_err(&vpfe_dev->v4l2_dev, "ccdc device not registered\n"); + ret = -ENODEV; + goto unlock; + } + + if (!try_module_get(ccdc_dev->owner)) { + v4l2_err(&vpfe_dev->v4l2_dev, "Couldn't lock ccdc module\n"); + ret = -ENODEV; + goto unlock; + } + ret = ccdc_dev->hw_ops.open(vpfe_dev->pdev); + if (!ret) + vpfe_dev->initialized = 1; + + /* Clear all VPFE/CCDC interrupts */ + if (vpfe_dev->cfg->clr_intr) + vpfe_dev->cfg->clr_intr(-1); + +unlock: + mutex_unlock(&ccdc_lock); + return ret; +} + +/* + * vpfe_open : It creates object of file handle structure and + * stores it in private_data member of filepointer + */ +static int vpfe_open(struct file *file) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_fh *fh; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_open\n"); + + if (!vpfe_dev->cfg->num_subdevs) { + v4l2_err(&vpfe_dev->v4l2_dev, "No decoder registered\n"); + return -ENODEV; + } + + /* Allocate memory for the file handle object */ + fh = kmalloc(sizeof(struct vpfe_fh), GFP_KERNEL); + if (NULL == fh) { + v4l2_err(&vpfe_dev->v4l2_dev, + "unable to allocate memory for file handle object\n"); + return -ENOMEM; + } + /* store pointer to fh in private_data member of file */ + file->private_data = fh; + fh->vpfe_dev = vpfe_dev; + mutex_lock(&vpfe_dev->lock); + /* If decoder is not initialized. initialize it */ + if (!vpfe_dev->initialized) { + if (vpfe_initialize_device(vpfe_dev)) { + mutex_unlock(&vpfe_dev->lock); + return -ENODEV; + } + } + /* Increment device usrs counter */ + vpfe_dev->usrs++; + /* Set io_allowed member to false */ + fh->io_allowed = 0; + /* Initialize priority of this instance to default priority */ + fh->prio = V4L2_PRIORITY_UNSET; + v4l2_prio_open(&vpfe_dev->prio, &fh->prio); + mutex_unlock(&vpfe_dev->lock); + return 0; +} + +static void vpfe_schedule_next_buffer(struct vpfe_device *vpfe_dev) +{ + unsigned long addr; + + vpfe_dev->next_frm = list_entry(vpfe_dev->dma_queue.next, + struct videobuf_buffer, queue); + list_del(&vpfe_dev->next_frm->queue); + vpfe_dev->next_frm->state = VIDEOBUF_ACTIVE; + addr = videobuf_to_dma_contig(vpfe_dev->next_frm); + + ccdc_dev->hw_ops.setfbaddr(addr); +} + +static void vpfe_schedule_bottom_field(struct vpfe_device *vpfe_dev) +{ + unsigned long addr; + + addr = videobuf_to_dma_contig(vpfe_dev->cur_frm); + addr += vpfe_dev->field_off; + ccdc_dev->hw_ops.setfbaddr(addr); +} + +static void vpfe_process_buffer_complete(struct vpfe_device *vpfe_dev) +{ + struct timeval timevalue; + + do_gettimeofday(&timevalue); + vpfe_dev->cur_frm->ts = timevalue; + vpfe_dev->cur_frm->state = VIDEOBUF_DONE; + vpfe_dev->cur_frm->size = vpfe_dev->fmt.fmt.pix.sizeimage; + wake_up_interruptible(&vpfe_dev->cur_frm->done); + vpfe_dev->cur_frm = vpfe_dev->next_frm; +} + +/* ISR for VINT0*/ +static irqreturn_t vpfe_isr(int irq, void *dev_id) +{ + struct vpfe_device *vpfe_dev = dev_id; + enum v4l2_field field; + int fid; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "\nStarting vpfe_isr...\n"); + field = vpfe_dev->fmt.fmt.pix.field; + + /* if streaming not started, don't do anything */ + if (!vpfe_dev->started) + goto clear_intr; + + /* only for 6446 this will be applicable */ + if (NULL != ccdc_dev->hw_ops.reset) + ccdc_dev->hw_ops.reset(); + + if (field == V4L2_FIELD_NONE) { + /* handle progressive frame capture */ + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "frame format is progressive...\n"); + if (vpfe_dev->cur_frm != vpfe_dev->next_frm) + vpfe_process_buffer_complete(vpfe_dev); + goto clear_intr; + } + + /* interlaced or TB capture check which field we are in hardware */ + fid = ccdc_dev->hw_ops.getfid(); + + /* switch the software maintained field id */ + vpfe_dev->field_id ^= 1; + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "field id = %x:%x.\n", + fid, vpfe_dev->field_id); + if (fid == vpfe_dev->field_id) { + /* we are in-sync here,continue */ + if (fid == 0) { + /* + * One frame is just being captured. If the next frame + * is available, release the current frame and move on + */ + if (vpfe_dev->cur_frm != vpfe_dev->next_frm) + vpfe_process_buffer_complete(vpfe_dev); + /* + * based on whether the two fields are stored + * interleavely or separately in memory, reconfigure + * the CCDC memory address + */ + if (field == V4L2_FIELD_SEQ_TB) { + vpfe_schedule_bottom_field(vpfe_dev); + } + goto clear_intr; + } + /* + * if one field is just being captured configure + * the next frame get the next frame from the empty + * queue if no frame is available hold on to the + * current buffer + */ + spin_lock(&vpfe_dev->dma_queue_lock); + if (!list_empty(&vpfe_dev->dma_queue) && + vpfe_dev->cur_frm == vpfe_dev->next_frm) + vpfe_schedule_next_buffer(vpfe_dev); + spin_unlock(&vpfe_dev->dma_queue_lock); + } else if (fid == 0) { + /* + * out of sync. Recover from any hardware out-of-sync. + * May loose one frame + */ + vpfe_dev->field_id = fid; + } +clear_intr: + if (vpfe_dev->cfg->clr_intr) + vpfe_dev->cfg->clr_intr(irq); + + return IRQ_HANDLED; +} + +/* vdint1_isr - isr handler for VINT1 interrupt */ +static irqreturn_t vdint1_isr(int irq, void *dev_id) +{ + struct vpfe_device *vpfe_dev = dev_id; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "\nInside vdint1_isr...\n"); + + /* if streaming not started, don't do anything */ + if (!vpfe_dev->started) { + if (vpfe_dev->cfg->clr_intr) + vpfe_dev->cfg->clr_intr(irq); + return IRQ_HANDLED; + } + + spin_lock(&vpfe_dev->dma_queue_lock); + if ((vpfe_dev->fmt.fmt.pix.field == V4L2_FIELD_NONE) && + !list_empty(&vpfe_dev->dma_queue) && + vpfe_dev->cur_frm == vpfe_dev->next_frm) + vpfe_schedule_next_buffer(vpfe_dev); + spin_unlock(&vpfe_dev->dma_queue_lock); + + if (vpfe_dev->cfg->clr_intr) + vpfe_dev->cfg->clr_intr(irq); + + return IRQ_HANDLED; +} + +static void vpfe_detach_irq(struct vpfe_device *vpfe_dev) +{ + enum ccdc_frmfmt frame_format; + + frame_format = ccdc_dev->hw_ops.get_frame_format(); + if (frame_format == CCDC_FRMFMT_PROGRESSIVE) + free_irq(vpfe_dev->ccdc_irq1, vpfe_dev); +} + +static int vpfe_attach_irq(struct vpfe_device *vpfe_dev) +{ + enum ccdc_frmfmt frame_format; + + frame_format = ccdc_dev->hw_ops.get_frame_format(); + if (frame_format == CCDC_FRMFMT_PROGRESSIVE) { + return request_irq(vpfe_dev->ccdc_irq1, vdint1_isr, + IRQF_DISABLED, "vpfe_capture1", + vpfe_dev); + } + return 0; +} + +/* vpfe_stop_ccdc_capture: stop streaming in ccdc/isif */ +static void vpfe_stop_ccdc_capture(struct vpfe_device *vpfe_dev) +{ + vpfe_dev->started = 0; + ccdc_dev->hw_ops.enable(0); + if (ccdc_dev->hw_ops.enable_out_to_sdram) + ccdc_dev->hw_ops.enable_out_to_sdram(0); +} + +/* + * vpfe_release : This function deletes buffer queue, frees the + * buffers and the vpfe file handle + */ +static int vpfe_release(struct file *file) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_fh *fh = file->private_data; + struct vpfe_subdev_info *sdinfo; + int ret; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_release\n"); + + /* Get the device lock */ + mutex_lock(&vpfe_dev->lock); + /* if this instance is doing IO */ + if (fh->io_allowed) { + if (vpfe_dev->started) { + sdinfo = vpfe_dev->current_subdev; + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, + sdinfo->grp_id, + video, s_stream, 0); + if (ret && (ret != -ENOIOCTLCMD)) + v4l2_err(&vpfe_dev->v4l2_dev, + "stream off failed in subdev\n"); + vpfe_stop_ccdc_capture(vpfe_dev); + vpfe_detach_irq(vpfe_dev); + videobuf_streamoff(&vpfe_dev->buffer_queue); + } + vpfe_dev->io_usrs = 0; + vpfe_dev->numbuffers = config_params.numbuffers; + } + + /* Decrement device usrs counter */ + vpfe_dev->usrs--; + /* Close the priority */ + v4l2_prio_close(&vpfe_dev->prio, fh->prio); + /* If this is the last file handle */ + if (!vpfe_dev->usrs) { + vpfe_dev->initialized = 0; + if (ccdc_dev->hw_ops.close) + ccdc_dev->hw_ops.close(vpfe_dev->pdev); + module_put(ccdc_dev->owner); + } + mutex_unlock(&vpfe_dev->lock); + file->private_data = NULL; + /* Free memory allocated to file handle object */ + kfree(fh); + return 0; +} + +/* + * vpfe_mmap : It is used to map kernel space buffers + * into user spaces + */ +static int vpfe_mmap(struct file *file, struct vm_area_struct *vma) +{ + /* Get the device object and file handle object */ + struct vpfe_device *vpfe_dev = video_drvdata(file); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_mmap\n"); + + return videobuf_mmap_mapper(&vpfe_dev->buffer_queue, vma); +} + +/* + * vpfe_poll: It is used for select/poll system call + */ +static unsigned int vpfe_poll(struct file *file, poll_table *wait) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_poll\n"); + + if (vpfe_dev->started) + return videobuf_poll_stream(file, + &vpfe_dev->buffer_queue, wait); + return 0; +} + +/* vpfe capture driver file operations */ +static const struct v4l2_file_operations vpfe_fops = { + .owner = THIS_MODULE, + .open = vpfe_open, + .release = vpfe_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vpfe_mmap, + .poll = vpfe_poll +}; + +/* + * vpfe_check_format() + * This function adjust the input pixel format as per hardware + * capabilities and update the same in pixfmt. + * Following algorithm used :- + * + * If given pixformat is not in the vpfe list of pix formats or not + * supported by the hardware, current value of pixformat in the device + * is used + * If given field is not supported, then current field is used. If field + * is different from current, then it is matched with that from sub device. + * Minimum height is 2 lines for interlaced or tb field and 1 line for + * progressive. Maximum height is clamped to active active lines of scan + * Minimum width is 32 bytes in memory and width is clamped to active + * pixels of scan. + * bytesperline is a multiple of 32. + */ +static const struct vpfe_pixel_format * + vpfe_check_format(struct vpfe_device *vpfe_dev, + struct v4l2_pix_format *pixfmt) +{ + u32 min_height = 1, min_width = 32, max_width, max_height; + const struct vpfe_pixel_format *vpfe_pix_fmt; + u32 pix; + int temp, found; + + vpfe_pix_fmt = vpfe_lookup_pix_format(pixfmt->pixelformat); + if (NULL == vpfe_pix_fmt) { + /* + * use current pixel format in the vpfe device. We + * will find this pix format in the table + */ + pixfmt->pixelformat = vpfe_dev->fmt.fmt.pix.pixelformat; + vpfe_pix_fmt = vpfe_lookup_pix_format(pixfmt->pixelformat); + } + + /* check if hw supports it */ + temp = 0; + found = 0; + while (ccdc_dev->hw_ops.enum_pix(&pix, temp) >= 0) { + if (vpfe_pix_fmt->fmtdesc.pixelformat == pix) { + found = 1; + break; + } + temp++; + } + + if (!found) { + /* use current pixel format */ + pixfmt->pixelformat = vpfe_dev->fmt.fmt.pix.pixelformat; + /* + * Since this is currently used in the vpfe device, we + * will find this pix format in the table + */ + vpfe_pix_fmt = vpfe_lookup_pix_format(pixfmt->pixelformat); + } + + /* check what field format is supported */ + if (pixfmt->field == V4L2_FIELD_ANY) { + /* if field is any, use current value as default */ + pixfmt->field = vpfe_dev->fmt.fmt.pix.field; + } + + /* + * if field is not same as current field in the vpfe device + * try matching the field with the sub device field + */ + if (vpfe_dev->fmt.fmt.pix.field != pixfmt->field) { + /* + * If field value is not in the supported fields, use current + * field used in the device as default + */ + switch (pixfmt->field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + /* if sub device is supporting progressive, use that */ + if (!vpfe_dev->std_info.frame_format) + pixfmt->field = V4L2_FIELD_NONE; + break; + case V4L2_FIELD_NONE: + if (vpfe_dev->std_info.frame_format) + pixfmt->field = V4L2_FIELD_INTERLACED; + break; + + default: + /* use current field as default */ + pixfmt->field = vpfe_dev->fmt.fmt.pix.field; + break; + } + } + + /* Now adjust image resolutions supported */ + if (pixfmt->field == V4L2_FIELD_INTERLACED || + pixfmt->field == V4L2_FIELD_SEQ_TB) + min_height = 2; + + max_width = vpfe_dev->std_info.active_pixels; + max_height = vpfe_dev->std_info.active_lines; + min_width /= vpfe_pix_fmt->bpp; + + v4l2_info(&vpfe_dev->v4l2_dev, "width = %d, height = %d, bpp = %d\n", + pixfmt->width, pixfmt->height, vpfe_pix_fmt->bpp); + + pixfmt->width = clamp((pixfmt->width), min_width, max_width); + pixfmt->height = clamp((pixfmt->height), min_height, max_height); + + /* If interlaced, adjust height to be a multiple of 2 */ + if (pixfmt->field == V4L2_FIELD_INTERLACED) + pixfmt->height &= (~1); + /* + * recalculate bytesperline and sizeimage since width + * and height might have changed + */ + pixfmt->bytesperline = (((pixfmt->width * vpfe_pix_fmt->bpp) + 31) + & ~31); + if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) + pixfmt->sizeimage = + pixfmt->bytesperline * pixfmt->height + + ((pixfmt->bytesperline * pixfmt->height) >> 1); + else + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + + v4l2_info(&vpfe_dev->v4l2_dev, "adjusted width = %d, height =" + " %d, bpp = %d, bytesperline = %d, sizeimage = %d\n", + pixfmt->width, pixfmt->height, vpfe_pix_fmt->bpp, + pixfmt->bytesperline, pixfmt->sizeimage); + return vpfe_pix_fmt; +} + +static int vpfe_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querycap\n"); + + cap->version = VPFE_CAPTURE_VERSION_CODE; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + strlcpy(cap->driver, CAPTURE_DRV_NAME, sizeof(cap->driver)); + strlcpy(cap->bus_info, "VPFE", sizeof(cap->bus_info)); + strlcpy(cap->card, vpfe_dev->cfg->card_name, sizeof(cap->card)); + return 0; +} + +static int vpfe_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + int ret = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_fmt_vid_cap\n"); + /* Fill in the information about format */ + *fmt = vpfe_dev->fmt; + return ret; +} + +static int vpfe_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + const struct vpfe_pixel_format *pix_fmt; + int temp_index; + u32 pix; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_fmt_vid_cap\n"); + + if (ccdc_dev->hw_ops.enum_pix(&pix, fmt->index) < 0) + return -EINVAL; + + /* Fill in the information about format */ + pix_fmt = vpfe_lookup_pix_format(pix); + if (NULL != pix_fmt) { + temp_index = fmt->index; + *fmt = pix_fmt->fmtdesc; + fmt->index = temp_index; + return 0; + } + return -EINVAL; +} + +static int vpfe_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + const struct vpfe_pixel_format *pix_fmts; + int ret = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_fmt_vid_cap\n"); + + /* If streaming is started, return error */ + if (vpfe_dev->started) { + v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is started\n"); + return -EBUSY; + } + + /* Check for valid frame format */ + pix_fmts = vpfe_check_format(vpfe_dev, &fmt->fmt.pix); + + if (NULL == pix_fmts) + return -EINVAL; + + /* store the pixel format in the device object */ + ret = mutex_lock_interruptible(&vpfe_dev->lock); + if (ret) + return ret; + + /* First detach any IRQ if currently attached */ + vpfe_detach_irq(vpfe_dev); + vpfe_dev->fmt = *fmt; + /* set image capture parameters in the ccdc */ + ret = vpfe_config_ccdc_image_format(vpfe_dev); + mutex_unlock(&vpfe_dev->lock); + return ret; +} + +static int vpfe_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + const struct vpfe_pixel_format *pix_fmts; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_try_fmt_vid_cap\n"); + + pix_fmts = vpfe_check_format(vpfe_dev, &f->fmt.pix); + if (NULL == pix_fmts) + return -EINVAL; + return 0; +} + +/* + * vpfe_get_subdev_input_index - Get subdev index and subdev input index for a + * given app input index + */ +static int vpfe_get_subdev_input_index(struct vpfe_device *vpfe_dev, + int *subdev_index, + int *subdev_input_index, + int app_input_index) +{ + struct vpfe_config *cfg = vpfe_dev->cfg; + struct vpfe_subdev_info *sdinfo; + int i, j = 0; + + for (i = 0; i < cfg->num_subdevs; i++) { + sdinfo = &cfg->sub_devs[i]; + if (app_input_index < (j + sdinfo->num_inputs)) { + *subdev_index = i; + *subdev_input_index = app_input_index - j; + return 0; + } + j += sdinfo->num_inputs; + } + return -EINVAL; +} + +/* + * vpfe_get_app_input - Get app input index for a given subdev input index + * driver stores the input index of the current sub device and translate it + * when application request the current input + */ +static int vpfe_get_app_input_index(struct vpfe_device *vpfe_dev, + int *app_input_index) +{ + struct vpfe_config *cfg = vpfe_dev->cfg; + struct vpfe_subdev_info *sdinfo; + int i, j = 0; + + for (i = 0; i < cfg->num_subdevs; i++) { + sdinfo = &cfg->sub_devs[i]; + if (!strcmp(sdinfo->name, vpfe_dev->current_subdev->name)) { + if (vpfe_dev->current_input >= sdinfo->num_inputs) + return -1; + *app_input_index = j + vpfe_dev->current_input; + return 0; + } + j += sdinfo->num_inputs; + } + return -EINVAL; +} + +static int vpfe_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + int subdev, index ; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_input\n"); + + if (vpfe_get_subdev_input_index(vpfe_dev, + &subdev, + &index, + inp->index) < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, "input information not found" + " for the subdev\n"); + return -EINVAL; + } + sdinfo = &vpfe_dev->cfg->sub_devs[subdev]; + memcpy(inp, &sdinfo->inputs[index], sizeof(struct v4l2_input)); + return 0; +} + +static int vpfe_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_input\n"); + + return vpfe_get_app_input_index(vpfe_dev, index); +} + + +static int vpfe_s_input(struct file *file, void *priv, unsigned int index) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + int subdev_index, inp_index; + struct vpfe_route *route; + u32 input = 0, output = 0; + int ret = -EINVAL; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_input\n"); + + ret = mutex_lock_interruptible(&vpfe_dev->lock); + if (ret) + return ret; + + /* + * If streaming is started return device busy + * error + */ + if (vpfe_dev->started) { + v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is on\n"); + ret = -EBUSY; + goto unlock_out; + } + ret = vpfe_get_subdev_input_index(vpfe_dev, + &subdev_index, + &inp_index, + index); + if (ret < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, "invalid input index\n"); + goto unlock_out; + } + + sdinfo = &vpfe_dev->cfg->sub_devs[subdev_index]; + route = &sdinfo->routes[inp_index]; + if (route && sdinfo->can_route) { + input = route->input; + output = route->output; + } + + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + video, s_routing, input, output, 0); + + if (ret) { + v4l2_err(&vpfe_dev->v4l2_dev, + "vpfe_doioctl:error in setting input in decoder\n"); + ret = -EINVAL; + goto unlock_out; + } + vpfe_dev->current_subdev = sdinfo; + vpfe_dev->current_input = index; + vpfe_dev->std_index = 0; + + /* set the bus/interface parameter for the sub device in ccdc */ + ret = ccdc_dev->hw_ops.set_hw_if_params(&sdinfo->ccdc_if_params); + if (ret) + goto unlock_out; + + /* set the default image parameters in the device */ + ret = vpfe_config_image_format(vpfe_dev, + &vpfe_standards[vpfe_dev->std_index].std_id); +unlock_out: + mutex_unlock(&vpfe_dev->lock); + return ret; +} + +static int vpfe_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + int ret = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querystd\n"); + + ret = mutex_lock_interruptible(&vpfe_dev->lock); + sdinfo = vpfe_dev->current_subdev; + if (ret) + return ret; + /* Call querystd function of decoder device */ + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + video, querystd, std_id); + mutex_unlock(&vpfe_dev->lock); + return ret; +} + +static int vpfe_s_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + int ret = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_std\n"); + + /* Call decoder driver function to set the standard */ + ret = mutex_lock_interruptible(&vpfe_dev->lock); + if (ret) + return ret; + + sdinfo = vpfe_dev->current_subdev; + /* If streaming is started, return device busy error */ + if (vpfe_dev->started) { + v4l2_err(&vpfe_dev->v4l2_dev, "streaming is started\n"); + ret = -EBUSY; + goto unlock_out; + } + + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + core, s_std, *std_id); + if (ret < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, "Failed to set standard\n"); + goto unlock_out; + } + ret = vpfe_config_image_format(vpfe_dev, std_id); + +unlock_out: + mutex_unlock(&vpfe_dev->lock); + return ret; +} + +static int vpfe_g_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_std\n"); + + *std_id = vpfe_standards[vpfe_dev->std_index].std_id; + return 0; +} +/* + * Videobuf operations + */ +static int vpfe_videobuf_setup(struct videobuf_queue *vq, + unsigned int *count, + unsigned int *size) +{ + struct vpfe_fh *fh = vq->priv_data; + struct vpfe_device *vpfe_dev = fh->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_setup\n"); + *size = vpfe_dev->fmt.fmt.pix.sizeimage; + if (vpfe_dev->memory == V4L2_MEMORY_MMAP && + vpfe_dev->fmt.fmt.pix.sizeimage > config_params.device_bufsize) + *size = config_params.device_bufsize; + + if (*count < config_params.min_numbuffers) + *count = config_params.min_numbuffers; + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "count=%d, size=%d\n", *count, *size); + return 0; +} + +static int vpfe_videobuf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct vpfe_fh *fh = vq->priv_data; + struct vpfe_device *vpfe_dev = fh->vpfe_dev; + unsigned long addr; + int ret; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_prepare\n"); + + /* If buffer is not initialized, initialize it */ + if (VIDEOBUF_NEEDS_INIT == vb->state) { + vb->width = vpfe_dev->fmt.fmt.pix.width; + vb->height = vpfe_dev->fmt.fmt.pix.height; + vb->size = vpfe_dev->fmt.fmt.pix.sizeimage; + vb->field = field; + + ret = videobuf_iolock(vq, vb, NULL); + if (ret < 0) + return ret; + + addr = videobuf_to_dma_contig(vb); + /* Make sure user addresses are aligned to 32 bytes */ + if (!ALIGN(addr, 32)) + return -EINVAL; + + vb->state = VIDEOBUF_PREPARED; + } + return 0; +} + +static void vpfe_videobuf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + /* Get the file handle object and device object */ + struct vpfe_fh *fh = vq->priv_data; + struct vpfe_device *vpfe_dev = fh->vpfe_dev; + unsigned long flags; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_queue\n"); + + /* add the buffer to the DMA queue */ + spin_lock_irqsave(&vpfe_dev->dma_queue_lock, flags); + list_add_tail(&vb->queue, &vpfe_dev->dma_queue); + spin_unlock_irqrestore(&vpfe_dev->dma_queue_lock, flags); + + /* Change state of the buffer */ + vb->state = VIDEOBUF_QUEUED; +} + +static void vpfe_videobuf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct vpfe_fh *fh = vq->priv_data; + struct vpfe_device *vpfe_dev = fh->vpfe_dev; + unsigned long flags; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_videobuf_release\n"); + + /* + * We need to flush the buffer from the dma queue since + * they are de-allocated + */ + spin_lock_irqsave(&vpfe_dev->dma_queue_lock, flags); + INIT_LIST_HEAD(&vpfe_dev->dma_queue); + spin_unlock_irqrestore(&vpfe_dev->dma_queue_lock, flags); + videobuf_dma_contig_free(vq, vb); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static struct videobuf_queue_ops vpfe_videobuf_qops = { + .buf_setup = vpfe_videobuf_setup, + .buf_prepare = vpfe_videobuf_prepare, + .buf_queue = vpfe_videobuf_queue, + .buf_release = vpfe_videobuf_release, +}; + +/* + * vpfe_reqbufs. currently support REQBUF only once opening + * the device. + */ +static int vpfe_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req_buf) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_fh *fh = file->private_data; + int ret = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_reqbufs\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != req_buf->type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buffer type\n"); + return -EINVAL; + } + + ret = mutex_lock_interruptible(&vpfe_dev->lock); + if (ret) + return ret; + + if (vpfe_dev->io_usrs != 0) { + v4l2_err(&vpfe_dev->v4l2_dev, "Only one IO user allowed\n"); + ret = -EBUSY; + goto unlock_out; + } + + vpfe_dev->memory = req_buf->memory; + videobuf_queue_dma_contig_init(&vpfe_dev->buffer_queue, + &vpfe_videobuf_qops, + vpfe_dev->pdev, + &vpfe_dev->irqlock, + req_buf->type, + vpfe_dev->fmt.fmt.pix.field, + sizeof(struct videobuf_buffer), + fh, NULL); + + fh->io_allowed = 1; + vpfe_dev->io_usrs = 1; + INIT_LIST_HEAD(&vpfe_dev->dma_queue); + ret = videobuf_reqbufs(&vpfe_dev->buffer_queue, req_buf); +unlock_out: + mutex_unlock(&vpfe_dev->lock); + return ret; +} + +static int vpfe_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querybuf\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + + if (vpfe_dev->memory != V4L2_MEMORY_MMAP) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid memory\n"); + return -EINVAL; + } + /* Call videobuf_querybuf to get information */ + return videobuf_querybuf(&vpfe_dev->buffer_queue, buf); +} + +static int vpfe_qbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_fh *fh = file->private_data; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_qbuf\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != p->type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + + /* + * If this file handle is not allowed to do IO, + * return error + */ + if (!fh->io_allowed) { + v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n"); + return -EACCES; + } + return videobuf_qbuf(&vpfe_dev->buffer_queue, p); +} + +static int vpfe_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_dqbuf\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + return videobuf_dqbuf(&vpfe_dev->buffer_queue, + buf, file->f_flags & O_NONBLOCK); +} + +static int vpfe_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qctrl) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + + sdinfo = vpfe_dev->current_subdev; + + return v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + core, queryctrl, qctrl); + +} + +static int vpfe_g_ctrl(struct file *file, void *priv, struct v4l2_control *ctrl) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + + sdinfo = vpfe_dev->current_subdev; + + return v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + core, g_ctrl, ctrl); +} + +static int vpfe_s_ctrl(struct file *file, void *priv, struct v4l2_control *ctrl) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + + sdinfo = vpfe_dev->current_subdev; + + return v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + core, s_ctrl, ctrl); +} + +/* + * vpfe_calculate_offsets : This function calculates buffers offset + * for top and bottom field + */ +static void vpfe_calculate_offsets(struct vpfe_device *vpfe_dev) +{ + struct v4l2_rect image_win; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_calculate_offsets\n"); + + ccdc_dev->hw_ops.get_image_window(&image_win); + vpfe_dev->field_off = image_win.height * image_win.width; +} + +/* vpfe_start_ccdc_capture: start streaming in ccdc/isif */ +static void vpfe_start_ccdc_capture(struct vpfe_device *vpfe_dev) +{ + ccdc_dev->hw_ops.enable(1); + if (ccdc_dev->hw_ops.enable_out_to_sdram) + ccdc_dev->hw_ops.enable_out_to_sdram(1); + vpfe_dev->started = 1; +} + +/* + * vpfe_streamon. Assume the DMA queue is not empty. + * application is expected to call QBUF before calling + * this ioctl. If not, driver returns error + */ +static int vpfe_streamon(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_fh *fh = file->private_data; + struct vpfe_subdev_info *sdinfo; + unsigned long addr; + int ret = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamon\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf_type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + + /* If file handle is not allowed IO, return error */ + if (!fh->io_allowed) { + v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n"); + return -EACCES; + } + + sdinfo = vpfe_dev->current_subdev; + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + video, s_stream, 1); + + if (ret && (ret != -ENOIOCTLCMD)) { + v4l2_err(&vpfe_dev->v4l2_dev, "stream on failed in subdev\n"); + return -EINVAL; + } + + /* If buffer queue is empty, return error */ + if (list_empty(&vpfe_dev->buffer_queue.stream)) { + v4l2_err(&vpfe_dev->v4l2_dev, "buffer queue is empty\n"); + return -EIO; + } + + /* Call videobuf_streamon to start streaming * in videobuf */ + ret = videobuf_streamon(&vpfe_dev->buffer_queue); + if (ret) + return ret; + + + ret = mutex_lock_interruptible(&vpfe_dev->lock); + if (ret) + goto streamoff; + /* Get the next frame from the buffer queue */ + vpfe_dev->next_frm = list_entry(vpfe_dev->dma_queue.next, + struct videobuf_buffer, queue); + vpfe_dev->cur_frm = vpfe_dev->next_frm; + /* Remove buffer from the buffer queue */ + list_del(&vpfe_dev->cur_frm->queue); + /* Mark state of the current frame to active */ + vpfe_dev->cur_frm->state = VIDEOBUF_ACTIVE; + /* Initialize field_id and started member */ + vpfe_dev->field_id = 0; + addr = videobuf_to_dma_contig(vpfe_dev->cur_frm); + + /* Calculate field offset */ + vpfe_calculate_offsets(vpfe_dev); + + if (vpfe_attach_irq(vpfe_dev) < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, + "Error in attaching interrupt handle\n"); + ret = -EFAULT; + goto unlock_out; + } + if (ccdc_dev->hw_ops.configure() < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, + "Error in configuring ccdc\n"); + ret = -EINVAL; + goto unlock_out; + } + ccdc_dev->hw_ops.setfbaddr((unsigned long)(addr)); + vpfe_start_ccdc_capture(vpfe_dev); + mutex_unlock(&vpfe_dev->lock); + return ret; +unlock_out: + mutex_unlock(&vpfe_dev->lock); +streamoff: + ret = videobuf_streamoff(&vpfe_dev->buffer_queue); + return ret; +} + +static int vpfe_streamoff(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + struct vpfe_fh *fh = file->private_data; + struct vpfe_subdev_info *sdinfo; + int ret = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamoff\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf_type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + + /* If io is allowed for this file handle, return error */ + if (!fh->io_allowed) { + v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n"); + return -EACCES; + } + + /* If streaming is not started, return error */ + if (!vpfe_dev->started) { + v4l2_err(&vpfe_dev->v4l2_dev, "device started\n"); + return -EINVAL; + } + + ret = mutex_lock_interruptible(&vpfe_dev->lock); + if (ret) + return ret; + + vpfe_stop_ccdc_capture(vpfe_dev); + vpfe_detach_irq(vpfe_dev); + + sdinfo = vpfe_dev->current_subdev; + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + video, s_stream, 0); + + if (ret && (ret != -ENOIOCTLCMD)) + v4l2_err(&vpfe_dev->v4l2_dev, "stream off failed in subdev\n"); + ret = videobuf_streamoff(&vpfe_dev->buffer_queue); + mutex_unlock(&vpfe_dev->lock); + return ret; +} + +static int vpfe_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *crop) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_cropcap\n"); + + if (vpfe_dev->std_index >= ARRAY_SIZE(vpfe_standards)) + return -EINVAL; + + memset(crop, 0, sizeof(struct v4l2_cropcap)); + crop->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop->bounds.width = crop->defrect.width = + vpfe_standards[vpfe_dev->std_index].width; + crop->bounds.height = crop->defrect.height = + vpfe_standards[vpfe_dev->std_index].height; + crop->pixelaspect = vpfe_standards[vpfe_dev->std_index].pixelaspect; + return 0; +} + +static int vpfe_g_crop(struct file *file, void *priv, + struct v4l2_crop *crop) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_crop\n"); + + crop->c = vpfe_dev->crop; + return 0; +} + +static int vpfe_s_crop(struct file *file, void *priv, + const struct v4l2_crop *crop) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + int ret = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_crop\n"); + + if (vpfe_dev->started) { + /* make sure streaming is not started */ + v4l2_err(&vpfe_dev->v4l2_dev, + "Cannot change crop when streaming is ON\n"); + return -EBUSY; + } + + ret = mutex_lock_interruptible(&vpfe_dev->lock); + if (ret) + return ret; + + if (crop->c.top < 0 || crop->c.left < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, + "doesn't support negative values for top & left\n"); + ret = -EINVAL; + goto unlock_out; + } + + /* adjust the width to 16 pixel boundary */ + crop->c.width = ((crop->c.width + 15) & ~0xf); + + /* make sure parameters are valid */ + if ((crop->c.left + crop->c.width > + vpfe_dev->std_info.active_pixels) || + (crop->c.top + crop->c.height > + vpfe_dev->std_info.active_lines)) { + v4l2_err(&vpfe_dev->v4l2_dev, "Error in S_CROP params\n"); + ret = -EINVAL; + goto unlock_out; + } + ccdc_dev->hw_ops.set_image_window(&crop->c); + vpfe_dev->fmt.fmt.pix.width = crop->c.width; + vpfe_dev->fmt.fmt.pix.height = crop->c.height; + vpfe_dev->fmt.fmt.pix.bytesperline = + ccdc_dev->hw_ops.get_line_length(); + vpfe_dev->fmt.fmt.pix.sizeimage = + vpfe_dev->fmt.fmt.pix.bytesperline * + vpfe_dev->fmt.fmt.pix.height; + vpfe_dev->crop = crop->c; +unlock_out: + mutex_unlock(&vpfe_dev->lock); + return ret; +} + + +static long vpfe_param_handler(struct file *file, void *priv, + bool valid_prio, int cmd, void *param) +{ + struct vpfe_device *vpfe_dev = video_drvdata(file); + int ret = 0; + + v4l2_dbg(2, debug, &vpfe_dev->v4l2_dev, "vpfe_param_handler\n"); + + if (vpfe_dev->started) { + /* only allowed if streaming is not started */ + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "device already started\n"); + return -EBUSY; + } + + ret = mutex_lock_interruptible(&vpfe_dev->lock); + if (ret) + return ret; + + switch (cmd) { + case VPFE_CMD_S_CCDC_RAW_PARAMS: + v4l2_warn(&vpfe_dev->v4l2_dev, + "VPFE_CMD_S_CCDC_RAW_PARAMS: experimental ioctl\n"); + if (ccdc_dev->hw_ops.set_params) { + ret = ccdc_dev->hw_ops.set_params(param); + if (ret) { + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "Error setting parameters in CCDC\n"); + goto unlock_out; + } + ret = vpfe_get_ccdc_image_format(vpfe_dev, + &vpfe_dev->fmt); + if (ret < 0) { + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "Invalid image format at CCDC\n"); + goto unlock_out; + } + } else { + ret = -EINVAL; + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "VPFE_CMD_S_CCDC_RAW_PARAMS not supported\n"); + } + break; + default: + ret = -ENOTTY; + } +unlock_out: + mutex_unlock(&vpfe_dev->lock); + return ret; +} + + +/* vpfe capture ioctl operations */ +static const struct v4l2_ioctl_ops vpfe_ioctl_ops = { + .vidioc_querycap = vpfe_querycap, + .vidioc_g_fmt_vid_cap = vpfe_g_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = vpfe_enum_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vpfe_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vpfe_try_fmt_vid_cap, + .vidioc_enum_input = vpfe_enum_input, + .vidioc_g_input = vpfe_g_input, + .vidioc_s_input = vpfe_s_input, + .vidioc_querystd = vpfe_querystd, + .vidioc_s_std = vpfe_s_std, + .vidioc_g_std = vpfe_g_std, + .vidioc_queryctrl = vpfe_queryctrl, + .vidioc_g_ctrl = vpfe_g_ctrl, + .vidioc_s_ctrl = vpfe_s_ctrl, + .vidioc_reqbufs = vpfe_reqbufs, + .vidioc_querybuf = vpfe_querybuf, + .vidioc_qbuf = vpfe_qbuf, + .vidioc_dqbuf = vpfe_dqbuf, + .vidioc_streamon = vpfe_streamon, + .vidioc_streamoff = vpfe_streamoff, + .vidioc_cropcap = vpfe_cropcap, + .vidioc_g_crop = vpfe_g_crop, + .vidioc_s_crop = vpfe_s_crop, + .vidioc_default = vpfe_param_handler, +}; + +static struct vpfe_device *vpfe_initialize(void) +{ + struct vpfe_device *vpfe_dev; + + /* Default number of buffers should be 3 */ + if ((numbuffers > 0) && + (numbuffers < config_params.min_numbuffers)) + numbuffers = config_params.min_numbuffers; + + /* + * Set buffer size to min buffers size if invalid buffer size is + * given + */ + if (bufsize < config_params.min_bufsize) + bufsize = config_params.min_bufsize; + + config_params.numbuffers = numbuffers; + + if (numbuffers) + config_params.device_bufsize = bufsize; + + /* Allocate memory for device objects */ + vpfe_dev = kzalloc(sizeof(*vpfe_dev), GFP_KERNEL); + + return vpfe_dev; +} + +/* + * vpfe_probe : This function creates device entries by register + * itself to the V4L2 driver and initializes fields of each + * device objects + */ +static __devinit int vpfe_probe(struct platform_device *pdev) +{ + struct vpfe_subdev_info *sdinfo; + struct vpfe_config *vpfe_cfg; + struct resource *res1; + struct vpfe_device *vpfe_dev; + struct i2c_adapter *i2c_adap; + struct video_device *vfd; + int ret = -ENOMEM, i, j; + int num_subdevs = 0; + + /* Get the pointer to the device object */ + vpfe_dev = vpfe_initialize(); + + if (!vpfe_dev) { + v4l2_err(pdev->dev.driver, + "Failed to allocate memory for vpfe_dev\n"); + return ret; + } + + vpfe_dev->pdev = &pdev->dev; + + if (NULL == pdev->dev.platform_data) { + v4l2_err(pdev->dev.driver, "Unable to get vpfe config\n"); + ret = -ENODEV; + goto probe_free_dev_mem; + } + + vpfe_cfg = pdev->dev.platform_data; + vpfe_dev->cfg = vpfe_cfg; + if (NULL == vpfe_cfg->ccdc || + NULL == vpfe_cfg->card_name || + NULL == vpfe_cfg->sub_devs) { + v4l2_err(pdev->dev.driver, "null ptr in vpfe_cfg\n"); + ret = -ENOENT; + goto probe_free_dev_mem; + } + + /* Allocate memory for ccdc configuration */ + ccdc_cfg = kmalloc(sizeof(struct ccdc_config), GFP_KERNEL); + if (NULL == ccdc_cfg) { + v4l2_err(pdev->dev.driver, + "Memory allocation failed for ccdc_cfg\n"); + goto probe_free_lock; + } + + mutex_lock(&ccdc_lock); + + strncpy(ccdc_cfg->name, vpfe_cfg->ccdc, 32); + /* Get VINT0 irq resource */ + res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res1) { + v4l2_err(pdev->dev.driver, + "Unable to get interrupt for VINT0\n"); + ret = -ENODEV; + goto probe_free_ccdc_cfg_mem; + } + vpfe_dev->ccdc_irq0 = res1->start; + + /* Get VINT1 irq resource */ + res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + if (!res1) { + v4l2_err(pdev->dev.driver, + "Unable to get interrupt for VINT1\n"); + ret = -ENODEV; + goto probe_free_ccdc_cfg_mem; + } + vpfe_dev->ccdc_irq1 = res1->start; + + ret = request_irq(vpfe_dev->ccdc_irq0, vpfe_isr, IRQF_DISABLED, + "vpfe_capture0", vpfe_dev); + + if (0 != ret) { + v4l2_err(pdev->dev.driver, "Unable to request interrupt\n"); + goto probe_free_ccdc_cfg_mem; + } + + /* Allocate memory for video device */ + vfd = video_device_alloc(); + if (NULL == vfd) { + ret = -ENOMEM; + v4l2_err(pdev->dev.driver, "Unable to alloc video device\n"); + goto probe_out_release_irq; + } + + /* Initialize field of video device */ + vfd->release = video_device_release; + vfd->fops = &vpfe_fops; + vfd->ioctl_ops = &vpfe_ioctl_ops; + vfd->tvnorms = 0; + vfd->current_norm = V4L2_STD_PAL; + vfd->v4l2_dev = &vpfe_dev->v4l2_dev; + snprintf(vfd->name, sizeof(vfd->name), + "%s_V%d.%d.%d", + CAPTURE_DRV_NAME, + (VPFE_CAPTURE_VERSION_CODE >> 16) & 0xff, + (VPFE_CAPTURE_VERSION_CODE >> 8) & 0xff, + (VPFE_CAPTURE_VERSION_CODE) & 0xff); + /* Set video_dev to the video device */ + vpfe_dev->video_dev = vfd; + + ret = v4l2_device_register(&pdev->dev, &vpfe_dev->v4l2_dev); + if (ret) { + v4l2_err(pdev->dev.driver, + "Unable to register v4l2 device.\n"); + goto probe_out_video_release; + } + v4l2_info(&vpfe_dev->v4l2_dev, "v4l2 device registered\n"); + spin_lock_init(&vpfe_dev->irqlock); + spin_lock_init(&vpfe_dev->dma_queue_lock); + mutex_init(&vpfe_dev->lock); + + /* Initialize field of the device objects */ + vpfe_dev->numbuffers = config_params.numbuffers; + + /* Initialize prio member of device object */ + v4l2_prio_init(&vpfe_dev->prio); + /* register video device */ + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "trying to register vpfe device.\n"); + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "video_dev=%x\n", (int)&vpfe_dev->video_dev); + vpfe_dev->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = video_register_device(vpfe_dev->video_dev, + VFL_TYPE_GRABBER, -1); + + if (ret) { + v4l2_err(pdev->dev.driver, + "Unable to register video device.\n"); + goto probe_out_v4l2_unregister; + } + + v4l2_info(&vpfe_dev->v4l2_dev, "video device registered\n"); + /* set the driver data in platform device */ + platform_set_drvdata(pdev, vpfe_dev); + /* set driver private data */ + video_set_drvdata(vpfe_dev->video_dev, vpfe_dev); + i2c_adap = i2c_get_adapter(vpfe_cfg->i2c_adapter_id); + num_subdevs = vpfe_cfg->num_subdevs; + vpfe_dev->sd = kmalloc(sizeof(struct v4l2_subdev *) * num_subdevs, + GFP_KERNEL); + if (NULL == vpfe_dev->sd) { + v4l2_err(&vpfe_dev->v4l2_dev, + "unable to allocate memory for subdevice pointers\n"); + ret = -ENOMEM; + goto probe_out_video_unregister; + } + + for (i = 0; i < num_subdevs; i++) { + struct v4l2_input *inps; + + sdinfo = &vpfe_cfg->sub_devs[i]; + + /* Load up the subdevice */ + vpfe_dev->sd[i] = + v4l2_i2c_new_subdev_board(&vpfe_dev->v4l2_dev, + i2c_adap, + &sdinfo->board_info, + NULL); + if (vpfe_dev->sd[i]) { + v4l2_info(&vpfe_dev->v4l2_dev, + "v4l2 sub device %s registered\n", + sdinfo->name); + vpfe_dev->sd[i]->grp_id = sdinfo->grp_id; + /* update tvnorms from the sub devices */ + for (j = 0; j < sdinfo->num_inputs; j++) { + inps = &sdinfo->inputs[j]; + vfd->tvnorms |= inps->std; + } + } else { + v4l2_info(&vpfe_dev->v4l2_dev, + "v4l2 sub device %s register fails\n", + sdinfo->name); + goto probe_sd_out; + } + } + + /* set first sub device as current one */ + vpfe_dev->current_subdev = &vpfe_cfg->sub_devs[0]; + + /* We have at least one sub device to work with */ + mutex_unlock(&ccdc_lock); + return 0; + +probe_sd_out: + kfree(vpfe_dev->sd); +probe_out_video_unregister: + video_unregister_device(vpfe_dev->video_dev); +probe_out_v4l2_unregister: + v4l2_device_unregister(&vpfe_dev->v4l2_dev); +probe_out_video_release: + if (!video_is_registered(vpfe_dev->video_dev)) + video_device_release(vpfe_dev->video_dev); +probe_out_release_irq: + free_irq(vpfe_dev->ccdc_irq0, vpfe_dev); +probe_free_ccdc_cfg_mem: + kfree(ccdc_cfg); +probe_free_lock: + mutex_unlock(&ccdc_lock); +probe_free_dev_mem: + kfree(vpfe_dev); + return ret; +} + +/* + * vpfe_remove : It un-register device from V4L2 driver + */ +static int __devexit vpfe_remove(struct platform_device *pdev) +{ + struct vpfe_device *vpfe_dev = platform_get_drvdata(pdev); + + v4l2_info(pdev->dev.driver, "vpfe_remove\n"); + + free_irq(vpfe_dev->ccdc_irq0, vpfe_dev); + kfree(vpfe_dev->sd); + v4l2_device_unregister(&vpfe_dev->v4l2_dev); + video_unregister_device(vpfe_dev->video_dev); + kfree(vpfe_dev); + kfree(ccdc_cfg); + return 0; +} + +static int vpfe_suspend(struct device *dev) +{ + return 0; +} + +static int vpfe_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops vpfe_dev_pm_ops = { + .suspend = vpfe_suspend, + .resume = vpfe_resume, +}; + +static struct platform_driver vpfe_driver = { + .driver = { + .name = CAPTURE_DRV_NAME, + .owner = THIS_MODULE, + .pm = &vpfe_dev_pm_ops, + }, + .probe = vpfe_probe, + .remove = __devexit_p(vpfe_remove), +}; + +module_platform_driver(vpfe_driver); diff --git a/drivers/media/platform/davinci/vpif.c b/drivers/media/platform/davinci/vpif.c new file mode 100644 index 000000000000..cff3c0ab501f --- /dev/null +++ b/drivers/media/platform/davinci/vpif.c @@ -0,0 +1,516 @@ +/* + * vpif - Video Port Interface driver + * VPIF is a receiver and transmitter for video data. It has two channels(0, 1) + * that receiveing video byte stream and two channels(2, 3) for video output. + * The hardware supports SDTV, HDTV formats, raw data capture. + * Currently, the driver supports NTSC and PAL standards. + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "vpif.h" + +MODULE_DESCRIPTION("TI DaVinci Video Port Interface driver"); +MODULE_LICENSE("GPL"); + +#define VPIF_CH0_MAX_MODES (22) +#define VPIF_CH1_MAX_MODES (02) +#define VPIF_CH2_MAX_MODES (15) +#define VPIF_CH3_MAX_MODES (02) + +static resource_size_t res_len; +static struct resource *res; +spinlock_t vpif_lock; + +void __iomem *vpif_base; +struct clk *vpif_clk; + +/** + * ch_params: video standard configuration parameters for vpif + * The table must include all presets from supported subdevices. + */ +const struct vpif_channel_config_params ch_params[] = { + /* HDTV formats */ + { + .name = "480p59_94", + .width = 720, + .height = 480, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 138-8, + .sav2eav = 720, + .l1 = 1, + .l3 = 43, + .l5 = 523, + .vsize = 525, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_720X480P59_94, + }, + { + .name = "576p50", + .width = 720, + .height = 576, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 144-8, + .sav2eav = 720, + .l1 = 1, + .l3 = 45, + .l5 = 621, + .vsize = 625, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_720X576P50, + }, + { + .name = "720p50", + .width = 1280, + .height = 720, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 700-8, + .sav2eav = 1280, + .l1 = 1, + .l3 = 26, + .l5 = 746, + .vsize = 750, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1280X720P50, + }, + { + .name = "720p60", + .width = 1280, + .height = 720, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 370 - 8, + .sav2eav = 1280, + .l1 = 1, + .l3 = 26, + .l5 = 746, + .vsize = 750, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1280X720P60, + }, + { + .name = "1080I50", + .width = 1920, + .height = 1080, + .frm_fmt = 0, + .ycmux_mode = 0, + .eav2sav = 720 - 8, + .sav2eav = 1920, + .l1 = 1, + .l3 = 21, + .l5 = 561, + .l7 = 563, + .l9 = 584, + .l11 = 1124, + .vsize = 1125, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1920X1080I50, + }, + { + .name = "1080I60", + .width = 1920, + .height = 1080, + .frm_fmt = 0, + .ycmux_mode = 0, + .eav2sav = 280 - 8, + .sav2eav = 1920, + .l1 = 1, + .l3 = 21, + .l5 = 561, + .l7 = 563, + .l9 = 584, + .l11 = 1124, + .vsize = 1125, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1920X1080I60, + }, + { + .name = "1080p60", + .width = 1920, + .height = 1080, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 280 - 8, + .sav2eav = 1920, + .l1 = 1, + .l3 = 42, + .l5 = 1122, + .vsize = 1125, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1920X1080P60, + }, + + /* SDTV formats */ + { + .name = "NTSC_M", + .width = 720, + .height = 480, + .frm_fmt = 0, + .ycmux_mode = 1, + .eav2sav = 268, + .sav2eav = 1440, + .l1 = 1, + .l3 = 23, + .l5 = 263, + .l7 = 266, + .l9 = 286, + .l11 = 525, + .vsize = 525, + .capture_format = 0, + .vbi_supported = 1, + .hd_sd = 0, + .stdid = V4L2_STD_525_60, + }, + { + .name = "PAL_BDGHIK", + .width = 720, + .height = 576, + .frm_fmt = 0, + .ycmux_mode = 1, + .eav2sav = 280, + .sav2eav = 1440, + .l1 = 1, + .l3 = 23, + .l5 = 311, + .l7 = 313, + .l9 = 336, + .l11 = 624, + .vsize = 625, + .capture_format = 0, + .vbi_supported = 1, + .hd_sd = 0, + .stdid = V4L2_STD_625_50, + }, +}; + +const unsigned int vpif_ch_params_count = ARRAY_SIZE(ch_params); + +static inline void vpif_wr_bit(u32 reg, u32 bit, u32 val) +{ + if (val) + vpif_set_bit(reg, bit); + else + vpif_clr_bit(reg, bit); +} + +/* This structure is used to keep track of VPIF size register's offsets */ +struct vpif_registers { + u32 h_cfg, v_cfg_00, v_cfg_01, v_cfg_02, v_cfg, ch_ctrl; + u32 line_offset, vanc0_strt, vanc0_size, vanc1_strt; + u32 vanc1_size, width_mask, len_mask; + u8 max_modes; +}; + +static const struct vpif_registers vpifregs[VPIF_NUM_CHANNELS] = { + /* Channel0 */ + { + VPIF_CH0_H_CFG, VPIF_CH0_V_CFG_00, VPIF_CH0_V_CFG_01, + VPIF_CH0_V_CFG_02, VPIF_CH0_V_CFG_03, VPIF_CH0_CTRL, + VPIF_CH0_IMG_ADD_OFST, 0, 0, 0, 0, 0x1FFF, 0xFFF, + VPIF_CH0_MAX_MODES, + }, + /* Channel1 */ + { + VPIF_CH1_H_CFG, VPIF_CH1_V_CFG_00, VPIF_CH1_V_CFG_01, + VPIF_CH1_V_CFG_02, VPIF_CH1_V_CFG_03, VPIF_CH1_CTRL, + VPIF_CH1_IMG_ADD_OFST, 0, 0, 0, 0, 0x1FFF, 0xFFF, + VPIF_CH1_MAX_MODES, + }, + /* Channel2 */ + { + VPIF_CH2_H_CFG, VPIF_CH2_V_CFG_00, VPIF_CH2_V_CFG_01, + VPIF_CH2_V_CFG_02, VPIF_CH2_V_CFG_03, VPIF_CH2_CTRL, + VPIF_CH2_IMG_ADD_OFST, VPIF_CH2_VANC0_STRT, VPIF_CH2_VANC0_SIZE, + VPIF_CH2_VANC1_STRT, VPIF_CH2_VANC1_SIZE, 0x7FF, 0x7FF, + VPIF_CH2_MAX_MODES + }, + /* Channel3 */ + { + VPIF_CH3_H_CFG, VPIF_CH3_V_CFG_00, VPIF_CH3_V_CFG_01, + VPIF_CH3_V_CFG_02, VPIF_CH3_V_CFG_03, VPIF_CH3_CTRL, + VPIF_CH3_IMG_ADD_OFST, VPIF_CH3_VANC0_STRT, VPIF_CH3_VANC0_SIZE, + VPIF_CH3_VANC1_STRT, VPIF_CH3_VANC1_SIZE, 0x7FF, 0x7FF, + VPIF_CH3_MAX_MODES + }, +}; + +/* vpif_set_mode_info: + * This function is used to set horizontal and vertical config parameters + * As per the standard in the channel, configure the values of L1, L3, + * L5, L7 L9, L11 in VPIF Register , also write width and height + */ +static void vpif_set_mode_info(const struct vpif_channel_config_params *config, + u8 channel_id, u8 config_channel_id) +{ + u32 value; + + value = (config->eav2sav & vpifregs[config_channel_id].width_mask); + value <<= VPIF_CH_LEN_SHIFT; + value |= (config->sav2eav & vpifregs[config_channel_id].width_mask); + regw(value, vpifregs[channel_id].h_cfg); + + value = (config->l1 & vpifregs[config_channel_id].len_mask); + value <<= VPIF_CH_LEN_SHIFT; + value |= (config->l3 & vpifregs[config_channel_id].len_mask); + regw(value, vpifregs[channel_id].v_cfg_00); + + value = (config->l5 & vpifregs[config_channel_id].len_mask); + value <<= VPIF_CH_LEN_SHIFT; + value |= (config->l7 & vpifregs[config_channel_id].len_mask); + regw(value, vpifregs[channel_id].v_cfg_01); + + value = (config->l9 & vpifregs[config_channel_id].len_mask); + value <<= VPIF_CH_LEN_SHIFT; + value |= (config->l11 & vpifregs[config_channel_id].len_mask); + regw(value, vpifregs[channel_id].v_cfg_02); + + value = (config->vsize & vpifregs[config_channel_id].len_mask); + regw(value, vpifregs[channel_id].v_cfg); +} + +/* config_vpif_params + * Function to set the parameters of a channel + * Mainly modifies the channel ciontrol register + * It sets frame format, yc mux mode + */ +static void config_vpif_params(struct vpif_params *vpifparams, + u8 channel_id, u8 found) +{ + const struct vpif_channel_config_params *config = &vpifparams->std_info; + u32 value, ch_nip, reg; + u8 start, end; + int i; + + start = channel_id; + end = channel_id + found; + + for (i = start; i < end; i++) { + reg = vpifregs[i].ch_ctrl; + if (channel_id < 2) + ch_nip = VPIF_CAPTURE_CH_NIP; + else + ch_nip = VPIF_DISPLAY_CH_NIP; + + vpif_wr_bit(reg, ch_nip, config->frm_fmt); + vpif_wr_bit(reg, VPIF_CH_YC_MUX_BIT, config->ycmux_mode); + vpif_wr_bit(reg, VPIF_CH_INPUT_FIELD_FRAME_BIT, + vpifparams->video_params.storage_mode); + + /* Set raster scanning SDR Format */ + vpif_clr_bit(reg, VPIF_CH_SDR_FMT_BIT); + vpif_wr_bit(reg, VPIF_CH_DATA_MODE_BIT, config->capture_format); + + if (channel_id > 1) /* Set the Pixel enable bit */ + vpif_set_bit(reg, VPIF_DISPLAY_PIX_EN_BIT); + else if (config->capture_format) { + /* Set the polarity of various pins */ + vpif_wr_bit(reg, VPIF_CH_FID_POLARITY_BIT, + vpifparams->iface.fid_pol); + vpif_wr_bit(reg, VPIF_CH_V_VALID_POLARITY_BIT, + vpifparams->iface.vd_pol); + vpif_wr_bit(reg, VPIF_CH_H_VALID_POLARITY_BIT, + vpifparams->iface.hd_pol); + + value = regr(reg); + /* Set data width */ + value &= ~(0x3u << + VPIF_CH_DATA_WIDTH_BIT); + value |= ((vpifparams->params.data_sz) << + VPIF_CH_DATA_WIDTH_BIT); + regw(value, reg); + } + + /* Write the pitch in the driver */ + regw((vpifparams->video_params.hpitch), + vpifregs[i].line_offset); + } +} + +/* vpif_set_video_params + * This function is used to set video parameters in VPIF register + */ +int vpif_set_video_params(struct vpif_params *vpifparams, u8 channel_id) +{ + const struct vpif_channel_config_params *config = &vpifparams->std_info; + int found = 1; + + vpif_set_mode_info(config, channel_id, channel_id); + if (!config->ycmux_mode) { + /* YC are on separate channels (HDTV formats) */ + vpif_set_mode_info(config, channel_id + 1, channel_id); + found = 2; + } + + config_vpif_params(vpifparams, channel_id, found); + + regw(0x80, VPIF_REQ_SIZE); + regw(0x01, VPIF_EMULATION_CTRL); + + return found; +} +EXPORT_SYMBOL(vpif_set_video_params); + +void vpif_set_vbi_display_params(struct vpif_vbi_params *vbiparams, + u8 channel_id) +{ + u32 value; + + value = 0x3F8 & (vbiparams->hstart0); + value |= 0x3FFFFFF & ((vbiparams->vstart0) << 16); + regw(value, vpifregs[channel_id].vanc0_strt); + + value = 0x3F8 & (vbiparams->hstart1); + value |= 0x3FFFFFF & ((vbiparams->vstart1) << 16); + regw(value, vpifregs[channel_id].vanc1_strt); + + value = 0x3F8 & (vbiparams->hsize0); + value |= 0x3FFFFFF & ((vbiparams->vsize0) << 16); + regw(value, vpifregs[channel_id].vanc0_size); + + value = 0x3F8 & (vbiparams->hsize1); + value |= 0x3FFFFFF & ((vbiparams->vsize1) << 16); + regw(value, vpifregs[channel_id].vanc1_size); + +} +EXPORT_SYMBOL(vpif_set_vbi_display_params); + +int vpif_channel_getfid(u8 channel_id) +{ + return (regr(vpifregs[channel_id].ch_ctrl) & VPIF_CH_FID_MASK) + >> VPIF_CH_FID_SHIFT; +} +EXPORT_SYMBOL(vpif_channel_getfid); + +static int __devinit vpif_probe(struct platform_device *pdev) +{ + int status = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + res_len = resource_size(res); + + res = request_mem_region(res->start, res_len, res->name); + if (!res) + return -EBUSY; + + vpif_base = ioremap(res->start, res_len); + if (!vpif_base) { + status = -EBUSY; + goto fail; + } + + vpif_clk = clk_get(&pdev->dev, "vpif"); + if (IS_ERR(vpif_clk)) { + status = PTR_ERR(vpif_clk); + goto clk_fail; + } + clk_enable(vpif_clk); + + spin_lock_init(&vpif_lock); + dev_info(&pdev->dev, "vpif probe success\n"); + return 0; + +clk_fail: + iounmap(vpif_base); +fail: + release_mem_region(res->start, res_len); + return status; +} + +static int __devexit vpif_remove(struct platform_device *pdev) +{ + if (vpif_clk) { + clk_disable(vpif_clk); + clk_put(vpif_clk); + } + + iounmap(vpif_base); + release_mem_region(res->start, res_len); + return 0; +} + +#ifdef CONFIG_PM +static int vpif_suspend(struct device *dev) +{ + clk_disable(vpif_clk); + return 0; +} + +static int vpif_resume(struct device *dev) +{ + clk_enable(vpif_clk); + return 0; +} + +static const struct dev_pm_ops vpif_pm = { + .suspend = vpif_suspend, + .resume = vpif_resume, +}; + +#define vpif_pm_ops (&vpif_pm) +#else +#define vpif_pm_ops NULL +#endif + +static struct platform_driver vpif_driver = { + .driver = { + .name = "vpif", + .owner = THIS_MODULE, + .pm = vpif_pm_ops, + }, + .remove = __devexit_p(vpif_remove), + .probe = vpif_probe, +}; + +static void vpif_exit(void) +{ + platform_driver_unregister(&vpif_driver); +} + +static int __init vpif_init(void) +{ + return platform_driver_register(&vpif_driver); +} +subsys_initcall(vpif_init); +module_exit(vpif_exit); + diff --git a/drivers/media/platform/davinci/vpif.h b/drivers/media/platform/davinci/vpif.h new file mode 100644 index 000000000000..a1ab6a0f4e9e --- /dev/null +++ b/drivers/media/platform/davinci/vpif.h @@ -0,0 +1,688 @@ +/* + * VPIF header file + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef VPIF_H +#define VPIF_H + +#include +#include +#include + +/* Maximum channel allowed */ +#define VPIF_NUM_CHANNELS (4) +#define VPIF_CAPTURE_NUM_CHANNELS (2) +#define VPIF_DISPLAY_NUM_CHANNELS (2) + +/* Macros to read/write registers */ +extern void __iomem *vpif_base; +extern spinlock_t vpif_lock; + +#define regr(reg) readl((reg) + vpif_base) +#define regw(value, reg) writel(value, (reg + vpif_base)) + +/* Register Address Offsets */ +#define VPIF_PID (0x0000) +#define VPIF_CH0_CTRL (0x0004) +#define VPIF_CH1_CTRL (0x0008) +#define VPIF_CH2_CTRL (0x000C) +#define VPIF_CH3_CTRL (0x0010) + +#define VPIF_INTEN (0x0020) +#define VPIF_INTEN_SET (0x0024) +#define VPIF_INTEN_CLR (0x0028) +#define VPIF_STATUS (0x002C) +#define VPIF_STATUS_CLR (0x0030) +#define VPIF_EMULATION_CTRL (0x0034) +#define VPIF_REQ_SIZE (0x0038) + +#define VPIF_CH0_TOP_STRT_ADD_LUMA (0x0040) +#define VPIF_CH0_BTM_STRT_ADD_LUMA (0x0044) +#define VPIF_CH0_TOP_STRT_ADD_CHROMA (0x0048) +#define VPIF_CH0_BTM_STRT_ADD_CHROMA (0x004c) +#define VPIF_CH0_TOP_STRT_ADD_HANC (0x0050) +#define VPIF_CH0_BTM_STRT_ADD_HANC (0x0054) +#define VPIF_CH0_TOP_STRT_ADD_VANC (0x0058) +#define VPIF_CH0_BTM_STRT_ADD_VANC (0x005c) +#define VPIF_CH0_SP_CFG (0x0060) +#define VPIF_CH0_IMG_ADD_OFST (0x0064) +#define VPIF_CH0_HANC_ADD_OFST (0x0068) +#define VPIF_CH0_H_CFG (0x006c) +#define VPIF_CH0_V_CFG_00 (0x0070) +#define VPIF_CH0_V_CFG_01 (0x0074) +#define VPIF_CH0_V_CFG_02 (0x0078) +#define VPIF_CH0_V_CFG_03 (0x007c) + +#define VPIF_CH1_TOP_STRT_ADD_LUMA (0x0080) +#define VPIF_CH1_BTM_STRT_ADD_LUMA (0x0084) +#define VPIF_CH1_TOP_STRT_ADD_CHROMA (0x0088) +#define VPIF_CH1_BTM_STRT_ADD_CHROMA (0x008c) +#define VPIF_CH1_TOP_STRT_ADD_HANC (0x0090) +#define VPIF_CH1_BTM_STRT_ADD_HANC (0x0094) +#define VPIF_CH1_TOP_STRT_ADD_VANC (0x0098) +#define VPIF_CH1_BTM_STRT_ADD_VANC (0x009c) +#define VPIF_CH1_SP_CFG (0x00a0) +#define VPIF_CH1_IMG_ADD_OFST (0x00a4) +#define VPIF_CH1_HANC_ADD_OFST (0x00a8) +#define VPIF_CH1_H_CFG (0x00ac) +#define VPIF_CH1_V_CFG_00 (0x00b0) +#define VPIF_CH1_V_CFG_01 (0x00b4) +#define VPIF_CH1_V_CFG_02 (0x00b8) +#define VPIF_CH1_V_CFG_03 (0x00bc) + +#define VPIF_CH2_TOP_STRT_ADD_LUMA (0x00c0) +#define VPIF_CH2_BTM_STRT_ADD_LUMA (0x00c4) +#define VPIF_CH2_TOP_STRT_ADD_CHROMA (0x00c8) +#define VPIF_CH2_BTM_STRT_ADD_CHROMA (0x00cc) +#define VPIF_CH2_TOP_STRT_ADD_HANC (0x00d0) +#define VPIF_CH2_BTM_STRT_ADD_HANC (0x00d4) +#define VPIF_CH2_TOP_STRT_ADD_VANC (0x00d8) +#define VPIF_CH2_BTM_STRT_ADD_VANC (0x00dc) +#define VPIF_CH2_SP_CFG (0x00e0) +#define VPIF_CH2_IMG_ADD_OFST (0x00e4) +#define VPIF_CH2_HANC_ADD_OFST (0x00e8) +#define VPIF_CH2_H_CFG (0x00ec) +#define VPIF_CH2_V_CFG_00 (0x00f0) +#define VPIF_CH2_V_CFG_01 (0x00f4) +#define VPIF_CH2_V_CFG_02 (0x00f8) +#define VPIF_CH2_V_CFG_03 (0x00fc) +#define VPIF_CH2_HANC0_STRT (0x0100) +#define VPIF_CH2_HANC0_SIZE (0x0104) +#define VPIF_CH2_HANC1_STRT (0x0108) +#define VPIF_CH2_HANC1_SIZE (0x010c) +#define VPIF_CH2_VANC0_STRT (0x0110) +#define VPIF_CH2_VANC0_SIZE (0x0114) +#define VPIF_CH2_VANC1_STRT (0x0118) +#define VPIF_CH2_VANC1_SIZE (0x011c) + +#define VPIF_CH3_TOP_STRT_ADD_LUMA (0x0140) +#define VPIF_CH3_BTM_STRT_ADD_LUMA (0x0144) +#define VPIF_CH3_TOP_STRT_ADD_CHROMA (0x0148) +#define VPIF_CH3_BTM_STRT_ADD_CHROMA (0x014c) +#define VPIF_CH3_TOP_STRT_ADD_HANC (0x0150) +#define VPIF_CH3_BTM_STRT_ADD_HANC (0x0154) +#define VPIF_CH3_TOP_STRT_ADD_VANC (0x0158) +#define VPIF_CH3_BTM_STRT_ADD_VANC (0x015c) +#define VPIF_CH3_SP_CFG (0x0160) +#define VPIF_CH3_IMG_ADD_OFST (0x0164) +#define VPIF_CH3_HANC_ADD_OFST (0x0168) +#define VPIF_CH3_H_CFG (0x016c) +#define VPIF_CH3_V_CFG_00 (0x0170) +#define VPIF_CH3_V_CFG_01 (0x0174) +#define VPIF_CH3_V_CFG_02 (0x0178) +#define VPIF_CH3_V_CFG_03 (0x017c) +#define VPIF_CH3_HANC0_STRT (0x0180) +#define VPIF_CH3_HANC0_SIZE (0x0184) +#define VPIF_CH3_HANC1_STRT (0x0188) +#define VPIF_CH3_HANC1_SIZE (0x018c) +#define VPIF_CH3_VANC0_STRT (0x0190) +#define VPIF_CH3_VANC0_SIZE (0x0194) +#define VPIF_CH3_VANC1_STRT (0x0198) +#define VPIF_CH3_VANC1_SIZE (0x019c) + +#define VPIF_IODFT_CTRL (0x01c0) + +/* Functions for bit Manipulation */ +static inline void vpif_set_bit(u32 reg, u32 bit) +{ + regw((regr(reg)) | (0x01 << bit), reg); +} + +static inline void vpif_clr_bit(u32 reg, u32 bit) +{ + regw(((regr(reg)) & ~(0x01 << bit)), reg); +} + +/* Macro for Generating mask */ +#ifdef GENERATE_MASK +#undef GENERATE_MASK +#endif + +#define GENERATE_MASK(bits, pos) \ + ((((0xFFFFFFFF) << (32 - bits)) >> (32 - bits)) << pos) + +/* Bit positions in the channel control registers */ +#define VPIF_CH_DATA_MODE_BIT (2) +#define VPIF_CH_YC_MUX_BIT (3) +#define VPIF_CH_SDR_FMT_BIT (4) +#define VPIF_CH_HANC_EN_BIT (8) +#define VPIF_CH_VANC_EN_BIT (9) + +#define VPIF_CAPTURE_CH_NIP (10) +#define VPIF_DISPLAY_CH_NIP (11) + +#define VPIF_DISPLAY_PIX_EN_BIT (10) + +#define VPIF_CH_INPUT_FIELD_FRAME_BIT (12) + +#define VPIF_CH_FID_POLARITY_BIT (15) +#define VPIF_CH_V_VALID_POLARITY_BIT (14) +#define VPIF_CH_H_VALID_POLARITY_BIT (13) +#define VPIF_CH_DATA_WIDTH_BIT (28) + +#define VPIF_CH_CLK_EDGE_CTRL_BIT (31) + +/* Mask various length */ +#define VPIF_CH_EAVSAV_MASK GENERATE_MASK(13, 0) +#define VPIF_CH_LEN_MASK GENERATE_MASK(12, 0) +#define VPIF_CH_WIDTH_MASK GENERATE_MASK(13, 0) +#define VPIF_CH_LEN_SHIFT (16) + +/* VPIF masks for registers */ +#define VPIF_REQ_SIZE_MASK (0x1ff) + +/* bit posotion of interrupt vpif_ch_intr register */ +#define VPIF_INTEN_FRAME_CH0 (0x00000001) +#define VPIF_INTEN_FRAME_CH1 (0x00000002) +#define VPIF_INTEN_FRAME_CH2 (0x00000004) +#define VPIF_INTEN_FRAME_CH3 (0x00000008) + +/* bit position of clock and channel enable in vpif_chn_ctrl register */ + +#define VPIF_CH0_CLK_EN (0x00000002) +#define VPIF_CH0_EN (0x00000001) +#define VPIF_CH1_CLK_EN (0x00000002) +#define VPIF_CH1_EN (0x00000001) +#define VPIF_CH2_CLK_EN (0x00000002) +#define VPIF_CH2_EN (0x00000001) +#define VPIF_CH3_CLK_EN (0x00000002) +#define VPIF_CH3_EN (0x00000001) +#define VPIF_CH_CLK_EN (0x00000002) +#define VPIF_CH_EN (0x00000001) + +#define VPIF_INT_TOP (0x00) +#define VPIF_INT_BOTTOM (0x01) +#define VPIF_INT_BOTH (0x02) + +#define VPIF_CH0_INT_CTRL_SHIFT (6) +#define VPIF_CH1_INT_CTRL_SHIFT (6) +#define VPIF_CH2_INT_CTRL_SHIFT (6) +#define VPIF_CH3_INT_CTRL_SHIFT (6) +#define VPIF_CH_INT_CTRL_SHIFT (6) + +#define VPIF_CH2_CLIP_ANC_EN 14 +#define VPIF_CH2_CLIP_ACTIVE_EN 13 + +#define VPIF_CH3_CLIP_ANC_EN 14 +#define VPIF_CH3_CLIP_ACTIVE_EN 13 + +/* enabled interrupt on both the fields on vpid_ch0_ctrl register */ +#define channel0_intr_assert() (regw((regr(VPIF_CH0_CTRL)|\ + (VPIF_INT_BOTH << VPIF_CH0_INT_CTRL_SHIFT)), VPIF_CH0_CTRL)) + +/* enabled interrupt on both the fields on vpid_ch1_ctrl register */ +#define channel1_intr_assert() (regw((regr(VPIF_CH1_CTRL)|\ + (VPIF_INT_BOTH << VPIF_CH1_INT_CTRL_SHIFT)), VPIF_CH1_CTRL)) + +/* enabled interrupt on both the fields on vpid_ch0_ctrl register */ +#define channel2_intr_assert() (regw((regr(VPIF_CH2_CTRL)|\ + (VPIF_INT_BOTH << VPIF_CH2_INT_CTRL_SHIFT)), VPIF_CH2_CTRL)) + +/* enabled interrupt on both the fields on vpid_ch1_ctrl register */ +#define channel3_intr_assert() (regw((regr(VPIF_CH3_CTRL)|\ + (VPIF_INT_BOTH << VPIF_CH3_INT_CTRL_SHIFT)), VPIF_CH3_CTRL)) + +#define VPIF_CH_FID_MASK (0x20) +#define VPIF_CH_FID_SHIFT (5) + +#define VPIF_NTSC_VBI_START_FIELD0 (1) +#define VPIF_NTSC_VBI_START_FIELD1 (263) +#define VPIF_PAL_VBI_START_FIELD0 (624) +#define VPIF_PAL_VBI_START_FIELD1 (311) + +#define VPIF_NTSC_HBI_START_FIELD0 (1) +#define VPIF_NTSC_HBI_START_FIELD1 (263) +#define VPIF_PAL_HBI_START_FIELD0 (624) +#define VPIF_PAL_HBI_START_FIELD1 (311) + +#define VPIF_NTSC_VBI_COUNT_FIELD0 (20) +#define VPIF_NTSC_VBI_COUNT_FIELD1 (19) +#define VPIF_PAL_VBI_COUNT_FIELD0 (24) +#define VPIF_PAL_VBI_COUNT_FIELD1 (25) + +#define VPIF_NTSC_HBI_COUNT_FIELD0 (263) +#define VPIF_NTSC_HBI_COUNT_FIELD1 (262) +#define VPIF_PAL_HBI_COUNT_FIELD0 (312) +#define VPIF_PAL_HBI_COUNT_FIELD1 (313) + +#define VPIF_NTSC_VBI_SAMPLES_PER_LINE (720) +#define VPIF_PAL_VBI_SAMPLES_PER_LINE (720) +#define VPIF_NTSC_HBI_SAMPLES_PER_LINE (268) +#define VPIF_PAL_HBI_SAMPLES_PER_LINE (280) + +#define VPIF_CH_VANC_EN (0x20) +#define VPIF_DMA_REQ_SIZE (0x080) +#define VPIF_EMULATION_DISABLE (0x01) + +extern u8 irq_vpif_capture_channel[VPIF_NUM_CHANNELS]; + +/* inline function to enable/disable channel0 */ +static inline void enable_channel0(int enable) +{ + if (enable) + regw((regr(VPIF_CH0_CTRL) | (VPIF_CH0_EN)), VPIF_CH0_CTRL); + else + regw((regr(VPIF_CH0_CTRL) & (~VPIF_CH0_EN)), VPIF_CH0_CTRL); +} + +/* inline function to enable/disable channel1 */ +static inline void enable_channel1(int enable) +{ + if (enable) + regw((regr(VPIF_CH1_CTRL) | (VPIF_CH1_EN)), VPIF_CH1_CTRL); + else + regw((regr(VPIF_CH1_CTRL) & (~VPIF_CH1_EN)), VPIF_CH1_CTRL); +} + +/* inline function to enable interrupt for channel0 */ +static inline void channel0_intr_enable(int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&vpif_lock, flags); + + if (enable) { + regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + + regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH0), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH0), + VPIF_INTEN_SET); + } else { + regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH0)), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH0), + VPIF_INTEN_SET); + } + spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to enable interrupt for channel1 */ +static inline void channel1_intr_enable(int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&vpif_lock, flags); + + if (enable) { + regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + + regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH1), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH1), + VPIF_INTEN_SET); + } else { + regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH1)), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH1), + VPIF_INTEN_SET); + } + spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to set buffer addresses in case of Y/C non mux mode */ +static inline void ch0_set_videobuf_addr_yc_nmux(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH0_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH0_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH1_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH1_BTM_STRT_ADD_CHROMA); +} + +/* inline function to set buffer addresses in VPIF registers for video data */ +static inline void ch0_set_videobuf_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH0_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH0_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH0_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH0_BTM_STRT_ADD_CHROMA); +} + +static inline void ch1_set_videobuf_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + + regw(top_strt_luma, VPIF_CH1_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH1_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH1_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH1_BTM_STRT_ADD_CHROMA); +} + +static inline void ch0_set_vbi_addr(unsigned long top_vbi, + unsigned long btm_vbi, unsigned long a, unsigned long b) +{ + regw(top_vbi, VPIF_CH0_TOP_STRT_ADD_VANC); + regw(btm_vbi, VPIF_CH0_BTM_STRT_ADD_VANC); +} + +static inline void ch0_set_hbi_addr(unsigned long top_vbi, + unsigned long btm_vbi, unsigned long a, unsigned long b) +{ + regw(top_vbi, VPIF_CH0_TOP_STRT_ADD_HANC); + regw(btm_vbi, VPIF_CH0_BTM_STRT_ADD_HANC); +} + +static inline void ch1_set_vbi_addr(unsigned long top_vbi, + unsigned long btm_vbi, unsigned long a, unsigned long b) +{ + regw(top_vbi, VPIF_CH1_TOP_STRT_ADD_VANC); + regw(btm_vbi, VPIF_CH1_BTM_STRT_ADD_VANC); +} + +static inline void ch1_set_hbi_addr(unsigned long top_vbi, + unsigned long btm_vbi, unsigned long a, unsigned long b) +{ + regw(top_vbi, VPIF_CH1_TOP_STRT_ADD_HANC); + regw(btm_vbi, VPIF_CH1_BTM_STRT_ADD_HANC); +} + +/* Inline function to enable raw vbi in the given channel */ +static inline void disable_raw_feature(u8 channel_id, u8 index) +{ + u32 ctrl_reg; + if (0 == channel_id) + ctrl_reg = VPIF_CH0_CTRL; + else + ctrl_reg = VPIF_CH1_CTRL; + + if (1 == index) + vpif_clr_bit(ctrl_reg, VPIF_CH_VANC_EN_BIT); + else + vpif_clr_bit(ctrl_reg, VPIF_CH_HANC_EN_BIT); +} + +static inline void enable_raw_feature(u8 channel_id, u8 index) +{ + u32 ctrl_reg; + if (0 == channel_id) + ctrl_reg = VPIF_CH0_CTRL; + else + ctrl_reg = VPIF_CH1_CTRL; + + if (1 == index) + vpif_set_bit(ctrl_reg, VPIF_CH_VANC_EN_BIT); + else + vpif_set_bit(ctrl_reg, VPIF_CH_HANC_EN_BIT); +} + +/* inline function to enable/disable channel2 */ +static inline void enable_channel2(int enable) +{ + if (enable) { + regw((regr(VPIF_CH2_CTRL) | (VPIF_CH2_CLK_EN)), VPIF_CH2_CTRL); + regw((regr(VPIF_CH2_CTRL) | (VPIF_CH2_EN)), VPIF_CH2_CTRL); + } else { + regw((regr(VPIF_CH2_CTRL) & (~VPIF_CH2_CLK_EN)), VPIF_CH2_CTRL); + regw((regr(VPIF_CH2_CTRL) & (~VPIF_CH2_EN)), VPIF_CH2_CTRL); + } +} + +/* inline function to enable/disable channel3 */ +static inline void enable_channel3(int enable) +{ + if (enable) { + regw((regr(VPIF_CH3_CTRL) | (VPIF_CH3_CLK_EN)), VPIF_CH3_CTRL); + regw((regr(VPIF_CH3_CTRL) | (VPIF_CH3_EN)), VPIF_CH3_CTRL); + } else { + regw((regr(VPIF_CH3_CTRL) & (~VPIF_CH3_CLK_EN)), VPIF_CH3_CTRL); + regw((regr(VPIF_CH3_CTRL) & (~VPIF_CH3_EN)), VPIF_CH3_CTRL); + } +} + +/* inline function to enable interrupt for channel2 */ +static inline void channel2_intr_enable(int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&vpif_lock, flags); + + if (enable) { + regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH2), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH2), + VPIF_INTEN_SET); + } else { + regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH2)), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH2), + VPIF_INTEN_SET); + } + spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to enable interrupt for channel3 */ +static inline void channel3_intr_enable(int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&vpif_lock, flags); + + if (enable) { + regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + + regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH3), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH3), + VPIF_INTEN_SET); + } else { + regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH3)), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH3), + VPIF_INTEN_SET); + } + spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to enable raw vbi data for channel2 */ +static inline void channel2_raw_enable(int enable, u8 index) +{ + u32 mask; + + if (1 == index) + mask = VPIF_CH_VANC_EN_BIT; + else + mask = VPIF_CH_HANC_EN_BIT; + + if (enable) + vpif_set_bit(VPIF_CH2_CTRL, mask); + else + vpif_clr_bit(VPIF_CH2_CTRL, mask); +} + +/* inline function to enable raw vbi data for channel3*/ +static inline void channel3_raw_enable(int enable, u8 index) +{ + u32 mask; + + if (1 == index) + mask = VPIF_CH_VANC_EN_BIT; + else + mask = VPIF_CH_HANC_EN_BIT; + + if (enable) + vpif_set_bit(VPIF_CH3_CTRL, mask); + else + vpif_clr_bit(VPIF_CH3_CTRL, mask); +} + +/* function to enable clipping (for both active and blanking regions) on ch 2 */ +static inline void channel2_clipping_enable(int enable) +{ + if (enable) { + vpif_set_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ANC_EN); + vpif_set_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ACTIVE_EN); + } else { + vpif_clr_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ANC_EN); + vpif_clr_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ACTIVE_EN); + } +} + +/* function to enable clipping (for both active and blanking regions) on ch 3 */ +static inline void channel3_clipping_enable(int enable) +{ + if (enable) { + vpif_set_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ANC_EN); + vpif_set_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ACTIVE_EN); + } else { + vpif_clr_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ANC_EN); + vpif_clr_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ACTIVE_EN); + } +} + +/* inline function to set buffer addresses in case of Y/C non mux mode */ +static inline void ch2_set_videobuf_addr_yc_nmux(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH2_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH2_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH3_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH3_BTM_STRT_ADD_CHROMA); +} + +/* inline function to set buffer addresses in VPIF registers for video data */ +static inline void ch2_set_videobuf_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH2_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH2_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH2_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH2_BTM_STRT_ADD_CHROMA); +} + +static inline void ch3_set_videobuf_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH3_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH3_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH3_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH3_BTM_STRT_ADD_CHROMA); +} + +/* inline function to set buffer addresses in VPIF registers for vbi data */ +static inline void ch2_set_vbi_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH2_TOP_STRT_ADD_VANC); + regw(btm_strt_luma, VPIF_CH2_BTM_STRT_ADD_VANC); +} + +static inline void ch3_set_vbi_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH3_TOP_STRT_ADD_VANC); + regw(btm_strt_luma, VPIF_CH3_BTM_STRT_ADD_VANC); +} + +static inline int vpif_intr_status(int channel) +{ + int status = 0; + int mask; + + if (channel < 0 || channel > 3) + return 0; + + mask = 1 << channel; + status = regr(VPIF_STATUS) & mask; + regw(status, VPIF_STATUS_CLR); + + return status; +} + +#define VPIF_MAX_NAME (30) + +/* This structure will store size parameters as per the mode selected by user */ +struct vpif_channel_config_params { + char name[VPIF_MAX_NAME]; /* Name of the mode */ + u16 width; /* Indicates width of the image */ + u16 height; /* Indicates height of the image */ + u8 frm_fmt; /* Interlaced (0) or progressive (1) */ + u8 ycmux_mode; /* This mode requires one (0) or two (1) + channels */ + u16 eav2sav; /* length of eav 2 sav */ + u16 sav2eav; /* length of sav 2 eav */ + u16 l1, l3, l5, l7, l9, l11; /* Other parameter configurations */ + u16 vsize; /* Vertical size of the image */ + u8 capture_format; /* Indicates whether capture format + * is in BT or in CCD/CMOS */ + u8 vbi_supported; /* Indicates whether this mode + * supports capturing vbi or not */ + u8 hd_sd; /* HDTV (1) or SDTV (0) format */ + v4l2_std_id stdid; /* SDTV format */ + struct v4l2_dv_timings dv_timings; /* HDTV format */ +}; + +extern const unsigned int vpif_ch_params_count; +extern const struct vpif_channel_config_params ch_params[]; + +struct vpif_video_params; +struct vpif_params; +struct vpif_vbi_params; + +int vpif_set_video_params(struct vpif_params *vpifparams, u8 channel_id); +void vpif_set_vbi_display_params(struct vpif_vbi_params *vbiparams, + u8 channel_id); +int vpif_channel_getfid(u8 channel_id); + +enum data_size { + _8BITS = 0, + _10BITS, + _12BITS, +}; + +/* Structure for vpif parameters for raw vbi data */ +struct vpif_vbi_params { + __u32 hstart0; /* Horizontal start of raw vbi data for first field */ + __u32 vstart0; /* Vertical start of raw vbi data for first field */ + __u32 hsize0; /* Horizontal size of raw vbi data for first field */ + __u32 vsize0; /* Vertical size of raw vbi data for first field */ + __u32 hstart1; /* Horizontal start of raw vbi data for second field */ + __u32 vstart1; /* Vertical start of raw vbi data for second field */ + __u32 hsize1; /* Horizontal size of raw vbi data for second field */ + __u32 vsize1; /* Vertical size of raw vbi data for second field */ +}; + +/* structure for vpif parameters */ +struct vpif_video_params { + __u8 storage_mode; /* Indicates field or frame mode */ + unsigned long hpitch; + v4l2_std_id stdid; +}; + +struct vpif_params { + struct vpif_interface iface; + struct vpif_video_params video_params; + struct vpif_channel_config_params std_info; + union param { + struct vpif_vbi_params vbi_params; + enum data_size data_sz; + } params; +}; + +#endif /* End of #ifndef VPIF_H */ + diff --git a/drivers/media/platform/davinci/vpif_capture.c b/drivers/media/platform/davinci/vpif_capture.c new file mode 100644 index 000000000000..0bafecac4923 --- /dev/null +++ b/drivers/media/platform/davinci/vpif_capture.c @@ -0,0 +1,2380 @@ +/* + * Copyright (C) 2009 Texas Instruments Inc + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * TODO : add support for VBI & HBI data service + * add static buffer allocation + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vpif_capture.h" +#include "vpif.h" + +MODULE_DESCRIPTION("TI DaVinci VPIF Capture driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VPIF_CAPTURE_VERSION); + +#define vpif_err(fmt, arg...) v4l2_err(&vpif_obj.v4l2_dev, fmt, ## arg) +#define vpif_dbg(level, debug, fmt, arg...) \ + v4l2_dbg(level, debug, &vpif_obj.v4l2_dev, fmt, ## arg) + +static int debug = 1; +static u32 ch0_numbuffers = 3; +static u32 ch1_numbuffers = 3; +static u32 ch0_bufsize = 1920 * 1080 * 2; +static u32 ch1_bufsize = 720 * 576 * 2; + +module_param(debug, int, 0644); +module_param(ch0_numbuffers, uint, S_IRUGO); +module_param(ch1_numbuffers, uint, S_IRUGO); +module_param(ch0_bufsize, uint, S_IRUGO); +module_param(ch1_bufsize, uint, S_IRUGO); + +MODULE_PARM_DESC(debug, "Debug level 0-1"); +MODULE_PARM_DESC(ch2_numbuffers, "Channel0 buffer count (default:3)"); +MODULE_PARM_DESC(ch3_numbuffers, "Channel1 buffer count (default:3)"); +MODULE_PARM_DESC(ch2_bufsize, "Channel0 buffer size (default:1920 x 1080 x 2)"); +MODULE_PARM_DESC(ch3_bufsize, "Channel1 buffer size (default:720 x 576 x 2)"); + +static struct vpif_config_params config_params = { + .min_numbuffers = 3, + .numbuffers[0] = 3, + .numbuffers[1] = 3, + .min_bufsize[0] = 720 * 480 * 2, + .min_bufsize[1] = 720 * 480 * 2, + .channel_bufsize[0] = 1920 * 1080 * 2, + .channel_bufsize[1] = 720 * 576 * 2, +}; + +/* global variables */ +static struct vpif_device vpif_obj = { {NULL} }; +static struct device *vpif_dev; +static void vpif_calculate_offsets(struct channel_obj *ch); +static void vpif_config_addr(struct channel_obj *ch, int muxmode); + +/** + * buffer_prepare : callback function for buffer prepare + * @vb: ptr to vb2_buffer + * + * This is the callback function for buffer prepare when vb2_qbuf() + * function is called. The buffer is prepared and user space virtual address + * or user address is converted into physical address + */ +static int vpif_buffer_prepare(struct vb2_buffer *vb) +{ + /* Get the file handle object and channel object */ + struct vpif_fh *fh = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_queue *q = vb->vb2_queue; + struct channel_obj *ch = fh->channel; + struct common_obj *common; + unsigned long addr; + + vpif_dbg(2, debug, "vpif_buffer_prepare\n"); + + common = &ch->common[VPIF_VIDEO_INDEX]; + + if (vb->state != VB2_BUF_STATE_ACTIVE && + vb->state != VB2_BUF_STATE_PREPARED) { + vb2_set_plane_payload(vb, 0, common->fmt.fmt.pix.sizeimage); + if (vb2_plane_vaddr(vb, 0) && + vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) + goto exit; + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (q->streaming) { + if (!IS_ALIGNED((addr + common->ytop_off), 8) || + !IS_ALIGNED((addr + common->ybtm_off), 8) || + !IS_ALIGNED((addr + common->ctop_off), 8) || + !IS_ALIGNED((addr + common->cbtm_off), 8)) + goto exit; + } + } + return 0; +exit: + vpif_dbg(1, debug, "buffer_prepare:offset is not aligned to 8 bytes\n"); + return -EINVAL; +} + +/** + * vpif_buffer_queue_setup : Callback function for buffer setup. + * @vq: vb2_queue ptr + * @fmt: v4l2 format + * @nbuffers: ptr to number of buffers requested by application + * @nplanes:: contains number of distinct video planes needed to hold a frame + * @sizes[]: contains the size (in bytes) of each plane. + * @alloc_ctxs: ptr to allocation context + * + * This callback function is called when reqbuf() is called to adjust + * the buffer count and buffer size + */ +static int vpif_buffer_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + /* Get the file handle object and channel object */ + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + unsigned long size; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + vpif_dbg(2, debug, "vpif_buffer_setup\n"); + + /* If memory type is not mmap, return */ + if (V4L2_MEMORY_MMAP == common->memory) { + /* Calculate the size of the buffer */ + size = config_params.channel_bufsize[ch->channel_id]; + /* + * Checking if the buffer size exceeds the available buffer + * ycmux_mode = 0 means 1 channel mode HD and + * ycmux_mode = 1 means 2 channels mode SD + */ + if (ch->vpifparams.std_info.ycmux_mode == 0) { + if (config_params.video_limit[ch->channel_id]) + while (size * *nbuffers > + (config_params.video_limit[0] + + config_params.video_limit[1])) + (*nbuffers)--; + } else { + if (config_params.video_limit[ch->channel_id]) + while (size * *nbuffers > + config_params.video_limit[ch->channel_id]) + (*nbuffers)--; + } + + } else { + size = common->fmt.fmt.pix.sizeimage; + } + + if (*nbuffers < config_params.min_numbuffers) + *nbuffers = config_params.min_numbuffers; + + *nplanes = 1; + sizes[0] = size; + alloc_ctxs[0] = common->alloc_ctx; + + return 0; +} + +/** + * vpif_buffer_queue : Callback function to add buffer to DMA queue + * @vb: ptr to vb2_buffer + */ +static void vpif_buffer_queue(struct vb2_buffer *vb) +{ + /* Get the file handle object and channel object */ + struct vpif_fh *fh = vb2_get_drv_priv(vb->vb2_queue); + struct channel_obj *ch = fh->channel; + struct vpif_cap_buffer *buf = container_of(vb, + struct vpif_cap_buffer, vb); + struct common_obj *common; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + vpif_dbg(2, debug, "vpif_buffer_queue\n"); + + /* add the buffer to the DMA queue */ + list_add_tail(&buf->list, &common->dma_queue); +} + +/** + * vpif_buf_cleanup : Callback function to free buffer + * @vb: ptr to vb2_buffer + * + * This function is called from the videobuf2 layer to free memory + * allocated to the buffers + */ +static void vpif_buf_cleanup(struct vb2_buffer *vb) +{ + /* Get the file handle object and channel object */ + struct vpif_fh *fh = vb2_get_drv_priv(vb->vb2_queue); + struct vpif_cap_buffer *buf = container_of(vb, + struct vpif_cap_buffer, vb); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + unsigned long flags; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + spin_lock_irqsave(&common->irqlock, flags); + if (vb->state == VB2_BUF_STATE_ACTIVE) + list_del_init(&buf->list); + spin_unlock_irqrestore(&common->irqlock, flags); + +} + +static void vpif_wait_prepare(struct vb2_queue *vq) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + + common = &ch->common[VPIF_VIDEO_INDEX]; + mutex_unlock(&common->lock); +} + +static void vpif_wait_finish(struct vb2_queue *vq) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + + common = &ch->common[VPIF_VIDEO_INDEX]; + mutex_lock(&common->lock); +} + +static int vpif_buffer_init(struct vb2_buffer *vb) +{ + struct vpif_cap_buffer *buf = container_of(vb, + struct vpif_cap_buffer, vb); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static u8 channel_first_int[VPIF_NUMBER_OF_OBJECTS][2] = + { {1, 1} }; + +static int vpif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vpif_capture_config *vpif_config_data = + vpif_dev->platform_data; + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_params *vpif = &ch->vpifparams; + unsigned long addr = 0; + int ret; + + /* If buffer queue is empty, return error */ + if (list_empty(&common->dma_queue)) { + vpif_dbg(1, debug, "buffer queue is empty\n"); + return -EIO; + } + + /* Get the next frame from the buffer queue */ + common->cur_frm = common->next_frm = list_entry(common->dma_queue.next, + struct vpif_cap_buffer, list); + /* Remove buffer from the buffer queue */ + list_del(&common->cur_frm->list); + /* Mark state of the current frame to active */ + common->cur_frm->vb.state = VB2_BUF_STATE_ACTIVE; + /* Initialize field_id and started member */ + ch->field_id = 0; + common->started = 1; + addr = vb2_dma_contig_plane_dma_addr(&common->cur_frm->vb, 0); + + /* Calculate the offset for Y and C data in the buffer */ + vpif_calculate_offsets(ch); + + if ((vpif->std_info.frm_fmt && + ((common->fmt.fmt.pix.field != V4L2_FIELD_NONE) && + (common->fmt.fmt.pix.field != V4L2_FIELD_ANY))) || + (!vpif->std_info.frm_fmt && + (common->fmt.fmt.pix.field == V4L2_FIELD_NONE))) { + vpif_dbg(1, debug, "conflict in field format and std format\n"); + return -EINVAL; + } + + /* configure 1 or 2 channel mode */ + ret = vpif_config_data->setup_input_channel_mode + (vpif->std_info.ycmux_mode); + + if (ret < 0) { + vpif_dbg(1, debug, "can't set vpif channel mode\n"); + return ret; + } + + /* Call vpif_set_params function to set the parameters and addresses */ + ret = vpif_set_video_params(vpif, ch->channel_id); + + if (ret < 0) { + vpif_dbg(1, debug, "can't set video params\n"); + return ret; + } + + common->started = ret; + vpif_config_addr(ch, ret); + + common->set_addr(addr + common->ytop_off, + addr + common->ybtm_off, + addr + common->ctop_off, + addr + common->cbtm_off); + + /** + * Set interrupt for both the fields in VPIF Register enable channel in + * VPIF register + */ + channel_first_int[VPIF_VIDEO_INDEX][ch->channel_id] = 1; + if ((VPIF_CHANNEL0_VIDEO == ch->channel_id)) { + channel0_intr_assert(); + channel0_intr_enable(1); + enable_channel0(1); + } + if ((VPIF_CHANNEL1_VIDEO == ch->channel_id) || + (common->started == 2)) { + channel1_intr_assert(); + channel1_intr_enable(1); + enable_channel1(1); + } + + return 0; +} + +/* abort streaming and wait for last buffer */ +static int vpif_stop_streaming(struct vb2_queue *vq) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + + if (!vb2_is_streaming(vq)) + return 0; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + /* release all active buffers */ + while (!list_empty(&common->dma_queue)) { + common->next_frm = list_entry(common->dma_queue.next, + struct vpif_cap_buffer, list); + list_del(&common->next_frm->list); + vb2_buffer_done(&common->next_frm->vb, VB2_BUF_STATE_ERROR); + } + + return 0; +} + +static struct vb2_ops video_qops = { + .queue_setup = vpif_buffer_queue_setup, + .wait_prepare = vpif_wait_prepare, + .wait_finish = vpif_wait_finish, + .buf_init = vpif_buffer_init, + .buf_prepare = vpif_buffer_prepare, + .start_streaming = vpif_start_streaming, + .stop_streaming = vpif_stop_streaming, + .buf_cleanup = vpif_buf_cleanup, + .buf_queue = vpif_buffer_queue, +}; + +/** + * vpif_process_buffer_complete: process a completed buffer + * @common: ptr to common channel object + * + * This function time stamp the buffer and mark it as DONE. It also + * wake up any process waiting on the QUEUE and set the next buffer + * as current + */ +static void vpif_process_buffer_complete(struct common_obj *common) +{ + do_gettimeofday(&common->cur_frm->vb.v4l2_buf.timestamp); + vb2_buffer_done(&common->cur_frm->vb, + VB2_BUF_STATE_DONE); + /* Make curFrm pointing to nextFrm */ + common->cur_frm = common->next_frm; +} + +/** + * vpif_schedule_next_buffer: set next buffer address for capture + * @common : ptr to common channel object + * + * This function will get next buffer from the dma queue and + * set the buffer address in the vpif register for capture. + * the buffer is marked active + */ +static void vpif_schedule_next_buffer(struct common_obj *common) +{ + unsigned long addr = 0; + + common->next_frm = list_entry(common->dma_queue.next, + struct vpif_cap_buffer, list); + /* Remove that buffer from the buffer queue */ + list_del(&common->next_frm->list); + common->next_frm->vb.state = VB2_BUF_STATE_ACTIVE; + addr = vb2_dma_contig_plane_dma_addr(&common->next_frm->vb, 0); + + /* Set top and bottom field addresses in VPIF registers */ + common->set_addr(addr + common->ytop_off, + addr + common->ybtm_off, + addr + common->ctop_off, + addr + common->cbtm_off); +} + +/** + * vpif_channel_isr : ISR handler for vpif capture + * @irq: irq number + * @dev_id: dev_id ptr + * + * It changes status of the captured buffer, takes next buffer from the queue + * and sets its address in VPIF registers + */ +static irqreturn_t vpif_channel_isr(int irq, void *dev_id) +{ + struct vpif_device *dev = &vpif_obj; + struct common_obj *common; + struct channel_obj *ch; + enum v4l2_field field; + int channel_id = 0; + int fid = -1, i; + + channel_id = *(int *)(dev_id); + if (!vpif_intr_status(channel_id)) + return IRQ_NONE; + + ch = dev->dev[channel_id]; + + field = ch->common[VPIF_VIDEO_INDEX].fmt.fmt.pix.field; + + for (i = 0; i < VPIF_NUMBER_OF_OBJECTS; i++) { + common = &ch->common[i]; + /* skip If streaming is not started in this channel */ + if (0 == common->started) + continue; + + /* Check the field format */ + if (1 == ch->vpifparams.std_info.frm_fmt) { + /* Progressive mode */ + if (list_empty(&common->dma_queue)) + continue; + + if (!channel_first_int[i][channel_id]) + vpif_process_buffer_complete(common); + + channel_first_int[i][channel_id] = 0; + + vpif_schedule_next_buffer(common); + + + channel_first_int[i][channel_id] = 0; + } else { + /** + * Interlaced mode. If it is first interrupt, ignore + * it + */ + if (channel_first_int[i][channel_id]) { + channel_first_int[i][channel_id] = 0; + continue; + } + if (0 == i) { + ch->field_id ^= 1; + /* Get field id from VPIF registers */ + fid = vpif_channel_getfid(ch->channel_id); + if (fid != ch->field_id) { + /** + * If field id does not match stored + * field id, make them in sync + */ + if (0 == fid) + ch->field_id = fid; + return IRQ_HANDLED; + } + } + /* device field id and local field id are in sync */ + if (0 == fid) { + /* this is even field */ + if (common->cur_frm == common->next_frm) + continue; + + /* mark the current buffer as done */ + vpif_process_buffer_complete(common); + } else if (1 == fid) { + /* odd field */ + if (list_empty(&common->dma_queue) || + (common->cur_frm != common->next_frm)) + continue; + + vpif_schedule_next_buffer(common); + } + } + } + return IRQ_HANDLED; +} + +/** + * vpif_update_std_info() - update standard related info + * @ch: ptr to channel object + * + * For a given standard selected by application, update values + * in the device data structures + */ +static int vpif_update_std_info(struct channel_obj *ch) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_params *vpifparams = &ch->vpifparams; + const struct vpif_channel_config_params *config; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + struct video_obj *vid_ch = &ch->video; + int index; + + vpif_dbg(2, debug, "vpif_update_std_info\n"); + + for (index = 0; index < vpif_ch_params_count; index++) { + config = &ch_params[index]; + if (config->hd_sd == 0) { + vpif_dbg(2, debug, "SD format\n"); + if (config->stdid & vid_ch->stdid) { + memcpy(std_info, config, sizeof(*config)); + break; + } + } else { + vpif_dbg(2, debug, "HD format\n"); + if (!memcmp(&config->dv_timings, &vid_ch->dv_timings, + sizeof(vid_ch->dv_timings))) { + memcpy(std_info, config, sizeof(*config)); + break; + } + } + } + + /* standard not found */ + if (index == vpif_ch_params_count) + return -EINVAL; + + common->fmt.fmt.pix.width = std_info->width; + common->width = std_info->width; + common->fmt.fmt.pix.height = std_info->height; + common->height = std_info->height; + common->fmt.fmt.pix.bytesperline = std_info->width; + vpifparams->video_params.hpitch = std_info->width; + vpifparams->video_params.storage_mode = std_info->frm_fmt; + + return 0; +} + +/** + * vpif_calculate_offsets : This function calculates buffers offsets + * @ch : ptr to channel object + * + * This function calculates buffer offsets for Y and C in the top and + * bottom field + */ +static void vpif_calculate_offsets(struct channel_obj *ch) +{ + unsigned int hpitch, vpitch, sizeimage; + struct video_obj *vid_ch = &(ch->video); + struct vpif_params *vpifparams = &ch->vpifparams; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + enum v4l2_field field = common->fmt.fmt.pix.field; + + vpif_dbg(2, debug, "vpif_calculate_offsets\n"); + + if (V4L2_FIELD_ANY == field) { + if (vpifparams->std_info.frm_fmt) + vid_ch->buf_field = V4L2_FIELD_NONE; + else + vid_ch->buf_field = V4L2_FIELD_INTERLACED; + } else + vid_ch->buf_field = common->fmt.fmt.pix.field; + + sizeimage = common->fmt.fmt.pix.sizeimage; + + hpitch = common->fmt.fmt.pix.bytesperline; + vpitch = sizeimage / (hpitch * 2); + + if ((V4L2_FIELD_NONE == vid_ch->buf_field) || + (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) { + /* Calculate offsets for Y top, Y Bottom, C top and C Bottom */ + common->ytop_off = 0; + common->ybtm_off = hpitch; + common->ctop_off = sizeimage / 2; + common->cbtm_off = sizeimage / 2 + hpitch; + } else if (V4L2_FIELD_SEQ_TB == vid_ch->buf_field) { + /* Calculate offsets for Y top, Y Bottom, C top and C Bottom */ + common->ytop_off = 0; + common->ybtm_off = sizeimage / 4; + common->ctop_off = sizeimage / 2; + common->cbtm_off = common->ctop_off + sizeimage / 4; + } else if (V4L2_FIELD_SEQ_BT == vid_ch->buf_field) { + /* Calculate offsets for Y top, Y Bottom, C top and C Bottom */ + common->ybtm_off = 0; + common->ytop_off = sizeimage / 4; + common->cbtm_off = sizeimage / 2; + common->ctop_off = common->cbtm_off + sizeimage / 4; + } + if ((V4L2_FIELD_NONE == vid_ch->buf_field) || + (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) + vpifparams->video_params.storage_mode = 1; + else + vpifparams->video_params.storage_mode = 0; + + if (1 == vpifparams->std_info.frm_fmt) + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline; + else { + if ((field == V4L2_FIELD_ANY) + || (field == V4L2_FIELD_INTERLACED)) + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline * 2; + else + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline; + } + + ch->vpifparams.video_params.stdid = vpifparams->std_info.stdid; +} + +/** + * vpif_config_format: configure default frame format in the device + * ch : ptr to channel object + */ +static void vpif_config_format(struct channel_obj *ch) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + vpif_dbg(2, debug, "vpif_config_format\n"); + + common->fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (config_params.numbuffers[ch->channel_id] == 0) + common->memory = V4L2_MEMORY_USERPTR; + else + common->memory = V4L2_MEMORY_MMAP; + + common->fmt.fmt.pix.sizeimage + = config_params.channel_bufsize[ch->channel_id]; + + if (ch->vpifparams.iface.if_type == VPIF_IF_RAW_BAYER) + common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_SBGGR8; + else + common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P; + common->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +} + +/** + * vpif_get_default_field() - Get default field type based on interface + * @vpif_params - ptr to vpif params + */ +static inline enum v4l2_field vpif_get_default_field( + struct vpif_interface *iface) +{ + return (iface->if_type == VPIF_IF_RAW_BAYER) ? V4L2_FIELD_NONE : + V4L2_FIELD_INTERLACED; +} + +/** + * vpif_check_format() - check given pixel format for compatibility + * @ch - channel ptr + * @pixfmt - Given pixel format + * @update - update the values as per hardware requirement + * + * Check the application pixel format for S_FMT and update the input + * values as per hardware limits for TRY_FMT. The default pixel and + * field format is selected based on interface type. + */ +static int vpif_check_format(struct channel_obj *ch, + struct v4l2_pix_format *pixfmt, + int update) +{ + struct common_obj *common = &(ch->common[VPIF_VIDEO_INDEX]); + struct vpif_params *vpif_params = &ch->vpifparams; + enum v4l2_field field = pixfmt->field; + u32 sizeimage, hpitch, vpitch; + int ret = -EINVAL; + + vpif_dbg(2, debug, "vpif_check_format\n"); + /** + * first check for the pixel format. If if_type is Raw bayer, + * only V4L2_PIX_FMT_SBGGR8 format is supported. Otherwise only + * V4L2_PIX_FMT_YUV422P is supported + */ + if (vpif_params->iface.if_type == VPIF_IF_RAW_BAYER) { + if (pixfmt->pixelformat != V4L2_PIX_FMT_SBGGR8) { + if (!update) { + vpif_dbg(2, debug, "invalid pix format\n"); + goto exit; + } + pixfmt->pixelformat = V4L2_PIX_FMT_SBGGR8; + } + } else { + if (pixfmt->pixelformat != V4L2_PIX_FMT_YUV422P) { + if (!update) { + vpif_dbg(2, debug, "invalid pixel format\n"); + goto exit; + } + pixfmt->pixelformat = V4L2_PIX_FMT_YUV422P; + } + } + + if (!(VPIF_VALID_FIELD(field))) { + if (!update) { + vpif_dbg(2, debug, "invalid field format\n"); + goto exit; + } + /** + * By default use FIELD_NONE for RAW Bayer capture + * and FIELD_INTERLACED for other interfaces + */ + field = vpif_get_default_field(&vpif_params->iface); + } else if (field == V4L2_FIELD_ANY) + /* unsupported field. Use default */ + field = vpif_get_default_field(&vpif_params->iface); + + /* validate the hpitch */ + hpitch = pixfmt->bytesperline; + if (hpitch < vpif_params->std_info.width) { + if (!update) { + vpif_dbg(2, debug, "invalid hpitch\n"); + goto exit; + } + hpitch = vpif_params->std_info.width; + } + + sizeimage = pixfmt->sizeimage; + + vpitch = sizeimage / (hpitch * 2); + + /* validate the vpitch */ + if (vpitch < vpif_params->std_info.height) { + if (!update) { + vpif_dbg(2, debug, "Invalid vpitch\n"); + goto exit; + } + vpitch = vpif_params->std_info.height; + } + + /* Check for 8 byte alignment */ + if (!ALIGN(hpitch, 8)) { + if (!update) { + vpif_dbg(2, debug, "invalid pitch alignment\n"); + goto exit; + } + /* adjust to next 8 byte boundary */ + hpitch = (((hpitch + 7) / 8) * 8); + } + /* if update is set, modify the bytesperline and sizeimage */ + if (update) { + pixfmt->bytesperline = hpitch; + pixfmt->sizeimage = hpitch * vpitch * 2; + } + /** + * Image width and height is always based on current standard width and + * height + */ + pixfmt->width = common->fmt.fmt.pix.width; + pixfmt->height = common->fmt.fmt.pix.height; + return 0; +exit: + return ret; +} + +/** + * vpif_config_addr() - function to configure buffer address in vpif + * @ch - channel ptr + * @muxmode - channel mux mode + */ +static void vpif_config_addr(struct channel_obj *ch, int muxmode) +{ + struct common_obj *common; + + vpif_dbg(2, debug, "vpif_config_addr\n"); + + common = &(ch->common[VPIF_VIDEO_INDEX]); + + if (VPIF_CHANNEL1_VIDEO == ch->channel_id) + common->set_addr = ch1_set_videobuf_addr; + else if (2 == muxmode) + common->set_addr = ch0_set_videobuf_addr_yc_nmux; + else + common->set_addr = ch0_set_videobuf_addr; +} + +/** + * vpif_mmap : It is used to map kernel space buffers into user spaces + * @filep: file pointer + * @vma: ptr to vm_area_struct + */ +static int vpif_mmap(struct file *filep, struct vm_area_struct *vma) +{ + /* Get the channel object and file handle object */ + struct vpif_fh *fh = filep->private_data; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &(ch->common[VPIF_VIDEO_INDEX]); + int ret; + + vpif_dbg(2, debug, "vpif_mmap\n"); + + if (mutex_lock_interruptible(&common->lock)) + return -ERESTARTSYS; + ret = vb2_mmap(&common->buffer_queue, vma); + mutex_unlock(&common->lock); + return ret; +} + +/** + * vpif_poll: It is used for select/poll system call + * @filep: file pointer + * @wait: poll table to wait + */ +static unsigned int vpif_poll(struct file *filep, poll_table * wait) +{ + struct vpif_fh *fh = filep->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = &(channel->common[VPIF_VIDEO_INDEX]); + unsigned int res = 0; + + vpif_dbg(2, debug, "vpif_poll\n"); + + if (common->started) { + mutex_lock(&common->lock); + res = vb2_poll(&common->buffer_queue, filep, wait); + mutex_unlock(&common->lock); + } + return res; +} + +/** + * vpif_open : vpif open handler + * @filep: file ptr + * + * It creates object of file handle structure and stores it in private_data + * member of filepointer + */ +static int vpif_open(struct file *filep) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(filep); + struct common_obj *common; + struct video_obj *vid_ch; + struct channel_obj *ch; + struct vpif_fh *fh; + int i; + + vpif_dbg(2, debug, "vpif_open\n"); + + ch = video_get_drvdata(vdev); + + vid_ch = &ch->video; + common = &ch->common[VPIF_VIDEO_INDEX]; + + if (NULL == ch->curr_subdev_info) { + /** + * search through the sub device to see a registered + * sub device and make it as current sub device + */ + for (i = 0; i < config->subdev_count; i++) { + if (vpif_obj.sd[i]) { + /* the sub device is registered */ + ch->curr_subdev_info = &config->subdev_info[i]; + /* make first input as the current input */ + vid_ch->input_idx = 0; + break; + } + } + if (i == config->subdev_count) { + vpif_err("No sub device registered\n"); + return -ENOENT; + } + } + + /* Allocate memory for the file handle object */ + fh = kzalloc(sizeof(struct vpif_fh), GFP_KERNEL); + if (NULL == fh) { + vpif_err("unable to allocate memory for file handle object\n"); + return -ENOMEM; + } + + if (mutex_lock_interruptible(&common->lock)) { + kfree(fh); + return -ERESTARTSYS; + } + /* store pointer to fh in private_data member of filep */ + filep->private_data = fh; + fh->channel = ch; + fh->initialized = 0; + /* If decoder is not initialized. initialize it */ + if (!ch->initialized) { + fh->initialized = 1; + ch->initialized = 1; + memset(&(ch->vpifparams), 0, sizeof(struct vpif_params)); + } + /* Increment channel usrs counter */ + ch->usrs++; + /* Set io_allowed member to false */ + fh->io_allowed[VPIF_VIDEO_INDEX] = 0; + /* Initialize priority of this instance to default priority */ + fh->prio = V4L2_PRIORITY_UNSET; + v4l2_prio_open(&ch->prio, &fh->prio); + mutex_unlock(&common->lock); + return 0; +} + +/** + * vpif_release : function to clean up file close + * @filep: file pointer + * + * This function deletes buffer queue, frees the buffers and the vpif file + * handle + */ +static int vpif_release(struct file *filep) +{ + struct vpif_fh *fh = filep->private_data; + struct channel_obj *ch = fh->channel; + struct common_obj *common; + + vpif_dbg(2, debug, "vpif_release\n"); + + common = &ch->common[VPIF_VIDEO_INDEX]; + + mutex_lock(&common->lock); + /* if this instance is doing IO */ + if (fh->io_allowed[VPIF_VIDEO_INDEX]) { + /* Reset io_usrs member of channel object */ + common->io_usrs = 0; + /* Disable channel as per its device type and channel id */ + if (VPIF_CHANNEL0_VIDEO == ch->channel_id) { + enable_channel0(0); + channel0_intr_enable(0); + } + if ((VPIF_CHANNEL1_VIDEO == ch->channel_id) || + (2 == common->started)) { + enable_channel1(0); + channel1_intr_enable(0); + } + common->started = 0; + /* Free buffers allocated */ + vb2_queue_release(&common->buffer_queue); + vb2_dma_contig_cleanup_ctx(common->alloc_ctx); + } + + /* Decrement channel usrs counter */ + ch->usrs--; + + /* Close the priority */ + v4l2_prio_close(&ch->prio, fh->prio); + + if (fh->initialized) + ch->initialized = 0; + + mutex_unlock(&common->lock); + filep->private_data = NULL; + kfree(fh); + return 0; +} + +/** + * vpif_reqbufs() - request buffer handler + * @file: file ptr + * @priv: file handle + * @reqbuf: request buffer structure ptr + */ +static int vpif_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbuf) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common; + u8 index = 0; + struct vb2_queue *q; + + vpif_dbg(2, debug, "vpif_reqbufs\n"); + + /** + * This file handle has not initialized the channel, + * It is not allowed to do settings + */ + if ((VPIF_CHANNEL0_VIDEO == ch->channel_id) + || (VPIF_CHANNEL1_VIDEO == ch->channel_id)) { + if (!fh->initialized) { + vpif_dbg(1, debug, "Channel Busy\n"); + return -EBUSY; + } + } + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != reqbuf->type || !vpif_dev) + return -EINVAL; + + index = VPIF_VIDEO_INDEX; + + common = &ch->common[index]; + + if (0 != common->io_usrs) + return -EBUSY; + + /* Initialize videobuf2 queue as per the buffer type */ + common->alloc_ctx = vb2_dma_contig_init_ctx(vpif_dev); + if (!common->alloc_ctx) { + vpif_err("Failed to get the context\n"); + return -EINVAL; + } + q = &common->buffer_queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->drv_priv = fh; + q->ops = &video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct vpif_cap_buffer); + + vb2_queue_init(q); + + /* Set io allowed member of file handle to TRUE */ + fh->io_allowed[index] = 1; + /* Increment io usrs member of channel object to 1 */ + common->io_usrs = 1; + /* Store type of memory requested in channel object */ + common->memory = reqbuf->memory; + INIT_LIST_HEAD(&common->dma_queue); + + /* Allocate buffers */ + return vb2_reqbufs(&common->buffer_queue, reqbuf); +} + +/** + * vpif_querybuf() - query buffer handler + * @file: file ptr + * @priv: file handle + * @buf: v4l2 buffer structure ptr + */ +static int vpif_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + vpif_dbg(2, debug, "vpif_querybuf\n"); + + if (common->fmt.type != buf->type) + return -EINVAL; + + if (common->memory != V4L2_MEMORY_MMAP) { + vpif_dbg(1, debug, "Invalid memory\n"); + return -EINVAL; + } + + return vb2_querybuf(&common->buffer_queue, buf); +} + +/** + * vpif_qbuf() - query buffer handler + * @file: file ptr + * @priv: file handle + * @buf: v4l2 buffer structure ptr + */ +static int vpif_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct v4l2_buffer tbuf = *buf; + + vpif_dbg(2, debug, "vpif_qbuf\n"); + + if (common->fmt.type != tbuf.type) { + vpif_err("invalid buffer type\n"); + return -EINVAL; + } + + if (!fh->io_allowed[VPIF_VIDEO_INDEX]) { + vpif_err("fh io not allowed\n"); + return -EACCES; + } + + return vb2_qbuf(&common->buffer_queue, buf); +} + +/** + * vpif_dqbuf() - query buffer handler + * @file: file ptr + * @priv: file handle + * @buf: v4l2 buffer structure ptr + */ +static int vpif_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + vpif_dbg(2, debug, "vpif_dqbuf\n"); + + return vb2_dqbuf(&common->buffer_queue, buf, + (file->f_flags & O_NONBLOCK)); +} + +/** + * vpif_streamon() - streamon handler + * @file: file ptr + * @priv: file handle + * @buftype: v4l2 buffer type + */ +static int vpif_streamon(struct file *file, void *priv, + enum v4l2_buf_type buftype) +{ + + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct channel_obj *oth_ch = vpif_obj.dev[!ch->channel_id]; + struct vpif_params *vpif; + int ret = 0; + + vpif_dbg(2, debug, "vpif_streamon\n"); + + vpif = &ch->vpifparams; + + if (buftype != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + vpif_dbg(1, debug, "buffer type not supported\n"); + return -EINVAL; + } + + /* If file handle is not allowed IO, return error */ + if (!fh->io_allowed[VPIF_VIDEO_INDEX]) { + vpif_dbg(1, debug, "io not allowed\n"); + return -EACCES; + } + + /* If Streaming is already started, return error */ + if (common->started) { + vpif_dbg(1, debug, "channel->started\n"); + return -EBUSY; + } + + if ((ch->channel_id == VPIF_CHANNEL0_VIDEO && + oth_ch->common[VPIF_VIDEO_INDEX].started && + vpif->std_info.ycmux_mode == 0) || + ((ch->channel_id == VPIF_CHANNEL1_VIDEO) && + (2 == oth_ch->common[VPIF_VIDEO_INDEX].started))) { + vpif_dbg(1, debug, "other channel is being used\n"); + return -EBUSY; + } + + ret = vpif_check_format(ch, &common->fmt.fmt.pix, 0); + if (ret) + return ret; + + /* Enable streamon on the sub device */ + ret = v4l2_subdev_call(vpif_obj.sd[ch->curr_sd_index], video, + s_stream, 1); + + if (ret && (ret != -ENOIOCTLCMD)) { + vpif_dbg(1, debug, "stream on failed in subdev\n"); + return ret; + } + + /* Call vb2_streamon to start streaming in videobuf2 */ + ret = vb2_streamon(&common->buffer_queue, buftype); + if (ret) { + vpif_dbg(1, debug, "vb2_streamon\n"); + return ret; + } + + return ret; +} + +/** + * vpif_streamoff() - streamoff handler + * @file: file ptr + * @priv: file handle + * @buftype: v4l2 buffer type + */ +static int vpif_streamoff(struct file *file, void *priv, + enum v4l2_buf_type buftype) +{ + + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + int ret; + + vpif_dbg(2, debug, "vpif_streamoff\n"); + + if (buftype != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + vpif_dbg(1, debug, "buffer type not supported\n"); + return -EINVAL; + } + + /* If io is allowed for this file handle, return error */ + if (!fh->io_allowed[VPIF_VIDEO_INDEX]) { + vpif_dbg(1, debug, "io not allowed\n"); + return -EACCES; + } + + /* If streaming is not started, return error */ + if (!common->started) { + vpif_dbg(1, debug, "channel->started\n"); + return -EINVAL; + } + + /* disable channel */ + if (VPIF_CHANNEL0_VIDEO == ch->channel_id) { + enable_channel0(0); + channel0_intr_enable(0); + } else { + enable_channel1(0); + channel1_intr_enable(0); + } + + common->started = 0; + + ret = v4l2_subdev_call(vpif_obj.sd[ch->curr_sd_index], video, + s_stream, 0); + + if (ret && (ret != -ENOIOCTLCMD)) + vpif_dbg(1, debug, "stream off failed in subdev\n"); + + return vb2_streamoff(&common->buffer_queue, buftype); +} + +/** + * vpif_map_sub_device_to_input() - Maps sub device to input + * @ch - ptr to channel + * @config - ptr to capture configuration + * @input_index - Given input index from application + * @sub_device_index - index into sd table + * + * lookup the sub device information for a given input index. + * we report all the inputs to application. inputs table also + * has sub device name for the each input + */ +static struct vpif_subdev_info *vpif_map_sub_device_to_input( + struct channel_obj *ch, + struct vpif_capture_config *vpif_cfg, + int input_index, + int *sub_device_index) +{ + struct vpif_capture_chan_config *chan_cfg; + struct vpif_subdev_info *subdev_info = NULL; + const char *subdev_name = NULL; + int i; + + vpif_dbg(2, debug, "vpif_map_sub_device_to_input\n"); + + chan_cfg = &vpif_cfg->chan_config[ch->channel_id]; + + /** + * search through the inputs to find the sub device supporting + * the input + */ + for (i = 0; i < chan_cfg->input_count; i++) { + /* For each sub device, loop through input */ + if (i == input_index) { + subdev_name = chan_cfg->inputs[i].subdev_name; + break; + } + } + + /* if reached maximum. return null */ + if (i == chan_cfg->input_count || (NULL == subdev_name)) + return subdev_info; + + /* loop through the sub device list to get the sub device info */ + for (i = 0; i < vpif_cfg->subdev_count; i++) { + subdev_info = &vpif_cfg->subdev_info[i]; + if (!strcmp(subdev_info->name, subdev_name)) + break; + } + + if (i == vpif_cfg->subdev_count) + return subdev_info; + + /* check if the sub device is registered */ + if (NULL == vpif_obj.sd[i]) + return NULL; + + *sub_device_index = i; + return subdev_info; +} + +/** + * vpif_querystd() - querystd handler + * @file: file ptr + * @priv: file handle + * @std_id: ptr to std id + * + * This function is called to detect standard at the selected input + */ +static int vpif_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + int ret = 0; + + vpif_dbg(2, debug, "vpif_querystd\n"); + + /* Call querystd function of decoder device */ + ret = v4l2_subdev_call(vpif_obj.sd[ch->curr_sd_index], video, + querystd, std_id); + if (ret < 0) + vpif_dbg(1, debug, "Failed to set standard for sub devices\n"); + + return ret; +} + +/** + * vpif_g_std() - get STD handler + * @file: file ptr + * @priv: file handle + * @std_id: ptr to std id + */ +static int vpif_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + vpif_dbg(2, debug, "vpif_g_std\n"); + + *std = ch->video.stdid; + return 0; +} + +/** + * vpif_s_std() - set STD handler + * @file: file ptr + * @priv: file handle + * @std_id: ptr to std id + */ +static int vpif_s_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + int ret = 0; + + vpif_dbg(2, debug, "vpif_s_std\n"); + + if (common->started) { + vpif_err("streaming in progress\n"); + return -EBUSY; + } + + if ((VPIF_CHANNEL0_VIDEO == ch->channel_id) || + (VPIF_CHANNEL1_VIDEO == ch->channel_id)) { + if (!fh->initialized) { + vpif_dbg(1, debug, "Channel Busy\n"); + return -EBUSY; + } + } + + ret = v4l2_prio_check(&ch->prio, fh->prio); + if (0 != ret) + return ret; + + fh->initialized = 1; + + /* Call encoder subdevice function to set the standard */ + ch->video.stdid = *std_id; + memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); + + /* Get the information about the standard */ + if (vpif_update_std_info(ch)) { + vpif_err("Error getting the standard info\n"); + return -EINVAL; + } + + /* Configure the default format information */ + vpif_config_format(ch); + + /* set standard in the sub device */ + ret = v4l2_subdev_call(vpif_obj.sd[ch->curr_sd_index], core, + s_std, *std_id); + if (ret < 0) + vpif_dbg(1, debug, "Failed to set standard for sub devices\n"); + return ret; +} + +/** + * vpif_enum_input() - ENUMINPUT handler + * @file: file ptr + * @priv: file handle + * @input: ptr to input structure + */ +static int vpif_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + + struct vpif_capture_config *config = vpif_dev->platform_data; + struct vpif_capture_chan_config *chan_cfg; + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + chan_cfg = &config->chan_config[ch->channel_id]; + + if (input->index >= chan_cfg->input_count) { + vpif_dbg(1, debug, "Invalid input index\n"); + return -EINVAL; + } + + memcpy(input, &chan_cfg->inputs[input->index].input, + sizeof(*input)); + return 0; +} + +/** + * vpif_g_input() - Get INPUT handler + * @file: file ptr + * @priv: file handle + * @index: ptr to input index + */ +static int vpif_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct video_obj *vid_ch = &ch->video; + + *index = vid_ch->input_idx; + + return 0; +} + +/** + * vpif_s_input() - Set INPUT handler + * @file: file ptr + * @priv: file handle + * @index: input index + */ +static int vpif_s_input(struct file *file, void *priv, unsigned int index) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + struct vpif_capture_chan_config *chan_cfg; + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct video_obj *vid_ch = &ch->video; + struct vpif_subdev_info *subdev_info; + int ret = 0, sd_index = 0; + u32 input = 0, output = 0; + + chan_cfg = &config->chan_config[ch->channel_id]; + + if (common->started) { + vpif_err("Streaming in progress\n"); + return -EBUSY; + } + + if ((VPIF_CHANNEL0_VIDEO == ch->channel_id) || + (VPIF_CHANNEL1_VIDEO == ch->channel_id)) { + if (!fh->initialized) { + vpif_dbg(1, debug, "Channel Busy\n"); + return -EBUSY; + } + } + + ret = v4l2_prio_check(&ch->prio, fh->prio); + if (0 != ret) + return ret; + + fh->initialized = 1; + subdev_info = vpif_map_sub_device_to_input(ch, config, index, + &sd_index); + if (NULL == subdev_info) { + vpif_dbg(1, debug, + "couldn't lookup sub device for the input index\n"); + return -EINVAL; + } + + /* first setup input path from sub device to vpif */ + if (config->setup_input_path) { + ret = config->setup_input_path(ch->channel_id, + subdev_info->name); + if (ret < 0) { + vpif_dbg(1, debug, "couldn't setup input path for the" + " sub device %s, for input index %d\n", + subdev_info->name, index); + return ret; + } + } + + if (subdev_info->can_route) { + input = subdev_info->input; + output = subdev_info->output; + ret = v4l2_subdev_call(vpif_obj.sd[sd_index], video, s_routing, + input, output, 0); + if (ret < 0) { + vpif_dbg(1, debug, "Failed to set input\n"); + return ret; + } + } + vid_ch->input_idx = index; + ch->curr_subdev_info = subdev_info; + ch->curr_sd_index = sd_index; + /* copy interface parameters to vpif */ + ch->vpifparams.iface = subdev_info->vpif_if; + + /* update tvnorms from the sub device input info */ + ch->video_dev->tvnorms = chan_cfg->inputs[index].input.std; + return ret; +} + +/** + * vpif_enum_fmt_vid_cap() - ENUM_FMT handler + * @file: file ptr + * @priv: file handle + * @index: input index + */ +static int vpif_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + if (fmt->index != 0) { + vpif_dbg(1, debug, "Invalid format index\n"); + return -EINVAL; + } + + /* Fill in the information about format */ + if (ch->vpifparams.iface.if_type == VPIF_IF_RAW_BAYER) { + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + strcpy(fmt->description, "Raw Mode -Bayer Pattern GrRBGb"); + fmt->pixelformat = V4L2_PIX_FMT_SBGGR8; + } else { + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + strcpy(fmt->description, "YCbCr4:2:2 YC Planar"); + fmt->pixelformat = V4L2_PIX_FMT_YUV422P; + } + return 0; +} + +/** + * vpif_try_fmt_vid_cap() - TRY_FMT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to v4l2 format structure + */ +static int vpif_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + return vpif_check_format(ch, pixfmt, 1); +} + + +/** + * vpif_g_fmt_vid_cap() - Set INPUT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to v4l2 format structure + */ +static int vpif_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + /* Check the validity of the buffer type */ + if (common->fmt.type != fmt->type) + return -EINVAL; + + /* Fill in the information about format */ + *fmt = common->fmt; + return 0; +} + +/** + * vpif_s_fmt_vid_cap() - Set FMT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to v4l2 format structure + */ +static int vpif_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct v4l2_pix_format *pixfmt; + int ret = 0; + + vpif_dbg(2, debug, "%s\n", __func__); + + /* If streaming is started, return error */ + if (common->started) { + vpif_dbg(1, debug, "Streaming is started\n"); + return -EBUSY; + } + + if ((VPIF_CHANNEL0_VIDEO == ch->channel_id) || + (VPIF_CHANNEL1_VIDEO == ch->channel_id)) { + if (!fh->initialized) { + vpif_dbg(1, debug, "Channel Busy\n"); + return -EBUSY; + } + } + + ret = v4l2_prio_check(&ch->prio, fh->prio); + if (0 != ret) + return ret; + + fh->initialized = 1; + + pixfmt = &fmt->fmt.pix; + /* Check for valid field format */ + ret = vpif_check_format(ch, pixfmt, 0); + + if (ret) + return ret; + /* store the format in the channel object */ + common->fmt = *fmt; + return 0; +} + +/** + * vpif_querycap() - QUERYCAP handler + * @file: file ptr + * @priv: file handle + * @cap: ptr to v4l2_capability structure + */ +static int vpif_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + strlcpy(cap->driver, "vpif capture", sizeof(cap->driver)); + strlcpy(cap->bus_info, "VPIF Platform", sizeof(cap->bus_info)); + strlcpy(cap->card, config->card_name, sizeof(cap->card)); + + return 0; +} + +/** + * vpif_g_priority() - get priority handler + * @file: file ptr + * @priv: file handle + * @prio: ptr to v4l2_priority structure + */ +static int vpif_g_priority(struct file *file, void *priv, + enum v4l2_priority *prio) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + *prio = v4l2_prio_max(&ch->prio); + + return 0; +} + +/** + * vpif_s_priority() - set priority handler + * @file: file ptr + * @priv: file handle + * @prio: ptr to v4l2_priority structure + */ +static int vpif_s_priority(struct file *file, void *priv, enum v4l2_priority p) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + return v4l2_prio_change(&ch->prio, &fh->prio, p); +} + +/** + * vpif_cropcap() - cropcap handler + * @file: file ptr + * @priv: file handle + * @crop: ptr to v4l2_cropcap structure + */ +static int vpif_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *crop) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != crop->type) + return -EINVAL; + + crop->bounds.left = 0; + crop->bounds.top = 0; + crop->bounds.height = common->height; + crop->bounds.width = common->width; + crop->defrect = crop->bounds; + return 0; +} + +/** + * vpif_enum_dv_timings() - ENUM_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: input timings + */ +static int +vpif_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + return v4l2_subdev_call(vpif_obj.sd[ch->curr_sd_index], + video, enum_dv_timings, timings); +} + +/** + * vpif_query_dv_timings() - QUERY_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: input timings + */ +static int +vpif_query_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + return v4l2_subdev_call(vpif_obj.sd[ch->curr_sd_index], + video, query_dv_timings, timings); +} + +/** + * vpif_s_dv_timings() - S_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_s_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct vpif_params *vpifparams = &ch->vpifparams; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + struct video_obj *vid_ch = &ch->video; + struct v4l2_bt_timings *bt = &vid_ch->dv_timings.bt; + int ret; + + if (timings->type != V4L2_DV_BT_656_1120) { + vpif_dbg(2, debug, "Timing type not defined\n"); + return -EINVAL; + } + + /* Configure subdevice timings, if any */ + ret = v4l2_subdev_call(vpif_obj.sd[ch->curr_sd_index], + video, s_dv_timings, timings); + if (ret == -ENOIOCTLCMD) { + vpif_dbg(2, debug, "Custom DV timings not supported by " + "subdevice\n"); + return -EINVAL; + } + if (ret < 0) { + vpif_dbg(2, debug, "Error setting custom DV timings\n"); + return ret; + } + + if (!(timings->bt.width && timings->bt.height && + (timings->bt.hbackporch || + timings->bt.hfrontporch || + timings->bt.hsync) && + timings->bt.vfrontporch && + (timings->bt.vbackporch || + timings->bt.vsync))) { + vpif_dbg(2, debug, "Timings for width, height, " + "horizontal back porch, horizontal sync, " + "horizontal front porch, vertical back porch, " + "vertical sync and vertical back porch " + "must be defined\n"); + return -EINVAL; + } + + vid_ch->dv_timings = *timings; + + /* Configure video port timings */ + + std_info->eav2sav = bt->hbackporch + bt->hfrontporch + + bt->hsync - 8; + std_info->sav2eav = bt->width; + + std_info->l1 = 1; + std_info->l3 = bt->vsync + bt->vbackporch + 1; + + if (bt->interlaced) { + if (bt->il_vbackporch || bt->il_vfrontporch || bt->il_vsync) { + std_info->vsize = bt->height * 2 + + bt->vfrontporch + bt->vsync + bt->vbackporch + + bt->il_vfrontporch + bt->il_vsync + + bt->il_vbackporch; + std_info->l5 = std_info->vsize/2 - + (bt->vfrontporch - 1); + std_info->l7 = std_info->vsize/2 + 1; + std_info->l9 = std_info->l7 + bt->il_vsync + + bt->il_vbackporch + 1; + std_info->l11 = std_info->vsize - + (bt->il_vfrontporch - 1); + } else { + vpif_dbg(2, debug, "Required timing values for " + "interlaced BT format missing\n"); + return -EINVAL; + } + } else { + std_info->vsize = bt->height + bt->vfrontporch + + bt->vsync + bt->vbackporch; + std_info->l5 = std_info->vsize - (bt->vfrontporch - 1); + } + strncpy(std_info->name, "Custom timings BT656/1120", VPIF_MAX_NAME); + std_info->width = bt->width; + std_info->height = bt->height; + std_info->frm_fmt = bt->interlaced ? 0 : 1; + std_info->ycmux_mode = 0; + std_info->capture_format = 0; + std_info->vbi_supported = 0; + std_info->hd_sd = 1; + std_info->stdid = 0; + + vid_ch->stdid = 0; + return 0; +} + +/** + * vpif_g_dv_timings() - G_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_g_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct video_obj *vid_ch = &ch->video; + + *timings = vid_ch->dv_timings; + + return 0; +} + +/* + * vpif_g_chip_ident() - Identify the chip + * @file: file ptr + * @priv: file handle + * @chip: chip identity + * + * Returns zero or -EINVAL if read operations fails. + */ +static int vpif_g_chip_ident(struct file *file, void *priv, + struct v4l2_dbg_chip_ident *chip) +{ + chip->ident = V4L2_IDENT_NONE; + chip->revision = 0; + if (chip->match.type != V4L2_CHIP_MATCH_I2C_DRIVER && + chip->match.type != V4L2_CHIP_MATCH_I2C_ADDR) { + vpif_dbg(2, debug, "match_type is invalid.\n"); + return -EINVAL; + } + + return v4l2_device_call_until_err(&vpif_obj.v4l2_dev, 0, core, + g_chip_ident, chip); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +/* + * vpif_dbg_g_register() - Read register + * @file: file ptr + * @priv: file handle + * @reg: register to be read + * + * Debugging only + * Returns zero or -EINVAL if read operations fails. + */ +static int vpif_dbg_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg){ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + return v4l2_subdev_call(vpif_obj.sd[ch->curr_sd_index], core, + g_register, reg); +} + +/* + * vpif_dbg_s_register() - Write to register + * @file: file ptr + * @priv: file handle + * @reg: register to be modified + * + * Debugging only + * Returns zero or -EINVAL if write operations fails. + */ +static int vpif_dbg_s_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg){ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + return v4l2_subdev_call(vpif_obj.sd[ch->curr_sd_index], core, + s_register, reg); +} +#endif + +/* + * vpif_log_status() - Status information + * @file: file ptr + * @priv: file handle + * + * Returns zero. + */ +static int vpif_log_status(struct file *filep, void *priv) +{ + /* status for sub devices */ + v4l2_device_call_all(&vpif_obj.v4l2_dev, 0, core, log_status); + + return 0; +} + +/* vpif capture ioctl operations */ +static const struct v4l2_ioctl_ops vpif_ioctl_ops = { + .vidioc_querycap = vpif_querycap, + .vidioc_g_priority = vpif_g_priority, + .vidioc_s_priority = vpif_s_priority, + .vidioc_enum_fmt_vid_cap = vpif_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vpif_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vpif_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vpif_try_fmt_vid_cap, + .vidioc_enum_input = vpif_enum_input, + .vidioc_s_input = vpif_s_input, + .vidioc_g_input = vpif_g_input, + .vidioc_reqbufs = vpif_reqbufs, + .vidioc_querybuf = vpif_querybuf, + .vidioc_querystd = vpif_querystd, + .vidioc_s_std = vpif_s_std, + .vidioc_g_std = vpif_g_std, + .vidioc_qbuf = vpif_qbuf, + .vidioc_dqbuf = vpif_dqbuf, + .vidioc_streamon = vpif_streamon, + .vidioc_streamoff = vpif_streamoff, + .vidioc_cropcap = vpif_cropcap, + .vidioc_enum_dv_timings = vpif_enum_dv_timings, + .vidioc_query_dv_timings = vpif_query_dv_timings, + .vidioc_s_dv_timings = vpif_s_dv_timings, + .vidioc_g_dv_timings = vpif_g_dv_timings, + .vidioc_g_chip_ident = vpif_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vpif_dbg_g_register, + .vidioc_s_register = vpif_dbg_s_register, +#endif + .vidioc_log_status = vpif_log_status, +}; + +/* vpif file operations */ +static struct v4l2_file_operations vpif_fops = { + .owner = THIS_MODULE, + .open = vpif_open, + .release = vpif_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vpif_mmap, + .poll = vpif_poll +}; + +/* vpif video template */ +static struct video_device vpif_video_template = { + .name = "vpif", + .fops = &vpif_fops, + .minor = -1, + .ioctl_ops = &vpif_ioctl_ops, +}; + +/** + * initialize_vpif() - Initialize vpif data structures + * + * Allocate memory for data structures and initialize them + */ +static int initialize_vpif(void) +{ + int err = 0, i, j; + int free_channel_objects_index; + + /* Default number of buffers should be 3 */ + if ((ch0_numbuffers > 0) && + (ch0_numbuffers < config_params.min_numbuffers)) + ch0_numbuffers = config_params.min_numbuffers; + if ((ch1_numbuffers > 0) && + (ch1_numbuffers < config_params.min_numbuffers)) + ch1_numbuffers = config_params.min_numbuffers; + + /* Set buffer size to min buffers size if it is invalid */ + if (ch0_bufsize < config_params.min_bufsize[VPIF_CHANNEL0_VIDEO]) + ch0_bufsize = + config_params.min_bufsize[VPIF_CHANNEL0_VIDEO]; + if (ch1_bufsize < config_params.min_bufsize[VPIF_CHANNEL1_VIDEO]) + ch1_bufsize = + config_params.min_bufsize[VPIF_CHANNEL1_VIDEO]; + + config_params.numbuffers[VPIF_CHANNEL0_VIDEO] = ch0_numbuffers; + config_params.numbuffers[VPIF_CHANNEL1_VIDEO] = ch1_numbuffers; + if (ch0_numbuffers) { + config_params.channel_bufsize[VPIF_CHANNEL0_VIDEO] + = ch0_bufsize; + } + if (ch1_numbuffers) { + config_params.channel_bufsize[VPIF_CHANNEL1_VIDEO] + = ch1_bufsize; + } + + /* Allocate memory for six channel objects */ + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + vpif_obj.dev[i] = + kzalloc(sizeof(*vpif_obj.dev[i]), GFP_KERNEL); + /* If memory allocation fails, return error */ + if (!vpif_obj.dev[i]) { + free_channel_objects_index = i; + err = -ENOMEM; + goto vpif_init_free_channel_objects; + } + } + return 0; + +vpif_init_free_channel_objects: + for (j = 0; j < free_channel_objects_index; j++) + kfree(vpif_obj.dev[j]); + return err; +} + +/** + * vpif_probe : This function probes the vpif capture driver + * @pdev: platform device pointer + * + * This creates device entries by register itself to the V4L2 driver and + * initializes fields of each channel objects + */ +static __init int vpif_probe(struct platform_device *pdev) +{ + struct vpif_subdev_info *subdevdata; + struct vpif_capture_config *config; + int i, j, k, m, q, err; + struct i2c_adapter *i2c_adap; + struct channel_obj *ch; + struct common_obj *common; + struct video_device *vfd; + struct resource *res; + int subdev_count; + size_t size; + + vpif_dev = &pdev->dev; + + err = initialize_vpif(); + if (err) { + v4l2_err(vpif_dev->driver, "Error initializing vpif\n"); + return err; + } + + err = v4l2_device_register(vpif_dev, &vpif_obj.v4l2_dev); + if (err) { + v4l2_err(vpif_dev->driver, "Error registering v4l2 device\n"); + return err; + } + + k = 0; + while ((res = platform_get_resource(pdev, IORESOURCE_IRQ, k))) { + for (i = res->start; i <= res->end; i++) { + if (request_irq(i, vpif_channel_isr, IRQF_SHARED, + "VPIF_Capture", + (void *)(&vpif_obj.dev[k]->channel_id))) { + err = -EBUSY; + i--; + goto vpif_int_err; + } + } + k++; + } + + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + /* Allocate memory for video device */ + vfd = video_device_alloc(); + if (NULL == vfd) { + for (j = 0; j < i; j++) { + ch = vpif_obj.dev[j]; + video_device_release(ch->video_dev); + } + err = -ENOMEM; + goto vpif_dev_alloc_err; + } + + /* Initialize field of video device */ + *vfd = vpif_video_template; + vfd->v4l2_dev = &vpif_obj.v4l2_dev; + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), + "VPIF_Capture_DRIVER_V%s", + VPIF_CAPTURE_VERSION); + /* Set video_dev to the video device */ + ch->video_dev = vfd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) { + size = resource_size(res); + /* The resources are divided into two equal memory and when we + * have HD output we can add them together + */ + for (j = 0; j < VPIF_CAPTURE_MAX_DEVICES; j++) { + ch = vpif_obj.dev[j]; + ch->channel_id = j; + /* only enabled if second resource exists */ + config_params.video_limit[ch->channel_id] = 0; + if (size) + config_params.video_limit[ch->channel_id] = + size/2; + } + } + + for (j = 0; j < VPIF_CAPTURE_MAX_DEVICES; j++) { + ch = vpif_obj.dev[j]; + ch->channel_id = j; + common = &(ch->common[VPIF_VIDEO_INDEX]); + spin_lock_init(&common->irqlock); + mutex_init(&common->lock); + ch->video_dev->lock = &common->lock; + /* Initialize prio member of channel object */ + v4l2_prio_init(&ch->prio); + err = video_register_device(ch->video_dev, + VFL_TYPE_GRABBER, (j ? 1 : 0)); + if (err) + goto probe_out; + + video_set_drvdata(ch->video_dev, ch); + + } + + i2c_adap = i2c_get_adapter(1); + config = pdev->dev.platform_data; + + subdev_count = config->subdev_count; + vpif_obj.sd = kzalloc(sizeof(struct v4l2_subdev *) * subdev_count, + GFP_KERNEL); + if (vpif_obj.sd == NULL) { + vpif_err("unable to allocate memory for subdevice pointers\n"); + err = -ENOMEM; + goto probe_out; + } + + for (i = 0; i < subdev_count; i++) { + subdevdata = &config->subdev_info[i]; + vpif_obj.sd[i] = + v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, + i2c_adap, + &subdevdata->board_info, + NULL); + + if (!vpif_obj.sd[i]) { + vpif_err("Error registering v4l2 subdevice\n"); + goto probe_subdev_out; + } + v4l2_info(&vpif_obj.v4l2_dev, "registered sub device %s\n", + subdevdata->name); + + if (vpif_obj.sd[i]) + vpif_obj.sd[i]->grp_id = 1 << i; + } + + v4l2_info(&vpif_obj.v4l2_dev, "VPIF capture driver initialized\n"); + return 0; + +probe_subdev_out: + /* free sub devices memory */ + kfree(vpif_obj.sd); + + j = VPIF_CAPTURE_MAX_DEVICES; +probe_out: + for (k = 0; k < j; k++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[k]; + /* Unregister video device */ + video_unregister_device(ch->video_dev); + } + +vpif_dev_alloc_err: + k = VPIF_CAPTURE_MAX_DEVICES-1; + res = platform_get_resource(pdev, IORESOURCE_IRQ, k); + i = res->end; + +vpif_int_err: + for (q = k; q >= 0; q--) { + for (m = i; m >= (int)res->start; m--) + free_irq(m, (void *)(&vpif_obj.dev[q]->channel_id)); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, q-1); + if (res) + i = res->end; + } + v4l2_device_unregister(&vpif_obj.v4l2_dev); + return err; +} + +/** + * vpif_remove() - driver remove handler + * @device: ptr to platform device structure + * + * The vidoe device is unregistered + */ +static int vpif_remove(struct platform_device *device) +{ + int i; + struct channel_obj *ch; + + v4l2_device_unregister(&vpif_obj.v4l2_dev); + + /* un-register device */ + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + /* Unregister video device */ + video_unregister_device(ch->video_dev); + } + return 0; +} + +#ifdef CONFIG_PM +/** + * vpif_suspend: vpif device suspend + */ +static int vpif_suspend(struct device *dev) +{ + + struct common_obj *common; + struct channel_obj *ch; + int i; + + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + common = &ch->common[VPIF_VIDEO_INDEX]; + mutex_lock(&common->lock); + if (ch->usrs && common->io_usrs) { + /* Disable channel */ + if (ch->channel_id == VPIF_CHANNEL0_VIDEO) { + enable_channel0(0); + channel0_intr_enable(0); + } + if (ch->channel_id == VPIF_CHANNEL1_VIDEO || + common->started == 2) { + enable_channel1(0); + channel1_intr_enable(0); + } + } + mutex_unlock(&common->lock); + } + + return 0; +} + +/* + * vpif_resume: vpif device suspend + */ +static int vpif_resume(struct device *dev) +{ + struct common_obj *common; + struct channel_obj *ch; + int i; + + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + common = &ch->common[VPIF_VIDEO_INDEX]; + mutex_lock(&common->lock); + if (ch->usrs && common->io_usrs) { + /* Disable channel */ + if (ch->channel_id == VPIF_CHANNEL0_VIDEO) { + enable_channel0(1); + channel0_intr_enable(1); + } + if (ch->channel_id == VPIF_CHANNEL1_VIDEO || + common->started == 2) { + enable_channel1(1); + channel1_intr_enable(1); + } + } + mutex_unlock(&common->lock); + } + + return 0; +} + +static const struct dev_pm_ops vpif_dev_pm_ops = { + .suspend = vpif_suspend, + .resume = vpif_resume, +}; + +#define vpif_pm_ops (&vpif_dev_pm_ops) +#else +#define vpif_pm_ops NULL +#endif + +static __refdata struct platform_driver vpif_driver = { + .driver = { + .name = "vpif_capture", + .owner = THIS_MODULE, + .pm = vpif_pm_ops, + }, + .probe = vpif_probe, + .remove = vpif_remove, +}; + +/** + * vpif_init: initialize the vpif driver + * + * This function registers device and driver to the kernel, requests irq + * handler and allocates memory + * for channel objects + */ +static __init int vpif_init(void) +{ + return platform_driver_register(&vpif_driver); +} + +/** + * vpif_cleanup : This function clean up the vpif capture resources + * + * This will un-registers device and driver to the kernel, frees + * requested irq handler and de-allocates memory allocated for channel + * objects. + */ +static void vpif_cleanup(void) +{ + struct platform_device *pdev; + struct resource *res; + int irq_num; + int i = 0; + + pdev = container_of(vpif_dev, struct platform_device, dev); + while ((res = platform_get_resource(pdev, IORESOURCE_IRQ, i))) { + for (irq_num = res->start; irq_num <= res->end; irq_num++) + free_irq(irq_num, + (void *)(&vpif_obj.dev[i]->channel_id)); + i++; + } + + platform_driver_unregister(&vpif_driver); + + kfree(vpif_obj.sd); + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) + kfree(vpif_obj.dev[i]); +} + +/* Function for module initialization and cleanup */ +module_init(vpif_init); +module_exit(vpif_cleanup); diff --git a/drivers/media/platform/davinci/vpif_capture.h b/drivers/media/platform/davinci/vpif_capture.h new file mode 100644 index 000000000000..d24efc17e4c8 --- /dev/null +++ b/drivers/media/platform/davinci/vpif_capture.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2009 Texas Instruments Inc + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef VPIF_CAPTURE_H +#define VPIF_CAPTURE_H + +#ifdef __KERNEL__ + +/* Header files */ +#include +#include +#include +#include +#include + +#include "vpif.h" + +/* Macros */ +#define VPIF_CAPTURE_VERSION "0.0.2" + +#define VPIF_VALID_FIELD(field) (((V4L2_FIELD_ANY == field) || \ + (V4L2_FIELD_NONE == field)) || \ + (((V4L2_FIELD_INTERLACED == field) || \ + (V4L2_FIELD_SEQ_TB == field)) || \ + (V4L2_FIELD_SEQ_BT == field))) + +#define VPIF_CAPTURE_MAX_DEVICES 2 +#define VPIF_VIDEO_INDEX 0 +#define VPIF_NUMBER_OF_OBJECTS 1 + +/* Enumerated data type to give id to each device per channel */ +enum vpif_channel_id { + VPIF_CHANNEL0_VIDEO = 0, + VPIF_CHANNEL1_VIDEO, +}; + +struct video_obj { + enum v4l2_field buf_field; + /* Currently selected or default standard */ + v4l2_std_id stdid; + struct v4l2_dv_timings dv_timings; + /* This is to track the last input that is passed to application */ + u32 input_idx; +}; + +struct vpif_cap_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +struct common_obj { + /* Pointer pointing to current v4l2_buffer */ + struct vpif_cap_buffer *cur_frm; + /* Pointer pointing to current v4l2_buffer */ + struct vpif_cap_buffer *next_frm; + /* + * This field keeps track of type of buffer exchange mechanism + * user has selected + */ + enum v4l2_memory memory; + /* Used to store pixel format */ + struct v4l2_format fmt; + /* Buffer queue used in video-buf */ + struct vb2_queue buffer_queue; + /* allocator-specific contexts for each plane */ + struct vb2_alloc_ctx *alloc_ctx; + /* Queue of filled frames */ + struct list_head dma_queue; + /* Used in video-buf */ + spinlock_t irqlock; + /* lock used to access this structure */ + struct mutex lock; + /* number of users performing IO */ + u32 io_usrs; + /* Indicates whether streaming started */ + u8 started; + /* Function pointer to set the addresses */ + void (*set_addr) (unsigned long, unsigned long, unsigned long, + unsigned long); + /* offset where Y top starts from the starting of the buffer */ + u32 ytop_off; + /* offset where Y bottom starts from the starting of the buffer */ + u32 ybtm_off; + /* offset where C top starts from the starting of the buffer */ + u32 ctop_off; + /* offset where C bottom starts from the starting of the buffer */ + u32 cbtm_off; + /* Indicates width of the image data */ + u32 width; + /* Indicates height of the image data */ + u32 height; +}; + +struct channel_obj { + /* Identifies video device for this channel */ + struct video_device *video_dev; + /* Used to keep track of state of the priority */ + struct v4l2_prio_state prio; + /* number of open instances of the channel */ + int usrs; + /* Indicates id of the field which is being displayed */ + u32 field_id; + /* flag to indicate whether decoder is initialized */ + u8 initialized; + /* Identifies channel */ + enum vpif_channel_id channel_id; + /* index into sd table */ + int curr_sd_index; + /* ptr to current sub device information */ + struct vpif_subdev_info *curr_subdev_info; + /* vpif configuration params */ + struct vpif_params vpifparams; + /* common object array */ + struct common_obj common[VPIF_NUMBER_OF_OBJECTS]; + /* video object */ + struct video_obj video; +}; + +/* File handle structure */ +struct vpif_fh { + /* pointer to channel object for opened device */ + struct channel_obj *channel; + /* Indicates whether this file handle is doing IO */ + u8 io_allowed[VPIF_NUMBER_OF_OBJECTS]; + /* Used to keep track priority of this instance */ + enum v4l2_priority prio; + /* Used to indicate channel is initialize or not */ + u8 initialized; +}; + +struct vpif_device { + struct v4l2_device v4l2_dev; + struct channel_obj *dev[VPIF_CAPTURE_NUM_CHANNELS]; + struct v4l2_subdev **sd; +}; + +struct vpif_config_params { + u8 min_numbuffers; + u8 numbuffers[VPIF_CAPTURE_NUM_CHANNELS]; + s8 device_type; + u32 min_bufsize[VPIF_CAPTURE_NUM_CHANNELS]; + u32 channel_bufsize[VPIF_CAPTURE_NUM_CHANNELS]; + u8 default_device[VPIF_CAPTURE_NUM_CHANNELS]; + u32 video_limit[VPIF_CAPTURE_NUM_CHANNELS]; + u8 max_device_type; +}; +/* Struct which keeps track of the line numbers for the sliced vbi service */ +struct vpif_service_line { + u16 service_id; + u16 service_line[2]; +}; +#endif /* End of __KERNEL__ */ +#endif /* VPIF_CAPTURE_H */ diff --git a/drivers/media/platform/davinci/vpif_display.c b/drivers/media/platform/davinci/vpif_display.c new file mode 100644 index 000000000000..a5b88689abad --- /dev/null +++ b/drivers/media/platform/davinci/vpif_display.c @@ -0,0 +1,1933 @@ +/* + * vpif-display - VPIF display driver + * Display driver for TI DaVinci VPIF + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "vpif_display.h" +#include "vpif.h" + +MODULE_DESCRIPTION("TI DaVinci VPIF Display driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VPIF_DISPLAY_VERSION); + +#define VPIF_V4L2_STD (V4L2_STD_525_60 | V4L2_STD_625_50) + +#define vpif_err(fmt, arg...) v4l2_err(&vpif_obj.v4l2_dev, fmt, ## arg) +#define vpif_dbg(level, debug, fmt, arg...) \ + v4l2_dbg(level, debug, &vpif_obj.v4l2_dev, fmt, ## arg) + +static int debug = 1; +static u32 ch2_numbuffers = 3; +static u32 ch3_numbuffers = 3; +static u32 ch2_bufsize = 1920 * 1080 * 2; +static u32 ch3_bufsize = 720 * 576 * 2; + +module_param(debug, int, 0644); +module_param(ch2_numbuffers, uint, S_IRUGO); +module_param(ch3_numbuffers, uint, S_IRUGO); +module_param(ch2_bufsize, uint, S_IRUGO); +module_param(ch3_bufsize, uint, S_IRUGO); + +MODULE_PARM_DESC(debug, "Debug level 0-1"); +MODULE_PARM_DESC(ch2_numbuffers, "Channel2 buffer count (default:3)"); +MODULE_PARM_DESC(ch3_numbuffers, "Channel3 buffer count (default:3)"); +MODULE_PARM_DESC(ch2_bufsize, "Channel2 buffer size (default:1920 x 1080 x 2)"); +MODULE_PARM_DESC(ch3_bufsize, "Channel3 buffer size (default:720 x 576 x 2)"); + +static struct vpif_config_params config_params = { + .min_numbuffers = 3, + .numbuffers[0] = 3, + .numbuffers[1] = 3, + .min_bufsize[0] = 720 * 480 * 2, + .min_bufsize[1] = 720 * 480 * 2, + .channel_bufsize[0] = 1920 * 1080 * 2, + .channel_bufsize[1] = 720 * 576 * 2, +}; + +static struct vpif_device vpif_obj = { {NULL} }; +static struct device *vpif_dev; +static void vpif_calculate_offsets(struct channel_obj *ch); +static void vpif_config_addr(struct channel_obj *ch, int muxmode); + +/* + * buffer_prepare: This is the callback function called from vb2_qbuf() + * function the buffer is prepared and user space virtual address is converted + * into physical address + */ +static int vpif_buffer_prepare(struct vb2_buffer *vb) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_queue *q = vb->vb2_queue; + struct common_obj *common; + unsigned long addr; + + common = &fh->channel->common[VPIF_VIDEO_INDEX]; + if (vb->state != VB2_BUF_STATE_ACTIVE && + vb->state != VB2_BUF_STATE_PREPARED) { + vb2_set_plane_payload(vb, 0, common->fmt.fmt.pix.sizeimage); + if (vb2_plane_vaddr(vb, 0) && + vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) + goto buf_align_exit; + + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + if (q->streaming && + (V4L2_BUF_TYPE_SLICED_VBI_OUTPUT != q->type)) { + if (!ISALIGNED(addr + common->ytop_off) || + !ISALIGNED(addr + common->ybtm_off) || + !ISALIGNED(addr + common->ctop_off) || + !ISALIGNED(addr + common->cbtm_off)) + goto buf_align_exit; + } + } + return 0; + +buf_align_exit: + vpif_err("buffer offset not aligned to 8 bytes\n"); + return -EINVAL; +} + +/* + * vpif_buffer_queue_setup: This function allocates memory for the buffers + */ +static int vpif_buffer_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + unsigned long size; + + if (V4L2_MEMORY_MMAP == common->memory) { + size = config_params.channel_bufsize[ch->channel_id]; + /* + * Checking if the buffer size exceeds the available buffer + * ycmux_mode = 0 means 1 channel mode HD and + * ycmux_mode = 1 means 2 channels mode SD + */ + if (ch->vpifparams.std_info.ycmux_mode == 0) { + if (config_params.video_limit[ch->channel_id]) + while (size * *nbuffers > + (config_params.video_limit[0] + + config_params.video_limit[1])) + (*nbuffers)--; + } else { + if (config_params.video_limit[ch->channel_id]) + while (size * *nbuffers > + config_params.video_limit[ch->channel_id]) + (*nbuffers)--; + } + } else { + size = common->fmt.fmt.pix.sizeimage; + } + + if (*nbuffers < config_params.min_numbuffers) + *nbuffers = config_params.min_numbuffers; + + *nplanes = 1; + sizes[0] = size; + alloc_ctxs[0] = common->alloc_ctx; + return 0; +} + +/* + * vpif_buffer_queue: This function adds the buffer to DMA queue + */ +static void vpif_buffer_queue(struct vb2_buffer *vb) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vb->vb2_queue); + struct vpif_disp_buffer *buf = container_of(vb, + struct vpif_disp_buffer, vb); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + /* add the buffer to the DMA queue */ + list_add_tail(&buf->list, &common->dma_queue); +} + +/* + * vpif_buf_cleanup: This function is called from the videobuf2 layer to + * free memory allocated to the buffers + */ +static void vpif_buf_cleanup(struct vb2_buffer *vb) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vb->vb2_queue); + struct vpif_disp_buffer *buf = container_of(vb, + struct vpif_disp_buffer, vb); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + unsigned long flags; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + spin_lock_irqsave(&common->irqlock, flags); + if (vb->state == VB2_BUF_STATE_ACTIVE) + list_del_init(&buf->list); + spin_unlock_irqrestore(&common->irqlock, flags); +} + +static void vpif_wait_prepare(struct vb2_queue *vq) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + + common = &ch->common[VPIF_VIDEO_INDEX]; + mutex_unlock(&common->lock); +} + +static void vpif_wait_finish(struct vb2_queue *vq) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + + common = &ch->common[VPIF_VIDEO_INDEX]; + mutex_lock(&common->lock); +} + +static int vpif_buffer_init(struct vb2_buffer *vb) +{ + struct vpif_disp_buffer *buf = container_of(vb, + struct vpif_disp_buffer, vb); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static u8 channel_first_int[VPIF_NUMOBJECTS][2] = { {1, 1} }; + +static int vpif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vpif_display_config *vpif_config_data = + vpif_dev->platform_data; + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_params *vpif = &ch->vpifparams; + unsigned long addr = 0; + int ret; + + /* If buffer queue is empty, return error */ + if (list_empty(&common->dma_queue)) { + vpif_err("buffer queue is empty\n"); + return -EIO; + } + + /* Get the next frame from the buffer queue */ + common->next_frm = common->cur_frm = + list_entry(common->dma_queue.next, + struct vpif_disp_buffer, list); + + list_del(&common->cur_frm->list); + /* Mark state of the current frame to active */ + common->cur_frm->vb.state = VB2_BUF_STATE_ACTIVE; + + /* Initialize field_id and started member */ + ch->field_id = 0; + common->started = 1; + addr = vb2_dma_contig_plane_dma_addr(&common->cur_frm->vb, 0); + /* Calculate the offset for Y and C data in the buffer */ + vpif_calculate_offsets(ch); + + if ((ch->vpifparams.std_info.frm_fmt && + ((common->fmt.fmt.pix.field != V4L2_FIELD_NONE) + && (common->fmt.fmt.pix.field != V4L2_FIELD_ANY))) + || (!ch->vpifparams.std_info.frm_fmt + && (common->fmt.fmt.pix.field == V4L2_FIELD_NONE))) { + vpif_err("conflict in field format and std format\n"); + return -EINVAL; + } + + /* clock settings */ + ret = + vpif_config_data->set_clock(ch->vpifparams.std_info.ycmux_mode, + ch->vpifparams.std_info.hd_sd); + if (ret < 0) { + vpif_err("can't set clock\n"); + return ret; + } + + /* set the parameters and addresses */ + ret = vpif_set_video_params(vpif, ch->channel_id + 2); + if (ret < 0) + return ret; + + common->started = ret; + vpif_config_addr(ch, ret); + common->set_addr((addr + common->ytop_off), + (addr + common->ybtm_off), + (addr + common->ctop_off), + (addr + common->cbtm_off)); + + /* Set interrupt for both the fields in VPIF + Register enable channel in VPIF register */ + channel_first_int[VPIF_VIDEO_INDEX][ch->channel_id] = 1; + if (VPIF_CHANNEL2_VIDEO == ch->channel_id) { + channel2_intr_assert(); + channel2_intr_enable(1); + enable_channel2(1); + if (vpif_config_data->ch2_clip_en) + channel2_clipping_enable(1); + } + + if ((VPIF_CHANNEL3_VIDEO == ch->channel_id) + || (common->started == 2)) { + channel3_intr_assert(); + channel3_intr_enable(1); + enable_channel3(1); + if (vpif_config_data->ch3_clip_en) + channel3_clipping_enable(1); + } + + return 0; +} + +/* abort streaming and wait for last buffer */ +static int vpif_stop_streaming(struct vb2_queue *vq) +{ + struct vpif_fh *fh = vb2_get_drv_priv(vq); + struct channel_obj *ch = fh->channel; + struct common_obj *common; + + if (!vb2_is_streaming(vq)) + return 0; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + /* release all active buffers */ + while (!list_empty(&common->dma_queue)) { + common->next_frm = list_entry(common->dma_queue.next, + struct vpif_disp_buffer, list); + list_del(&common->next_frm->list); + vb2_buffer_done(&common->next_frm->vb, VB2_BUF_STATE_ERROR); + } + + return 0; +} + +static struct vb2_ops video_qops = { + .queue_setup = vpif_buffer_queue_setup, + .wait_prepare = vpif_wait_prepare, + .wait_finish = vpif_wait_finish, + .buf_init = vpif_buffer_init, + .buf_prepare = vpif_buffer_prepare, + .start_streaming = vpif_start_streaming, + .stop_streaming = vpif_stop_streaming, + .buf_cleanup = vpif_buf_cleanup, + .buf_queue = vpif_buffer_queue, +}; + +static void process_progressive_mode(struct common_obj *common) +{ + unsigned long addr = 0; + + /* Get the next buffer from buffer queue */ + common->next_frm = list_entry(common->dma_queue.next, + struct vpif_disp_buffer, list); + /* Remove that buffer from the buffer queue */ + list_del(&common->next_frm->list); + /* Mark status of the buffer as active */ + common->next_frm->vb.state = VB2_BUF_STATE_ACTIVE; + + /* Set top and bottom field addrs in VPIF registers */ + addr = vb2_dma_contig_plane_dma_addr(&common->next_frm->vb, 0); + common->set_addr(addr + common->ytop_off, + addr + common->ybtm_off, + addr + common->ctop_off, + addr + common->cbtm_off); +} + +static void process_interlaced_mode(int fid, struct common_obj *common) +{ + /* device field id and local field id are in sync */ + /* If this is even field */ + if (0 == fid) { + if (common->cur_frm == common->next_frm) + return; + + /* one frame is displayed If next frame is + * available, release cur_frm and move on */ + /* Copy frame display time */ + do_gettimeofday(&common->cur_frm->vb.v4l2_buf.timestamp); + /* Change status of the cur_frm */ + vb2_buffer_done(&common->cur_frm->vb, + VB2_BUF_STATE_DONE); + /* Make cur_frm pointing to next_frm */ + common->cur_frm = common->next_frm; + + } else if (1 == fid) { /* odd field */ + if (list_empty(&common->dma_queue) + || (common->cur_frm != common->next_frm)) { + return; + } + /* one field is displayed configure the next + * frame if it is available else hold on current + * frame */ + /* Get next from the buffer queue */ + process_progressive_mode(common); + + } +} + +/* + * vpif_channel_isr: It changes status of the displayed buffer, takes next + * buffer from the queue and sets its address in VPIF registers + */ +static irqreturn_t vpif_channel_isr(int irq, void *dev_id) +{ + struct vpif_device *dev = &vpif_obj; + struct channel_obj *ch; + struct common_obj *common; + enum v4l2_field field; + int fid = -1, i; + int channel_id = 0; + + channel_id = *(int *)(dev_id); + if (!vpif_intr_status(channel_id + 2)) + return IRQ_NONE; + + ch = dev->dev[channel_id]; + field = ch->common[VPIF_VIDEO_INDEX].fmt.fmt.pix.field; + for (i = 0; i < VPIF_NUMOBJECTS; i++) { + common = &ch->common[i]; + /* If streaming is started in this channel */ + if (0 == common->started) + continue; + + if (1 == ch->vpifparams.std_info.frm_fmt) { + if (list_empty(&common->dma_queue)) + continue; + + /* Progressive mode */ + if (!channel_first_int[i][channel_id]) { + /* Mark status of the cur_frm to + * done and unlock semaphore on it */ + do_gettimeofday(&common->cur_frm->vb. + v4l2_buf.timestamp); + vb2_buffer_done(&common->cur_frm->vb, + VB2_BUF_STATE_DONE); + /* Make cur_frm pointing to next_frm */ + common->cur_frm = common->next_frm; + } + + channel_first_int[i][channel_id] = 0; + process_progressive_mode(common); + } else { + /* Interlaced mode */ + /* If it is first interrupt, ignore it */ + + if (channel_first_int[i][channel_id]) { + channel_first_int[i][channel_id] = 0; + continue; + } + + if (0 == i) { + ch->field_id ^= 1; + /* Get field id from VPIF registers */ + fid = vpif_channel_getfid(ch->channel_id + 2); + /* If fid does not match with stored field id */ + if (fid != ch->field_id) { + /* Make them in sync */ + if (0 == fid) + ch->field_id = fid; + + return IRQ_HANDLED; + } + } + process_interlaced_mode(fid, common); + } + } + + return IRQ_HANDLED; +} + +static int vpif_update_std_info(struct channel_obj *ch) +{ + struct video_obj *vid_ch = &ch->video; + struct vpif_params *vpifparams = &ch->vpifparams; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + const struct vpif_channel_config_params *config; + + int i; + + for (i = 0; i < vpif_ch_params_count; i++) { + config = &ch_params[i]; + if (config->hd_sd == 0) { + vpif_dbg(2, debug, "SD format\n"); + if (config->stdid & vid_ch->stdid) { + memcpy(std_info, config, sizeof(*config)); + break; + } + } + } + + if (i == vpif_ch_params_count) { + vpif_dbg(1, debug, "Format not found\n"); + return -EINVAL; + } + + return 0; +} + +static int vpif_update_resolution(struct channel_obj *ch) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct video_obj *vid_ch = &ch->video; + struct vpif_params *vpifparams = &ch->vpifparams; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + + if (!vid_ch->stdid && !vid_ch->dv_timings.bt.height) + return -EINVAL; + + if (vid_ch->stdid) { + if (vpif_update_std_info(ch)) + return -EINVAL; + } + + common->fmt.fmt.pix.width = std_info->width; + common->fmt.fmt.pix.height = std_info->height; + vpif_dbg(1, debug, "Pixel details: Width = %d,Height = %d\n", + common->fmt.fmt.pix.width, common->fmt.fmt.pix.height); + + /* Set height and width paramateres */ + common->height = std_info->height; + common->width = std_info->width; + + return 0; +} + +/* + * vpif_calculate_offsets: This function calculates buffers offset for Y and C + * in the top and bottom field + */ +static void vpif_calculate_offsets(struct channel_obj *ch) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_params *vpifparams = &ch->vpifparams; + enum v4l2_field field = common->fmt.fmt.pix.field; + struct video_obj *vid_ch = &ch->video; + unsigned int hpitch, vpitch, sizeimage; + + if (V4L2_FIELD_ANY == common->fmt.fmt.pix.field) { + if (ch->vpifparams.std_info.frm_fmt) + vid_ch->buf_field = V4L2_FIELD_NONE; + else + vid_ch->buf_field = V4L2_FIELD_INTERLACED; + } else { + vid_ch->buf_field = common->fmt.fmt.pix.field; + } + + sizeimage = common->fmt.fmt.pix.sizeimage; + + hpitch = common->fmt.fmt.pix.bytesperline; + vpitch = sizeimage / (hpitch * 2); + if ((V4L2_FIELD_NONE == vid_ch->buf_field) || + (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) { + common->ytop_off = 0; + common->ybtm_off = hpitch; + common->ctop_off = sizeimage / 2; + common->cbtm_off = sizeimage / 2 + hpitch; + } else if (V4L2_FIELD_SEQ_TB == vid_ch->buf_field) { + common->ytop_off = 0; + common->ybtm_off = sizeimage / 4; + common->ctop_off = sizeimage / 2; + common->cbtm_off = common->ctop_off + sizeimage / 4; + } else if (V4L2_FIELD_SEQ_BT == vid_ch->buf_field) { + common->ybtm_off = 0; + common->ytop_off = sizeimage / 4; + common->cbtm_off = sizeimage / 2; + common->ctop_off = common->cbtm_off + sizeimage / 4; + } + + if ((V4L2_FIELD_NONE == vid_ch->buf_field) || + (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) { + vpifparams->video_params.storage_mode = 1; + } else { + vpifparams->video_params.storage_mode = 0; + } + + if (ch->vpifparams.std_info.frm_fmt == 1) { + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline; + } else { + if ((field == V4L2_FIELD_ANY) || + (field == V4L2_FIELD_INTERLACED)) + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline * 2; + else + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline; + } + + ch->vpifparams.video_params.stdid = ch->vpifparams.std_info.stdid; +} + +static void vpif_config_format(struct channel_obj *ch) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + common->fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (config_params.numbuffers[ch->channel_id] == 0) + common->memory = V4L2_MEMORY_USERPTR; + else + common->memory = V4L2_MEMORY_MMAP; + + common->fmt.fmt.pix.sizeimage = + config_params.channel_bufsize[ch->channel_id]; + common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P; + common->fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +} + +static int vpif_check_format(struct channel_obj *ch, + struct v4l2_pix_format *pixfmt) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + enum v4l2_field field = pixfmt->field; + u32 sizeimage, hpitch, vpitch; + + if (pixfmt->pixelformat != V4L2_PIX_FMT_YUV422P) + goto invalid_fmt_exit; + + if (!(VPIF_VALID_FIELD(field))) + goto invalid_fmt_exit; + + if (pixfmt->bytesperline <= 0) + goto invalid_pitch_exit; + + sizeimage = pixfmt->sizeimage; + + if (vpif_update_resolution(ch)) + return -EINVAL; + + hpitch = pixfmt->bytesperline; + vpitch = sizeimage / (hpitch * 2); + + /* Check for valid value of pitch */ + if ((hpitch < ch->vpifparams.std_info.width) || + (vpitch < ch->vpifparams.std_info.height)) + goto invalid_pitch_exit; + + /* Check for 8 byte alignment */ + if (!ISALIGNED(hpitch)) { + vpif_err("invalid pitch alignment\n"); + return -EINVAL; + } + pixfmt->width = common->fmt.fmt.pix.width; + pixfmt->height = common->fmt.fmt.pix.height; + + return 0; + +invalid_fmt_exit: + vpif_err("invalid field format\n"); + return -EINVAL; + +invalid_pitch_exit: + vpif_err("invalid pitch\n"); + return -EINVAL; +} + +static void vpif_config_addr(struct channel_obj *ch, int muxmode) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + if (VPIF_CHANNEL3_VIDEO == ch->channel_id) { + common->set_addr = ch3_set_videobuf_addr; + } else { + if (2 == muxmode) + common->set_addr = ch2_set_videobuf_addr_yc_nmux; + else + common->set_addr = ch2_set_videobuf_addr; + } +} + +/* + * vpif_mmap: It is used to map kernel space buffers into user spaces + */ +static int vpif_mmap(struct file *filep, struct vm_area_struct *vma) +{ + struct vpif_fh *fh = filep->private_data; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &(ch->common[VPIF_VIDEO_INDEX]); + int ret; + + vpif_dbg(2, debug, "vpif_mmap\n"); + + if (mutex_lock_interruptible(&common->lock)) + return -ERESTARTSYS; + ret = vb2_mmap(&common->buffer_queue, vma); + mutex_unlock(&common->lock); + return ret; +} + +/* + * vpif_poll: It is used for select/poll system call + */ +static unsigned int vpif_poll(struct file *filep, poll_table *wait) +{ + struct vpif_fh *fh = filep->private_data; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + unsigned int res = 0; + + if (common->started) { + mutex_lock(&common->lock); + res = vb2_poll(&common->buffer_queue, filep, wait); + mutex_unlock(&common->lock); + } + + return res; +} + +/* + * vpif_open: It creates object of file handle structure and stores it in + * private_data member of filepointer + */ +static int vpif_open(struct file *filep) +{ + struct video_device *vdev = video_devdata(filep); + struct channel_obj *ch = video_get_drvdata(vdev); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_fh *fh; + + /* Allocate memory for the file handle object */ + fh = kzalloc(sizeof(struct vpif_fh), GFP_KERNEL); + if (fh == NULL) { + vpif_err("unable to allocate memory for file handle object\n"); + return -ENOMEM; + } + + if (mutex_lock_interruptible(&common->lock)) { + kfree(fh); + return -ERESTARTSYS; + } + /* store pointer to fh in private_data member of filep */ + filep->private_data = fh; + fh->channel = ch; + fh->initialized = 0; + if (!ch->initialized) { + fh->initialized = 1; + ch->initialized = 1; + memset(&ch->vpifparams, 0, sizeof(ch->vpifparams)); + } + + /* Increment channel usrs counter */ + atomic_inc(&ch->usrs); + /* Set io_allowed[VPIF_VIDEO_INDEX] member to false */ + fh->io_allowed[VPIF_VIDEO_INDEX] = 0; + /* Initialize priority of this instance to default priority */ + fh->prio = V4L2_PRIORITY_UNSET; + v4l2_prio_open(&ch->prio, &fh->prio); + mutex_unlock(&common->lock); + + return 0; +} + +/* + * vpif_release: This function deletes buffer queue, frees the buffers and + * the vpif file handle + */ +static int vpif_release(struct file *filep) +{ + struct vpif_fh *fh = filep->private_data; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + mutex_lock(&common->lock); + /* if this instance is doing IO */ + if (fh->io_allowed[VPIF_VIDEO_INDEX]) { + /* Reset io_usrs member of channel object */ + common->io_usrs = 0; + /* Disable channel */ + if (VPIF_CHANNEL2_VIDEO == ch->channel_id) { + enable_channel2(0); + channel2_intr_enable(0); + } + if ((VPIF_CHANNEL3_VIDEO == ch->channel_id) || + (2 == common->started)) { + enable_channel3(0); + channel3_intr_enable(0); + } + common->started = 0; + + /* Free buffers allocated */ + vb2_queue_release(&common->buffer_queue); + vb2_dma_contig_cleanup_ctx(common->alloc_ctx); + + common->numbuffers = + config_params.numbuffers[ch->channel_id]; + } + + /* Decrement channel usrs counter */ + atomic_dec(&ch->usrs); + /* If this file handle has initialize encoder device, reset it */ + if (fh->initialized) + ch->initialized = 0; + + /* Close the priority */ + v4l2_prio_close(&ch->prio, fh->prio); + filep->private_data = NULL; + fh->initialized = 0; + mutex_unlock(&common->lock); + kfree(fh); + + return 0; +} + +/* functions implementing ioctls */ +/** + * vpif_querycap() - QUERYCAP handler + * @file: file ptr + * @priv: file handle + * @cap: ptr to v4l2_capability structure + */ +static int vpif_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpif_display_config *config = vpif_dev->platform_data; + + cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + strlcpy(cap->driver, "vpif display", sizeof(cap->driver)); + strlcpy(cap->bus_info, "Platform", sizeof(cap->bus_info)); + strlcpy(cap->card, config->card_name, sizeof(cap->card)); + + return 0; +} + +static int vpif_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + if (fmt->index != 0) { + vpif_err("Invalid format index\n"); + return -EINVAL; + } + + /* Fill in the information about format */ + fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + strcpy(fmt->description, "YCbCr4:2:2 YC Planar"); + fmt->pixelformat = V4L2_PIX_FMT_YUV422P; + + return 0; +} + +static int vpif_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + /* Check the validity of the buffer type */ + if (common->fmt.type != fmt->type) + return -EINVAL; + + if (vpif_update_resolution(ch)) + return -EINVAL; + *fmt = common->fmt; + return 0; +} + +static int vpif_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpif_fh *fh = priv; + struct v4l2_pix_format *pixfmt; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + int ret = 0; + + if ((VPIF_CHANNEL2_VIDEO == ch->channel_id) + || (VPIF_CHANNEL3_VIDEO == ch->channel_id)) { + if (!fh->initialized) { + vpif_dbg(1, debug, "Channel Busy\n"); + return -EBUSY; + } + + /* Check for the priority */ + ret = v4l2_prio_check(&ch->prio, fh->prio); + if (0 != ret) + return ret; + fh->initialized = 1; + } + + if (common->started) { + vpif_dbg(1, debug, "Streaming in progress\n"); + return -EBUSY; + } + + pixfmt = &fmt->fmt.pix; + /* Check for valid field format */ + ret = vpif_check_format(ch, pixfmt); + if (ret) + return ret; + + /* store the pix format in the channel object */ + common->fmt.fmt.pix = *pixfmt; + /* store the format in the channel object */ + common->fmt = *fmt; + return 0; +} + +static int vpif_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + int ret = 0; + + ret = vpif_check_format(ch, pixfmt); + if (ret) { + *pixfmt = common->fmt.fmt.pix; + pixfmt->sizeimage = pixfmt->width * pixfmt->height * 2; + } + + return ret; +} + +static int vpif_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbuf) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common; + enum v4l2_field field; + struct vb2_queue *q; + u8 index = 0; + + /* This file handle has not initialized the channel, + It is not allowed to do settings */ + if ((VPIF_CHANNEL2_VIDEO == ch->channel_id) + || (VPIF_CHANNEL3_VIDEO == ch->channel_id)) { + if (!fh->initialized) { + vpif_err("Channel Busy\n"); + return -EBUSY; + } + } + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != reqbuf->type) + return -EINVAL; + + index = VPIF_VIDEO_INDEX; + + common = &ch->common[index]; + + if (common->fmt.type != reqbuf->type || !vpif_dev) + return -EINVAL; + if (0 != common->io_usrs) + return -EBUSY; + + if (reqbuf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + if (common->fmt.fmt.pix.field == V4L2_FIELD_ANY) + field = V4L2_FIELD_INTERLACED; + else + field = common->fmt.fmt.pix.field; + } else { + field = V4L2_VBI_INTERLACED; + } + /* Initialize videobuf2 queue as per the buffer type */ + common->alloc_ctx = vb2_dma_contig_init_ctx(vpif_dev); + if (!common->alloc_ctx) { + vpif_err("Failed to get the context\n"); + return -EINVAL; + } + q = &common->buffer_queue; + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->drv_priv = fh; + q->ops = &video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct vpif_disp_buffer); + + vb2_queue_init(q); + + /* Set io allowed member of file handle to TRUE */ + fh->io_allowed[index] = 1; + /* Increment io usrs member of channel object to 1 */ + common->io_usrs = 1; + /* Store type of memory requested in channel object */ + common->memory = reqbuf->memory; + INIT_LIST_HEAD(&common->dma_queue); + /* Allocate buffers */ + return vb2_reqbufs(&common->buffer_queue, reqbuf); +} + +static int vpif_querybuf(struct file *file, void *priv, + struct v4l2_buffer *tbuf) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + if (common->fmt.type != tbuf->type) + return -EINVAL; + + return vb2_querybuf(&common->buffer_queue, tbuf); +} + +static int vpif_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct vpif_fh *fh = NULL; + struct channel_obj *ch = NULL; + struct common_obj *common = NULL; + + if (!buf || !priv) + return -EINVAL; + + fh = priv; + ch = fh->channel; + if (!ch) + return -EINVAL; + + common = &(ch->common[VPIF_VIDEO_INDEX]); + if (common->fmt.type != buf->type) + return -EINVAL; + + if (!fh->io_allowed[VPIF_VIDEO_INDEX]) { + vpif_err("fh->io_allowed\n"); + return -EACCES; + } + + return vb2_qbuf(&common->buffer_queue, buf); +} + +static int vpif_s_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + int ret = 0; + + if (!(*std_id & VPIF_V4L2_STD)) + return -EINVAL; + + if (common->started) { + vpif_err("streaming in progress\n"); + return -EBUSY; + } + + /* Call encoder subdevice function to set the standard */ + ch->video.stdid = *std_id; + memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); + /* Get the information about the standard */ + if (vpif_update_resolution(ch)) + return -EINVAL; + + if ((ch->vpifparams.std_info.width * + ch->vpifparams.std_info.height * 2) > + config_params.channel_bufsize[ch->channel_id]) { + vpif_err("invalid std for this size\n"); + return -EINVAL; + } + + common->fmt.fmt.pix.bytesperline = common->fmt.fmt.pix.width; + /* Configure the default format information */ + vpif_config_format(ch); + + ret = v4l2_device_call_until_err(&vpif_obj.v4l2_dev, 1, video, + s_std_output, *std_id); + if (ret < 0) { + vpif_err("Failed to set output standard\n"); + return ret; + } + + ret = v4l2_device_call_until_err(&vpif_obj.v4l2_dev, 1, core, + s_std, *std_id); + if (ret < 0) + vpif_err("Failed to set standard for sub devices\n"); + return ret; +} + +static int vpif_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + *std = ch->video.stdid; + return 0; +} + +static int vpif_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + return vb2_dqbuf(&common->buffer_queue, p, + (file->f_flags & O_NONBLOCK)); +} + +static int vpif_streamon(struct file *file, void *priv, + enum v4l2_buf_type buftype) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct channel_obj *oth_ch = vpif_obj.dev[!ch->channel_id]; + int ret = 0; + + if (buftype != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + vpif_err("buffer type not supported\n"); + return -EINVAL; + } + + if (!fh->io_allowed[VPIF_VIDEO_INDEX]) { + vpif_err("fh->io_allowed\n"); + return -EACCES; + } + + /* If Streaming is already started, return error */ + if (common->started) { + vpif_err("channel->started\n"); + return -EBUSY; + } + + if ((ch->channel_id == VPIF_CHANNEL2_VIDEO + && oth_ch->common[VPIF_VIDEO_INDEX].started && + ch->vpifparams.std_info.ycmux_mode == 0) + || ((ch->channel_id == VPIF_CHANNEL3_VIDEO) + && (2 == oth_ch->common[VPIF_VIDEO_INDEX].started))) { + vpif_err("other channel is using\n"); + return -EBUSY; + } + + ret = vpif_check_format(ch, &common->fmt.fmt.pix); + if (ret < 0) + return ret; + + /* Call vb2_streamon to start streaming in videobuf2 */ + ret = vb2_streamon(&common->buffer_queue, buftype); + if (ret < 0) { + vpif_err("vb2_streamon\n"); + return ret; + } + + return ret; +} + +static int vpif_streamoff(struct file *file, void *priv, + enum v4l2_buf_type buftype) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_display_config *vpif_config_data = + vpif_dev->platform_data; + + if (buftype != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + vpif_err("buffer type not supported\n"); + return -EINVAL; + } + + if (!fh->io_allowed[VPIF_VIDEO_INDEX]) { + vpif_err("fh->io_allowed\n"); + return -EACCES; + } + + if (!common->started) { + vpif_err("channel->started\n"); + return -EINVAL; + } + + if (buftype == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + /* disable channel */ + if (VPIF_CHANNEL2_VIDEO == ch->channel_id) { + if (vpif_config_data->ch2_clip_en) + channel2_clipping_enable(0); + enable_channel2(0); + channel2_intr_enable(0); + } + if ((VPIF_CHANNEL3_VIDEO == ch->channel_id) || + (2 == common->started)) { + if (vpif_config_data->ch3_clip_en) + channel3_clipping_enable(0); + enable_channel3(0); + channel3_intr_enable(0); + } + } + + common->started = 0; + return vb2_streamoff(&common->buffer_queue, buftype); +} + +static int vpif_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *crop) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != crop->type) + return -EINVAL; + + crop->bounds.left = crop->bounds.top = 0; + crop->defrect.left = crop->defrect.top = 0; + crop->defrect.height = crop->bounds.height = common->height; + crop->defrect.width = crop->bounds.width = common->width; + + return 0; +} + +static int vpif_enum_output(struct file *file, void *fh, + struct v4l2_output *output) +{ + + struct vpif_display_config *config = vpif_dev->platform_data; + + if (output->index >= config->output_count) { + vpif_dbg(1, debug, "Invalid output index\n"); + return -EINVAL; + } + + strcpy(output->name, config->output[output->index]); + output->type = V4L2_OUTPUT_TYPE_ANALOG; + output->std = VPIF_V4L2_STD; + + return 0; +} + +static int vpif_s_output(struct file *file, void *priv, unsigned int i) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct video_obj *vid_ch = &ch->video; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + int ret = 0; + + if (common->started) { + vpif_err("Streaming in progress\n"); + return -EBUSY; + } + + ret = v4l2_device_call_until_err(&vpif_obj.v4l2_dev, 1, video, + s_routing, 0, i, 0); + + if (ret < 0) + vpif_err("Failed to set output standard\n"); + + vid_ch->output_id = i; + return ret; +} + +static int vpif_g_output(struct file *file, void *priv, unsigned int *i) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct video_obj *vid_ch = &ch->video; + + *i = vid_ch->output_id; + + return 0; +} + +static int vpif_g_priority(struct file *file, void *priv, enum v4l2_priority *p) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + *p = v4l2_prio_max(&ch->prio); + + return 0; +} + +static int vpif_s_priority(struct file *file, void *priv, enum v4l2_priority p) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + + return v4l2_prio_change(&ch->prio, &fh->prio, p); +} + +/** + * vpif_enum_dv_timings() - ENUM_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: input timings + */ +static int +vpif_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct video_obj *vid_ch = &ch->video; + + return v4l2_subdev_call(vpif_obj.sd[vid_ch->output_id], + video, enum_dv_timings, timings); +} + +/** + * vpif_s_dv_timings() - S_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_s_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct vpif_params *vpifparams = &ch->vpifparams; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + struct video_obj *vid_ch = &ch->video; + struct v4l2_bt_timings *bt = &vid_ch->dv_timings.bt; + int ret; + + if (timings->type != V4L2_DV_BT_656_1120) { + vpif_dbg(2, debug, "Timing type not defined\n"); + return -EINVAL; + } + + /* Configure subdevice timings, if any */ + ret = v4l2_subdev_call(vpif_obj.sd[vid_ch->output_id], + video, s_dv_timings, timings); + if (ret == -ENOIOCTLCMD) { + vpif_dbg(2, debug, "Custom DV timings not supported by " + "subdevice\n"); + return -EINVAL; + } + if (ret < 0) { + vpif_dbg(2, debug, "Error setting custom DV timings\n"); + return ret; + } + + if (!(timings->bt.width && timings->bt.height && + (timings->bt.hbackporch || + timings->bt.hfrontporch || + timings->bt.hsync) && + timings->bt.vfrontporch && + (timings->bt.vbackporch || + timings->bt.vsync))) { + vpif_dbg(2, debug, "Timings for width, height, " + "horizontal back porch, horizontal sync, " + "horizontal front porch, vertical back porch, " + "vertical sync and vertical back porch " + "must be defined\n"); + return -EINVAL; + } + + vid_ch->dv_timings = *timings; + + /* Configure video port timings */ + + std_info->eav2sav = bt->hbackporch + bt->hfrontporch + + bt->hsync - 8; + std_info->sav2eav = bt->width; + + std_info->l1 = 1; + std_info->l3 = bt->vsync + bt->vbackporch + 1; + + if (bt->interlaced) { + if (bt->il_vbackporch || bt->il_vfrontporch || bt->il_vsync) { + std_info->vsize = bt->height * 2 + + bt->vfrontporch + bt->vsync + bt->vbackporch + + bt->il_vfrontporch + bt->il_vsync + + bt->il_vbackporch; + std_info->l5 = std_info->vsize/2 - + (bt->vfrontporch - 1); + std_info->l7 = std_info->vsize/2 + 1; + std_info->l9 = std_info->l7 + bt->il_vsync + + bt->il_vbackporch + 1; + std_info->l11 = std_info->vsize - + (bt->il_vfrontporch - 1); + } else { + vpif_dbg(2, debug, "Required timing values for " + "interlaced BT format missing\n"); + return -EINVAL; + } + } else { + std_info->vsize = bt->height + bt->vfrontporch + + bt->vsync + bt->vbackporch; + std_info->l5 = std_info->vsize - (bt->vfrontporch - 1); + } + strncpy(std_info->name, "Custom timings BT656/1120", + VPIF_MAX_NAME); + std_info->width = bt->width; + std_info->height = bt->height; + std_info->frm_fmt = bt->interlaced ? 0 : 1; + std_info->ycmux_mode = 0; + std_info->capture_format = 0; + std_info->vbi_supported = 0; + std_info->hd_sd = 1; + std_info->stdid = 0; + vid_ch->stdid = 0; + + return 0; +} + +/** + * vpif_g_dv_timings() - G_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_g_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct video_obj *vid_ch = &ch->video; + + *timings = vid_ch->dv_timings; + + return 0; +} + +/* + * vpif_g_chip_ident() - Identify the chip + * @file: file ptr + * @priv: file handle + * @chip: chip identity + * + * Returns zero or -EINVAL if read operations fails. + */ +static int vpif_g_chip_ident(struct file *file, void *priv, + struct v4l2_dbg_chip_ident *chip) +{ + chip->ident = V4L2_IDENT_NONE; + chip->revision = 0; + if (chip->match.type != V4L2_CHIP_MATCH_I2C_DRIVER && + chip->match.type != V4L2_CHIP_MATCH_I2C_ADDR) { + vpif_dbg(2, debug, "match_type is invalid.\n"); + return -EINVAL; + } + + return v4l2_device_call_until_err(&vpif_obj.v4l2_dev, 0, core, + g_chip_ident, chip); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +/* + * vpif_dbg_g_register() - Read register + * @file: file ptr + * @priv: file handle + * @reg: register to be read + * + * Debugging only + * Returns zero or -EINVAL if read operations fails. + */ +static int vpif_dbg_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg){ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct video_obj *vid_ch = &ch->video; + + return v4l2_subdev_call(vpif_obj.sd[vid_ch->output_id], core, + g_register, reg); +} + +/* + * vpif_dbg_s_register() - Write to register + * @file: file ptr + * @priv: file handle + * @reg: register to be modified + * + * Debugging only + * Returns zero or -EINVAL if write operations fails. + */ +static int vpif_dbg_s_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg){ + struct vpif_fh *fh = priv; + struct channel_obj *ch = fh->channel; + struct video_obj *vid_ch = &ch->video; + + return v4l2_subdev_call(vpif_obj.sd[vid_ch->output_id], core, + s_register, reg); +} +#endif + +/* + * vpif_log_status() - Status information + * @file: file ptr + * @priv: file handle + * + * Returns zero. + */ +static int vpif_log_status(struct file *filep, void *priv) +{ + /* status for sub devices */ + v4l2_device_call_all(&vpif_obj.v4l2_dev, 0, core, log_status); + + return 0; +} + +/* vpif display ioctl operations */ +static const struct v4l2_ioctl_ops vpif_ioctl_ops = { + .vidioc_querycap = vpif_querycap, + .vidioc_g_priority = vpif_g_priority, + .vidioc_s_priority = vpif_s_priority, + .vidioc_enum_fmt_vid_out = vpif_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vpif_g_fmt_vid_out, + .vidioc_s_fmt_vid_out = vpif_s_fmt_vid_out, + .vidioc_try_fmt_vid_out = vpif_try_fmt_vid_out, + .vidioc_reqbufs = vpif_reqbufs, + .vidioc_querybuf = vpif_querybuf, + .vidioc_qbuf = vpif_qbuf, + .vidioc_dqbuf = vpif_dqbuf, + .vidioc_streamon = vpif_streamon, + .vidioc_streamoff = vpif_streamoff, + .vidioc_s_std = vpif_s_std, + .vidioc_g_std = vpif_g_std, + .vidioc_enum_output = vpif_enum_output, + .vidioc_s_output = vpif_s_output, + .vidioc_g_output = vpif_g_output, + .vidioc_cropcap = vpif_cropcap, + .vidioc_enum_dv_timings = vpif_enum_dv_timings, + .vidioc_s_dv_timings = vpif_s_dv_timings, + .vidioc_g_dv_timings = vpif_g_dv_timings, + .vidioc_g_chip_ident = vpif_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vpif_dbg_g_register, + .vidioc_s_register = vpif_dbg_s_register, +#endif + .vidioc_log_status = vpif_log_status, +}; + +static const struct v4l2_file_operations vpif_fops = { + .owner = THIS_MODULE, + .open = vpif_open, + .release = vpif_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vpif_mmap, + .poll = vpif_poll +}; + +static struct video_device vpif_video_template = { + .name = "vpif", + .fops = &vpif_fops, + .ioctl_ops = &vpif_ioctl_ops, + .tvnorms = VPIF_V4L2_STD, + .current_norm = V4L2_STD_625_50, + +}; + +/*Configure the channels, buffer sizei, request irq */ +static int initialize_vpif(void) +{ + int free_channel_objects_index; + int free_buffer_channel_index; + int free_buffer_index; + int err = 0, i, j; + + /* Default number of buffers should be 3 */ + if ((ch2_numbuffers > 0) && + (ch2_numbuffers < config_params.min_numbuffers)) + ch2_numbuffers = config_params.min_numbuffers; + if ((ch3_numbuffers > 0) && + (ch3_numbuffers < config_params.min_numbuffers)) + ch3_numbuffers = config_params.min_numbuffers; + + /* Set buffer size to min buffers size if invalid buffer size is + * given */ + if (ch2_bufsize < config_params.min_bufsize[VPIF_CHANNEL2_VIDEO]) + ch2_bufsize = + config_params.min_bufsize[VPIF_CHANNEL2_VIDEO]; + if (ch3_bufsize < config_params.min_bufsize[VPIF_CHANNEL3_VIDEO]) + ch3_bufsize = + config_params.min_bufsize[VPIF_CHANNEL3_VIDEO]; + + config_params.numbuffers[VPIF_CHANNEL2_VIDEO] = ch2_numbuffers; + + if (ch2_numbuffers) { + config_params.channel_bufsize[VPIF_CHANNEL2_VIDEO] = + ch2_bufsize; + } + config_params.numbuffers[VPIF_CHANNEL3_VIDEO] = ch3_numbuffers; + + if (ch3_numbuffers) { + config_params.channel_bufsize[VPIF_CHANNEL3_VIDEO] = + ch3_bufsize; + } + + /* Allocate memory for six channel objects */ + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { + vpif_obj.dev[i] = + kzalloc(sizeof(struct channel_obj), GFP_KERNEL); + /* If memory allocation fails, return error */ + if (!vpif_obj.dev[i]) { + free_channel_objects_index = i; + err = -ENOMEM; + goto vpif_init_free_channel_objects; + } + } + + free_channel_objects_index = VPIF_DISPLAY_MAX_DEVICES; + free_buffer_channel_index = VPIF_DISPLAY_NUM_CHANNELS; + free_buffer_index = config_params.numbuffers[i - 1]; + + return 0; + +vpif_init_free_channel_objects: + for (j = 0; j < free_channel_objects_index; j++) + kfree(vpif_obj.dev[j]); + return err; +} + +/* + * vpif_probe: This function creates device entries by register itself to the + * V4L2 driver and initializes fields of each channel objects + */ +static __init int vpif_probe(struct platform_device *pdev) +{ + struct vpif_subdev_info *subdevdata; + struct vpif_display_config *config; + int i, j = 0, k, q, m, err = 0; + struct i2c_adapter *i2c_adap; + struct common_obj *common; + struct channel_obj *ch; + struct video_device *vfd; + struct resource *res; + int subdev_count; + size_t size; + + vpif_dev = &pdev->dev; + err = initialize_vpif(); + + if (err) { + v4l2_err(vpif_dev->driver, "Error initializing vpif\n"); + return err; + } + + err = v4l2_device_register(vpif_dev, &vpif_obj.v4l2_dev); + if (err) { + v4l2_err(vpif_dev->driver, "Error registering v4l2 device\n"); + return err; + } + + k = 0; + while ((res = platform_get_resource(pdev, IORESOURCE_IRQ, k))) { + for (i = res->start; i <= res->end; i++) { + if (request_irq(i, vpif_channel_isr, IRQF_SHARED, + "VPIF_Display", + (void *)(&vpif_obj.dev[k]->channel_id))) { + err = -EBUSY; + goto vpif_int_err; + } + } + k++; + } + + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { + + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + + /* Allocate memory for video device */ + vfd = video_device_alloc(); + if (vfd == NULL) { + for (j = 0; j < i; j++) { + ch = vpif_obj.dev[j]; + video_device_release(ch->video_dev); + } + err = -ENOMEM; + goto vpif_int_err; + } + + /* Initialize field of video device */ + *vfd = vpif_video_template; + vfd->v4l2_dev = &vpif_obj.v4l2_dev; + vfd->release = video_device_release; + vfd->vfl_dir = VFL_DIR_TX; + snprintf(vfd->name, sizeof(vfd->name), + "VPIF_Display_DRIVER_V%s", + VPIF_DISPLAY_VERSION); + + /* Set video_dev to the video device */ + ch->video_dev = vfd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) { + size = resource_size(res); + /* The resources are divided into two equal memory and when + * we have HD output we can add them together + */ + for (j = 0; j < VPIF_DISPLAY_MAX_DEVICES; j++) { + ch = vpif_obj.dev[j]; + ch->channel_id = j; + + /* only enabled if second resource exists */ + config_params.video_limit[ch->channel_id] = 0; + if (size) + config_params.video_limit[ch->channel_id] = + size/2; + } + } + + for (j = 0; j < VPIF_DISPLAY_MAX_DEVICES; j++) { + ch = vpif_obj.dev[j]; + /* Initialize field of the channel objects */ + atomic_set(&ch->usrs, 0); + for (k = 0; k < VPIF_NUMOBJECTS; k++) { + ch->common[k].numbuffers = 0; + common = &ch->common[k]; + common->io_usrs = 0; + common->started = 0; + spin_lock_init(&common->irqlock); + mutex_init(&common->lock); + common->numbuffers = 0; + common->set_addr = NULL; + common->ytop_off = common->ybtm_off = 0; + common->ctop_off = common->cbtm_off = 0; + common->cur_frm = common->next_frm = NULL; + memset(&common->fmt, 0, sizeof(common->fmt)); + common->numbuffers = config_params.numbuffers[k]; + + } + ch->initialized = 0; + ch->channel_id = j; + if (j < 2) + ch->common[VPIF_VIDEO_INDEX].numbuffers = + config_params.numbuffers[ch->channel_id]; + else + ch->common[VPIF_VIDEO_INDEX].numbuffers = 0; + + memset(&ch->vpifparams, 0, sizeof(ch->vpifparams)); + + /* Initialize prio member of channel object */ + v4l2_prio_init(&ch->prio); + ch->common[VPIF_VIDEO_INDEX].fmt.type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + ch->video_dev->lock = &common->lock; + + /* register video device */ + vpif_dbg(1, debug, "channel=%x,channel->video_dev=%x\n", + (int)ch, (int)&ch->video_dev); + + err = video_register_device(ch->video_dev, + VFL_TYPE_GRABBER, (j ? 3 : 2)); + if (err < 0) + goto probe_out; + + video_set_drvdata(ch->video_dev, ch); + } + + i2c_adap = i2c_get_adapter(1); + config = pdev->dev.platform_data; + subdev_count = config->subdev_count; + subdevdata = config->subdevinfo; + vpif_obj.sd = kzalloc(sizeof(struct v4l2_subdev *) * subdev_count, + GFP_KERNEL); + if (vpif_obj.sd == NULL) { + vpif_err("unable to allocate memory for subdevice pointers\n"); + err = -ENOMEM; + goto probe_out; + } + + for (i = 0; i < subdev_count; i++) { + vpif_obj.sd[i] = v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, + i2c_adap, + &subdevdata[i].board_info, + NULL); + if (!vpif_obj.sd[i]) { + vpif_err("Error registering v4l2 subdevice\n"); + goto probe_subdev_out; + } + + if (vpif_obj.sd[i]) + vpif_obj.sd[i]->grp_id = 1 << i; + } + + v4l2_info(&vpif_obj.v4l2_dev, + " VPIF display driver initialized\n"); + return 0; + +probe_subdev_out: + kfree(vpif_obj.sd); +probe_out: + for (k = 0; k < j; k++) { + ch = vpif_obj.dev[k]; + video_unregister_device(ch->video_dev); + video_device_release(ch->video_dev); + ch->video_dev = NULL; + } +vpif_int_err: + v4l2_device_unregister(&vpif_obj.v4l2_dev); + vpif_err("VPIF IRQ request failed\n"); + for (q = k; k >= 0; k--) { + for (m = i; m >= res->start; m--) + free_irq(m, (void *)(&vpif_obj.dev[k]->channel_id)); + res = platform_get_resource(pdev, IORESOURCE_IRQ, k-1); + m = res->end; + } + + return err; +} + +/* + * vpif_remove: It un-register channels from V4L2 driver + */ +static int vpif_remove(struct platform_device *device) +{ + struct channel_obj *ch; + int i; + + v4l2_device_unregister(&vpif_obj.v4l2_dev); + + /* un-register device */ + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + /* Unregister video device */ + video_unregister_device(ch->video_dev); + + ch->video_dev = NULL; + } + + return 0; +} + +#ifdef CONFIG_PM +static int vpif_suspend(struct device *dev) +{ + struct common_obj *common; + struct channel_obj *ch; + int i; + + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + common = &ch->common[VPIF_VIDEO_INDEX]; + mutex_lock(&common->lock); + if (atomic_read(&ch->usrs) && common->io_usrs) { + /* Disable channel */ + if (ch->channel_id == VPIF_CHANNEL2_VIDEO) { + enable_channel2(0); + channel2_intr_enable(0); + } + if (ch->channel_id == VPIF_CHANNEL3_VIDEO || + common->started == 2) { + enable_channel3(0); + channel3_intr_enable(0); + } + } + mutex_unlock(&common->lock); + } + + return 0; +} + +static int vpif_resume(struct device *dev) +{ + + struct common_obj *common; + struct channel_obj *ch; + int i; + + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + common = &ch->common[VPIF_VIDEO_INDEX]; + mutex_lock(&common->lock); + if (atomic_read(&ch->usrs) && common->io_usrs) { + /* Enable channel */ + if (ch->channel_id == VPIF_CHANNEL2_VIDEO) { + enable_channel2(1); + channel2_intr_enable(1); + } + if (ch->channel_id == VPIF_CHANNEL3_VIDEO || + common->started == 2) { + enable_channel3(1); + channel3_intr_enable(1); + } + } + mutex_unlock(&common->lock); + } + + return 0; +} + +static const struct dev_pm_ops vpif_pm = { + .suspend = vpif_suspend, + .resume = vpif_resume, +}; + +#define vpif_pm_ops (&vpif_pm) +#else +#define vpif_pm_ops NULL +#endif + +static __refdata struct platform_driver vpif_driver = { + .driver = { + .name = "vpif_display", + .owner = THIS_MODULE, + .pm = vpif_pm_ops, + }, + .probe = vpif_probe, + .remove = vpif_remove, +}; + +static __init int vpif_init(void) +{ + return platform_driver_register(&vpif_driver); +} + +/* + * vpif_cleanup: This function un-registers device and driver to the kernel, + * frees requested irq handler and de-allocates memory allocated for channel + * objects. + */ +static void vpif_cleanup(void) +{ + struct platform_device *pdev; + struct resource *res; + int irq_num; + int i = 0; + + pdev = container_of(vpif_dev, struct platform_device, dev); + + while ((res = platform_get_resource(pdev, IORESOURCE_IRQ, i))) { + for (irq_num = res->start; irq_num <= res->end; irq_num++) + free_irq(irq_num, + (void *)(&vpif_obj.dev[i]->channel_id)); + i++; + } + + platform_driver_unregister(&vpif_driver); + kfree(vpif_obj.sd); + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) + kfree(vpif_obj.dev[i]); +} + +module_init(vpif_init); +module_exit(vpif_cleanup); diff --git a/drivers/media/platform/davinci/vpif_display.h b/drivers/media/platform/davinci/vpif_display.h new file mode 100644 index 000000000000..f628ebcf3674 --- /dev/null +++ b/drivers/media/platform/davinci/vpif_display.h @@ -0,0 +1,179 @@ +/* + * VPIF display header file + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DAVINCIHD_DISPLAY_H +#define DAVINCIHD_DISPLAY_H + +/* Header files */ +#include +#include +#include +#include +#include + +#include "vpif.h" + +/* Macros */ +#define VPIF_DISPLAY_VERSION "0.0.2" + +#define VPIF_VALID_FIELD(field) \ + (((V4L2_FIELD_ANY == field) || (V4L2_FIELD_NONE == field)) || \ + (((V4L2_FIELD_INTERLACED == field) || (V4L2_FIELD_SEQ_TB == field)) || \ + (V4L2_FIELD_SEQ_BT == field))) + +#define VPIF_DISPLAY_MAX_DEVICES (2) +#define VPIF_SLICED_BUF_SIZE (256) +#define VPIF_SLICED_MAX_SERVICES (3) +#define VPIF_VIDEO_INDEX (0) +#define VPIF_VBI_INDEX (1) +#define VPIF_HBI_INDEX (2) + +/* Setting it to 1 as HBI/VBI support yet to be added , else 3*/ +#define VPIF_NUMOBJECTS (1) + +/* Macros */ +#define ISALIGNED(a) (0 == ((a) & 7)) + +/* enumerated data types */ +/* Enumerated data type to give id to each device per channel */ +enum vpif_channel_id { + VPIF_CHANNEL2_VIDEO = 0, /* Channel2 Video */ + VPIF_CHANNEL3_VIDEO, /* Channel3 Video */ +}; + +/* structures */ + +struct video_obj { + enum v4l2_field buf_field; + u32 latest_only; /* indicate whether to return + * most recent displayed frame only */ + v4l2_std_id stdid; /* Currently selected or default + * standard */ + struct v4l2_dv_timings dv_timings; + u32 output_id; /* Current output id */ +}; + +struct vbi_obj { + int num_services; + struct vpif_vbi_params vbiparams; /* vpif parameters for the raw + * vbi data */ +}; + +struct vpif_disp_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +struct common_obj { + /* Buffer specific parameters */ + u8 *fbuffers[VIDEO_MAX_FRAME]; /* List of buffer pointers for + * storing frames */ + u32 numbuffers; /* number of buffers */ + struct vpif_disp_buffer *cur_frm; /* Pointer pointing to current + * vb2_buffer */ + struct vpif_disp_buffer *next_frm; /* Pointer pointing to next + * vb2_buffer */ + enum v4l2_memory memory; /* This field keeps track of + * type of buffer exchange + * method user has selected */ + struct v4l2_format fmt; /* Used to store the format */ + struct vb2_queue buffer_queue; /* Buffer queue used in + * video-buf */ + /* allocator-specific contexts for each plane */ + struct vb2_alloc_ctx *alloc_ctx; + + struct list_head dma_queue; /* Queue of filled frames */ + spinlock_t irqlock; /* Used in video-buf */ + + /* channel specific parameters */ + struct mutex lock; /* lock used to access this + * structure */ + u32 io_usrs; /* number of users performing + * IO */ + u8 started; /* Indicates whether streaming + * started */ + u32 ytop_off; /* offset of Y top from the + * starting of the buffer */ + u32 ybtm_off; /* offset of Y bottom from the + * starting of the buffer */ + u32 ctop_off; /* offset of C top from the + * starting of the buffer */ + u32 cbtm_off; /* offset of C bottom from the + * starting of the buffer */ + /* Function pointer to set the addresses */ + void (*set_addr) (unsigned long, unsigned long, + unsigned long, unsigned long); + u32 height; + u32 width; +}; + +struct channel_obj { + /* V4l2 specific parameters */ + struct video_device *video_dev; /* Identifies video device for + * this channel */ + struct v4l2_prio_state prio; /* Used to keep track of state of + * the priority */ + atomic_t usrs; /* number of open instances of + * the channel */ + u32 field_id; /* Indicates id of the field + * which is being displayed */ + u8 initialized; /* flag to indicate whether + * encoder is initialized */ + + enum vpif_channel_id channel_id;/* Identifies channel */ + struct vpif_params vpifparams; + struct common_obj common[VPIF_NUMOBJECTS]; + struct video_obj video; + struct vbi_obj vbi; +}; + +/* File handle structure */ +struct vpif_fh { + struct channel_obj *channel; /* pointer to channel object for + * opened device */ + u8 io_allowed[VPIF_NUMOBJECTS]; /* Indicates whether this file handle + * is doing IO */ + enum v4l2_priority prio; /* Used to keep track priority of + * this instance */ + u8 initialized; /* Used to keep track of whether this + * file handle has initialized + * channel or not */ +}; + +/* vpif device structure */ +struct vpif_device { + struct v4l2_device v4l2_dev; + struct channel_obj *dev[VPIF_DISPLAY_NUM_CHANNELS]; + struct v4l2_subdev **sd; + +}; + +struct vpif_config_params { + u32 min_bufsize[VPIF_DISPLAY_NUM_CHANNELS]; + u32 channel_bufsize[VPIF_DISPLAY_NUM_CHANNELS]; + u8 numbuffers[VPIF_DISPLAY_NUM_CHANNELS]; + u32 video_limit[VPIF_DISPLAY_NUM_CHANNELS]; + u8 min_numbuffers; +}; + +/* Struct which keeps track of the line numbers for the sliced vbi service */ +struct vpif_service_line { + u16 service_id; + u16 service_line[2]; + u16 enc_service_id; + u8 bytestowrite; +}; + +#endif /* DAVINCIHD_DISPLAY_H */ diff --git a/drivers/media/platform/davinci/vpss.c b/drivers/media/platform/davinci/vpss.c new file mode 100644 index 000000000000..146e4b01ac17 --- /dev/null +++ b/drivers/media/platform/davinci/vpss.c @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2009 Texas Instruments. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * common vpss system module platform driver for all video drivers. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("VPSS Driver"); +MODULE_AUTHOR("Texas Instruments"); + +/* DM644x defines */ +#define DM644X_SBL_PCR_VPSS (4) + +#define DM355_VPSSBL_INTSEL 0x10 +#define DM355_VPSSBL_EVTSEL 0x14 +/* vpss BL register offsets */ +#define DM355_VPSSBL_CCDCMUX 0x1c +/* vpss CLK register offsets */ +#define DM355_VPSSCLK_CLKCTRL 0x04 +/* masks and shifts */ +#define VPSS_HSSISEL_SHIFT 4 +/* + * VDINT0 - vpss_int0, VDINT1 - vpss_int1, H3A - vpss_int4, + * IPIPE_INT1_SDR - vpss_int5 + */ +#define DM355_VPSSBL_INTSEL_DEFAULT 0xff83ff10 +/* VENCINT - vpss_int8 */ +#define DM355_VPSSBL_EVTSEL_DEFAULT 0x4 + +#define DM365_ISP5_PCCR 0x04 +#define DM365_ISP5_INTSEL1 0x10 +#define DM365_ISP5_INTSEL2 0x14 +#define DM365_ISP5_INTSEL3 0x18 +#define DM365_ISP5_CCDCMUX 0x20 +#define DM365_ISP5_PG_FRAME_SIZE 0x28 +#define DM365_VPBE_CLK_CTRL 0x00 +/* + * vpss interrupts. VDINT0 - vpss_int0, VDINT1 - vpss_int1, + * AF - vpss_int3 + */ +#define DM365_ISP5_INTSEL1_DEFAULT 0x0b1f0100 +/* AEW - vpss_int6, RSZ_INT_DMA - vpss_int5 */ +#define DM365_ISP5_INTSEL2_DEFAULT 0x1f0a0f1f +/* VENC - vpss_int8 */ +#define DM365_ISP5_INTSEL3_DEFAULT 0x00000015 + +/* masks and shifts for DM365*/ +#define DM365_CCDC_PG_VD_POL_SHIFT 0 +#define DM365_CCDC_PG_HD_POL_SHIFT 1 + +#define CCD_SRC_SEL_MASK (BIT_MASK(5) | BIT_MASK(4)) +#define CCD_SRC_SEL_SHIFT 4 + +/* Different SoC platforms supported by this driver */ +enum vpss_platform_type { + DM644X, + DM355, + DM365, +}; + +/* + * vpss operations. Depends on platform. Not all functions are available + * on all platforms. The api, first check if a functio is available before + * invoking it. In the probe, the function ptrs are initialized based on + * vpss name. vpss name can be "dm355_vpss", "dm644x_vpss" etc. + */ +struct vpss_hw_ops { + /* enable clock */ + int (*enable_clock)(enum vpss_clock_sel clock_sel, int en); + /* select input to ccdc */ + void (*select_ccdc_source)(enum vpss_ccdc_source_sel src_sel); + /* clear wbl overflow bit */ + int (*clear_wbl_overflow)(enum vpss_wbl_sel wbl_sel); +}; + +/* vpss configuration */ +struct vpss_oper_config { + __iomem void *vpss_regs_base0; + __iomem void *vpss_regs_base1; + enum vpss_platform_type platform; + spinlock_t vpss_lock; + struct vpss_hw_ops hw_ops; +}; + +static struct vpss_oper_config oper_cfg; + +/* register access routines */ +static inline u32 bl_regr(u32 offset) +{ + return __raw_readl(oper_cfg.vpss_regs_base0 + offset); +} + +static inline void bl_regw(u32 val, u32 offset) +{ + __raw_writel(val, oper_cfg.vpss_regs_base0 + offset); +} + +static inline u32 vpss_regr(u32 offset) +{ + return __raw_readl(oper_cfg.vpss_regs_base1 + offset); +} + +static inline void vpss_regw(u32 val, u32 offset) +{ + __raw_writel(val, oper_cfg.vpss_regs_base1 + offset); +} + +/* For DM365 only */ +static inline u32 isp5_read(u32 offset) +{ + return __raw_readl(oper_cfg.vpss_regs_base0 + offset); +} + +/* For DM365 only */ +static inline void isp5_write(u32 val, u32 offset) +{ + __raw_writel(val, oper_cfg.vpss_regs_base0 + offset); +} + +static void dm365_select_ccdc_source(enum vpss_ccdc_source_sel src_sel) +{ + u32 temp = isp5_read(DM365_ISP5_CCDCMUX) & ~CCD_SRC_SEL_MASK; + + /* if we are using pattern generator, enable it */ + if (src_sel == VPSS_PGLPBK || src_sel == VPSS_CCDCPG) + temp |= 0x08; + + temp |= (src_sel << CCD_SRC_SEL_SHIFT); + isp5_write(temp, DM365_ISP5_CCDCMUX); +} + +static void dm355_select_ccdc_source(enum vpss_ccdc_source_sel src_sel) +{ + bl_regw(src_sel << VPSS_HSSISEL_SHIFT, DM355_VPSSBL_CCDCMUX); +} + +int vpss_select_ccdc_source(enum vpss_ccdc_source_sel src_sel) +{ + if (!oper_cfg.hw_ops.select_ccdc_source) + return -EINVAL; + + oper_cfg.hw_ops.select_ccdc_source(src_sel); + return 0; +} +EXPORT_SYMBOL(vpss_select_ccdc_source); + +static int dm644x_clear_wbl_overflow(enum vpss_wbl_sel wbl_sel) +{ + u32 mask = 1, val; + + if (wbl_sel < VPSS_PCR_AEW_WBL_0 || + wbl_sel > VPSS_PCR_CCDC_WBL_O) + return -EINVAL; + + /* writing a 0 clear the overflow */ + mask = ~(mask << wbl_sel); + val = bl_regr(DM644X_SBL_PCR_VPSS) & mask; + bl_regw(val, DM644X_SBL_PCR_VPSS); + return 0; +} + +int vpss_clear_wbl_overflow(enum vpss_wbl_sel wbl_sel) +{ + if (!oper_cfg.hw_ops.clear_wbl_overflow) + return -EINVAL; + + return oper_cfg.hw_ops.clear_wbl_overflow(wbl_sel); +} +EXPORT_SYMBOL(vpss_clear_wbl_overflow); + +/* + * dm355_enable_clock - Enable VPSS Clock + * @clock_sel: CLock to be enabled/disabled + * @en: enable/disable flag + * + * This is called to enable or disable a vpss clock + */ +static int dm355_enable_clock(enum vpss_clock_sel clock_sel, int en) +{ + unsigned long flags; + u32 utemp, mask = 0x1, shift = 0; + + switch (clock_sel) { + case VPSS_VPBE_CLOCK: + /* nothing since lsb */ + break; + case VPSS_VENC_CLOCK_SEL: + shift = 2; + break; + case VPSS_CFALD_CLOCK: + shift = 3; + break; + case VPSS_H3A_CLOCK: + shift = 4; + break; + case VPSS_IPIPE_CLOCK: + shift = 5; + break; + case VPSS_CCDC_CLOCK: + shift = 6; + break; + default: + printk(KERN_ERR "dm355_enable_clock:" + " Invalid selector: %d\n", clock_sel); + return -EINVAL; + } + + spin_lock_irqsave(&oper_cfg.vpss_lock, flags); + utemp = vpss_regr(DM355_VPSSCLK_CLKCTRL); + if (!en) + utemp &= ~(mask << shift); + else + utemp |= (mask << shift); + + vpss_regw(utemp, DM355_VPSSCLK_CLKCTRL); + spin_unlock_irqrestore(&oper_cfg.vpss_lock, flags); + return 0; +} + +static int dm365_enable_clock(enum vpss_clock_sel clock_sel, int en) +{ + unsigned long flags; + u32 utemp, mask = 0x1, shift = 0, offset = DM365_ISP5_PCCR; + u32 (*read)(u32 offset) = isp5_read; + void(*write)(u32 val, u32 offset) = isp5_write; + + switch (clock_sel) { + case VPSS_BL_CLOCK: + break; + case VPSS_CCDC_CLOCK: + shift = 1; + break; + case VPSS_H3A_CLOCK: + shift = 2; + break; + case VPSS_RSZ_CLOCK: + shift = 3; + break; + case VPSS_IPIPE_CLOCK: + shift = 4; + break; + case VPSS_IPIPEIF_CLOCK: + shift = 5; + break; + case VPSS_PCLK_INTERNAL: + shift = 6; + break; + case VPSS_PSYNC_CLOCK_SEL: + shift = 7; + break; + case VPSS_VPBE_CLOCK: + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_VENC_CLOCK_SEL: + shift = 2; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_LDC_CLOCK: + shift = 3; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_FDIF_CLOCK: + shift = 4; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_OSD_CLOCK_SEL: + shift = 6; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_LDC_CLOCK_SEL: + shift = 7; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + default: + printk(KERN_ERR "dm365_enable_clock: Invalid selector: %d\n", + clock_sel); + return -1; + } + + spin_lock_irqsave(&oper_cfg.vpss_lock, flags); + utemp = read(offset); + if (!en) { + mask = ~mask; + utemp &= (mask << shift); + } else + utemp |= (mask << shift); + + write(utemp, offset); + spin_unlock_irqrestore(&oper_cfg.vpss_lock, flags); + + return 0; +} + +int vpss_enable_clock(enum vpss_clock_sel clock_sel, int en) +{ + if (!oper_cfg.hw_ops.enable_clock) + return -EINVAL; + + return oper_cfg.hw_ops.enable_clock(clock_sel, en); +} +EXPORT_SYMBOL(vpss_enable_clock); + +void dm365_vpss_set_sync_pol(struct vpss_sync_pol sync) +{ + int val = 0; + val = isp5_read(DM365_ISP5_CCDCMUX); + + val |= (sync.ccdpg_hdpol << DM365_CCDC_PG_HD_POL_SHIFT); + val |= (sync.ccdpg_vdpol << DM365_CCDC_PG_VD_POL_SHIFT); + + isp5_write(val, DM365_ISP5_CCDCMUX); +} +EXPORT_SYMBOL(dm365_vpss_set_sync_pol); + +void dm365_vpss_set_pg_frame_size(struct vpss_pg_frame_size frame_size) +{ + int current_reg = ((frame_size.hlpfr >> 1) - 1) << 16; + + current_reg |= (frame_size.pplen - 1); + isp5_write(current_reg, DM365_ISP5_PG_FRAME_SIZE); +} +EXPORT_SYMBOL(dm365_vpss_set_pg_frame_size); + +static int __devinit vpss_probe(struct platform_device *pdev) +{ + struct resource *r1, *r2; + char *platform_name; + int status; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENOENT; + } + + platform_name = pdev->dev.platform_data; + if (!strcmp(platform_name, "dm355_vpss")) + oper_cfg.platform = DM355; + else if (!strcmp(platform_name, "dm365_vpss")) + oper_cfg.platform = DM365; + else if (!strcmp(platform_name, "dm644x_vpss")) + oper_cfg.platform = DM644X; + else { + dev_err(&pdev->dev, "vpss driver not supported on" + " this platform\n"); + return -ENODEV; + } + + dev_info(&pdev->dev, "%s vpss probed\n", platform_name); + r1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r1) + return -ENOENT; + + r1 = request_mem_region(r1->start, resource_size(r1), r1->name); + if (!r1) + return -EBUSY; + + oper_cfg.vpss_regs_base0 = ioremap(r1->start, resource_size(r1)); + if (!oper_cfg.vpss_regs_base0) { + status = -EBUSY; + goto fail1; + } + + if (oper_cfg.platform == DM355 || oper_cfg.platform == DM365) { + r2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!r2) { + status = -ENOENT; + goto fail2; + } + r2 = request_mem_region(r2->start, resource_size(r2), r2->name); + if (!r2) { + status = -EBUSY; + goto fail2; + } + + oper_cfg.vpss_regs_base1 = ioremap(r2->start, + resource_size(r2)); + if (!oper_cfg.vpss_regs_base1) { + status = -EBUSY; + goto fail3; + } + } + + if (oper_cfg.platform == DM355) { + oper_cfg.hw_ops.enable_clock = dm355_enable_clock; + oper_cfg.hw_ops.select_ccdc_source = dm355_select_ccdc_source; + /* Setup vpss interrupts */ + bl_regw(DM355_VPSSBL_INTSEL_DEFAULT, DM355_VPSSBL_INTSEL); + bl_regw(DM355_VPSSBL_EVTSEL_DEFAULT, DM355_VPSSBL_EVTSEL); + } else if (oper_cfg.platform == DM365) { + oper_cfg.hw_ops.enable_clock = dm365_enable_clock; + oper_cfg.hw_ops.select_ccdc_source = dm365_select_ccdc_source; + /* Setup vpss interrupts */ + isp5_write(DM365_ISP5_INTSEL1_DEFAULT, DM365_ISP5_INTSEL1); + isp5_write(DM365_ISP5_INTSEL2_DEFAULT, DM365_ISP5_INTSEL2); + isp5_write(DM365_ISP5_INTSEL3_DEFAULT, DM365_ISP5_INTSEL3); + } else + oper_cfg.hw_ops.clear_wbl_overflow = dm644x_clear_wbl_overflow; + + spin_lock_init(&oper_cfg.vpss_lock); + dev_info(&pdev->dev, "%s vpss probe success\n", platform_name); + return 0; + +fail3: + release_mem_region(r2->start, resource_size(r2)); +fail2: + iounmap(oper_cfg.vpss_regs_base0); +fail1: + release_mem_region(r1->start, resource_size(r1)); + return status; +} + +static int __devexit vpss_remove(struct platform_device *pdev) +{ + struct resource *res; + + iounmap(oper_cfg.vpss_regs_base0); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + if (oper_cfg.platform == DM355 || oper_cfg.platform == DM365) { + iounmap(oper_cfg.vpss_regs_base1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + release_mem_region(res->start, resource_size(res)); + } + return 0; +} + +static struct platform_driver vpss_driver = { + .driver = { + .name = "vpss", + .owner = THIS_MODULE, + }, + .remove = __devexit_p(vpss_remove), + .probe = vpss_probe, +}; + +static void vpss_exit(void) +{ + platform_driver_unregister(&vpss_driver); +} + +static int __init vpss_init(void) +{ + return platform_driver_register(&vpss_driver); +} +subsys_initcall(vpss_init); +module_exit(vpss_exit); diff --git a/drivers/media/platform/exynos-gsc/Makefile b/drivers/media/platform/exynos-gsc/Makefile new file mode 100644 index 000000000000..6d1411c6d49f --- /dev/null +++ b/drivers/media/platform/exynos-gsc/Makefile @@ -0,0 +1,3 @@ +exynos-gsc-objs := gsc-core.o gsc-m2m.o gsc-regs.o + +obj-$(CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC) += exynos-gsc.o diff --git a/drivers/media/platform/exynos-gsc/gsc-core.c b/drivers/media/platform/exynos-gsc/gsc-core.c new file mode 100644 index 000000000000..bfec9e65aefb --- /dev/null +++ b/drivers/media/platform/exynos-gsc/gsc-core.c @@ -0,0 +1,1252 @@ +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung EXYNOS5 SoC series G-Scaler driver + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gsc-core.h" + +#define GSC_CLOCK_GATE_NAME "gscl" + +static const struct gsc_fmt gsc_formats[] = { + { + .name = "RGB565", + .pixelformat = V4L2_PIX_FMT_RGB565X, + .depth = { 16 }, + .color = GSC_RGB, + .num_planes = 1, + .num_comp = 1, + }, { + .name = "XRGB-8-8-8-8, 32 bpp", + .pixelformat = V4L2_PIX_FMT_RGB32, + .depth = { 32 }, + .color = GSC_RGB, + .num_planes = 1, + .num_comp = 1, + }, { + .name = "YUV 4:2:2 packed, YCbYCr", + .pixelformat = V4L2_PIX_FMT_YUYV, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 1, + .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, + }, { + .name = "YUV 4:2:2 packed, CbYCrY", + .pixelformat = V4L2_PIX_FMT_UYVY, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_C, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 1, + .mbus_code = V4L2_MBUS_FMT_UYVY8_2X8, + }, { + .name = "YUV 4:2:2 packed, CrYCbY", + .pixelformat = V4L2_PIX_FMT_VYUY, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_C, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 1, + .mbus_code = V4L2_MBUS_FMT_VYUY8_2X8, + }, { + .name = "YUV 4:2:2 packed, YCrYCb", + .pixelformat = V4L2_PIX_FMT_YVYU, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 1, + .mbus_code = V4L2_MBUS_FMT_YVYU8_2X8, + }, { + .name = "YUV 4:4:4 planar, YCbYCr", + .pixelformat = V4L2_PIX_FMT_YUV32, + .depth = { 32 }, + .color = GSC_YUV444, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 1, + }, { + .name = "YUV 4:2:2 planar, Y/Cb/Cr", + .pixelformat = V4L2_PIX_FMT_YUV422P, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 3, + }, { + .name = "YUV 4:2:2 planar, Y/CbCr", + .pixelformat = V4L2_PIX_FMT_NV16, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 2, + }, { + .name = "YUV 4:2:2 planar, Y/CrCb", + .pixelformat = V4L2_PIX_FMT_NV61, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 2, + }, { + .name = "YUV 4:2:0 planar, YCbCr", + .pixelformat = V4L2_PIX_FMT_YUV420, + .depth = { 12 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 3, + }, { + .name = "YUV 4:2:0 planar, YCrCb", + .pixelformat = V4L2_PIX_FMT_YVU420, + .depth = { 12 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 3, + + }, { + .name = "YUV 4:2:0 planar, Y/CbCr", + .pixelformat = V4L2_PIX_FMT_NV12, + .depth = { 12 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 2, + }, { + .name = "YUV 4:2:0 planar, Y/CrCb", + .pixelformat = V4L2_PIX_FMT_NV21, + .depth = { 12 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 2, + }, { + .name = "YUV 4:2:0 non-contig. 2p, Y/CbCr", + .pixelformat = V4L2_PIX_FMT_NV12M, + .depth = { 8, 4 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 2, + .num_comp = 2, + }, { + .name = "YUV 4:2:0 non-contig. 3p, Y/Cb/Cr", + .pixelformat = V4L2_PIX_FMT_YUV420M, + .depth = { 8, 2, 2 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 3, + .num_comp = 3, + }, { + .name = "YUV 4:2:0 non-contig. 3p, Y/Cr/Cb", + .pixelformat = V4L2_PIX_FMT_YVU420M, + .depth = { 8, 2, 2 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 3, + .num_comp = 3, + } +}; + +const struct gsc_fmt *get_format(int index) +{ + if (index >= ARRAY_SIZE(gsc_formats)) + return NULL; + + return (struct gsc_fmt *)&gsc_formats[index]; +} + +const struct gsc_fmt *find_fmt(u32 *pixelformat, u32 *mbus_code, u32 index) +{ + const struct gsc_fmt *fmt, *def_fmt = NULL; + unsigned int i; + + if (index >= ARRAY_SIZE(gsc_formats)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(gsc_formats); ++i) { + fmt = get_format(i); + if (pixelformat && fmt->pixelformat == *pixelformat) + return fmt; + if (mbus_code && fmt->mbus_code == *mbus_code) + return fmt; + if (index == i) + def_fmt = fmt; + } + return def_fmt; + +} + +void gsc_set_frame_size(struct gsc_frame *frame, int width, int height) +{ + frame->f_width = width; + frame->f_height = height; + frame->crop.width = width; + frame->crop.height = height; + frame->crop.left = 0; + frame->crop.top = 0; +} + +int gsc_cal_prescaler_ratio(struct gsc_variant *var, u32 src, u32 dst, + u32 *ratio) +{ + if ((dst > src) || (dst >= src / var->poly_sc_down_max)) { + *ratio = 1; + return 0; + } + + if ((src / var->poly_sc_down_max / var->pre_sc_down_max) > dst) { + pr_err("Exceeded maximum downscaling ratio (1/16))"); + return -EINVAL; + } + + *ratio = (dst > (src / 8)) ? 2 : 4; + + return 0; +} + +void gsc_get_prescaler_shfactor(u32 hratio, u32 vratio, u32 *sh) +{ + if (hratio == 4 && vratio == 4) + *sh = 4; + else if ((hratio == 4 && vratio == 2) || + (hratio == 2 && vratio == 4)) + *sh = 3; + else if ((hratio == 4 && vratio == 1) || + (hratio == 1 && vratio == 4) || + (hratio == 2 && vratio == 2)) + *sh = 2; + else if (hratio == 1 && vratio == 1) + *sh = 0; + else + *sh = 1; +} + +void gsc_check_src_scale_info(struct gsc_variant *var, + struct gsc_frame *s_frame, u32 *wratio, + u32 tx, u32 ty, u32 *hratio) +{ + int remainder = 0, walign, halign; + + if (is_yuv420(s_frame->fmt->color)) { + walign = GSC_SC_ALIGN_4; + halign = GSC_SC_ALIGN_4; + } else if (is_yuv422(s_frame->fmt->color)) { + walign = GSC_SC_ALIGN_4; + halign = GSC_SC_ALIGN_2; + } else { + walign = GSC_SC_ALIGN_2; + halign = GSC_SC_ALIGN_2; + } + + remainder = s_frame->crop.width % (*wratio * walign); + if (remainder) { + s_frame->crop.width -= remainder; + gsc_cal_prescaler_ratio(var, s_frame->crop.width, tx, wratio); + pr_info("cropped src width size is recalculated from %d to %d", + s_frame->crop.width + remainder, s_frame->crop.width); + } + + remainder = s_frame->crop.height % (*hratio * halign); + if (remainder) { + s_frame->crop.height -= remainder; + gsc_cal_prescaler_ratio(var, s_frame->crop.height, ty, hratio); + pr_info("cropped src height size is recalculated from %d to %d", + s_frame->crop.height + remainder, s_frame->crop.height); + } +} + +int gsc_enum_fmt_mplane(struct v4l2_fmtdesc *f) +{ + const struct gsc_fmt *fmt; + + fmt = find_fmt(NULL, NULL, f->index); + if (!fmt) + return -EINVAL; + + strlcpy(f->description, fmt->name, sizeof(f->description)); + f->pixelformat = fmt->pixelformat; + + return 0; +} + +static u32 get_plane_info(struct gsc_frame *frm, u32 addr, u32 *index) +{ + if (frm->addr.y == addr) { + *index = 0; + return frm->addr.y; + } else if (frm->addr.cb == addr) { + *index = 1; + return frm->addr.cb; + } else if (frm->addr.cr == addr) { + *index = 2; + return frm->addr.cr; + } else { + pr_err("Plane address is wrong"); + return -EINVAL; + } +} + +void gsc_set_prefbuf(struct gsc_dev *gsc, struct gsc_frame *frm) +{ + u32 f_chk_addr, f_chk_len, s_chk_addr, s_chk_len; + f_chk_addr = f_chk_len = s_chk_addr = s_chk_len = 0; + + f_chk_addr = frm->addr.y; + f_chk_len = frm->payload[0]; + if (frm->fmt->num_planes == 2) { + s_chk_addr = frm->addr.cb; + s_chk_len = frm->payload[1]; + } else if (frm->fmt->num_planes == 3) { + u32 low_addr, low_plane, mid_addr, mid_plane; + u32 high_addr, high_plane; + u32 t_min, t_max; + + t_min = min3(frm->addr.y, frm->addr.cb, frm->addr.cr); + low_addr = get_plane_info(frm, t_min, &low_plane); + t_max = max3(frm->addr.y, frm->addr.cb, frm->addr.cr); + high_addr = get_plane_info(frm, t_max, &high_plane); + + mid_plane = 3 - (low_plane + high_plane); + if (mid_plane == 0) + mid_addr = frm->addr.y; + else if (mid_plane == 1) + mid_addr = frm->addr.cb; + else if (mid_plane == 2) + mid_addr = frm->addr.cr; + else + return; + + f_chk_addr = low_addr; + if (mid_addr + frm->payload[mid_plane] - low_addr > + high_addr + frm->payload[high_plane] - mid_addr) { + f_chk_len = frm->payload[low_plane]; + s_chk_addr = mid_addr; + s_chk_len = high_addr + + frm->payload[high_plane] - mid_addr; + } else { + f_chk_len = mid_addr + + frm->payload[mid_plane] - low_addr; + s_chk_addr = high_addr; + s_chk_len = frm->payload[high_plane]; + } + } + pr_debug("f_addr = 0x%08x, f_len = %d, s_addr = 0x%08x, s_len = %d\n", + f_chk_addr, f_chk_len, s_chk_addr, s_chk_len); +} + +int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) +{ + struct gsc_dev *gsc = ctx->gsc_dev; + struct gsc_variant *variant = gsc->variant; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct gsc_fmt *fmt; + u32 max_w, max_h, mod_x, mod_y; + u32 min_w, min_h, tmp_w, tmp_h; + int i; + + pr_debug("user put w: %d, h: %d", pix_mp->width, pix_mp->height); + + fmt = find_fmt(&pix_mp->pixelformat, NULL, 0); + if (!fmt) { + pr_err("pixelformat format (0x%X) invalid\n", + pix_mp->pixelformat); + return -EINVAL; + } + + if (pix_mp->field == V4L2_FIELD_ANY) + pix_mp->field = V4L2_FIELD_NONE; + else if (pix_mp->field != V4L2_FIELD_NONE) { + pr_err("Not supported field order(%d)\n", pix_mp->field); + return -EINVAL; + } + + max_w = variant->pix_max->target_rot_dis_w; + max_h = variant->pix_max->target_rot_dis_h; + + mod_x = ffs(variant->pix_align->org_w) - 1; + if (is_yuv420(fmt->color)) + mod_y = ffs(variant->pix_align->org_h) - 1; + else + mod_y = ffs(variant->pix_align->org_h) - 2; + + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + min_w = variant->pix_min->org_w; + min_h = variant->pix_min->org_h; + } else { + min_w = variant->pix_min->target_rot_dis_w; + min_h = variant->pix_min->target_rot_dis_h; + } + + pr_debug("mod_x: %d, mod_y: %d, max_w: %d, max_h = %d", + mod_x, mod_y, max_w, max_h); + + /* To check if image size is modified to adjust parameter against + hardware abilities */ + tmp_w = pix_mp->width; + tmp_h = pix_mp->height; + + v4l_bound_align_image(&pix_mp->width, min_w, max_w, mod_x, + &pix_mp->height, min_h, max_h, mod_y, 0); + if (tmp_w != pix_mp->width || tmp_h != pix_mp->height) + pr_info("Image size has been modified from %dx%d to %dx%d", + tmp_w, tmp_h, pix_mp->width, pix_mp->height); + + pix_mp->num_planes = fmt->num_planes; + + if (pix_mp->width >= 1280) /* HD */ + pix_mp->colorspace = V4L2_COLORSPACE_REC709; + else /* SD */ + pix_mp->colorspace = V4L2_COLORSPACE_SMPTE170M; + + + for (i = 0; i < pix_mp->num_planes; ++i) { + int bpl = (pix_mp->width * fmt->depth[i]) >> 3; + pix_mp->plane_fmt[i].bytesperline = bpl; + pix_mp->plane_fmt[i].sizeimage = bpl * pix_mp->height; + + pr_debug("[%d]: bpl: %d, sizeimage: %d", + i, bpl, pix_mp->plane_fmt[i].sizeimage); + } + + return 0; +} + +int gsc_g_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) +{ + struct gsc_frame *frame; + struct v4l2_pix_format_mplane *pix_mp; + int i; + + frame = ctx_get_frame(ctx, f->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + pix_mp = &f->fmt.pix_mp; + + pix_mp->width = frame->f_width; + pix_mp->height = frame->f_height; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->pixelformat = frame->fmt->pixelformat; + pix_mp->colorspace = V4L2_COLORSPACE_REC709; + pix_mp->num_planes = frame->fmt->num_planes; + + for (i = 0; i < pix_mp->num_planes; ++i) { + pix_mp->plane_fmt[i].bytesperline = (frame->f_width * + frame->fmt->depth[i]) / 8; + pix_mp->plane_fmt[i].sizeimage = + pix_mp->plane_fmt[i].bytesperline * frame->f_height; + } + + return 0; +} + +void gsc_check_crop_change(u32 tmp_w, u32 tmp_h, u32 *w, u32 *h) +{ + if (tmp_w != *w || tmp_h != *h) { + pr_info("Cropped size has been modified from %dx%d to %dx%d", + *w, *h, tmp_w, tmp_h); + *w = tmp_w; + *h = tmp_h; + } +} + +int gsc_g_crop(struct gsc_ctx *ctx, struct v4l2_crop *cr) +{ + struct gsc_frame *frame; + + frame = ctx_get_frame(ctx, cr->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + cr->c = frame->crop; + + return 0; +} + +int gsc_try_crop(struct gsc_ctx *ctx, struct v4l2_crop *cr) +{ + struct gsc_frame *f; + struct gsc_dev *gsc = ctx->gsc_dev; + struct gsc_variant *variant = gsc->variant; + u32 mod_x = 0, mod_y = 0, tmp_w, tmp_h; + u32 min_w, min_h, max_w, max_h; + + if (cr->c.top < 0 || cr->c.left < 0) { + pr_err("doesn't support negative values for top & left\n"); + return -EINVAL; + } + pr_debug("user put w: %d, h: %d", cr->c.width, cr->c.height); + + if (cr->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + f = &ctx->d_frame; + else if (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + f = &ctx->s_frame; + else + return -EINVAL; + + max_w = f->f_width; + max_h = f->f_height; + tmp_w = cr->c.width; + tmp_h = cr->c.height; + + if (V4L2_TYPE_IS_OUTPUT(cr->type)) { + if ((is_yuv422(f->fmt->color) && f->fmt->num_comp == 1) || + is_rgb(f->fmt->color)) + min_w = 32; + else + min_w = 64; + if ((is_yuv422(f->fmt->color) && f->fmt->num_comp == 3) || + is_yuv420(f->fmt->color)) + min_h = 32; + else + min_h = 16; + } else { + if (is_yuv420(f->fmt->color) || is_yuv422(f->fmt->color)) + mod_x = ffs(variant->pix_align->target_w) - 1; + if (is_yuv420(f->fmt->color)) + mod_y = ffs(variant->pix_align->target_h) - 1; + if (ctx->gsc_ctrls.rotate->val == 90 || + ctx->gsc_ctrls.rotate->val == 270) { + max_w = f->f_height; + max_h = f->f_width; + min_w = variant->pix_min->target_rot_en_w; + min_h = variant->pix_min->target_rot_en_h; + tmp_w = cr->c.height; + tmp_h = cr->c.width; + } else { + min_w = variant->pix_min->target_rot_dis_w; + min_h = variant->pix_min->target_rot_dis_h; + } + } + pr_debug("mod_x: %d, mod_y: %d, min_w: %d, min_h = %d", + mod_x, mod_y, min_w, min_h); + pr_debug("tmp_w : %d, tmp_h : %d", tmp_w, tmp_h); + + v4l_bound_align_image(&tmp_w, min_w, max_w, mod_x, + &tmp_h, min_h, max_h, mod_y, 0); + + if (!V4L2_TYPE_IS_OUTPUT(cr->type) && + (ctx->gsc_ctrls.rotate->val == 90 || + ctx->gsc_ctrls.rotate->val == 270)) + gsc_check_crop_change(tmp_h, tmp_w, + &cr->c.width, &cr->c.height); + else + gsc_check_crop_change(tmp_w, tmp_h, + &cr->c.width, &cr->c.height); + + + /* adjust left/top if cropping rectangle is out of bounds */ + /* Need to add code to algin left value with 2's multiple */ + if (cr->c.left + tmp_w > max_w) + cr->c.left = max_w - tmp_w; + if (cr->c.top + tmp_h > max_h) + cr->c.top = max_h - tmp_h; + + if ((is_yuv420(f->fmt->color) || is_yuv422(f->fmt->color)) && + cr->c.left & 1) + cr->c.left -= 1; + + pr_debug("Aligned l:%d, t:%d, w:%d, h:%d, f_w: %d, f_h: %d", + cr->c.left, cr->c.top, cr->c.width, cr->c.height, max_w, max_h); + + return 0; +} + +int gsc_check_scaler_ratio(struct gsc_variant *var, int sw, int sh, int dw, + int dh, int rot, int out_path) +{ + int tmp_w, tmp_h, sc_down_max; + + if (out_path == GSC_DMA) + sc_down_max = var->sc_down_max; + else + sc_down_max = var->local_sc_down; + + if (rot == 90 || rot == 270) { + tmp_w = dh; + tmp_h = dw; + } else { + tmp_w = dw; + tmp_h = dh; + } + + if ((sw / tmp_w) > sc_down_max || + (sh / tmp_h) > sc_down_max || + (tmp_w / sw) > var->sc_up_max || + (tmp_h / sh) > var->sc_up_max) + return -EINVAL; + + return 0; +} + +int gsc_set_scaler_info(struct gsc_ctx *ctx) +{ + struct gsc_scaler *sc = &ctx->scaler; + struct gsc_frame *s_frame = &ctx->s_frame; + struct gsc_frame *d_frame = &ctx->d_frame; + struct gsc_variant *variant = ctx->gsc_dev->variant; + struct device *dev = &ctx->gsc_dev->pdev->dev; + int tx, ty; + int ret; + + ret = gsc_check_scaler_ratio(variant, s_frame->crop.width, + s_frame->crop.height, d_frame->crop.width, d_frame->crop.height, + ctx->gsc_ctrls.rotate->val, ctx->out_path); + if (ret) { + pr_err("out of scaler range"); + return ret; + } + + if (ctx->gsc_ctrls.rotate->val == 90 || + ctx->gsc_ctrls.rotate->val == 270) { + ty = d_frame->crop.width; + tx = d_frame->crop.height; + } else { + tx = d_frame->crop.width; + ty = d_frame->crop.height; + } + + if (tx <= 0 || ty <= 0) { + dev_err(dev, "Invalid target size: %dx%d", tx, ty); + return -EINVAL; + } + + ret = gsc_cal_prescaler_ratio(variant, s_frame->crop.width, + tx, &sc->pre_hratio); + if (ret) { + pr_err("Horizontal scale ratio is out of range"); + return ret; + } + + ret = gsc_cal_prescaler_ratio(variant, s_frame->crop.height, + ty, &sc->pre_vratio); + if (ret) { + pr_err("Vertical scale ratio is out of range"); + return ret; + } + + gsc_check_src_scale_info(variant, s_frame, &sc->pre_hratio, + tx, ty, &sc->pre_vratio); + + gsc_get_prescaler_shfactor(sc->pre_hratio, sc->pre_vratio, + &sc->pre_shfactor); + + sc->main_hratio = (s_frame->crop.width << 16) / tx; + sc->main_vratio = (s_frame->crop.height << 16) / ty; + + pr_debug("scaler input/output size : sx = %d, sy = %d, tx = %d, ty = %d", + s_frame->crop.width, s_frame->crop.height, tx, ty); + pr_debug("scaler ratio info : pre_shfactor : %d, pre_h : %d", + sc->pre_shfactor, sc->pre_hratio); + pr_debug("pre_v :%d, main_h : %d, main_v : %d", + sc->pre_vratio, sc->main_hratio, sc->main_vratio); + + return 0; +} + +static int __gsc_s_ctrl(struct gsc_ctx *ctx, struct v4l2_ctrl *ctrl) +{ + struct gsc_dev *gsc = ctx->gsc_dev; + struct gsc_variant *variant = gsc->variant; + unsigned int flags = GSC_DST_FMT | GSC_SRC_FMT; + int ret = 0; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctx->hflip = ctrl->val; + break; + + case V4L2_CID_VFLIP: + ctx->vflip = ctrl->val; + break; + + case V4L2_CID_ROTATE: + if ((ctx->state & flags) == flags) { + ret = gsc_check_scaler_ratio(variant, + ctx->s_frame.crop.width, + ctx->s_frame.crop.height, + ctx->d_frame.crop.width, + ctx->d_frame.crop.height, + ctx->gsc_ctrls.rotate->val, + ctx->out_path); + + if (ret) + return -EINVAL; + } + + ctx->rotation = ctrl->val; + break; + + case V4L2_CID_ALPHA_COMPONENT: + ctx->d_frame.alpha = ctrl->val; + break; + } + + ctx->state |= GSC_PARAMS; + return 0; +} + +static int gsc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct gsc_ctx *ctx = ctrl_to_ctx(ctrl); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ctx->gsc_dev->slock, flags); + ret = __gsc_s_ctrl(ctx, ctrl); + spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags); + + return ret; +} + +static const struct v4l2_ctrl_ops gsc_ctrl_ops = { + .s_ctrl = gsc_s_ctrl, +}; + +int gsc_ctrls_create(struct gsc_ctx *ctx) +{ + if (ctx->ctrls_rdy) { + pr_err("Control handler of this context was created already"); + return 0; + } + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, GSC_MAX_CTRL_NUM); + + ctx->gsc_ctrls.rotate = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &gsc_ctrl_ops, V4L2_CID_ROTATE, 0, 270, 90, 0); + ctx->gsc_ctrls.hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &gsc_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + ctx->gsc_ctrls.vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &gsc_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + ctx->gsc_ctrls.global_alpha = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &gsc_ctrl_ops, V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0); + + ctx->ctrls_rdy = ctx->ctrl_handler.error == 0; + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + pr_err("Failed to create G-Scaler control handlers"); + return err; + } + + return 0; +} + +void gsc_ctrls_delete(struct gsc_ctx *ctx) +{ + if (ctx->ctrls_rdy) { + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + ctx->ctrls_rdy = false; + } +} + +/* The color format (num_comp, num_planes) must be already configured. */ +int gsc_prepare_addr(struct gsc_ctx *ctx, struct vb2_buffer *vb, + struct gsc_frame *frame, struct gsc_addr *addr) +{ + int ret = 0; + u32 pix_size; + + if ((vb == NULL) || (frame == NULL)) + return -EINVAL; + + pix_size = frame->f_width * frame->f_height; + + pr_debug("num_planes= %d, num_comp= %d, pix_size= %d", + frame->fmt->num_planes, frame->fmt->num_comp, pix_size); + + addr->y = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (frame->fmt->num_planes == 1) { + switch (frame->fmt->num_comp) { + case 1: + addr->cb = 0; + addr->cr = 0; + break; + case 2: + /* decompose Y into Y/Cb */ + addr->cb = (dma_addr_t)(addr->y + pix_size); + addr->cr = 0; + break; + case 3: + /* decompose Y into Y/Cb/Cr */ + addr->cb = (dma_addr_t)(addr->y + pix_size); + if (GSC_YUV420 == frame->fmt->color) + addr->cr = (dma_addr_t)(addr->cb + + (pix_size >> 2)); + else /* 422 */ + addr->cr = (dma_addr_t)(addr->cb + + (pix_size >> 1)); + break; + default: + pr_err("Invalid the number of color planes"); + return -EINVAL; + } + } else { + if (frame->fmt->num_planes >= 2) + addr->cb = vb2_dma_contig_plane_dma_addr(vb, 1); + + if (frame->fmt->num_planes == 3) + addr->cr = vb2_dma_contig_plane_dma_addr(vb, 2); + } + + if ((frame->fmt->pixelformat == V4L2_PIX_FMT_VYUY) || + (frame->fmt->pixelformat == V4L2_PIX_FMT_YVYU) || + (frame->fmt->pixelformat == V4L2_PIX_FMT_NV61) || + (frame->fmt->pixelformat == V4L2_PIX_FMT_YVU420) || + (frame->fmt->pixelformat == V4L2_PIX_FMT_NV21) || + (frame->fmt->pixelformat == V4L2_PIX_FMT_YVU420M)) + swap(addr->cb, addr->cr); + + pr_debug("ADDR: y= 0x%X cb= 0x%X cr= 0x%X ret= %d", + addr->y, addr->cb, addr->cr, ret); + + return ret; +} + +static irqreturn_t gsc_irq_handler(int irq, void *priv) +{ + struct gsc_dev *gsc = priv; + struct gsc_ctx *ctx; + int gsc_irq; + + gsc_irq = gsc_hw_get_irq_status(gsc); + gsc_hw_clear_irq(gsc, gsc_irq); + + if (gsc_irq == GSC_IRQ_OVERRUN) { + pr_err("Local path input over-run interrupt has occurred!\n"); + return IRQ_HANDLED; + } + + spin_lock(&gsc->slock); + + if (test_and_clear_bit(ST_M2M_PEND, &gsc->state)) { + + gsc_hw_enable_control(gsc, false); + + if (test_and_clear_bit(ST_M2M_SUSPENDING, &gsc->state)) { + set_bit(ST_M2M_SUSPENDED, &gsc->state); + wake_up(&gsc->irq_queue); + goto isr_unlock; + } + ctx = v4l2_m2m_get_curr_priv(gsc->m2m.m2m_dev); + + if (!ctx || !ctx->m2m_ctx) + goto isr_unlock; + + spin_unlock(&gsc->slock); + gsc_m2m_job_finish(ctx, VB2_BUF_STATE_DONE); + + /* wake_up job_abort, stop_streaming */ + if (ctx->state & GSC_CTX_STOP_REQ) { + ctx->state &= ~GSC_CTX_STOP_REQ; + wake_up(&gsc->irq_queue); + } + return IRQ_HANDLED; + } + +isr_unlock: + spin_unlock(&gsc->slock); + return IRQ_HANDLED; +} + +static struct gsc_pix_max gsc_v_100_max = { + .org_scaler_bypass_w = 8192, + .org_scaler_bypass_h = 8192, + .org_scaler_input_w = 4800, + .org_scaler_input_h = 3344, + .real_rot_dis_w = 4800, + .real_rot_dis_h = 3344, + .real_rot_en_w = 2047, + .real_rot_en_h = 2047, + .target_rot_dis_w = 4800, + .target_rot_dis_h = 3344, + .target_rot_en_w = 2016, + .target_rot_en_h = 2016, +}; + +static struct gsc_pix_min gsc_v_100_min = { + .org_w = 64, + .org_h = 32, + .real_w = 64, + .real_h = 32, + .target_rot_dis_w = 64, + .target_rot_dis_h = 32, + .target_rot_en_w = 32, + .target_rot_en_h = 16, +}; + +static struct gsc_pix_align gsc_v_100_align = { + .org_h = 16, + .org_w = 16, /* yuv420 : 16, others : 8 */ + .offset_h = 2, /* yuv420/422 : 2, others : 1 */ + .real_w = 16, /* yuv420/422 : 4~16, others : 2~8 */ + .real_h = 16, /* yuv420 : 4~16, others : 1 */ + .target_w = 2, /* yuv420/422 : 2, others : 1 */ + .target_h = 2, /* yuv420 : 2, others : 1 */ +}; + +static struct gsc_variant gsc_v_100_variant = { + .pix_max = &gsc_v_100_max, + .pix_min = &gsc_v_100_min, + .pix_align = &gsc_v_100_align, + .in_buf_cnt = 8, + .out_buf_cnt = 16, + .sc_up_max = 8, + .sc_down_max = 16, + .poly_sc_down_max = 4, + .pre_sc_down_max = 4, + .local_sc_down = 2, +}; + +static struct gsc_driverdata gsc_v_100_drvdata = { + .variant = { + [0] = &gsc_v_100_variant, + [1] = &gsc_v_100_variant, + [2] = &gsc_v_100_variant, + [3] = &gsc_v_100_variant, + }, + .num_entities = 4, + .lclk_frequency = 266000000UL, +}; + +static struct platform_device_id gsc_driver_ids[] = { + { + .name = "exynos-gsc", + .driver_data = (unsigned long)&gsc_v_100_drvdata, + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, gsc_driver_ids); + +static const struct of_device_id exynos_gsc_match[] = { + { .compatible = "samsung,exynos5250-gsc", + .data = &gsc_v_100_drvdata, }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_gsc_match); + +static void *gsc_get_drv_data(struct platform_device *pdev) +{ + struct gsc_driverdata *driver_data = NULL; + + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(of_match_ptr(exynos_gsc_match), + pdev->dev.of_node); + if (match) + driver_data = match->data; + } else { + driver_data = (struct gsc_driverdata *) + platform_get_device_id(pdev)->driver_data; + } + + return driver_data; +} + +static void gsc_clk_put(struct gsc_dev *gsc) +{ + if (IS_ERR_OR_NULL(gsc->clock)) + return; + + clk_unprepare(gsc->clock); + clk_put(gsc->clock); + gsc->clock = NULL; +} + +static int gsc_clk_get(struct gsc_dev *gsc) +{ + int ret; + + dev_dbg(&gsc->pdev->dev, "gsc_clk_get Called\n"); + + gsc->clock = clk_get(&gsc->pdev->dev, GSC_CLOCK_GATE_NAME); + if (IS_ERR(gsc->clock)) + goto err_print; + + ret = clk_prepare(gsc->clock); + if (ret < 0) { + clk_put(gsc->clock); + gsc->clock = NULL; + goto err; + } + + return 0; + +err: + dev_err(&gsc->pdev->dev, "clock prepare failed for clock: %s\n", + GSC_CLOCK_GATE_NAME); + gsc_clk_put(gsc); +err_print: + dev_err(&gsc->pdev->dev, "failed to get clock~~~: %s\n", + GSC_CLOCK_GATE_NAME); + return -ENXIO; +} + +static int gsc_m2m_suspend(struct gsc_dev *gsc) +{ + unsigned long flags; + int timeout; + + spin_lock_irqsave(&gsc->slock, flags); + if (!gsc_m2m_pending(gsc)) { + spin_unlock_irqrestore(&gsc->slock, flags); + return 0; + } + clear_bit(ST_M2M_SUSPENDED, &gsc->state); + set_bit(ST_M2M_SUSPENDING, &gsc->state); + spin_unlock_irqrestore(&gsc->slock, flags); + + timeout = wait_event_timeout(gsc->irq_queue, + test_bit(ST_M2M_SUSPENDED, &gsc->state), + GSC_SHUTDOWN_TIMEOUT); + + clear_bit(ST_M2M_SUSPENDING, &gsc->state); + return timeout == 0 ? -EAGAIN : 0; +} + +static int gsc_m2m_resume(struct gsc_dev *gsc) +{ + unsigned long flags; + + spin_lock_irqsave(&gsc->slock, flags); + /* Clear for full H/W setup in first run after resume */ + gsc->m2m.ctx = NULL; + spin_unlock_irqrestore(&gsc->slock, flags); + + if (test_and_clear_bit(ST_M2M_SUSPENDED, &gsc->state)) + gsc_m2m_job_finish(gsc->m2m.ctx, + VB2_BUF_STATE_ERROR); + return 0; +} + +static int gsc_probe(struct platform_device *pdev) +{ + struct gsc_dev *gsc; + struct resource *res; + struct gsc_driverdata *drv_data = gsc_get_drv_data(pdev); + struct device *dev = &pdev->dev; + int ret = 0; + + gsc = devm_kzalloc(dev, sizeof(struct gsc_dev), GFP_KERNEL); + if (!gsc) + return -ENOMEM; + + if (dev->of_node) + gsc->id = of_alias_get_id(pdev->dev.of_node, "gsc"); + else + gsc->id = pdev->id; + + if (gsc->id < 0 || gsc->id >= drv_data->num_entities) { + dev_err(dev, "Invalid platform device id: %d\n", gsc->id); + return -EINVAL; + } + + gsc->variant = drv_data->variant[gsc->id]; + gsc->pdev = pdev; + gsc->pdata = dev->platform_data; + + init_waitqueue_head(&gsc->irq_queue); + spin_lock_init(&gsc->slock); + mutex_init(&gsc->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + gsc->regs = devm_request_and_ioremap(dev, res); + if (!gsc->regs) { + dev_err(dev, "failed to map registers\n"); + return -ENOENT; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "failed to get IRQ resource\n"); + return -ENXIO; + } + + ret = gsc_clk_get(gsc); + if (ret) + return ret; + + ret = devm_request_irq(dev, res->start, gsc_irq_handler, + 0, pdev->name, gsc); + if (ret) { + dev_err(dev, "failed to install irq (%d)\n", ret); + goto err_clk; + } + + ret = gsc_register_m2m_device(gsc); + if (ret) + goto err_clk; + + platform_set_drvdata(pdev, gsc); + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) + goto err_m2m; + + /* Initialize continious memory allocator */ + gsc->alloc_ctx = vb2_dma_contig_init_ctx(dev); + if (IS_ERR(gsc->alloc_ctx)) { + ret = PTR_ERR(gsc->alloc_ctx); + goto err_pm; + } + + dev_dbg(dev, "gsc-%d registered successfully\n", gsc->id); + + pm_runtime_put(dev); + return 0; +err_pm: + pm_runtime_put(dev); +err_m2m: + gsc_unregister_m2m_device(gsc); +err_clk: + gsc_clk_put(gsc); + return ret; +} + +static int __devexit gsc_remove(struct platform_device *pdev) +{ + struct gsc_dev *gsc = platform_get_drvdata(pdev); + + gsc_unregister_m2m_device(gsc); + + vb2_dma_contig_cleanup_ctx(gsc->alloc_ctx); + pm_runtime_disable(&pdev->dev); + + dev_dbg(&pdev->dev, "%s driver unloaded\n", pdev->name); + return 0; +} + +static int gsc_runtime_resume(struct device *dev) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + int ret = 0; + + pr_debug("gsc%d: state: 0x%lx", gsc->id, gsc->state); + + ret = clk_enable(gsc->clock); + if (ret) + return ret; + + gsc_hw_set_sw_reset(gsc); + gsc_wait_reset(gsc); + + return gsc_m2m_resume(gsc); +} + +static int gsc_runtime_suspend(struct device *dev) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + int ret = 0; + + ret = gsc_m2m_suspend(gsc); + if (!ret) + clk_disable(gsc->clock); + + pr_debug("gsc%d: state: 0x%lx", gsc->id, gsc->state); + return ret; +} + +static int gsc_resume(struct device *dev) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + unsigned long flags; + + pr_debug("gsc%d: state: 0x%lx", gsc->id, gsc->state); + + /* Do not resume if the device was idle before system suspend */ + spin_lock_irqsave(&gsc->slock, flags); + if (!test_and_clear_bit(ST_SUSPEND, &gsc->state) || + !gsc_m2m_active(gsc)) { + spin_unlock_irqrestore(&gsc->slock, flags); + return 0; + } + gsc_hw_set_sw_reset(gsc); + gsc_wait_reset(gsc); + + spin_unlock_irqrestore(&gsc->slock, flags); + + return gsc_m2m_resume(gsc); +} + +static int gsc_suspend(struct device *dev) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + + pr_debug("gsc%d: state: 0x%lx", gsc->id, gsc->state); + + if (test_and_set_bit(ST_SUSPEND, &gsc->state)) + return 0; + + return gsc_m2m_suspend(gsc); +} + +static const struct dev_pm_ops gsc_pm_ops = { + .suspend = gsc_suspend, + .resume = gsc_resume, + .runtime_suspend = gsc_runtime_suspend, + .runtime_resume = gsc_runtime_resume, +}; + +static struct platform_driver gsc_driver = { + .probe = gsc_probe, + .remove = __devexit_p(gsc_remove), + .id_table = gsc_driver_ids, + .driver = { + .name = GSC_MODULE_NAME, + .owner = THIS_MODULE, + .pm = &gsc_pm_ops, + .of_match_table = exynos_gsc_match, + } +}; + +module_platform_driver(gsc_driver); + +MODULE_AUTHOR("Hyunwong Kim "); +MODULE_DESCRIPTION("Samsung EXYNOS5 Soc series G-Scaler driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/exynos-gsc/gsc-core.h b/drivers/media/platform/exynos-gsc/gsc-core.h new file mode 100644 index 000000000000..5f157efd24f0 --- /dev/null +++ b/drivers/media/platform/exynos-gsc/gsc-core.h @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * header file for Samsung EXYNOS5 SoC series G-Scaler driver + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef GSC_CORE_H_ +#define GSC_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gsc-regs.h" + +#define CONFIG_VB2_GSC_DMA_CONTIG 1 +#define GSC_MODULE_NAME "exynos-gsc" + +#define GSC_SHUTDOWN_TIMEOUT ((100*HZ)/1000) +#define GSC_MAX_DEVS 4 +#define GSC_M2M_BUF_NUM 0 +#define GSC_MAX_CTRL_NUM 10 +#define GSC_SC_ALIGN_4 4 +#define GSC_SC_ALIGN_2 2 +#define DEFAULT_CSC_EQ 1 +#define DEFAULT_CSC_RANGE 1 + +#define GSC_PARAMS (1 << 0) +#define GSC_SRC_FMT (1 << 1) +#define GSC_DST_FMT (1 << 2) +#define GSC_CTX_M2M (1 << 3) +#define GSC_CTX_STOP_REQ (1 << 6) + +enum gsc_dev_flags { + /* for global */ + ST_SUSPEND, + + /* for m2m node */ + ST_M2M_OPEN, + ST_M2M_RUN, + ST_M2M_PEND, + ST_M2M_SUSPENDED, + ST_M2M_SUSPENDING, +}; + +enum gsc_irq { + GSC_IRQ_DONE, + GSC_IRQ_OVERRUN +}; + +/** + * enum gsc_datapath - the path of data used for G-Scaler + * @GSC_CAMERA: from camera + * @GSC_DMA: from/to DMA + * @GSC_LOCAL: to local path + * @GSC_WRITEBACK: from FIMD + */ +enum gsc_datapath { + GSC_CAMERA = 0x1, + GSC_DMA, + GSC_MIXER, + GSC_FIMD, + GSC_WRITEBACK, +}; + +enum gsc_color_fmt { + GSC_RGB = 0x1, + GSC_YUV420 = 0x2, + GSC_YUV422 = 0x4, + GSC_YUV444 = 0x8, +}; + +enum gsc_yuv_fmt { + GSC_LSB_Y = 0x10, + GSC_LSB_C, + GSC_CBCR = 0x20, + GSC_CRCB, +}; + +#define fh_to_ctx(__fh) container_of(__fh, struct gsc_ctx, fh) +#define is_rgb(x) (!!((x) & 0x1)) +#define is_yuv420(x) (!!((x) & 0x2)) +#define is_yuv422(x) (!!((x) & 0x4)) + +#define gsc_m2m_active(dev) test_bit(ST_M2M_RUN, &(dev)->state) +#define gsc_m2m_pending(dev) test_bit(ST_M2M_PEND, &(dev)->state) +#define gsc_m2m_opened(dev) test_bit(ST_M2M_OPEN, &(dev)->state) + +#define ctrl_to_ctx(__ctrl) \ + container_of((__ctrl)->handler, struct gsc_ctx, ctrl_handler) +/** + * struct gsc_fmt - the driver's internal color format data + * @mbus_code: Media Bus pixel code, -1 if not applicable + * @name: format description + * @pixelformat: the fourcc code for this format, 0 if not applicable + * @yorder: Y/C order + * @corder: Chrominance order control + * @num_planes: number of physically non-contiguous data planes + * @nr_comp: number of physically contiguous data planes + * @depth: per plane driver's private 'number of bits per pixel' + * @flags: flags indicating which operation mode format applies to + */ +struct gsc_fmt { + enum v4l2_mbus_pixelcode mbus_code; + char *name; + u32 pixelformat; + u32 color; + u32 yorder; + u32 corder; + u16 num_planes; + u16 num_comp; + u8 depth[VIDEO_MAX_PLANES]; + u32 flags; +}; + +/** + * struct gsc_input_buf - the driver's video buffer + * @vb: videobuf2 buffer + * @list : linked list structure for buffer queue + * @idx : index of G-Scaler input buffer + */ +struct gsc_input_buf { + struct vb2_buffer vb; + struct list_head list; + int idx; +}; + +/** + * struct gsc_addr - the G-Scaler physical address set + * @y: luminance plane address + * @cb: Cb plane address + * @cr: Cr plane address + */ +struct gsc_addr { + dma_addr_t y; + dma_addr_t cb; + dma_addr_t cr; +}; + +/* struct gsc_ctrls - the G-Scaler control set + * @rotate: rotation degree + * @hflip: horizontal flip + * @vflip: vertical flip + * @global_alpha: the alpha value of current frame + */ +struct gsc_ctrls { + struct v4l2_ctrl *rotate; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *global_alpha; +}; + +/** + * struct gsc_scaler - the configuration data for G-Scaler inetrnal scaler + * @pre_shfactor: pre sclaer shift factor + * @pre_hratio: horizontal ratio of the prescaler + * @pre_vratio: vertical ratio of the prescaler + * @main_hratio: the main scaler's horizontal ratio + * @main_vratio: the main scaler's vertical ratio + */ +struct gsc_scaler { + u32 pre_shfactor; + u32 pre_hratio; + u32 pre_vratio; + u32 main_hratio; + u32 main_vratio; +}; + +struct gsc_dev; + +struct gsc_ctx; + +/** + * struct gsc_frame - source/target frame properties + * @f_width: SRC : SRCIMG_WIDTH, DST : OUTPUTDMA_WHOLE_IMG_WIDTH + * @f_height: SRC : SRCIMG_HEIGHT, DST : OUTPUTDMA_WHOLE_IMG_HEIGHT + * @crop: cropped(source)/scaled(destination) size + * @payload: image size in bytes (w x h x bpp) + * @addr: image frame buffer physical addresses + * @fmt: G-Scaler color format pointer + * @colorspace: value indicating v4l2_colorspace + * @alpha: frame's alpha value + */ +struct gsc_frame { + u32 f_width; + u32 f_height; + struct v4l2_rect crop; + unsigned long payload[VIDEO_MAX_PLANES]; + struct gsc_addr addr; + const struct gsc_fmt *fmt; + u32 colorspace; + u8 alpha; +}; + +/** + * struct gsc_m2m_device - v4l2 memory-to-memory device data + * @vfd: the video device node for v4l2 m2m mode + * @m2m_dev: v4l2 memory-to-memory device data + * @ctx: hardware context data + * @refcnt: the reference counter + */ +struct gsc_m2m_device { + struct video_device *vfd; + struct v4l2_m2m_dev *m2m_dev; + struct gsc_ctx *ctx; + int refcnt; +}; + +/** + * struct gsc_pix_max - image pixel size limits in various IP configurations + * + * @org_scaler_bypass_w: max pixel width when the scaler is disabled + * @org_scaler_bypass_h: max pixel height when the scaler is disabled + * @org_scaler_input_w: max pixel width when the scaler is enabled + * @org_scaler_input_h: max pixel height when the scaler is enabled + * @real_rot_dis_w: max pixel src cropped height with the rotator is off + * @real_rot_dis_h: max pixel src croppped width with the rotator is off + * @real_rot_en_w: max pixel src cropped width with the rotator is on + * @real_rot_en_h: max pixel src cropped height with the rotator is on + * @target_rot_dis_w: max pixel dst scaled width with the rotator is off + * @target_rot_dis_h: max pixel dst scaled height with the rotator is off + * @target_rot_en_w: max pixel dst scaled width with the rotator is on + * @target_rot_en_h: max pixel dst scaled height with the rotator is on + */ +struct gsc_pix_max { + u16 org_scaler_bypass_w; + u16 org_scaler_bypass_h; + u16 org_scaler_input_w; + u16 org_scaler_input_h; + u16 real_rot_dis_w; + u16 real_rot_dis_h; + u16 real_rot_en_w; + u16 real_rot_en_h; + u16 target_rot_dis_w; + u16 target_rot_dis_h; + u16 target_rot_en_w; + u16 target_rot_en_h; +}; + +/** + * struct gsc_pix_min - image pixel size limits in various IP configurations + * + * @org_w: minimum source pixel width + * @org_h: minimum source pixel height + * @real_w: minimum input crop pixel width + * @real_h: minimum input crop pixel height + * @target_rot_dis_w: minimum output scaled pixel height when rotator is off + * @target_rot_dis_h: minimum output scaled pixel height when rotator is off + * @target_rot_en_w: minimum output scaled pixel height when rotator is on + * @target_rot_en_h: minimum output scaled pixel height when rotator is on + */ +struct gsc_pix_min { + u16 org_w; + u16 org_h; + u16 real_w; + u16 real_h; + u16 target_rot_dis_w; + u16 target_rot_dis_h; + u16 target_rot_en_w; + u16 target_rot_en_h; +}; + +struct gsc_pix_align { + u16 org_h; + u16 org_w; + u16 offset_h; + u16 real_w; + u16 real_h; + u16 target_w; + u16 target_h; +}; + +/** + * struct gsc_variant - G-Scaler variant information + */ +struct gsc_variant { + struct gsc_pix_max *pix_max; + struct gsc_pix_min *pix_min; + struct gsc_pix_align *pix_align; + u16 in_buf_cnt; + u16 out_buf_cnt; + u16 sc_up_max; + u16 sc_down_max; + u16 poly_sc_down_max; + u16 pre_sc_down_max; + u16 local_sc_down; +}; + +/** + * struct gsc_driverdata - per device type driver data for init time. + * + * @variant: the variant information for this driver. + * @lclk_frequency: G-Scaler clock frequency + * @num_entities: the number of g-scalers + */ +struct gsc_driverdata { + struct gsc_variant *variant[GSC_MAX_DEVS]; + unsigned long lclk_frequency; + int num_entities; +}; + +/** + * struct gsc_dev - abstraction for G-Scaler entity + * @slock: the spinlock protecting this data structure + * @lock: the mutex protecting this data structure + * @pdev: pointer to the G-Scaler platform device + * @variant: the IP variant information + * @id: G-Scaler device index (0..GSC_MAX_DEVS) + * @clock: clocks required for G-Scaler operation + * @regs: the mapped hardware registers + * @irq_queue: interrupt handler waitqueue + * @m2m: memory-to-memory V4L2 device information + * @state: flags used to synchronize m2m and capture mode operation + * @alloc_ctx: videobuf2 memory allocator context + * @vdev: video device for G-Scaler instance + */ +struct gsc_dev { + spinlock_t slock; + struct mutex lock; + struct platform_device *pdev; + struct gsc_variant *variant; + u16 id; + struct clk *clock; + void __iomem *regs; + wait_queue_head_t irq_queue; + struct gsc_m2m_device m2m; + struct exynos_platform_gscaler *pdata; + unsigned long state; + struct vb2_alloc_ctx *alloc_ctx; + struct video_device vdev; +}; + +/** + * gsc_ctx - the device context data + * @s_frame: source frame properties + * @d_frame: destination frame properties + * @in_path: input mode (DMA or camera) + * @out_path: output mode (DMA or FIFO) + * @scaler: image scaler properties + * @flags: additional flags for image conversion + * @state: flags to keep track of user configuration + * @gsc_dev: the G-Scaler device this context applies to + * @m2m_ctx: memory-to-memory device context + * @fh: v4l2 file handle + * @ctrl_handler: v4l2 controls handler + * @gsc_ctrls G-Scaler control set + * @ctrls_rdy: true if the control handler is initialized + */ +struct gsc_ctx { + struct gsc_frame s_frame; + struct gsc_frame d_frame; + enum gsc_datapath in_path; + enum gsc_datapath out_path; + struct gsc_scaler scaler; + u32 flags; + u32 state; + int rotation; + unsigned int hflip:1; + unsigned int vflip:1; + struct gsc_dev *gsc_dev; + struct v4l2_m2m_ctx *m2m_ctx; + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; + struct gsc_ctrls gsc_ctrls; + bool ctrls_rdy; +}; + +void gsc_set_prefbuf(struct gsc_dev *gsc, struct gsc_frame *frm); +int gsc_register_m2m_device(struct gsc_dev *gsc); +void gsc_unregister_m2m_device(struct gsc_dev *gsc); +void gsc_m2m_job_finish(struct gsc_ctx *ctx, int vb_state); + +u32 get_plane_size(struct gsc_frame *fr, unsigned int plane); +const struct gsc_fmt *get_format(int index); +const struct gsc_fmt *find_fmt(u32 *pixelformat, u32 *mbus_code, u32 index); +int gsc_enum_fmt_mplane(struct v4l2_fmtdesc *f); +int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f); +void gsc_set_frame_size(struct gsc_frame *frame, int width, int height); +int gsc_g_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f); +void gsc_check_crop_change(u32 tmp_w, u32 tmp_h, u32 *w, u32 *h); +int gsc_g_crop(struct gsc_ctx *ctx, struct v4l2_crop *cr); +int gsc_try_crop(struct gsc_ctx *ctx, struct v4l2_crop *cr); +int gsc_cal_prescaler_ratio(struct gsc_variant *var, u32 src, u32 dst, + u32 *ratio); +void gsc_get_prescaler_shfactor(u32 hratio, u32 vratio, u32 *sh); +void gsc_check_src_scale_info(struct gsc_variant *var, + struct gsc_frame *s_frame, + u32 *wratio, u32 tx, u32 ty, u32 *hratio); +int gsc_check_scaler_ratio(struct gsc_variant *var, int sw, int sh, int dw, + int dh, int rot, int out_path); +int gsc_set_scaler_info(struct gsc_ctx *ctx); +int gsc_ctrls_create(struct gsc_ctx *ctx); +void gsc_ctrls_delete(struct gsc_ctx *ctx); +int gsc_prepare_addr(struct gsc_ctx *ctx, struct vb2_buffer *vb, + struct gsc_frame *frame, struct gsc_addr *addr); + +static inline void gsc_ctx_state_lock_set(u32 state, struct gsc_ctx *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->gsc_dev->slock, flags); + ctx->state |= state; + spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags); +} + +static inline void gsc_ctx_state_lock_clear(u32 state, struct gsc_ctx *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->gsc_dev->slock, flags); + ctx->state &= ~state; + spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags); +} + +static inline void gsc_hw_enable_control(struct gsc_dev *dev, bool on) +{ + u32 cfg = readl(dev->regs + GSC_ENABLE); + + if (on) + cfg |= GSC_ENABLE_ON; + else + cfg &= ~GSC_ENABLE_ON; + + writel(cfg, dev->regs + GSC_ENABLE); +} + +static inline int gsc_hw_get_irq_status(struct gsc_dev *dev) +{ + u32 cfg = readl(dev->regs + GSC_IRQ); + if (cfg & GSC_IRQ_STATUS_OR_IRQ) + return GSC_IRQ_OVERRUN; + else + return GSC_IRQ_DONE; + +} + +static inline void gsc_hw_clear_irq(struct gsc_dev *dev, int irq) +{ + u32 cfg = readl(dev->regs + GSC_IRQ); + if (irq == GSC_IRQ_OVERRUN) + cfg |= GSC_IRQ_STATUS_OR_IRQ; + else if (irq == GSC_IRQ_DONE) + cfg |= GSC_IRQ_STATUS_FRM_DONE_IRQ; + writel(cfg, dev->regs + GSC_IRQ); +} + +static inline void gsc_lock(struct vb2_queue *vq) +{ + struct gsc_ctx *ctx = vb2_get_drv_priv(vq); + mutex_lock(&ctx->gsc_dev->lock); +} + +static inline void gsc_unlock(struct vb2_queue *vq) +{ + struct gsc_ctx *ctx = vb2_get_drv_priv(vq); + mutex_unlock(&ctx->gsc_dev->lock); +} + +static inline bool gsc_ctx_state_is_set(u32 mask, struct gsc_ctx *ctx) +{ + unsigned long flags; + bool ret; + + spin_lock_irqsave(&ctx->gsc_dev->slock, flags); + ret = (ctx->state & mask) == mask; + spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags); + return ret; +} + +static inline struct gsc_frame *ctx_get_frame(struct gsc_ctx *ctx, + enum v4l2_buf_type type) +{ + struct gsc_frame *frame; + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE == type) { + frame = &ctx->s_frame; + } else if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == type) { + frame = &ctx->d_frame; + } else { + pr_err("Wrong buffer/video queue type (%d)", type); + return ERR_PTR(-EINVAL); + } + + return frame; +} + +void gsc_hw_set_sw_reset(struct gsc_dev *dev); +int gsc_wait_reset(struct gsc_dev *dev); + +void gsc_hw_set_frm_done_irq_mask(struct gsc_dev *dev, bool mask); +void gsc_hw_set_gsc_irq_enable(struct gsc_dev *dev, bool mask); +void gsc_hw_set_input_buf_masking(struct gsc_dev *dev, u32 shift, bool enable); +void gsc_hw_set_output_buf_masking(struct gsc_dev *dev, u32 shift, bool enable); +void gsc_hw_set_input_addr(struct gsc_dev *dev, struct gsc_addr *addr, + int index); +void gsc_hw_set_output_addr(struct gsc_dev *dev, struct gsc_addr *addr, + int index); +void gsc_hw_set_input_path(struct gsc_ctx *ctx); +void gsc_hw_set_in_size(struct gsc_ctx *ctx); +void gsc_hw_set_in_image_rgb(struct gsc_ctx *ctx); +void gsc_hw_set_in_image_format(struct gsc_ctx *ctx); +void gsc_hw_set_output_path(struct gsc_ctx *ctx); +void gsc_hw_set_out_size(struct gsc_ctx *ctx); +void gsc_hw_set_out_image_rgb(struct gsc_ctx *ctx); +void gsc_hw_set_out_image_format(struct gsc_ctx *ctx); +void gsc_hw_set_prescaler(struct gsc_ctx *ctx); +void gsc_hw_set_mainscaler(struct gsc_ctx *ctx); +void gsc_hw_set_rotation(struct gsc_ctx *ctx); +void gsc_hw_set_global_alpha(struct gsc_ctx *ctx); +void gsc_hw_set_sfr_update(struct gsc_ctx *ctx); + +#endif /* GSC_CORE_H_ */ diff --git a/drivers/media/platform/exynos-gsc/gsc-m2m.c b/drivers/media/platform/exynos-gsc/gsc-m2m.c new file mode 100644 index 000000000000..3c7f00577bd9 --- /dev/null +++ b/drivers/media/platform/exynos-gsc/gsc-m2m.c @@ -0,0 +1,770 @@ +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung EXYNOS5 SoC series G-Scaler driver + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gsc-core.h" + +static int gsc_m2m_ctx_stop_req(struct gsc_ctx *ctx) +{ + struct gsc_ctx *curr_ctx; + struct gsc_dev *gsc = ctx->gsc_dev; + int ret; + + curr_ctx = v4l2_m2m_get_curr_priv(gsc->m2m.m2m_dev); + if (!gsc_m2m_pending(gsc) || (curr_ctx != ctx)) + return 0; + + gsc_ctx_state_lock_set(GSC_CTX_STOP_REQ, ctx); + ret = wait_event_timeout(gsc->irq_queue, + !gsc_ctx_state_is_set(GSC_CTX_STOP_REQ, ctx), + GSC_SHUTDOWN_TIMEOUT); + + return ret == 0 ? -ETIMEDOUT : ret; +} + +static int gsc_m2m_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct gsc_ctx *ctx = q->drv_priv; + int ret; + + ret = pm_runtime_get_sync(&ctx->gsc_dev->pdev->dev); + return ret > 0 ? 0 : ret; +} + +static int gsc_m2m_stop_streaming(struct vb2_queue *q) +{ + struct gsc_ctx *ctx = q->drv_priv; + int ret; + + ret = gsc_m2m_ctx_stop_req(ctx); + if (ret == -ETIMEDOUT) + gsc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR); + + pm_runtime_put(&ctx->gsc_dev->pdev->dev); + + return 0; +} + +void gsc_m2m_job_finish(struct gsc_ctx *ctx, int vb_state) +{ + struct vb2_buffer *src_vb, *dst_vb; + + if (!ctx || !ctx->m2m_ctx) + return; + + src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + + if (src_vb && dst_vb) { + v4l2_m2m_buf_done(src_vb, vb_state); + v4l2_m2m_buf_done(dst_vb, vb_state); + + v4l2_m2m_job_finish(ctx->gsc_dev->m2m.m2m_dev, + ctx->m2m_ctx); + } +} + + +static void gsc_m2m_job_abort(void *priv) +{ + struct gsc_ctx *ctx = priv; + int ret; + + ret = gsc_m2m_ctx_stop_req(ctx); + if (ret == -ETIMEDOUT) + gsc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR); +} + +static int gsc_fill_addr(struct gsc_ctx *ctx) +{ + struct gsc_frame *s_frame, *d_frame; + struct vb2_buffer *vb = NULL; + int ret; + + s_frame = &ctx->s_frame; + d_frame = &ctx->d_frame; + + vb = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + ret = gsc_prepare_addr(ctx, vb, s_frame, &s_frame->addr); + if (ret) + return ret; + + vb = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + return gsc_prepare_addr(ctx, vb, d_frame, &d_frame->addr); +} + +static void gsc_m2m_device_run(void *priv) +{ + struct gsc_ctx *ctx = priv; + struct gsc_dev *gsc; + unsigned long flags; + u32 ret; + bool is_set = false; + + if (WARN(!ctx, "null hardware context\n")) + return; + + gsc = ctx->gsc_dev; + spin_lock_irqsave(&gsc->slock, flags); + + set_bit(ST_M2M_PEND, &gsc->state); + + /* Reconfigure hardware if the context has changed. */ + if (gsc->m2m.ctx != ctx) { + pr_debug("gsc->m2m.ctx = 0x%p, current_ctx = 0x%p", + gsc->m2m.ctx, ctx); + ctx->state |= GSC_PARAMS; + gsc->m2m.ctx = ctx; + } + + is_set = (ctx->state & GSC_CTX_STOP_REQ) ? 1 : 0; + ctx->state &= ~GSC_CTX_STOP_REQ; + if (is_set) { + wake_up(&gsc->irq_queue); + goto put_device; + } + + ret = gsc_fill_addr(ctx); + if (ret) { + pr_err("Wrong address"); + goto put_device; + } + + gsc_set_prefbuf(gsc, &ctx->s_frame); + gsc_hw_set_input_addr(gsc, &ctx->s_frame.addr, GSC_M2M_BUF_NUM); + gsc_hw_set_output_addr(gsc, &ctx->d_frame.addr, GSC_M2M_BUF_NUM); + + if (ctx->state & GSC_PARAMS) { + gsc_hw_set_input_buf_masking(gsc, GSC_M2M_BUF_NUM, false); + gsc_hw_set_output_buf_masking(gsc, GSC_M2M_BUF_NUM, false); + gsc_hw_set_frm_done_irq_mask(gsc, false); + gsc_hw_set_gsc_irq_enable(gsc, true); + + if (gsc_set_scaler_info(ctx)) { + pr_err("Scaler setup error"); + goto put_device; + } + + gsc_hw_set_input_path(ctx); + gsc_hw_set_in_size(ctx); + gsc_hw_set_in_image_format(ctx); + + gsc_hw_set_output_path(ctx); + gsc_hw_set_out_size(ctx); + gsc_hw_set_out_image_format(ctx); + + gsc_hw_set_prescaler(ctx); + gsc_hw_set_mainscaler(ctx); + gsc_hw_set_rotation(ctx); + gsc_hw_set_global_alpha(ctx); + } + + /* update shadow registers */ + gsc_hw_set_sfr_update(ctx); + + ctx->state &= ~GSC_PARAMS; + gsc_hw_enable_control(gsc, true); + + spin_unlock_irqrestore(&gsc->slock, flags); + return; + +put_device: + ctx->state &= ~GSC_PARAMS; + spin_unlock_irqrestore(&gsc->slock, flags); +} + +static int gsc_m2m_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], void *allocators[]) +{ + struct gsc_ctx *ctx = vb2_get_drv_priv(vq); + struct gsc_frame *frame; + int i; + + frame = ctx_get_frame(ctx, vq->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + if (!frame->fmt) + return -EINVAL; + + *num_planes = frame->fmt->num_planes; + for (i = 0; i < frame->fmt->num_planes; i++) { + sizes[i] = frame->payload[i]; + allocators[i] = ctx->gsc_dev->alloc_ctx; + } + return 0; +} + +static int gsc_m2m_buf_prepare(struct vb2_buffer *vb) +{ + struct gsc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct gsc_frame *frame; + int i; + + frame = ctx_get_frame(ctx, vb->vb2_queue->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + if (!V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + for (i = 0; i < frame->fmt->num_planes; i++) + vb2_set_plane_payload(vb, i, frame->payload[i]); + } + + return 0; +} + +static void gsc_m2m_buf_queue(struct vb2_buffer *vb) +{ + struct gsc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + pr_debug("ctx: %p, ctx->state: 0x%x", ctx, ctx->state); + + if (ctx->m2m_ctx) + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); +} + +static struct vb2_ops gsc_m2m_qops = { + .queue_setup = gsc_m2m_queue_setup, + .buf_prepare = gsc_m2m_buf_prepare, + .buf_queue = gsc_m2m_buf_queue, + .wait_prepare = gsc_unlock, + .wait_finish = gsc_lock, + .stop_streaming = gsc_m2m_stop_streaming, + .start_streaming = gsc_m2m_start_streaming, +}; + +static int gsc_m2m_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + struct gsc_dev *gsc = ctx->gsc_dev; + + strlcpy(cap->driver, gsc->pdev->name, sizeof(cap->driver)); + strlcpy(cap->card, gsc->pdev->name, sizeof(cap->card)); + strlcpy(cap->bus_info, "platform", sizeof(cap->bus_info)); + cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE | + V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE; + + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int gsc_m2m_enum_fmt_mplane(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return gsc_enum_fmt_mplane(f); +} + +static int gsc_m2m_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + + return gsc_g_fmt_mplane(ctx, f); +} + +static int gsc_m2m_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + + return gsc_try_fmt_mplane(ctx, f); +} + +static int gsc_m2m_s_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + struct vb2_queue *vq; + struct gsc_frame *frame; + struct v4l2_pix_format_mplane *pix; + int i, ret = 0; + + ret = gsc_m2m_try_fmt_mplane(file, fh, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + + if (vb2_is_streaming(vq)) { + pr_err("queue (%d) busy", f->type); + return -EBUSY; + } + + if (V4L2_TYPE_IS_OUTPUT(f->type)) + frame = &ctx->s_frame; + else + frame = &ctx->d_frame; + + pix = &f->fmt.pix_mp; + frame->fmt = find_fmt(&pix->pixelformat, NULL, 0); + frame->colorspace = pix->colorspace; + if (!frame->fmt) + return -EINVAL; + + for (i = 0; i < frame->fmt->num_planes; i++) + frame->payload[i] = pix->plane_fmt[i].sizeimage; + + gsc_set_frame_size(frame, pix->width, pix->height); + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + gsc_ctx_state_lock_set(GSC_PARAMS | GSC_DST_FMT, ctx); + else + gsc_ctx_state_lock_set(GSC_PARAMS | GSC_SRC_FMT, ctx); + + pr_debug("f_w: %d, f_h: %d", frame->f_width, frame->f_height); + + return 0; +} + +static int gsc_m2m_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *reqbufs) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + struct gsc_dev *gsc = ctx->gsc_dev; + struct gsc_frame *frame; + u32 max_cnt; + + max_cnt = (reqbufs->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ? + gsc->variant->in_buf_cnt : gsc->variant->out_buf_cnt; + if (reqbufs->count > max_cnt) { + return -EINVAL; + } else if (reqbufs->count == 0) { + if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + gsc_ctx_state_lock_clear(GSC_SRC_FMT, ctx); + else + gsc_ctx_state_lock_clear(GSC_DST_FMT, ctx); + } + + frame = ctx_get_frame(ctx, reqbufs->type); + + return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); +} + +static int gsc_m2m_querybuf(struct file *file, void *fh, + struct v4l2_buffer *buf) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); +} + +static int gsc_m2m_qbuf(struct file *file, void *fh, + struct v4l2_buffer *buf) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int gsc_m2m_dqbuf(struct file *file, void *fh, + struct v4l2_buffer *buf) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); +} + +static int gsc_m2m_streamon(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + + /* The source and target color format need to be set */ + if (V4L2_TYPE_IS_OUTPUT(type)) { + if (!gsc_ctx_state_is_set(GSC_SRC_FMT, ctx)) + return -EINVAL; + } else if (!gsc_ctx_state_is_set(GSC_DST_FMT, ctx)) { + return -EINVAL; + } + + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); +} + +static int gsc_m2m_streamoff(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); +} + +/* Return 1 if rectangle a is enclosed in rectangle b, or 0 otherwise. */ +static int is_rectangle_enclosed(struct v4l2_rect *a, struct v4l2_rect *b) +{ + if (a->left < b->left || a->top < b->top) + return 0; + + if (a->left + a->width > b->left + b->width) + return 0; + + if (a->top + a->height > b->top + b->height) + return 0; + + return 1; +} + +static int gsc_m2m_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct gsc_frame *frame; + struct gsc_ctx *ctx = fh_to_ctx(fh); + + if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && + (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) + return -EINVAL; + + frame = ctx_get_frame(ctx, s->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->f_width; + s->r.height = frame->f_height; + return 0; + + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_CROP: + s->r.left = frame->crop.left; + s->r.top = frame->crop.top; + s->r.width = frame->crop.width; + s->r.height = frame->crop.height; + return 0; + } + + return -EINVAL; +} + +static int gsc_m2m_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct gsc_frame *frame; + struct gsc_ctx *ctx = fh_to_ctx(fh); + struct v4l2_crop cr; + struct gsc_variant *variant = ctx->gsc_dev->variant; + int ret; + + cr.type = s->type; + cr.c = s->r; + + if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && + (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) + return -EINVAL; + + ret = gsc_try_crop(ctx, &cr); + if (ret) + return ret; + + if (s->flags & V4L2_SEL_FLAG_LE && + !is_rectangle_enclosed(&cr.c, &s->r)) + return -ERANGE; + + if (s->flags & V4L2_SEL_FLAG_GE && + !is_rectangle_enclosed(&s->r, &cr.c)) + return -ERANGE; + + s->r = cr.c; + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE: + frame = &ctx->s_frame; + break; + + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + frame = &ctx->d_frame; + break; + + default: + return -EINVAL; + } + + /* Check to see if scaling ratio is within supported range */ + if (gsc_ctx_state_is_set(GSC_DST_FMT | GSC_SRC_FMT, ctx)) { + if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = gsc_check_scaler_ratio(variant, cr.c.width, + cr.c.height, ctx->d_frame.crop.width, + ctx->d_frame.crop.height, + ctx->gsc_ctrls.rotate->val, ctx->out_path); + } else { + ret = gsc_check_scaler_ratio(variant, + ctx->s_frame.crop.width, + ctx->s_frame.crop.height, cr.c.width, + cr.c.height, ctx->gsc_ctrls.rotate->val, + ctx->out_path); + } + + if (ret) { + pr_err("Out of scaler range"); + return -EINVAL; + } + } + + frame->crop = cr.c; + + gsc_ctx_state_lock_set(GSC_PARAMS, ctx); + return 0; +} + +static const struct v4l2_ioctl_ops gsc_m2m_ioctl_ops = { + .vidioc_querycap = gsc_m2m_querycap, + .vidioc_enum_fmt_vid_cap_mplane = gsc_m2m_enum_fmt_mplane, + .vidioc_enum_fmt_vid_out_mplane = gsc_m2m_enum_fmt_mplane, + .vidioc_g_fmt_vid_cap_mplane = gsc_m2m_g_fmt_mplane, + .vidioc_g_fmt_vid_out_mplane = gsc_m2m_g_fmt_mplane, + .vidioc_try_fmt_vid_cap_mplane = gsc_m2m_try_fmt_mplane, + .vidioc_try_fmt_vid_out_mplane = gsc_m2m_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = gsc_m2m_s_fmt_mplane, + .vidioc_s_fmt_vid_out_mplane = gsc_m2m_s_fmt_mplane, + .vidioc_reqbufs = gsc_m2m_reqbufs, + .vidioc_querybuf = gsc_m2m_querybuf, + .vidioc_qbuf = gsc_m2m_qbuf, + .vidioc_dqbuf = gsc_m2m_dqbuf, + .vidioc_streamon = gsc_m2m_streamon, + .vidioc_streamoff = gsc_m2m_streamoff, + .vidioc_g_selection = gsc_m2m_g_selection, + .vidioc_s_selection = gsc_m2m_s_selection +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct gsc_ctx *ctx = priv; + int ret; + + memset(src_vq, 0, sizeof(*src_vq)); + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR; + src_vq->drv_priv = ctx; + src_vq->ops = &gsc_m2m_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + memset(dst_vq, 0, sizeof(*dst_vq)); + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; + dst_vq->drv_priv = ctx; + dst_vq->ops = &gsc_m2m_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + + return vb2_queue_init(dst_vq); +} + +static int gsc_m2m_open(struct file *file) +{ + struct gsc_dev *gsc = video_drvdata(file); + struct gsc_ctx *ctx = NULL; + int ret; + + pr_debug("pid: %d, state: 0x%lx", task_pid_nr(current), gsc->state); + + if (mutex_lock_interruptible(&gsc->lock)) + return -ERESTARTSYS; + + ctx = kzalloc(sizeof (*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto unlock; + } + + v4l2_fh_init(&ctx->fh, gsc->m2m.vfd); + ret = gsc_ctrls_create(ctx); + if (ret) + goto error_fh; + + /* Use separate control handler per file handle */ + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ctx->gsc_dev = gsc; + /* Default color format */ + ctx->s_frame.fmt = get_format(0); + ctx->d_frame.fmt = get_format(0); + /* Setup the device context for mem2mem mode. */ + ctx->state = GSC_CTX_M2M; + ctx->flags = 0; + ctx->in_path = GSC_DMA; + ctx->out_path = GSC_DMA; + + ctx->m2m_ctx = v4l2_m2m_ctx_init(gsc->m2m.m2m_dev, ctx, queue_init); + if (IS_ERR(ctx->m2m_ctx)) { + pr_err("Failed to initialize m2m context"); + ret = PTR_ERR(ctx->m2m_ctx); + goto error_ctrls; + } + + if (gsc->m2m.refcnt++ == 0) + set_bit(ST_M2M_OPEN, &gsc->state); + + pr_debug("gsc m2m driver is opened, ctx(0x%p)", ctx); + + mutex_unlock(&gsc->lock); + return 0; + +error_ctrls: + gsc_ctrls_delete(ctx); +error_fh: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); +unlock: + mutex_unlock(&gsc->lock); + return ret; +} + +static int gsc_m2m_release(struct file *file) +{ + struct gsc_ctx *ctx = fh_to_ctx(file->private_data); + struct gsc_dev *gsc = ctx->gsc_dev; + + pr_debug("pid: %d, state: 0x%lx, refcnt= %d", + task_pid_nr(current), gsc->state, gsc->m2m.refcnt); + + if (mutex_lock_interruptible(&gsc->lock)) + return -ERESTARTSYS; + + v4l2_m2m_ctx_release(ctx->m2m_ctx); + gsc_ctrls_delete(ctx); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + if (--gsc->m2m.refcnt <= 0) + clear_bit(ST_M2M_OPEN, &gsc->state); + kfree(ctx); + + mutex_unlock(&gsc->lock); + return 0; +} + +static unsigned int gsc_m2m_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct gsc_ctx *ctx = fh_to_ctx(file->private_data); + struct gsc_dev *gsc = ctx->gsc_dev; + int ret; + + if (mutex_lock_interruptible(&gsc->lock)) + return -ERESTARTSYS; + + ret = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); + mutex_unlock(&gsc->lock); + + return ret; +} + +static int gsc_m2m_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct gsc_ctx *ctx = fh_to_ctx(file->private_data); + struct gsc_dev *gsc = ctx->gsc_dev; + int ret; + + if (mutex_lock_interruptible(&gsc->lock)) + return -ERESTARTSYS; + + ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); + mutex_unlock(&gsc->lock); + + return ret; +} + +static const struct v4l2_file_operations gsc_m2m_fops = { + .owner = THIS_MODULE, + .open = gsc_m2m_open, + .release = gsc_m2m_release, + .poll = gsc_m2m_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = gsc_m2m_mmap, +}; + +static struct v4l2_m2m_ops gsc_m2m_ops = { + .device_run = gsc_m2m_device_run, + .job_abort = gsc_m2m_job_abort, +}; + +int gsc_register_m2m_device(struct gsc_dev *gsc) +{ + struct platform_device *pdev; + int ret; + + if (!gsc) + return -ENODEV; + + pdev = gsc->pdev; + + gsc->vdev.fops = &gsc_m2m_fops; + gsc->vdev.ioctl_ops = &gsc_m2m_ioctl_ops; + gsc->vdev.release = video_device_release_empty; + gsc->vdev.lock = &gsc->lock; + snprintf(gsc->vdev.name, sizeof(gsc->vdev.name), "%s.%d:m2m", + GSC_MODULE_NAME, gsc->id); + + video_set_drvdata(&gsc->vdev, gsc); + + gsc->m2m.vfd = &gsc->vdev; + gsc->m2m.m2m_dev = v4l2_m2m_init(&gsc_m2m_ops); + if (IS_ERR(gsc->m2m.m2m_dev)) { + dev_err(&pdev->dev, "failed to initialize v4l2-m2m device\n"); + ret = PTR_ERR(gsc->m2m.m2m_dev); + goto err_m2m_r1; + } + + ret = video_register_device(&gsc->vdev, VFL_TYPE_GRABBER, -1); + if (ret) { + dev_err(&pdev->dev, + "%s(): failed to register video device\n", __func__); + goto err_m2m_r2; + } + + pr_debug("gsc m2m driver registered as /dev/video%d", gsc->vdev.num); + return 0; + +err_m2m_r2: + v4l2_m2m_release(gsc->m2m.m2m_dev); +err_m2m_r1: + video_device_release(gsc->m2m.vfd); + + return ret; +} + +void gsc_unregister_m2m_device(struct gsc_dev *gsc) +{ + if (gsc) + v4l2_m2m_release(gsc->m2m.m2m_dev); +} diff --git a/drivers/media/platform/exynos-gsc/gsc-regs.c b/drivers/media/platform/exynos-gsc/gsc-regs.c new file mode 100644 index 000000000000..0d8625f03a32 --- /dev/null +++ b/drivers/media/platform/exynos-gsc/gsc-regs.c @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung EXYNOS5 SoC series G-Scaler driver + * + * 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. + */ + +#include +#include +#include + +#include "gsc-core.h" + +void gsc_hw_set_sw_reset(struct gsc_dev *dev) +{ + writel(GSC_SW_RESET_SRESET, dev->regs + GSC_SW_RESET); +} + +int gsc_wait_reset(struct gsc_dev *dev) +{ + unsigned long end = jiffies + msecs_to_jiffies(50); + u32 cfg; + + while (time_before(jiffies, end)) { + cfg = readl(dev->regs + GSC_SW_RESET); + if (!cfg) + return 0; + usleep_range(10, 20); + } + + return -EBUSY; +} + +void gsc_hw_set_frm_done_irq_mask(struct gsc_dev *dev, bool mask) +{ + u32 cfg; + + cfg = readl(dev->regs + GSC_IRQ); + if (mask) + cfg |= GSC_IRQ_FRMDONE_MASK; + else + cfg &= ~GSC_IRQ_FRMDONE_MASK; + writel(cfg, dev->regs + GSC_IRQ); +} + +void gsc_hw_set_gsc_irq_enable(struct gsc_dev *dev, bool mask) +{ + u32 cfg; + + cfg = readl(dev->regs + GSC_IRQ); + if (mask) + cfg |= GSC_IRQ_ENABLE; + else + cfg &= ~GSC_IRQ_ENABLE; + writel(cfg, dev->regs + GSC_IRQ); +} + +void gsc_hw_set_input_buf_masking(struct gsc_dev *dev, u32 shift, + bool enable) +{ + u32 cfg = readl(dev->regs + GSC_IN_BASE_ADDR_Y_MASK); + u32 mask = 1 << shift; + + cfg &= ~mask; + cfg |= enable << shift; + + writel(cfg, dev->regs + GSC_IN_BASE_ADDR_Y_MASK); + writel(cfg, dev->regs + GSC_IN_BASE_ADDR_CB_MASK); + writel(cfg, dev->regs + GSC_IN_BASE_ADDR_CR_MASK); +} + +void gsc_hw_set_output_buf_masking(struct gsc_dev *dev, u32 shift, + bool enable) +{ + u32 cfg = readl(dev->regs + GSC_OUT_BASE_ADDR_Y_MASK); + u32 mask = 1 << shift; + + cfg &= ~mask; + cfg |= enable << shift; + + writel(cfg, dev->regs + GSC_OUT_BASE_ADDR_Y_MASK); + writel(cfg, dev->regs + GSC_OUT_BASE_ADDR_CB_MASK); + writel(cfg, dev->regs + GSC_OUT_BASE_ADDR_CR_MASK); +} + +void gsc_hw_set_input_addr(struct gsc_dev *dev, struct gsc_addr *addr, + int index) +{ + pr_debug("src_buf[%d]: 0x%X, cb: 0x%X, cr: 0x%X", index, + addr->y, addr->cb, addr->cr); + writel(addr->y, dev->regs + GSC_IN_BASE_ADDR_Y(index)); + writel(addr->cb, dev->regs + GSC_IN_BASE_ADDR_CB(index)); + writel(addr->cr, dev->regs + GSC_IN_BASE_ADDR_CR(index)); + +} + +void gsc_hw_set_output_addr(struct gsc_dev *dev, + struct gsc_addr *addr, int index) +{ + pr_debug("dst_buf[%d]: 0x%X, cb: 0x%X, cr: 0x%X", + index, addr->y, addr->cb, addr->cr); + writel(addr->y, dev->regs + GSC_OUT_BASE_ADDR_Y(index)); + writel(addr->cb, dev->regs + GSC_OUT_BASE_ADDR_CB(index)); + writel(addr->cr, dev->regs + GSC_OUT_BASE_ADDR_CR(index)); +} + +void gsc_hw_set_input_path(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + + u32 cfg = readl(dev->regs + GSC_IN_CON); + cfg &= ~(GSC_IN_PATH_MASK | GSC_IN_LOCAL_SEL_MASK); + + if (ctx->in_path == GSC_DMA) + cfg |= GSC_IN_PATH_MEMORY; + + writel(cfg, dev->regs + GSC_IN_CON); +} + +void gsc_hw_set_in_size(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->s_frame; + u32 cfg; + + /* Set input pixel offset */ + cfg = GSC_SRCIMG_OFFSET_X(frame->crop.left); + cfg |= GSC_SRCIMG_OFFSET_Y(frame->crop.top); + writel(cfg, dev->regs + GSC_SRCIMG_OFFSET); + + /* Set input original size */ + cfg = GSC_SRCIMG_WIDTH(frame->f_width); + cfg |= GSC_SRCIMG_HEIGHT(frame->f_height); + writel(cfg, dev->regs + GSC_SRCIMG_SIZE); + + /* Set input cropped size */ + cfg = GSC_CROPPED_WIDTH(frame->crop.width); + cfg |= GSC_CROPPED_HEIGHT(frame->crop.height); + writel(cfg, dev->regs + GSC_CROPPED_SIZE); +} + +void gsc_hw_set_in_image_rgb(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->s_frame; + u32 cfg; + + cfg = readl(dev->regs + GSC_IN_CON); + if (frame->colorspace == V4L2_COLORSPACE_REC709) + cfg |= GSC_IN_RGB_HD_WIDE; + else + cfg |= GSC_IN_RGB_SD_WIDE; + + if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB565X) + cfg |= GSC_IN_RGB565; + else if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB32) + cfg |= GSC_IN_XRGB8888; + + writel(cfg, dev->regs + GSC_IN_CON); +} + +void gsc_hw_set_in_image_format(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->s_frame; + u32 i, depth = 0; + u32 cfg; + + cfg = readl(dev->regs + GSC_IN_CON); + cfg &= ~(GSC_IN_RGB_TYPE_MASK | GSC_IN_YUV422_1P_ORDER_MASK | + GSC_IN_CHROMA_ORDER_MASK | GSC_IN_FORMAT_MASK | + GSC_IN_TILE_TYPE_MASK | GSC_IN_TILE_MODE); + writel(cfg, dev->regs + GSC_IN_CON); + + if (is_rgb(frame->fmt->color)) { + gsc_hw_set_in_image_rgb(ctx); + return; + } + for (i = 0; i < frame->fmt->num_planes; i++) + depth += frame->fmt->depth[i]; + + switch (frame->fmt->num_comp) { + case 1: + cfg |= GSC_IN_YUV422_1P; + if (frame->fmt->yorder == GSC_LSB_Y) + cfg |= GSC_IN_YUV422_1P_ORDER_LSB_Y; + else + cfg |= GSC_IN_YUV422_1P_OEDER_LSB_C; + if (frame->fmt->corder == GSC_CBCR) + cfg |= GSC_IN_CHROMA_ORDER_CBCR; + else + cfg |= GSC_IN_CHROMA_ORDER_CRCB; + break; + case 2: + if (depth == 12) + cfg |= GSC_IN_YUV420_2P; + else + cfg |= GSC_IN_YUV422_2P; + if (frame->fmt->corder == GSC_CBCR) + cfg |= GSC_IN_CHROMA_ORDER_CBCR; + else + cfg |= GSC_IN_CHROMA_ORDER_CRCB; + break; + case 3: + if (depth == 12) + cfg |= GSC_IN_YUV420_3P; + else + cfg |= GSC_IN_YUV422_3P; + break; + }; + + writel(cfg, dev->regs + GSC_IN_CON); +} + +void gsc_hw_set_output_path(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + + u32 cfg = readl(dev->regs + GSC_OUT_CON); + cfg &= ~GSC_OUT_PATH_MASK; + + if (ctx->out_path == GSC_DMA) + cfg |= GSC_OUT_PATH_MEMORY; + else + cfg |= GSC_OUT_PATH_LOCAL; + + writel(cfg, dev->regs + GSC_OUT_CON); +} + +void gsc_hw_set_out_size(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->d_frame; + u32 cfg; + + /* Set output original size */ + if (ctx->out_path == GSC_DMA) { + cfg = GSC_DSTIMG_OFFSET_X(frame->crop.left); + cfg |= GSC_DSTIMG_OFFSET_Y(frame->crop.top); + writel(cfg, dev->regs + GSC_DSTIMG_OFFSET); + + cfg = GSC_DSTIMG_WIDTH(frame->f_width); + cfg |= GSC_DSTIMG_HEIGHT(frame->f_height); + writel(cfg, dev->regs + GSC_DSTIMG_SIZE); + } + + /* Set output scaled size */ + if (ctx->gsc_ctrls.rotate->val == 90 || + ctx->gsc_ctrls.rotate->val == 270) { + cfg = GSC_SCALED_WIDTH(frame->crop.height); + cfg |= GSC_SCALED_HEIGHT(frame->crop.width); + } else { + cfg = GSC_SCALED_WIDTH(frame->crop.width); + cfg |= GSC_SCALED_HEIGHT(frame->crop.height); + } + writel(cfg, dev->regs + GSC_SCALED_SIZE); +} + +void gsc_hw_set_out_image_rgb(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->d_frame; + u32 cfg; + + cfg = readl(dev->regs + GSC_OUT_CON); + if (frame->colorspace == V4L2_COLORSPACE_REC709) + cfg |= GSC_OUT_RGB_HD_WIDE; + else + cfg |= GSC_OUT_RGB_SD_WIDE; + + if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB565X) + cfg |= GSC_OUT_RGB565; + else if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB32) + cfg |= GSC_OUT_XRGB8888; + + writel(cfg, dev->regs + GSC_OUT_CON); +} + +void gsc_hw_set_out_image_format(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->d_frame; + u32 i, depth = 0; + u32 cfg; + + cfg = readl(dev->regs + GSC_OUT_CON); + cfg &= ~(GSC_OUT_RGB_TYPE_MASK | GSC_OUT_YUV422_1P_ORDER_MASK | + GSC_OUT_CHROMA_ORDER_MASK | GSC_OUT_FORMAT_MASK | + GSC_OUT_TILE_TYPE_MASK | GSC_OUT_TILE_MODE); + writel(cfg, dev->regs + GSC_OUT_CON); + + if (is_rgb(frame->fmt->color)) { + gsc_hw_set_out_image_rgb(ctx); + return; + } + + if (ctx->out_path != GSC_DMA) { + cfg |= GSC_OUT_YUV444; + goto end_set; + } + + for (i = 0; i < frame->fmt->num_planes; i++) + depth += frame->fmt->depth[i]; + + switch (frame->fmt->num_comp) { + case 1: + cfg |= GSC_OUT_YUV422_1P; + if (frame->fmt->yorder == GSC_LSB_Y) + cfg |= GSC_OUT_YUV422_1P_ORDER_LSB_Y; + else + cfg |= GSC_OUT_YUV422_1P_OEDER_LSB_C; + if (frame->fmt->corder == GSC_CBCR) + cfg |= GSC_OUT_CHROMA_ORDER_CBCR; + else + cfg |= GSC_OUT_CHROMA_ORDER_CRCB; + break; + case 2: + if (depth == 12) + cfg |= GSC_OUT_YUV420_2P; + else + cfg |= GSC_OUT_YUV422_2P; + if (frame->fmt->corder == GSC_CBCR) + cfg |= GSC_OUT_CHROMA_ORDER_CBCR; + else + cfg |= GSC_OUT_CHROMA_ORDER_CRCB; + break; + case 3: + cfg |= GSC_OUT_YUV420_3P; + break; + }; + +end_set: + writel(cfg, dev->regs + GSC_OUT_CON); +} + +void gsc_hw_set_prescaler(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_scaler *sc = &ctx->scaler; + u32 cfg; + + cfg = GSC_PRESC_SHFACTOR(sc->pre_shfactor); + cfg |= GSC_PRESC_H_RATIO(sc->pre_hratio); + cfg |= GSC_PRESC_V_RATIO(sc->pre_vratio); + writel(cfg, dev->regs + GSC_PRE_SCALE_RATIO); +} + +void gsc_hw_set_mainscaler(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_scaler *sc = &ctx->scaler; + u32 cfg; + + cfg = GSC_MAIN_H_RATIO_VALUE(sc->main_hratio); + writel(cfg, dev->regs + GSC_MAIN_H_RATIO); + + cfg = GSC_MAIN_V_RATIO_VALUE(sc->main_vratio); + writel(cfg, dev->regs + GSC_MAIN_V_RATIO); +} + +void gsc_hw_set_rotation(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + u32 cfg; + + cfg = readl(dev->regs + GSC_IN_CON); + cfg &= ~GSC_IN_ROT_MASK; + + switch (ctx->gsc_ctrls.rotate->val) { + case 270: + cfg |= GSC_IN_ROT_270; + break; + case 180: + cfg |= GSC_IN_ROT_180; + break; + case 90: + if (ctx->gsc_ctrls.hflip->val) + cfg |= GSC_IN_ROT_90_XFLIP; + else if (ctx->gsc_ctrls.vflip->val) + cfg |= GSC_IN_ROT_90_YFLIP; + else + cfg |= GSC_IN_ROT_90; + break; + case 0: + if (ctx->gsc_ctrls.hflip->val) + cfg |= GSC_IN_ROT_XFLIP; + else if (ctx->gsc_ctrls.vflip->val) + cfg |= GSC_IN_ROT_YFLIP; + } + + writel(cfg, dev->regs + GSC_IN_CON); +} + +void gsc_hw_set_global_alpha(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->d_frame; + u32 cfg; + + if (!is_rgb(frame->fmt->color)) { + pr_debug("Not a RGB format"); + return; + } + + cfg = readl(dev->regs + GSC_OUT_CON); + cfg &= ~GSC_OUT_GLOBAL_ALPHA_MASK; + + cfg |= GSC_OUT_GLOBAL_ALPHA(ctx->gsc_ctrls.global_alpha->val); + writel(cfg, dev->regs + GSC_OUT_CON); +} + +void gsc_hw_set_sfr_update(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + u32 cfg; + + cfg = readl(dev->regs + GSC_ENABLE); + cfg |= GSC_ENABLE_SFR_UPDATE; + writel(cfg, dev->regs + GSC_ENABLE); +} diff --git a/drivers/media/platform/exynos-gsc/gsc-regs.h b/drivers/media/platform/exynos-gsc/gsc-regs.h new file mode 100644 index 000000000000..533e9947a925 --- /dev/null +++ b/drivers/media/platform/exynos-gsc/gsc-regs.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Register definition file for Samsung G-Scaler driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef REGS_GSC_H_ +#define REGS_GSC_H_ + +/* G-Scaler enable */ +#define GSC_ENABLE 0x00 +#define GSC_ENABLE_OP_STATUS (1 << 2) +#define GSC_ENABLE_SFR_UPDATE (1 << 1) +#define GSC_ENABLE_ON (1 << 0) + +/* G-Scaler S/W reset */ +#define GSC_SW_RESET 0x04 +#define GSC_SW_RESET_SRESET (1 << 0) + +/* G-Scaler IRQ */ +#define GSC_IRQ 0x08 +#define GSC_IRQ_STATUS_OR_IRQ (1 << 17) +#define GSC_IRQ_STATUS_FRM_DONE_IRQ (1 << 16) +#define GSC_IRQ_FRMDONE_MASK (1 << 1) +#define GSC_IRQ_ENABLE (1 << 0) + +/* G-Scaler input control */ +#define GSC_IN_CON 0x10 +#define GSC_IN_ROT_MASK (7 << 16) +#define GSC_IN_ROT_270 (7 << 16) +#define GSC_IN_ROT_90_YFLIP (6 << 16) +#define GSC_IN_ROT_90_XFLIP (5 << 16) +#define GSC_IN_ROT_90 (4 << 16) +#define GSC_IN_ROT_180 (3 << 16) +#define GSC_IN_ROT_YFLIP (2 << 16) +#define GSC_IN_ROT_XFLIP (1 << 16) +#define GSC_IN_RGB_TYPE_MASK (3 << 14) +#define GSC_IN_RGB_HD_WIDE (3 << 14) +#define GSC_IN_RGB_HD_NARROW (2 << 14) +#define GSC_IN_RGB_SD_WIDE (1 << 14) +#define GSC_IN_RGB_SD_NARROW (0 << 14) +#define GSC_IN_YUV422_1P_ORDER_MASK (1 << 13) +#define GSC_IN_YUV422_1P_ORDER_LSB_Y (0 << 13) +#define GSC_IN_YUV422_1P_OEDER_LSB_C (1 << 13) +#define GSC_IN_CHROMA_ORDER_MASK (1 << 12) +#define GSC_IN_CHROMA_ORDER_CBCR (0 << 12) +#define GSC_IN_CHROMA_ORDER_CRCB (1 << 12) +#define GSC_IN_FORMAT_MASK (7 << 8) +#define GSC_IN_XRGB8888 (0 << 8) +#define GSC_IN_RGB565 (1 << 8) +#define GSC_IN_YUV420_2P (2 << 8) +#define GSC_IN_YUV420_3P (3 << 8) +#define GSC_IN_YUV422_1P (4 << 8) +#define GSC_IN_YUV422_2P (5 << 8) +#define GSC_IN_YUV422_3P (6 << 8) +#define GSC_IN_TILE_TYPE_MASK (1 << 4) +#define GSC_IN_TILE_C_16x8 (0 << 4) +#define GSC_IN_TILE_MODE (1 << 3) +#define GSC_IN_LOCAL_SEL_MASK (3 << 1) +#define GSC_IN_PATH_MASK (1 << 0) +#define GSC_IN_PATH_MEMORY (0 << 0) + +/* G-Scaler source image size */ +#define GSC_SRCIMG_SIZE 0x14 +#define GSC_SRCIMG_HEIGHT(x) ((x) << 16) +#define GSC_SRCIMG_WIDTH(x) ((x) << 0) + +/* G-Scaler source image offset */ +#define GSC_SRCIMG_OFFSET 0x18 +#define GSC_SRCIMG_OFFSET_Y(x) ((x) << 16) +#define GSC_SRCIMG_OFFSET_X(x) ((x) << 0) + +/* G-Scaler cropped source image size */ +#define GSC_CROPPED_SIZE 0x1c +#define GSC_CROPPED_HEIGHT(x) ((x) << 16) +#define GSC_CROPPED_WIDTH(x) ((x) << 0) + +/* G-Scaler output control */ +#define GSC_OUT_CON 0x20 +#define GSC_OUT_GLOBAL_ALPHA_MASK (0xff << 24) +#define GSC_OUT_GLOBAL_ALPHA(x) ((x) << 24) +#define GSC_OUT_RGB_TYPE_MASK (3 << 10) +#define GSC_OUT_RGB_HD_NARROW (3 << 10) +#define GSC_OUT_RGB_HD_WIDE (2 << 10) +#define GSC_OUT_RGB_SD_NARROW (1 << 10) +#define GSC_OUT_RGB_SD_WIDE (0 << 10) +#define GSC_OUT_YUV422_1P_ORDER_MASK (1 << 9) +#define GSC_OUT_YUV422_1P_ORDER_LSB_Y (0 << 9) +#define GSC_OUT_YUV422_1P_OEDER_LSB_C (1 << 9) +#define GSC_OUT_CHROMA_ORDER_MASK (1 << 8) +#define GSC_OUT_CHROMA_ORDER_CBCR (0 << 8) +#define GSC_OUT_CHROMA_ORDER_CRCB (1 << 8) +#define GSC_OUT_FORMAT_MASK (7 << 4) +#define GSC_OUT_XRGB8888 (0 << 4) +#define GSC_OUT_RGB565 (1 << 4) +#define GSC_OUT_YUV420_2P (2 << 4) +#define GSC_OUT_YUV420_3P (3 << 4) +#define GSC_OUT_YUV422_1P (4 << 4) +#define GSC_OUT_YUV422_2P (5 << 4) +#define GSC_OUT_YUV444 (7 << 4) +#define GSC_OUT_TILE_TYPE_MASK (1 << 2) +#define GSC_OUT_TILE_C_16x8 (0 << 2) +#define GSC_OUT_TILE_MODE (1 << 1) +#define GSC_OUT_PATH_MASK (1 << 0) +#define GSC_OUT_PATH_LOCAL (1 << 0) +#define GSC_OUT_PATH_MEMORY (0 << 0) + +/* G-Scaler scaled destination image size */ +#define GSC_SCALED_SIZE 0x24 +#define GSC_SCALED_HEIGHT(x) ((x) << 16) +#define GSC_SCALED_WIDTH(x) ((x) << 0) + +/* G-Scaler pre scale ratio */ +#define GSC_PRE_SCALE_RATIO 0x28 +#define GSC_PRESC_SHFACTOR(x) ((x) << 28) +#define GSC_PRESC_V_RATIO(x) ((x) << 16) +#define GSC_PRESC_H_RATIO(x) ((x) << 0) + +/* G-Scaler main scale horizontal ratio */ +#define GSC_MAIN_H_RATIO 0x2c +#define GSC_MAIN_H_RATIO_VALUE(x) ((x) << 0) + +/* G-Scaler main scale vertical ratio */ +#define GSC_MAIN_V_RATIO 0x30 +#define GSC_MAIN_V_RATIO_VALUE(x) ((x) << 0) + +/* G-Scaler destination image size */ +#define GSC_DSTIMG_SIZE 0x40 +#define GSC_DSTIMG_HEIGHT(x) ((x) << 16) +#define GSC_DSTIMG_WIDTH(x) ((x) << 0) + +/* G-Scaler destination image offset */ +#define GSC_DSTIMG_OFFSET 0x44 +#define GSC_DSTIMG_OFFSET_Y(x) ((x) << 16) +#define GSC_DSTIMG_OFFSET_X(x) ((x) << 0) + +/* G-Scaler input y address mask */ +#define GSC_IN_BASE_ADDR_Y_MASK 0x4c +/* G-Scaler input y base address */ +#define GSC_IN_BASE_ADDR_Y(n) (0x50 + (n) * 0x4) + +/* G-Scaler input cb address mask */ +#define GSC_IN_BASE_ADDR_CB_MASK 0x7c +/* G-Scaler input cb base address */ +#define GSC_IN_BASE_ADDR_CB(n) (0x80 + (n) * 0x4) + +/* G-Scaler input cr address mask */ +#define GSC_IN_BASE_ADDR_CR_MASK 0xac +/* G-Scaler input cr base address */ +#define GSC_IN_BASE_ADDR_CR(n) (0xb0 + (n) * 0x4) + +/* G-Scaler output y address mask */ +#define GSC_OUT_BASE_ADDR_Y_MASK 0x10c +/* G-Scaler output y base address */ +#define GSC_OUT_BASE_ADDR_Y(n) (0x110 + (n) * 0x4) + +/* G-Scaler output cb address mask */ +#define GSC_OUT_BASE_ADDR_CB_MASK 0x15c +/* G-Scaler output cb base address */ +#define GSC_OUT_BASE_ADDR_CB(n) (0x160 + (n) * 0x4) + +/* G-Scaler output cr address mask */ +#define GSC_OUT_BASE_ADDR_CR_MASK 0x1ac +/* G-Scaler output cr base address */ +#define GSC_OUT_BASE_ADDR_CR(n) (0x1b0 + (n) * 0x4) + +#endif /* REGS_GSC_H_ */ diff --git a/drivers/media/platform/fsl-viu.c b/drivers/media/platform/fsl-viu.c new file mode 100644 index 000000000000..897250b88647 --- /dev/null +++ b/drivers/media/platform/fsl-viu.c @@ -0,0 +1,1690 @@ +/* + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Freescale VIU video driver + * + * Authors: Hongjun Chen + * Porting to 2.6.35 by DENX Software Engineering, + * Anatolij Gustschin + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "fsl_viu" +#define VIU_VERSION "0.5.1" + +#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */ + +#define VIU_VID_MEM_LIMIT 4 /* Video memory limit, in Mb */ + +/* I2C address of video decoder chip is 0x4A */ +#define VIU_VIDEO_DECODER_ADDR 0x25 + +/* supported controls */ +static struct v4l2_queryctrl viu_qctrl[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 127, + .flags = 0, + }, { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = 0x10, + .flags = 0, + }, { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = 127, + .flags = 0, + }, { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Hue", + .minimum = -128, + .maximum = 127, + .step = 0x1, + .default_value = 0, + .flags = 0, + } +}; + +static int qctl_regs[ARRAY_SIZE(viu_qctrl)]; + +static int info_level; + +#define dprintk(level, fmt, arg...) \ + do { \ + if (level <= info_level) \ + printk(KERN_DEBUG "viu: " fmt , ## arg); \ + } while (0) + +/* + * Basic structures + */ +struct viu_fmt { + char name[32]; + u32 fourcc; /* v4l2 format id */ + u32 pixelformat; + int depth; +}; + +static struct viu_fmt formats[] = { + { + .name = "RGB-16 (5/B-6/G-5/R)", + .fourcc = V4L2_PIX_FMT_RGB565, + .pixelformat = V4L2_PIX_FMT_RGB565, + .depth = 16, + }, { + .name = "RGB-32 (A-R-G-B)", + .fourcc = V4L2_PIX_FMT_RGB32, + .pixelformat = V4L2_PIX_FMT_RGB32, + .depth = 32, + } +}; + +struct viu_dev; +struct viu_buf; + +/* buffer for one video frame */ +struct viu_buf { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + struct viu_fmt *fmt; +}; + +struct viu_dmaqueue { + struct viu_dev *dev; + struct list_head active; + struct list_head queued; + struct timer_list timeout; +}; + +struct viu_status { + u32 field_irq; + u32 vsync_irq; + u32 hsync_irq; + u32 vstart_irq; + u32 dma_end_irq; + u32 error_irq; +}; + +struct viu_reg { + u32 status_cfg; + u32 luminance; + u32 chroma_r; + u32 chroma_g; + u32 chroma_b; + u32 field_base_addr; + u32 dma_inc; + u32 picture_count; + u32 req_alarm; + u32 alpha; +} __attribute__ ((packed)); + +struct viu_dev { + struct v4l2_device v4l2_dev; + struct mutex lock; + spinlock_t slock; + int users; + + struct device *dev; + /* various device info */ + struct video_device *vdev; + struct viu_dmaqueue vidq; + enum v4l2_field capfield; + int field; + int first; + int dma_done; + + /* Hardware register area */ + struct viu_reg *vr; + + /* Interrupt vector */ + int irq; + struct viu_status irqs; + + /* video overlay */ + struct v4l2_framebuffer ovbuf; + struct viu_fmt *ovfmt; + unsigned int ovenable; + enum v4l2_field ovfield; + + /* crop */ + struct v4l2_rect crop_current; + + /* clock pointer */ + struct clk *clk; + + /* decoder */ + struct v4l2_subdev *decoder; + + v4l2_std_id std; +}; + +struct viu_fh { + struct viu_dev *dev; + + /* video capture */ + struct videobuf_queue vb_vidq; + spinlock_t vbq_lock; /* spinlock for the videobuf queue */ + + /* video overlay */ + struct v4l2_window win; + struct v4l2_clip clips[1]; + + /* video capture */ + struct viu_fmt *fmt; + int width, height, sizeimage; + enum v4l2_buf_type type; +}; + +static struct viu_reg reg_val; + +/* + * Macro definitions of VIU registers + */ + +/* STATUS_CONFIG register */ +enum status_config { + SOFT_RST = 1 << 0, + + ERR_MASK = 0x0f << 4, /* Error code mask */ + ERR_NO = 0x00, /* No error */ + ERR_DMA_V = 0x01 << 4, /* DMA in vertical active */ + ERR_DMA_VB = 0x02 << 4, /* DMA in vertical blanking */ + ERR_LINE_TOO_LONG = 0x04 << 4, /* Line too long */ + ERR_TOO_MANG_LINES = 0x05 << 4, /* Too many lines in field */ + ERR_LINE_TOO_SHORT = 0x06 << 4, /* Line too short */ + ERR_NOT_ENOUGH_LINE = 0x07 << 4, /* Not enough lines in field */ + ERR_FIFO_OVERFLOW = 0x08 << 4, /* FIFO overflow */ + ERR_FIFO_UNDERFLOW = 0x09 << 4, /* FIFO underflow */ + ERR_1bit_ECC = 0x0a << 4, /* One bit ECC error */ + ERR_MORE_ECC = 0x0b << 4, /* Two/more bits ECC error */ + + INT_FIELD_EN = 0x01 << 8, /* Enable field interrupt */ + INT_VSYNC_EN = 0x01 << 9, /* Enable vsync interrupt */ + INT_HSYNC_EN = 0x01 << 10, /* Enable hsync interrupt */ + INT_VSTART_EN = 0x01 << 11, /* Enable vstart interrupt */ + INT_DMA_END_EN = 0x01 << 12, /* Enable DMA end interrupt */ + INT_ERROR_EN = 0x01 << 13, /* Enable error interrupt */ + INT_ECC_EN = 0x01 << 14, /* Enable ECC interrupt */ + + INT_FIELD_STATUS = 0x01 << 16, /* field interrupt status */ + INT_VSYNC_STATUS = 0x01 << 17, /* vsync interrupt status */ + INT_HSYNC_STATUS = 0x01 << 18, /* hsync interrupt status */ + INT_VSTART_STATUS = 0x01 << 19, /* vstart interrupt status */ + INT_DMA_END_STATUS = 0x01 << 20, /* DMA end interrupt status */ + INT_ERROR_STATUS = 0x01 << 21, /* error interrupt status */ + + DMA_ACT = 0x01 << 27, /* Enable DMA transfer */ + FIELD_NO = 0x01 << 28, /* Field number */ + DITHER_ON = 0x01 << 29, /* Dithering is on */ + ROUND_ON = 0x01 << 30, /* Round is on */ + MODE_32BIT = 0x01 << 31, /* Data in RGBa888, + * 0 in RGB565 + */ +}; + +#define norm_maxw() 720 +#define norm_maxh() 576 + +#define INT_ALL_STATUS (INT_FIELD_STATUS | INT_VSYNC_STATUS | \ + INT_HSYNC_STATUS | INT_VSTART_STATUS | \ + INT_DMA_END_STATUS | INT_ERROR_STATUS) + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static irqreturn_t viu_intr(int irq, void *dev_id); + +struct viu_fmt *format_by_fourcc(int fourcc) +{ + int i; + + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].pixelformat == fourcc) + return formats + i; + } + + dprintk(0, "unknown pixelformat:'%4.4s'\n", (char *)&fourcc); + return NULL; +} + +void viu_start_dma(struct viu_dev *dev) +{ + struct viu_reg *vr = dev->vr; + + dev->field = 0; + + /* Enable DMA operation */ + out_be32(&vr->status_cfg, SOFT_RST); + out_be32(&vr->status_cfg, INT_FIELD_EN); +} + +void viu_stop_dma(struct viu_dev *dev) +{ + struct viu_reg *vr = dev->vr; + int cnt = 100; + u32 status_cfg; + + out_be32(&vr->status_cfg, 0); + + /* Clear pending interrupts */ + status_cfg = in_be32(&vr->status_cfg); + if (status_cfg & 0x3f0000) + out_be32(&vr->status_cfg, status_cfg & 0x3f0000); + + if (status_cfg & DMA_ACT) { + do { + status_cfg = in_be32(&vr->status_cfg); + if (status_cfg & INT_DMA_END_STATUS) + break; + } while (cnt--); + + if (cnt < 0) { + /* timed out, issue soft reset */ + out_be32(&vr->status_cfg, SOFT_RST); + out_be32(&vr->status_cfg, 0); + } else { + /* clear DMA_END and other pending irqs */ + out_be32(&vr->status_cfg, status_cfg & 0x3f0000); + } + } + + dev->field = 0; +} + +static int restart_video_queue(struct viu_dmaqueue *vidq) +{ + struct viu_buf *buf, *prev; + + dprintk(1, "%s vidq=0x%08lx\n", __func__, (unsigned long)vidq); + if (!list_empty(&vidq->active)) { + buf = list_entry(vidq->active.next, struct viu_buf, vb.queue); + dprintk(2, "restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + + viu_stop_dma(vidq->dev); + + /* cancel all outstanding capture requests */ + list_for_each_entry_safe(buf, prev, &vidq->active, vb.queue) { + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + } + mod_timer(&vidq->timeout, jiffies+BUFFER_TIMEOUT); + return 0; + } + + prev = NULL; + for (;;) { + if (list_empty(&vidq->queued)) + return 0; + buf = list_entry(vidq->queued.next, struct viu_buf, vb.queue); + if (prev == NULL) { + list_del(&buf->vb.queue); + list_add_tail(&buf->vb.queue, &vidq->active); + + dprintk(1, "Restarting video dma\n"); + viu_stop_dma(vidq->dev); + viu_start_dma(vidq->dev); + + buf->vb.state = VIDEOBUF_ACTIVE; + mod_timer(&vidq->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2, "[%p/%d] restart_queue - first active\n", + buf, buf->vb.i); + + } else if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_del(&buf->vb.queue); + list_add_tail(&buf->vb.queue, &vidq->active); + buf->vb.state = VIDEOBUF_ACTIVE; + dprintk(2, "[%p/%d] restart_queue - move to active\n", + buf, buf->vb.i); + } else { + return 0; + } + prev = buf; + } +} + +static void viu_vid_timeout(unsigned long data) +{ + struct viu_dev *dev = (struct viu_dev *)data; + struct viu_buf *buf; + struct viu_dmaqueue *vidq = &dev->vidq; + + while (!list_empty(&vidq->active)) { + buf = list_entry(vidq->active.next, struct viu_buf, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + dprintk(1, "viu/0: [%p/%d] timeout\n", buf, buf->vb.i); + } + + restart_video_queue(vidq); +} + +/* + * Videobuf operations + */ +static int buffer_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct viu_fh *fh = vq->priv_data; + + *size = fh->width * fh->height * fh->fmt->depth >> 3; + if (*count == 0) + *count = 32; + + while (*size * *count > VIU_VID_MEM_LIMIT * 1024 * 1024) + (*count)--; + + dprintk(1, "%s, count=%d, size=%d\n", __func__, *count, *size); + return 0; +} + +static void free_buffer(struct videobuf_queue *vq, struct viu_buf *buf) +{ + struct videobuf_buffer *vb = &buf->vb; + void *vaddr = NULL; + + BUG_ON(in_interrupt()); + + videobuf_waiton(vq, &buf->vb, 0, 0); + + if (vq->int_ops && vq->int_ops->vaddr) + vaddr = vq->int_ops->vaddr(vb); + + if (vaddr) + videobuf_dma_contig_free(vq, &buf->vb); + + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +inline int buffer_activate(struct viu_dev *dev, struct viu_buf *buf) +{ + struct viu_reg *vr = dev->vr; + int bpp; + + /* setup the DMA base address */ + reg_val.field_base_addr = videobuf_to_dma_contig(&buf->vb); + + dprintk(1, "buffer_activate [%p/%d]: dma addr 0x%lx\n", + buf, buf->vb.i, (unsigned long)reg_val.field_base_addr); + + /* interlace is on by default, set horizontal DMA increment */ + reg_val.status_cfg = 0; + bpp = buf->fmt->depth >> 3; + switch (bpp) { + case 2: + reg_val.status_cfg &= ~MODE_32BIT; + reg_val.dma_inc = buf->vb.width * 2; + break; + case 4: + reg_val.status_cfg |= MODE_32BIT; + reg_val.dma_inc = buf->vb.width * 4; + break; + default: + dprintk(0, "doesn't support color depth(%d)\n", + bpp * 8); + return -EINVAL; + } + + /* setup picture_count register */ + reg_val.picture_count = (buf->vb.height / 2) << 16 | + buf->vb.width; + + reg_val.status_cfg |= DMA_ACT | INT_DMA_END_EN | INT_FIELD_EN; + + buf->vb.state = VIDEOBUF_ACTIVE; + dev->capfield = buf->vb.field; + + /* reset dma increment if needed */ + if (!V4L2_FIELD_HAS_BOTH(buf->vb.field)) + reg_val.dma_inc = 0; + + out_be32(&vr->dma_inc, reg_val.dma_inc); + out_be32(&vr->picture_count, reg_val.picture_count); + out_be32(&vr->field_base_addr, reg_val.field_base_addr); + mod_timer(&dev->vidq.timeout, jiffies + BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct viu_fh *fh = vq->priv_data; + struct viu_buf *buf = container_of(vb, struct viu_buf, vb); + int rc; + + BUG_ON(fh->fmt == NULL); + + if (fh->width < 48 || fh->width > norm_maxw() || + fh->height < 32 || fh->height > norm_maxh()) + return -EINVAL; + buf->vb.size = (fh->width * fh->height * fh->fmt->depth) >> 3; + if (buf->vb.baddr != 0 && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.field != field) { + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + } + + if (buf->vb.state == VIDEOBUF_NEEDS_INIT) { + rc = videobuf_iolock(vq, &buf->vb, NULL); + if (rc != 0) + goto fail; + + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + buf->fmt = fh->fmt; + } + + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + +fail: + free_buffer(vq, buf); + return rc; +} + +static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct viu_buf *buf = container_of(vb, struct viu_buf, vb); + struct viu_fh *fh = vq->priv_data; + struct viu_dev *dev = fh->dev; + struct viu_dmaqueue *vidq = &dev->vidq; + struct viu_buf *prev; + + if (!list_empty(&vidq->queued)) { + dprintk(1, "adding vb queue=0x%08lx\n", + (unsigned long)&buf->vb.queue); + dprintk(1, "vidq pointer 0x%p, queued 0x%p\n", + vidq, &vidq->queued); + dprintk(1, "dev %p, queued: self %p, next %p, head %p\n", + dev, &vidq->queued, vidq->queued.next, + vidq->queued.prev); + list_add_tail(&buf->vb.queue, &vidq->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2, "[%p/%d] buffer_queue - append to queued\n", + buf, buf->vb.i); + } else if (list_empty(&vidq->active)) { + dprintk(1, "adding vb active=0x%08lx\n", + (unsigned long)&buf->vb.queue); + list_add_tail(&buf->vb.queue, &vidq->active); + buf->vb.state = VIDEOBUF_ACTIVE; + mod_timer(&vidq->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2, "[%p/%d] buffer_queue - first active\n", + buf, buf->vb.i); + + buffer_activate(dev, buf); + } else { + dprintk(1, "adding vb queue2=0x%08lx\n", + (unsigned long)&buf->vb.queue); + prev = list_entry(vidq->active.prev, struct viu_buf, vb.queue); + if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_add_tail(&buf->vb.queue, &vidq->active); + buf->vb.state = VIDEOBUF_ACTIVE; + dprintk(2, "[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + } else { + list_add_tail(&buf->vb.queue, &vidq->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2, "[%p/%d] buffer_queue - first queued\n", + buf, buf->vb.i); + } + } +} + +static void buffer_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct viu_buf *buf = container_of(vb, struct viu_buf, vb); + struct viu_fh *fh = vq->priv_data; + struct viu_dev *dev = (struct viu_dev *)fh->dev; + + viu_stop_dma(dev); + free_buffer(vq, buf); +} + +static struct videobuf_queue_ops viu_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* + * IOCTL vidioc handling + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strcpy(cap->driver, "viu"); + strcpy(cap->card, "viu"); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_READWRITE; + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int index = f->index; + + if (f->index > NUM_FORMATS) + return -EINVAL; + + strlcpy(f->description, formats[index].name, sizeof(f->description)); + f->pixelformat = formats[index].fourcc; + return 0; +} + +static int vidioc_g_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fh *fh = priv; + + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vb_vidq.field; + f->fmt.pix.pixelformat = fh->fmt->pixelformat; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = fh->sizeimage; + return 0; +} + +static int vidioc_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (!fmt) { + dprintk(1, "Fourcc format (0x%08x) invalid.", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + field = f->fmt.pix.field; + + if (field == V4L2_FIELD_ANY) { + field = V4L2_FIELD_INTERLACED; + } else if (field != V4L2_FIELD_INTERLACED) { + dprintk(1, "Field type invalid.\n"); + return -EINVAL; + } + + maxw = norm_maxw(); + maxh = norm_maxh(); + + f->fmt.pix.field = field; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + f->fmt.pix.width &= ~0x03; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + + return 0; +} + +static int vidioc_s_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fh *fh = priv; + int ret; + + ret = vidioc_try_fmt_cap(file, fh, f); + if (ret < 0) + return ret; + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->sizeimage = f->fmt.pix.sizeimage; + fh->vb_vidq.field = f->fmt.pix.field; + fh->type = f->type; + dprintk(1, "set to pixelformat '%4.6s'\n", (char *)&fh->fmt->name); + return 0; +} + +static int vidioc_g_fmt_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fh *fh = priv; + + f->fmt.win = fh->win; + return 0; +} + +static int verify_preview(struct viu_dev *dev, struct v4l2_window *win) +{ + enum v4l2_field field; + int maxw, maxh; + + if (dev->ovbuf.base == NULL) + return -EINVAL; + if (dev->ovfmt == NULL) + return -EINVAL; + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + + field = win->field; + maxw = dev->crop_current.width; + maxh = dev->crop_current.height; + + if (field == V4L2_FIELD_ANY) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + return 0; +} + +inline void viu_activate_overlay(struct viu_reg *viu_reg) +{ + struct viu_reg *vr = viu_reg; + + out_be32(&vr->field_base_addr, reg_val.field_base_addr); + out_be32(&vr->dma_inc, reg_val.dma_inc); + out_be32(&vr->picture_count, reg_val.picture_count); +} + +static int viu_setup_preview(struct viu_dev *dev, struct viu_fh *fh) +{ + int bpp; + + dprintk(1, "%s %dx%d %s\n", __func__, + fh->win.w.width, fh->win.w.height, dev->ovfmt->name); + + reg_val.status_cfg = 0; + + /* setup window */ + reg_val.picture_count = (fh->win.w.height / 2) << 16 | + fh->win.w.width; + + /* setup color depth and dma increment */ + bpp = dev->ovfmt->depth / 8; + switch (bpp) { + case 2: + reg_val.status_cfg &= ~MODE_32BIT; + reg_val.dma_inc = fh->win.w.width * 2; + break; + case 4: + reg_val.status_cfg |= MODE_32BIT; + reg_val.dma_inc = fh->win.w.width * 4; + break; + default: + dprintk(0, "device doesn't support color depth(%d)\n", + bpp * 8); + return -EINVAL; + } + + dev->ovfield = fh->win.field; + if (!V4L2_FIELD_HAS_BOTH(dev->ovfield)) + reg_val.dma_inc = 0; + + reg_val.status_cfg |= DMA_ACT | INT_DMA_END_EN | INT_FIELD_EN; + + /* setup the base address of the overlay buffer */ + reg_val.field_base_addr = (u32)dev->ovbuf.base; + + return 0; +} + +static int vidioc_s_fmt_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = (struct viu_dev *)fh->dev; + unsigned long flags; + int err; + + err = verify_preview(dev, &f->fmt.win); + if (err) + return err; + + fh->win = f->fmt.win; + + spin_lock_irqsave(&dev->slock, flags); + viu_setup_preview(dev, fh); + spin_unlock_irqrestore(&dev->slock, flags); + return 0; +} + +static int vidioc_try_fmt_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + return 0; +} + +static int vidioc_overlay(struct file *file, void *priv, unsigned int on) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = (struct viu_dev *)fh->dev; + unsigned long flags; + + if (on) { + spin_lock_irqsave(&dev->slock, flags); + viu_activate_overlay(dev->vr); + dev->ovenable = 1; + + /* start dma */ + viu_start_dma(dev); + spin_unlock_irqrestore(&dev->slock, flags); + } else { + viu_stop_dma(dev); + dev->ovenable = 0; + } + + return 0; +} + +int vidioc_g_fbuf(struct file *file, void *priv, struct v4l2_framebuffer *arg) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = fh->dev; + struct v4l2_framebuffer *fb = arg; + + *fb = dev->ovbuf; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + return 0; +} + +int vidioc_s_fbuf(struct file *file, void *priv, const struct v4l2_framebuffer *arg) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = fh->dev; + struct v4l2_framebuffer *fb = arg; + struct viu_fmt *fmt; + + if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = format_by_fourcc(fb->fmt.pixelformat); + if (fmt == NULL) + return -EINVAL; + + /* ok, accept it */ + dev->ovbuf = *fb; + dev->ovfmt = fmt; + if (dev->ovbuf.fmt.bytesperline == 0) { + dev->ovbuf.fmt.bytesperline = + dev->ovbuf.fmt.width * fmt->depth / 8; + } + return 0; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct viu_fh *fh = priv; + + return videobuf_reqbufs(&fh->vb_vidq, p); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct viu_fh *fh = priv; + + return videobuf_querybuf(&fh->vb_vidq, p); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct viu_fh *fh = priv; + + return videobuf_qbuf(&fh->vb_vidq, p); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct viu_fh *fh = priv; + + return videobuf_dqbuf(&fh->vb_vidq, p, + file->f_flags & O_NONBLOCK); +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = fh->dev; + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (fh->type != i) + return -EINVAL; + + if (dev->ovenable) + dev->ovenable = 0; + + viu_start_dma(fh->dev); + + return videobuf_streamon(&fh->vb_vidq); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct viu_fh *fh = priv; + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (fh->type != i) + return -EINVAL; + + viu_stop_dma(fh->dev); + + return videobuf_streamoff(&fh->vb_vidq); +} + +#define decoder_call(viu, o, f, args...) \ + v4l2_subdev_call(viu->decoder, o, f, ##args) + +static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct viu_fh *fh = priv; + + decoder_call(fh->dev, video, querystd, std_id); + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct viu_fh *fh = priv; + + fh->dev->std = *id; + decoder_call(fh->dev, core, s_std, *id); + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct viu_fh *fh = priv; + + *std_id = fh->dev->std; + return 0; +} + +/* only one input in this driver */ +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct viu_fh *fh = priv; + + if (inp->index != 0) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = fh->dev->vdev->tvnorms; + strcpy(inp->name, "Camera"); + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct viu_fh *fh = priv; + + if (i > 1) + return -EINVAL; + + decoder_call(fh->dev, video, s_routing, i, 0, 0); + return 0; +} + +/* Controls */ +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(viu_qctrl); i++) { + if (qc->id && qc->id == viu_qctrl[i].id) { + memcpy(qc, &(viu_qctrl[i]), sizeof(*qc)); + return 0; + } + } + return -EINVAL; +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(viu_qctrl); i++) { + if (ctrl->id == viu_qctrl[i].id) { + ctrl->value = qctl_regs[i]; + return 0; + } + } + return -EINVAL; +} +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(viu_qctrl); i++) { + if (ctrl->id == viu_qctrl[i].id) { + if (ctrl->value < viu_qctrl[i].minimum + || ctrl->value > viu_qctrl[i].maximum) + return -ERANGE; + qctl_regs[i] = ctrl->value; + return 0; + } + } + return -EINVAL; +} + +inline void viu_activate_next_buf(struct viu_dev *dev, + struct viu_dmaqueue *viuq) +{ + struct viu_dmaqueue *vidq = viuq; + struct viu_buf *buf; + + /* launch another DMA operation for an active/queued buffer */ + if (!list_empty(&vidq->active)) { + buf = list_entry(vidq->active.next, struct viu_buf, + vb.queue); + dprintk(1, "start another queued buffer: 0x%p\n", buf); + buffer_activate(dev, buf); + } else if (!list_empty(&vidq->queued)) { + buf = list_entry(vidq->queued.next, struct viu_buf, + vb.queue); + list_del(&buf->vb.queue); + + dprintk(1, "start another queued buffer: 0x%p\n", buf); + list_add_tail(&buf->vb.queue, &vidq->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buffer_activate(dev, buf); + } +} + +inline void viu_default_settings(struct viu_reg *viu_reg) +{ + struct viu_reg *vr = viu_reg; + + out_be32(&vr->luminance, 0x9512A254); + out_be32(&vr->chroma_r, 0x03310000); + out_be32(&vr->chroma_g, 0x06600F38); + out_be32(&vr->chroma_b, 0x00000409); + out_be32(&vr->alpha, 0x000000ff); + out_be32(&vr->req_alarm, 0x00000090); + dprintk(1, "status reg: 0x%08x, field base: 0x%08x\n", + in_be32(&vr->status_cfg), in_be32(&vr->field_base_addr)); +} + +static void viu_overlay_intr(struct viu_dev *dev, u32 status) +{ + struct viu_reg *vr = dev->vr; + + if (status & INT_DMA_END_STATUS) + dev->dma_done = 1; + + if (status & INT_FIELD_STATUS) { + if (dev->dma_done) { + u32 addr = reg_val.field_base_addr; + + dev->dma_done = 0; + if (status & FIELD_NO) + addr += reg_val.dma_inc; + + out_be32(&vr->field_base_addr, addr); + out_be32(&vr->dma_inc, reg_val.dma_inc); + out_be32(&vr->status_cfg, + (status & 0xffc0ffff) | + (status & INT_ALL_STATUS) | + reg_val.status_cfg); + } else if (status & INT_VSYNC_STATUS) { + out_be32(&vr->status_cfg, + (status & 0xffc0ffff) | + (status & INT_ALL_STATUS) | + reg_val.status_cfg); + } + } +} + +static void viu_capture_intr(struct viu_dev *dev, u32 status) +{ + struct viu_dmaqueue *vidq = &dev->vidq; + struct viu_reg *vr = dev->vr; + struct viu_buf *buf; + int field_num; + int need_two; + int dma_done = 0; + + field_num = status & FIELD_NO; + need_two = V4L2_FIELD_HAS_BOTH(dev->capfield); + + if (status & INT_DMA_END_STATUS) { + dma_done = 1; + if (((field_num == 0) && (dev->field == 0)) || + (field_num && (dev->field == 1))) + dev->field++; + } + + if (status & INT_FIELD_STATUS) { + dprintk(1, "irq: field %d, done %d\n", + !!field_num, dma_done); + if (unlikely(dev->first)) { + if (field_num == 0) { + dev->first = 0; + dprintk(1, "activate first buf\n"); + viu_activate_next_buf(dev, vidq); + } else + dprintk(1, "wait field 0\n"); + return; + } + + /* setup buffer address for next dma operation */ + if (!list_empty(&vidq->active)) { + u32 addr = reg_val.field_base_addr; + + if (field_num && need_two) { + addr += reg_val.dma_inc; + dprintk(1, "field 1, 0x%lx, dev field %d\n", + (unsigned long)addr, dev->field); + } + out_be32(&vr->field_base_addr, addr); + out_be32(&vr->dma_inc, reg_val.dma_inc); + out_be32(&vr->status_cfg, + (status & 0xffc0ffff) | + (status & INT_ALL_STATUS) | + reg_val.status_cfg); + return; + } + } + + if (dma_done && field_num && (dev->field == 2)) { + dev->field = 0; + buf = list_entry(vidq->active.next, + struct viu_buf, vb.queue); + dprintk(1, "viu/0: [%p/%d] 0x%lx/0x%lx: dma complete\n", + buf, buf->vb.i, + (unsigned long)videobuf_to_dma_contig(&buf->vb), + (unsigned long)in_be32(&vr->field_base_addr)); + + if (waitqueue_active(&buf->vb.done)) { + list_del(&buf->vb.queue); + do_gettimeofday(&buf->vb.ts); + buf->vb.state = VIDEOBUF_DONE; + buf->vb.field_count++; + wake_up(&buf->vb.done); + } + /* activate next dma buffer */ + viu_activate_next_buf(dev, vidq); + } +} + +static irqreturn_t viu_intr(int irq, void *dev_id) +{ + struct viu_dev *dev = (struct viu_dev *)dev_id; + struct viu_reg *vr = dev->vr; + u32 status; + u32 error; + + status = in_be32(&vr->status_cfg); + + if (status & INT_ERROR_STATUS) { + dev->irqs.error_irq++; + error = status & ERR_MASK; + if (error) + dprintk(1, "Err: error(%d), times:%d!\n", + error >> 4, dev->irqs.error_irq); + /* Clear interrupt error bit and error flags */ + out_be32(&vr->status_cfg, + (status & 0xffc0ffff) | INT_ERROR_STATUS); + } + + if (status & INT_DMA_END_STATUS) { + dev->irqs.dma_end_irq++; + dev->dma_done = 1; + dprintk(2, "VIU DMA end interrupt times: %d\n", + dev->irqs.dma_end_irq); + } + + if (status & INT_HSYNC_STATUS) + dev->irqs.hsync_irq++; + + if (status & INT_FIELD_STATUS) { + dev->irqs.field_irq++; + dprintk(2, "VIU field interrupt times: %d\n", + dev->irqs.field_irq); + } + + if (status & INT_VSTART_STATUS) + dev->irqs.vstart_irq++; + + if (status & INT_VSYNC_STATUS) { + dev->irqs.vsync_irq++; + dprintk(2, "VIU vsync interrupt times: %d\n", + dev->irqs.vsync_irq); + } + + /* clear all pending irqs */ + status = in_be32(&vr->status_cfg); + out_be32(&vr->status_cfg, + (status & 0xffc0ffff) | (status & INT_ALL_STATUS)); + + if (dev->ovenable) { + viu_overlay_intr(dev, status); + return IRQ_HANDLED; + } + + /* Capture mode */ + viu_capture_intr(dev, status); + return IRQ_HANDLED; +} + +/* + * File operations for the device + */ +static int viu_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct viu_dev *dev = video_get_drvdata(vdev); + struct viu_fh *fh; + struct viu_reg *vr; + int minor = vdev->minor; + u32 status_cfg; + int i; + + dprintk(1, "viu: open (minor=%d)\n", minor); + + dev->users++; + if (dev->users > 1) { + dev->users--; + return -EBUSY; + } + + vr = dev->vr; + + dprintk(1, "open minor=%d type=%s users=%d\n", minor, + v4l2_type_names[V4L2_BUF_TYPE_VIDEO_CAPTURE], dev->users); + + if (mutex_lock_interruptible(&dev->lock)) { + dev->users--; + return -ERESTARTSYS; + } + + /* allocate and initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (!fh) { + dev->users--; + mutex_unlock(&dev->lock); + return -ENOMEM; + } + + file->private_data = fh; + fh->dev = dev; + + fh->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_RGB32); + fh->width = norm_maxw(); + fh->height = norm_maxh(); + dev->crop_current.width = fh->width; + dev->crop_current.height = fh->height; + + /* Put all controls at a sane state */ + for (i = 0; i < ARRAY_SIZE(viu_qctrl); i++) + qctl_regs[i] = viu_qctrl[i].default_value; + + dprintk(1, "Open: fh=0x%08lx, dev=0x%08lx, dev->vidq=0x%08lx\n", + (unsigned long)fh, (unsigned long)dev, + (unsigned long)&dev->vidq); + dprintk(1, "Open: list_empty queued=%d\n", + list_empty(&dev->vidq.queued)); + dprintk(1, "Open: list_empty active=%d\n", + list_empty(&dev->vidq.active)); + + viu_default_settings(vr); + + status_cfg = in_be32(&vr->status_cfg); + out_be32(&vr->status_cfg, + status_cfg & ~(INT_VSYNC_EN | INT_HSYNC_EN | + INT_FIELD_EN | INT_VSTART_EN | + INT_DMA_END_EN | INT_ERROR_EN | INT_ECC_EN)); + + status_cfg = in_be32(&vr->status_cfg); + out_be32(&vr->status_cfg, status_cfg | INT_ALL_STATUS); + + spin_lock_init(&fh->vbq_lock); + videobuf_queue_dma_contig_init(&fh->vb_vidq, &viu_video_qops, + dev->dev, &fh->vbq_lock, + fh->type, V4L2_FIELD_INTERLACED, + sizeof(struct viu_buf), fh, + &fh->dev->lock); + mutex_unlock(&dev->lock); + return 0; +} + +static ssize_t viu_read(struct file *file, char __user *data, size_t count, + loff_t *ppos) +{ + struct viu_fh *fh = file->private_data; + struct viu_dev *dev = fh->dev; + int ret = 0; + + dprintk(2, "%s\n", __func__); + if (dev->ovenable) + dev->ovenable = 0; + + if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + viu_start_dma(dev); + ret = videobuf_read_stream(&fh->vb_vidq, data, count, + ppos, 0, file->f_flags & O_NONBLOCK); + mutex_unlock(&dev->lock); + return ret; + } + return 0; +} + +static unsigned int viu_poll(struct file *file, struct poll_table_struct *wait) +{ + struct viu_fh *fh = file->private_data; + struct videobuf_queue *q = &fh->vb_vidq; + struct viu_dev *dev = fh->dev; + unsigned int res; + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type) + return POLLERR; + + mutex_lock(&dev->lock); + res = videobuf_poll_stream(file, q, wait); + mutex_unlock(&dev->lock); + return res; +} + +static int viu_release(struct file *file) +{ + struct viu_fh *fh = file->private_data; + struct viu_dev *dev = fh->dev; + int minor = video_devdata(file)->minor; + + mutex_lock(&dev->lock); + viu_stop_dma(dev); + videobuf_stop(&fh->vb_vidq); + videobuf_mmap_free(&fh->vb_vidq); + mutex_unlock(&dev->lock); + + kfree(fh); + + dev->users--; + dprintk(1, "close (minor=%d, users=%d)\n", + minor, dev->users); + return 0; +} + +void viu_reset(struct viu_reg *reg) +{ + out_be32(®->status_cfg, 0); + out_be32(®->luminance, 0x9512a254); + out_be32(®->chroma_r, 0x03310000); + out_be32(®->chroma_g, 0x06600f38); + out_be32(®->chroma_b, 0x00000409); + out_be32(®->field_base_addr, 0); + out_be32(®->dma_inc, 0); + out_be32(®->picture_count, 0x01e002d0); + out_be32(®->req_alarm, 0x00000090); + out_be32(®->alpha, 0x000000ff); +} + +static int viu_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct viu_fh *fh = file->private_data; + struct viu_dev *dev = fh->dev; + int ret; + + dprintk(1, "mmap called, vma=0x%08lx\n", (unsigned long)vma); + + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + ret = videobuf_mmap_mapper(&fh->vb_vidq, vma); + mutex_unlock(&dev->lock); + + dprintk(1, "vma start=0x%08lx, size=%ld, ret=%d\n", + (unsigned long)vma->vm_start, + (unsigned long)vma->vm_end-(unsigned long)vma->vm_start, + ret); + + return ret; +} + +static struct v4l2_file_operations viu_fops = { + .owner = THIS_MODULE, + .open = viu_open, + .release = viu_release, + .read = viu_read, + .poll = viu_poll, + .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */ + .mmap = viu_mmap, +}; + +static const struct v4l2_ioctl_ops viu_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_cap, + .vidioc_enum_fmt_vid_overlay = vidioc_enum_fmt, + .vidioc_g_fmt_vid_overlay = vidioc_g_fmt_overlay, + .vidioc_try_fmt_vid_overlay = vidioc_try_fmt_overlay, + .vidioc_s_fmt_vid_overlay = vidioc_s_fmt_overlay, + .vidioc_overlay = vidioc_overlay, + .vidioc_g_fbuf = vidioc_g_fbuf, + .vidioc_s_fbuf = vidioc_s_fbuf, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_querystd = vidioc_querystd, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +}; + +static struct video_device viu_template = { + .name = "FSL viu", + .fops = &viu_fops, + .minor = -1, + .ioctl_ops = &viu_ioctl_ops, + .release = video_device_release, + + .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL, + .current_norm = V4L2_STD_NTSC_M, +}; + +static int __devinit viu_of_probe(struct platform_device *op) +{ + struct viu_dev *viu_dev; + struct video_device *vdev; + struct resource r; + struct viu_reg __iomem *viu_regs; + struct i2c_adapter *ad; + int ret, viu_irq; + + ret = of_address_to_resource(op->dev.of_node, 0, &r); + if (ret) { + dev_err(&op->dev, "Can't parse device node resource\n"); + return -ENODEV; + } + + viu_irq = irq_of_parse_and_map(op->dev.of_node, 0); + if (viu_irq == NO_IRQ) { + dev_err(&op->dev, "Error while mapping the irq\n"); + return -EINVAL; + } + + /* request mem region */ + if (!devm_request_mem_region(&op->dev, r.start, + sizeof(struct viu_reg), DRV_NAME)) { + dev_err(&op->dev, "Error while requesting mem region\n"); + ret = -EBUSY; + goto err; + } + + /* remap registers */ + viu_regs = devm_ioremap(&op->dev, r.start, sizeof(struct viu_reg)); + if (!viu_regs) { + dev_err(&op->dev, "Can't map register set\n"); + ret = -ENOMEM; + goto err; + } + + /* Prepare our private structure */ + viu_dev = devm_kzalloc(&op->dev, sizeof(struct viu_dev), GFP_ATOMIC); + if (!viu_dev) { + dev_err(&op->dev, "Can't allocate private structure\n"); + ret = -ENOMEM; + goto err; + } + + viu_dev->vr = viu_regs; + viu_dev->irq = viu_irq; + viu_dev->dev = &op->dev; + + /* init video dma queues */ + INIT_LIST_HEAD(&viu_dev->vidq.active); + INIT_LIST_HEAD(&viu_dev->vidq.queued); + + snprintf(viu_dev->v4l2_dev.name, + sizeof(viu_dev->v4l2_dev.name), "%s", "VIU"); + ret = v4l2_device_register(viu_dev->dev, &viu_dev->v4l2_dev); + if (ret < 0) { + dev_err(&op->dev, "v4l2_device_register() failed: %d\n", ret); + goto err; + } + + ad = i2c_get_adapter(0); + viu_dev->decoder = v4l2_i2c_new_subdev(&viu_dev->v4l2_dev, ad, + "saa7113", VIU_VIDEO_DECODER_ADDR, NULL); + + viu_dev->vidq.timeout.function = viu_vid_timeout; + viu_dev->vidq.timeout.data = (unsigned long)viu_dev; + init_timer(&viu_dev->vidq.timeout); + viu_dev->first = 1; + + /* Allocate memory for video device */ + vdev = video_device_alloc(); + if (vdev == NULL) { + ret = -ENOMEM; + goto err_vdev; + } + + memcpy(vdev, &viu_template, sizeof(viu_template)); + + vdev->v4l2_dev = &viu_dev->v4l2_dev; + + viu_dev->vdev = vdev; + + /* initialize locks */ + mutex_init(&viu_dev->lock); + viu_dev->vdev->lock = &viu_dev->lock; + spin_lock_init(&viu_dev->slock); + + video_set_drvdata(viu_dev->vdev, viu_dev); + + mutex_lock(&viu_dev->lock); + + ret = video_register_device(viu_dev->vdev, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + video_device_release(viu_dev->vdev); + goto err_vdev; + } + + /* enable VIU clock */ + viu_dev->clk = clk_get(&op->dev, "viu_clk"); + if (IS_ERR(viu_dev->clk)) { + dev_err(&op->dev, "failed to find the clock module!\n"); + ret = -ENODEV; + goto err_clk; + } else { + clk_enable(viu_dev->clk); + } + + /* reset VIU module */ + viu_reset(viu_dev->vr); + + /* install interrupt handler */ + if (request_irq(viu_dev->irq, viu_intr, 0, "viu", (void *)viu_dev)) { + dev_err(&op->dev, "Request VIU IRQ failed.\n"); + ret = -ENODEV; + goto err_irq; + } + + mutex_unlock(&viu_dev->lock); + + dev_info(&op->dev, "Freescale VIU Video Capture Board\n"); + return ret; + +err_irq: + clk_disable(viu_dev->clk); + clk_put(viu_dev->clk); +err_clk: + video_unregister_device(viu_dev->vdev); +err_vdev: + mutex_unlock(&viu_dev->lock); + i2c_put_adapter(ad); + v4l2_device_unregister(&viu_dev->v4l2_dev); +err: + irq_dispose_mapping(viu_irq); + return ret; +} + +static int __devexit viu_of_remove(struct platform_device *op) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(&op->dev); + struct viu_dev *dev = container_of(v4l2_dev, struct viu_dev, v4l2_dev); + struct v4l2_subdev *sdev = list_entry(v4l2_dev->subdevs.next, + struct v4l2_subdev, list); + struct i2c_client *client = v4l2_get_subdevdata(sdev); + + free_irq(dev->irq, (void *)dev); + irq_dispose_mapping(dev->irq); + + clk_disable(dev->clk); + clk_put(dev->clk); + + video_unregister_device(dev->vdev); + i2c_put_adapter(client->adapter); + v4l2_device_unregister(&dev->v4l2_dev); + return 0; +} + +#ifdef CONFIG_PM +static int viu_suspend(struct platform_device *op, pm_message_t state) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(&op->dev); + struct viu_dev *dev = container_of(v4l2_dev, struct viu_dev, v4l2_dev); + + clk_disable(dev->clk); + return 0; +} + +static int viu_resume(struct platform_device *op) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(&op->dev); + struct viu_dev *dev = container_of(v4l2_dev, struct viu_dev, v4l2_dev); + + clk_enable(dev->clk); + return 0; +} +#endif + +/* + * Initialization and module stuff + */ +static struct of_device_id mpc512x_viu_of_match[] = { + { + .compatible = "fsl,mpc5121-viu", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpc512x_viu_of_match); + +static struct platform_driver viu_of_platform_driver = { + .probe = viu_of_probe, + .remove = __devexit_p(viu_of_remove), +#ifdef CONFIG_PM + .suspend = viu_suspend, + .resume = viu_resume, +#endif + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = mpc512x_viu_of_match, + }, +}; + +module_platform_driver(viu_of_platform_driver); + +MODULE_DESCRIPTION("Freescale Video-In(VIU)"); +MODULE_AUTHOR("Hongjun Chen"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VIU_VERSION); diff --git a/drivers/media/video/indycam.c b/drivers/media/platform/indycam.c similarity index 100% rename from drivers/media/video/indycam.c rename to drivers/media/platform/indycam.c diff --git a/drivers/media/video/indycam.h b/drivers/media/platform/indycam.h similarity index 100% rename from drivers/media/video/indycam.h rename to drivers/media/platform/indycam.h diff --git a/drivers/media/platform/m2m-deinterlace.c b/drivers/media/platform/m2m-deinterlace.c new file mode 100644 index 000000000000..45164c4f8452 --- /dev/null +++ b/drivers/media/platform/m2m-deinterlace.c @@ -0,0 +1,1124 @@ +/* + * V4L2 deinterlacing support. + * + * Copyright (c) 2012 Vista Silicon S.L. + * Javier Martin + * + * 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 + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MEM2MEM_TEST_MODULE_NAME "mem2mem-deinterlace" + +MODULE_DESCRIPTION("mem2mem device which supports deinterlacing using dmaengine"); +MODULE_AUTHOR("Javier Martin v4l2_dev, "%s: " fmt, __func__, ## arg) + +struct deinterlace_fmt { + char *name; + u32 fourcc; + /* Types the format can be used for */ + u32 types; +}; + +static struct deinterlace_fmt formats[] = { + { + .name = "YUV 4:2:0 Planar", + .fourcc = V4L2_PIX_FMT_YUV420, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, + { + .name = "YUYV 4:2:2", + .fourcc = V4L2_PIX_FMT_YUYV, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +/* Per-queue, driver-specific private data */ +struct deinterlace_q_data { + unsigned int width; + unsigned int height; + unsigned int sizeimage; + struct deinterlace_fmt *fmt; + enum v4l2_field field; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +enum { + YUV420_DMA_Y_ODD, + YUV420_DMA_Y_EVEN, + YUV420_DMA_U_ODD, + YUV420_DMA_U_EVEN, + YUV420_DMA_V_ODD, + YUV420_DMA_V_EVEN, + YUV420_DMA_Y_ODD_DOUBLING, + YUV420_DMA_U_ODD_DOUBLING, + YUV420_DMA_V_ODD_DOUBLING, + YUYV_DMA_ODD, + YUYV_DMA_EVEN, + YUYV_DMA_EVEN_DOUBLING, +}; + +/* Source and destination queue data */ +static struct deinterlace_q_data q_data[2]; + +static struct deinterlace_q_data *get_q_data(enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &q_data[V4L2_M2M_SRC]; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &q_data[V4L2_M2M_DST]; + default: + BUG(); + } + return NULL; +} + +static struct deinterlace_fmt *find_format(struct v4l2_format *f) +{ + struct deinterlace_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if ((fmt->types & f->type) && + (fmt->fourcc == f->fmt.pix.pixelformat)) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct deinterlace_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd; + + atomic_t busy; + struct mutex dev_mutex; + spinlock_t irqlock; + + struct dma_chan *dma_chan; + + struct v4l2_m2m_dev *m2m_dev; + struct vb2_alloc_ctx *alloc_ctx; +}; + +struct deinterlace_ctx { + struct deinterlace_dev *dev; + + /* Abort requested by m2m */ + int aborting; + enum v4l2_colorspace colorspace; + dma_cookie_t cookie; + struct v4l2_m2m_ctx *m2m_ctx; + struct dma_interleaved_template *xt; +}; + +/* + * mem2mem callbacks + */ +static int deinterlace_job_ready(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + struct deinterlace_dev *pcdev = ctx->dev; + + if ((v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) > 0) + && (v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx) > 0) + && (atomic_read(&ctx->dev->busy) == 0)) { + dprintk(pcdev, "Task ready\n"); + return 1; + } + + dprintk(pcdev, "Task not ready to run\n"); + + return 0; +} + +static void deinterlace_job_abort(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + struct deinterlace_dev *pcdev = ctx->dev; + + ctx->aborting = 1; + + dprintk(pcdev, "Aborting task\n"); + + v4l2_m2m_job_finish(pcdev->m2m_dev, ctx->m2m_ctx); +} + +static void deinterlace_lock(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + struct deinterlace_dev *pcdev = ctx->dev; + mutex_lock(&pcdev->dev_mutex); +} + +static void deinterlace_unlock(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + struct deinterlace_dev *pcdev = ctx->dev; + mutex_unlock(&pcdev->dev_mutex); +} + +static void dma_callback(void *data) +{ + struct deinterlace_ctx *curr_ctx = data; + struct deinterlace_dev *pcdev = curr_ctx->dev; + struct vb2_buffer *src_vb, *dst_vb; + + atomic_set(&pcdev->busy, 0); + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->m2m_ctx); + + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + + v4l2_m2m_job_finish(pcdev->m2m_dev, curr_ctx->m2m_ctx); + + dprintk(pcdev, "dma transfers completed.\n"); +} + +static void deinterlace_issue_dma(struct deinterlace_ctx *ctx, int op, + int do_callback) +{ + struct deinterlace_q_data *s_q_data, *d_q_data; + struct vb2_buffer *src_buf, *dst_buf; + struct deinterlace_dev *pcdev = ctx->dev; + struct dma_chan *chan = pcdev->dma_chan; + struct dma_device *dmadev = chan->device; + struct dma_async_tx_descriptor *tx; + unsigned int s_width, s_height; + unsigned int d_width, d_height; + unsigned int d_size, s_size; + dma_addr_t p_in, p_out; + enum dma_ctrl_flags flags; + + src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + + s_q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_OUTPUT); + s_width = s_q_data->width; + s_height = s_q_data->height; + s_size = s_width * s_height; + + d_q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_CAPTURE); + d_width = d_q_data->width; + d_height = d_q_data->height; + d_size = d_width * d_height; + + p_in = (dma_addr_t)vb2_dma_contig_plane_dma_addr(src_buf, 0); + p_out = (dma_addr_t)vb2_dma_contig_plane_dma_addr(dst_buf, 0); + if (!p_in || !p_out) { + v4l2_err(&pcdev->v4l2_dev, + "Acquiring kernel pointers to buffers failed\n"); + return; + } + + switch (op) { + case YUV420_DMA_Y_ODD: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width; + ctx->xt->sgl[0].icg = s_width; + ctx->xt->src_start = p_in; + ctx->xt->dst_start = p_out; + break; + case YUV420_DMA_Y_EVEN: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width; + ctx->xt->sgl[0].icg = s_width; + ctx->xt->src_start = p_in + s_size / 2; + ctx->xt->dst_start = p_out + s_width; + break; + case YUV420_DMA_U_ODD: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + s_size; + ctx->xt->dst_start = p_out + s_size; + break; + case YUV420_DMA_U_EVEN: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + (9 * s_size) / 8; + ctx->xt->dst_start = p_out + s_size + s_width / 2; + break; + case YUV420_DMA_V_ODD: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + (5 * s_size) / 4; + ctx->xt->dst_start = p_out + (5 * s_size) / 4; + break; + case YUV420_DMA_V_EVEN: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + (11 * s_size) / 8; + ctx->xt->dst_start = p_out + (5 * s_size) / 4 + s_width / 2; + break; + case YUV420_DMA_Y_ODD_DOUBLING: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width; + ctx->xt->sgl[0].icg = s_width; + ctx->xt->src_start = p_in; + ctx->xt->dst_start = p_out + s_width; + break; + case YUV420_DMA_U_ODD_DOUBLING: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + s_size; + ctx->xt->dst_start = p_out + s_size + s_width / 2; + break; + case YUV420_DMA_V_ODD_DOUBLING: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + (5 * s_size) / 4; + ctx->xt->dst_start = p_out + (5 * s_size) / 4 + s_width / 2; + break; + case YUYV_DMA_ODD: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width * 2; + ctx->xt->sgl[0].icg = s_width * 2; + ctx->xt->src_start = p_in; + ctx->xt->dst_start = p_out; + break; + case YUYV_DMA_EVEN: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width * 2; + ctx->xt->sgl[0].icg = s_width * 2; + ctx->xt->src_start = p_in + s_size; + ctx->xt->dst_start = p_out + s_width * 2; + break; + case YUYV_DMA_EVEN_DOUBLING: + default: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width * 2; + ctx->xt->sgl[0].icg = s_width * 2; + ctx->xt->src_start = p_in; + ctx->xt->dst_start = p_out + s_width * 2; + break; + } + + /* Common parameters for al transfers */ + ctx->xt->frame_size = 1; + ctx->xt->dir = DMA_MEM_TO_MEM; + ctx->xt->src_sgl = false; + ctx->xt->dst_sgl = true; + flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT | + DMA_COMPL_SKIP_DEST_UNMAP | DMA_COMPL_SKIP_SRC_UNMAP; + + tx = dmadev->device_prep_interleaved_dma(chan, ctx->xt, flags); + if (tx == NULL) { + v4l2_warn(&pcdev->v4l2_dev, "DMA interleaved prep error\n"); + return; + } + + if (do_callback) { + tx->callback = dma_callback; + tx->callback_param = ctx; + } + + ctx->cookie = dmaengine_submit(tx); + if (dma_submit_error(ctx->cookie)) { + v4l2_warn(&pcdev->v4l2_dev, + "DMA submit error %d with src=0x%x dst=0x%x len=0x%x\n", + ctx->cookie, (unsigned)p_in, (unsigned)p_out, + s_size * 3/2); + return; + } + + dma_async_issue_pending(chan); +} + +static void deinterlace_device_run(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + struct deinterlace_q_data *dst_q_data; + + atomic_set(&ctx->dev->busy, 1); + + dprintk(ctx->dev, "%s: DMA try issue.\n", __func__); + + dst_q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_CAPTURE); + + /* + * 4 possible field conversions are possible at the moment: + * V4L2_FIELD_SEQ_TB --> V4L2_FIELD_INTERLACED_TB: + * two separate fields in the same input buffer are interlaced + * in the output buffer using weaving. Top field comes first. + * V4L2_FIELD_SEQ_TB --> V4L2_FIELD_NONE: + * top field from the input buffer is copied to the output buffer + * using line doubling. Bottom field from the input buffer is discarded. + * V4L2_FIELD_SEQ_BT --> V4L2_FIELD_INTERLACED_BT: + * two separate fields in the same input buffer are interlaced + * in the output buffer using weaving. Bottom field comes first. + * V4L2_FIELD_SEQ_BT --> V4L2_FIELD_NONE: + * bottom field from the input buffer is copied to the output buffer + * using line doubling. Top field from the input buffer is discarded. + */ + switch (dst_q_data->fmt->fourcc) { + case V4L2_PIX_FMT_YUV420: + switch (dst_q_data->field) { + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + dprintk(ctx->dev, "%s: yuv420 interlaced tb.\n", + __func__); + deinterlace_issue_dma(ctx, YUV420_DMA_Y_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_Y_EVEN, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_U_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_U_EVEN, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_V_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_V_EVEN, 1); + break; + case V4L2_FIELD_NONE: + default: + dprintk(ctx->dev, "%s: yuv420 interlaced line doubling.\n", + __func__); + deinterlace_issue_dma(ctx, YUV420_DMA_Y_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_Y_ODD_DOUBLING, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_U_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_U_ODD_DOUBLING, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_V_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_V_ODD_DOUBLING, 1); + break; + } + break; + case V4L2_PIX_FMT_YUYV: + default: + switch (dst_q_data->field) { + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + dprintk(ctx->dev, "%s: yuyv interlaced_tb.\n", + __func__); + deinterlace_issue_dma(ctx, YUYV_DMA_ODD, 0); + deinterlace_issue_dma(ctx, YUYV_DMA_EVEN, 1); + break; + case V4L2_FIELD_NONE: + default: + dprintk(ctx->dev, "%s: yuyv interlaced line doubling.\n", + __func__); + deinterlace_issue_dma(ctx, YUYV_DMA_ODD, 0); + deinterlace_issue_dma(ctx, YUYV_DMA_EVEN_DOUBLING, 1); + break; + } + break; + } + + dprintk(ctx->dev, "%s: DMA issue done.\n", __func__); +} + +/* + * video ioctls + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strlcpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); + strlcpy(cap->card, MEM2MEM_NAME, sizeof(cap->card)); + strlcpy(cap->bus_info, MEM2MEM_NAME, sizeof(cap->card)); + /* + * This is only a mem-to-mem video device. The capture and output + * device capability flags are left only for backward compatibility + * and are scheduled for removal. + */ + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct deinterlace_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + strlcpy(f->description, fmt->name, sizeof(f->description)); + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int vidioc_g_fmt(struct deinterlace_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct deinterlace_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = q_data->field; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + + switch (q_data->fmt->fourcc) { + case V4L2_PIX_FMT_YUV420: + f->fmt.pix.bytesperline = q_data->width * 3 / 2; + break; + case V4L2_PIX_FMT_YUYV: + default: + f->fmt.pix.bytesperline = q_data->width * 2; + } + + f->fmt.pix.sizeimage = q_data->sizeimage; + f->fmt.pix.colorspace = ctx->colorspace; + + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_try_fmt(struct v4l2_format *f, struct deinterlace_fmt *fmt) +{ + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV420: + f->fmt.pix.bytesperline = f->fmt.pix.width * 3 / 2; + break; + case V4L2_PIX_FMT_YUYV: + default: + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + } + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_fmt *fmt; + struct deinterlace_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + + f->fmt.pix.colorspace = ctx->colorspace; + + if (f->fmt.pix.field != V4L2_FIELD_INTERLACED_TB && + f->fmt.pix.field != V4L2_FIELD_INTERLACED_BT && + f->fmt.pix.field != V4L2_FIELD_NONE) + f->fmt.pix.field = V4L2_FIELD_INTERLACED_TB; + + return vidioc_try_fmt(f, fmt); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_fmt *fmt; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + + if (!f->fmt.pix.colorspace) + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + + if (f->fmt.pix.field != V4L2_FIELD_SEQ_TB && + f->fmt.pix.field != V4L2_FIELD_SEQ_BT) + f->fmt.pix.field = V4L2_FIELD_SEQ_TB; + + return vidioc_try_fmt(f, fmt); +} + +static int vidioc_s_fmt(struct deinterlace_ctx *ctx, struct v4l2_format *f) +{ + struct deinterlace_q_data *q_data; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt = find_format(f); + if (!q_data->fmt) { + v4l2_err(&ctx->dev->v4l2_dev, + "Couldn't set format type %d, wxh: %dx%d. fmt: %d, field: %d\n", + f->type, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat, f->fmt.pix.field); + return -EINVAL; + } + + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + q_data->field = f->fmt.pix.field; + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV420: + f->fmt.pix.bytesperline = f->fmt.pix.width * 3 / 2; + q_data->sizeimage = (q_data->width * q_data->height * 3) / 2; + break; + case V4L2_PIX_FMT_YUYV: + default: + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + q_data->sizeimage = q_data->width * q_data->height * 2; + } + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d, field: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc, + q_data->field); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + return vidioc_s_fmt(priv, f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_ctx *ctx = priv; + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = vidioc_s_fmt(priv, f); + if (!ret) + ctx->colorspace = f->fmt.pix.colorspace; + + return ret; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct deinterlace_ctx *ctx = priv; + + return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct deinterlace_ctx *ctx = priv; + + return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct deinterlace_ctx *ctx = priv; + + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct deinterlace_ctx *ctx = priv; + + return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct deinterlace_q_data *s_q_data, *d_q_data; + struct deinterlace_ctx *ctx = priv; + + s_q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_OUTPUT); + d_q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_CAPTURE); + + /* Check that src and dst queues have the same pix format */ + if (s_q_data->fmt->fourcc != d_q_data->fmt->fourcc) { + v4l2_err(&ctx->dev->v4l2_dev, + "src and dst formats don't match.\n"); + return -EINVAL; + } + + /* Check that input and output deinterlacing types are compatible */ + switch (s_q_data->field) { + case V4L2_FIELD_SEQ_BT: + if (d_q_data->field != V4L2_FIELD_NONE && + d_q_data->field != V4L2_FIELD_INTERLACED_BT) { + v4l2_err(&ctx->dev->v4l2_dev, + "src and dst field conversion [(%d)->(%d)] not supported.\n", + s_q_data->field, d_q_data->field); + return -EINVAL; + } + break; + case V4L2_FIELD_SEQ_TB: + if (d_q_data->field != V4L2_FIELD_NONE && + d_q_data->field != V4L2_FIELD_INTERLACED_TB) { + v4l2_err(&ctx->dev->v4l2_dev, + "src and dst field conversion [(%d)->(%d)] not supported.\n", + s_q_data->field, d_q_data->field); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct deinterlace_ctx *ctx = priv; + + return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); +} + +static const struct v4l2_ioctl_ops deinterlace_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +}; + + +/* + * Queue operations + */ +struct vb2_dc_conf { + struct device *dev; +}; + +static int deinterlace_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); + struct deinterlace_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(vq->type); + + switch (q_data->fmt->fourcc) { + case V4L2_PIX_FMT_YUV420: + size = q_data->width * q_data->height * 3 / 2; + break; + case V4L2_PIX_FMT_YUYV: + default: + size = q_data->width * q_data->height * 2; + } + + *nplanes = 1; + *nbuffers = count; + sizes[0] = size; + + alloc_ctxs[0] = ctx->dev->alloc_ctx; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int deinterlace_buf_prepare(struct vb2_buffer *vb) +{ + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct deinterlace_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void deinterlace_buf_queue(struct vb2_buffer *vb) +{ + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); +} + +static struct vb2_ops deinterlace_qops = { + .queue_setup = deinterlace_queue_setup, + .buf_prepare = deinterlace_buf_prepare, + .buf_queue = deinterlace_buf_queue, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct deinterlace_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &deinterlace_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + q_data[V4L2_M2M_SRC].fmt = &formats[0]; + q_data[V4L2_M2M_SRC].width = 640; + q_data[V4L2_M2M_SRC].height = 480; + q_data[V4L2_M2M_SRC].sizeimage = (640 * 480 * 3) / 2; + q_data[V4L2_M2M_SRC].field = V4L2_FIELD_SEQ_TB; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &deinterlace_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + q_data[V4L2_M2M_DST].fmt = &formats[0]; + q_data[V4L2_M2M_DST].width = 640; + q_data[V4L2_M2M_DST].height = 480; + q_data[V4L2_M2M_DST].sizeimage = (640 * 480 * 3) / 2; + q_data[V4L2_M2M_SRC].field = V4L2_FIELD_INTERLACED_TB; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int deinterlace_open(struct file *file) +{ + struct deinterlace_dev *pcdev = video_drvdata(file); + struct deinterlace_ctx *ctx = NULL; + + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + file->private_data = ctx; + ctx->dev = pcdev; + + ctx->m2m_ctx = v4l2_m2m_ctx_init(pcdev->m2m_dev, ctx, &queue_init); + if (IS_ERR(ctx->m2m_ctx)) { + int ret = PTR_ERR(ctx->m2m_ctx); + + kfree(ctx); + return ret; + } + + ctx->xt = kzalloc(sizeof(struct dma_async_tx_descriptor) + + sizeof(struct data_chunk), GFP_KERNEL); + if (!ctx->xt) { + int ret = PTR_ERR(ctx->xt); + + kfree(ctx); + return ret; + } + + ctx->colorspace = V4L2_COLORSPACE_REC709; + + dprintk(pcdev, "Created instance %p, m2m_ctx: %p\n", ctx, ctx->m2m_ctx); + + return 0; +} + +static int deinterlace_release(struct file *file) +{ + struct deinterlace_dev *pcdev = video_drvdata(file); + struct deinterlace_ctx *ctx = file->private_data; + + dprintk(pcdev, "Releasing instance %p\n", ctx); + + v4l2_m2m_ctx_release(ctx->m2m_ctx); + kfree(ctx->xt); + kfree(ctx); + + return 0; +} + +static unsigned int deinterlace_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct deinterlace_ctx *ctx = file->private_data; + int ret; + + deinterlace_lock(ctx); + ret = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); + deinterlace_unlock(ctx); + + return ret; +} + +static int deinterlace_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct deinterlace_ctx *ctx = file->private_data; + + return v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); +} + +static const struct v4l2_file_operations deinterlace_fops = { + .owner = THIS_MODULE, + .open = deinterlace_open, + .release = deinterlace_release, + .poll = deinterlace_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = deinterlace_mmap, +}; + +static struct video_device deinterlace_videodev = { + .name = MEM2MEM_NAME, + .fops = &deinterlace_fops, + .ioctl_ops = &deinterlace_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, +}; + +static struct v4l2_m2m_ops m2m_ops = { + .device_run = deinterlace_device_run, + .job_ready = deinterlace_job_ready, + .job_abort = deinterlace_job_abort, + .lock = deinterlace_lock, + .unlock = deinterlace_unlock, +}; + +static int deinterlace_probe(struct platform_device *pdev) +{ + struct deinterlace_dev *pcdev; + struct video_device *vfd; + dma_cap_mask_t mask; + int ret = 0; + + pcdev = kzalloc(sizeof *pcdev, GFP_KERNEL); + if (!pcdev) + return -ENOMEM; + + spin_lock_init(&pcdev->irqlock); + + dma_cap_zero(mask); + dma_cap_set(DMA_INTERLEAVE, mask); + pcdev->dma_chan = dma_request_channel(mask, NULL, pcdev); + if (!pcdev->dma_chan) + goto free_dev; + + if (!dma_has_cap(DMA_INTERLEAVE, pcdev->dma_chan->device->cap_mask)) { + v4l2_err(&pcdev->v4l2_dev, "DMA does not support INTERLEAVE\n"); + goto rel_dma; + } + + ret = v4l2_device_register(&pdev->dev, &pcdev->v4l2_dev); + if (ret) + goto rel_dma; + + atomic_set(&pcdev->busy, 0); + mutex_init(&pcdev->dev_mutex); + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&pcdev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_dev; + } + + *vfd = deinterlace_videodev; + vfd->lock = &pcdev->dev_mutex; + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&pcdev->v4l2_dev, "Failed to register video device\n"); + goto rel_vdev; + } + + video_set_drvdata(vfd, pcdev); + snprintf(vfd->name, sizeof(vfd->name), "%s", deinterlace_videodev.name); + pcdev->vfd = vfd; + v4l2_info(&pcdev->v4l2_dev, MEM2MEM_TEST_MODULE_NAME + " Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, pcdev); + + pcdev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(pcdev->alloc_ctx)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to alloc vb2 context\n"); + ret = PTR_ERR(pcdev->alloc_ctx); + goto err_ctx; + } + + pcdev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(pcdev->m2m_dev)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(pcdev->m2m_dev); + goto err_m2m; + } + + return 0; + + v4l2_m2m_release(pcdev->m2m_dev); +err_m2m: + video_unregister_device(pcdev->vfd); +err_ctx: + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); +rel_vdev: + video_device_release(vfd); +unreg_dev: + v4l2_device_unregister(&pcdev->v4l2_dev); +rel_dma: + dma_release_channel(pcdev->dma_chan); +free_dev: + kfree(pcdev); + + return ret; +} + +static int deinterlace_remove(struct platform_device *pdev) +{ + struct deinterlace_dev *pcdev = + (struct deinterlace_dev *)platform_get_drvdata(pdev); + + v4l2_info(&pcdev->v4l2_dev, "Removing " MEM2MEM_TEST_MODULE_NAME); + v4l2_m2m_release(pcdev->m2m_dev); + video_unregister_device(pcdev->vfd); + v4l2_device_unregister(&pcdev->v4l2_dev); + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); + dma_release_channel(pcdev->dma_chan); + kfree(pcdev); + + return 0; +} + +static struct platform_driver deinterlace_pdrv = { + .probe = deinterlace_probe, + .remove = deinterlace_remove, + .driver = { + .name = MEM2MEM_NAME, + .owner = THIS_MODULE, + }, +}; + +static void __exit deinterlace_exit(void) +{ + platform_driver_unregister(&deinterlace_pdrv); +} + +static int __init deinterlace_init(void) +{ + return platform_driver_register(&deinterlace_pdrv); +} + +module_init(deinterlace_init); +module_exit(deinterlace_exit); + diff --git a/drivers/media/video/marvell-ccic/Kconfig b/drivers/media/platform/marvell-ccic/Kconfig similarity index 100% rename from drivers/media/video/marvell-ccic/Kconfig rename to drivers/media/platform/marvell-ccic/Kconfig diff --git a/drivers/media/video/marvell-ccic/Makefile b/drivers/media/platform/marvell-ccic/Makefile similarity index 100% rename from drivers/media/video/marvell-ccic/Makefile rename to drivers/media/platform/marvell-ccic/Makefile diff --git a/drivers/media/video/marvell-ccic/cafe-driver.c b/drivers/media/platform/marvell-ccic/cafe-driver.c similarity index 100% rename from drivers/media/video/marvell-ccic/cafe-driver.c rename to drivers/media/platform/marvell-ccic/cafe-driver.c diff --git a/drivers/media/video/marvell-ccic/mcam-core.c b/drivers/media/platform/marvell-ccic/mcam-core.c similarity index 100% rename from drivers/media/video/marvell-ccic/mcam-core.c rename to drivers/media/platform/marvell-ccic/mcam-core.c diff --git a/drivers/media/video/marvell-ccic/mcam-core.h b/drivers/media/platform/marvell-ccic/mcam-core.h similarity index 100% rename from drivers/media/video/marvell-ccic/mcam-core.h rename to drivers/media/platform/marvell-ccic/mcam-core.h diff --git a/drivers/media/video/marvell-ccic/mmp-driver.c b/drivers/media/platform/marvell-ccic/mmp-driver.c similarity index 100% rename from drivers/media/video/marvell-ccic/mmp-driver.c rename to drivers/media/platform/marvell-ccic/mmp-driver.c diff --git a/drivers/media/platform/mem2mem_testdev.c b/drivers/media/platform/mem2mem_testdev.c new file mode 100644 index 000000000000..d03637537118 --- /dev/null +++ b/drivers/media/platform/mem2mem_testdev.c @@ -0,0 +1,1130 @@ +/* + * A virtual v4l2-mem2mem example device. + * + * This is a virtual device driver for testing mem-to-mem videobuf framework. + * It simulates a device that uses memory buffers for both source and + * destination, processes the data and issues an "irq" (simulated by a timer). + * The device is capable of multi-instance, multi-buffer-per-transaction + * operation (via the mem2mem framework). + * + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, + * Marek Szyprowski, + * + * 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 + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define MEM2MEM_TEST_MODULE_NAME "mem2mem-testdev" + +MODULE_DESCRIPTION("Virtual device for mem2mem framework testing"); +MODULE_AUTHOR("Pawel Osciak, "); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1.1"); + +#define MIN_W 32 +#define MIN_H 32 +#define MAX_W 640 +#define MAX_H 480 +#define DIM_ALIGN_MASK 7 /* 8-byte alignment for line length */ + +/* Flags that indicate a format can be used for capture/output */ +#define MEM2MEM_CAPTURE (1 << 0) +#define MEM2MEM_OUTPUT (1 << 1) + +#define MEM2MEM_NAME "m2m-testdev" + +/* Per queue */ +#define MEM2MEM_DEF_NUM_BUFS VIDEO_MAX_FRAME +/* In bytes, per queue */ +#define MEM2MEM_VID_MEM_LIMIT (16 * 1024 * 1024) + +/* Default transaction time in msec */ +#define MEM2MEM_DEF_TRANSTIME 1000 +/* Default number of buffers per transaction */ +#define MEM2MEM_DEF_TRANSLEN 1 +#define MEM2MEM_COLOR_STEP (0xff >> 4) +#define MEM2MEM_NUM_TILES 8 + +/* Flags that indicate processing mode */ +#define MEM2MEM_HFLIP (1 << 0) +#define MEM2MEM_VFLIP (1 << 1) + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, 1, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + + +static void m2mtest_dev_release(struct device *dev) +{} + +static struct platform_device m2mtest_pdev = { + .name = MEM2MEM_NAME, + .dev.release = m2mtest_dev_release, +}; + +struct m2mtest_fmt { + char *name; + u32 fourcc; + int depth; + /* Types the format can be used for */ + u32 types; +}; + +static struct m2mtest_fmt formats[] = { + { + .name = "RGB565 (BE)", + .fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */ + .depth = 16, + /* Both capture and output format */ + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, + { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + /* Output-only format */ + .types = MEM2MEM_OUTPUT, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +/* Per-queue, driver-specific private data */ +struct m2mtest_q_data { + unsigned int width; + unsigned int height; + unsigned int sizeimage; + struct m2mtest_fmt *fmt; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +#define V4L2_CID_TRANS_TIME_MSEC (V4L2_CID_USER_BASE + 0x1000) +#define V4L2_CID_TRANS_NUM_BUFS (V4L2_CID_USER_BASE + 0x1001) + +static struct m2mtest_fmt *find_format(struct v4l2_format *f) +{ + struct m2mtest_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct m2mtest_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd; + + atomic_t num_inst; + struct mutex dev_mutex; + spinlock_t irqlock; + + struct timer_list timer; + + struct v4l2_m2m_dev *m2m_dev; +}; + +struct m2mtest_ctx { + struct v4l2_fh fh; + struct m2mtest_dev *dev; + + struct v4l2_ctrl_handler hdl; + + /* Processed buffers in this transaction */ + u8 num_processed; + + /* Transaction length (i.e. how many buffers per transaction) */ + u32 translen; + /* Transaction time (i.e. simulated processing time) in milliseconds */ + u32 transtime; + + /* Abort requested by m2m */ + int aborting; + + /* Processing mode */ + int mode; + + enum v4l2_colorspace colorspace; + + struct v4l2_m2m_ctx *m2m_ctx; + + /* Source and destination queue data */ + struct m2mtest_q_data q_data[2]; +}; + +static inline struct m2mtest_ctx *file2ctx(struct file *file) +{ + return container_of(file->private_data, struct m2mtest_ctx, fh); +} + +static struct m2mtest_q_data *get_q_data(struct m2mtest_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &ctx->q_data[V4L2_M2M_SRC]; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &ctx->q_data[V4L2_M2M_DST]; + default: + BUG(); + } + return NULL; +} + + +static int device_process(struct m2mtest_ctx *ctx, + struct vb2_buffer *in_vb, + struct vb2_buffer *out_vb) +{ + struct m2mtest_dev *dev = ctx->dev; + struct m2mtest_q_data *q_data; + u8 *p_in, *p_out; + int x, y, t, w; + int tile_w, bytes_left; + int width, height, bytesperline; + + q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + width = q_data->width; + height = q_data->height; + bytesperline = (q_data->width * q_data->fmt->depth) >> 3; + + p_in = vb2_plane_vaddr(in_vb, 0); + p_out = vb2_plane_vaddr(out_vb, 0); + if (!p_in || !p_out) { + v4l2_err(&dev->v4l2_dev, + "Acquiring kernel pointers to buffers failed\n"); + return -EFAULT; + } + + if (vb2_plane_size(in_vb, 0) > vb2_plane_size(out_vb, 0)) { + v4l2_err(&dev->v4l2_dev, "Output buffer is too small\n"); + return -EINVAL; + } + + tile_w = (width * (q_data[V4L2_M2M_DST].fmt->depth >> 3)) + / MEM2MEM_NUM_TILES; + bytes_left = bytesperline - tile_w * MEM2MEM_NUM_TILES; + w = 0; + + switch (ctx->mode) { + case MEM2MEM_HFLIP | MEM2MEM_VFLIP: + p_out += bytesperline * height - bytes_left; + for (y = 0; y < height; ++y) { + for (t = 0; t < MEM2MEM_NUM_TILES; ++t) { + if (w & 0x1) { + for (x = 0; x < tile_w; ++x) + *--p_out = *p_in++ + + MEM2MEM_COLOR_STEP; + } else { + for (x = 0; x < tile_w; ++x) + *--p_out = *p_in++ - + MEM2MEM_COLOR_STEP; + } + ++w; + } + p_in += bytes_left; + p_out -= bytes_left; + } + break; + + case MEM2MEM_HFLIP: + for (y = 0; y < height; ++y) { + p_out += MEM2MEM_NUM_TILES * tile_w; + for (t = 0; t < MEM2MEM_NUM_TILES; ++t) { + if (w & 0x01) { + for (x = 0; x < tile_w; ++x) + *--p_out = *p_in++ + + MEM2MEM_COLOR_STEP; + } else { + for (x = 0; x < tile_w; ++x) + *--p_out = *p_in++ - + MEM2MEM_COLOR_STEP; + } + ++w; + } + p_in += bytes_left; + p_out += bytesperline; + } + break; + + case MEM2MEM_VFLIP: + p_out += bytesperline * (height - 1); + for (y = 0; y < height; ++y) { + for (t = 0; t < MEM2MEM_NUM_TILES; ++t) { + if (w & 0x1) { + for (x = 0; x < tile_w; ++x) + *p_out++ = *p_in++ + + MEM2MEM_COLOR_STEP; + } else { + for (x = 0; x < tile_w; ++x) + *p_out++ = *p_in++ - + MEM2MEM_COLOR_STEP; + } + ++w; + } + p_in += bytes_left; + p_out += bytes_left - 2 * bytesperline; + } + break; + + default: + for (y = 0; y < height; ++y) { + for (t = 0; t < MEM2MEM_NUM_TILES; ++t) { + if (w & 0x1) { + for (x = 0; x < tile_w; ++x) + *p_out++ = *p_in++ + + MEM2MEM_COLOR_STEP; + } else { + for (x = 0; x < tile_w; ++x) + *p_out++ = *p_in++ - + MEM2MEM_COLOR_STEP; + } + ++w; + } + p_in += bytes_left; + p_out += bytes_left; + } + } + + return 0; +} + +static void schedule_irq(struct m2mtest_dev *dev, int msec_timeout) +{ + dprintk(dev, "Scheduling a simulated irq\n"); + mod_timer(&dev->timer, jiffies + msecs_to_jiffies(msec_timeout)); +} + +/* + * mem2mem callbacks + */ + +/** + * job_ready() - check whether an instance is ready to be scheduled to run + */ +static int job_ready(void *priv) +{ + struct m2mtest_ctx *ctx = priv; + + if (v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) < ctx->translen + || v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx) < ctx->translen) { + dprintk(ctx->dev, "Not enough buffers available\n"); + return 0; + } + + return 1; +} + +static void job_abort(void *priv) +{ + struct m2mtest_ctx *ctx = priv; + + /* Will cancel the transaction in the next interrupt handler */ + ctx->aborting = 1; +} + +static void m2mtest_lock(void *priv) +{ + struct m2mtest_ctx *ctx = priv; + struct m2mtest_dev *dev = ctx->dev; + mutex_lock(&dev->dev_mutex); +} + +static void m2mtest_unlock(void *priv) +{ + struct m2mtest_ctx *ctx = priv; + struct m2mtest_dev *dev = ctx->dev; + mutex_unlock(&dev->dev_mutex); +} + + +/* device_run() - prepares and starts the device + * + * This simulates all the immediate preparations required before starting + * a device. This will be called by the framework when it decides to schedule + * a particular instance. + */ +static void device_run(void *priv) +{ + struct m2mtest_ctx *ctx = priv; + struct m2mtest_dev *dev = ctx->dev; + struct vb2_buffer *src_buf, *dst_buf; + + src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + + device_process(ctx, src_buf, dst_buf); + + /* Run a timer, which simulates a hardware irq */ + schedule_irq(dev, ctx->transtime); +} + +static void device_isr(unsigned long priv) +{ + struct m2mtest_dev *m2mtest_dev = (struct m2mtest_dev *)priv; + struct m2mtest_ctx *curr_ctx; + struct vb2_buffer *src_vb, *dst_vb; + unsigned long flags; + + curr_ctx = v4l2_m2m_get_curr_priv(m2mtest_dev->m2m_dev); + + if (NULL == curr_ctx) { + printk(KERN_ERR + "Instance released before the end of transaction\n"); + return; + } + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->m2m_ctx); + + curr_ctx->num_processed++; + + spin_lock_irqsave(&m2mtest_dev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&m2mtest_dev->irqlock, flags); + + if (curr_ctx->num_processed == curr_ctx->translen + || curr_ctx->aborting) { + dprintk(curr_ctx->dev, "Finishing transaction\n"); + curr_ctx->num_processed = 0; + v4l2_m2m_job_finish(m2mtest_dev->m2m_dev, curr_ctx->m2m_ctx); + } else { + device_run(curr_ctx); + } +} + +/* + * video ioctls + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strncpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver) - 1); + strncpy(cap->card, MEM2MEM_NAME, sizeof(cap->card) - 1); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", MEM2MEM_NAME); + cap->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct m2mtest_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + strncpy(f->description, fmt->name, sizeof(f->description) - 1); + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int vidioc_g_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct m2mtest_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + f->fmt.pix.bytesperline = (q_data->width * q_data->fmt->depth) >> 3; + f->fmt.pix.sizeimage = q_data->sizeimage; + f->fmt.pix.colorspace = ctx->colorspace; + + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(file2ctx(file), f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(file2ctx(file), f); +} + +static int vidioc_try_fmt(struct v4l2_format *f, struct m2mtest_fmt *fmt) +{ + enum v4l2_field field; + + field = f->fmt.pix.field; + + if (field == V4L2_FIELD_ANY) + field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != field) + return -EINVAL; + + /* V4L2 specification suggests the driver corrects the format struct + * if any of the dimensions is unsupported */ + f->fmt.pix.field = field; + + if (f->fmt.pix.height < MIN_H) + f->fmt.pix.height = MIN_H; + else if (f->fmt.pix.height > MAX_H) + f->fmt.pix.height = MAX_H; + + if (f->fmt.pix.width < MIN_W) + f->fmt.pix.width = MIN_W; + else if (f->fmt.pix.width > MAX_W) + f->fmt.pix.width = MAX_W; + + f->fmt.pix.width &= ~DIM_ALIGN_MASK; + f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct m2mtest_fmt *fmt; + struct m2mtest_ctx *ctx = file2ctx(file); + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + f->fmt.pix.colorspace = ctx->colorspace; + + return vidioc_try_fmt(f, fmt); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct m2mtest_fmt *fmt; + struct m2mtest_ctx *ctx = file2ctx(file); + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + if (!f->fmt.pix.colorspace) + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + + return vidioc_try_fmt(f, fmt); +} + +static int vidioc_s_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f) +{ + struct m2mtest_q_data *q_data; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt = find_format(f); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + q_data->sizeimage = q_data->width * q_data->height + * q_data->fmt->depth >> 3; + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(file2ctx(file), f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct m2mtest_ctx *ctx = file2ctx(file); + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = vidioc_s_fmt(file2ctx(file), f); + if (!ret) + ctx->colorspace = f->fmt.pix.colorspace; + return ret; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct m2mtest_ctx *ctx = file2ctx(file); + + return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct m2mtest_ctx *ctx = file2ctx(file); + + return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct m2mtest_ctx *ctx = file2ctx(file); + + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct m2mtest_ctx *ctx = file2ctx(file); + + return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct m2mtest_ctx *ctx = file2ctx(file); + + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct m2mtest_ctx *ctx = file2ctx(file); + + return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); +} + +static int m2mtest_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct m2mtest_ctx *ctx = + container_of(ctrl->handler, struct m2mtest_ctx, hdl); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + if (ctrl->val) + ctx->mode |= MEM2MEM_HFLIP; + else + ctx->mode &= ~MEM2MEM_HFLIP; + break; + + case V4L2_CID_VFLIP: + if (ctrl->val) + ctx->mode |= MEM2MEM_VFLIP; + else + ctx->mode &= ~MEM2MEM_VFLIP; + break; + + case V4L2_CID_TRANS_TIME_MSEC: + ctx->transtime = ctrl->val; + break; + + case V4L2_CID_TRANS_NUM_BUFS: + ctx->translen = ctrl->val; + break; + + default: + v4l2_err(&ctx->dev->v4l2_dev, "Invalid control\n"); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops m2mtest_ctrl_ops = { + .s_ctrl = m2mtest_s_ctrl, +}; + + +static const struct v4l2_ioctl_ops m2mtest_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + + +/* + * Queue operations + */ + +static int m2mtest_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct m2mtest_ctx *ctx = vb2_get_drv_priv(vq); + struct m2mtest_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + + size = q_data->width * q_data->height * q_data->fmt->depth >> 3; + + while (size * count > MEM2MEM_VID_MEM_LIMIT) + (count)--; + + *nplanes = 1; + *nbuffers = count; + sizes[0] = size; + + /* + * videobuf2-vmalloc allocator is context-less so no need to set + * alloc_ctxs array. + */ + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int m2mtest_buf_prepare(struct vb2_buffer *vb) +{ + struct m2mtest_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct m2mtest_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void m2mtest_buf_queue(struct vb2_buffer *vb) +{ + struct m2mtest_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); +} + +static void m2mtest_wait_prepare(struct vb2_queue *q) +{ + struct m2mtest_ctx *ctx = vb2_get_drv_priv(q); + m2mtest_unlock(ctx); +} + +static void m2mtest_wait_finish(struct vb2_queue *q) +{ + struct m2mtest_ctx *ctx = vb2_get_drv_priv(q); + m2mtest_lock(ctx); +} + +static struct vb2_ops m2mtest_qops = { + .queue_setup = m2mtest_queue_setup, + .buf_prepare = m2mtest_buf_prepare, + .buf_queue = m2mtest_buf_queue, + .wait_prepare = m2mtest_wait_prepare, + .wait_finish = m2mtest_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) +{ + struct m2mtest_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &m2mtest_qops; + src_vq->mem_ops = &vb2_vmalloc_memops; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &m2mtest_qops; + dst_vq->mem_ops = &vb2_vmalloc_memops; + + return vb2_queue_init(dst_vq); +} + +static const struct v4l2_ctrl_config m2mtest_ctrl_trans_time_msec = { + .ops = &m2mtest_ctrl_ops, + .id = V4L2_CID_TRANS_TIME_MSEC, + .name = "Transaction Time (msec)", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = 1001, + .min = 1, + .max = 10001, + .step = 100, +}; + +static const struct v4l2_ctrl_config m2mtest_ctrl_trans_num_bufs = { + .ops = &m2mtest_ctrl_ops, + .id = V4L2_CID_TRANS_NUM_BUFS, + .name = "Buffers Per Transaction", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = 1, + .min = 1, + .max = MEM2MEM_DEF_NUM_BUFS, + .step = 1, +}; + +/* + * File operations + */ +static int m2mtest_open(struct file *file) +{ + struct m2mtest_dev *dev = video_drvdata(file); + struct m2mtest_ctx *ctx = NULL; + struct v4l2_ctrl_handler *hdl; + int rc = 0; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) { + rc = -ENOMEM; + goto open_unlock; + } + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + hdl = &ctx->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &m2mtest_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &m2mtest_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_custom(hdl, &m2mtest_ctrl_trans_time_msec, NULL); + v4l2_ctrl_new_custom(hdl, &m2mtest_ctrl_trans_num_bufs, NULL); + if (hdl->error) { + rc = hdl->error; + v4l2_ctrl_handler_free(hdl); + goto open_unlock; + } + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + + ctx->q_data[V4L2_M2M_SRC].fmt = &formats[0]; + ctx->q_data[V4L2_M2M_SRC].width = 640; + ctx->q_data[V4L2_M2M_SRC].height = 480; + ctx->q_data[V4L2_M2M_SRC].sizeimage = + ctx->q_data[V4L2_M2M_SRC].width * + ctx->q_data[V4L2_M2M_SRC].height * + (ctx->q_data[V4L2_M2M_SRC].fmt->depth >> 3); + ctx->q_data[V4L2_M2M_DST] = ctx->q_data[V4L2_M2M_SRC]; + ctx->colorspace = V4L2_COLORSPACE_REC709; + + ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->m2m_ctx)) { + rc = PTR_ERR(ctx->m2m_ctx); + + v4l2_ctrl_handler_free(hdl); + kfree(ctx); + goto open_unlock; + } + + v4l2_fh_add(&ctx->fh); + atomic_inc(&dev->num_inst); + + dprintk(dev, "Created instance %p, m2m_ctx: %p\n", ctx, ctx->m2m_ctx); + +open_unlock: + mutex_unlock(&dev->dev_mutex); + return rc; +} + +static int m2mtest_release(struct file *file) +{ + struct m2mtest_dev *dev = video_drvdata(file); + struct m2mtest_ctx *ctx = file2ctx(file); + + dprintk(dev, "Releasing instance %p\n", ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + mutex_lock(&dev->dev_mutex); + v4l2_m2m_ctx_release(ctx->m2m_ctx); + mutex_unlock(&dev->dev_mutex); + kfree(ctx); + + atomic_dec(&dev->num_inst); + + return 0; +} + +static unsigned int m2mtest_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct m2mtest_ctx *ctx = file2ctx(file); + + return v4l2_m2m_poll(file, ctx->m2m_ctx, wait); +} + +static int m2mtest_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct m2mtest_dev *dev = video_drvdata(file); + struct m2mtest_ctx *ctx = file2ctx(file); + int res; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + res = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); + mutex_unlock(&dev->dev_mutex); + return res; +} + +static const struct v4l2_file_operations m2mtest_fops = { + .owner = THIS_MODULE, + .open = m2mtest_open, + .release = m2mtest_release, + .poll = m2mtest_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = m2mtest_mmap, +}; + +static struct video_device m2mtest_videodev = { + .name = MEM2MEM_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &m2mtest_fops, + .ioctl_ops = &m2mtest_ioctl_ops, + .minor = -1, + .release = video_device_release, +}; + +static struct v4l2_m2m_ops m2m_ops = { + .device_run = device_run, + .job_ready = job_ready, + .job_abort = job_abort, + .lock = m2mtest_lock, + .unlock = m2mtest_unlock, +}; + +static int m2mtest_probe(struct platform_device *pdev) +{ + struct m2mtest_dev *dev; + struct video_device *vfd; + int ret; + + dev = kzalloc(sizeof *dev, GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->irqlock); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + goto free_dev; + + atomic_set(&dev->num_inst, 0); + mutex_init(&dev->dev_mutex); + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_dev; + } + + *vfd = m2mtest_videodev; + vfd->lock = &dev->dev_mutex; + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto rel_vdev; + } + + video_set_drvdata(vfd, dev); + snprintf(vfd->name, sizeof(vfd->name), "%s", m2mtest_videodev.name); + dev->vfd = vfd; + v4l2_info(&dev->v4l2_dev, MEM2MEM_TEST_MODULE_NAME + "Device registered as /dev/video%d\n", vfd->num); + + setup_timer(&dev->timer, device_isr, (long)dev); + platform_set_drvdata(pdev, dev); + + dev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dev->m2m_dev); + goto err_m2m; + } + + return 0; + + v4l2_m2m_release(dev->m2m_dev); +err_m2m: + video_unregister_device(dev->vfd); +rel_vdev: + video_device_release(vfd); +unreg_dev: + v4l2_device_unregister(&dev->v4l2_dev); +free_dev: + kfree(dev); + + return ret; +} + +static int m2mtest_remove(struct platform_device *pdev) +{ + struct m2mtest_dev *dev = + (struct m2mtest_dev *)platform_get_drvdata(pdev); + + v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_TEST_MODULE_NAME); + v4l2_m2m_release(dev->m2m_dev); + del_timer_sync(&dev->timer); + video_unregister_device(dev->vfd); + v4l2_device_unregister(&dev->v4l2_dev); + kfree(dev); + + return 0; +} + +static struct platform_driver m2mtest_pdrv = { + .probe = m2mtest_probe, + .remove = m2mtest_remove, + .driver = { + .name = MEM2MEM_NAME, + .owner = THIS_MODULE, + }, +}; + +static void __exit m2mtest_exit(void) +{ + platform_driver_unregister(&m2mtest_pdrv); + platform_device_unregister(&m2mtest_pdev); +} + +static int __init m2mtest_init(void) +{ + int ret; + + ret = platform_device_register(&m2mtest_pdev); + if (ret) + return ret; + + ret = platform_driver_register(&m2mtest_pdrv); + if (ret) + platform_device_unregister(&m2mtest_pdev); + + return 0; +} + +module_init(m2mtest_init); +module_exit(m2mtest_exit); + diff --git a/drivers/media/platform/mx2_emmaprp.c b/drivers/media/platform/mx2_emmaprp.c new file mode 100644 index 000000000000..8f22ce543cf7 --- /dev/null +++ b/drivers/media/platform/mx2_emmaprp.c @@ -0,0 +1,1028 @@ +/* + * Support eMMa-PrP through mem2mem framework. + * + * eMMa-PrP is a piece of HW that allows fetching buffers + * from one memory location and do several operations on + * them such as scaling or format conversion giving, as a result + * a new processed buffer in another memory location. + * + * Based on mem2mem_testdev.c by Pawel Osciak. + * + * Copyright (c) 2011 Vista Silicon S.L. + * Javier Martin + * + * 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 + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define EMMAPRP_MODULE_NAME "mem2mem-emmaprp" + +MODULE_DESCRIPTION("Mem-to-mem device which supports eMMa-PrP present in mx2 SoCs"); +MODULE_AUTHOR("Javier Martin v4l2_dev, "%s: " fmt, __func__, ## arg) + +/* EMMA PrP */ +#define PRP_CNTL 0x00 +#define PRP_INTR_CNTL 0x04 +#define PRP_INTRSTATUS 0x08 +#define PRP_SOURCE_Y_PTR 0x0c +#define PRP_SOURCE_CB_PTR 0x10 +#define PRP_SOURCE_CR_PTR 0x14 +#define PRP_DEST_RGB1_PTR 0x18 +#define PRP_DEST_RGB2_PTR 0x1c +#define PRP_DEST_Y_PTR 0x20 +#define PRP_DEST_CB_PTR 0x24 +#define PRP_DEST_CR_PTR 0x28 +#define PRP_SRC_FRAME_SIZE 0x2c +#define PRP_DEST_CH1_LINE_STRIDE 0x30 +#define PRP_SRC_PIXEL_FORMAT_CNTL 0x34 +#define PRP_CH1_PIXEL_FORMAT_CNTL 0x38 +#define PRP_CH1_OUT_IMAGE_SIZE 0x3c +#define PRP_CH2_OUT_IMAGE_SIZE 0x40 +#define PRP_SRC_LINE_STRIDE 0x44 +#define PRP_CSC_COEF_012 0x48 +#define PRP_CSC_COEF_345 0x4c +#define PRP_CSC_COEF_678 0x50 +#define PRP_CH1_RZ_HORI_COEF1 0x54 +#define PRP_CH1_RZ_HORI_COEF2 0x58 +#define PRP_CH1_RZ_HORI_VALID 0x5c +#define PRP_CH1_RZ_VERT_COEF1 0x60 +#define PRP_CH1_RZ_VERT_COEF2 0x64 +#define PRP_CH1_RZ_VERT_VALID 0x68 +#define PRP_CH2_RZ_HORI_COEF1 0x6c +#define PRP_CH2_RZ_HORI_COEF2 0x70 +#define PRP_CH2_RZ_HORI_VALID 0x74 +#define PRP_CH2_RZ_VERT_COEF1 0x78 +#define PRP_CH2_RZ_VERT_COEF2 0x7c +#define PRP_CH2_RZ_VERT_VALID 0x80 + +#define PRP_CNTL_CH1EN (1 << 0) +#define PRP_CNTL_CH2EN (1 << 1) +#define PRP_CNTL_CSIEN (1 << 2) +#define PRP_CNTL_DATA_IN_YUV420 (0 << 3) +#define PRP_CNTL_DATA_IN_YUV422 (1 << 3) +#define PRP_CNTL_DATA_IN_RGB16 (2 << 3) +#define PRP_CNTL_DATA_IN_RGB32 (3 << 3) +#define PRP_CNTL_CH1_OUT_RGB8 (0 << 5) +#define PRP_CNTL_CH1_OUT_RGB16 (1 << 5) +#define PRP_CNTL_CH1_OUT_RGB32 (2 << 5) +#define PRP_CNTL_CH1_OUT_YUV422 (3 << 5) +#define PRP_CNTL_CH2_OUT_YUV420 (0 << 7) +#define PRP_CNTL_CH2_OUT_YUV422 (1 << 7) +#define PRP_CNTL_CH2_OUT_YUV444 (2 << 7) +#define PRP_CNTL_CH1_LEN (1 << 9) +#define PRP_CNTL_CH2_LEN (1 << 10) +#define PRP_CNTL_SKIP_FRAME (1 << 11) +#define PRP_CNTL_SWRST (1 << 12) +#define PRP_CNTL_CLKEN (1 << 13) +#define PRP_CNTL_WEN (1 << 14) +#define PRP_CNTL_CH1BYP (1 << 15) +#define PRP_CNTL_IN_TSKIP(x) ((x) << 16) +#define PRP_CNTL_CH1_TSKIP(x) ((x) << 19) +#define PRP_CNTL_CH2_TSKIP(x) ((x) << 22) +#define PRP_CNTL_INPUT_FIFO_LEVEL(x) ((x) << 25) +#define PRP_CNTL_RZ_FIFO_LEVEL(x) ((x) << 27) +#define PRP_CNTL_CH2B1EN (1 << 29) +#define PRP_CNTL_CH2B2EN (1 << 30) +#define PRP_CNTL_CH2FEN (1 << 31) + +#define PRP_SIZE_HEIGHT(x) (x) +#define PRP_SIZE_WIDTH(x) ((x) << 16) + +/* IRQ Enable and status register */ +#define PRP_INTR_RDERR (1 << 0) +#define PRP_INTR_CH1WERR (1 << 1) +#define PRP_INTR_CH2WERR (1 << 2) +#define PRP_INTR_CH1FC (1 << 3) +#define PRP_INTR_CH2FC (1 << 5) +#define PRP_INTR_LBOVF (1 << 7) +#define PRP_INTR_CH2OVF (1 << 8) + +#define PRP_INTR_ST_RDERR (1 << 0) +#define PRP_INTR_ST_CH1WERR (1 << 1) +#define PRP_INTR_ST_CH2WERR (1 << 2) +#define PRP_INTR_ST_CH2B2CI (1 << 3) +#define PRP_INTR_ST_CH2B1CI (1 << 4) +#define PRP_INTR_ST_CH1B2CI (1 << 5) +#define PRP_INTR_ST_CH1B1CI (1 << 6) +#define PRP_INTR_ST_LBOVF (1 << 7) +#define PRP_INTR_ST_CH2OVF (1 << 8) + +struct emmaprp_fmt { + char *name; + u32 fourcc; + /* Types the format can be used for */ + u32 types; +}; + +static struct emmaprp_fmt formats[] = { + { + .name = "YUV 4:2:0 Planar", + .fourcc = V4L2_PIX_FMT_YUV420, + .types = MEM2MEM_CAPTURE, + }, + { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .types = MEM2MEM_OUTPUT, + }, +}; + +/* Per-queue, driver-specific private data */ +struct emmaprp_q_data { + unsigned int width; + unsigned int height; + unsigned int sizeimage; + struct emmaprp_fmt *fmt; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static struct emmaprp_fmt *find_format(struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct emmaprp_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd; + + struct mutex dev_mutex; + spinlock_t irqlock; + + int irq_emma; + void __iomem *base_emma; + struct clk *clk_emma_ahb, *clk_emma_ipg; + struct resource *res_emma; + + struct v4l2_m2m_dev *m2m_dev; + struct vb2_alloc_ctx *alloc_ctx; +}; + +struct emmaprp_ctx { + struct emmaprp_dev *dev; + /* Abort requested by m2m */ + int aborting; + struct emmaprp_q_data q_data[2]; + struct v4l2_m2m_ctx *m2m_ctx; +}; + +static struct emmaprp_q_data *get_q_data(struct emmaprp_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &(ctx->q_data[V4L2_M2M_SRC]); + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &(ctx->q_data[V4L2_M2M_DST]); + default: + BUG(); + } + return NULL; +} + +/* + * mem2mem callbacks + */ +static void emmaprp_job_abort(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_dev *pcdev = ctx->dev; + + ctx->aborting = 1; + + dprintk(pcdev, "Aborting task\n"); + + v4l2_m2m_job_finish(pcdev->m2m_dev, ctx->m2m_ctx); +} + +static void emmaprp_lock(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_dev *pcdev = ctx->dev; + mutex_lock(&pcdev->dev_mutex); +} + +static void emmaprp_unlock(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_dev *pcdev = ctx->dev; + mutex_unlock(&pcdev->dev_mutex); +} + +static inline void emmaprp_dump_regs(struct emmaprp_dev *pcdev) +{ + dprintk(pcdev, + "eMMa-PrP Registers:\n" + " SOURCE_Y_PTR = 0x%08X\n" + " SRC_FRAME_SIZE = 0x%08X\n" + " DEST_Y_PTR = 0x%08X\n" + " DEST_CR_PTR = 0x%08X\n" + " DEST_CB_PTR = 0x%08X\n" + " CH2_OUT_IMAGE_SIZE = 0x%08X\n" + " CNTL = 0x%08X\n", + readl(pcdev->base_emma + PRP_SOURCE_Y_PTR), + readl(pcdev->base_emma + PRP_SRC_FRAME_SIZE), + readl(pcdev->base_emma + PRP_DEST_Y_PTR), + readl(pcdev->base_emma + PRP_DEST_CR_PTR), + readl(pcdev->base_emma + PRP_DEST_CB_PTR), + readl(pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE), + readl(pcdev->base_emma + PRP_CNTL)); +} + +static void emmaprp_device_run(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_q_data *s_q_data, *d_q_data; + struct vb2_buffer *src_buf, *dst_buf; + struct emmaprp_dev *pcdev = ctx->dev; + unsigned int s_width, s_height; + unsigned int d_width, d_height; + unsigned int d_size; + dma_addr_t p_in, p_out; + u32 tmp; + + src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + + s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + s_width = s_q_data->width; + s_height = s_q_data->height; + + d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + d_width = d_q_data->width; + d_height = d_q_data->height; + d_size = d_width * d_height; + + p_in = vb2_dma_contig_plane_dma_addr(src_buf, 0); + p_out = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + if (!p_in || !p_out) { + v4l2_err(&pcdev->v4l2_dev, + "Acquiring kernel pointers to buffers failed\n"); + return; + } + + /* Input frame parameters */ + writel(p_in, pcdev->base_emma + PRP_SOURCE_Y_PTR); + writel(PRP_SIZE_WIDTH(s_width) | PRP_SIZE_HEIGHT(s_height), + pcdev->base_emma + PRP_SRC_FRAME_SIZE); + + /* Output frame parameters */ + writel(p_out, pcdev->base_emma + PRP_DEST_Y_PTR); + writel(p_out + d_size, pcdev->base_emma + PRP_DEST_CB_PTR); + writel(p_out + d_size + (d_size >> 2), + pcdev->base_emma + PRP_DEST_CR_PTR); + writel(PRP_SIZE_WIDTH(d_width) | PRP_SIZE_HEIGHT(d_height), + pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE); + + /* IRQ configuration */ + tmp = readl(pcdev->base_emma + PRP_INTR_CNTL); + writel(tmp | PRP_INTR_RDERR | + PRP_INTR_CH2WERR | + PRP_INTR_CH2FC, + pcdev->base_emma + PRP_INTR_CNTL); + + emmaprp_dump_regs(pcdev); + + /* Enable transfer */ + tmp = readl(pcdev->base_emma + PRP_CNTL); + writel(tmp | PRP_CNTL_CH2_OUT_YUV420 | + PRP_CNTL_DATA_IN_YUV422 | + PRP_CNTL_CH2EN, + pcdev->base_emma + PRP_CNTL); +} + +static irqreturn_t emmaprp_irq(int irq_emma, void *data) +{ + struct emmaprp_dev *pcdev = data; + struct emmaprp_ctx *curr_ctx; + struct vb2_buffer *src_vb, *dst_vb; + unsigned long flags; + u32 irqst; + + /* Check irq flags and clear irq */ + irqst = readl(pcdev->base_emma + PRP_INTRSTATUS); + writel(irqst, pcdev->base_emma + PRP_INTRSTATUS); + dprintk(pcdev, "irqst = 0x%08x\n", irqst); + + curr_ctx = v4l2_m2m_get_curr_priv(pcdev->m2m_dev); + if (curr_ctx == NULL) { + pr_err("Instance released before the end of transaction\n"); + return IRQ_HANDLED; + } + + if (!curr_ctx->aborting) { + if ((irqst & PRP_INTR_ST_RDERR) || + (irqst & PRP_INTR_ST_CH2WERR)) { + pr_err("PrP bus error ocurred, this transfer is probably corrupted\n"); + writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); + } else if (irqst & PRP_INTR_ST_CH2B1CI) { /* buffer ready */ + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->m2m_ctx); + + spin_lock_irqsave(&pcdev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&pcdev->irqlock, flags); + } + } + + v4l2_m2m_job_finish(pcdev->m2m_dev, curr_ctx->m2m_ctx); + return IRQ_HANDLED; +} + +/* + * video ioctls + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strncpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver) - 1); + strncpy(cap->card, MEM2MEM_NAME, sizeof(cap->card) - 1); + /* + * This is only a mem-to-mem video device. The capture and output + * device capability flags are left only for backward compatibility + * and are scheduled for removal. + */ + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct emmaprp_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + strlcpy(f->description, fmt->name, sizeof(f->description) - 1); + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int vidioc_g_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct emmaprp_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + f->fmt.pix.bytesperline = q_data->width * 3 / 2; + else /* YUYV */ + f->fmt.pix.bytesperline = q_data->width * 2; + f->fmt.pix.sizeimage = q_data->sizeimage; + + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_try_fmt(struct v4l2_format *f) +{ + enum v4l2_field field; + + + if (!find_format(f)) + return -EINVAL; + + field = f->fmt.pix.field; + if (field == V4L2_FIELD_ANY) + field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != field) + return -EINVAL; + + /* V4L2 specification suggests the driver corrects the format struct + * if any of the dimensions is unsupported */ + f->fmt.pix.field = field; + + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_YUV420, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 3 / 2; + } else { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_OTHERS, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + } + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_s_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct emmaprp_q_data *q_data; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = vidioc_try_fmt(f); + if (ret) + return ret; + + q_data->fmt = find_format(f); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + q_data->sizeimage = q_data->width * q_data->height * 3 / 2; + else /* YUYV */ + q_data->sizeimage = q_data->width * q_data->height * 2; + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); +} + +static const struct v4l2_ioctl_ops emmaprp_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +}; + + +/* + * Queue operations + */ +static int emmaprp_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vq); + struct emmaprp_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + size = q_data->width * q_data->height * 3 / 2; + else + size = q_data->width * q_data->height * 2; + + while (size * count > MEM2MEM_VID_MEM_LIMIT) + (count)--; + + *nplanes = 1; + *nbuffers = count; + sizes[0] = size; + + alloc_ctxs[0] = ctx->dev->alloc_ctx; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int emmaprp_buf_prepare(struct vb2_buffer *vb) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct emmaprp_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, "%s data will not fit into plane" + "(%lu < %lu)\n", __func__, + vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void emmaprp_buf_queue(struct vb2_buffer *vb) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); +} + +static struct vb2_ops emmaprp_qops = { + .queue_setup = emmaprp_queue_setup, + .buf_prepare = emmaprp_buf_prepare, + .buf_queue = emmaprp_buf_queue, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct emmaprp_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &emmaprp_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &emmaprp_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int emmaprp_open(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx; + + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + file->private_data = ctx; + ctx->dev = pcdev; + + if (mutex_lock_interruptible(&pcdev->dev_mutex)) { + kfree(ctx); + return -ERESTARTSYS; + } + + ctx->m2m_ctx = v4l2_m2m_ctx_init(pcdev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->m2m_ctx)) { + int ret = PTR_ERR(ctx->m2m_ctx); + + mutex_unlock(&pcdev->dev_mutex); + kfree(ctx); + return ret; + } + + clk_prepare_enable(pcdev->clk_emma_ipg); + clk_prepare_enable(pcdev->clk_emma_ahb); + ctx->q_data[V4L2_M2M_SRC].fmt = &formats[1]; + ctx->q_data[V4L2_M2M_DST].fmt = &formats[0]; + mutex_unlock(&pcdev->dev_mutex); + + dprintk(pcdev, "Created instance %p, m2m_ctx: %p\n", ctx, ctx->m2m_ctx); + + return 0; +} + +static int emmaprp_release(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx = file->private_data; + + dprintk(pcdev, "Releasing instance %p\n", ctx); + + mutex_lock(&pcdev->dev_mutex); + clk_disable_unprepare(pcdev->clk_emma_ahb); + clk_disable_unprepare(pcdev->clk_emma_ipg); + v4l2_m2m_ctx_release(ctx->m2m_ctx); + mutex_unlock(&pcdev->dev_mutex); + kfree(ctx); + + return 0; +} + +static unsigned int emmaprp_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx = file->private_data; + unsigned int res; + + mutex_lock(&pcdev->dev_mutex); + res = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); + mutex_unlock(&pcdev->dev_mutex); + return res; +} + +static int emmaprp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx = file->private_data; + int ret; + + if (mutex_lock_interruptible(&pcdev->dev_mutex)) + return -ERESTARTSYS; + ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); + mutex_unlock(&pcdev->dev_mutex); + return ret; +} + +static const struct v4l2_file_operations emmaprp_fops = { + .owner = THIS_MODULE, + .open = emmaprp_open, + .release = emmaprp_release, + .poll = emmaprp_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = emmaprp_mmap, +}; + +static struct video_device emmaprp_videodev = { + .name = MEM2MEM_NAME, + .fops = &emmaprp_fops, + .ioctl_ops = &emmaprp_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, +}; + +static struct v4l2_m2m_ops m2m_ops = { + .device_run = emmaprp_device_run, + .job_abort = emmaprp_job_abort, + .lock = emmaprp_lock, + .unlock = emmaprp_unlock, +}; + +static int emmaprp_probe(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev; + struct video_device *vfd; + struct resource *res_emma; + int irq_emma; + int ret; + + pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); + if (!pcdev) + return -ENOMEM; + + spin_lock_init(&pcdev->irqlock); + + pcdev->clk_emma_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(pcdev->clk_emma_ipg)) { + return PTR_ERR(pcdev->clk_emma_ipg); + } + + pcdev->clk_emma_ahb = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(pcdev->clk_emma_ahb)) + return PTR_ERR(pcdev->clk_emma_ahb); + + irq_emma = platform_get_irq(pdev, 0); + res_emma = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (irq_emma < 0 || res_emma == NULL) { + dev_err(&pdev->dev, "Missing platform resources data\n"); + return -ENODEV; + } + + ret = v4l2_device_register(&pdev->dev, &pcdev->v4l2_dev); + if (ret) + return ret; + + mutex_init(&pcdev->dev_mutex); + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&pcdev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_dev; + } + + *vfd = emmaprp_videodev; + vfd->lock = &pcdev->dev_mutex; + + video_set_drvdata(vfd, pcdev); + snprintf(vfd->name, sizeof(vfd->name), "%s", emmaprp_videodev.name); + pcdev->vfd = vfd; + v4l2_info(&pcdev->v4l2_dev, EMMAPRP_MODULE_NAME + " Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, pcdev); + + pcdev->base_emma = devm_request_and_ioremap(&pdev->dev, res_emma); + if (!pcdev->base_emma) { + ret = -ENXIO; + goto rel_vdev; + } + + pcdev->irq_emma = irq_emma; + pcdev->res_emma = res_emma; + + if (devm_request_irq(&pdev->dev, pcdev->irq_emma, emmaprp_irq, + 0, MEM2MEM_NAME, pcdev) < 0) { + ret = -ENODEV; + goto rel_vdev; + } + + pcdev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(pcdev->alloc_ctx)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to alloc vb2 context\n"); + ret = PTR_ERR(pcdev->alloc_ctx); + goto rel_vdev; + } + + pcdev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(pcdev->m2m_dev)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(pcdev->m2m_dev); + goto rel_ctx; + } + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&pcdev->v4l2_dev, "Failed to register video device\n"); + goto rel_m2m; + } + + return 0; + + +rel_m2m: + v4l2_m2m_release(pcdev->m2m_dev); +rel_ctx: + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); +rel_vdev: + video_device_release(vfd); +unreg_dev: + v4l2_device_unregister(&pcdev->v4l2_dev); + + return ret; +} + +static int emmaprp_remove(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev = platform_get_drvdata(pdev); + + v4l2_info(&pcdev->v4l2_dev, "Removing " EMMAPRP_MODULE_NAME); + + video_unregister_device(pcdev->vfd); + v4l2_m2m_release(pcdev->m2m_dev); + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); + v4l2_device_unregister(&pcdev->v4l2_dev); + + return 0; +} + +static struct platform_driver emmaprp_pdrv = { + .probe = emmaprp_probe, + .remove = emmaprp_remove, + .driver = { + .name = MEM2MEM_NAME, + .owner = THIS_MODULE, + }, +}; + +static void __exit emmaprp_exit(void) +{ + platform_driver_unregister(&emmaprp_pdrv); +} + +static int __init emmaprp_init(void) +{ + return platform_driver_register(&emmaprp_pdrv); +} + +module_init(emmaprp_init); +module_exit(emmaprp_exit); diff --git a/drivers/media/video/omap/Kconfig b/drivers/media/platform/omap/Kconfig similarity index 100% rename from drivers/media/video/omap/Kconfig rename to drivers/media/platform/omap/Kconfig diff --git a/drivers/media/platform/omap/Makefile b/drivers/media/platform/omap/Makefile new file mode 100644 index 000000000000..d80df41fde28 --- /dev/null +++ b/drivers/media/platform/omap/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the omap video device drivers. +# + +# OMAP2/3 Display driver +omap-vout-y += omap_vout.o omap_voutlib.o +omap-vout-$(CONFIG_VIDEO_OMAP2_VOUT_VRFB) += omap_vout_vrfb.o +obj-$(CONFIG_VIDEO_OMAP2_VOUT) += omap-vout.o diff --git a/drivers/media/platform/omap/omap_vout.c b/drivers/media/platform/omap/omap_vout.c new file mode 100644 index 000000000000..66ac21d466af --- /dev/null +++ b/drivers/media/platform/omap/omap_vout.c @@ -0,0 +1,2291 @@ +/* + * omap_vout.c + * + * Copyright (C) 2005-2010 Texas Instruments. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * Leveraged code from the OMAP2 camera driver + * Video-for-Linux (Version 2) camera capture driver for + * the OMAP24xx camera controller. + * + * Author: Andy Lowe (source@mvista.com) + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2010 Texas Instruments. + * + * History: + * 20-APR-2006 Khasim Modified VRFB based Rotation, + * The image data is always read from 0 degree + * view and written + * to the virtual space of desired rotation angle + * 4-DEC-2006 Jian Changed to support better memory management + * + * 17-Nov-2008 Hardik Changed driver to use video_ioctl2 + * + * 23-Feb-2010 Vaibhav H Modified to use new DSS2 interface + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include