dect
/
linux-2.6
Archived
13
0
Fork 0

Merge branch 'v4l_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media

Pull media updates from Mauro Carvalho Chehab:
 - V4L2 API additions to better support JPEG compression control
 - media API additions to properly support MPEG decoders
 - V4L2 API additions for image crop/scaling
 - a few other V4L2 API DocBook fixes/improvements
 - two new DVB frontend drivers: m88rs2000 and rtl2830
 - two new DVB drivers: az6007 and rtl28xxu
 - a framework for ISA drivers, that removed lots of common code found
   at the ISA radio drivers
 - a new FM transmitter driver (radio-keene)
 - a GPIO-based IR receiver driver
 - a new sensor driver: mt9m032
 - some new video drivers: adv7183, blackfin, mx2_emmaprp, sii9234_drv,
   vs6624
 - several new board additions, driver fixes, improvements and cleanups.

* 'v4l_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (295 commits)
  [media] update CARDLIST.em28xx
  [media] partially reverts changeset fa5527c
  [media] stb0899: fix the limits for signal strength values
  [media] em28xx: support for 2304:0242 PCTV QuatroStick (510e)
  [media] em28xx: support for 2013:0251 PCTV QuatroStick nano (520e)
  [media] -EINVAL -> -ENOTTY
  [media] gspca - sn9c20x: Cleanup source
  [media] gspca - sn9c20x: Simplify register write for capture start/stop
  [media] gspca - sn9c20x: Add automatic JPEG compression mechanism
  [media] gspca - sn9c20x: Greater delay in case of sensor no response
  [media] gspca - sn9c20x: Optimize the code of write sequences
  [media] gspca - sn9c20x: Add the JPEG compression quality control
  [media] gspca - sn9c20x: Add a delay after Omnivision sensor reset
  [media] gspca - sn9c20x: Propagate USB errors to higher level
  [media] gspca - sn9c20x: Use the new video control mechanism
  [media] gspca - sn9c20x: Fix loss of frame start
  [media] gspca - zc3xx: Lack of register 08 value for sensor cs2102k
  [media] gspca - ov534_9: Add brightness to OmniVision 5621 sensor
  [media] gspca - zc3xx: Add V4L2_CID_JPEG_COMPRESSION_QUALITY control support
  [media] pvrusb2: fix 7MHz & 8MHz DVB-T tuner support for HVR1900 rev D1F5
  ...
This commit is contained in:
Linus Torvalds 2012-03-23 14:39:09 -07:00
commit e317234975
310 changed files with 18567 additions and 7095 deletions

View File

