mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-11-20 00:26:39 +08:00
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:
commit
e317234975
@ -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 — Digital Compression and Coding of Continous-Tone
|
||||
Still Images — 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 12M</abbrev>
|
||||
<authorgroup>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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> </entry>
|
||||
<entry>enum 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> </entry>
|
||||
<entry>Automatically determines the best playback mode.</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_STEREO</constant> </entry>
|
||||
<entry>Stereo playback.</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_LEFT</constant> </entry>
|
||||
<entry>Left channel playback.</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_RIGHT</constant> </entry>
|
||||
<entry>Right channel playback.</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_MONO</constant> </entry>
|
||||
<entry>Mono playback.</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><constant>V4L2_MPEG_AUDIO_DEC_PLAYBACK_SWAPPED_STEREO</constant> </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> </entry>
|
||||
<entry>enum 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> </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> </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> </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>
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
256
Documentation/DocBook/media/v4l/vidioc-decoder-cmd.xml
Normal file
256
Documentation/DocBook/media/v4l/vidioc-decoder-cmd.xml
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
178
Documentation/video4linux/fimc.txt
Normal file
178
Documentation/video4linux/fimc.txt
Normal 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).
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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) \
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 &&
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
ret = fixpt_div32(freq / 1000, priv->config->osc_clk * 1000,
|
||||
"ient, &fraction);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/* 20-bit fraction */
|
||||
fraction >>= 12;
|
||||
|
@ -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,
|
||||
},
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
957
drivers/media/dvb/dvb-usb/az6007.c
Normal file
957
drivers/media/dvb/dvb-usb/az6007.c
Normal 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");
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
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");
|
||||
|
@ -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,9 +402,7 @@ 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;
|
||||
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,11 +1039,12 @@ static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap)
|
||||
int ret = 0;
|
||||
|
||||
st->i2c_talk_onoff = 1;
|
||||
|
||||
switch (le16_to_cpu(adap->dev->udev->descriptor.idProduct)) {
|
||||
case 0x1122:
|
||||
case 0x1120:
|
||||
st->i2c_gate = 4;
|
||||
adap->fe_adap[0].fe = dvb_attach(tda10086_attach, &tda10086_config,
|
||||
&adap->dev->i2c_adap);
|
||||
|
||||
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;
|
||||
@ -973,12 +1055,12 @@ static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap)
|
||||
dvb_usb_lme2510_firmware = TUNER_LG;
|
||||
ret = lme_firmware_switch(adap->dev->udev, 1);
|
||||
}
|
||||
goto end;
|
||||
break;
|
||||
}
|
||||
|
||||
st->i2c_gate = 4;
|
||||
adap->fe_adap[0].fe = dvb_attach(stv0299_attach, &sharp_z0194_config,
|
||||
&adap->dev->i2c_adap);
|
||||
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;
|
||||
@ -989,12 +1071,13 @@ static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap)
|
||||
dvb_usb_lme2510_firmware = TUNER_S0194;
|
||||
ret = lme_firmware_switch(adap->dev->udev, 1);
|
||||
}
|
||||
goto end;
|
||||
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;
|
||||
@ -1005,13 +1088,33 @@ static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap)
|
||||
dvb_usb_lme2510_firmware = TUNER_S7395;
|
||||
ret = lme_firmware_switch(adap->dev->udev, 1);
|
||||
}
|
||||
} else {
|
||||
info("DM04 Not Supported");
|
||||
break;
|
||||
}
|
||||
case 0x22f0:
|
||||
st->i2c_gate = 5;
|
||||
adap->fe_adap[0].fe = dvb_attach(m88rs2000_attach,
|
||||
&m88rs2000_config, &adap->dev->i2c_adap);
|
||||
|
||||
if (adap->fe_adap[0].fe) {
|
||||
info("FE Found M88RS2000");
|
||||
st->i2c_tuner_gate_w = 5;
|
||||
st->i2c_tuner_gate_r = 5;
|
||||
st->i2c_tuner_addr = 0xc0;
|
||||
st->tuner_config = TUNER_RS2000;
|
||||
adap->fe_adap[0].fe->ops.read_signal_strength =
|
||||
dm04_rs2000_read_signal_strength;
|
||||
adap->fe_adap[0].fe->ops.read_snr =
|
||||
dm04_rs2000_read_snr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (adap->fe_adap[0].fe == NULL) {
|
||||
info("DM04/QQBOX Not Powered up or not Supported");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
|
||||
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");
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
982
drivers/media/dvb/dvb-usb/rtl28xxu.c
Normal file
982
drivers/media/dvb/dvb-usb/rtl28xxu.c
Normal 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");
|
264
drivers/media/dvb/dvb-usb/rtl28xxu.h
Normal file
264
drivers/media/dvb/dvb-usb/rtl28xxu.h
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,10 +29,13 @@ 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;
|
||||
|
||||
u8 mpeg_out_clk_strength;
|
||||
int chunk_size;
|
||||
|
||||
const char *microcode_name;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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},
|
||||
|
@ -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*/
|
||||
int ret = 0;
|
||||
if (state->config->read_slevel) {
|
||||
if (state->it913x_status & FE_HAS_SIGNAL)
|
||||
ret = (ret * 0xff) / 0x64;
|
||||
else
|
||||
ret = 0x0;
|
||||
ret |= ret << 0x8;
|
||||
*strength = ret;
|
||||
return 0;
|
||||
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");
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
904
drivers/media/dvb/frontends/m88rs2000.c
Normal file
904
drivers/media/dvb/frontends/m88rs2000.c
Normal 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");
|
||||
|
66
drivers/media/dvb/frontends/m88rs2000.h
Normal file
66
drivers/media/dvb/frontends/m88rs2000.h
Normal 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 */
|
562
drivers/media/dvb/frontends/rtl2830.c
Normal file
562
drivers/media/dvb/frontends/rtl2830.c
Normal 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 = ®,
|
||||
}, {
|
||||
.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");
|
97
drivers/media/dvb/frontends/rtl2830.h
Normal file
97
drivers/media/dvb/frontends/rtl2830.h
Normal 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 */
|
57
drivers/media/dvb/frontends/rtl2830_priv.h
Normal file
57
drivers/media/dvb/frontends/rtl2830_priv.h
Normal 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 */
|
@ -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)) {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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,6 +1066,7 @@ static void __devexit pt1_remove(struct pci_dev *pdev)
|
||||
pt1 = pci_get_drvdata(pdev);
|
||||
regs = pt1->regs;
|
||||
|
||||
if (pt1->kthread)
|
||||
kthread_stop(pt1->kthread);
|
||||
pt1_cleanup_tables(pt1);
|
||||
pt1_cleanup_frontends(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:
|
||||
|
@ -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>");
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
#define GEMTEK_MAX 4
|
||||
|
||||
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;
|
||||
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(io, int, 0444);
|
||||
MODULE_PARM_DESC(io, "Force I/O port for the GemTek Radio card if automatic "
|
||||
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 = >->isa;
|
||||
int i, bit, q, mute;
|
||||
|
||||
mutex_lock(>->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(>->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 ? >->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(>->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(>->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(>->lock);
|
||||
|
||||
i = inb_p(gt->io);
|
||||
outb_p(i >> 5, gt->io);
|
||||
udelay(SHORT_DELAY);
|
||||
|
||||
mutex_unlock(>->lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get signal strength (= stereo status).
|
||||
*/
|
||||
static inline int gemtek_getsigstr(struct gemtek *gt)
|
||||
{
|
||||
int sig;
|
||||
|
||||
mutex_lock(>->lock);
|
||||
sig = inb_p(gt->io) & GEMTEK_NS ? 0 : 1;
|
||||
mutex_unlock(>->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(>->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(>->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(>->lock);
|
||||
gt->verified = port;
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Automatic probing for card.
|
||||
*/
|
||||
static int gemtek_probe(struct gemtek *gt)
|
||||
{
|
||||
struct v4l2_device *v4l2_dev = >->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 = >->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(>->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(>->vdev, gt);
|
||||
|
||||
/* Set defaults */
|
||||
gt->lastfreq = GEMTEK_LOWFREQ;
|
||||
gt->bu2614data = 0;
|
||||
|
||||
if (initmute)
|
||||
gemtek_mute(gt);
|
||||
|
||||
if (video_register_device(>->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 = >->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(>->vdev);
|
||||
v4l2_device_unregister(>->v4l2_dev);
|
||||
release_region(gt->io, 1);
|
||||
isa_unregister_driver(&gemtek_driver.driver);
|
||||
}
|
||||
|
||||
module_init(gemtek_init);
|
||||
|
340
drivers/media/radio/radio-isa.c
Normal file
340
drivers/media/radio/radio-isa.c
Normal 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);
|
105
drivers/media/radio/radio-isa.h
Normal file
105
drivers/media/radio/radio-isa.h
Normal 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
|
427
drivers/media/radio/radio-keene.c
Normal file
427
drivers/media/radio/radio-keene.c
Normal 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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user