@ -128,6 +128,26 @@ url="http://www.ijg.org">http://www.ijg.org</ulink>)</corpauthor>
<subtitle>Version 1.02</subtitle>
</biblioentry>
<biblioentry id="itu-t81">
<abbrev>ITU-T.81</abbrev>
<authorgroup>
<corpauthor>International Telecommunication Union
(<ulink url="http://www.itu.int">http://www.itu.int</ulink>)</corpauthor>
</authorgroup>
<title>ITU-T Recommendation T.81
"Information Technology &mdash; Digital Compression and Coding of Continous-Tone
Still Images &mdash; Requirements and Guidelines"</title>
</biblioentry>
<biblioentry id="w3c-jpeg-jfif">
<abbrev>W3C JPEG JFIF</abbrev>
<authorgroup>
<corpauthor>The World Wide Web Consortium (<ulink
url="http://www.w3.org/Graphics/JPEG">http://www.w3.org</ulink>)</corpauthor>
</authorgroup>
<title>JPEG JFIF</title>
</biblioentry>
<biblioentry id="smpte12m">
<abbrev>SMPTE&nbsp;12M</abbrev>
<authorgroup>

View File

@ -2393,6 +2393,20 @@ details.</para>
to the <link linkend="control">User controls class</link>.
</para>
</listitem>
<listitem>
<para>Added the device_caps field to struct v4l2_capabilities and added the new
V4L2_CAP_DEVICE_CAPS capability.</para>
</listitem>
</orderedlist>
</section>
<section>
<title>V4L2 in Linux 3.4</title>
<orderedlist>
<listitem>
<para>Added <link linkend="jpeg-controls">JPEG compression control
class</link>.</para>
</listitem>
</orderedlist>
</section>

View File

@ -1284,6 +1284,49 @@ values are:</entry>
capturing. This is not done by muting audio hardware, which can still
produce a slight hiss, but in the encoder itself, guaranteeing a fixed
and reproducible audio bitstream. 0 = unmuted, 1 = muted.</entry>
</row>
<row><entry></entry></row>
<row id="v4l2-mpeg-audio-dec-playback">
<entry spanname="id"><constant>V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK</constant>&nbsp;</entry>
<entry>enum&nbsp;v4l2_mpeg_audio_dec_playback</entry>
</row><row><entry spanname="descr">Determines how monolingual audio should be played back.
Possible values are:</entry>
</row>
<row>
<entrytbl spanname="descr" cols="2">
<tbody valign="top">
<row>
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO</constant>&nbsp;</entry>
<entry>Automatically determines the best playback mode.</entry>
</row>
<row>
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_STEREO</constant>&nbsp;</entry>
<entry>Stereo playback.</entry>
</row>
<row>
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_LEFT</constant>&nbsp;</entry>
<entry>Left channel playback.</entry>
</row>
<row>
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_RIGHT</constant>&nbsp;</entry>
<entry>Right channel playback.</entry>
</row>
<row>
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_MONO</constant>&nbsp;</entry>
<entry>Mono playback.</entry>
</row>
<row>
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_SWAPPED_STEREO</constant>&nbsp;</entry>
<entry>Stereo playback with swapped left and right channels.</entry>
</row>
</tbody>
</entrytbl>
</row>
<row><entry></entry></row>
<row id="v4l2-mpeg-audio-dec-multilingual-playback">
<entry spanname="id"><constant>V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK</constant>&nbsp;</entry>
<entry>enum&nbsp;v4l2_mpeg_audio_dec_playback</entry>
</row><row><entry spanname="descr">Determines how multilingual audio should be played back.</entry>
</row>
<row><entry></entry></row>
<row id="v4l2-mpeg-video-encoding">
@ -1447,6 +1490,22 @@ of the video. The supplied 32-bit integer is interpreted as follows (bit
</tbody>
</entrytbl>
</row>
<row><entry></entry></row>
<row id="v4l2-mpeg-video-dec-pts">
<entry spanname="id"><constant>V4L2_CID_MPEG_VIDEO_DEC_PTS</constant>&nbsp;</entry>
<entry>integer64</entry>
</row><row><entry spanname="descr">This read-only control returns the
33-bit video Presentation Time Stamp as defined in ITU T-REC-H.222.0 and ISO/IEC 13818-1 of
the currently displayed frame. This is the same PTS as is used in &VIDIOC-DECODER-CMD;.</entry>
</row>
<row><entry></entry></row>
<row id="v4l2-mpeg-video-dec-frame">
<entry spanname="id"><constant>V4L2_CID_MPEG_VIDEO_DEC_FRAME</constant>&nbsp;</entry>
<entry>integer64</entry>
</row><row><entry spanname="descr">This read-only control returns the
frame counter of the frame that is currently displayed (decoded). This value is reset to 0 whenever
the decoder is started.</entry>
</row>
<row><entry></entry></row>
@ -3377,6 +3436,167 @@ interface and may change in the future.</para>
</tbody>
</tgroup>
</table>
</section>
<section id="jpeg-controls">
<title>JPEG Control Reference</title>
<para>The JPEG class includes controls for common features of JPEG
encoders and decoders. Currently it includes features for codecs
implementing progressive baseline DCT compression process with
Huffman entrophy coding.</para>
<table pgwide="1" frame="none" id="jpeg-control-id">
<title>JPEG Control IDs</title>
<tgroup cols="4">
<colspec colname="c1" colwidth="1*" />
<colspec colname="c2" colwidth="6*" />
<colspec colname="c3" colwidth="2*" />
<colspec colname="c4" colwidth="6*" />
<spanspec namest="c1" nameend="c2" spanname="id" />
<spanspec namest="c2" nameend="c4" spanname="descr" />
<thead>
<row>
<entry spanname="id" align="left">ID</entry>
<entry align="left">Type</entry>
</row><row rowsep="1"><entry spanname="descr" align="left">Description</entry>
</row>
</thead>
<tbody valign="top">
<row><entry></entry></row>
<row>
<entry spanname="id"><constant>V4L2_CID_JPEG_CLASS</constant>&nbsp;</entry>
<entry>class</entry>
</row><row><entry spanname="descr">The JPEG class descriptor. Calling
&VIDIOC-QUERYCTRL; for this control will return a description of this
control class.
</entry>
</row>
<row>
<entry spanname="id"><constant>V4L2_CID_JPEG_CHROMA_SUBSAMPLING</constant></entry>
<entry>menu</entry>
</row>
<row id="jpeg-chroma-subsampling-control">
<entry spanname="descr">The chroma subsampling factors describe how
each component of an input image is sampled, in respect to maximum
sample rate in each spatial dimension. See <xref linkend="itu-t81"/>,
clause A.1.1. for more details. The <constant>
V4L2_CID_JPEG_CHROMA_SUBSAMPLING</constant> control determines how
Cb and Cr components are downsampled after coverting an input image
from RGB to Y'CbCr color space.
</entry>
</row>
<row>
<entrytbl spanname="descr" cols="2">
<tbody valign="top">
<row>
<entry><constant>V4L2_JPEG_CHROMA_SUBSAMPLING_444</constant>
</entry><entry>No chroma subsampling, each pixel has
Y, Cr and Cb values.</entry>
</row>
<row>
<entry><constant>V4L2_JPEG_CHROMA_SUBSAMPLING_422</constant>
</entry><entry>Horizontally subsample Cr, Cb components
by a factor of 2.</entry>
</row>
<row>
<entry><constant>V4L2_JPEG_CHROMA_SUBSAMPLING_420</constant>
</entry><entry>Subsample Cr, Cb components horizontally
and vertically by 2.</entry>
</row>
<row>
<entry><constant>V4L2_JPEG_CHROMA_SUBSAMPLING_411</constant>
</entry><entry>Horizontally subsample Cr, Cb components
by a factor of 4.</entry>
</row>
<row>
<entry><constant>V4L2_JPEG_CHROMA_SUBSAMPLING_410</constant>
</entry><entry>Subsample Cr, Cb components horizontally
by 4 and vertically by 2.</entry>
</row>
<row>
<entry><constant>V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY</constant>
</entry><entry>Use only luminance component.</entry>
</row>
</tbody>
</entrytbl>
</row>
<row>
<entry spanname="id"><constant>V4L2_CID_JPEG_RESTART_INTERVAL</constant>
</entry><entry>integer</entry>
</row>
<row><entry spanname="descr">
The restart interval determines an interval of inserting RSTm
markers (m = 0..7). The purpose of these markers is to additionally
reinitialize the encoder process, in order to process blocks of
an image independently.
For the lossy compression processes the restart interval unit is
MCU (Minimum Coded Unit) and its value is contained in DRI
(Define Restart Interval) marker. If <constant>
V4L2_CID_JPEG_RESTART_INTERVAL</constant> control is set to 0,
DRI and RSTm markers will not be inserted.
</entry>
</row>
<row id="jpeg-quality-control">
<entry spanname="id"><constant>V4L2_CID_JPEG_COMPRESION_QUALITY</constant></entry>
<entry>integer</entry>
</row>
<row>
<entry spanname="descr">
<constant>V4L2_CID_JPEG_COMPRESION_QUALITY</constant> control
determines trade-off between image quality and size.
It provides simpler method for applications to control image quality,
without a need for direct reconfiguration of luminance and chrominance
quantization tables.
In cases where a driver uses quantization tables configured directly
by an application, using interfaces defined elsewhere, <constant>
V4L2_CID_JPEG_COMPRESION_QUALITY</constant> control should be set
by driver to 0.
<para>The value range of this control is driver-specific. Only
positive, non-zero values are meaningful. The recommended range
is 1 - 100, where larger values correspond to better image quality.
</para>
</entry>
</row>
<row id="jpeg-active-marker-control">
<entry spanname="id"><constant>V4L2_CID_JPEG_ACTIVE_MARKER</constant></entry>
<entry>bitmask</entry>
</row>
<row>
<entry spanname="descr">Specify which JPEG markers are included
in compressed stream. This control is valid only for encoders.
</entry>
</row>
<row>
<entrytbl spanname="descr" cols="2">
<tbody valign="top">
<row>
<entry><constant>V4L2_JPEG_ACTIVE_MARKER_APP0</constant></entry>
<entry>Application data segment APP<subscript>0</subscript>.</entry>
</row><row>
<entry><constant>V4L2_JPEG_ACTIVE_MARKER_APP1</constant></entry>
<entry>Application data segment APP<subscript>1</subscript>.</entry>
</row><row>
<entry><constant>V4L2_JPEG_ACTIVE_MARKER_COM</constant></entry>
<entry>Comment segment.</entry>
</row><row>
<entry><constant>V4L2_JPEG_ACTIVE_MARKER_DQT</constant></entry>
<entry>Quantization tables segment.</entry>
</row><row>
<entry><constant>V4L2_JPEG_ACTIVE_MARKER_DHT</constant></entry>
<entry>Huffman tables segment.</entry>
</row>
</tbody>
</entrytbl>
</row>
<row><entry></entry></row>
</tbody>
</tgroup>
</table>
<para>For more details about JPEG specification, refer
to <xref linkend="itu-t81"/>, <xref linkend="jfif"/>,
<xref linkend="w3c-jpeg-jfif"/>.</para>
</section>
</section>

View File

@ -52,6 +52,10 @@ cropping and composing rectangles have the same size.</para>
</textobject>
</mediaobject>
</figure>
For complete list of the available selection targets see table <xref
linkend="v4l2-sel-target"/>
</section>
<section>
@ -186,7 +190,7 @@ V4L2_SEL_TGT_COMPOSE_ACTIVE </constant> target.</para>
<section>
<title>Scaling control.</title>
<title>Scaling control</title>
<para>An application can detect if scaling is performed by comparing the width
and the height of rectangles obtained using <constant> V4L2_SEL_TGT_CROP_ACTIVE
@ -200,7 +204,7 @@ the scaling ratios using these values.</para>
<section>
<title>Comparison with old cropping API.</title>
<title>Comparison with old cropping API</title>
<para>The selection API was introduced to cope with deficiencies of previous
<link linkend="crop"> API </link>, that was designed to control simple capture

View File

@ -127,6 +127,22 @@ structs, ioctls) must be noted in more detail in the history chapter
(compat.xml), along with the possible impact on existing drivers and
applications. -->
<revision>
<revnumber>3.4</revnumber>
<date>2012-01-25</date>
<authorinitials>sn</authorinitials>
<revremark>Added <link linkend="jpeg-controls">JPEG compression
control class.</link>
</revremark>
</revision>
<revision>
<revnumber>3.3</revnumber>
<date>2012-01-11</date>
<authorinitials>hv</authorinitials>
<revremark>Added device_caps field to struct v4l2_capabilities.</revremark>
</revision>
<revision>
<revnumber>3.2</revnumber>
<date>2011-08-26</date>
@ -417,7 +433,7 @@ and discussions on the V4L mailing list.</revremark>
</partinfo>
<title>Video for Linux Two API Specification</title>
<subtitle>Revision 3.2</subtitle>
<subtitle>Revision 3.3</subtitle>
<chapter id="common">
&sub-common;
@ -473,6 +489,7 @@ and discussions on the V4L mailing list.</revremark>
&sub-cropcap;
&sub-dbg-g-chip-ident;
&sub-dbg-g-register;
&sub-decoder-cmd;
&sub-dqevent;
&sub-encoder-cmd;
&sub-enumaudio;

View File

@ -0,0 +1,256 @@
<refentry id="vidioc-decoder-cmd">
<refmeta>
<refentrytitle>ioctl VIDIOC_DECODER_CMD, VIDIOC_TRY_DECODER_CMD</refentrytitle>
&manvol;
</refmeta>
<refnamediv>
<refname>VIDIOC_DECODER_CMD</refname>
<refname>VIDIOC_TRY_DECODER_CMD</refname>
<refpurpose>Execute an decoder command</refpurpose>
</refnamediv>
<refsynopsisdiv>
<funcsynopsis>
<funcprototype>
<funcdef>int <function>ioctl</function></funcdef>
<paramdef>int <parameter>fd</parameter></paramdef>
<paramdef>int <parameter>request</parameter></paramdef>
<paramdef>struct v4l2_decoder_cmd *<parameter>argp</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><parameter>fd</parameter></term>
<listitem>
<para>&fd;</para>
</listitem>
</varlistentry>
<varlistentry>
<term><parameter>request</parameter></term>
<listitem>
<para>VIDIOC_DECODER_CMD, VIDIOC_TRY_DECODER_CMD</para>
</listitem>
</varlistentry>
<varlistentry>
<term><parameter>argp</parameter></term>
<listitem>
<para></para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Description</title>
<note>
<title>Experimental</title>
<para>This is an <link linkend="experimental">experimental</link>
interface and may change in the future.</para>
</note>
<para>These ioctls control an audio/video (usually MPEG-) decoder.
<constant>VIDIOC_DECODER_CMD</constant> sends a command to the
decoder, <constant>VIDIOC_TRY_DECODER_CMD</constant> can be used to
try a command without actually executing it. To send a command applications
must initialize all fields of a &v4l2-decoder-cmd; and call
<constant>VIDIOC_DECODER_CMD</constant> or <constant>VIDIOC_TRY_DECODER_CMD</constant>
with a pointer to this structure.</para>
<para>The <structfield>cmd</structfield> field must contain the
command code. Some commands use the <structfield>flags</structfield> field for
additional information.
</para>
<para>A <function>write</function>() or &VIDIOC-STREAMON; call sends an implicit
START command to the decoder if it has not been started yet.
</para>
<para>A <function>close</function>() or &VIDIOC-STREAMOFF; call of a streaming
file descriptor sends an implicit immediate STOP command to the decoder, and all
buffered data is discarded.</para>
<para>These ioctls are optional, not all drivers may support
them. They were introduced in Linux 3.3.</para>
<table pgwide="1" frame="none" id="v4l2-decoder-cmd">
<title>struct <structname>v4l2_decoder_cmd</structname></title>
<tgroup cols="5">
&cs-str;
<tbody valign="top">
<row>
<entry>__u32</entry>
<entry><structfield>cmd</structfield></entry>
<entry></entry>
<entry></entry>
<entry>The decoder command, see <xref linkend="decoder-cmds" />.</entry>
</row>
<row>
<entry>__u32</entry>
<entry><structfield>flags</structfield></entry>
<entry></entry>
<entry></entry>
<entry>Flags to go with the command. If no flags are defined for
this command, drivers and applications must set this field to zero.</entry>
</row>
<row>
<entry>union</entry>
<entry>(anonymous)</entry>
<entry></entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry></entry>
<entry>struct</entry>
<entry><structfield>start</structfield></entry>
<entry></entry>
<entry>Structure containing additional data for the
<constant>V4L2_DEC_CMD_START</constant> command.</entry>
</row>
<row>
<entry></entry>
<entry></entry>
<entry>__s32</entry>
<entry><structfield>speed</structfield></entry>
<entry>Playback speed and direction. The playback speed is defined as
<structfield>speed</structfield>/1000 of the normal speed. So 1000 is normal playback.
Negative numbers denote reverse playback, so -1000 does reverse playback at normal
speed. Speeds -1, 0 and 1 have special meanings: speed 0 is shorthand for 1000
(normal playback). A speed of 1 steps just one frame forward, a speed of -1 steps
just one frame back.
</entry>
</row>
<row>
<entry></entry>
<entry></entry>
<entry>__u32</entry>
<entry><structfield>format</structfield></entry>
<entry>Format restrictions. This field is set by the driver, not the
application. Possible values are <constant>V4L2_DEC_START_FMT_NONE</constant> if
there are no format restrictions or <constant>V4L2_DEC_START_FMT_GOP</constant>
if the decoder operates on full GOPs (<wordasword>Group Of Pictures</wordasword>).
This is usually the case for reverse playback: the decoder needs full GOPs, which
it can then play in reverse order. So to implement reverse playback the application
must feed the decoder the last GOP in the video file, then the GOP before that, etc. etc.
</entry>
</row>
<row>
<entry></entry>
<entry>struct</entry>
<entry><structfield>stop</structfield></entry>
<entry></entry>
<entry>Structure containing additional data for the
<constant>V4L2_DEC_CMD_STOP</constant> command.</entry>
</row>
<row>
<entry></entry>
<entry></entry>
<entry>__u64</entry>
<entry><structfield>pts</structfield></entry>
<entry>Stop playback at this <structfield>pts</structfield> or immediately
if the playback is already past that timestamp. Leave to 0 if you want to stop after the
last frame was decoded.
</entry>
</row>
<row>
<entry></entry>
<entry>struct</entry>
<entry><structfield>raw</structfield></entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry></entry>
<entry></entry>
<entry>__u32</entry>
<entry><structfield>data</structfield>[16]</entry>
<entry>Reserved for future extensions. Drivers and
applications must set the array to zero.</entry>
</row>
</tbody>
</tgroup>
</table>
<table pgwide="1" frame="none" id="decoder-cmds">
<title>Decoder Commands</title>
<tgroup cols="3">
&cs-def;
<tbody valign="top">
<row>
<entry><constant>V4L2_DEC_CMD_START</constant></entry>
<entry>0</entry>
<entry>Start the decoder. When the decoder is already
running or paused, this command will just change the playback speed.
That means that calling <constant>V4L2_DEC_CMD_START</constant> when
the decoder was paused will <emphasis>not</emphasis> resume the decoder.
You have to explicitly call <constant>V4L2_DEC_CMD_RESUME</constant> for that.
This command has one flag:
<constant>V4L2_DEC_CMD_START_MUTE_AUDIO</constant>. If set, then audio will
be muted when playing back at a non-standard speed.
</entry>
</row>
<row>
<entry><constant>V4L2_DEC_CMD_STOP</constant></entry>
<entry>1</entry>
<entry>Stop the decoder. When the decoder is already stopped,
this command does nothing. This command has two flags:
if <constant>V4L2_DEC_CMD_STOP_TO_BLACK</constant> is set, then the decoder will
set the picture to black after it stopped decoding. Otherwise the last image will
repeat. If <constant>V4L2_DEC_CMD_STOP_IMMEDIATELY</constant> is set, then the decoder
stops immediately (ignoring the <structfield>pts</structfield> value), otherwise it
will keep decoding until timestamp >= pts or until the last of the pending data from
its internal buffers was decoded.
</entry>
</row>
<row>
<entry><constant>V4L2_DEC_CMD_PAUSE</constant></entry>
<entry>2</entry>
<entry>Pause the decoder. When the decoder has not been
started yet, the driver will return an &EPERM;. When the decoder is
already paused, this command does nothing. This command has one flag:
if <constant>V4L2_DEC_CMD_PAUSE_TO_BLACK</constant> is set, then set the
decoder output to black when paused.
</entry>
</row>
<row>
<entry><constant>V4L2_DEC_CMD_RESUME</constant></entry>
<entry>3</entry>
<entry>Resume decoding after a PAUSE command. When the
decoder has not been started yet, the driver will return an &EPERM;.
When the decoder is already running, this command does nothing. No
flags are defined for this command.</entry>
</row>
</tbody>
</tgroup>
</table>
</refsect1>
<refsect1>
&return-value;
<variablelist>
<varlistentry>
<term><errorcode>EINVAL</errorcode></term>
<listitem>
<para>The <structfield>cmd</structfield> field is invalid.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><errorcode>EPERM</errorcode></term>
<listitem>
<para>The application sent a PAUSE or RESUME command when
the decoder was not running.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
</refentry>

View File

@ -74,15 +74,16 @@ only used by the STOP command and contains one bit: If the
encoding will continue until the end of the current <wordasword>Group
Of Pictures</wordasword>, otherwise it will stop immediately.</para>
<para>A <function>read</function>() call sends a START command to
the encoder if it has not been started yet. After a STOP command,
<para>A <function>read</function>() or &VIDIOC-STREAMON; call sends an implicit
START command to the encoder if it has not been started yet. After a STOP command,
<function>read</function>() calls will read the remaining data
buffered by the driver. When the buffer is empty,
<function>read</function>() will return zero and the next
<function>read</function>() call will restart the encoder.</para>
<para>A <function>close</function>() call sends an immediate STOP
to the encoder, and all buffered data is discarded.</para>
<para>A <function>close</function>() or &VIDIOC-STREAMOFF; call of a streaming
file descriptor sends an implicit immediate STOP to the encoder, and all buffered
data is discarded.</para>
<para>These ioctls are optional, not all drivers may support
them. They were introduced in Linux 2.6.21.</para>

View File

@ -57,6 +57,11 @@
<refsect1>
<title>Description</title>
<para>These ioctls are <emphasis role="bold">deprecated</emphasis>.
New drivers and applications should use <link linkend="jpeg-controls">
JPEG class controls</link> for image quality and JPEG markers control.
</para>
<para>[to do]</para>
<para>Ronald Bultje elaborates:</para>
@ -86,7 +91,10 @@ to add them.</para>
<row>
<entry>int</entry>
<entry><structfield>quality</structfield></entry>
<entry></entry>
<entry>Deprecated. If <link linkend="jpeg-quality-control"><constant>
V4L2_CID_JPEG_IMAGE_QUALITY</constant></link> control is exposed by
a driver applications should use it instead and ignore this field.
</entry>
</row>
<row>
<entry>int</entry>
@ -116,7 +124,11 @@ to add them.</para>
<row>
<entry>__u32</entry>
<entry><structfield>jpeg_markers</structfield></entry>
<entry>See <xref linkend="jpeg-markers" />.</entry>
<entry>See <xref linkend="jpeg-markers"/>. Deprecated.
If <link linkend="jpeg-active-marker-control"><constant>
V4L2_CID_JPEG_ACTIVE_MARKER</constant></link> control
is exposed by a driver applications should use it instead
and ignore this field.</entry>
</row>
</tbody>
</tgroup>

View File

@ -58,43 +58,43 @@
<para>The ioctls are used to query and configure selection rectangles.</para>
<para> To query the cropping (composing) rectangle set <structfield>
&v4l2-selection;::type </structfield> to the respective buffer type. Do not
use multiplanar buffers. Use <constant> V4L2_BUF_TYPE_VIDEO_CAPTURE
<para> To query the cropping (composing) rectangle set &v4l2-selection;
<structfield> type </structfield> field to the respective buffer type.
Do not use multiplanar buffers. Use <constant> V4L2_BUF_TYPE_VIDEO_CAPTURE
</constant> instead of <constant> V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
</constant>. Use <constant> V4L2_BUF_TYPE_VIDEO_OUTPUT </constant> instead of
<constant> V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE </constant>. The next step is
setting <structfield> &v4l2-selection;::target </structfield> to value
<constant> V4L2_SEL_TGT_CROP_ACTIVE </constant> (<constant>
setting the value of &v4l2-selection; <structfield>target</structfield> field
to <constant> V4L2_SEL_TGT_CROP_ACTIVE </constant> (<constant>
V4L2_SEL_TGT_COMPOSE_ACTIVE </constant>). Please refer to table <xref
linkend="v4l2-sel-target" /> or <xref linkend="selection-api" /> for additional
targets. Fields <structfield> &v4l2-selection;::flags </structfield> and
<structfield> &v4l2-selection;::reserved </structfield> are ignored and they
must be filled with zeros. The driver fills the rest of the structure or
targets. The <structfield>flags</structfield> and <structfield>reserved
</structfield> fields of &v4l2-selection; are ignored and they must be filled
with zeros. The driver fills the rest of the structure or
returns &EINVAL; if incorrect buffer type or target was used. If cropping
(composing) is not supported then the active rectangle is not mutable and it is
always equal to the bounds rectangle. Finally, structure <structfield>
&v4l2-selection;::r </structfield> is filled with the current cropping
always equal to the bounds rectangle. Finally, the &v4l2-rect;
<structfield>r</structfield> rectangle is filled with the current cropping
(composing) coordinates. The coordinates are expressed in driver-dependent
units. The only exception are rectangles for images in raw formats, whose
coordinates are always expressed in pixels. </para>
<para> To change the cropping (composing) rectangle set <structfield>
&v4l2-selection;::type </structfield> to the respective buffer type. Do not
<para> To change the cropping (composing) rectangle set the &v4l2-selection;
<structfield>type</structfield> field to the respective buffer type. Do not
use multiplanar buffers. Use <constant> V4L2_BUF_TYPE_VIDEO_CAPTURE
</constant> instead of <constant> V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
</constant>. Use <constant> V4L2_BUF_TYPE_VIDEO_OUTPUT </constant> instead of
<constant> V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE </constant>. The next step is
setting <structfield> &v4l2-selection;::target </structfield> to value
<constant> V4L2_SEL_TGT_CROP_ACTIVE </constant> (<constant>
setting the value of &v4l2-selection; <structfield>target</structfield> to
<constant>V4L2_SEL_TGT_CROP_ACTIVE</constant> (<constant>
V4L2_SEL_TGT_COMPOSE_ACTIVE </constant>). Please refer to table <xref
linkend="v4l2-sel-target" /> or <xref linkend="selection-api" /> for additional
targets. Set desired active area into the field <structfield>
&v4l2-selection;::r </structfield>. Field <structfield>
&v4l2-selection;::reserved </structfield> is ignored and must be filled with
zeros. The driver may adjust the rectangle coordinates. An application may
introduce constraints to control rounding behaviour. Set the field
<structfield> &v4l2-selection;::flags </structfield> to one of values:
targets. The &v4l2-rect; <structfield>r</structfield> rectangle need to be
set to the desired active area. Field &v4l2-selection; <structfield> reserved
</structfield> is ignored and must be filled with zeros. The driver may adjust
coordinates of the requested rectangle. An application may
introduce constraints to control rounding behaviour. The &v4l2-selection;
<structfield>flags</structfield> field must be set to one of the following:
<itemizedlist>
<listitem>
@ -129,7 +129,7 @@ and vertical offset and sizes are chosen according to following priority:
<orderedlist>
<listitem>
<para>Satisfy constraints from <structfield>&v4l2-selection;::flags</structfield>.</para>
<para>Satisfy constraints from &v4l2-selection; <structfield>flags</structfield>.</para>
</listitem>
<listitem>
<para>Adjust width, height, left, and top to hardware limits and alignments.</para>
@ -145,7 +145,7 @@ and vertical offset and sizes are chosen according to following priority:
</listitem>
</orderedlist>
On success the field <structfield> &v4l2-selection;::r </structfield> contains
On success the &v4l2-rect; <structfield>r</structfield> field contains
the adjusted rectangle. When the parameters are unsuitable the application may
modify the cropping (composing) or image parameters and repeat the cycle until
satisfactory parameters have been negotiated. If constraints flags have to be
@ -162,38 +162,38 @@ exist no rectangle </emphasis> that satisfies the constraints.</para>
<tbody valign="top">
<row>
<entry><constant>V4L2_SEL_TGT_CROP_ACTIVE</constant></entry>
<entry>0</entry>
<entry>area that is currently cropped by hardware</entry>
<entry>0x0000</entry>
<entry>The area that is currently cropped by hardware.</entry>
</row>
<row>
<entry><constant>V4L2_SEL_TGT_CROP_DEFAULT</constant></entry>
<entry>1</entry>
<entry>suggested cropping rectangle that covers the "whole picture"</entry>
<entry>0x0001</entry>
<entry>Suggested cropping rectangle that covers the "whole picture".</entry>
</row>
<row>
<entry><constant>V4L2_SEL_TGT_CROP_BOUNDS</constant></entry>
<entry>2</entry>
<entry>limits for the cropping rectangle</entry>
<entry>0x0002</entry>
<entry>Limits for the cropping rectangle.</entry>
</row>
<row>
<entry><constant>V4L2_SEL_TGT_COMPOSE_ACTIVE</constant></entry>
<entry>256</entry>
<entry>area to which data are composed by hardware</entry>
<entry>0x0100</entry>
<entry>The area to which data is composed by hardware.</entry>
</row>
<row>
<entry><constant>V4L2_SEL_TGT_COMPOSE_DEFAULT</constant></entry>
<entry>257</entry>
<entry>suggested composing rectangle that covers the "whole picture"</entry>
<entry>0x0101</entry>
<entry>Suggested composing rectangle that covers the "whole picture".</entry>
</row>
<row>
<entry><constant>V4L2_SEL_TGT_COMPOSE_BOUNDS</constant></entry>
<entry>258</entry>
<entry>limits for the composing rectangle</entry>
<entry>0x0102</entry>
<entry>Limits for the composing rectangle.</entry>
</row>
<row>
<entry><constant>V4L2_SEL_TGT_COMPOSE_PADDED</constant></entry>
<entry>259</entry>
<entry>the active area and all padding pixels that are inserted or modified by the hardware</entry>
<entry>0x0103</entry>
<entry>The active area and all padding pixels that are inserted or modified by hardware.</entry>
</row>
</tbody>
</tgroup>
@ -209,12 +209,14 @@ exist no rectangle </emphasis> that satisfies the constraints.</para>
<row>
<entry><constant>V4L2_SEL_FLAG_GE</constant></entry>
<entry>0x00000001</entry>
<entry>indicate that adjusted rectangle must contain a rectangle from <structfield>&v4l2-selection;::r</structfield></entry>
<entry>Indicates that the adjusted rectangle must contain the original
&v4l2-selection; <structfield>r</structfield> rectangle.</entry>
</row>
<row>
<entry><constant>V4L2_SEL_FLAG_LE</constant></entry>
<entry>0x00000002</entry>
<entry>indicate that adjusted rectangle must be inside a rectangle from <structfield>&v4l2-selection;::r</structfield></entry>
<entry>Indicates that the adjusted rectangle must be inside the original
&v4l2-rect; <structfield>r</structfield> rectangle.</entry>
</row>
</tbody>
</tgroup>
@ -245,27 +247,29 @@ exist no rectangle </emphasis> that satisfies the constraints.</para>
<row>
<entry>__u32</entry>
<entry><structfield>type</structfield></entry>
<entry>Type of the buffer (from &v4l2-buf-type;)</entry>
<entry>Type of the buffer (from &v4l2-buf-type;).</entry>
</row>
<row>
<entry>__u32</entry>
<entry><structfield>target</structfield></entry>
<entry>used to select between <link linkend="v4l2-sel-target"> cropping and composing rectangles </link></entry>
<entry>Used to select between <link linkend="v4l2-sel-target"> cropping
and composing rectangles</link>.</entry>
</row>
<row>
<entry>__u32</entry>
<entry><structfield>flags</structfield></entry>
<entry>control over coordinates adjustments, refer to <link linkend="v4l2-sel-flags">selection flags</link></entry>
<entry>Flags controlling the selection rectangle adjustments, refer to
<link linkend="v4l2-sel-flags">selection flags</link>.</entry>
</row>
<row>
<entry>&v4l2-rect;</entry>
<entry><structfield>r</structfield></entry>
<entry>selection rectangle</entry>
<entry>The selection rectangle.</entry>
</row>
<row>
<entry>__u32</entry>
<entry><structfield>reserved[9]</structfield></entry>
<entry>Reserved fields for future use</entry>
<entry>Reserved fields for future use.</entry>
</row>
</tbody>
</tgroup>
@ -278,24 +282,24 @@ exist no rectangle </emphasis> that satisfies the constraints.</para>
<varlistentry>
<term><errorcode>EINVAL</errorcode></term>
<listitem>
<para>The buffer <structfield> &v4l2-selection;::type </structfield>
or <structfield> &v4l2-selection;::target </structfield> is not supported, or
the <structfield> &v4l2-selection;::flags </structfield> are invalid.</para>
<para>Given buffer type <structfield>type</structfield> or
the selection target <structfield>target</structfield> is not supported,
or the <structfield>flags</structfield> argument is not valid.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><errorcode>ERANGE</errorcode></term>
<listitem>
<para>it is not possible to adjust a rectangle <structfield>
&v4l2-selection;::r </structfield> that satisfies all contraints from
<structfield> &v4l2-selection;::flags </structfield>.</para>
<para>It is not possible to adjust &v4l2-rect; <structfield>
r</structfield> rectangle to satisfy all contraints given in the
<structfield>flags</structfield> argument.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><errorcode>EBUSY</errorcode></term>
<listitem>
<para>it is not possible to apply change of selection rectangle at the moment.
Usually because streaming is in progress.</para>
<para>It is not possible to apply change of the selection rectangle
at the moment. Usually because streaming is in progress.</para>
</listitem>
</varlistentry>
</variablelist>

View File

@ -124,12 +124,35 @@ printf ("Version: %u.%u.%u\n",
<row>
<entry>__u32</entry>
<entry><structfield>capabilities</structfield></entry>
<entry>Device capabilities, see <xref
linkend="device-capabilities" />.</entry>
<entry>Available capabilities of the physical device as a whole, see <xref
linkend="device-capabilities" />. The same physical device can export
multiple devices in /dev (e.g. /dev/videoX, /dev/vbiY and /dev/radioZ).
The <structfield>capabilities</structfield> field should contain a union
of all capabilities available around the several V4L2 devices exported
to userspace.
For all those devices the <structfield>capabilities</structfield> field
returns the same set of capabilities. This allows applications to open
just one of the devices (typically the video device) and discover whether
video, vbi and/or radio are also supported.
</entry>
</row>
<row>
<entry>__u32</entry>
<entry><structfield>reserved</structfield>[4]</entry>
<entry><structfield>device_caps</structfield></entry>
<entry>Device capabilities of the opened device, see <xref
linkend="device-capabilities" />. Should contain the available capabilities
of that specific device node. So, for example, <structfield>device_caps</structfield>
of a radio device will only contain radio related capabilities and
no video or vbi capabilities. This field is only set if the <structfield>capabilities</structfield>
field contains the <constant>V4L2_CAP_DEVICE_CAPS</constant> capability.
Only the <structfield>capabilities</structfield> field can have the
<constant>V4L2_CAP_DEVICE_CAPS</constant> capability, <structfield>device_caps</structfield>
will never set <constant>V4L2_CAP_DEVICE_CAPS</constant>.
</entry>
</row>
<row>
<entry>__u32</entry>
<entry><structfield>reserved</structfield>[3]</entry>
<entry>Reserved for future extensions. Drivers must set
this array to zero.</entry>
</row>
@ -276,6 +299,13 @@ linkend="async">asynchronous</link> I/O methods.</entry>
<entry>The device supports the <link
linkend="mmap">streaming</link> I/O method.</entry>
</row>
<row>
<entry><constant>V4L2_CAP_DEVICE_CAPS</constant></entry>
<entry>0x80000000</entry>
<entry>The driver fills the <structfield>device_caps</structfield>
field. This capability can only appear in the <structfield>capabilities</structfield>
field and never in the <structfield>device_caps</structfield> field.</entry>
</row>
</tbody>
</tgroup>
</table>

View File

@ -96,8 +96,8 @@ field and the &v4l2-tuner; <structfield>index</structfield> field.</entry>
<row>
<entry>__u32</entry>
<entry><structfield>reserved</structfield>[7]</entry>
<entry>Reserved for future extensions. Drivers and
applications must set the array to zero.</entry>
<entry>Reserved for future extensions. Applications
must set the array to zero.</entry>
</row>
</tbody>
</tgroup>
@ -112,7 +112,7 @@ field and the &v4l2-tuner; <structfield>index</structfield> field.</entry>
<term><errorcode>EINVAL</errorcode></term>
<listitem>
<para>The <structfield>tuner</structfield> index is out of
bounds or the value in the <structfield>type</structfield> field is
bounds, the wrap_around value is not supported or the value in the <structfield>type</structfield> field is
wrong.</para>
</listitem>
</varlistentry>

View File

@ -119,4 +119,5 @@ o Cards based on the Phillips saa7134 PCI bridge:
- Compro Videomate DVB-T300
- Compro Videomate DVB-T200
- AVerMedia AVerTVHD MCE A180
- KWorld PC150-U ATSC Hybrid

View File

@ -66,5 +66,16 @@ dd if=US290D.sys ibs=1 skip=36856 count=3976 of=dvb-usb-lme2510-s0194.fw
For LME2510C
dd if=US290D.sys ibs=1 skip=33152 count=3697 of=dvb-usb-lme2510c-s0194.fw
---------------------------------------------------------------------
The m88rs2000 tuner driver can be found in windows/system32/drivers
US2B0D.sys (dated 29 Jun 2010)
dd if=US2B0D.sys ibs=1 skip=34432 count=3871 of=dvb-usb-lme2510c-rs2000.fw
We need to modify id of rs2000 firmware or it will warm boot id 3344:1120.
echo -ne \\xF0\\x22 | dd conv=notrunc bs=1 count=2 seek=266 of=dvb-usb-lme2510c-rs2000.fw
Copy the firmware file(s) to /lib/firmware

View File

@ -32,3 +32,4 @@
31 -> Leadtek Winfast PxDVR3200 H XC4000 [107d:6f39]
32 -> MPX-885
33 -> Mygica X8507 [14f1:8502]
34 -> TerraTec Cinergy T PCIe Dual [153b:117e]

View File

@ -59,7 +59,7 @@
58 -> Pinnacle PCTV HD 800i [11bd:0051]
59 -> DViCO FusionHDTV 5 PCI nano [18ac:d530]
60 -> Pinnacle Hybrid PCTV [12ab:1788]
61 -> Leadtek TV2000 XP Global [107d:6f18,107d:6618]
61 -> Leadtek TV2000 XP Global [107d:6f18,107d:6618,107d:6619]
62 -> PowerColor RA330 [14f1:ea3d]
63 -> Geniatech X8000-MT DVBT [14f1:8852]
64 -> DViCO FusionHDTV DVB-T PRO [18ac:db30]
@ -87,3 +87,5 @@
86 -> TeVii S464 DVB-S/S2 [d464:9022]
87 -> Leadtek WinFast DTV2000 H PLUS [107d:6f42]
88 -> Leadtek WinFast DTV1800 H (XC4000) [107d:6f38]
89 -> Leadtek TV2000 XP Global (SC4100) [107d:6f36]
90 -> Leadtek TV2000 XP Global (XC4100) [107d:6f43]

View File

@ -7,7 +7,7 @@
6 -> Terratec Cinergy 200 USB (em2800)
7 -> Leadtek Winfast USB II (em2800) [0413:6023]
8 -> Kworld USB2800 (em2800)
9 -> Pinnacle Dazzle DVC 90/100/101/107 / Kaiser Baas Video to DVD maker (em2820/em2840) [1b80:e302,1b80:e304,2304:0207,2304:021a]
9 -> Pinnacle Dazzle DVC 90/100/101/107 / Kaiser Baas Video to DVD maker (em2820/em2840) [1b80:e302,1b80:e304,2304:0207,2304:021a,093b:a003]
10 -> Hauppauge WinTV HVR 900 (em2880) [2040:6500]
11 -> Terratec Hybrid XS (em2880)
12 -> Kworld PVR TV 2800 RF (em2820/em2840)
@ -61,7 +61,7 @@
61 -> Pixelview PlayTV Box 4 USB 2.0 (em2820/em2840)
62 -> Gadmei TVR200 (em2820/em2840)
63 -> Kaiomy TVnPC U2 (em2860) [eb1a:e303]
64 -> Easy Cap Capture DC-60 (em2860)
64 -> Easy Cap Capture DC-60 (em2860) [1b80:e309]
65 -> IO-DATA GV-MVP/SZ (em2820/em2840) [04bb:0515]
66 -> Empire dual TV (em2880)
67 -> Terratec Grabby (em2860) [0ccd:0096,0ccd:10AF]
@ -76,7 +76,11 @@
76 -> KWorld PlusTV 340U or UB435-Q (ATSC) (em2870) [1b80:a340]
77 -> EM2874 Leadership ISDBT (em2874)
78 -> PCTV nanoStick T2 290e (em28174)
79 -> Terratec Cinergy H5 (em2884) [0ccd:10a2,0ccd:10ad]
79 -> Terratec Cinergy H5 (em2884) [0ccd:008e,0ccd:00ac,0ccd:10a2,0ccd:10ad]
80 -> PCTV DVB-S2 Stick (460e) (em28174)
81 -> Hauppauge WinTV HVR 930C (em2884) [2040:1605]
82 -> Terratec Cinergy HTC Stick (em2884) [0ccd:00b2]
83 -> Honestech Vidbox NW03 (em2860) [eb1a:5006]
84 -> MaxMedia UB425-TC (em2874) [1b80:e425]
85 -> PCTV QuatroStick (510e) (em2884) [2304:0242]
86 -> PCTV QuatroStick nano (520e) (em2884) [2013:0251]

View File

@ -187,3 +187,4 @@
186 -> Beholder BeholdTV 501 [5ace:5010]
187 -> Beholder BeholdTV 503 FM [5ace:5030]
188 -> Sensoray 811/911 [6000:0811,6000:0911]
189 -> Kworld PC150-U [17de:a134]

View File

@ -78,10 +78,11 @@ tuner=77 - TCL tuner MF02GIP-5N-E
tuner=78 - Philips FMD1216MEX MK3 Hybrid Tuner
tuner=79 - Philips PAL/SECAM multi (FM1216 MK5)
tuner=80 - Philips FQ1216LME MK3 PAL/SECAM w/active loopthrough
tuner=81 - Xceive 4000 tuner
tuner=81 - Partsnic (Daewoo) PTI-5NF05
tuner=82 - Philips CU1216L
tuner=83 - NXP TDA18271
tuner=84 - Sony BTF-Pxn01Z
tuner=85 - Philips FQ1236 MK5
tuner=86 - Tena TNF5337 MFD
tuner=87 - Xceive 4000 tuner
tuner=88 - Xceive 5000C tuner

View File

@ -0,0 +1,178 @@
Samsung S5P/EXYNOS4 FIMC driver
Copyright (C) 2012 Samsung Electronics Co., Ltd.
---------------------------------------------------------------------------
The FIMC (Fully Interactive Mobile Camera) device available in Samsung
SoC Application Processors is an integrated camera host interface, color
space converter, image resizer and rotator. It's also capable of capturing
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.
1. Supported SoCs
=================
S5PC100 (mem-to-mem only), S5PV210, EXYNOS4210
2. Supported features
=====================
- camera parallel interface capture (ITU-R.BT601/565);
- camera serial interface capture (MIPI-CSI2);
- memory-to-memory processing (color space conversion, scaling, mirror
and rotation);
- dynamic pipeline re-configuration at runtime (re-attachment of any FIMC
instance to any parallel video input or any MIPI-CSI front-end);
- runtime PM and system wide suspend/resume
Not currently supported:
- LCD writeback input
- per frame clock gating (mem-to-mem)
3. Files partitioning
=====================
- media device driver
drivers/media/video/s5p-fimc/fimc-mdevice.[ch]
- camera capture video device driver
drivers/media/video/s5p-fimc/fimc-capture.c
- MIPI-CSI2 receiver subdev
drivers/media/video/s5p-fimc/mipi-csis.[ch]
- video post-processor (mem-to-mem)
drivers/media/video/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
4. User space interfaces
========================
4.1. Media device interface
The driver supports Media Controller API as defined at
http://http://linuxtv.org/downloads/v4l-dvb-apis/media_common.html
The media device driver name is "SAMSUNG S5P FIMC".
The purpose of this interface is to allow changing assignment of FIMC instances
to the SoC peripheral camera input at runtime and optionally to control internal
connections of the MIPI-CSIS device(s) to the FIMC entities.
The media device interface allows to configure the SoC for capturing image
data from the sensor through more than one FIMC instance (e.g. for simultaneous
viewfinder and still capture setup).
Reconfiguration is done by enabling/disabling media links created by the driver
during initialization. The internal device topology can be easily discovered
through media entity and links enumeration.
4.2. Memory-to-memory video node
V4L2 memory-to-memory interface at /dev/video? device node. This is standalone
video device, it has no media pads. However please note the mem-to-mem and
capture video node operation on same FIMC instance is not allowed. The driver
detects such cases but the applications should prevent them to avoid an
undefined behaviour.
4.3. Capture video node
The driver supports V4L2 Video Capture Interface as defined at:
http://linuxtv.org/downloads/v4l-dvb-apis/devices.html
At the capture and mem-to-mem video nodes only the multi-planar API is
supported. For more details see:
http://linuxtv.org/downloads/v4l-dvb-apis/planar-apis.html
4.4. Camera capture subdevs
Each FIMC instance exports a sub-device node (/dev/v4l-subdev?), a sub-device
node is also created per each available and enabled at the platform level
MIPI-CSI receiver device (currently up to two).
4.5. sysfs
In order to enable more precise camera pipeline control through the sub-device
API the driver creates a sysfs entry associated with "s5p-fimc-md" platform
device. The entry path is: /sys/platform/devices/s5p-fimc-md/subdev_conf_mode.
In typical use case there could be a following capture pipeline configuration:
sensor subdev -> mipi-csi subdev -> fimc subdev -> video node
When we configure these devices through sub-device API at user space, the
configuration flow must be from left to right, and the video node is
configured as last one.
When we don't use sub-device user space API the whole configuration of all
devices belonging to the pipeline is done at the video node driver.
The sysfs entry allows to instruct the capture node driver not to configure
the sub-devices (format, crop), to avoid resetting the subdevs' configuration
when the last configuration steps at the video node is performed.
For full sub-device control support (subdevs configured at user space before
starting streaming):
# echo "sub-dev" > /sys/platform/devices/s5p-fimc-md/subdev_conf_mode
For V4L2 video node control only (subdevs configured internally by the host
driver):
# echo "vid-dev" > /sys/platform/devices/s5p-fimc-md/subdev_conf_mode
This is a default option.
5. Device mapping to video and subdev device nodes
==================================================
There are associated two video device nodes with each device instance in
hardware - video capture and mem-to-mem and additionally a subdev node for
more precise FIMC capture subsystem control. In addition a separate v4l2
sub-device node is created per each MIPI-CSIS device.
How to find out which /dev/video? or /dev/v4l-subdev? is assigned to which
device?
You can either grep through the kernel log to find relevant information, i.e.
# dmesg | grep -i fimc
(note that udev, if present, might still have rearranged the video nodes),
or retrieve the information from /dev/media? with help of the media-ctl tool:
# media-ctl -p
6. Platform support
===================
The machine code (plat-s5p and arch/arm/mach-*) must select following options
CONFIG_S5P_DEV_FIMC0 mandatory
CONFIG_S5P_DEV_FIMC1 \
CONFIG_S5P_DEV_FIMC2 | optional
CONFIG_S5P_DEV_FIMC3 |
CONFIG_S5P_SETUP_FIMC /
CONFIG_S5P_SETUP_MIPIPHY \
CONFIG_S5P_DEV_CSIS0 | optional for MIPI-CSI interface
CONFIG_S5P_DEV_CSIS1 /
Except that, relevant s5p_device_fimc? should be registered in the machine code
in addition to a "s5p-fimc-md" platform device to which the media device driver
is bound. The "s5p-fimc-md" device instance is required even if only mem-to-mem
operation is used.
The description of sensor(s) attached to FIMC/MIPI-CSIS camera inputs should be
passed as the "s5p-fimc-md" device platform_data. The platform data structure
is defined in file include/media/s5p_fimc.h.
7. Build
========
This driver depends on following config options:
PLAT_S5P,
PM_RUNTIME,
I2C,
REGULATOR,
VIDEO_V4L2_SUBDEV_API,
If the driver is built as a loadable kernel module (CONFIG_VIDEO_SAMSUNG_S5P_FIMC=m)
two modules are created (in addition to the core v4l2 modules): s5p-fimc.ko and
optional s5p-csis.ko (MIPI-CSI receiver subdev).

View File

@ -217,6 +217,7 @@ ov534_9 06f8:3003 Hercules Dualpix HD Weblog
sonixj 06f8:3004 Hercules Classic Silver
sonixj 06f8:3008 Hercules Deluxe Optical Glass
pac7302 06f8:3009 Hercules Classic Link
pac7302 06f8:301b Hercules Link
nw80x 0728:d001 AVerMedia Camguard
spca508 0733:0110 ViewQuest VQ110
spca501 0733:0401 Intel Create and Share

View File

@ -661,7 +661,7 @@ static struct clk_lookup lookups[] = {
_REGISTER_CLOCK(NULL, "dma", dma_clk)
_REGISTER_CLOCK(NULL, "rtic", rtic_clk)
_REGISTER_CLOCK(NULL, "brom", brom_clk)
_REGISTER_CLOCK(NULL, "emma", emma_clk)
_REGISTER_CLOCK("m2m-emmaprp.0", NULL, emma_clk)
_REGISTER_CLOCK(NULL, "slcdc", slcdc_clk)
_REGISTER_CLOCK("imx27-fec.0", NULL, fec_clk)
_REGISTER_CLOCK(NULL, "emi", emi_clk)

View File

@ -50,6 +50,8 @@ extern const struct imx_imx_uart_1irq_data imx27_imx_uart_data[];
extern const struct imx_mx2_camera_data imx27_mx2_camera_data;
#define imx27_add_mx2_camera(pdata) \
imx_add_mx2_camera(&imx27_mx2_camera_data, pdata)
#define imx27_add_mx2_emmaprp(pdata) \
imx_add_mx2_emmaprp(&imx27_mx2_camera_data)
extern const struct imx_mxc_ehci_data imx27_mxc_ehci_otg_data;
#define imx27_add_mxc_ehci_otg(pdata) \

View File

@ -62,3 +62,21 @@ struct platform_device *__init imx_add_mx2_camera(
res, data->iobaseemmaprp ? 4 : 2,
pdata, sizeof(*pdata), DMA_BIT_MASK(32));
}
struct platform_device *__init imx_add_mx2_emmaprp(
const struct imx_mx2_camera_data *data)
{
struct resource res[] = {
{
.start = data->iobaseemmaprp,
.end = data->iobaseemmaprp + data->iosizeemmaprp - 1,
.flags = IORESOURCE_MEM,
}, {
.start = data->irqemmaprp,
.end = data->irqemmaprp,
.flags = IORESOURCE_IRQ,
},
};
return imx_add_platform_device_dmamask("m2m-emmaprp", 0,
res, 2, NULL, 0, DMA_BIT_MASK(32));
}

View File

@ -223,6 +223,8 @@ struct imx_mx2_camera_data {
struct platform_device *__init imx_add_mx2_camera(
const struct imx_mx2_camera_data *data,
const struct mx2_camera_platform_data *pdata);
struct platform_device *__init imx_add_mx2_emmaprp(
const struct imx_mx2_camera_data *data);
#include <mach/mxc_ehci.h>
struct imx_mxc_ehci_data {

View File

@ -2026,6 +2026,16 @@ static bool hid_ignore(struct hid_device *hdev)
if (hdev->product >= USB_DEVICE_ID_LOGITECH_HARMONY_FIRST &&
hdev->product <= USB_DEVICE_ID_LOGITECH_HARMONY_LAST)
return true;
/*
* The Keene FM transmitter USB device has the same USB ID as
* the Logitech AudioHub Speaker, but it should ignore the hid.
* Check if the name is that of the Keene device.
* For reference: the name of the AudioHub is
* "HOLTEK AudioHub Speaker".
*/
if (hdev->product == USB_DEVICE_ID_LOGITECH_AUDIOHUB &&
!strcmp(hdev->name, "HOLTEK B-LINK USB Audio "))
return true;
break;
case USB_VENDOR_ID_SOUNDGRAPH:
if (hdev->product >= USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST &&

View File

@ -471,6 +471,7 @@
#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
#define USB_VENDOR_ID_LOGITECH 0x046d
#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101
#define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110
#define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f

View File

@ -29,5 +29,5 @@ obj-$(CONFIG_MEDIA_TUNER_MAX2165) += max2165.o
obj-$(CONFIG_MEDIA_TUNER_TDA18218) += tda18218.o
obj-$(CONFIG_MEDIA_TUNER_TDA18212) += tda18212.o
ccflags-y += -Idrivers/media/dvb/dvb-core
ccflags-y += -Idrivers/media/dvb/frontends
ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core
ccflags-y += -I$(srctree)/drivers/media/dvb/frontends

View File

@ -168,7 +168,7 @@ int fixpt_div32(u32 dividend, u32 divisor, u32 *quotient, u32 *fraction)
int i;
if (0 == divisor)
return -1;
return -EINVAL;
q = dividend / divisor;
remainder = dividend - q * divisor;
@ -194,10 +194,13 @@ static int max2165_set_rf(struct max2165_priv *priv, u32 freq)
u8 tf_ntch;
u32 t;
u32 quotient, fraction;
int ret;
/* Set PLL divider according to RF frequency */
fixpt_div32(freq / 1000, priv->config->osc_clk * 1000,
&quotient, &fraction);
ret = fixpt_div32(freq / 1000, priv->config->osc_clk * 1000,
&quotient, &fraction);
if (ret != 0)
return ret;
/* 20-bit fraction */
fraction >>= 12;

View File

@ -350,7 +350,7 @@ static int MT2063_Sleep(struct dvb_frontend *fe)
/*
* ToDo: Add code here to implement a OS blocking
*/
msleep(10);
msleep(100);
return 0;
}
@ -2226,7 +2226,7 @@ static struct dvb_tuner_ops mt2063_ops = {
.info = {
.name = "MT2063 Silicon Tuner",
.frequency_min = 45000000,
.frequency_max = 850000000,
.frequency_max = 865000000,
.frequency_step = 0,
},

View File

@ -23,10 +23,6 @@ static inline struct dvb_frontend *mt2063_attach(struct dvb_frontend *fe,
return NULL;
}
int mt2063_setTune(struct dvb_frontend *fe, u32 f_in,
u32 bw_in,
enum MTTune_atv_standard tv_type);
/* FIXME: Should use the standard DVB attachment interfaces */
unsigned int tuner_MT2063_SoftwareShutdown(struct dvb_frontend *fe);
unsigned int tuner_MT2063_ClearPowerMaskBits(struct dvb_frontend *fe);

View File

@ -1868,6 +1868,10 @@ struct tunertype tuners[] = {
.params = tuner_tena_tnf_5337_params,
.count = ARRAY_SIZE(tuner_tena_tnf_5337_params),
},
[TUNER_XC5000C] = { /* Xceive 5000C */
.name = "Xceive 5000C tuner",
/* see xc5000.c for details */
},
};
EXPORT_SYMBOL(tuners);

View File

@ -49,9 +49,6 @@ static LIST_HEAD(hybrid_tuner_instance_list);
#define dprintk(level, fmt, arg...) if (debug >= level) \
printk(KERN_INFO "%s: " fmt, "xc5000", ## arg)
#define XC5000_DEFAULT_FIRMWARE "dvb-fe-xc5000-1.6.114.fw"
#define XC5000_DEFAULT_FIRMWARE_SIZE 12401
struct xc5000_priv {
struct tuner_i2c_props i2c_props;
struct list_head hybrid_tuner_instance_list;
@ -62,6 +59,8 @@ struct xc5000_priv {
u8 video_standard;
u8 rf_mode;
u8 radio_input;
int chip_id;
};
/* Misc Defines */
@ -204,6 +203,33 @@ static struct XC_TV_STANDARD XC5000_Standard[MAX_TV_STANDARD] = {
{"FM Radio-INPUT1_MONO", 0x0278, 0x9002}
};
struct xc5000_fw_cfg {
char *name;
u16 size;
};
static const struct xc5000_fw_cfg xc5000a_1_6_114 = {
.name = "dvb-fe-xc5000-1.6.114.fw",
.size = 12401,
};
static const struct xc5000_fw_cfg xc5000c_41_024_5_31875 = {
.name = "dvb-fe-xc5000c-41.024.5-31875.fw",
.size = 16503,
};
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_31875;
}
}
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);
@ -552,12 +578,14 @@ 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",
XC5000_DEFAULT_FIRMWARE);
desired_fw->name);
ret = request_firmware(&fw, XC5000_DEFAULT_FIRMWARE,
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");
@ -569,7 +597,7 @@ static int xc5000_fwupload(struct dvb_frontend *fe)
ret = XC_RESULT_SUCCESS;
}
if (fw->size != XC5000_DEFAULT_FIRMWARE_SIZE) {
if (fw->size != desired_fw->size) {
printk(KERN_ERR "xc5000: firmware incorrect size\n");
ret = XC_RESULT_RESET_FAILURE;
} else {
@ -1139,6 +1167,13 @@ struct dvb_frontend *xc5000_attach(struct dvb_frontend *fe,
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.
*/

View File

@ -27,10 +27,15 @@
struct dvb_frontend;
struct i2c_adapter;
#define XC5000A 1
#define XC5000C 2
struct xc5000_config {
u8 i2c_address;
u32 if_khz;
u8 radio_input;
int chip_id;
};
/* xc5000 callback command */

View File

@ -578,6 +578,7 @@ static int demod_attach_drxk(struct ddb_input *input)
struct drxk_config config;
memset(&config, 0, sizeof(config));
config.microcode_name = "drxk_a3.mc";
config.adr = 0x29 + (input->nr & 1);
fe = input->fe = dvb_attach(drxk_attach, &config, i2c);

View File

@ -32,8 +32,6 @@
#include <asm/dma.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/ca.h>
#include <linux/dvb/video.h>
#include <linux/dvb/audio.h>
#include <linux/socket.h>
#include "dmxdev.h"

View File

@ -655,6 +655,8 @@ restart:
dprintk("%s: Retune requested, FESTATE_RETUNE\n", __func__);
re_tune = true;
fepriv->state = FESTATE_TUNED;
} else {
re_tune = false;
}
if (fe->ops.tune)

View File

@ -361,6 +361,14 @@ config DVB_USB_EC168
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
@ -378,6 +386,7 @@ config DVB_USB_LME2510
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 .
@ -403,3 +412,13 @@ config DVB_USB_MXL111SF
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 MEDIA_TUNER_QT1010 if !MEDIA_TUNER_CUSTOMISE
select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE
select MEDIA_TUNER_MXL5005S if !MEDIA_TUNER_CUSTOMISE
help
Say Y here to support the Realtek RTL28xxU DVB USB receiver.

View File

@ -54,7 +54,6 @@ 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
@ -88,6 +87,9 @@ 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
@ -105,8 +107,12 @@ 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
ccflags-y += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/
# due to tuner-xc3028
ccflags-y += -Idrivers/media/common/tuners
EXTRA_CFLAGS += -Idrivers/media/dvb/ttpci
dvb-usb-rtl28xxu-objs = rtl28xxu.o
obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.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

View File

@ -1164,6 +1164,41 @@ static int af9015_af9013_sleep(struct dvb_frontend *fe)
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;
@ -1283,6 +1318,7 @@ static struct mxl5007t_config af9015_mxl5007t_config = {
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) {
@ -1340,6 +1376,19 @@ static int af9015_tuner_attach(struct dvb_usb_adapter *adap)
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;
}

View File

@ -108,6 +108,8 @@ struct af9015_state {
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 {

View File

@ -58,7 +58,7 @@ 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;
int act_len, ret, i;
u8 buf[64];
memcpy(&buf[0], sbuf, slen);
@ -73,26 +73,52 @@ static int anysee_ctrl_msg(struct dvb_usb_device *d, u8 *sbuf, u8 slen,
/* 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) {
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)
err("%s: recv bulk message failed: %d", __func__, ret);
else {
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 (!ret && rbuf && rlen)
if (rbuf && rlen)
memcpy(rbuf, buf, rlen);
error_unlock:
mutex_unlock(&anysee_usb_mutex);
return ret;

View File

@ -0,0 +1,957 @@
/*
* Driver for AzureWave 6007 DVB-C/T USB2.0 and clones
*
* Copyright (c) Henry Wang <Henry.wang@AzureWave.com>
*
* 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 <mchehab@redhat.com>
* 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, sizeof(mac));
if (ret > 0)
deb_info("%s: mac is %02x:%02x:%02x:%02x:%02x:%02x\n",
__func__, mac[0], mac[1], mac[2],
mac[3], mac[4], mac[5]);
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 <Henry.wang@AzureWave.com>");
MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>");
MODULE_DESCRIPTION("Driver for AzureWave 6007 DVB-C/T USB2.0 and clones");
MODULE_VERSION("1.1");
MODULE_LICENSE("GPL");

View File

@ -677,11 +677,9 @@ static void dib0700_rc_urb_completion(struct urb *purb)
u8 toggle;
deb_info("%s()\n", __func__);
if (d == NULL)
return;
if (d->rc_dev == NULL) {
/* This will occur if disable_rc_polling=1 */
kfree(purb->transfer_buffer);
usb_free_urb(purb);
return;
}
@ -690,6 +688,7 @@ static void dib0700_rc_urb_completion(struct urb *purb)
if (purb->status < 0) {
deb_info("discontinuing polling\n");
kfree(purb->transfer_buffer);
usb_free_urb(purb);
return;
}
@ -784,8 +783,11 @@ int dib0700_rc_setup(struct dvb_usb_device *d)
dib0700_rc_urb_completion, d);
ret = usb_submit_urb(purb, GFP_ATOMIC);
if (ret)
if (ret) {
err("rc submit urb failed\n");
kfree(purb->transfer_buffer);
usb_free_urb(purb);
}
return ret;
}

View File

@ -51,6 +51,7 @@
#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
@ -80,6 +81,7 @@
#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
@ -125,6 +127,8 @@
#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
@ -226,6 +230,8 @@
#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_PINNACLE_EXPRESSCARD_320CX 0x022e
@ -249,6 +255,8 @@
#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

View File

@ -64,6 +64,7 @@ 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;
@ -259,15 +260,16 @@ static u32 it913x_query(struct usb_device *udev, u8 pro)
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;
if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0)
return -EAGAIN;
mutex_lock(&adap->dev->i2c_mutex);
deb_info(1, "PID_C (%02x)", onoff);
ret = it913x_wr_reg(udev, pro, PID_EN, onoff);
ret = it913x_wr_reg(udev, pro, PID_EN, st->pid_filter_onoff);
mutex_unlock(&adap->dev->i2c_mutex);
return ret;
@ -276,12 +278,13 @@ static int it913x_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff)
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;
if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0)
return -EAGAIN;
mutex_lock(&adap->dev->i2c_mutex);
deb_info(1, "PID_F (%02x)", onoff);
ret = it913x_wr_reg(udev, pro, PID_LSB, (u8)(pid & 0xff));
@ -292,6 +295,13 @@ static int it913x_pid_filter(struct dvb_usb_adapter *adap,
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;
}
@ -316,8 +326,8 @@ static int it913x_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
int ret;
u32 reg;
u8 pro;
if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
return -EAGAIN;
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);
@ -358,8 +368,7 @@ static int it913x_rc_query(struct dvb_usb_device *d)
int ret;
u32 key;
/* Avoid conflict with frontends*/
if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
return -EAGAIN;
mutex_lock(&d->i2c_mutex);
ret = it913x_io(d->udev, READ_LONG, PRO_LINK, CMD_IR_GET,
0, 0, &ibuf[0], sizeof(ibuf));
@ -388,19 +397,12 @@ static int ite_firmware_select(struct usb_device *udev,
{
int sw;
/* auto switch */
if (le16_to_cpu(udev->descriptor.idProduct) ==
USB_PID_ITETECH_IT9135)
sw = IT9135_V1_FW;
else if (le16_to_cpu(udev->descriptor.idProduct) ==
USB_PID_ITETECH_IT9135_9005)
sw = IT9135_V1_FW;
else if (le16_to_cpu(udev->descriptor.idProduct) ==
USB_PID_ITETECH_IT9135_9006) {
sw = IT9135_V2_FW;
if (it913x_config.tuner_id_0 == 0)
it913x_config.tuner_id_0 = IT9135_60;
} else
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)
@ -410,41 +412,103 @@ static int ite_firmware_select(struct usb_device *udev,
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_identify_state(struct usb_device *udev,
struct dvb_usb_device_properties *props,
struct dvb_usb_device_description **desc,
int *cold)
static int it913x_select_config(struct usb_device *udev,
struct dvb_usb_device_properties *props)
{
int ret = 0, firm_no;
u8 reg, remote;
int ret = 0, reg;
bool proprietary_ir = false;
firm_no = it913x_return_status(udev);
if (it913x_config.chip_ver == 0x02
&& it913x_config.chip_type == 0x9135)
reg = it913x_read_reg(udev, 0x461d);
else
reg = it913x_read_reg(udev, 0x461b);
/* checnk for dual mode */
it913x_config.dual_mode = it913x_read_reg(udev, 0x49c5);
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;
@ -459,17 +523,6 @@ static int it913x_identify_state(struct usb_device *udev,
if(props->adapter[0].fe[0].pid_filter_count == 5)
props->adapter[0].fe[0].pid_filter_count = 31;
/* TODO different remotes */
remote = it913x_read_reg(udev, 0x49ac); /* Remote */
if (remote == 0)
props->rc.core.rc_codes = NULL;
/* TODO at the moment tuner_id is always assigned to 0x38 */
it913x_config.tuner_id_0 = it913x_read_reg(udev, 0x49d0);
info("Dual mode=%x Remote=%x Tuner Type=%x", it913x_config.dual_mode
, remote, it913x_config.tuner_id_0);
/* Select Stream Buffer Size and pid filter option*/
if (pid_filter) {
props->adapter[0].fe[0].stream.u.bulk.buffersize =
@ -490,8 +543,29 @@ static int it913x_identify_state(struct usb_device *udev,
} 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;
@ -538,18 +612,22 @@ static int it913x_identify_state(struct usb_device *udev,
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;
if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0)
return -EAGAIN;
deb_info(1, "STM (%02x)", onoff);
if (!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;
mutex_unlock(&adap->dev->i2c_mutex);
}
return ret;
}
@ -789,7 +867,7 @@ static struct dvb_usb_device_properties it913x_properties = {
.rc_query = it913x_rc_query,
.rc_interval = IT913X_POLL,
.allowed_protos = RC_TYPE_NEC,
.rc_codes = RC_MAP_MSI_DIGIVOX_III,
.rc_codes = RC_MAP_IT913X_V1,
},
.i2c_algo = &it913x_i2c_algo,
.num_device_descs = 5,
@ -823,5 +901,5 @@ module_usb_driver(it913x_driver);
MODULE_AUTHOR("Malcolm Priestley <tvboxspy@gmail.com>");
MODULE_DESCRIPTION("it913x USB 2 Driver");
MODULE_VERSION("1.22");
MODULE_VERSION("1.27");
MODULE_LICENSE("GPL");

View File

@ -77,6 +77,7 @@
#include "stv0299.h"
#include "dvb-pll.h"
#include "z0194a.h"
#include "m88rs2000.h"
@ -104,7 +105,7 @@ 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=on 1=off");
MODULE_PARM_DESC(pid, "set default 0=default 1=off 2=on");
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
@ -113,6 +114,7 @@ DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
#define TUNER_LG 0x1
#define TUNER_S7395 0x2
#define TUNER_S0194 0x3
#define TUNER_RS2000 0x4
struct lme2510_state {
u8 id;
@ -121,6 +123,8 @@ struct lme2510_state {
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;
@ -128,6 +132,7 @@ struct lme2510_state {
u8 i2c_tuner_addr;
u8 stream_on;
u8 pid_size;
u8 pid_off;
void *buffer;
struct urb *lme_urb;
void *usb_buffer;
@ -178,14 +183,8 @@ static int lme2510_usb_talk(struct dvb_usb_device *d,
/* the read/write capped at 64 */
memcpy(buff, wbuf, (wlen < 64) ? wlen : 64);
ret |= usb_clear_halt(d->udev, usb_sndbulkpipe(d->udev, 0x01));
ret |= lme2510_bulk_write(d->udev, buff, wlen , 0x01);
msleep(10);
ret |= usb_clear_halt(d->udev, usb_rcvbulkpipe(d->udev, 0x01));
ret |= lme2510_bulk_read(d->udev, buff, (rlen < 64) ?
rlen : 64 , 0x01);
@ -199,9 +198,14 @@ static int lme2510_usb_talk(struct dvb_usb_device *d,
static int lme2510_stream_restart(struct dvb_usb_device *d)
{
static u8 stream_on[] = LME_ST_ON_W;
struct lme2510_state *st = d->priv;
u8 all_pids[] = LME_ALL_PIDS;
u8 stream_on[] = LME_ST_ON_W;
int ret;
u8 rbuff[10];
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));
@ -308,6 +312,14 @@ static void lme2510_int_response(struct urb *lme_urb)
((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;
}
@ -359,19 +371,20 @@ static int lme2510_int_read(struct dvb_usb_adapter *adap)
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_CLEAR_PID;
static u8 clear_pid_reg[] = LME_ALL_PIDS;
static u8 rbuf[1];
int ret;
deb_info(1, "PID Clearing Filter");
ret = mutex_lock_interruptible(&adap->dev->i2c_mutex);
if (ret < 0)
return -EAGAIN;
mutex_lock(&adap->dev->i2c_mutex);
if (!onoff)
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;
@ -389,11 +402,9 @@ static int lme2510_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid,
pid, index, onoff);
if (onoff) {
ret = mutex_lock_interruptible(&adap->dev->i2c_mutex);
if (ret < 0)
return -EAGAIN;
ret |= lme2510_enable_pid(adap->dev, index, pid);
mutex_unlock(&adap->dev->i2c_mutex);
mutex_lock(&adap->dev->i2c_mutex);
ret |= lme2510_enable_pid(adap->dev, index, pid);
mutex_unlock(&adap->dev->i2c_mutex);
}
@ -425,9 +436,6 @@ static int lme2510_msg(struct dvb_usb_device *d,
int ret = 0;
struct lme2510_state *st = d->priv;
if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
return -EAGAIN;
if (st->i2c_talk_onoff == 1) {
ret = lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen);
@ -456,8 +464,6 @@ static int lme2510_msg(struct dvb_usb_device *d,
st->i2c_talk_onoff = 0;
}
}
if ((wbuf[3] != 0x6) & (wbuf[3] != 0x5))
msleep(5);
}
break;
case TUNER_S0194:
@ -472,10 +478,12 @@ static int lme2510_msg(struct dvb_usb_device *d,
}
}
break;
case TUNER_RS2000:
default:
break;
}
} else {
/* TODO rewrite this section */
switch (st->tuner_config) {
case TUNER_LG:
switch (wbuf[3]) {
@ -559,6 +567,24 @@ static int lme2510_msg(struct dvb_usb_device *d,
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;
}
@ -568,8 +594,6 @@ static int lme2510_msg(struct dvb_usb_device *d,
}
mutex_unlock(&d->i2c_mutex);
return ret;
}
@ -584,6 +608,8 @@ static int lme2510_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
u16 len;
u8 gate = st->i2c_gate;
mutex_lock(&d->i2c_mutex);
if (gate == 0)
gate = 5;
@ -622,6 +648,7 @@ static int lme2510_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
if (lme2510_msg(d, obuf, len, ibuf, 64) < 0) {
deb_info(1, "i2c transfer failed.");
mutex_unlock(&d->i2c_mutex);
return -EAGAIN;
}
@ -634,6 +661,8 @@ static int lme2510_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
}
}
}
mutex_unlock(&d->i2c_mutex);
return i;
}
@ -653,7 +682,7 @@ static int lme2510_identify_state(struct usb_device *udev,
struct dvb_usb_device_description **desc,
int *cold)
{
if (pid_filter > 0)
if (pid_filter != 2)
props->adapter[0].fe[0].caps &=
~DVB_USB_ADAP_NEED_PID_FILTERING;
*cold = 0;
@ -663,7 +692,7 @@ static int lme2510_identify_state(struct usb_device *udev,
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_CLEAR_PID;
static u8 clear_reg_3[] = LME_ALL_PIDS;
static u8 rbuf[1];
int ret = 0, rlen = sizeof(rbuf);
@ -675,8 +704,7 @@ static int lme2510_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
else {
deb_info(1, "STM Steam Off");
/* mutex is here only to avoid collision with I2C */
if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0)
return -EAGAIN;
mutex_lock(&adap->dev->i2c_mutex);
ret = lme2510_usb_talk(adap->dev, clear_reg_3,
sizeof(clear_reg_3), rbuf, rlen);
@ -781,16 +809,18 @@ static int lme_firmware_switch(struct usb_device *udev, int cold)
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, cold_fw;
int ret = 0, cold_fw;
cold = (cold > 0) ? (cold & 1) : 0;
cold_fw = !cold;
if (le16_to_cpu(udev->descriptor.idProduct) == 0x1122) {
switch (le16_to_cpu(udev->descriptor.idProduct)) {
case 0x1122:
switch (dvb_usb_lme2510_firmware) {
default:
dvb_usb_lme2510_firmware = TUNER_S0194;
@ -813,7 +843,8 @@ static int lme_firmware_switch(struct usb_device *udev, int cold)
cold_fw = 0;
break;
}
} else {
break;
case 0x1120:
switch (dvb_usb_lme2510_firmware) {
default:
dvb_usb_lme2510_firmware = TUNER_S7395;
@ -842,8 +873,17 @@ static int lme_firmware_switch(struct usb_device *udev, int cold)
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);
@ -906,6 +946,29 @@ static struct stv0299_config sharp_z0194_config = {
.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)
{
@ -915,8 +978,7 @@ static int dm04_lme2510_set_voltage(struct dvb_frontend *fe,
static u8 rbuf[1];
int ret = 0, len = 3, rlen = 1;
if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0)
return -EAGAIN;
mutex_lock(&adap->dev->i2c_mutex);
switch (voltage) {
case SEC_VOLTAGE_18:
@ -937,12 +999,31 @@ static int dm04_lme2510_set_voltage(struct dvb_frontend *fe,
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"};
" SHARP:BS2F7HZ0194", " RS2000"};
char *name = adap->fe_adap[0].fe->ops.info.name;
strlcpy(name, desc, 128);
@ -958,60 +1039,82 @@ static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap)
int ret = 0;
st->i2c_talk_onoff = 1;
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);
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;
}
goto end;
}
st->i2c_gate = 4;
adap->fe_adap[0].fe = dvb_attach(stv0299_attach, &sharp_z0194_config,
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 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);
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;
}
goto end;
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;
}
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);
}
} else {
info("DM04 Not Supported");
return -ENODEV;
if (adap->fe_adap[0].fe == NULL) {
info("DM04/QQBOX Not Powered up or not Supported");
return -ENODEV;
}
end: if (ret) {
if (ret) {
if (adap->fe_adap[0].fe) {
dvb_frontend_detach(adap->fe_adap[0].fe);
adap->fe_adap[0].fe = NULL;
@ -1028,7 +1131,7 @@ end: if (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"};
char *tun_msg[] = {"", "TDA8263", "IX2505V", "DVB_PLL_OPERA", "RS2000"};
int ret = 0;
switch (st->tuner_config) {
@ -1047,6 +1150,9 @@ static int dm04_lme2510_tuner(struct dvb_usb_adapter *adap)
&adap->dev->i2c_adap, DVB_PLL_OPERA1))
ret = st->tuner_config;
break;
case TUNER_RS2000:
ret = st->tuner_config;
break;
default:
break;
}
@ -1075,10 +1181,9 @@ static int lme2510_powerup(struct dvb_usb_device *d, int onoff)
static u8 lnb_on[] = LNB_ON;
static u8 lnb_off[] = LNB_OFF;
static u8 rbuf[1];
int ret, len = 3, rlen = 1;
int ret = 0, len = 3, rlen = 1;
if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
return -EAGAIN;
mutex_lock(&d->i2c_mutex);
if (onoff)
ret = lme2510_usb_talk(d, lnb_on, len, rbuf, rlen);
@ -1136,6 +1241,7 @@ static int lme2510_probe(struct usb_interface *intf,
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 */
};
@ -1153,7 +1259,7 @@ static struct dvb_usb_device_properties lme2510_properties = {
DVB_USB_ADAP_NEED_PID_FILTERING|
DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
.streaming_ctrl = lme2510_streaming_ctrl,
.pid_filter_count = 15,
.pid_filter_count = 32,
.pid_filter = lme2510_pid_filter,
.pid_filter_ctrl = lme2510_pid_filter_ctrl,
.frontend_attach = dm04_lme2510_frontend_attach,
@ -1204,7 +1310,7 @@ static struct dvb_usb_device_properties lme2510c_properties = {
DVB_USB_ADAP_NEED_PID_FILTERING|
DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
.streaming_ctrl = lme2510_streaming_ctrl,
.pid_filter_count = 15,
.pid_filter_count = 32,
.pid_filter = lme2510_pid_filter,
.pid_filter_ctrl = lme2510_pid_filter_ctrl,
.frontend_attach = dm04_lme2510_frontend_attach,
@ -1234,11 +1340,14 @@ static struct dvb_usb_device_properties lme2510c_properties = {
.identify_state = lme2510_identify_state,
.i2c_algo = &lme2510_i2c_algo,
.generic_bulk_ctrl_endpoint = 0,
.num_device_descs = 1,
.num_device_descs = 2,
.devices = {
{ "DM04_LME2510C_DVB-S",
{ &lme2510_table[1], NULL },
},
{ "DM04_LME2510C_DVB-S RS2000",
{ &lme2510_table[2], NULL },
},
}
};
@ -1295,5 +1404,5 @@ module_usb_driver(lme2510_driver);
MODULE_AUTHOR("Malcolm Priestley <tvboxspy@gmail.com>");
MODULE_DESCRIPTION("LME2510(C) DVB-S USB2.0");
MODULE_VERSION("1.91");
MODULE_VERSION("1.99");
MODULE_LICENSE("GPL");

View File

@ -41,6 +41,7 @@
#define LME_ST_ON_W {0x06, 0x00}
#define LME_CLEAR_PID {0x03, 0x02, 0x20, 0xa0}
#define LME_ZERO_PID {0x03, 0x06, 0x00, 0x00, 0x01, 0x00, 0x20, 0x9c}
#define LME_ALL_PIDS {0x03, 0x06, 0x00, 0xff, 0x01, 0x1f, 0x20, 0x81}
/* LNB Voltage
* 07 XX XX

View File

@ -351,15 +351,13 @@ static int mxl111sf_ep6_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
adap_state->ep6_clockphase,
0, 0);
mxl_fail(ret);
#if 0
} else {
ret = mxl111sf_disable_656_port(state);
mxl_fail(ret);
#endif
}
mxl111sf_read_reg(state, 0x12, &tmp);
tmp &= ~0x04;
mxl111sf_write_reg(state, 0x12, tmp);
return ret;
}

View File

@ -0,0 +1,982 @@
/*
* Realtek RTL28xxU DVB USB driver
*
* Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
* Copyright (C) 2011 Antti Palosaari <crope@iki.fi>
*
* 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 "qt1010.h"
#include "mt2060.h"
#include "mxl5005s.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 rtl2831_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 rtl2831_wr_reg(struct dvb_usb_device *d, u16 reg, u8 val)
{
return rtl2831_wr_regs(d, reg, &val, 1);
}
static int rtl2831_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 = rtl2831_wr_reg(adap->dev, SYS_GPIO_DIR, 0x0a);
if (ret)
goto err;
/* enable as output GPIO0, GPIO2, GPIO4 */
ret = rtl2831_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.
*/
/* 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 int rtl2832u_frontend_attach(struct dvb_usb_adapter *adap)
{
int ret;
struct rtl28xxu_priv *priv = adap->dev->priv;
u8 buf[1];
/* 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 FC2580 tuner probe */
struct rtl28xxu_req req_fc2580 = {0x01ac, CMD_I2C_RD, 1, buf};
deb_info("%s:\n", __func__);
/* GPIO direction */
ret = rtl2831_wr_reg(adap->dev, SYS_GPIO_DIR, 0x0a);
if (ret)
goto err;
/* enable as output GPIO0, GPIO2, GPIO4 */
ret = rtl2831_wr_reg(adap->dev, SYS_GPIO_OUT_EN, 0x15);
if (ret)
goto err;
ret = rtl2831_wr_reg(adap->dev, SYS_DEMOD_CTL, 0xe8);
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;
/* 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;
deb_info("%s: FC2580\n", __func__);
goto found;
} else {
deb_info("%s: FC2580 probe failed=%d - %02x\n",
__func__, ret, buf[0]);
}
/* close demod I2C gate */
ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate_close);
if (ret)
goto err;
/* tuner not found */
ret = -ENODEV;
goto err;
found:
/* close demod I2C gate */
ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate_close);
if (ret)
goto err;
/* attach demodulator */
/* TODO: */
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_FC2580:
/* TODO: */
fe = NULL;
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 rtl28xxu_streaming_ctrl(struct dvb_usb_adapter *adap , int onoff)
{
int ret;
u8 buf[2], gpio;
deb_info("%s: onoff=%d\n", __func__, onoff);
ret = rtl2831_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 = rtl2831_wr_reg(adap->dev, SYS_GPIO_OUT_VAL, gpio);
if (ret)
goto err;
ret = rtl2831_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 rtl28xxu_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 = rtl2831_rd_reg(d, SYS_SYS0, &sys0);
if (ret)
goto err;
/* tuner power, read GPIOs */
ret = rtl2831_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 = rtl2831_wr_reg(d, SYS_SYS0, sys0);
if (ret)
goto err;
/* tuner power, write GPIOs */
ret = rtl2831_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 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 = rtl2831_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 = rtl2831_wr_reg(d, SYS_IRRC_SR, 1);
if (ret)
goto err;
/* repeated intentionally to avoid extra keypress */
ret = rtl2831_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 = rtl2831_wr_reg(d, rc_nec_tab[i].reg,
rc_nec_tab[i].val);
if (ret)
goto err;
}
priv->rc_active = true;
}
ret = rtl2831_rd_reg(d, IR_RX_IF, &buf[0]);
if (ret)
goto err;
if (buf[0] != 0x83)
goto exit;
ret = rtl2831_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 = rtl2831_wr_reg(d, IR_RX_IF, 0x03);
ret = rtl2831_wr_reg(d, IR_RX_BUF_CTRL, 0x80);
ret = rtl2831_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,
};
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 */
{} /* 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 = rtl28xxu_streaming_ctrl,
.stream = {
.type = USB_BULK,
.count = 6,
.endpoint = 0x81,
.u = {
.bulk = {
.buffersize = 8*512,
}
}
}
}
}
}
},
.power_ctrl = rtl28xxu_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 = rtl28xxu_streaming_ctrl,
.stream = {
.type = USB_BULK,
.count = 6,
.endpoint = 0x81,
.u = {
.bulk = {
.buffersize = 8*512,
}
}
}
}
}
}
},
.power_ctrl = rtl28xxu_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 = 0, /* disabled as no support for RTL2832 */
.devices = {
{
.name = "Realtek RTL2832U reference design",
},
}
},
};
static int rtl28xxu_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
int ret, i;
int properties_count = ARRAY_SIZE(rtl28xxu_properties);
struct dvb_usb_device *d;
deb_info("%s: interface=%d\n", __func__,
intf->cur_altsetting->desc.bInterfaceNumber);
if (intf->cur_altsetting->desc.bInterfaceNumber != 0)
return 0;
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 = rtl2831_wr_reg(d, USB_SYSCTL_0, 0x09);
if (ret)
goto err;
ret = rtl2831_wr_regs(d, USB_EPA_MAXPKT, "\x00\x02\x00\x00", 4);
if (ret)
goto err;
ret = rtl2831_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 <crope@iki.fi>");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,264 @@
/*
* Realtek RTL28xxU DVB USB driver
*
* Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
* Copyright (C) 2011 Antti Palosaari <crope@iki.fi>
*
* 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

View File

@ -425,6 +425,13 @@ config DVB_CXD2820R
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.
comment "DVB-C (cable) frontends"
depends on DVB_CORE
@ -698,6 +705,14 @@ config DVB_IT913X_FE
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.
comment "Tools to develop new frontends"
config DVB_DUMMY_FE

View File

@ -2,8 +2,8 @@
# Makefile for the kernel DVB frontend device drivers.
#
ccflags-y += -Idrivers/media/dvb/dvb-core/
ccflags-y += -Idrivers/media/common/tuners/
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
@ -96,4 +96,6 @@ 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_M88RS2000) += m88rs2000.o

View File

@ -839,15 +839,4 @@ static struct i2c_driver au8522_driver = {
.id_table = au8522_id,
};
static __init int init_au8522(void)
{
return i2c_add_driver(&au8522_driver);
}
static __exit void exit_au8522(void)
{
i2c_del_driver(&au8522_driver);
}
module_init(init_au8522);
module_exit(exit_au8522);
module_i2c_driver(au8522_driver);

View File

@ -588,11 +588,6 @@ static int au8522_set_frontend(struct dvb_frontend *fe)
(state->current_modulation == c->modulation))
return 0;
au8522_enable_modulation(fe, c->modulation);
/* Allow the demod to settle */
msleep(100);
if (fe->ops.tuner_ops.set_params) {
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 1);
@ -604,6 +599,11 @@ static int au8522_set_frontend(struct dvb_frontend *fe)
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;

View File

@ -502,10 +502,26 @@ static int cx22702_read_signal_strength(struct dvb_frontend *fe,
u16 *signal_strength)
{
struct cx22702_state *state = fe->demodulator_priv;
u8 reg23;
u16 rs_ber;
rs_ber = cx22702_readreg(state, 0x23);
*signal_strength = (rs_ber << 8) | rs_ber;
/*
* Experience suggests that the strength signal register works as
* follows:
* - In the absence of signal, value is 0xff.
* - In the presence of a weak signal, bit 7 is set, not sure what
* the lower 7 bits mean.
* - In the presence of a strong signal, the register holds a 7-bit
* value (bit 7 is cleared), with greater values standing for
* weaker signals.
*/
reg23 = cx22702_readreg(state, 0x23);
if (reg23 & 0x80) {
*signal_strength = 0;
} else {
reg23 = ~reg23 & 0x7f;
/* Scale to 16 bit */
*signal_strength = (reg23 << 9) | (reg23 << 2) | (reg23 >> 5);
}
return 0;
}

View File

@ -519,7 +519,7 @@ static int dib0090_fw_identify(struct dvb_frontend *fe)
return 0;
identification_error:
return -EIO;;
return -EIO;
}
static void dib0090_reset_digital(struct dvb_frontend *fe, const struct dib0090_config *cfg)

View File

@ -33,7 +33,7 @@ struct i2c_device {
/* lock */
#define DIB_LOCK struct mutex
#define DibAcquireLock(lock) do { if (mutex_lock_interruptible(lock) < 0) dprintk("could not get the lock"); } while (0)
#define DibAcquireLock(lock) mutex_lock_interruptible(lock)
#define DibReleaseLock(lock) mutex_unlock(lock)
#define DibInitLock(lock) mutex_init(lock)
#define DibFreeLock(lock)
@ -446,7 +446,10 @@ static int dib9000_risc_mem_read(struct dib9000_state *state, u8 cmd, u8 * b, u1
if (!state->platform.risc.fw_is_running)
return -EIO;
DibAcquireLock(&state->platform.risc.mem_lock);
if (DibAcquireLock(&state->platform.risc.mem_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
dib9000_risc_mem_setup(state, cmd | 0x80);
dib9000_risc_mem_read_chunks(state, b, len);
DibReleaseLock(&state->platform.risc.mem_lock);
@ -459,7 +462,10 @@ static int dib9000_risc_mem_write(struct dib9000_state *state, u8 cmd, const u8
if (!state->platform.risc.fw_is_running)
return -EIO;
DibAcquireLock(&state->platform.risc.mem_lock);
if (DibAcquireLock(&state->platform.risc.mem_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
dib9000_risc_mem_setup(state, cmd);
dib9000_risc_mem_write_chunks(state, b, m->size);
DibReleaseLock(&state->platform.risc.mem_lock);
@ -531,7 +537,10 @@ static int dib9000_mbx_send_attr(struct dib9000_state *state, u8 id, u16 * data,
if (!state->platform.risc.fw_is_running)
return -EINVAL;
DibAcquireLock(&state->platform.risc.mbx_if_lock);
if (DibAcquireLock(&state->platform.risc.mbx_if_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
tmp = MAX_MAILBOX_TRY;
do {
size = dib9000_read_word_attr(state, 1043, attr) & 0xff;
@ -593,7 +602,10 @@ static u8 dib9000_mbx_read(struct dib9000_state *state, u16 * data, u8 risc_id,
if (!state->platform.risc.fw_is_running)
return 0;
DibAcquireLock(&state->platform.risc.mbx_if_lock);
if (DibAcquireLock(&state->platform.risc.mbx_if_lock) < 0) {
dprintk("could not get the lock");
return 0;
}
if (risc_id == 1)
mc_base = 16;
else
@ -701,7 +713,10 @@ static int dib9000_mbx_process(struct dib9000_state *state, u16 attr)
if (!state->platform.risc.fw_is_running)
return -1;
DibAcquireLock(&state->platform.risc.mbx_lock);
if (DibAcquireLock(&state->platform.risc.mbx_lock) < 0) {
dprintk("could not get the lock");
return -1;
}
if (dib9000_mbx_count(state, 1, attr)) /* 1=RiscB */
ret = dib9000_mbx_fetch_to_cache(state, attr);
@ -1178,7 +1193,10 @@ static int dib9000_fw_get_channel(struct dvb_frontend *fe)
struct dibDVBTChannel *ch;
int ret = 0;
DibAcquireLock(&state->platform.risc.mem_mbx_lock);
if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) {
ret = -EIO;
goto error;
@ -1660,7 +1678,10 @@ static int dib9000_fw_component_bus_xfer(struct i2c_adapter *i2c_adap, struct i2
p[12] = 0;
}
DibAcquireLock(&state->platform.risc.mem_mbx_lock);
if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) {
dprintk("could not get the lock");
return 0;
}
dib9000_risc_mem_write(state, FE_MM_W_COMPONENT_ACCESS, p);
@ -1768,7 +1789,10 @@ int dib9000_fw_pid_filter_ctrl(struct dvb_frontend *fe, u8 onoff)
return 0;
}
DibAcquireLock(&state->demod_lock);
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
val = dib9000_read_word(state, 294 + 1) & 0xffef;
val |= (onoff & 0x1) << 4;
@ -1800,7 +1824,10 @@ int dib9000_fw_pid_filter(struct dvb_frontend *fe, u8 id, u16 pid, u8 onoff)
return 0;
}
DibAcquireLock(&state->demod_lock);
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
dprintk("Index %x, PID %d, OnOff %d", id, pid, onoff);
ret = dib9000_write_word(state, 300 + 1 + id,
onoff ? (1 << 13) | pid : 0);
@ -1848,7 +1875,10 @@ static int dib9000_sleep(struct dvb_frontend *fe)
u8 index_frontend;
int ret = 0;
DibAcquireLock(&state->demod_lock);
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) {
ret = state->fe[index_frontend]->ops.sleep(state->fe[index_frontend]);
if (ret < 0)
@ -1874,8 +1904,12 @@ static int dib9000_get_frontend(struct dvb_frontend *fe)
fe_status_t stat;
int ret = 0;
if (state->get_frontend_internal == 0)
DibAcquireLock(&state->demod_lock);
if (state->get_frontend_internal == 0) {
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
}
for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) {
state->fe[index_frontend]->ops.read_status(state->fe[index_frontend], &stat);
@ -1978,7 +2012,10 @@ static int dib9000_set_frontend(struct dvb_frontend *fe)
}
state->pid_ctrl_index = -1; /* postpone the pid filtering cmd */
DibAcquireLock(&state->demod_lock);
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return 0;
}
fe->dtv_property_cache.delivery_system = SYS_DVBT;
@ -2138,7 +2175,10 @@ static int dib9000_read_status(struct dvb_frontend *fe, fe_status_t * stat)
u8 index_frontend;
u16 lock = 0, lock_slave = 0;
DibAcquireLock(&state->demod_lock);
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++)
lock_slave |= dib9000_read_lock(state->fe[index_frontend]);
@ -2168,8 +2208,15 @@ static int dib9000_read_ber(struct dvb_frontend *fe, u32 * ber)
u16 *c;
int ret = 0;
DibAcquireLock(&state->demod_lock);
DibAcquireLock(&state->platform.risc.mem_mbx_lock);
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) {
dprintk("could not get the lock");
ret = -EINTR;
goto error;
}
if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) {
DibReleaseLock(&state->platform.risc.mem_mbx_lock);
ret = -EIO;
@ -2196,7 +2243,10 @@ static int dib9000_read_signal_strength(struct dvb_frontend *fe, u16 * strength)
u16 val;
int ret = 0;
DibAcquireLock(&state->demod_lock);
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
*strength = 0;
for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) {
state->fe[index_frontend]->ops.read_signal_strength(state->fe[index_frontend], &val);
@ -2206,8 +2256,13 @@ static int dib9000_read_signal_strength(struct dvb_frontend *fe, u16 * strength)
*strength += val;
}
DibAcquireLock(&state->platform.risc.mem_mbx_lock);
if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) {
dprintk("could not get the lock");
ret = -EINTR;
goto error;
}
if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) {
DibReleaseLock(&state->platform.risc.mem_mbx_lock);
ret = -EIO;
goto error;
}
@ -2232,9 +2287,14 @@ static u32 dib9000_get_snr(struct dvb_frontend *fe)
u32 n, s, exp;
u16 val;
DibAcquireLock(&state->platform.risc.mem_mbx_lock);
if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0)
return -EIO;
if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) {
dprintk("could not get the lock");
return 0;
}
if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) {
DibReleaseLock(&state->platform.risc.mem_mbx_lock);
return 0;
}
dib9000_risc_mem_read(state, FE_MM_R_FE_MONITOR, (u8 *) c, 16 * 2);
DibReleaseLock(&state->platform.risc.mem_mbx_lock);
@ -2266,7 +2326,10 @@ static int dib9000_read_snr(struct dvb_frontend *fe, u16 * snr)
u8 index_frontend;
u32 snr_master;
DibAcquireLock(&state->demod_lock);
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
snr_master = dib9000_get_snr(fe);
for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++)
snr_master += dib9000_get_snr(state->fe[index_frontend]);
@ -2288,9 +2351,17 @@ static int dib9000_read_unc_blocks(struct dvb_frontend *fe, u32 * unc)
u16 *c = (u16 *)state->i2c_read_buffer;
int ret = 0;
DibAcquireLock(&state->demod_lock);
DibAcquireLock(&state->platform.risc.mem_mbx_lock);
if (DibAcquireLock(&state->demod_lock) < 0) {
dprintk("could not get the lock");
return -EINTR;
}
if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) {
dprintk("could not get the lock");
ret = -EINTR;
goto error;
}
if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) {
DibReleaseLock(&state->platform.risc.mem_mbx_lock);
ret = -EIO;
goto error;
}

View File

@ -101,9 +101,9 @@ struct SCfgAgc {
struct SNoiseCal {
int cpOpt;
u16 cpNexpOfs;
u16 tdCal2k;
u16 tdCal8k;
short cpNexpOfs;
short tdCal2k;
short tdCal8k;
};
enum app_env {

View File

@ -7,15 +7,19 @@
/**
* 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,
* @adr: I2C Address of the DRX-K
* @parallel_ts: True means that the device uses parallel TS,
* Serial otherwise.
* 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
* @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.
* microcode_name: Name of the firmware file with the microcode
* @mpeg_out_clk_strength: DRXK Mpeg output clock drive strength.
* @microcode_name: Name of the firmware file with the microcode
*
* On the *_gpio vars, bit 0 is UIO-1, bit 1 is UIO-2 and bit 2 is
* UIO-3.
@ -25,11 +29,14 @@ struct drxk_config {
bool single_master;
bool no_i2c_bridge;
bool parallel_ts;
bool dynamic_clk;
bool enable_merr_cfg;
bool antenna_dvbt;
u16 antenna_gpio;
int chunk_size;
u8 mpeg_out_clk_strength;
int chunk_size;
const char *microcode_name;
};

View File

@ -90,10 +90,6 @@ bool IsA1WithRomCode(struct drxk_state *state)
#define DRXK_MPEG_PARALLEL_OUTPUT_PIN_DRIVE_STRENGTH (0x03)
#endif
#ifndef DRXK_MPEG_OUTPUT_CLK_DRIVE_STRENGTH
#define DRXK_MPEG_OUTPUT_CLK_DRIVE_STRENGTH (0x06)
#endif
#define DEFAULT_DRXK_MPEG_LOCK_TIMEOUT 700
#define DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT 500
@ -649,9 +645,6 @@ static int init_state(struct drxk_state *state)
u32 ulQual83 = DEFAULT_MER_83;
u32 ulQual93 = DEFAULT_MER_93;
u32 ulDVBTStaticTSClock = 1;
u32 ulDVBCStaticTSClock = 1;
u32 ulMpegLockTimeOut = DEFAULT_DRXK_MPEG_LOCK_TIMEOUT;
u32 ulDemodLockTimeOut = DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT;
@ -661,7 +654,6 @@ static int init_state(struct drxk_state *state)
u32 ulGPIOCfg = 0x0113;
u32 ulInvertTSClock = 0;
u32 ulTSDataStrength = DRXK_MPEG_SERIAL_OUTPUT_PIN_DRIVE_STRENGTH;
u32 ulTSClockkStrength = DRXK_MPEG_OUTPUT_CLK_DRIVE_STRENGTH;
u32 ulDVBTBitrate = 50000000;
u32 ulDVBCBitrate = DRXK_QAM_SYMBOLRATE_MAX * 8;
@ -814,8 +806,7 @@ static int init_state(struct drxk_state *state)
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 */
state->m_DVBTStaticCLK = (ulDVBTStaticTSClock != 0);
state->m_DVBCStaticCLK = (ulDVBCStaticTSClock != 0);
/* If TRUE; static MPEG clockrate will be used;
otherwise clockrate will adapt to the bitrate of the TS */
@ -823,7 +814,6 @@ static int init_state(struct drxk_state *state)
state->m_DVBCBitrate = ulDVBCBitrate;
state->m_TSDataStrength = (ulTSDataStrength & 0x07);
state->m_TSClockkStrength = (ulTSClockkStrength & 0x07);
/* Maximum bitrate in b/s in case static clockrate is selected */
state->m_mpegTsStaticBitrate = 19392658;
@ -1188,6 +1178,7 @@ 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",
@ -1253,12 +1244,17 @@ static int MPEGTSConfigurePins(struct drxk_state *state, bool mpegEnable)
status = write16(state, SIO_PDR_MSTRT_CFG__A, sioPdrMdxCfg);
if (status < 0)
goto error;
status = write16(state, SIO_PDR_MERR_CFG__A, 0x0000); /* Disable */
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, 0x0000); /* Disable */
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);
@ -6069,9 +6065,7 @@ static int init_drxk(struct drxk_state *state)
if (status < 0)
goto error;
if (!state->microcode_name)
load_microcode(state, "drxk_a3.mc");
else
if (state->microcode_name)
load_microcode(state, state->microcode_name);
/* disable token-ring bus through OFDM block for possible ucode upload */
@ -6322,15 +6316,12 @@ static int drxk_get_tune_settings(struct dvb_frontend *fe, struct dvb_frontend_t
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:
/*
* For DVB-T, let it use the default DVB core way, that is:
* fepriv->step_size = fe->ops.info.frequency_stepsize * 2
*/
return -EINVAL;
}
}
@ -6390,6 +6381,21 @@ struct dvb_frontend *drxk_attach(const struct drxk_config *config,
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;

View File

@ -332,6 +332,7 @@ struct drxk_state {
u16 UIO_mask; /* Bits used by UIO */
bool enable_merr_cfg;
bool single_master;
bool no_i2c_bridge;
bool antenna_dvbt;

View File

@ -201,6 +201,11 @@ fe_modulation_t fe_con[] = {
QAM_64,
};
enum {
PRIORITY_HIGH = 0, /* High-priority stream */
PRIORITY_LOW, /* Low-priority stream */
};
/* Standard demodulator functions */
static struct it913xset set_solo_fe[] = {
{PRO_LINK, GPIOH5_EN, {0x01}, 0x01},

View File

@ -57,6 +57,7 @@ struct it913x_fe_state {
u32 frequency;
fe_modulation_t constellation;
fe_transmit_mode_t transmission_mode;
u8 priority;
u32 crystalFrequency;
u32 adcFrequency;
u8 tuner_type;
@ -500,19 +501,87 @@ static int it913x_fe_read_status(struct dvb_frontend *fe, fe_status_t *status)
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 = it913x_read_reg_u8(state, SIGNAL_LEVEL);
/*SIGNAL_LEVEL always returns 100%! so using FE_HAS_SIGNAL as switch*/
if (state->it913x_status & FE_HAS_SIGNAL)
ret = (ret * 0xff) / 0x64;
else
ret = 0x0;
ret |= ret << 0x8;
*strength = ret;
return 0;
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)
@ -606,6 +675,8 @@ static int it913x_fe_get_frontend(struct dvb_frontend *fe)
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;
@ -972,5 +1043,5 @@ static struct dvb_frontend_ops it913x_fe_ofdm_ops = {
MODULE_DESCRIPTION("it913x Frontend and it9137 tuner");
MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com");
MODULE_VERSION("1.13");
MODULE_VERSION("1.15");
MODULE_LICENSE("GPL");

View File

@ -34,6 +34,8 @@ struct ite_config {
u8 tuner_id_1;
u8 dual_mode;
u8 adf;
/* option to read SIGNAL_LEVEL */
u8 read_slevel;
};
#if defined(CONFIG_DVB_IT913X_FE) || (defined(CONFIG_DVB_IT913X_FE_MODULE) && \
@ -168,6 +170,8 @@ static inline struct dvb_frontend *it913x_fe_attach(
#define EST_SIGNAL_LEVEL 0x004a
#define FREE_BAND 0x004b
#define SUSPEND_FLAG 0x004c
#define VAR_P_INBAND 0x00f7
/* Build in tuner types */
#define IT9137 0x38
#define IT9135_38 0x38

View File

@ -104,8 +104,8 @@ static int i2c_write_demod_bytes (struct lgdt330x_state* state,
* then reads the data returned for (len) bytes.
*/
static u8 i2c_read_demod_bytes (struct lgdt330x_state* state,
enum I2C_REG reg, u8* buf, int len)
static int i2c_read_demod_bytes(struct lgdt330x_state *state,
enum I2C_REG reg, u8 *buf, int len)
{
u8 wr [] = { reg };
struct i2c_msg msg [] = {
@ -118,6 +118,8 @@ static u8 i2c_read_demod_bytes (struct lgdt330x_state* state,
ret = i2c_transfer(state->i2c, msg, 2);
if (ret != 2) {
printk(KERN_WARNING "lgdt330x: %s: addr 0x%02x select 0x%02x error (ret == %i)\n", __func__, state->config->demod_address, reg, ret);
if (ret >= 0)
ret = -EIO;
} else {
ret = 0;
}

View File

@ -0,0 +1,904 @@
/*
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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/types.h>
#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)
{
deb_info("%s: %s\n", __func__,
volt == SEC_VOLTAGE_13 ? "SEC_VOLTAGE_13" :
volt == SEC_VOLTAGE_18 ? "SEC_VOLTAGE_18" : "??");
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)
{
int ret;
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;
}
ret = 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++) {
u8 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");

View File

@ -0,0 +1,66 @@
/*
Driver for M88RS2000 demodulator
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 M88RS2000_H
#define M88RS2000_H
#include <linux/dvb/frontend.h>
#include "dvb_frontend.h"
struct m88rs2000_config {
/* Demodulator i2c address */
u8 demod_addr;
/* Tuner address */
u8 tuner_addr;
u8 *inittab;
/* minimum delay before retuning */
int min_delay_ms;
int (*set_ts_params)(struct dvb_frontend *, int);
};
enum {
CALL_IS_SET_FRONTEND = 0x0,
CALL_IS_READ,
};
#if defined(CONFIG_DVB_M88RS2000) || (defined(CONFIG_DVB_M88RS2000_MODULE) && \
defined(MODULE))
extern struct dvb_frontend *m88rs2000_attach(
const struct m88rs2000_config *config, struct i2c_adapter *i2c);
#else
static inline struct dvb_frontend *m88rs2000_attach(
const struct m88rs2000_config *config, struct i2c_adapter *i2c)
{
printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
return NULL;
}
#endif /* CONFIG_DVB_M88RS2000 */
#define FE_CRYSTAL_KHZ 27000
#define FREQ_OFFSET_LOW_SYM_RATE 3000
enum {
DEMOD_WRITE = 0x1,
TUNER_WRITE,
WRITE_DELAY = 0x10,
};
#endif /* M88RS2000_H */

View File

@ -0,0 +1,562 @@
/*
* Realtek RTL2830 DVB-T demodulator driver
*
* Copyright (C) 2011 Antti Palosaari <crope@iki.fi>
*
* 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 = &reg,
}, {
.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_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)
{
*snr = 0;
return 0;
}
static int rtl2830_read_ber(struct dvb_frontend *fe, u32 *ber)
{
*ber = 0;
return 0;
}
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)
{
*strength = 0;
return 0;
}
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,
.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 <crope@iki.fi>");
MODULE_DESCRIPTION("Realtek RTL2830 DVB-T demodulator driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,97 @@
/*
* Realtek RTL2830 DVB-T demodulator driver
*
* Copyright (C) 2011 Antti Palosaari <crope@iki.fi>
*
* 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 <linux/dvb/frontend.h>
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 */

View File

@ -0,0 +1,57 @@
/*
* Realtek RTL2830 DVB-T demodulator driver
*
* Copyright (C) 2011 Antti Palosaari <crope@iki.fi>
*
* 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 "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 */

View File

@ -67,7 +67,7 @@ static const struct stb0899_tab stb0899_cn_tab[] = {
* Crude linear extrapolation below -84.8dBm and above -8.0dBm.
*/
static const struct stb0899_tab stb0899_dvbsrf_tab[] = {
{ -950, -128 },
{ -750, -128 },
{ -748, -94 },
{ -745, -92 },
{ -735, -90 },
@ -131,7 +131,7 @@ static const struct stb0899_tab stb0899_dvbs2rf_tab[] = {
{ -730, 13645 },
{ -750, 13909 },
{ -766, 14153 },
{ -999, 16383 }
{ -950, 16383 }
};
/* DVB-S2 Es/N0 quant in dB/100 vs read value * 100*/
@ -964,6 +964,7 @@ static int stb0899_read_signal_strength(struct dvb_frontend *fe, u16 *strength)
int val;
u32 reg;
*strength = 0;
switch (state->delsys) {
case SYS_DVBS:
case SYS_DSS:
@ -983,11 +984,11 @@ static int stb0899_read_signal_strength(struct dvb_frontend *fe, u16 *strength)
break;
case SYS_DVBS2:
if (internal->lock) {
reg = STB0899_READ_S2REG(STB0899_DEMOD, IF_AGC_GAIN);
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 += 750;
*strength += 950;
dprintk(state->verbose, FE_DEBUG, 1, "IF_AGC_GAIN = 0x%04x, C = %d * 0.1 dBm",
val & 0x3fff, *strength);
}
@ -1009,6 +1010,7 @@ static int stb0899_read_snr(struct dvb_frontend *fe, u16 *snr)
u8 buf[2];
u32 reg;
*snr = 0;
reg = stb0899_read_reg(state, STB0899_VSTATUS);
switch (state->delsys) {
case SYS_DVBS:
@ -1071,7 +1073,7 @@ static int stb0899_read_status(struct dvb_frontend *fe, enum fe_status *status)
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_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)) {

View File

@ -506,7 +506,7 @@ static int stv0288_set_frontend(struct dvb_frontend *fe)
tda[1] = (unsigned char)tm;
stv0288_writeregI(state, 0x2b, tda[1]);
stv0288_writeregI(state, 0x2c, tda[2]);
udelay(30);
msleep(30);
}
state->tuner_frequency = c->frequency;
state->fec_inner = FEC_AUTO;

View File

@ -1215,7 +1215,7 @@ error:
EXPORT_SYMBOL(tda10071_attach);
static struct dvb_frontend_ops tda10071_ops = {
.delsys = { SYS_DVBT, SYS_DVBT2 },
.delsys = { SYS_DVBS, SYS_DVBS2 },
.info = {
.name = "NXP TDA10071",
.frequency_min = 950000,

View File

@ -216,6 +216,7 @@ static int demod_attach_drxk(struct ngene_channel *chan,
struct drxk_config config;
memset(&config, 0, sizeof(config));
config.microcode_name = "drxk_a3.mc";
config.adr = 0x29 + (chan->number ^ 2);
chan->fe = dvb_attach(drxk_attach, &config, i2c);

View File

@ -28,6 +28,7 @@
#include <linux/pci.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <linux/ratelimit.h>
#include "dvbdev.h"
#include "dvb_demux.h"
@ -77,6 +78,8 @@ struct pt1 {
struct pt1_adapter *adaps[PT1_NR_ADAPS];
struct pt1_table *tables;
struct task_struct *kthread;
int table_index;
int buf_index;
struct mutex lock;
int power;
@ -90,12 +93,12 @@ struct pt1_adapter {
u8 *buf;
int upacket_count;
int packet_count;
int st_count;
struct dvb_adapter adap;
struct dvb_demux demux;
int users;
struct dmxdev dmxdev;
struct dvb_net net;
struct dvb_frontend *fe;
int (*orig_set_voltage)(struct dvb_frontend *fe,
fe_sec_voltage_t voltage);
@ -119,7 +122,7 @@ static u32 pt1_read_reg(struct pt1 *pt1, int reg)
return readl(pt1->regs + reg * 4);
}
static int pt1_nr_tables = 64;
static int pt1_nr_tables = 8;
module_param_named(nr_tables, pt1_nr_tables, int, 0);
static void pt1_increment_table_count(struct pt1 *pt1)
@ -264,6 +267,7 @@ static int pt1_filter(struct pt1 *pt1, struct pt1_buffer_page *page)
struct pt1_adapter *adap;
int offset;
u8 *buf;
int sc;
if (!page->upackets[PT1_NR_UPACKETS - 1])
return 0;
@ -280,6 +284,16 @@ static int pt1_filter(struct pt1 *pt1, struct pt1_buffer_page *page)
else if (!adap->upacket_count)
continue;
if (upacket >> 24 & 1)
printk_ratelimited(KERN_INFO "earth-pt1: device "
"buffer overflowing. table[%d] buf[%d]\n",
pt1->table_index, pt1->buf_index);
sc = upacket >> 26 & 0x7;
if (adap->st_count != -1 && sc != ((adap->st_count + 1) & 0x7))
printk_ratelimited(KERN_INFO "earth-pt1: data loss"
" in streamID(adapter)[%d]\n", index);
adap->st_count = sc;
buf = adap->buf;
offset = adap->packet_count * 188 + adap->upacket_count * 3;
buf[offset] = upacket >> 16;
@ -303,30 +317,25 @@ static int pt1_filter(struct pt1 *pt1, struct pt1_buffer_page *page)
static int pt1_thread(void *data)
{
struct pt1 *pt1;
int table_index;
int buf_index;
struct pt1_buffer_page *page;
pt1 = data;
set_freezable();
table_index = 0;
buf_index = 0;
while (!kthread_should_stop()) {
try_to_freeze();
page = pt1->tables[table_index].bufs[buf_index].page;
page = pt1->tables[pt1->table_index].bufs[pt1->buf_index].page;
if (!pt1_filter(pt1, page)) {
schedule_timeout_interruptible((HZ + 999) / 1000);
continue;
}
if (++buf_index >= PT1_NR_BUFS) {
if (++pt1->buf_index >= PT1_NR_BUFS) {
pt1_increment_table_count(pt1);
buf_index = 0;
if (++table_index >= pt1_nr_tables)
table_index = 0;
pt1->buf_index = 0;
if (++pt1->table_index >= pt1_nr_tables)
pt1->table_index = 0;
}
}
@ -477,21 +486,60 @@ err:
return ret;
}
static int pt1_start_polling(struct pt1 *pt1)
{
int ret = 0;
mutex_lock(&pt1->lock);
if (!pt1->kthread) {
pt1->kthread = kthread_run(pt1_thread, pt1, "earth-pt1");
if (IS_ERR(pt1->kthread)) {
ret = PTR_ERR(pt1->kthread);
pt1->kthread = NULL;
}
}
mutex_unlock(&pt1->lock);
return ret;
}
static int pt1_start_feed(struct dvb_demux_feed *feed)
{
struct pt1_adapter *adap;
adap = container_of(feed->demux, struct pt1_adapter, demux);
if (!adap->users++)
if (!adap->users++) {
int ret;
ret = pt1_start_polling(adap->pt1);
if (ret)
return ret;
pt1_set_stream(adap->pt1, adap->index, 1);
}
return 0;
}
static void pt1_stop_polling(struct pt1 *pt1)
{
int i, count;
mutex_lock(&pt1->lock);
for (i = 0, count = 0; i < PT1_NR_ADAPS; i++)
count += pt1->adaps[i]->users;
if (count == 0 && pt1->kthread) {
kthread_stop(pt1->kthread);
pt1->kthread = NULL;
}
mutex_unlock(&pt1->lock);
}
static int pt1_stop_feed(struct dvb_demux_feed *feed)
{
struct pt1_adapter *adap;
adap = container_of(feed->demux, struct pt1_adapter, demux);
if (!--adap->users)
if (!--adap->users) {
pt1_set_stream(adap->pt1, adap->index, 0);
pt1_stop_polling(adap->pt1);
}
return 0;
}
@ -575,7 +623,6 @@ static int pt1_wakeup(struct dvb_frontend *fe)
static void pt1_free_adapter(struct pt1_adapter *adap)
{
dvb_net_release(&adap->net);
adap->demux.dmx.close(&adap->demux.dmx);
dvb_dmxdev_release(&adap->dmxdev);
dvb_dmx_release(&adap->demux);
@ -616,6 +663,7 @@ pt1_alloc_adapter(struct pt1 *pt1)
adap->buf = buf;
adap->upacket_count = 0;
adap->packet_count = 0;
adap->st_count = -1;
dvb_adap = &adap->adap;
dvb_adap->priv = adap;
@ -644,8 +692,6 @@ pt1_alloc_adapter(struct pt1 *pt1)
if (ret < 0)
goto err_dmx_release;
dvb_net_init(dvb_adap, &adap->net, &demux->dmx);
return adap;
err_dmx_release:
@ -1020,7 +1066,8 @@ static void __devexit pt1_remove(struct pci_dev *pdev)
pt1 = pci_get_drvdata(pdev);
regs = pt1->regs;
kthread_stop(pt1->kthread);
if (pt1->kthread)
kthread_stop(pt1->kthread);
pt1_cleanup_tables(pt1);
pt1_cleanup_frontends(pt1);
pt1_disable_ram(pt1);
@ -1043,7 +1090,6 @@ pt1_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
void __iomem *regs;
struct pt1 *pt1;
struct i2c_adapter *i2c_adap;
struct task_struct *kthread;
ret = pci_enable_device(pdev);
if (ret < 0)
@ -1139,17 +1185,8 @@ pt1_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
if (ret < 0)
goto err_pt1_cleanup_frontends;
kthread = kthread_run(pt1_thread, pt1, "pt1");
if (IS_ERR(kthread)) {
ret = PTR_ERR(kthread);
goto err_pt1_cleanup_tables;
}
pt1->kthread = kthread;
return 0;
err_pt1_cleanup_tables:
pt1_cleanup_tables(pt1);
err_pt1_cleanup_frontends:
pt1_cleanup_frontends(pt1);
err_pt1_disable_ram:

View File

@ -312,7 +312,7 @@ static void __exit media_devnode_exit(void)
unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES);
}
module_init(media_devnode_init)
subsys_initcall(media_devnode_init);
module_exit(media_devnode_exit)
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");

View File

@ -43,7 +43,7 @@ config USB_DSBR
config RADIO_MAXIRADIO
tristate "Guillemot MAXI Radio FM 2000 radio"
depends on VIDEO_V4L2 && PCI
depends on VIDEO_V4L2 && PCI && SND
---help---
Choose Y here if you have this radio card. This card may also be
found as Gemtek PCI FM.
@ -80,6 +80,16 @@ config RADIO_SI4713
To compile this driver as a module, choose M here: the
module will be called radio-si4713.
config USB_KEENE
tristate "Keene FM Transmitter USB support"
depends on USB && VIDEO_V4L2
---help---
Say Y here if you want to connect this type of FM transmitter
to your computer's USB port.
To compile this driver as a module, choose M here: the
module will be called radio-keene.
config RADIO_TEA5764
tristate "TEA5764 I2C FM radio support"
depends on I2C && VIDEO_V4L2
@ -167,6 +177,10 @@ menuconfig V4L_RADIO_ISA_DRIVERS
if V4L_RADIO_ISA_DRIVERS
config RADIO_ISA
depends on ISA
tristate
config RADIO_CADET
tristate "ADS Cadet AM/FM Tuner"
depends on ISA && VIDEO_V4L2
@ -174,20 +188,13 @@ config RADIO_CADET
Choose Y here if you have one of these AM/FM radio cards, and then
fill in the port address below.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>.
Further documentation on this driver can be found on the WWW at
<http://linux.blackhawke.net/cadet/>.
To compile this driver as a module, choose M here: the
module will be called radio-cadet.
config RADIO_RTRACK
tristate "AIMSlab RadioTrack (aka RadioReveal) support"
depends on ISA && VIDEO_V4L2
select RADIO_ISA
---help---
Choose Y here if you have one of these FM radio cards, and then fill
in the port address below.
@ -201,11 +208,7 @@ config RADIO_RTRACK
You must also pass the module a suitable io parameter, 0x248 has
been reported to be used by these cards.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>. More information is
contained in the file
More information is contained in the file
<file:Documentation/video4linux/radiotrack.txt>.
To compile this driver as a module, choose M here: the
@ -214,7 +217,7 @@ config RADIO_RTRACK
config RADIO_RTRACK_PORT
hex "RadioTrack i/o port (0x20f or 0x30f)"
depends on RADIO_RTRACK=y
default "20f"
default "30f"
help
Enter either 0x30f or 0x20f here. The card default is 0x30f, if you
haven't changed the jumper setting on the card.
@ -222,14 +225,14 @@ config RADIO_RTRACK_PORT
config RADIO_RTRACK2
tristate "AIMSlab RadioTrack II support"
depends on ISA && VIDEO_V4L2
select RADIO_ISA
---help---
Choose Y here if you have this FM radio card, and then fill in the
port address below.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
To compile this driver as a module, choose M here: the
module will be called radio-rtrack2.
@ -245,15 +248,11 @@ config RADIO_RTRACK2_PORT
config RADIO_AZTECH
tristate "Aztech/Packard Bell Radio"
depends on ISA && VIDEO_V4L2
select RADIO_ISA
---help---
Choose Y here if you have one of these FM radio cards, and then fill
in the port address below.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>.
To compile this driver as a module, choose M here: the
module will be called radio-aztech.
@ -269,6 +268,7 @@ config RADIO_AZTECH_PORT
config RADIO_GEMTEK
tristate "GemTek Radio card (or compatible) support"
depends on ISA && VIDEO_V4L2
select RADIO_ISA
---help---
Choose Y here if you have this FM radio card, and then fill in the
I/O port address and settings below. The following cards either have
@ -278,23 +278,21 @@ config RADIO_GEMTEK
- Typhoon Radio card (some models)
- Hama Radio card
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>.
To compile this driver as a module, choose M here: the
module will be called radio-gemtek.
config RADIO_GEMTEK_PORT
hex "Fixed I/O port (0x20c, 0x30c, 0x24c, 0x34c, 0c24c or 0x28c)"
hex "Fixed I/O port (0x20c, 0x30c, 0x24c, 0x34c, 0x248 or 0x28c)"
depends on RADIO_GEMTEK=y
default "34c"
help
Enter either 0x20c, 0x30c, 0x24c or 0x34c here. The card default is
0x34c, if you haven't changed the jumper setting on the card. On
Sound Vision 16 Gold PnP with FM Radio (ESS1869+FM Gemtek), the I/O
Enter either 0x20c, 0x30c, 0x24c, 0x34c, 0x248 or 0x28c here. The
card default is 0x34c, if you haven't changed the jumper setting
on the card.
On Sound Vision 16 Gold PnP with FM Radio (ESS1869+FM Gemtek), the I/O
port is 0x20c, 0x248 or 0x28c.
If automatic I/O port probing is enabled this port will be used only
in case of automatic probing failure, ie. as a fallback.
@ -318,11 +316,6 @@ config RADIO_MIROPCM20
sound card driver "Miro miroSOUND PCM1pro/PCM12/PCM20radio" as this
is required for the radio-miropcm20.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>.
To compile this driver as a module, choose M here: the
module will be called radio-miropcm20.
@ -332,11 +325,6 @@ config RADIO_SF16FMI
---help---
Choose Y here if you have one of these FM radio cards.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>.
To compile this driver as a module, choose M here: the
module will be called radio-sf16fmi.
@ -346,50 +334,35 @@ config RADIO_SF16FMR2
---help---
Choose Y here if you have one of these FM radio cards.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found on the WWW at
<http://roadrunner.swansea.uk.linux.org/v4l.shtml>.
To compile this driver as a module, choose M here: the
module will be called radio-sf16fmr2.
config RADIO_TERRATEC
tristate "TerraTec ActiveRadio ISA Standalone"
depends on ISA && VIDEO_V4L2
select RADIO_ISA
---help---
Choose Y here if you have this FM radio card, and then fill in the
port address below. (TODO)
Choose Y here if you have this FM radio card.
Note: This driver is in its early stages. Right now volume and
frequency control and muting works at least for me, but
unfortunately I have not found anybody who wants to use this card
with Linux. So if it is this what YOU are trying to do right now,
PLEASE DROP ME A NOTE!! Rolf Offermanns <rolf@offermanns.de>.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
To compile this driver as a module, choose M here: the
module will be called radio-terratec.
config RADIO_TERRATEC_PORT
hex "Terratec i/o port (normally 0x590)"
depends on RADIO_TERRATEC=y
default "590"
help
Fill in the I/O port of your TerraTec FM radio card. If unsure, go
with the default.
config RADIO_TRUST
tristate "Trust FM radio card"
depends on ISA && VIDEO_V4L2
select RADIO_ISA
help
This is a driver for the Trust FM radio cards. Say Y if you have
such a card and want to use it under Linux.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
To compile this driver as a module, choose M here: the
module will be called radio-trust.
@ -404,14 +377,14 @@ config RADIO_TRUST_PORT
config RADIO_TYPHOON
tristate "Typhoon Radio (a.k.a. EcoRadio)"
depends on ISA && VIDEO_V4L2
select RADIO_ISA
---help---
Choose Y here if you have one of these FM radio cards, and then fill
in the port address and the frequency used for muting below.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
To compile this driver as a module, choose M here: the
module will be called radio-typhoon.
@ -438,14 +411,14 @@ config RADIO_TYPHOON_MUTEFREQ
config RADIO_ZOLTRIX
tristate "Zoltrix Radio"
depends on ISA && VIDEO_V4L2
select RADIO_ISA
---help---
Choose Y here if you have one of these FM radio cards, and then fill
in the port address below.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
To compile this driver as a module, choose M here: the
module will be called radio-zoltrix.

View File

@ -2,6 +2,7 @@
# Makefile for the kernel character device drivers.
#
obj-$(CONFIG_RADIO_ISA) += radio-isa.o
obj-$(CONFIG_RADIO_AZTECH) += radio-aztech.o
obj-$(CONFIG_RADIO_RTRACK2) += radio-rtrack2.o
obj-$(CONFIG_RADIO_SF16FMI) += radio-sf16fmi.o
@ -20,6 +21,7 @@ obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o
obj-$(CONFIG_USB_DSBR) += dsbr100.o
obj-$(CONFIG_RADIO_SI470X) += si470x/
obj-$(CONFIG_USB_MR800) += radio-mr800.o
obj-$(CONFIG_USB_KEENE) += radio-keene.o
obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o
obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o
obj-$(CONFIG_RADIO_TEF6862) += tef6862.o

View File

@ -1,16 +1,13 @@
/* radiotrack (radioreveal) driver for Linux radio support
* (c) 1997 M. Kirkwood
/*
* AimsLab RadioTrack (aka RadioVeveal) driver
*
* Copyright 1997 M. Kirkwood
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
* Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk>
* Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
*
* History:
* 1999-02-24 Russell Kroll <rkroll@exploits.org>
* Fine tuning/VIDEO_TUNER_LOW
* Frequency range expanded to start at 87 MHz
*
* TODO: Allow for more than one of these foolish entities :-)
*
* Notes on the hardware (reverse engineered from other peoples'
* reverse engineering of AIMS' code :-)
*
@ -26,6 +23,7 @@
* wait(a_wee_while);
* out(port, stop_changing_the_volume);
*
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*/
#include <linux/module.h> /* Modules */
@ -34,401 +32,179 @@
#include <linux/delay.h> /* msleep */
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include "radio-isa.h"
MODULE_AUTHOR("M.Kirkwood");
MODULE_AUTHOR("M. Kirkwood");
MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.3");
MODULE_VERSION("1.0.0");
#ifndef CONFIG_RADIO_RTRACK_PORT
#define CONFIG_RADIO_RTRACK_PORT -1
#endif
static int io = CONFIG_RADIO_RTRACK_PORT;
static int radio_nr = -1;
#define RTRACK_MAX 2
module_param(io, int, 0);
MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)");
module_param(radio_nr, int, 0);
static int io[RTRACK_MAX] = { [0] = CONFIG_RADIO_RTRACK_PORT,
[1 ... (RTRACK_MAX - 1)] = -1 };
static int radio_nr[RTRACK_MAX] = { [0 ... (RTRACK_MAX - 1)] = -1 };
struct rtrack
{
struct v4l2_device v4l2_dev;
struct video_device vdev;
int port;
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
struct rtrack {
struct radio_isa_card isa;
int curvol;
unsigned long curfreq;
int muted;
int io;
struct mutex lock;
};
static struct rtrack rtrack_card;
/* local things */
static void rt_decvol(struct rtrack *rt)
static struct radio_isa_card *rtrack_alloc(void)
{
outb(0x58, rt->io); /* volume down + sigstr + on */
msleep(100);
outb(0xd8, rt->io); /* volume steady + sigstr + on */
struct rtrack *rt = kzalloc(sizeof(struct rtrack), GFP_KERNEL);
if (rt)
rt->curvol = 0xff;
return rt ? &rt->isa : NULL;
}
static void rt_incvol(struct rtrack *rt)
{
outb(0x98, rt->io); /* volume up + sigstr + on */
msleep(100);
outb(0xd8, rt->io); /* volume steady + sigstr + on */
}
static void rt_mute(struct rtrack *rt)
{
rt->muted = 1;
mutex_lock(&rt->lock);
outb(0xd0, rt->io); /* volume steady, off */
mutex_unlock(&rt->lock);
}
static int rt_setvol(struct rtrack *rt, int vol)
{
int i;
mutex_lock(&rt->lock);
if (vol == rt->curvol) { /* requested volume = current */
if (rt->muted) { /* user is unmuting the card */
rt->muted = 0;
outb(0xd8, rt->io); /* enable card */
}
mutex_unlock(&rt->lock);
return 0;
}
if (vol == 0) { /* volume = 0 means mute the card */
outb(0x48, rt->io); /* volume down but still "on" */
msleep(2000); /* make sure it's totally down */
outb(0xd0, rt->io); /* volume steady, off */
rt->curvol = 0; /* track the volume state! */
mutex_unlock(&rt->lock);
return 0;
}
rt->muted = 0;
if (vol > rt->curvol)
for (i = rt->curvol; i < vol; i++)
rt_incvol(rt);
else
for (i = rt->curvol; i > vol; i--)
rt_decvol(rt);
rt->curvol = vol;
mutex_unlock(&rt->lock);
return 0;
}
/* the 128+64 on these outb's is to keep the volume stable while tuning
* without them, the volume _will_ creep up with each frequency change
* and bit 4 (+16) is to keep the signal strength meter enabled
/* The 128+64 on these outb's is to keep the volume stable while tuning.
* Without them, the volume _will_ creep up with each frequency change
* and bit 4 (+16) is to keep the signal strength meter enabled.
*/
static void send_0_byte(struct rtrack *rt)
static void send_0_byte(struct radio_isa_card *isa, int on)
{
if (rt->curvol == 0 || rt->muted) {
outb_p(128+64+16+ 1, rt->io); /* wr-enable + data low */
outb_p(128+64+16+2+1, rt->io); /* clock */
}
else {
outb_p(128+64+16+8+ 1, rt->io); /* on + wr-enable + data low */
outb_p(128+64+16+8+2+1, rt->io); /* clock */
}
outb_p(128+64+16+on+1, isa->io); /* wr-enable + data low */
outb_p(128+64+16+on+2+1, isa->io); /* clock */
msleep(1);
}
static void send_1_byte(struct rtrack *rt)
static void send_1_byte(struct radio_isa_card *isa, int on)
{
if (rt->curvol == 0 || rt->muted) {
outb_p(128+64+16+4 +1, rt->io); /* wr-enable+data high */
outb_p(128+64+16+4+2+1, rt->io); /* clock */
}
else {
outb_p(128+64+16+8+4 +1, rt->io); /* on+wr-enable+data high */
outb_p(128+64+16+8+4+2+1, rt->io); /* clock */
}
outb_p(128+64+16+on+4+1, isa->io); /* wr-enable+data high */
outb_p(128+64+16+on+4+2+1, isa->io); /* clock */
msleep(1);
}
static int rt_setfreq(struct rtrack *rt, unsigned long freq)
static int rtrack_s_frequency(struct radio_isa_card *isa, u32 freq)
{
int on = v4l2_ctrl_g_ctrl(isa->mute) ? 0 : 8;
int i;
mutex_lock(&rt->lock); /* Stop other ops interfering */
rt->curfreq = freq;
/* now uses VIDEO_TUNER_LOW for fine tuning */
freq += 171200; /* Add 10.7 MHz IF */
freq /= 800; /* Convert to 50 kHz units */
send_0_byte(rt); /* 0: LSB of frequency */
send_0_byte(isa, on); /* 0: LSB of frequency */
for (i = 0; i < 13; i++) /* : frequency bits (1-13) */
if (freq & (1 << i))
send_1_byte(rt);
send_1_byte(isa, on);
else
send_0_byte(rt);
send_0_byte(isa, on);
send_0_byte(rt); /* 14: test bit - always 0 */
send_0_byte(rt); /* 15: test bit - always 0 */
send_0_byte(isa, on); /* 14: test bit - always 0 */
send_0_byte(isa, on); /* 15: test bit - always 0 */
send_0_byte(rt); /* 16: band data 0 - always 0 */
send_0_byte(rt); /* 17: band data 1 - always 0 */
send_0_byte(rt); /* 18: band data 2 - always 0 */
send_0_byte(rt); /* 19: time base - always 0 */
send_0_byte(isa, on); /* 16: band data 0 - always 0 */
send_0_byte(isa, on); /* 17: band data 1 - always 0 */
send_0_byte(isa, on); /* 18: band data 2 - always 0 */
send_0_byte(isa, on); /* 19: time base - always 0 */
send_0_byte(rt); /* 20: spacing (0 = 25 kHz) */
send_1_byte(rt); /* 21: spacing (1 = 25 kHz) */
send_0_byte(rt); /* 22: spacing (0 = 25 kHz) */
send_1_byte(rt); /* 23: AM/FM (FM = 1, always) */
if (rt->curvol == 0 || rt->muted)
outb(0xd0, rt->io); /* volume steady + sigstr */
else
outb(0xd8, rt->io); /* volume steady + sigstr + on */
mutex_unlock(&rt->lock);
send_0_byte(isa, on); /* 20: spacing (0 = 25 kHz) */
send_1_byte(isa, on); /* 21: spacing (1 = 25 kHz) */
send_0_byte(isa, on); /* 22: spacing (0 = 25 kHz) */
send_1_byte(isa, on); /* 23: AM/FM (FM = 1, always) */
outb(0xd0 + on, isa->io); /* volume steady + sigstr */
return 0;
}
static int rt_getsigstr(struct rtrack *rt)
static u32 rtrack_g_signal(struct radio_isa_card *isa)
{
int sig = 1;
mutex_lock(&rt->lock);
if (inb(rt->io) & 2) /* bit set = no signal present */
sig = 0;
mutex_unlock(&rt->lock);
return sig;
/* bit set = no signal present */
return 0xffff * !(inb(isa->io) & 2);
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
static int rtrack_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
strlcpy(v->driver, "radio-aimslab", sizeof(v->driver));
strlcpy(v->card, "RadioTrack", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
struct rtrack *rt = container_of(isa, struct rtrack, isa);
int curvol = rt->curvol;
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct rtrack *rt = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = 87 * 16000;
v->rangehigh = 108 * 16000;
v->rxsubchans = V4L2_TUNER_SUB_MONO;
v->capability = V4L2_TUNER_CAP_LOW;
v->audmode = V4L2_TUNER_MODE_MONO;
v->signal = 0xffff * rt_getsigstr(rt);
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct rtrack *rt = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
rt_setfreq(rt, f->frequency);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct rtrack *rt = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = rt->curfreq;
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
case V4L2_CID_AUDIO_VOLUME:
return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff);
}
return -EINVAL;
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct rtrack *rt = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
ctrl->value = rt->muted;
return 0;
case V4L2_CID_AUDIO_VOLUME:
ctrl->value = rt->curvol;
if (mute) {
outb(0xd0, isa->io); /* volume steady + sigstr + off */
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct rtrack *rt = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
rt_mute(rt);
else
rt_setvol(rt, rt->curvol);
return 0;
case V4L2_CID_AUDIO_VOLUME:
rt_setvol(rt, ctrl->value);
return 0;
if (vol == 0) { /* volume = 0 means mute the card */
outb(0x48, isa->io); /* volume down but still "on" */
msleep(curvol * 3); /* make sure it's totally down */
} else if (curvol < vol) {
outb(0x98, isa->io); /* volume up + sigstr + on */
for (; curvol < vol; curvol++)
udelay(3000);
} else if (curvol > vol) {
outb(0x58, isa->io); /* volume down + sigstr + on */
for (; curvol > vol; curvol--)
udelay(3000);
}
return -EINVAL;
}
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
outb(0xd8, isa->io); /* volume steady + sigstr + on */
rt->curvol = vol;
return 0;
}
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
/* Mute card - prevents noisy bootups */
static int rtrack_initialize(struct radio_isa_card *isa)
{
return i ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
/* this ensures that the volume is all the way up */
outb(0x90, isa->io); /* volume up but still "on" */
msleep(3000); /* make sure it's totally up */
outb(0xc0, isa->io); /* steady volume, mute card */
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static const struct v4l2_file_operations rtrack_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
static const struct radio_isa_ops rtrack_ops = {
.alloc = rtrack_alloc,
.init = rtrack_initialize,
.s_mute_volume = rtrack_s_mute_volume,
.s_frequency = rtrack_s_frequency,
.g_signal = rtrack_g_signal,
};
static const struct v4l2_ioctl_ops rtrack_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_g_tuner = vidioc_g_tuner,
.vidioc_s_tuner = vidioc_s_tuner,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
static const int rtrack_ioports[] = { 0x20f, 0x30f };
static struct radio_isa_driver rtrack_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-aimslab",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = rtrack_ioports,
.num_of_io_ports = ARRAY_SIZE(rtrack_ioports),
.region_size = 2,
.card = "AIMSlab RadioTrack/RadioReveal",
.ops = &rtrack_ops,
.has_stereo = true,
.max_volume = 0xff,
};
static int __init rtrack_init(void)
{
struct rtrack *rt = &rtrack_card;
struct v4l2_device *v4l2_dev = &rt->v4l2_dev;
int res;
strlcpy(v4l2_dev->name, "rtrack", sizeof(v4l2_dev->name));
rt->io = io;
if (rt->io == -1) {
v4l2_err(v4l2_dev, "you must set an I/O address with io=0x20f or 0x30f\n");
return -EINVAL;
}
if (!request_region(rt->io, 2, "rtrack")) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n", rt->io);
return -EBUSY;
}
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(rt->io, 2);
v4l2_err(v4l2_dev, "could not register v4l2_device\n");
return res;
}
strlcpy(rt->vdev.name, v4l2_dev->name, sizeof(rt->vdev.name));
rt->vdev.v4l2_dev = v4l2_dev;
rt->vdev.fops = &rtrack_fops;
rt->vdev.ioctl_ops = &rtrack_ioctl_ops;
rt->vdev.release = video_device_release_empty;
video_set_drvdata(&rt->vdev, rt);
/* Set up the I/O locking */
mutex_init(&rt->lock);
/* mute card - prevents noisy bootups */
/* this ensures that the volume is all the way down */
outb(0x48, rt->io); /* volume down but still "on" */
msleep(2000); /* make sure it's totally down */
outb(0xc0, rt->io); /* steady volume, mute card */
if (video_register_device(&rt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(&rt->v4l2_dev);
release_region(rt->io, 2);
return -EINVAL;
}
v4l2_info(v4l2_dev, "AIMSlab RadioTrack/RadioReveal card driver.\n");
return 0;
return isa_register_driver(&rtrack_driver.driver, RTRACK_MAX);
}
static void __exit rtrack_exit(void)
{
struct rtrack *rt = &rtrack_card;
video_unregister_device(&rt->vdev);
v4l2_device_unregister(&rt->v4l2_dev);
release_region(rt->io, 2);
isa_unregister_driver(&rtrack_driver.driver);
}
module_init(rtrack_init);
module_exit(rtrack_exit);

View File

@ -1,5 +1,7 @@
/* radio-aztech.c - Aztech radio card driver for Linux 2.2
/*
* radio-aztech.c - Aztech radio card driver
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@xs4all.nl>
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
* Adapted to support the Video for Linux API by
* Russell Kroll <rkroll@exploits.org>. Based on original tuner code by:
@ -10,19 +12,7 @@
* Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
* William McGrath (wmcgrath@twilight.vtc.vsc.edu)
*
* The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/
* along with more information on the card itself.
*
* History:
* 1999-02-24 Russell Kroll <rkroll@exploits.org>
* Fine tuning/VIDEO_TUNER_LOW
* Range expanded to 87-108 MHz (from 87.9-107.8)
*
* Notable changes from the original source:
* - includes stripped down to the essentials
* - for loops used as delays replaced with udelay()
* - #defines removed, changed to static values
* - tuning structure changed - no more character arrays, other changes
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*/
#include <linux/module.h> /* Modules */
@ -31,126 +21,72 @@
#include <linux/delay.h> /* udelay */
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include "radio-isa.h"
MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
MODULE_DESCRIPTION("A driver for the Aztech radio card.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.3");
MODULE_VERSION("1.0.0");
/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
#ifndef CONFIG_RADIO_AZTECH_PORT
#define CONFIG_RADIO_AZTECH_PORT -1
#endif
static int io = CONFIG_RADIO_AZTECH_PORT;
static int radio_nr = -1;
static int radio_wait_time = 1000;
#define AZTECH_MAX 2
module_param(io, int, 0);
module_param(radio_nr, int, 0);
MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)");
static int io[AZTECH_MAX] = { [0] = CONFIG_RADIO_AZTECH_PORT,
[1 ... (AZTECH_MAX - 1)] = -1 };
static int radio_nr[AZTECH_MAX] = { [0 ... (AZTECH_MAX - 1)] = -1 };
static const int radio_wait_time = 1000;
struct aztech
{
struct v4l2_device v4l2_dev;
struct video_device vdev;
int io;
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the Aztech card (0x350 or 0x358)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
struct aztech {
struct radio_isa_card isa;
int curvol;
unsigned long curfreq;
int stereo;
struct mutex lock;
};
static struct aztech aztech_card;
static int volconvert(int level)
{
level >>= 14; /* Map 16bits down to 2 bit */
level &= 3;
/* convert to card-friendly values */
switch (level) {
case 0:
return 0;
case 1:
return 1;
case 2:
return 4;
case 3:
return 5;
}
return 0; /* Quieten gcc */
}
static void send_0_byte(struct aztech *az)
{
udelay(radio_wait_time);
outb_p(2 + volconvert(az->curvol), az->io);
outb_p(64 + 2 + volconvert(az->curvol), az->io);
outb_p(2 + az->curvol, az->isa.io);
outb_p(64 + 2 + az->curvol, az->isa.io);
}
static void send_1_byte(struct aztech *az)
{
udelay (radio_wait_time);
outb_p(128 + 2 + volconvert(az->curvol), az->io);
outb_p(128 + 64 + 2 + volconvert(az->curvol), az->io);
udelay(radio_wait_time);
outb_p(128 + 2 + az->curvol, az->isa.io);
outb_p(128 + 64 + 2 + az->curvol, az->isa.io);
}
static int az_setvol(struct aztech *az, int vol)
static struct radio_isa_card *aztech_alloc(void)
{
mutex_lock(&az->lock);
outb(volconvert(vol), az->io);
mutex_unlock(&az->lock);
return 0;
struct aztech *az = kzalloc(sizeof(*az), GFP_KERNEL);
return az ? &az->isa : NULL;
}
/* thanks to Michael Dwyer for giving me a dose of clues in
* the signal strength department..
*
* This card has a stereo bit - bit 0 set = mono, not set = stereo
* It also has a "signal" bit - bit 1 set = bad signal, not set = good
*
*/
static int az_getsigstr(struct aztech *az)
{
int sig = 1;
mutex_lock(&az->lock);
if (inb(az->io) & 2) /* bit set = no signal present */
sig = 0;
mutex_unlock(&az->lock);
return sig;
}
static int az_getstereo(struct aztech *az)
{
int stereo = 1;
mutex_lock(&az->lock);
if (inb(az->io) & 1) /* bit set = mono */
stereo = 0;
mutex_unlock(&az->lock);
return stereo;
}
static int az_setfreq(struct aztech *az, unsigned long frequency)
static int aztech_s_frequency(struct radio_isa_card *isa, u32 freq)
{
struct aztech *az = container_of(isa, struct aztech, isa);
int i;
mutex_lock(&az->lock);
az->curfreq = frequency;
frequency += 171200; /* Add 10.7 MHz IF */
frequency /= 800; /* Convert to 50 kHz units */
freq += 171200; /* Add 10.7 MHz IF */
freq /= 800; /* Convert to 50 kHz units */
send_0_byte(az); /* 0: LSB of frequency */
for (i = 0; i < 13; i++) /* : frequency bits (1-13) */
if (frequency & (1 << i))
if (freq & (1 << i))
send_1_byte(az);
else
send_0_byte(az);
@ -158,7 +94,7 @@ static int az_setfreq(struct aztech *az, unsigned long frequency)
send_0_byte(az); /* 14: test bit - always 0 */
send_0_byte(az); /* 15: test bit - always 0 */
send_0_byte(az); /* 16: band data 0 - always 0 */
if (az->stereo) /* 17: stereo (1 to enable) */
if (isa->stereo) /* 17: stereo (1 to enable) */
send_1_byte(az);
else
send_0_byte(az);
@ -173,225 +109,77 @@ static int az_setfreq(struct aztech *az, unsigned long frequency)
/* latch frequency */
udelay(radio_wait_time);
outb_p(128 + 64 + volconvert(az->curvol), az->io);
mutex_unlock(&az->lock);
outb_p(128 + 64 + az->curvol, az->isa.io);
return 0;
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
/* thanks to Michael Dwyer for giving me a dose of clues in
* the signal strength department..
*
* This card has a stereo bit - bit 0 set = mono, not set = stereo
*/
static u32 aztech_g_rxsubchans(struct radio_isa_card *isa)
{
strlcpy(v->driver, "radio-aztech", sizeof(v->driver));
strlcpy(v->card, "Aztech Radio", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
if (inb(isa->io) & 1)
return V4L2_TUNER_SUB_MONO;
return V4L2_TUNER_SUB_STEREO;
}
static int aztech_s_stereo(struct radio_isa_card *isa, bool stereo)
{
return aztech_s_frequency(isa, isa->freq);
}
static int aztech_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
struct aztech *az = container_of(isa, struct aztech, isa);
if (mute)
vol = 0;
az->curvol = (vol & 1) + ((vol & 2) << 1);
outb(az->curvol, isa->io);
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct aztech *az = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = 87 * 16000;
v->rangehigh = 108 * 16000;
v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
v->capability = V4L2_TUNER_CAP_LOW;
if (az_getstereo(az))
v->audmode = V4L2_TUNER_MODE_STEREO;
else
v->audmode = V4L2_TUNER_MODE_MONO;
v->signal = 0xFFFF * az_getsigstr(az);
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
{
return i ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct aztech *az = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
az_setfreq(az, f->frequency);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct aztech *az = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = az->curfreq;
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
case V4L2_CID_AUDIO_VOLUME:
return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff);
}
return -EINVAL;
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct aztech *az = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (az->curvol == 0)
ctrl->value = 1;
else
ctrl->value = 0;
return 0;
case V4L2_CID_AUDIO_VOLUME:
ctrl->value = az->curvol * 6554;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct aztech *az = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
az_setvol(az, 0);
else
az_setvol(az, az->curvol);
return 0;
case V4L2_CID_AUDIO_VOLUME:
az_setvol(az, ctrl->value);
return 0;
}
return -EINVAL;
}
static const struct v4l2_file_operations aztech_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
static const struct radio_isa_ops aztech_ops = {
.alloc = aztech_alloc,
.s_mute_volume = aztech_s_mute_volume,
.s_frequency = aztech_s_frequency,
.s_stereo = aztech_s_stereo,
.g_rxsubchans = aztech_g_rxsubchans,
};
static const struct v4l2_ioctl_ops aztech_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_g_tuner = vidioc_g_tuner,
.vidioc_s_tuner = vidioc_s_tuner,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
static const int aztech_ioports[] = { 0x350, 0x358 };
static struct radio_isa_driver aztech_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-aztech",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = aztech_ioports,
.num_of_io_ports = ARRAY_SIZE(aztech_ioports),
.region_size = 2,
.card = "Aztech Radio",
.ops = &aztech_ops,
.has_stereo = true,
.max_volume = 3,
};
static int __init aztech_init(void)
{
struct aztech *az = &aztech_card;
struct v4l2_device *v4l2_dev = &az->v4l2_dev;
int res;
strlcpy(v4l2_dev->name, "aztech", sizeof(v4l2_dev->name));
az->io = io;
if (az->io == -1) {
v4l2_err(v4l2_dev, "you must set an I/O address with io=0x350 or 0x358\n");
return -EINVAL;
}
if (!request_region(az->io, 2, "aztech")) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n", az->io);
return -EBUSY;
}
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(az->io, 2);
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
return res;
}
mutex_init(&az->lock);
strlcpy(az->vdev.name, v4l2_dev->name, sizeof(az->vdev.name));
az->vdev.v4l2_dev = v4l2_dev;
az->vdev.fops = &aztech_fops;
az->vdev.ioctl_ops = &aztech_ioctl_ops;
az->vdev.release = video_device_release_empty;
video_set_drvdata(&az->vdev, az);
/* mute card - prevents noisy bootups */
outb(0, az->io);
if (video_register_device(&az->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(v4l2_dev);
release_region(az->io, 2);
return -EINVAL;
}
v4l2_info(v4l2_dev, "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n");
return 0;
return isa_register_driver(&aztech_driver.driver, AZTECH_MAX);
}
static void __exit aztech_exit(void)
{
struct aztech *az = &aztech_card;
video_unregister_device(&az->vdev);
v4l2_device_unregister(&az->v4l2_dev);
release_region(az->io, 2);
isa_unregister_driver(&aztech_driver.driver);
}
module_init(aztech_init);

View File

@ -1,4 +1,7 @@
/* GemTek radio card driver for Linux (C) 1998 Jonas Munsin <jmunsin@iki.fi>
/*
* GemTek radio card driver
*
* Copyright 1998 Jonas Munsin <jmunsin@iki.fi>
*
* GemTek hasn't released any specs on the card, so the protocol had to
* be reverse engineered with dosemu.
@ -11,9 +14,12 @@
* Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk>
* Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
*
* TODO: Allow for more than one of these foolish entities :-)
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
*
* Note: this card seems to swap the left and right audio channels!
*
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*/
#include <linux/module.h> /* Modules */
@ -23,8 +29,10 @@
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/mutex.h>
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-device.h>
#include "radio-isa.h"
/*
* Module info.
@ -33,7 +41,7 @@
MODULE_AUTHOR("Jonas Munsin, Pekka Seppänen <pexu@kapsi.fi>");
MODULE_DESCRIPTION("A driver for the GemTek Radio card.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.4");
MODULE_VERSION("1.0.0");
/*
* Module params.
@ -46,45 +54,29 @@ MODULE_VERSION("0.0.4");
#define CONFIG_RADIO_GEMTEK_PROBE 1
#endif
static int io = CONFIG_RADIO_GEMTEK_PORT;
static bool probe = CONFIG_RADIO_GEMTEK_PROBE;
static bool hardmute;
static bool shutdown = 1;
static bool keepmuted = 1;
static bool initmute = 1;
static int radio_nr = -1;
#define GEMTEK_MAX 4
module_param(io, int, 0444);
MODULE_PARM_DESC(io, "Force I/O port for the GemTek Radio card if automatic "
static bool probe = CONFIG_RADIO_GEMTEK_PROBE;
static bool hardmute;
static int io[GEMTEK_MAX] = { [0] = CONFIG_RADIO_GEMTEK_PORT,
[1 ... (GEMTEK_MAX - 1)] = -1 };
static int radio_nr[GEMTEK_MAX] = { [0 ... (GEMTEK_MAX - 1)] = -1 };
module_param(probe, bool, 0444);
MODULE_PARM_DESC(probe, "Enable automatic device probing.");
module_param(hardmute, bool, 0644);
MODULE_PARM_DESC(hardmute, "Enable 'hard muting' by shutting down PLL, may "
"reduce static noise.");
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "Force I/O ports for the GemTek Radio card if automatic "
"probing is disabled or fails. The most common I/O ports are: 0x20c "
"0x30c, 0x24c or 0x34c (0x20c, 0x248 and 0x28c have been reported to "
"work for the combined sound/radiocard).");
module_param(probe, bool, 0444);
MODULE_PARM_DESC(probe, "Enable automatic device probing. Note: only the most "
"common I/O ports used by the card are probed.");
module_param(hardmute, bool, 0644);
MODULE_PARM_DESC(hardmute, "Enable `hard muting' by shutting down PLL, may "
"reduce static noise.");
module_param(shutdown, bool, 0644);
MODULE_PARM_DESC(shutdown, "Enable shutting down PLL and muting line when "
"module is unloaded.");
module_param(keepmuted, bool, 0644);
MODULE_PARM_DESC(keepmuted, "Keep card muted even when frequency is changed.");
module_param(initmute, bool, 0444);
MODULE_PARM_DESC(initmute, "Mute card when module is loaded.");
module_param(radio_nr, int, 0444);
/*
* Functions for controlling the card.
*/
#define GEMTEK_LOWFREQ (87*16000)
#define GEMTEK_HIGHFREQ (108*16000)
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
/*
* Frequency calculation constants. Intermediate frequency 10.52 MHz (nominal
@ -108,18 +100,11 @@ module_param(radio_nr, int, 0444);
#define LONG_DELAY 75 /* usec */
struct gemtek {
struct v4l2_device v4l2_dev;
struct video_device vdev;
struct mutex lock;
unsigned long lastfreq;
int muted;
int verified;
int io;
struct radio_isa_card isa;
bool muted;
u32 bu2614data;
};
static struct gemtek gemtek_card;
#define BU2614_FREQ_BITS 16 /* D0..D15, Frequency data */
#define BU2614_PORT_BITS 3 /* P0..P2, Output port control data */
#define BU2614_VOID_BITS 4 /* unused */
@ -166,31 +151,24 @@ static struct gemtek gemtek_card;
*/
static void gemtek_bu2614_transmit(struct gemtek *gt)
{
struct radio_isa_card *isa = &gt->isa;
int i, bit, q, mute;
mutex_lock(&gt->lock);
mute = gt->muted ? GEMTEK_MT : 0x00;
outb_p(mute | GEMTEK_DA | GEMTEK_CK, gt->io);
udelay(SHORT_DELAY);
outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, gt->io);
outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, isa->io);
udelay(LONG_DELAY);
for (i = 0, q = gt->bu2614data; i < 32; i++, q >>= 1) {
bit = (q & 1) ? GEMTEK_DA : 0;
outb_p(mute | GEMTEK_CE | bit, gt->io);
outb_p(mute | GEMTEK_CE | bit, isa->io);
udelay(SHORT_DELAY);
outb_p(mute | GEMTEK_CE | bit | GEMTEK_CK, gt->io);
outb_p(mute | GEMTEK_CE | bit | GEMTEK_CK, isa->io);
udelay(SHORT_DELAY);
}
outb_p(mute | GEMTEK_DA | GEMTEK_CK, gt->io);
outb_p(mute | GEMTEK_DA | GEMTEK_CK, isa->io);
udelay(SHORT_DELAY);
outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, gt->io);
udelay(LONG_DELAY);
mutex_unlock(&gt->lock);
}
/*
@ -198,21 +176,27 @@ static void gemtek_bu2614_transmit(struct gemtek *gt)
*/
static unsigned long gemtek_convfreq(unsigned long freq)
{
return ((freq<<FSCALE) + IF_OFFSET + REF_FREQ/2) / REF_FREQ;
return ((freq << FSCALE) + IF_OFFSET + REF_FREQ / 2) / REF_FREQ;
}
static struct radio_isa_card *gemtek_alloc(void)
{
struct gemtek *gt = kzalloc(sizeof(*gt), GFP_KERNEL);
if (gt)
gt->muted = true;
return gt ? &gt->isa : NULL;
}
/*
* Set FM-frequency.
*/
static void gemtek_setfreq(struct gemtek *gt, unsigned long freq)
static int gemtek_s_frequency(struct radio_isa_card *isa, u32 freq)
{
if (keepmuted && hardmute && gt->muted)
return;
struct gemtek *gt = container_of(isa, struct gemtek, isa);
freq = clamp_val(freq, GEMTEK_LOWFREQ, GEMTEK_HIGHFREQ);
gt->lastfreq = freq;
gt->muted = 0;
if (hardmute && gt->muted)
return 0;
gemtek_bu2614_set(gt, BU2614_PORT, 0);
gemtek_bu2614_set(gt, BU2614_FMES, 0);
@ -220,23 +204,25 @@ static void gemtek_setfreq(struct gemtek *gt, unsigned long freq)
gemtek_bu2614_set(gt, BU2614_SWAL, 0);
gemtek_bu2614_set(gt, BU2614_FMUN, 1); /* GT bit set */
gemtek_bu2614_set(gt, BU2614_TEST, 0);
gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_STDF_3_125_KHZ);
gemtek_bu2614_set(gt, BU2614_FREQ, gemtek_convfreq(freq));
gemtek_bu2614_transmit(gt);
return 0;
}
/*
* Set mute flag.
*/
static void gemtek_mute(struct gemtek *gt)
static int gemtek_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
struct gemtek *gt = container_of(isa, struct gemtek, isa);
int i;
gt->muted = 1;
gt->muted = mute;
if (hardmute) {
if (!mute)
return gemtek_s_frequency(isa, isa->freq);
/* Turn off PLL, disable data output */
gemtek_bu2614_set(gt, BU2614_PORT, 0);
gemtek_bu2614_set(gt, BU2614_FMES, 0); /* CT bit off */
@ -247,367 +233,85 @@ static void gemtek_mute(struct gemtek *gt)
gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_PLL_OFF);
gemtek_bu2614_set(gt, BU2614_FREQ, 0);
gemtek_bu2614_transmit(gt);
return;
return 0;
}
mutex_lock(&gt->lock);
/* Read bus contents (CE, CK and DA). */
i = inb_p(gt->io);
i = inb_p(isa->io);
/* Write it back with mute flag set. */
outb_p((i >> 5) | GEMTEK_MT, gt->io);
outb_p((i >> 5) | (mute ? GEMTEK_MT : 0), isa->io);
udelay(SHORT_DELAY);
mutex_unlock(&gt->lock);
return 0;
}
/*
* Unset mute flag.
*/
static void gemtek_unmute(struct gemtek *gt)
static u32 gemtek_g_rxsubchans(struct radio_isa_card *isa)
{
int i;
gt->muted = 0;
if (hardmute) {
/* Turn PLL back on. */
gemtek_setfreq(gt, gt->lastfreq);
return;
}
mutex_lock(&gt->lock);
i = inb_p(gt->io);
outb_p(i >> 5, gt->io);
udelay(SHORT_DELAY);
mutex_unlock(&gt->lock);
}
/*
* Get signal strength (= stereo status).
*/
static inline int gemtek_getsigstr(struct gemtek *gt)
{
int sig;
mutex_lock(&gt->lock);
sig = inb_p(gt->io) & GEMTEK_NS ? 0 : 1;
mutex_unlock(&gt->lock);
return sig;
if (inb_p(isa->io) & GEMTEK_NS)
return V4L2_TUNER_SUB_MONO;
return V4L2_TUNER_SUB_STEREO;
}
/*
* Check if requested card acts like GemTek Radio card.
*/
static int gemtek_verify(struct gemtek *gt, int port)
static bool gemtek_probe(struct radio_isa_card *isa, int io)
{
int i, q;
if (gt->verified == port)
return 1;
mutex_lock(&gt->lock);
q = inb_p(port); /* Read bus contents before probing. */
q = inb_p(io); /* Read bus contents before probing. */
/* Try to turn on CE, CK and DA respectively and check if card responds
properly. */
for (i = 0; i < 3; ++i) {
outb_p(1 << i, port);
outb_p(1 << i, io);
udelay(SHORT_DELAY);
if ((inb_p(port) & (~GEMTEK_NS)) != (0x17 | (1 << (i + 5)))) {
mutex_unlock(&gt->lock);
return 0;
}
if ((inb_p(io) & ~GEMTEK_NS) != (0x17 | (1 << (i + 5))))
return false;
}
outb_p(q >> 5, port); /* Write bus contents back. */
outb_p(q >> 5, io); /* Write bus contents back. */
udelay(SHORT_DELAY);
mutex_unlock(&gt->lock);
gt->verified = port;
return 1;
return true;
}
/*
* Automatic probing for card.
*/
static int gemtek_probe(struct gemtek *gt)
{
struct v4l2_device *v4l2_dev = &gt->v4l2_dev;
int ioports[] = { 0x20c, 0x30c, 0x24c, 0x34c, 0x248, 0x28c };
int i;
if (!probe) {
v4l2_info(v4l2_dev, "Automatic device probing disabled.\n");
return -1;
}
v4l2_info(v4l2_dev, "Automatic device probing enabled.\n");
for (i = 0; i < ARRAY_SIZE(ioports); ++i) {
v4l2_info(v4l2_dev, "Trying I/O port 0x%x...\n", ioports[i]);
if (!request_region(ioports[i], 1, "gemtek-probe")) {
v4l2_warn(v4l2_dev, "I/O port 0x%x busy!\n",
ioports[i]);
continue;
}
if (gemtek_verify(gt, ioports[i])) {
v4l2_info(v4l2_dev, "Card found from I/O port "
"0x%x!\n", ioports[i]);
release_region(ioports[i], 1);
gt->io = ioports[i];
return gt->io;
}
release_region(ioports[i], 1);
}
v4l2_err(v4l2_dev, "Automatic probing failed!\n");
return -1;
}
/*
* Video 4 Linux stuff.
*/
static const struct v4l2_file_operations gemtek_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
static const struct radio_isa_ops gemtek_ops = {
.alloc = gemtek_alloc,
.probe = gemtek_probe,
.s_mute_volume = gemtek_s_mute_volume,
.s_frequency = gemtek_s_frequency,
.g_rxsubchans = gemtek_g_rxsubchans,
};
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
strlcpy(v->driver, "radio-gemtek", sizeof(v->driver));
strlcpy(v->card, "GemTek", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static const int gemtek_ioports[] = { 0x20c, 0x30c, 0x24c, 0x34c, 0x248, 0x28c };
static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v)
{
struct gemtek *gt = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = GEMTEK_LOWFREQ;
v->rangehigh = GEMTEK_HIGHFREQ;
v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
v->signal = 0xffff * gemtek_getsigstr(gt);
if (v->signal) {
v->audmode = V4L2_TUNER_MODE_STEREO;
v->rxsubchans = V4L2_TUNER_SUB_STEREO;
} else {
v->audmode = V4L2_TUNER_MODE_MONO;
v->rxsubchans = V4L2_TUNER_SUB_MONO;
}
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *v)
{
return (v->index != 0) ? -EINVAL : 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct gemtek *gt = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = gt->lastfreq;
return 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct gemtek *gt = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
gemtek_setfreq(gt, f->frequency);
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0);
default:
return -EINVAL;
}
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct gemtek *gt = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
ctrl->value = gt->muted;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct gemtek *gt = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
gemtek_mute(gt);
else
gemtek_unmute(gt);
return 0;
}
return -EINVAL;
}
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
{
return (i != 0) ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *a)
{
return (a->index != 0) ? -EINVAL : 0;
}
static const struct v4l2_ioctl_ops gemtek_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_g_tuner = vidioc_g_tuner,
.vidioc_s_tuner = vidioc_s_tuner,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl
static struct radio_isa_driver gemtek_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-gemtek",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = gemtek_ioports,
.num_of_io_ports = ARRAY_SIZE(gemtek_ioports),
.region_size = 1,
.card = "GemTek Radio",
.ops = &gemtek_ops,
.has_stereo = true,
};
/*
* Initialization / cleanup related stuff.
*/
static int __init gemtek_init(void)
{
struct gemtek *gt = &gemtek_card;
struct v4l2_device *v4l2_dev = &gt->v4l2_dev;
int res;
strlcpy(v4l2_dev->name, "gemtek", sizeof(v4l2_dev->name));
v4l2_info(v4l2_dev, "GemTek Radio card driver: v0.0.3\n");
mutex_init(&gt->lock);
gt->verified = -1;
gt->io = io;
gemtek_probe(gt);
if (gt->io) {
if (!request_region(gt->io, 1, "gemtek")) {
v4l2_err(v4l2_dev, "I/O port 0x%x already in use.\n", gt->io);
return -EBUSY;
}
if (!gemtek_verify(gt, gt->io))
v4l2_warn(v4l2_dev, "Card at I/O port 0x%x does not "
"respond properly, check your "
"configuration.\n", gt->io);
else
v4l2_info(v4l2_dev, "Using I/O port 0x%x.\n", gt->io);
} else if (probe) {
v4l2_err(v4l2_dev, "Automatic probing failed and no "
"fixed I/O port defined.\n");
return -ENODEV;
} else {
v4l2_err(v4l2_dev, "Automatic probing disabled but no fixed "
"I/O port defined.");
return -EINVAL;
}
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
release_region(gt->io, 1);
return res;
}
strlcpy(gt->vdev.name, v4l2_dev->name, sizeof(gt->vdev.name));
gt->vdev.v4l2_dev = v4l2_dev;
gt->vdev.fops = &gemtek_fops;
gt->vdev.ioctl_ops = &gemtek_ioctl_ops;
gt->vdev.release = video_device_release_empty;
video_set_drvdata(&gt->vdev, gt);
/* Set defaults */
gt->lastfreq = GEMTEK_LOWFREQ;
gt->bu2614data = 0;
if (initmute)
gemtek_mute(gt);
if (video_register_device(&gt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(v4l2_dev);
release_region(gt->io, 1);
return -EBUSY;
}
return 0;
gemtek_driver.probe = probe;
return isa_register_driver(&gemtek_driver.driver, GEMTEK_MAX);
}
/*
* Module cleanup
*/
static void __exit gemtek_exit(void)
{
struct gemtek *gt = &gemtek_card;
struct v4l2_device *v4l2_dev = &gt->v4l2_dev;
if (shutdown) {
hardmute = 1; /* Turn off PLL */
gemtek_mute(gt);
} else {
v4l2_info(v4l2_dev, "Module unloaded but card not muted!\n");
}
video_unregister_device(&gt->vdev);
v4l2_device_unregister(&gt->v4l2_dev);
release_region(gt->io, 1);
hardmute = 1; /* Turn off PLL */
isa_unregister_driver(&gemtek_driver.driver);
}
module_init(gemtek_init);

View File

@ -0,0 +1,340 @@
/*
* Framework for ISA radio drivers.
* This takes care of all the V4L2 scaffolding, allowing the ISA drivers
* to concentrate on the actual hardware operation.
*
* Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.com>
*
* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include "radio-isa.h"
MODULE_AUTHOR("Hans Verkuil");
MODULE_DESCRIPTION("A framework for ISA radio drivers.");
MODULE_LICENSE("GPL");
#define FREQ_LOW (87U * 16000U)
#define FREQ_HIGH (108U * 16000U)
static int radio_isa_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct radio_isa_card *isa = video_drvdata(file);
strlcpy(v->driver, isa->drv->driver.driver.name, sizeof(v->driver));
strlcpy(v->card, isa->drv->card, sizeof(v->card));
snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", isa->v4l2_dev.name);
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
v->device_caps = v->capabilities | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int radio_isa_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct radio_isa_card *isa = video_drvdata(file);
const struct radio_isa_ops *ops = isa->drv->ops;
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = FREQ_LOW;
v->rangehigh = FREQ_HIGH;
v->capability = V4L2_TUNER_CAP_LOW;
if (isa->drv->has_stereo)
v->capability |= V4L2_TUNER_CAP_STEREO;
if (ops->g_rxsubchans)
v->rxsubchans = ops->g_rxsubchans(isa);
else
v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
v->audmode = isa->stereo ? V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO;
if (ops->g_signal)
v->signal = ops->g_signal(isa);
else
v->signal = (v->rxsubchans & V4L2_TUNER_SUB_STEREO) ?
0xffff : 0;
return 0;
}
static int radio_isa_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct radio_isa_card *isa = video_drvdata(file);
const struct radio_isa_ops *ops = isa->drv->ops;
if (v->index)
return -EINVAL;
if (ops->s_stereo) {
isa->stereo = (v->audmode == V4L2_TUNER_MODE_STEREO);
return ops->s_stereo(isa, isa->stereo);
}
return 0;
}
static int radio_isa_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct radio_isa_card *isa = video_drvdata(file);
int res;
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
f->frequency = clamp(f->frequency, FREQ_LOW, FREQ_HIGH);
res = isa->drv->ops->s_frequency(isa, f->frequency);
if (res == 0)
isa->freq = f->frequency;
return res;
}
static int radio_isa_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct radio_isa_card *isa = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = isa->freq;
return 0;
}
static int radio_isa_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct radio_isa_card *isa =
container_of(ctrl->handler, struct radio_isa_card, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
return isa->drv->ops->s_mute_volume(isa, ctrl->val,
isa->volume ? isa->volume->val : 0);
}
return -EINVAL;
}
static int radio_isa_log_status(struct file *file, void *priv)
{
struct radio_isa_card *isa = video_drvdata(file);
v4l2_info(&isa->v4l2_dev, "I/O Port = 0x%03x\n", isa->io);
v4l2_ctrl_handler_log_status(&isa->hdl, isa->v4l2_dev.name);
return 0;
}
static int radio_isa_subscribe_event(struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
if (sub->type == V4L2_EVENT_CTRL)
return v4l2_event_subscribe(fh, sub, 0);
return -EINVAL;
}
static const struct v4l2_ctrl_ops radio_isa_ctrl_ops = {
.s_ctrl = radio_isa_s_ctrl,
};
static const struct v4l2_file_operations radio_isa_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
static const struct v4l2_ioctl_ops radio_isa_ioctl_ops = {
.vidioc_querycap = radio_isa_querycap,
.vidioc_g_tuner = radio_isa_g_tuner,
.vidioc_s_tuner = radio_isa_s_tuner,
.vidioc_g_frequency = radio_isa_g_frequency,
.vidioc_s_frequency = radio_isa_s_frequency,
.vidioc_log_status = radio_isa_log_status,
.vidioc_subscribe_event = radio_isa_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
int radio_isa_match(struct device *pdev, unsigned int dev)
{
struct radio_isa_driver *drv = pdev->platform_data;
return drv->probe || drv->io_params[dev] >= 0;
}
EXPORT_SYMBOL_GPL(radio_isa_match);
static bool radio_isa_valid_io(const struct radio_isa_driver *drv, int io)
{
int i;
for (i = 0; i < drv->num_of_io_ports; i++)
if (drv->io_ports[i] == io)
return true;
return false;
}
int radio_isa_probe(struct device *pdev, unsigned int dev)
{
struct radio_isa_driver *drv = pdev->platform_data;
const struct radio_isa_ops *ops = drv->ops;
struct v4l2_device *v4l2_dev;
struct radio_isa_card *isa;
int res;
isa = drv->ops->alloc();
if (isa == NULL)
return -ENOMEM;
dev_set_drvdata(pdev, isa);
isa->drv = drv;
isa->io = drv->io_params[dev];
v4l2_dev = &isa->v4l2_dev;
strlcpy(v4l2_dev->name, dev_name(pdev), sizeof(v4l2_dev->name));
if (drv->probe && ops->probe) {
int i;
for (i = 0; i < drv->num_of_io_ports; ++i) {
int io = drv->io_ports[i];
if (request_region(io, drv->region_size, v4l2_dev->name)) {
bool found = ops->probe(isa, io);
release_region(io, drv->region_size);
if (found) {
isa->io = io;
break;
}
}
}
}
if (!radio_isa_valid_io(drv, isa->io)) {
int i;
if (isa->io < 0)
return -ENODEV;
v4l2_err(v4l2_dev, "you must set an I/O address with io=0x%03x",
drv->io_ports[0]);
for (i = 1; i < drv->num_of_io_ports; i++)
printk(KERN_CONT "/0x%03x", drv->io_ports[i]);
printk(KERN_CONT ".\n");
kfree(isa);
return -EINVAL;
}
if (!request_region(isa->io, drv->region_size, v4l2_dev->name)) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n", isa->io);
kfree(isa);
return -EBUSY;
}
res = v4l2_device_register(pdev, v4l2_dev);
if (res < 0) {
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
goto err_dev_reg;
}
v4l2_ctrl_handler_init(&isa->hdl, 1);
isa->mute = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
if (drv->max_volume)
isa->volume = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, drv->max_volume, 1,
drv->max_volume);
v4l2_dev->ctrl_handler = &isa->hdl;
if (isa->hdl.error) {
res = isa->hdl.error;
v4l2_err(v4l2_dev, "Could not register controls\n");
goto err_hdl;
}
if (drv->max_volume)
v4l2_ctrl_cluster(2, &isa->mute);
v4l2_dev->ctrl_handler = &isa->hdl;
mutex_init(&isa->lock);
isa->vdev.lock = &isa->lock;
strlcpy(isa->vdev.name, v4l2_dev->name, sizeof(isa->vdev.name));
isa->vdev.v4l2_dev = v4l2_dev;
isa->vdev.fops = &radio_isa_fops;
isa->vdev.ioctl_ops = &radio_isa_ioctl_ops;
isa->vdev.release = video_device_release_empty;
set_bit(V4L2_FL_USE_FH_PRIO, &isa->vdev.flags);
video_set_drvdata(&isa->vdev, isa);
isa->freq = FREQ_LOW;
isa->stereo = drv->has_stereo;
if (ops->init)
res = ops->init(isa);
if (!res)
res = v4l2_ctrl_handler_setup(&isa->hdl);
if (!res)
res = ops->s_frequency(isa, isa->freq);
if (!res && ops->s_stereo)
res = ops->s_stereo(isa, isa->stereo);
if (res < 0) {
v4l2_err(v4l2_dev, "Could not setup card\n");
goto err_node_reg;
}
res = video_register_device(&isa->vdev, VFL_TYPE_RADIO,
drv->radio_nr_params[dev]);
if (res < 0) {
v4l2_err(v4l2_dev, "Could not register device node\n");
goto err_node_reg;
}
v4l2_info(v4l2_dev, "Initialized radio card %s on port 0x%03x\n",
drv->card, isa->io);
return 0;
err_node_reg:
v4l2_ctrl_handler_free(&isa->hdl);
err_hdl:
v4l2_device_unregister(&isa->v4l2_dev);
err_dev_reg:
release_region(isa->io, drv->region_size);
kfree(isa);
return res;
}
EXPORT_SYMBOL_GPL(radio_isa_probe);
int radio_isa_remove(struct device *pdev, unsigned int dev)
{
struct radio_isa_card *isa = dev_get_drvdata(pdev);
const struct radio_isa_ops *ops = isa->drv->ops;
ops->s_mute_volume(isa, true, isa->volume ? isa->volume->cur.val : 0);
video_unregister_device(&isa->vdev);
v4l2_ctrl_handler_free(&isa->hdl);
v4l2_device_unregister(&isa->v4l2_dev);
release_region(isa->io, isa->drv->region_size);
v4l2_info(&isa->v4l2_dev, "Removed radio card %s\n", isa->drv->card);
kfree(isa);
return 0;
}
EXPORT_SYMBOL_GPL(radio_isa_remove);

View File

@ -0,0 +1,105 @@
/*
* Framework for ISA radio drivers.
* This takes care of all the V4L2 scaffolding, allowing the ISA drivers
* to concentrate on the actual hardware operation.
*
* Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.com>
*
* 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 _RADIO_ISA_H_
#define _RADIO_ISA_H_
#include <linux/isa.h>
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ctrls.h>
struct radio_isa_driver;
struct radio_isa_ops;
/* Core structure for radio ISA cards */
struct radio_isa_card {
const struct radio_isa_driver *drv;
struct v4l2_device v4l2_dev;
struct v4l2_ctrl_handler hdl;
struct video_device vdev;
struct mutex lock;
const struct radio_isa_ops *ops;
struct { /* mute/volume cluster */
struct v4l2_ctrl *mute;
struct v4l2_ctrl *volume;
};
/* I/O port */
int io;
/* Card is in stereo audio mode */
bool stereo;
/* Current frequency */
u32 freq;
};
struct radio_isa_ops {
/* Allocate and initialize a radio_isa_card struct */
struct radio_isa_card *(*alloc)(void);
/* Probe whether a card is present at the given port */
bool (*probe)(struct radio_isa_card *isa, int io);
/* Special card initialization can be done here, this is called after
* the standard controls are registered, but before they are setup,
* thus allowing drivers to add their own controls here. */
int (*init)(struct radio_isa_card *isa);
/* Set mute and volume. */
int (*s_mute_volume)(struct radio_isa_card *isa, bool mute, int volume);
/* Set frequency */
int (*s_frequency)(struct radio_isa_card *isa, u32 freq);
/* Set stereo/mono audio mode */
int (*s_stereo)(struct radio_isa_card *isa, bool stereo);
/* Get rxsubchans value for VIDIOC_G_TUNER */
u32 (*g_rxsubchans)(struct radio_isa_card *isa);
/* Get the signal strength for VIDIOC_G_TUNER */
u32 (*g_signal)(struct radio_isa_card *isa);
};
/* Top level structure needed to instantiate the cards */
struct radio_isa_driver {
struct isa_driver driver;
const struct radio_isa_ops *ops;
/* The module_param_array with the specified I/O ports */
int *io_params;
/* The module_param_array with the radio_nr values */
int *radio_nr_params;
/* Whether we should probe for possible cards */
bool probe;
/* The list of possible I/O ports */
const int *io_ports;
/* The size of that list */
int num_of_io_ports;
/* The region size to request */
unsigned region_size;
/* The name of the card */
const char *card;
/* Card can capture stereo audio */
bool has_stereo;
/* The maximum volume for the volume control. If 0, then there
is no volume control possible. */
int max_volume;
};
int radio_isa_match(struct device *pdev, unsigned int dev);
int radio_isa_probe(struct device *pdev, unsigned int dev);
int radio_isa_remove(struct device *pdev, unsigned int dev);
#endif

View File

@ -0,0 +1,427 @@
/*
* Copyright (c) 2012 Hans Verkuil <hverkuil@xs4all.nl>
*
* 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
*/
/* kernel includes */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <linux/usb.h>
#include <linux/version.h>
#include <linux/mutex.h>
/* driver and module definitions */
MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
MODULE_DESCRIPTION("Keene FM Transmitter driver");
MODULE_LICENSE("GPL");
/* Actually, it advertises itself as a Logitech */
#define USB_KEENE_VENDOR 0x046d
#define USB_KEENE_PRODUCT 0x0a0e
/* Probably USB_TIMEOUT should be modified in module parameter */
#define BUFFER_LENGTH 8
#define USB_TIMEOUT 500
/* Frequency limits in MHz */
#define FREQ_MIN 76U
#define FREQ_MAX 108U
#define FREQ_MUL 16000U
/* USB Device ID List */
static struct usb_device_id usb_keene_device_table[] = {
{USB_DEVICE_AND_INTERFACE_INFO(USB_KEENE_VENDOR, USB_KEENE_PRODUCT,
USB_CLASS_HID, 0, 0) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, usb_keene_device_table);
struct keene_device {
struct usb_device *usbdev;
struct usb_interface *intf;
struct video_device vdev;
struct v4l2_device v4l2_dev;
struct v4l2_ctrl_handler hdl;
struct mutex lock;
u8 *buffer;
unsigned curfreq;
u8 tx;
u8 pa;
bool stereo;
bool muted;
bool preemph_75_us;
};
static inline struct keene_device *to_keene_dev(struct v4l2_device *v4l2_dev)
{
return container_of(v4l2_dev, struct keene_device, v4l2_dev);
}
/* Set frequency (if non-0), PA, mute and turn on/off the FM transmitter. */
static int keene_cmd_main(struct keene_device *radio, unsigned freq, bool play)
{
unsigned short freq_send = freq ? (freq - 76 * 16000) / 800 : 0;
int ret;
radio->buffer[0] = 0x00;
radio->buffer[1] = 0x50;
radio->buffer[2] = (freq_send >> 8) & 0xff;
radio->buffer[3] = freq_send & 0xff;
radio->buffer[4] = radio->pa;
/* If bit 4 is set, then tune to the frequency.
If bit 3 is set, then unmute; if bit 2 is set, then mute.
If bit 1 is set, then enter idle mode; if bit 0 is set,
then enter transit mode.
*/
radio->buffer[5] = (radio->muted ? 4 : 8) | (play ? 1 : 2) |
(freq ? 0x10 : 0);
radio->buffer[6] = 0x00;
radio->buffer[7] = 0x00;
ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT);
if (ret < 0) {
dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret);
return ret;
}
if (freq)
radio->curfreq = freq;
return 0;
}
/* Set TX, stereo and preemphasis mode (50 us vs 75 us). */
static int keene_cmd_set(struct keene_device *radio)
{
int ret;
radio->buffer[0] = 0x00;
radio->buffer[1] = 0x51;
radio->buffer[2] = radio->tx;
/* If bit 0 is set, then transmit mono, otherwise stereo.
If bit 2 is set, then enable 75 us preemphasis, otherwise
it is 50 us. */
radio->buffer[3] = (!radio->stereo) | (radio->preemph_75_us ? 4 : 0);
radio->buffer[4] = 0x00;
radio->buffer[5] = 0x00;
radio->buffer[6] = 0x00;
radio->buffer[7] = 0x00;
ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT);
if (ret < 0) {
dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret);
return ret;
}
return 0;
}
/* Handle unplugging the device.
* We call video_unregister_device in any case.
* The last function called in this procedure is
* usb_keene_device_release.
*/
static void usb_keene_disconnect(struct usb_interface *intf)
{
struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf));
v4l2_device_get(&radio->v4l2_dev);
mutex_lock(&radio->lock);
usb_set_intfdata(intf, NULL);
video_unregister_device(&radio->vdev);
v4l2_device_disconnect(&radio->v4l2_dev);
mutex_unlock(&radio->lock);
v4l2_device_put(&radio->v4l2_dev);
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct keene_device *radio = video_drvdata(file);
strlcpy(v->driver, "radio-keene", sizeof(v->driver));
strlcpy(v->card, "Keene FM Transmitter", sizeof(v->card));
usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info));
v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_MODULATOR;
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int vidioc_g_modulator(struct file *file, void *priv,
struct v4l2_modulator *v)
{
struct keene_device *radio = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->rangelow = FREQ_MIN * FREQ_MUL;
v->rangehigh = FREQ_MAX * FREQ_MUL;
v->txsubchans = radio->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
return 0;
}
static int vidioc_s_modulator(struct file *file, void *priv,
struct v4l2_modulator *v)
{
struct keene_device *radio = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
radio->stereo = (v->txsubchans == V4L2_TUNER_SUB_STEREO);
return keene_cmd_set(radio);
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct keene_device *radio = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
f->frequency = clamp(f->frequency,
FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL);
return keene_cmd_main(radio, f->frequency, true);
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct keene_device *radio = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = radio->curfreq;
return 0;
}
static int keene_s_ctrl(struct v4l2_ctrl *ctrl)
{
static const u8 db2tx[] = {
/* -15, -12, -9, -6, -3, 0 dB */
0x03, 0x13, 0x02, 0x12, 0x22, 0x32,
/* 3, 6, 9, 12, 15, 18 dB */
0x21, 0x31, 0x20, 0x30, 0x40, 0x50
};
struct keene_device *radio =
container_of(ctrl->handler, struct keene_device, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
radio->muted = ctrl->val;
return keene_cmd_main(radio, 0, true);
case V4L2_CID_TUNE_POWER_LEVEL:
/* To go from dBuV to the register value we apply the
following formula: */
radio->pa = (ctrl->val - 71) * 100 / 62;
return keene_cmd_main(radio, 0, true);
case V4L2_CID_TUNE_PREEMPHASIS:
radio->preemph_75_us = ctrl->val == V4L2_PREEMPHASIS_75_uS;
return keene_cmd_set(radio);
case V4L2_CID_AUDIO_COMPRESSION_GAIN:
radio->tx = db2tx[(ctrl->val - ctrl->minimum) / ctrl->step];
return keene_cmd_set(radio);
}
return -EINVAL;
}
static int vidioc_subscribe_event(struct v4l2_fh *fh,
struct v4l2_event_subscription *sub)
{
switch (sub->type) {
case V4L2_EVENT_CTRL:
return v4l2_event_subscribe(fh, sub, 0);
default:
return -EINVAL;
}
}
/* File system interface */
static const struct v4l2_file_operations usb_keene_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
static const struct v4l2_ctrl_ops keene_ctrl_ops = {
.s_ctrl = keene_s_ctrl,
};
static const struct v4l2_ioctl_ops usb_keene_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_g_modulator = vidioc_g_modulator,
.vidioc_s_modulator = vidioc_s_modulator,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = vidioc_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static void usb_keene_video_device_release(struct v4l2_device *v4l2_dev)
{
struct keene_device *radio = to_keene_dev(v4l2_dev);
/* free rest memory */
v4l2_ctrl_handler_free(&radio->hdl);
kfree(radio->buffer);
kfree(radio);
}
/* check if the device is present and register with v4l and usb if it is */
static int usb_keene_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct keene_device *radio;
struct v4l2_ctrl_handler *hdl;
int retval = 0;
/*
* The Keene FM transmitter USB device has the same USB ID as
* the Logitech AudioHub Speaker, but it should ignore the hid.
* Check if the name is that of the Keene device.
* If not, then someone connected the AudioHub and we shouldn't
* attempt to handle this driver.
* For reference: the product name of the AudioHub is
* "AudioHub Speaker".
*/
if (dev->product && strcmp(dev->product, "B-LINK USB Audio "))
return -ENODEV;
radio = kzalloc(sizeof(struct keene_device), GFP_KERNEL);
if (radio)
radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL);
if (!radio || !radio->buffer) {
dev_err(&intf->dev, "kmalloc for keene_device failed\n");
kfree(radio);
retval = -ENOMEM;
goto err;
}
hdl = &radio->hdl;
v4l2_ctrl_handler_init(hdl, 4);
v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_MUTE,
0, 1, 1, 0);
v4l2_ctrl_new_std_menu(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_PREEMPHASIS,
V4L2_PREEMPHASIS_75_uS, 1, V4L2_PREEMPHASIS_50_uS);
v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_POWER_LEVEL,
84, 118, 1, 118);
v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_COMPRESSION_GAIN,
-15, 18, 3, 0);
radio->pa = 118;
radio->tx = 0x32;
radio->stereo = true;
radio->curfreq = 95.16 * FREQ_MUL;
if (hdl->error) {
retval = hdl->error;
v4l2_ctrl_handler_free(hdl);
goto err_v4l2;
}
retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
if (retval < 0) {
dev_err(&intf->dev, "couldn't register v4l2_device\n");
goto err_v4l2;
}
mutex_init(&radio->lock);
radio->v4l2_dev.ctrl_handler = hdl;
radio->v4l2_dev.release = usb_keene_video_device_release;
strlcpy(radio->vdev.name, radio->v4l2_dev.name,
sizeof(radio->vdev.name));
radio->vdev.v4l2_dev = &radio->v4l2_dev;
radio->vdev.fops = &usb_keene_fops;
radio->vdev.ioctl_ops = &usb_keene_ioctl_ops;
radio->vdev.lock = &radio->lock;
radio->vdev.release = video_device_release_empty;
radio->usbdev = interface_to_usbdev(intf);
radio->intf = intf;
usb_set_intfdata(intf, &radio->v4l2_dev);
video_set_drvdata(&radio->vdev, radio);
set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags);
retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1);
if (retval < 0) {
dev_err(&intf->dev, "could not register video device\n");
goto err_vdev;
}
v4l2_ctrl_handler_setup(hdl);
dev_info(&intf->dev, "V4L2 device registered as %s\n",
video_device_node_name(&radio->vdev));
return 0;
err_vdev:
v4l2_device_unregister(&radio->v4l2_dev);
err_v4l2:
kfree(radio->buffer);
kfree(radio);
err:
return retval;
}
/* USB subsystem interface */
static struct usb_driver usb_keene_driver = {
.name = "radio-keene",
.probe = usb_keene_probe,
.disconnect = usb_keene_disconnect,
.id_table = usb_keene_device_table,
};
static int __init keene_init(void)
{
int retval = usb_register(&usb_keene_driver);
if (retval)
pr_err(KBUILD_MODNAME
": usb_register failed. Error number %d\n", retval);
return retval;
}
static void __exit keene_exit(void)
{
usb_deregister(&usb_keene_driver);
}
module_init(keene_init);
module_exit(keene_exit);

View File

@ -42,67 +42,37 @@
#include <linux/videodev2.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <sound/tea575x-tuner.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#define DRIVER_VERSION "0.7.8"
#include <media/v4l2-fh.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net");
MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio.");
MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000.");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
MODULE_VERSION("1.0.0");
static int radio_nr = -1;
module_param(radio_nr, int, 0);
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "activates debug info");
#define dprintk(dev, num, fmt, arg...) \
v4l2_dbg(num, debug, &dev->v4l2_dev, fmt, ## arg)
#ifndef PCI_VENDOR_ID_GUILLEMOT
#define PCI_VENDOR_ID_GUILLEMOT 0x5046
#endif
#ifndef PCI_DEVICE_ID_GUILLEMOT
#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001
#endif
module_param(radio_nr, int, 0644);
MODULE_PARM_DESC(radio_nr, "Radio device number");
/* TEA5757 pin mappings */
static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16;
#define FREQ_LO (87 * 16000)
#define FREQ_HI (108 * 16000)
#define FREQ_IF 171200 /* 10.7*16000 */
#define FREQ_STEP 200 /* 12.5*16 */
/* (x==fmhz*16*1000) -> bits */
#define FREQ2BITS(x) \
((((unsigned int)(x) + FREQ_IF + (FREQ_STEP << 1)) / (FREQ_STEP << 2)) << 2)
#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF)
static atomic_t maxiradio_instance = ATOMIC_INIT(0);
#define PCI_VENDOR_ID_GUILLEMOT 0x5046
#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001
struct maxiradio
{
struct snd_tea575x tea;
struct v4l2_device v4l2_dev;
struct video_device vdev;
struct pci_dev *pdev;
u16 io; /* base of radio io */
u16 muted; /* VIDEO_AUDIO_MUTE */
u16 stereo; /* VIDEO_TUNER_STEREO_ON */
u16 tuned; /* signal strength (0 or 0xffff) */
unsigned long freq;
struct mutex lock;
};
static inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev)
@ -110,259 +80,41 @@ static inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev)
return container_of(v4l2_dev, struct maxiradio, v4l2_dev);
}
static void outbit(unsigned long bit, u16 io)
static void maxiradio_tea575x_set_pins(struct snd_tea575x *tea, u8 pins)
{
int val = power | wren | (bit ? data : 0);
struct maxiradio *dev = tea->private_data;
u8 bits = 0;
outb(val, io);
udelay(4);
outb(val | clk, io);
udelay(4);
outb(val, io);
udelay(4);
bits |= (pins & TEA575X_DATA) ? data : 0;
bits |= (pins & TEA575X_CLK) ? clk : 0;
bits |= (pins & TEA575X_WREN) ? wren : 0;
bits |= power;
outb(bits, dev->io);
}
static void turn_power(struct maxiradio *dev, int p)
/* Note: this card cannot read out the data of the shift registers,
only the mono/stereo pin works. */
static u8 maxiradio_tea575x_get_pins(struct snd_tea575x *tea)
{
if (p != 0) {
dprintk(dev, 1, "Radio powered on\n");
outb(power, dev->io);
} else {
dprintk(dev, 1, "Radio powered off\n");
outb(0, dev->io);
}
struct maxiradio *dev = tea->private_data;
u8 bits = inb(dev->io);
return ((bits & data) ? TEA575X_DATA : 0) |
((bits & mo_st) ? TEA575X_MOST : 0);
}
static void set_freq(struct maxiradio *dev, u32 freq)
static void maxiradio_tea575x_set_direction(struct snd_tea575x *tea, bool output)
{
unsigned long int si;
int bl;
int io = dev->io;
int val = FREQ2BITS(freq);
/* TEA5757 shift register bits (see pdf) */
outbit(0, io); /* 24 search */
outbit(1, io); /* 23 search up/down */
outbit(0, io); /* 22 stereo/mono */
outbit(0, io); /* 21 band */
outbit(0, io); /* 20 band (only 00=FM works I think) */
outbit(0, io); /* 19 port ? */
outbit(0, io); /* 18 port ? */
outbit(0, io); /* 17 search level */
outbit(0, io); /* 16 search level */
si = 0x8000;
for (bl = 1; bl <= 16; bl++) {
outbit(val & si, io);
si >>= 1;
}
dprintk(dev, 1, "Radio freq set to %d.%02d MHz\n",
freq / 16000,
freq % 16000 * 100 / 16000);
turn_power(dev, 1);
}
static int get_stereo(u16 io)
{
outb(power,io);
udelay(4);
return !(inb(io) & mo_st);
}
static int get_tune(u16 io)
{
outb(power+clk,io);
udelay(4);
return !(inb(io) & mo_st);
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct maxiradio *dev = video_drvdata(file);
strlcpy(v->driver, "radio-maxiradio", sizeof(v->driver));
strlcpy(v->card, "Maxi Radio FM2000 radio", sizeof(v->card));
snprintf(v->bus_info, sizeof(v->bus_info), "PCI:%s", pci_name(dev->pdev));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct maxiradio *dev = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
mutex_lock(&dev->lock);
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = FREQ_LO;
v->rangehigh = FREQ_HI;
v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
v->capability = V4L2_TUNER_CAP_LOW;
if (get_stereo(dev->io))
v->audmode = V4L2_TUNER_MODE_STEREO;
else
v->audmode = V4L2_TUNER_MODE_MONO;
v->signal = 0xffff * get_tune(dev->io);
mutex_unlock(&dev->lock);
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
{
return i ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct maxiradio *dev = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
if (f->frequency < FREQ_LO || f->frequency > FREQ_HI) {
dprintk(dev, 1, "radio freq (%d.%02d MHz) out of range (%d-%d)\n",
f->frequency / 16000,
f->frequency % 16000 * 100 / 16000,
FREQ_LO / 16000, FREQ_HI / 16000);
return -EINVAL;
}
mutex_lock(&dev->lock);
dev->freq = f->frequency;
set_freq(dev, dev->freq);
msleep(125);
mutex_unlock(&dev->lock);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct maxiradio *dev = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = dev->freq;
dprintk(dev, 4, "radio freq is %d.%02d MHz",
f->frequency / 16000,
f->frequency % 16000 * 100 / 16000);
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
}
return -EINVAL;
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct maxiradio *dev = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
ctrl->value = dev->muted;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct maxiradio *dev = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
mutex_lock(&dev->lock);
dev->muted = ctrl->value;
if (dev->muted)
turn_power(dev, 0);
else
set_freq(dev, dev->freq);
mutex_unlock(&dev->lock);
return 0;
}
return -EINVAL;
}
static const struct v4l2_file_operations maxiradio_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
static struct snd_tea575x_ops maxiradio_tea_ops = {
.set_pins = maxiradio_tea575x_set_pins,
.get_pins = maxiradio_tea575x_get_pins,
.set_direction = maxiradio_tea575x_set_direction,
};
static const struct v4l2_ioctl_ops maxiradio_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_g_tuner = vidioc_g_tuner,
.vidioc_s_tuner = vidioc_s_tuner,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
};
static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
static int __devinit maxiradio_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct maxiradio *dev;
struct v4l2_device *v4l2_dev;
@ -375,63 +127,60 @@ static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_d
}
v4l2_dev = &dev->v4l2_dev;
mutex_init(&dev->lock);
dev->pdev = pdev;
dev->muted = 1;
dev->freq = FREQ_LO;
strlcpy(v4l2_dev->name, "maxiradio", sizeof(v4l2_dev->name));
v4l2_device_set_name(v4l2_dev, "maxiradio", &maxiradio_instance);
retval = v4l2_device_register(&pdev->dev, v4l2_dev);
if (retval < 0) {
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
goto errfr;
}
dev->tea.private_data = dev;
dev->tea.ops = &maxiradio_tea_ops;
/* The data pin cannot be read. This may be a hardware limitation, or
we just don't know how to read it. */
dev->tea.cannot_read_data = true;
dev->tea.v4l2_dev = v4l2_dev;
dev->tea.radio_nr = radio_nr;
strlcpy(dev->tea.card, "Maxi Radio FM2000", sizeof(dev->tea.card));
snprintf(dev->tea.bus_info, sizeof(dev->tea.bus_info),
"PCI:%s", pci_name(pdev));
retval = -ENODEV;
if (!request_region(pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) {
v4l2_err(v4l2_dev, "can't reserve I/O ports\n");
goto err_out;
pci_resource_len(pdev, 0), v4l2_dev->name)) {
dev_err(&pdev->dev, "can't reserve I/O ports\n");
goto err_hdl;
}
if (pci_enable_device(pdev))
goto err_out_free_region;
dev->io = pci_resource_start(pdev, 0);
strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
dev->vdev.v4l2_dev = v4l2_dev;
dev->vdev.fops = &maxiradio_fops;
dev->vdev.ioctl_ops = &maxiradio_ioctl_ops;
dev->vdev.release = video_device_release_empty;
video_set_drvdata(&dev->vdev, dev);
if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_err(v4l2_dev, "can't register device!");
if (snd_tea575x_init(&dev->tea)) {
printk(KERN_ERR "radio-maxiradio: Unable to detect TEA575x tuner\n");
goto err_out_free_region;
}
v4l2_info(v4l2_dev, "version " DRIVER_VERSION "\n");
v4l2_info(v4l2_dev, "found Guillemot MAXI Radio device (io = 0x%x)\n",
dev->io);
return 0;
err_out_free_region:
release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
err_out:
err_hdl:
v4l2_device_unregister(v4l2_dev);
errfr:
kfree(dev);
return -ENODEV;
return retval;
}
static void __devexit maxiradio_remove_one(struct pci_dev *pdev)
static void __devexit maxiradio_remove(struct pci_dev *pdev)
{
struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev);
struct maxiradio *dev = to_maxiradio(v4l2_dev);
video_unregister_device(&dev->vdev);
v4l2_device_unregister(&dev->v4l2_dev);
snd_tea575x_exit(&dev->tea);
/* Turn off power */
outb(0, dev->io);
v4l2_device_unregister(v4l2_dev);
release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
}
@ -446,19 +195,19 @@ MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl);
static struct pci_driver maxiradio_driver = {
.name = "radio-maxiradio",
.id_table = maxiradio_pci_tbl,
.probe = maxiradio_init_one,
.remove = __devexit_p(maxiradio_remove_one),
.probe = maxiradio_probe,
.remove = __devexit_p(maxiradio_remove),
};
static int __init maxiradio_radio_init(void)
static int __init maxiradio_init(void)
{
return pci_register_driver(&maxiradio_driver);
}
static void __exit maxiradio_radio_exit(void)
static void __exit maxiradio_exit(void)
{
pci_unregister_driver(&maxiradio_driver);
}
module_init(maxiradio_radio_init);
module_exit(maxiradio_radio_exit);
module_init(maxiradio_init);
module_exit(maxiradio_exit);

View File

@ -1,11 +1,12 @@
/* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff
/*
* RadioTrack II driver
* Copyright 1998 Ben Pfaff
*
* Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood
* Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk>
* Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
*
* TODO: Allow for more than one of these foolish entities :-)
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
*/
@ -18,323 +19,120 @@
#include <linux/io.h> /* outb, outb_p */
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
MODULE_AUTHOR("Ben Pfaff");
MODULE_DESCRIPTION("A driver for the RadioTrack II radio card.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.3");
MODULE_VERSION("0.1.99");
#ifndef CONFIG_RADIO_RTRACK2_PORT
#define CONFIG_RADIO_RTRACK2_PORT -1
#endif
static int io = CONFIG_RADIO_RTRACK2_PORT;
static int radio_nr = -1;
#define RTRACK2_MAX 2
module_param(io, int, 0);
MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)");
module_param(radio_nr, int, 0);
static int io[RTRACK2_MAX] = { [0] = CONFIG_RADIO_RTRACK2_PORT,
[1 ... (RTRACK2_MAX - 1)] = -1 };
static int radio_nr[RTRACK2_MAX] = { [0 ... (RTRACK2_MAX - 1)] = -1 };
struct rtrack2
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
static struct radio_isa_card *rtrack2_alloc(void)
{
struct v4l2_device v4l2_dev;
struct video_device vdev;
int io;
unsigned long curfreq;
int muted;
struct mutex lock;
};
static struct rtrack2 rtrack2_card;
/* local things */
static void rt_mute(struct rtrack2 *dev)
{
if (dev->muted)
return;
mutex_lock(&dev->lock);
outb(1, dev->io);
mutex_unlock(&dev->lock);
dev->muted = 1;
return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL);
}
static void rt_unmute(struct rtrack2 *dev)
static void zero(struct radio_isa_card *isa)
{
if(dev->muted == 0)
return;
mutex_lock(&dev->lock);
outb(0, dev->io);
mutex_unlock(&dev->lock);
dev->muted = 0;
outb_p(1, isa->io);
outb_p(3, isa->io);
outb_p(1, isa->io);
}
static void zero(struct rtrack2 *dev)
static void one(struct radio_isa_card *isa)
{
outb_p(1, dev->io);
outb_p(3, dev->io);
outb_p(1, dev->io);
outb_p(5, isa->io);
outb_p(7, isa->io);
outb_p(5, isa->io);
}
static void one(struct rtrack2 *dev)
{
outb_p(5, dev->io);
outb_p(7, dev->io);
outb_p(5, dev->io);
}
static int rt_setfreq(struct rtrack2 *dev, unsigned long freq)
static int rtrack2_s_frequency(struct radio_isa_card *isa, u32 freq)
{
int i;
mutex_lock(&dev->lock);
dev->curfreq = freq;
freq = freq / 200 + 856;
outb_p(0xc8, dev->io);
outb_p(0xc9, dev->io);
outb_p(0xc9, dev->io);
outb_p(0xc8, isa->io);
outb_p(0xc9, isa->io);
outb_p(0xc9, isa->io);
for (i = 0; i < 10; i++)
zero(dev);
zero(isa);
for (i = 14; i >= 0; i--)
if (freq & (1 << i))
one(dev);
one(isa);
else
zero(dev);
zero(isa);
outb_p(0xc8, dev->io);
if (!dev->muted)
outb_p(0, dev->io);
mutex_unlock(&dev->lock);
outb_p(0xc8, isa->io);
if (!v4l2_ctrl_g_ctrl(isa->mute))
outb_p(0, isa->io);
return 0;
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
static u32 rtrack2_g_signal(struct radio_isa_card *isa)
{
strlcpy(v->driver, "radio-rtrack2", sizeof(v->driver));
strlcpy(v->card, "RadioTrack II", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
/* bit set = no signal present */
return (inb(isa->io) & 2) ? 0 : 0xffff;
}
static int rtrack2_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
outb(mute, isa->io);
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int rt_getsigstr(struct rtrack2 *dev)
{
int sig = 1;
mutex_lock(&dev->lock);
if (inb(dev->io) & 2) /* bit set = no signal present */
sig = 0;
mutex_unlock(&dev->lock);
return sig;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct rtrack2 *rt = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = 88 * 16000;
v->rangehigh = 108 * 16000;
v->rxsubchans = V4L2_TUNER_SUB_MONO;
v->capability = V4L2_TUNER_CAP_LOW;
v->audmode = V4L2_TUNER_MODE_MONO;
v->signal = 0xFFFF * rt_getsigstr(rt);
return 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct rtrack2 *rt = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
rt_setfreq(rt, f->frequency);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct rtrack2 *rt = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = rt->curfreq;
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
case V4L2_CID_AUDIO_VOLUME:
return v4l2_ctrl_query_fill(qc, 0, 65535, 65535, 65535);
}
return -EINVAL;
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct rtrack2 *rt = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
ctrl->value = rt->muted;
return 0;
case V4L2_CID_AUDIO_VOLUME:
if (rt->muted)
ctrl->value = 0;
else
ctrl->value = 65535;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct rtrack2 *rt = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
rt_mute(rt);
else
rt_unmute(rt);
return 0;
case V4L2_CID_AUDIO_VOLUME:
if (ctrl->value)
rt_unmute(rt);
else
rt_mute(rt);
return 0;
}
return -EINVAL;
}
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
{
return i ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static const struct v4l2_file_operations rtrack2_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
static const struct radio_isa_ops rtrack2_ops = {
.alloc = rtrack2_alloc,
.s_mute_volume = rtrack2_s_mute_volume,
.s_frequency = rtrack2_s_frequency,
.g_signal = rtrack2_g_signal,
};
static const struct v4l2_ioctl_ops rtrack2_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.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_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
static const int rtrack2_ioports[] = { 0x20f, 0x30f };
static struct radio_isa_driver rtrack2_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-rtrack2",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = rtrack2_ioports,
.num_of_io_ports = ARRAY_SIZE(rtrack2_ioports),
.region_size = 4,
.card = "AIMSlab RadioTrack II",
.ops = &rtrack2_ops,
.has_stereo = true,
};
static int __init rtrack2_init(void)
{
struct rtrack2 *dev = &rtrack2_card;
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
int res;
strlcpy(v4l2_dev->name, "rtrack2", sizeof(v4l2_dev->name));
dev->io = io;
if (dev->io == -1) {
v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or io=0x30c\n");
return -EINVAL;
}
if (!request_region(dev->io, 4, "rtrack2")) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n", dev->io);
return -EBUSY;
}
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(dev->io, 4);
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
return res;
}
strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
dev->vdev.v4l2_dev = v4l2_dev;
dev->vdev.fops = &rtrack2_fops;
dev->vdev.ioctl_ops = &rtrack2_ioctl_ops;
dev->vdev.release = video_device_release_empty;
video_set_drvdata(&dev->vdev, dev);
/* mute card - prevents noisy bootups */
outb(1, dev->io);
dev->muted = 1;
mutex_init(&dev->lock);
if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(v4l2_dev);
release_region(dev->io, 4);
return -EINVAL;
}
v4l2_info(v4l2_dev, "AIMSlab Radiotrack II card driver.\n");
return 0;
return isa_register_driver(&rtrack2_driver.driver, RTRACK2_MAX);
}
static void __exit rtrack2_exit(void)
{
struct rtrack2 *dev = &rtrack2_card;
video_unregister_device(&dev->vdev);
v4l2_device_unregister(&dev->v4l2_dev);
release_region(dev->io, 4);
isa_unregister_driver(&rtrack2_driver.driver);
}
module_init(rtrack2_init);

View File

@ -9,16 +9,23 @@
#include <linux/delay.h>
#include <linux/module.h> /* Modules */
#include <linux/init.h> /* Initdata */
#include <linux/slab.h>
#include <linux/ioport.h> /* request_region */
#include <linux/io.h> /* outb, outb_p */
#include <linux/isa.h>
#include <sound/tea575x-tuner.h>
MODULE_AUTHOR("Ondrej Zary");
MODULE_DESCRIPTION("MediaForte SF16-FMR2 FM radio card driver");
MODULE_LICENSE("GPL");
static int radio_nr = -1;
module_param(radio_nr, int, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device number");
struct fmr2 {
int io;
struct v4l2_device v4l2_dev;
struct snd_tea575x tea;
struct v4l2_ctrl *volume;
struct v4l2_ctrl *balance;
@ -26,7 +33,6 @@ struct fmr2 {
/* the port is hardwired so no need to support multiple cards */
#define FMR2_PORT 0x384
static struct fmr2 fmr2_card;
/* TEA575x tuner pins */
#define STR_DATA (1 << 0)
@ -180,26 +186,46 @@ static int fmr2_tea_ext_init(struct snd_tea575x *tea)
return 0;
}
static int __init fmr2_init(void)
static int __devinit fmr2_probe(struct device *pdev, unsigned int dev)
{
struct fmr2 *fmr2 = &fmr2_card;
struct fmr2 *fmr2;
int err;
fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL);
if (fmr2 == NULL)
return -ENOMEM;
strlcpy(fmr2->v4l2_dev.name, dev_name(pdev),
sizeof(fmr2->v4l2_dev.name));
fmr2->io = FMR2_PORT;
if (!request_region(fmr2->io, 2, "SF16-FMR2")) {
if (!request_region(fmr2->io, 2, fmr2->v4l2_dev.name)) {
printk(KERN_ERR "radio-sf16fmr2: I/O port 0x%x already in use\n", fmr2->io);
kfree(fmr2);
return -EBUSY;
}
dev_set_drvdata(pdev, fmr2);
err = v4l2_device_register(pdev, &fmr2->v4l2_dev);
if (err < 0) {
v4l2_err(&fmr2->v4l2_dev, "Could not register v4l2_device\n");
release_region(fmr2->io, 2);
kfree(fmr2);
return err;
}
fmr2->tea.v4l2_dev = &fmr2->v4l2_dev;
fmr2->tea.private_data = fmr2;
fmr2->tea.radio_nr = radio_nr;
fmr2->tea.ops = &fmr2_tea_ops;
fmr2->tea.ext_init = fmr2_tea_ext_init;
strlcpy(fmr2->tea.card, "SF16-FMR2", sizeof(fmr2->tea.card));
strcpy(fmr2->tea.bus_info, "ISA");
snprintf(fmr2->tea.bus_info, sizeof(fmr2->tea.bus_info), "ISA:%s",
fmr2->v4l2_dev.name);
if (snd_tea575x_init(&fmr2->tea)) {
printk(KERN_ERR "radio-sf16fmr2: Unable to detect TEA575x tuner\n");
release_region(fmr2->io, 2);
kfree(fmr2);
return -ENODEV;
}
@ -207,12 +233,33 @@ static int __init fmr2_init(void)
return 0;
}
static void __exit fmr2_exit(void)
static int __exit fmr2_remove(struct device *pdev, unsigned int dev)
{
struct fmr2 *fmr2 = &fmr2_card;
struct fmr2 *fmr2 = dev_get_drvdata(pdev);
snd_tea575x_exit(&fmr2->tea);
release_region(fmr2->io, 2);
v4l2_device_unregister(&fmr2->v4l2_dev);
kfree(fmr2);
return 0;
}
struct isa_driver fmr2_driver = {
.probe = fmr2_probe,
.remove = fmr2_remove,
.driver = {
.name = "radio-sf16fmr2",
},
};
static int __init fmr2_init(void)
{
return isa_register_driver(&fmr2_driver, 1);
}
static void __exit fmr2_exit(void)
{
isa_unregister_driver(&fmr2_driver);
}
module_init(fmr2_init);

View File

@ -575,21 +575,7 @@ static struct i2c_driver tea5764_i2c_driver = {
.id_table = tea5764_id,
};
/* init the driver */
static int __init tea5764_init(void)
{
int ret = i2c_add_driver(&tea5764_i2c_driver);
printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ": "
DRIVER_DESC "\n");
return ret;
}
/* cleanup the driver */
static void __exit tea5764_exit(void)
{
i2c_del_driver(&tea5764_i2c_driver);
}
module_i2c_driver(tea5764_i2c_driver);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
@ -600,6 +586,3 @@ module_param(use_xtal, int, 0);
MODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board");
module_param(radio_nr, int, 0);
MODULE_PARM_DESC(radio_nr, "video4linux device number to use");
module_init(tea5764_init);
module_exit(tea5764_exit);

View File

@ -16,11 +16,7 @@
* Frequency control is done digitally -- ie out(port,encodefreq(95.8));
* Volume Control is done digitally
*
* there is a I2C controlled RDS decoder (SAA6588) onboard, which i would like to support someday
* (as soon i have understand how to get started :)
* If you can help me out with that, please contact me!!
*
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
*/
@ -30,43 +26,24 @@
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/mutex.h>
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
MODULE_AUTHOR("R.OFFERMANNS & others");
MODULE_AUTHOR("R. Offermans & others");
MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.3");
MODULE_VERSION("0.1.99");
#ifndef CONFIG_RADIO_TERRATEC_PORT
#define CONFIG_RADIO_TERRATEC_PORT 0x590
#endif
static int io = CONFIG_RADIO_TERRATEC_PORT;
/* Note: there seems to be only one possible port (0x590), but without
hardware this is hard to verify. For now, this is the only one we will
support. */
static int io = 0x590;
static int radio_nr = -1;
module_param(io, int, 0);
MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)");
module_param(radio_nr, int, 0);
static struct v4l2_queryctrl radio_qctrl[] = {
{
.id = V4L2_CID_AUDIO_MUTE,
.name = "Mute",
.minimum = 0,
.maximum = 1,
.default_value = 1,
.type = V4L2_CTRL_TYPE_BOOLEAN,
},{
.id = V4L2_CID_AUDIO_VOLUME,
.name = "Volume",
.minimum = 0,
.maximum = 0xff,
.step = 1,
.default_value = 0xff,
.type = V4L2_CTRL_TYPE_INTEGER,
}
};
module_param(radio_nr, int, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device number");
#define WRT_DIS 0x00
#define CLK_OFF 0x00
@ -76,63 +53,24 @@ static struct v4l2_queryctrl radio_qctrl[] = {
#define CLK_ON 0x08
#define WRT_EN 0x10
struct terratec
static struct radio_isa_card *terratec_alloc(void)
{
struct v4l2_device v4l2_dev;
struct video_device vdev;
int io;
int curvol;
unsigned long curfreq;
int muted;
struct mutex lock;
};
return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL);
}
static struct terratec terratec_card;
/* local things */
static void tt_write_vol(struct terratec *tt, int volume)
static int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
int i;
volume = volume + (volume * 32); /* change both channels */
mutex_lock(&tt->lock);
if (mute)
vol = 0;
vol = vol + (vol * 32); /* change both channels */
for (i = 0; i < 8; i++) {
if (volume & (0x80 >> i))
outb(0x80, tt->io + 1);
if (vol & (0x80 >> i))
outb(0x80, isa->io + 1);
else
outb(0x00, tt->io + 1);
outb(0x00, isa->io + 1);
}
mutex_unlock(&tt->lock);
}
static void tt_mute(struct terratec *tt)
{
tt->muted = 1;
tt_write_vol(tt, 0);
}
static int tt_setvol(struct terratec *tt, int vol)
{
if (vol == tt->curvol) { /* requested volume = current */
if (tt->muted) { /* user is unmuting the card */
tt->muted = 0;
tt_write_vol(tt, vol); /* enable card */
}
return 0;
}
if (vol == 0) { /* volume = 0 means mute the card */
tt_write_vol(tt, 0); /* "turn off card" by setting vol to 0 */
tt->curvol = vol; /* track the volume state! */
return 0;
}
tt->muted = 0;
tt_write_vol(tt, vol);
tt->curvol = vol;
return 0;
}
@ -140,20 +78,15 @@ static int tt_setvol(struct terratec *tt, int vol)
/* this is the worst part in this driver */
/* many more or less strange things are going on here, but hey, it works :) */
static int tt_setfreq(struct terratec *tt, unsigned long freq1)
static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq)
{
int freq;
int i;
int p;
int temp;
int temp;
long rest;
unsigned char buffer[25]; /* we have to bit shift 25 registers */
mutex_lock(&tt->lock);
tt->curfreq = freq1;
freq = freq1 / 160; /* convert the freq. to a nice to handle value */
freq = freq / 160; /* convert the freq. to a nice to handle value */
memset(buffer, 0, sizeof(buffer));
rest = freq * 10 + 10700; /* I once had understood what is going on here */
@ -175,239 +108,61 @@ static int tt_setfreq(struct terratec *tt, unsigned long freq1)
for (i = 24; i > -1; i--) { /* bit shift the values to the radiocard */
if (buffer[i] == 1) {
outb(WRT_EN | DATA, tt->io);
outb(WRT_EN | DATA | CLK_ON, tt->io);
outb(WRT_EN | DATA, tt->io);
outb(WRT_EN | DATA, isa->io);
outb(WRT_EN | DATA | CLK_ON, isa->io);
outb(WRT_EN | DATA, isa->io);
} else {
outb(WRT_EN | 0x00, tt->io);
outb(WRT_EN | 0x00 | CLK_ON, tt->io);
outb(WRT_EN | 0x00, isa->io);
outb(WRT_EN | 0x00 | CLK_ON, isa->io);
}
}
outb(0x00, tt->io);
mutex_unlock(&tt->lock);
outb(0x00, isa->io);
return 0;
}
static int tt_getsigstr(struct terratec *tt)
static u32 terratec_g_signal(struct radio_isa_card *isa)
{
if (inb(tt->io) & 2) /* bit set = no signal present */
return 0;
return 1; /* signal present */
/* bit set = no signal present */
return (inb(isa->io) & 2) ? 0 : 0xffff;
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
strlcpy(v->driver, "radio-terratec", sizeof(v->driver));
strlcpy(v->card, "ActiveRadio", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct terratec *tt = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = 87 * 16000;
v->rangehigh = 108 * 16000;
v->rxsubchans = V4L2_TUNER_SUB_MONO;
v->capability = V4L2_TUNER_CAP_LOW;
v->audmode = V4L2_TUNER_MODE_MONO;
v->signal = 0xFFFF * tt_getsigstr(tt);
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct terratec *tt = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
tt_setfreq(tt, f->frequency);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct terratec *tt = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = tt->curfreq;
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
int i;
for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
if (qc->id && qc->id == radio_qctrl[i].id) {
memcpy(qc, &(radio_qctrl[i]), sizeof(*qc));
return 0;
}
}
return -EINVAL;
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct terratec *tt = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (tt->muted)
ctrl->value = 1;
else
ctrl->value = 0;
return 0;
case V4L2_CID_AUDIO_VOLUME:
ctrl->value = tt->curvol * 6554;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct terratec *tt = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
tt_mute(tt);
else
tt_setvol(tt,tt->curvol);
return 0;
case V4L2_CID_AUDIO_VOLUME:
tt_setvol(tt,ctrl->value);
return 0;
}
return -EINVAL;
}
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
{
return i ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static const struct v4l2_file_operations terratec_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
static const struct radio_isa_ops terratec_ops = {
.alloc = terratec_alloc,
.s_mute_volume = terratec_s_mute_volume,
.s_frequency = terratec_s_frequency,
.g_signal = terratec_g_signal,
};
static const struct v4l2_ioctl_ops terratec_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.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_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
static const int terratec_ioports[] = { 0x590 };
static struct radio_isa_driver terratec_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-terratec",
},
},
.io_params = &io,
.radio_nr_params = &radio_nr,
.io_ports = terratec_ioports,
.num_of_io_ports = ARRAY_SIZE(terratec_ioports),
.region_size = 2,
.card = "TerraTec ActiveRadio",
.ops = &terratec_ops,
.has_stereo = true,
.max_volume = 10,
};
static int __init terratec_init(void)
{
struct terratec *tt = &terratec_card;
struct v4l2_device *v4l2_dev = &tt->v4l2_dev;
int res;
strlcpy(v4l2_dev->name, "terratec", sizeof(v4l2_dev->name));
tt->io = io;
if (tt->io == -1) {
v4l2_err(v4l2_dev, "you must set an I/O address with io=0x590 or 0x591\n");
return -EINVAL;
}
if (!request_region(tt->io, 2, "terratec")) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n", io);
return -EBUSY;
}
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(tt->io, 2);
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
return res;
}
strlcpy(tt->vdev.name, v4l2_dev->name, sizeof(tt->vdev.name));
tt->vdev.v4l2_dev = v4l2_dev;
tt->vdev.fops = &terratec_fops;
tt->vdev.ioctl_ops = &terratec_ioctl_ops;
tt->vdev.release = video_device_release_empty;
video_set_drvdata(&tt->vdev, tt);
mutex_init(&tt->lock);
/* mute card - prevents noisy bootups */
tt_write_vol(tt, 0);
if (video_register_device(&tt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(&tt->v4l2_dev);
release_region(tt->io, 2);
return -EINVAL;
}
v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver.\n");
return 0;
return isa_register_driver(&terratec_driver.driver, 1);
}
static void __exit terratec_exit(void)
{
struct terratec *tt = &terratec_card;
struct v4l2_device *v4l2_dev = &tt->v4l2_dev;
video_unregister_device(&tt->vdev);
v4l2_device_unregister(&tt->v4l2_dev);
release_region(tt->io, 2);
v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver unloaded.\n");
isa_unregister_driver(&terratec_driver.driver);
}
module_init(terratec_init);

View File

@ -21,13 +21,15 @@
#include <linux/ioport.h>
#include <linux/videodev2.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
MODULE_DESCRIPTION("A driver for the Trust FM Radio card.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.3");
MODULE_VERSION("0.1.99");
/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
@ -35,39 +37,38 @@ MODULE_VERSION("0.0.3");
#define CONFIG_RADIO_TRUST_PORT -1
#endif
static int io = CONFIG_RADIO_TRUST_PORT;
static int radio_nr = -1;
#define TRUST_MAX 2
module_param(io, int, 0);
MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)");
module_param(radio_nr, int, 0);
static int io[TRUST_MAX] = { [0] = CONFIG_RADIO_TRUST_PORT,
[1 ... (TRUST_MAX - 1)] = -1 };
static int radio_nr[TRUST_MAX] = { [0 ... (TRUST_MAX - 1)] = -1 };
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the Trust FM Radio card (0x350 or 0x358)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
struct trust {
struct v4l2_device v4l2_dev;
struct video_device vdev;
int io;
struct radio_isa_card isa;
int ioval;
__u16 curvol;
__u16 curbass;
__u16 curtreble;
int muted;
unsigned long curfreq;
int curstereo;
int curmute;
struct mutex lock;
};
static struct trust trust_card;
static struct radio_isa_card *trust_alloc(void)
{
struct trust *tr = kzalloc(sizeof(*tr), GFP_KERNEL);
return tr ? &tr->isa : NULL;
}
/* i2c addresses */
#define TDA7318_ADDR 0x88
#define TSA6060T_ADDR 0xc4
#define TR_DELAY do { inb(tr->io); inb(tr->io); inb(tr->io); } while (0)
#define TR_SET_SCL outb(tr->ioval |= 2, tr->io)
#define TR_CLR_SCL outb(tr->ioval &= 0xfd, tr->io)
#define TR_SET_SDA outb(tr->ioval |= 1, tr->io)
#define TR_CLR_SDA outb(tr->ioval &= 0xfe, tr->io)
#define TR_DELAY do { inb(tr->isa.io); inb(tr->isa.io); inb(tr->isa.io); } while (0)
#define TR_SET_SCL outb(tr->ioval |= 2, tr->isa.io)
#define TR_CLR_SCL outb(tr->ioval &= 0xfd, tr->isa.io)
#define TR_SET_SDA outb(tr->ioval |= 1, tr->isa.io)
#define TR_CLR_SDA outb(tr->ioval &= 0xfe, tr->isa.io)
static void write_i2c(struct trust *tr, int n, ...)
{
@ -84,10 +85,10 @@ static void write_i2c(struct trust *tr, int n, ...)
TR_CLR_SCL;
TR_DELAY;
for(; n; n--) {
for (; n; n--) {
val = va_arg(args, unsigned);
for(mask = 0x80; mask; mask >>= 1) {
if(val & mask)
for (mask = 0x80; mask; mask >>= 1) {
if (val & mask)
TR_SET_SDA;
else
TR_CLR_SDA;
@ -115,317 +116,128 @@ static void write_i2c(struct trust *tr, int n, ...)
va_end(args);
}
static void tr_setvol(struct trust *tr, __u16 vol)
static int trust_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
mutex_lock(&tr->lock);
tr->curvol = vol / 2048;
write_i2c(tr, 2, TDA7318_ADDR, tr->curvol ^ 0x1f);
mutex_unlock(&tr->lock);
struct trust *tr = container_of(isa, struct trust, isa);
tr->ioval = (tr->ioval & 0xf7) | (mute << 3);
outb(tr->ioval, isa->io);
write_i2c(tr, 2, TDA7318_ADDR, vol ^ 0x1f);
return 0;
}
static int trust_s_stereo(struct radio_isa_card *isa, bool stereo)
{
struct trust *tr = container_of(isa, struct trust, isa);
tr->ioval = (tr->ioval & 0xfb) | (!stereo << 2);
outb(tr->ioval, isa->io);
return 0;
}
static u32 trust_g_signal(struct radio_isa_card *isa)
{
int i, v;
for (i = 0, v = 0; i < 100; i++)
v |= inb(isa->io);
return (v & 1) ? 0 : 0xffff;
}
static int trust_s_frequency(struct radio_isa_card *isa, u32 freq)
{
struct trust *tr = container_of(isa, struct trust, isa);
freq /= 160; /* Convert to 10 kHz units */
freq += 1070; /* Add 10.7 MHz IF */
write_i2c(tr, 5, TSA6060T_ADDR, (freq << 1) | 1,
freq >> 7, 0x60 | ((freq >> 15) & 1), 0);
return 0;
}
static int basstreble2chip[15] = {
0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8
};
static void tr_setbass(struct trust *tr, __u16 bass)
static int trust_s_ctrl(struct v4l2_ctrl *ctrl)
{
mutex_lock(&tr->lock);
tr->curbass = bass / 4370;
write_i2c(tr, 2, TDA7318_ADDR, 0x60 | basstreble2chip[tr->curbass]);
mutex_unlock(&tr->lock);
}
static void tr_settreble(struct trust *tr, __u16 treble)
{
mutex_lock(&tr->lock);
tr->curtreble = treble / 4370;
write_i2c(tr, 2, TDA7318_ADDR, 0x70 | basstreble2chip[tr->curtreble]);
mutex_unlock(&tr->lock);
}
static void tr_setstereo(struct trust *tr, int stereo)
{
mutex_lock(&tr->lock);
tr->curstereo = !!stereo;
tr->ioval = (tr->ioval & 0xfb) | (!tr->curstereo << 2);
outb(tr->ioval, tr->io);
mutex_unlock(&tr->lock);
}
static void tr_setmute(struct trust *tr, int mute)
{
mutex_lock(&tr->lock);
tr->curmute = !!mute;
tr->ioval = (tr->ioval & 0xf7) | (tr->curmute << 3);
outb(tr->ioval, tr->io);
mutex_unlock(&tr->lock);
}
static int tr_getsigstr(struct trust *tr)
{
int i, v;
mutex_lock(&tr->lock);
for (i = 0, v = 0; i < 100; i++)
v |= inb(tr->io);
mutex_unlock(&tr->lock);
return (v & 1) ? 0 : 0xffff;
}
static int tr_getstereo(struct trust *tr)
{
/* don't know how to determine it, just return the setting */
return tr->curstereo;
}
static void tr_setfreq(struct trust *tr, unsigned long f)
{
mutex_lock(&tr->lock);
tr->curfreq = f;
f /= 160; /* Convert to 10 kHz units */
f += 1070; /* Add 10.7 MHz IF */
write_i2c(tr, 5, TSA6060T_ADDR, (f << 1) | 1, f >> 7, 0x60 | ((f >> 15) & 1), 0);
mutex_unlock(&tr->lock);
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
strlcpy(v->driver, "radio-trust", sizeof(v->driver));
strlcpy(v->card, "Trust FM Radio", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct trust *tr = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = 87.5 * 16000;
v->rangehigh = 108 * 16000;
v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
v->capability = V4L2_TUNER_CAP_LOW;
if (tr_getstereo(tr))
v->audmode = V4L2_TUNER_MODE_STEREO;
else
v->audmode = V4L2_TUNER_MODE_MONO;
v->signal = tr_getsigstr(tr);
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct trust *tr = video_drvdata(file);
if (v->index)
return -EINVAL;
tr_setstereo(tr, v->audmode == V4L2_TUNER_MODE_STEREO);
return 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct trust *tr = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
tr_setfreq(tr, f->frequency);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct trust *tr = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = tr->curfreq;
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
case V4L2_CID_AUDIO_VOLUME:
return v4l2_ctrl_query_fill(qc, 0, 65535, 2048, 65535);
case V4L2_CID_AUDIO_BASS:
case V4L2_CID_AUDIO_TREBLE:
return v4l2_ctrl_query_fill(qc, 0, 65535, 4370, 32768);
}
return -EINVAL;
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct trust *tr = video_drvdata(file);
struct radio_isa_card *isa =
container_of(ctrl->handler, struct radio_isa_card, hdl);
struct trust *tr = container_of(isa, struct trust, isa);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
ctrl->value = tr->curmute;
return 0;
case V4L2_CID_AUDIO_VOLUME:
ctrl->value = tr->curvol * 2048;
return 0;
case V4L2_CID_AUDIO_BASS:
ctrl->value = tr->curbass * 4370;
write_i2c(tr, 2, TDA7318_ADDR, 0x60 | basstreble2chip[ctrl->val]);
return 0;
case V4L2_CID_AUDIO_TREBLE:
ctrl->value = tr->curtreble * 4370;
write_i2c(tr, 2, TDA7318_ADDR, 0x70 | basstreble2chip[ctrl->val]);
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct trust *tr = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
tr_setmute(tr, ctrl->value);
return 0;
case V4L2_CID_AUDIO_VOLUME:
tr_setvol(tr, ctrl->value);
return 0;
case V4L2_CID_AUDIO_BASS:
tr_setbass(tr, ctrl->value);
return 0;
case V4L2_CID_AUDIO_TREBLE:
tr_settreble(tr, ctrl->value);
return 0;
}
return -EINVAL;
}
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
{
return i ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static const struct v4l2_file_operations trust_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
static const struct v4l2_ctrl_ops trust_ctrl_ops = {
.s_ctrl = trust_s_ctrl,
};
static const struct v4l2_ioctl_ops trust_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.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_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
};
static int __init trust_init(void)
static int trust_initialize(struct radio_isa_card *isa)
{
struct trust *tr = &trust_card;
struct v4l2_device *v4l2_dev = &tr->v4l2_dev;
int res;
struct trust *tr = container_of(isa, struct trust, isa);
strlcpy(v4l2_dev->name, "trust", sizeof(v4l2_dev->name));
tr->io = io;
tr->ioval = 0xf;
mutex_init(&tr->lock);
if (tr->io == -1) {
v4l2_err(v4l2_dev, "You must set an I/O address with io=0x0x350 or 0x358\n");
return -EINVAL;
}
if (!request_region(tr->io, 2, "Trust FM Radio")) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n", tr->io);
return -EBUSY;
}
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(tr->io, 2);
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
return res;
}
strlcpy(tr->vdev.name, v4l2_dev->name, sizeof(tr->vdev.name));
tr->vdev.v4l2_dev = v4l2_dev;
tr->vdev.fops = &trust_fops;
tr->vdev.ioctl_ops = &trust_ioctl_ops;
tr->vdev.release = video_device_release_empty;
video_set_drvdata(&tr->vdev, tr);
write_i2c(tr, 2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */
write_i2c(tr, 2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */
write_i2c(tr, 2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */
write_i2c(tr, 2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */
write_i2c(tr, 2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */
tr_setvol(tr, 0xffff);
tr_setbass(tr, 0x8000);
tr_settreble(tr, 0x8000);
tr_setstereo(tr, 1);
/* mute card - prevents noisy bootups */
tr_setmute(tr, 1);
if (video_register_device(&tr->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(v4l2_dev);
release_region(tr->io, 2);
return -EINVAL;
}
v4l2_info(v4l2_dev, "Trust FM Radio card driver v1.0.\n");
return 0;
v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops,
V4L2_CID_AUDIO_BASS, 0, 15, 1, 8);
v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops,
V4L2_CID_AUDIO_TREBLE, 0, 15, 1, 8);
return isa->hdl.error;
}
static void __exit cleanup_trust_module(void)
{
struct trust *tr = &trust_card;
static const struct radio_isa_ops trust_ops = {
.init = trust_initialize,
.alloc = trust_alloc,
.s_mute_volume = trust_s_mute_volume,
.s_frequency = trust_s_frequency,
.s_stereo = trust_s_stereo,
.g_signal = trust_g_signal,
};
video_unregister_device(&tr->vdev);
v4l2_device_unregister(&tr->v4l2_dev);
release_region(tr->io, 2);
static const int trust_ioports[] = { 0x350, 0x358 };
static struct radio_isa_driver trust_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-trust",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = trust_ioports,
.num_of_io_ports = ARRAY_SIZE(trust_ioports),
.region_size = 2,
.card = "Trust FM Radio",
.ops = &trust_ops,
.has_stereo = true,
.max_volume = 31,
};
static int __init trust_init(void)
{
return isa_register_driver(&trust_driver.driver, TRUST_MAX);
}
static void __exit trust_exit(void)
{
isa_unregister_driver(&trust_driver.driver);
}
module_init(trust_init);
module_exit(cleanup_trust_module);
module_exit(trust_exit);

View File

@ -33,63 +33,53 @@
#include <linux/ioport.h> /* request_region */
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
#define DRIVER_VERSION "0.1.2"
MODULE_AUTHOR("Dr. Henrik Seidel");
MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
MODULE_VERSION("0.1.99");
#ifndef CONFIG_RADIO_TYPHOON_PORT
#define CONFIG_RADIO_TYPHOON_PORT -1
#endif
#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
#define CONFIG_RADIO_TYPHOON_MUTEFREQ 0
#define CONFIG_RADIO_TYPHOON_MUTEFREQ 87000
#endif
static int io = CONFIG_RADIO_TYPHOON_PORT;
static int radio_nr = -1;
module_param(io, int, 0);
MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)");
module_param(radio_nr, int, 0);
#define TYPHOON_MAX 2
static int io[TYPHOON_MAX] = { [0] = CONFIG_RADIO_TYPHOON_PORT,
[1 ... (TYPHOON_MAX - 1)] = -1 };
static int radio_nr[TYPHOON_MAX] = { [0 ... (TYPHOON_MAX - 1)] = -1 };
static unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ;
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the Typhoon card (0x316 or 0x336)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
module_param(mutefreq, ulong, 0);
MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");
#define BANNER "Typhoon Radio Card driver v" DRIVER_VERSION "\n"
struct typhoon {
struct v4l2_device v4l2_dev;
struct video_device vdev;
int io;
int curvol;
struct radio_isa_card isa;
int muted;
unsigned long curfreq;
unsigned long mutefreq;
struct mutex lock;
};
static struct typhoon typhoon_card;
static void typhoon_setvol_generic(struct typhoon *dev, int vol)
static struct radio_isa_card *typhoon_alloc(void)
{
mutex_lock(&dev->lock);
vol >>= 14; /* Map 16 bit to 2 bit */
vol &= 3;
outb_p(vol / 2, dev->io); /* Set the volume, high bit. */
outb_p(vol % 2, dev->io + 2); /* Set the volume, low bit. */
mutex_unlock(&dev->lock);
struct typhoon *ty = kzalloc(sizeof(*ty), GFP_KERNEL);
return ty ? &ty->isa : NULL;
}
static int typhoon_setfreq_generic(struct typhoon *dev,
unsigned long frequency)
static int typhoon_s_frequency(struct radio_isa_card *isa, u32 freq)
{
unsigned long outval;
unsigned long x;
@ -105,302 +95,86 @@ static int typhoon_setfreq_generic(struct typhoon *dev,
*
*/
mutex_lock(&dev->lock);
x = frequency / 160;
x = freq / 160;
outval = (x * x + 2500) / 5000;
outval = (outval * x + 5000) / 10000;
outval -= (10 * x * x + 10433) / 20866;
outval += 4 * x - 11505;
outb_p((outval >> 8) & 0x01, dev->io + 4);
outb_p(outval >> 9, dev->io + 6);
outb_p(outval & 0xff, dev->io + 8);
mutex_unlock(&dev->lock);
outb_p((outval >> 8) & 0x01, isa->io + 4);
outb_p(outval >> 9, isa->io + 6);
outb_p(outval & 0xff, isa->io + 8);
return 0;
}
static int typhoon_setfreq(struct typhoon *dev, unsigned long frequency)
static int typhoon_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
typhoon_setfreq_generic(dev, frequency);
dev->curfreq = frequency;
return 0;
}
struct typhoon *ty = container_of(isa, struct typhoon, isa);
static void typhoon_mute(struct typhoon *dev)
{
if (dev->muted == 1)
return;
typhoon_setvol_generic(dev, 0);
typhoon_setfreq_generic(dev, dev->mutefreq);
dev->muted = 1;
}
if (mute)
vol = 0;
vol >>= 14; /* Map 16 bit to 2 bit */
vol &= 3;
outb_p(vol / 2, isa->io); /* Set the volume, high bit. */
outb_p(vol % 2, isa->io + 2); /* Set the volume, low bit. */
static void typhoon_unmute(struct typhoon *dev)
{
if (dev->muted == 0)
return;
typhoon_setfreq_generic(dev, dev->curfreq);
typhoon_setvol_generic(dev, dev->curvol);
dev->muted = 0;
}
static int typhoon_setvol(struct typhoon *dev, int vol)
{
if (dev->muted && vol != 0) { /* user is unmuting the card */
dev->curvol = vol;
typhoon_unmute(dev);
return 0;
if (vol == 0 && !ty->muted) {
ty->muted = true;
return typhoon_s_frequency(isa, mutefreq << 4);
}
if (vol == dev->curvol) /* requested volume == current */
return 0;
if (vol == 0) { /* volume == 0 means mute the card */
typhoon_mute(dev);
dev->curvol = vol;
return 0;
if (vol && ty->muted) {
ty->muted = false;
return typhoon_s_frequency(isa, isa->freq);
}
typhoon_setvol_generic(dev, vol);
dev->curvol = vol;
return 0;
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
strlcpy(v->driver, "radio-typhoon", sizeof(v->driver));
strlcpy(v->card, "Typhoon Radio", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = 87.5 * 16000;
v->rangehigh = 108 * 16000;
v->rxsubchans = V4L2_TUNER_SUB_MONO;
v->capability = V4L2_TUNER_CAP_LOW;
v->audmode = V4L2_TUNER_MODE_MONO;
v->signal = 0xFFFF; /* We can't get the signal strength */
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct typhoon *dev = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = dev->curfreq;
return 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct typhoon *dev = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
dev->curfreq = f->frequency;
typhoon_setfreq(dev, dev->curfreq);
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
case V4L2_CID_AUDIO_VOLUME:
return v4l2_ctrl_query_fill(qc, 0, 65535, 16384, 65535);
}
return -EINVAL;
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct typhoon *dev = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
ctrl->value = dev->muted;
return 0;
case V4L2_CID_AUDIO_VOLUME:
ctrl->value = dev->curvol;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl (struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct typhoon *dev = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
typhoon_mute(dev);
else
typhoon_unmute(dev);
return 0;
case V4L2_CID_AUDIO_VOLUME:
typhoon_setvol(dev, ctrl->value);
return 0;
}
return -EINVAL;
}
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
{
return i ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static int vidioc_log_status(struct file *file, void *priv)
{
struct typhoon *dev = video_drvdata(file);
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
v4l2_info(v4l2_dev, BANNER);
#ifdef MODULE
v4l2_info(v4l2_dev, "Load type: Driver loaded as a module\n\n");
#else
v4l2_info(v4l2_dev, "Load type: Driver compiled into kernel\n\n");
#endif
v4l2_info(v4l2_dev, "frequency = %lu kHz\n", dev->curfreq >> 4);
v4l2_info(v4l2_dev, "volume = %d\n", dev->curvol);
v4l2_info(v4l2_dev, "mute = %s\n", dev->muted ? "on" : "off");
v4l2_info(v4l2_dev, "io = 0x%x\n", dev->io);
v4l2_info(v4l2_dev, "mute frequency = %lu kHz\n", dev->mutefreq >> 4);
return 0;
}
static const struct v4l2_file_operations typhoon_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
static const struct radio_isa_ops typhoon_ops = {
.alloc = typhoon_alloc,
.s_mute_volume = typhoon_s_mute_volume,
.s_frequency = typhoon_s_frequency,
};
static const struct v4l2_ioctl_ops typhoon_ioctl_ops = {
.vidioc_log_status = vidioc_log_status,
.vidioc_querycap = vidioc_querycap,
.vidioc_g_tuner = vidioc_g_tuner,
.vidioc_s_tuner = vidioc_s_tuner,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
static const int typhoon_ioports[] = { 0x316, 0x336 };
static struct radio_isa_driver typhoon_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-typhoon",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = typhoon_ioports,
.num_of_io_ports = ARRAY_SIZE(typhoon_ioports),
.region_size = 8,
.card = "Typhoon Radio",
.ops = &typhoon_ops,
.has_stereo = true,
.max_volume = 3,
};
static int __init typhoon_init(void)
{
struct typhoon *dev = &typhoon_card;
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
int res;
strlcpy(v4l2_dev->name, "typhoon", sizeof(v4l2_dev->name));
dev->io = io;
if (dev->io == -1) {
v4l2_err(v4l2_dev, "You must set an I/O address with io=0x316 or io=0x336\n");
return -EINVAL;
if (mutefreq < 87000 || mutefreq > 108000) {
printk(KERN_ERR "%s: You must set a frequency (in kHz) used when muting the card,\n",
typhoon_driver.driver.driver.name);
printk(KERN_ERR "%s: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108000)\n",
typhoon_driver.driver.driver.name);
return -ENODEV;
}
if (mutefreq < 87000 || mutefreq > 108500) {
v4l2_err(v4l2_dev, "You must set a frequency (in kHz) used when muting the card,\n");
v4l2_err(v4l2_dev, "e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n");
return -EINVAL;
}
dev->curfreq = dev->mutefreq = mutefreq << 4;
mutex_init(&dev->lock);
if (!request_region(dev->io, 8, "typhoon")) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n",
dev->io);
return -EBUSY;
}
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(dev->io, 8);
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
return res;
}
v4l2_info(v4l2_dev, BANNER);
strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
dev->vdev.v4l2_dev = v4l2_dev;
dev->vdev.fops = &typhoon_fops;
dev->vdev.ioctl_ops = &typhoon_ioctl_ops;
dev->vdev.release = video_device_release_empty;
video_set_drvdata(&dev->vdev, dev);
/* mute card - prevents noisy bootups */
typhoon_mute(dev);
if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(&dev->v4l2_dev);
release_region(dev->io, 8);
return -EINVAL;
}
v4l2_info(v4l2_dev, "port 0x%x.\n", dev->io);
v4l2_info(v4l2_dev, "mute frequency is %lu kHz.\n", mutefreq);
return 0;
return isa_register_driver(&typhoon_driver.driver, TYPHOON_MAX);
}
static void __exit typhoon_exit(void)
{
struct typhoon *dev = &typhoon_card;
video_unregister_device(&dev->vdev);
v4l2_device_unregister(&dev->v4l2_dev);
release_region(dev->io, 8);
isa_unregister_driver(&typhoon_driver.driver);
}
module_init(typhoon_init);
module_exit(typhoon_exit);

View File

@ -1,5 +1,6 @@
/* zoltrix radio plus driver for Linux radio support
* (c) 1998 C. van Schaik <carl@leg.uct.ac.za>
/*
* Zoltrix Radio Plus driver
* Copyright 1998 C. van Schaik <carl@leg.uct.ac.za>
*
* BUGS
* Due to the inconsistency in reading from the signal flags
@ -27,6 +28,14 @@
*
* 2006-07-24 - Converted to V4L2 API
* by Mauro Carvalho Chehab <mchehab@infradead.org>
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
*
* Note that this is the driver for the Zoltrix Radio Plus.
* This driver does not work for the Zoltrix Radio Plus 108 or the
* Zoltrix Radio Plus for Windows.
*
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*/
#include <linux/module.h> /* Modules */
@ -36,82 +45,70 @@
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/mutex.h>
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
MODULE_AUTHOR("C.van Schaik");
MODULE_AUTHOR("C. van Schaik");
MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.3");
MODULE_VERSION("0.1.99");
#ifndef CONFIG_RADIO_ZOLTRIX_PORT
#define CONFIG_RADIO_ZOLTRIX_PORT -1
#endif
static int io = CONFIG_RADIO_ZOLTRIX_PORT;
static int radio_nr = -1;
#define ZOLTRIX_MAX 2
module_param(io, int, 0);
MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
module_param(radio_nr, int, 0);
static int io[ZOLTRIX_MAX] = { [0] = CONFIG_RADIO_ZOLTRIX_PORT,
[1 ... (ZOLTRIX_MAX - 1)] = -1 };
static int radio_nr[ZOLTRIX_MAX] = { [0 ... (ZOLTRIX_MAX - 1)] = -1 };
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
struct zoltrix {
struct v4l2_device v4l2_dev;
struct video_device vdev;
int io;
struct radio_isa_card isa;
int curvol;
unsigned long curfreq;
int muted;
unsigned int stereo;
struct mutex lock;
bool muted;
};
static struct zoltrix zoltrix_card;
static int zol_setvol(struct zoltrix *zol, int vol)
static struct radio_isa_card *zoltrix_alloc(void)
{
zol->curvol = vol;
if (zol->muted)
return 0;
struct zoltrix *zol = kzalloc(sizeof(*zol), GFP_KERNEL);
mutex_lock(&zol->lock);
if (vol == 0) {
outb(0, zol->io);
outb(0, zol->io);
inb(zol->io + 3); /* Zoltrix needs to be read to confirm */
mutex_unlock(&zol->lock);
return zol ? &zol->isa : NULL;
}
static int zoltrix_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
zol->curvol = vol;
zol->muted = mute;
if (mute || vol == 0) {
outb(0, isa->io);
outb(0, isa->io);
inb(isa->io + 3); /* Zoltrix needs to be read to confirm */
return 0;
}
outb(zol->curvol-1, zol->io);
outb(vol - 1, isa->io);
msleep(10);
inb(zol->io + 2);
mutex_unlock(&zol->lock);
inb(isa->io + 2);
return 0;
}
static void zol_mute(struct zoltrix *zol)
/* tunes the radio to the desired frequency */
static int zoltrix_s_frequency(struct radio_isa_card *isa, u32 freq)
{
zol->muted = 1;
mutex_lock(&zol->lock);
outb(0, zol->io);
outb(0, zol->io);
inb(zol->io + 3); /* Zoltrix needs to be read to confirm */
mutex_unlock(&zol->lock);
}
static void zol_unmute(struct zoltrix *zol)
{
zol->muted = 0;
zol_setvol(zol, zol->curvol);
}
static int zol_setfreq(struct zoltrix *zol, unsigned long freq)
{
/* tunes the radio to the desired frequency */
struct v4l2_device *v4l2_dev = &zol->v4l2_dev;
struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
struct v4l2_device *v4l2_dev = &isa->v4l2_dev;
unsigned long long bitmask, f, m;
unsigned int stereo = zol->stereo;
bool stereo = isa->stereo;
int i;
if (freq == 0) {
@ -125,340 +122,125 @@ static int zol_setfreq(struct zoltrix *zol, unsigned long freq)
bitmask = 0xc480402c10080000ull;
i = 45;
mutex_lock(&zol->lock);
outb(0, isa->io);
outb(0, isa->io);
inb(isa->io + 3); /* Zoltrix needs to be read to confirm */
zol->curfreq = freq;
outb(0, zol->io);
outb(0, zol->io);
inb(zol->io + 3); /* Zoltrix needs to be read to confirm */
outb(0x40, zol->io);
outb(0xc0, zol->io);
outb(0x40, isa->io);
outb(0xc0, isa->io);
bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31));
while (i--) {
if ((bitmask & 0x8000000000000000ull) != 0) {
outb(0x80, zol->io);
outb(0x80, isa->io);
udelay(50);
outb(0x00, zol->io);
outb(0x00, isa->io);
udelay(50);
outb(0x80, zol->io);
outb(0x80, isa->io);
udelay(50);
} else {
outb(0xc0, zol->io);
outb(0xc0, isa->io);
udelay(50);
outb(0x40, zol->io);
outb(0x40, isa->io);
udelay(50);
outb(0xc0, zol->io);
outb(0xc0, isa->io);
udelay(50);
}
bitmask *= 2;
}
/* termination sequence */
outb(0x80, zol->io);
outb(0xc0, zol->io);
outb(0x40, zol->io);
outb(0x80, isa->io);
outb(0xc0, isa->io);
outb(0x40, isa->io);
udelay(1000);
inb(zol->io + 2);
inb(isa->io + 2);
udelay(1000);
if (zol->muted) {
outb(0, zol->io);
outb(0, zol->io);
inb(zol->io + 3);
udelay(1000);
}
mutex_unlock(&zol->lock);
if (!zol->muted)
zol_setvol(zol, zol->curvol);
return 0;
return zoltrix_s_mute_volume(isa, zol->muted, zol->curvol);
}
/* Get signal strength */
static int zol_getsigstr(struct zoltrix *zol)
static u32 zoltrix_g_rxsubchans(struct radio_isa_card *isa)
{
struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
int a, b;
mutex_lock(&zol->lock);
outb(0x00, zol->io); /* This stuff I found to do nothing */
outb(zol->curvol, zol->io);
outb(0x00, isa->io); /* This stuff I found to do nothing */
outb(zol->curvol, isa->io);
msleep(20);
a = inb(zol->io);
a = inb(isa->io);
msleep(10);
b = inb(zol->io);
b = inb(isa->io);
mutex_unlock(&zol->lock);
return (a == b && a == 0xcf) ?
V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
}
static u32 zoltrix_g_signal(struct radio_isa_card *isa)
{
struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
int a, b;
outb(0x00, isa->io); /* This stuff I found to do nothing */
outb(zol->curvol, isa->io);
msleep(20);
a = inb(isa->io);
msleep(10);
b = inb(isa->io);
if (a != b)
return 0;
/* I found this out by playing with a binary scanner on the card io */
return a == 0xcf || a == 0xdf || a == 0xef;
return (a == 0xcf || a == 0xdf || a == 0xef) ? 0xffff : 0;
}
static int zol_is_stereo(struct zoltrix *zol)
static int zoltrix_s_stereo(struct radio_isa_card *isa, bool stereo)
{
int x1, x2;
mutex_lock(&zol->lock);
outb(0x00, zol->io);
outb(zol->curvol, zol->io);
msleep(20);
x1 = inb(zol->io);
msleep(10);
x2 = inb(zol->io);
mutex_unlock(&zol->lock);
return x1 == x2 && x1 == 0xcf;
return zoltrix_s_frequency(isa, isa->freq);
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
strlcpy(v->driver, "radio-zoltrix", sizeof(v->driver));
strlcpy(v->card, "Zoltrix Radio", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct zoltrix *zol = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = 88 * 16000;
v->rangehigh = 108 * 16000;
v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
v->capability = V4L2_TUNER_CAP_LOW;
if (zol_is_stereo(zol))
v->audmode = V4L2_TUNER_MODE_STEREO;
else
v->audmode = V4L2_TUNER_MODE_MONO;
v->signal = 0xFFFF * zol_getsigstr(zol);
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct zoltrix *zol = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
if (zol_setfreq(zol, f->frequency) != 0)
return -EINVAL;
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct zoltrix *zol = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = zol->curfreq;
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
case V4L2_CID_AUDIO_VOLUME:
return v4l2_ctrl_query_fill(qc, 0, 65535, 4096, 65535);
}
return -EINVAL;
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct zoltrix *zol = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
ctrl->value = zol->muted;
return 0;
case V4L2_CID_AUDIO_VOLUME:
ctrl->value = zol->curvol * 4096;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct zoltrix *zol = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
zol_mute(zol);
else {
zol_unmute(zol);
zol_setvol(zol, zol->curvol);
}
return 0;
case V4L2_CID_AUDIO_VOLUME:
zol_setvol(zol, ctrl->value / 4096);
return 0;
}
zol->stereo = 1;
if (zol_setfreq(zol, zol->curfreq) != 0)
return -EINVAL;
#if 0
/* FIXME: Implement stereo/mono switch on V4L2 */
if (v->mode & VIDEO_SOUND_STEREO) {
zol->stereo = 1;
zol_setfreq(zol, zol->curfreq);
}
if (v->mode & VIDEO_SOUND_MONO) {
zol->stereo = 0;
zol_setfreq(zol, zol->curfreq);
}
#endif
return -EINVAL;
}
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
{
return i ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static const struct v4l2_file_operations zoltrix_fops =
{
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
static const struct radio_isa_ops zoltrix_ops = {
.alloc = zoltrix_alloc,
.s_mute_volume = zoltrix_s_mute_volume,
.s_frequency = zoltrix_s_frequency,
.s_stereo = zoltrix_s_stereo,
.g_rxsubchans = zoltrix_g_rxsubchans,
.g_signal = zoltrix_g_signal,
};
static const struct v4l2_ioctl_ops zoltrix_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_g_tuner = vidioc_g_tuner,
.vidioc_s_tuner = vidioc_s_tuner,
.vidioc_g_audio = vidioc_g_audio,
.vidioc_s_audio = vidioc_s_audio,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
static const int zoltrix_ioports[] = { 0x20c, 0x30c };
static struct radio_isa_driver zoltrix_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-zoltrix",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = zoltrix_ioports,
.num_of_io_ports = ARRAY_SIZE(zoltrix_ioports),
.region_size = 2,
.card = "Zoltrix Radio Plus",
.ops = &zoltrix_ops,
.has_stereo = true,
.max_volume = 15,
};
static int __init zoltrix_init(void)
{
struct zoltrix *zol = &zoltrix_card;
struct v4l2_device *v4l2_dev = &zol->v4l2_dev;
int res;
strlcpy(v4l2_dev->name, "zoltrix", sizeof(v4l2_dev->name));
zol->io = io;
if (zol->io == -1) {
v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or 0x30c\n");
return -EINVAL;
}
if (zol->io != 0x20c && zol->io != 0x30c) {
v4l2_err(v4l2_dev, "invalid port, try 0x20c or 0x30c\n");
return -ENXIO;
}
if (!request_region(zol->io, 2, "zoltrix")) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n", zol->io);
return -EBUSY;
}
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(zol->io, 2);
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
return res;
}
mutex_init(&zol->lock);
/* mute card - prevents noisy bootups */
/* this ensures that the volume is all the way down */
outb(0, zol->io);
outb(0, zol->io);
msleep(20);
inb(zol->io + 3);
zol->curvol = 0;
zol->stereo = 1;
strlcpy(zol->vdev.name, v4l2_dev->name, sizeof(zol->vdev.name));
zol->vdev.v4l2_dev = v4l2_dev;
zol->vdev.fops = &zoltrix_fops;
zol->vdev.ioctl_ops = &zoltrix_ioctl_ops;
zol->vdev.release = video_device_release_empty;
video_set_drvdata(&zol->vdev, zol);
if (video_register_device(&zol->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(v4l2_dev);
release_region(zol->io, 2);
return -EINVAL;
}
v4l2_info(v4l2_dev, "Zoltrix Radio Plus card driver.\n");
return 0;
return isa_register_driver(&zoltrix_driver.driver, ZOLTRIX_MAX);
}
static void __exit zoltrix_exit(void)
{
struct zoltrix *zol = &zoltrix_card;
video_unregister_device(&zol->vdev);
v4l2_device_unregister(&zol->v4l2_dev);
release_region(zol->io, 2);
isa_unregister_driver(&zoltrix_driver.driver);
}
module_init(zoltrix_init);

View File

@ -434,18 +434,7 @@ static struct i2c_driver saa7706h_driver = {
.id_table = saa7706h_id,
};
static __init int saa7706h_init(void)
{
return i2c_add_driver(&saa7706h_driver);
}
static __exit void saa7706h_exit(void)
{
i2c_del_driver(&saa7706h_driver);
}
module_init(saa7706h_init);
module_exit(saa7706h_exit);
module_i2c_driver(saa7706h_driver);
MODULE_DESCRIPTION("SAA7706H Car Radio DSP driver");
MODULE_AUTHOR("Mocean Laboratories");

View File

@ -539,33 +539,7 @@ static struct i2c_driver si470x_i2c_driver = {
.id_table = si470x_i2c_id,
};
/**************************************************************************
* Module Interface
**************************************************************************/
/*
* si470x_i2c_init - module init
*/
static int __init si470x_i2c_init(void)
{
printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n");
return i2c_add_driver(&si470x_i2c_driver);
}
/*
* si470x_i2c_exit - module exit
*/
static void __exit si470x_i2c_exit(void)
{
i2c_del_driver(&si470x_i2c_driver);
}
module_init(si470x_i2c_init);
module_exit(si470x_i2c_exit);
module_i2c_driver(si470x_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);

View File

@ -2106,17 +2106,4 @@ static struct i2c_driver si4713_i2c_driver = {
.id_table = si4713_id,
};
/* Module Interface */
static int __init si4713_module_init(void)
{
return i2c_add_driver(&si4713_i2c_driver);
}
static void __exit si4713_module_exit(void)
{
i2c_del_driver(&si4713_i2c_driver);
}
module_init(si4713_module_init);
module_exit(si4713_module_exit);
module_i2c_driver(si4713_i2c_driver);

View File

@ -215,20 +215,8 @@ static struct i2c_driver tef6862_driver = {
.id_table = tef6862_id,
};
static __init int tef6862_init(void)
{
return i2c_add_driver(&tef6862_driver);
}
static __exit void tef6862_exit(void)
{
i2c_del_driver(&tef6862_driver);
}
module_init(tef6862_init);
module_exit(tef6862_exit);
module_i2c_driver(tef6862_driver);
MODULE_DESCRIPTION("TEF6862 Car Radio Enhanced Selectivity Tuner");
MODULE_AUTHOR("Mocean Laboratories");
MODULE_LICENSE("GPL v2");

View File

@ -266,4 +266,13 @@ config RC_LOOPBACK
To compile this driver as a module, choose M here: the module will
be called rc_loopback.
config IR_GPIO_CIR
tristate "GPIO IR remote control"
depends on RC_CORE
---help---
Say Y if you want to use GPIO based IR Receiver.
To compile this driver as a module, choose M here: the module will
be called gpio-ir-recv.
endif #RC_CORE

View File

@ -26,3 +26,4 @@ obj-$(CONFIG_IR_REDRAT3) += redrat3.o
obj-$(CONFIG_IR_STREAMZAP) += streamzap.o
obj-$(CONFIG_IR_WINBOND_CIR) += winbond-cir.o
obj-$(CONFIG_RC_LOOPBACK) += rc-loopback.o
obj-$(CONFIG_IR_GPIO_CIR) += gpio-ir-recv.o

View File

@ -117,7 +117,7 @@ static u8 fintek_cir_reg_read(struct fintek_dev *fintek, u8 offset)
static void cir_dump_regs(struct fintek_dev *fintek)
{
fintek_config_mode_enable(fintek);
fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR);
fintek_select_logical_dev(fintek, fintek->logical_dev_cir);
pr_reg("%s: Dump CIR logical device registers:\n", FINTEK_DRIVER_NAME);
pr_reg(" * CR CIR BASE ADDR: 0x%x\n",
@ -143,7 +143,7 @@ static int fintek_hw_detect(struct fintek_dev *fintek)
u8 chip_major, chip_minor;
u8 vendor_major, vendor_minor;
u8 portsel, ir_class;
u16 vendor;
u16 vendor, chip;
int ret = 0;
fintek_config_mode_enable(fintek);
@ -176,6 +176,7 @@ static int fintek_hw_detect(struct fintek_dev *fintek)
chip_major = fintek_cr_read(fintek, GCR_CHIP_ID_HI);
chip_minor = fintek_cr_read(fintek, GCR_CHIP_ID_LO);
chip = chip_major << 8 | chip_minor;
vendor_major = fintek_cr_read(fintek, GCR_VENDOR_ID_HI);
vendor_minor = fintek_cr_read(fintek, GCR_VENDOR_ID_LO);
@ -192,6 +193,15 @@ static int fintek_hw_detect(struct fintek_dev *fintek)
fintek->chip_major = chip_major;
fintek->chip_minor = chip_minor;
fintek->chip_vendor = vendor;
/*
* Newer reviews of this chipset uses port 8 instead of 5
*/
if ((chip != 0x0408) || (chip != 0x0804))
fintek->logical_dev_cir = LOGICAL_DEV_CIR_REV2;
else
fintek->logical_dev_cir = LOGICAL_DEV_CIR_REV1;
spin_unlock_irqrestore(&fintek->fintek_lock, flags);
return ret;
@ -200,7 +210,7 @@ static int fintek_hw_detect(struct fintek_dev *fintek)
static void fintek_cir_ldev_init(struct fintek_dev *fintek)
{
/* Select CIR logical device and enable */
fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR);
fintek_select_logical_dev(fintek, fintek->logical_dev_cir);
fintek_cr_write(fintek, LOGICAL_DEV_ENABLE, CIR_CR_DEV_EN);
/* Write allocated CIR address and IRQ information to hardware */
@ -381,7 +391,7 @@ static irqreturn_t fintek_cir_isr(int irq, void *data)
fit_dbg_verbose("%s firing", __func__);
fintek_config_mode_enable(fintek);
fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR);
fintek_select_logical_dev(fintek, fintek->logical_dev_cir);
fintek_config_mode_disable(fintek);
/*
@ -422,7 +432,7 @@ static void fintek_enable_cir(struct fintek_dev *fintek)
fintek_config_mode_enable(fintek);
/* enable the CIR logical device */
fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR);
fintek_select_logical_dev(fintek, fintek->logical_dev_cir);
fintek_cr_write(fintek, LOGICAL_DEV_ENABLE, CIR_CR_DEV_EN);
fintek_config_mode_disable(fintek);
@ -439,7 +449,7 @@ static void fintek_disable_cir(struct fintek_dev *fintek)
fintek_config_mode_enable(fintek);
/* disable the CIR logical device */
fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR);
fintek_select_logical_dev(fintek, fintek->logical_dev_cir);
fintek_cr_write(fintek, LOGICAL_DEV_DISABLE, CIR_CR_DEV_EN);
fintek_config_mode_disable(fintek);
@ -611,7 +621,7 @@ static int fintek_suspend(struct pnp_dev *pdev, pm_message_t state)
fintek_config_mode_enable(fintek);
/* disable cir logical dev */
fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR);
fintek_select_logical_dev(fintek, fintek->logical_dev_cir);
fintek_cr_write(fintek, LOGICAL_DEV_DISABLE, CIR_CR_DEV_EN);
fintek_config_mode_disable(fintek);
@ -634,7 +644,7 @@ static int fintek_resume(struct pnp_dev *pdev)
/* Enable CIR logical device */
fintek_config_mode_enable(fintek);
fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR);
fintek_select_logical_dev(fintek, fintek->logical_dev_cir);
fintek_cr_write(fintek, LOGICAL_DEV_ENABLE, CIR_CR_DEV_EN);
fintek_config_mode_disable(fintek);

View File

@ -88,6 +88,7 @@ struct fintek_dev {
u8 chip_major;
u8 chip_minor;
u16 chip_vendor;
u8 logical_dev_cir;
/* hardware features */
bool hw_learning_capable;
@ -172,7 +173,8 @@ struct fintek_dev {
#define LOGICAL_DEV_ENABLE 0x01
/* Logical device number of the CIR function */
#define LOGICAL_DEV_CIR 0x05
#define LOGICAL_DEV_CIR_REV1 0x05
#define LOGICAL_DEV_CIR_REV2 0x08
/* CIR Logical Device (LDN 0x08) config registers */
#define CIR_CR_COMMAND_INDEX 0x04

Some files were not shown because too many files have changed in this diff Show More